Hi Guile Users! In my explorations into making examples for web development, I came across the question of how to get an absolute path from any given path. This is useful for example when checking, whether a path points to something inside a static assets directory, or perhaps sneakily tries to escape that and access things it should not.
I found in Guile's manual the function (canonicalize-path path). However, this function has one problem, which makes it not sufficient on its own: It raises an exception, when a path given points to something that does not exist. I would like to have a function, that gives me the absolute path of any path I give as argument, not only for existing paths. So i went ahead and wrote the following code (https://notabug.org/ZelphirKaltstahl/guile-examples/src/14a76a6aee18a900ac9b9de2b79ede239f8cf9f0/file-system/path-handling.scm): ~~~~START~~~~ (define-module (path-handling) #:export (path-split path-join absolute-path? absolute-path)) (use-modules (srfi srfi-1)) ;;; ;;; HELPERS ;;; ;;; LOGGING (define displayln (lambda* (#:key (output-port (current-output-port)) (verbose #t) . msgs) (when verbose (display (string-append (string-join (map (lambda (msg) (simple-format #f "~a" msg)) msgs) " ") "\n") output-port)))) ;; alias for displayln (define debug displayln) ;;; STRINGS (use-modules (ice-9 exceptions)) (define char->string (λ (c) (list->string (list c)))) (define string->char (λ (str) "Convert a string, which has only one single character into a character. This is useful, because some functions expect a characters as input instead of a string." (cond [(= (string-length str) 1) (car (string->list str))] [else (raise-exception (make-exception (make-non-continuable-error) (make-exception-with-message "trying to convert string of more than 1 character to char") (make-exception-with-irritants (list str)) (make-exception-with-origin 'string->char)))]))) #;(define has-prefix? (λ (str prefix) (= (string-prefix-length str prefix) (string-length prefix)))) ;;; LISTS (define list-prefix? (λ (lst lst-prefix) (cond [(null? lst-prefix) #t] [(null? lst) #f] [else (cond [(equal? (car lst) (car lst-prefix)) (list-prefix? (cdr lst) (cdr lst-prefix))] [else #f])]))) ;;; ;;; PATH FUNCTIONS ;;; (define absolute-path? (λ (path) "Check, whether the given path is an absolute path." ;; Guile already offers a function for this, but it is a ;; little bit strangely named. We only give it an alias. (absolute-file-name? path))) (define path-join (λ (path1 . other-path-parts) "Join paths using the system preferred separator." (debug "joining path parts:" (cons path1 other-path-parts)) (fold (λ (p2 p1) (cond [(null? p2) p1] [(absolute-path? p2) p2] [else (let ([dir-sep (car (string->list file-name-separator-string))]) (string-append ;; Remove any trailing separators to make sure ;; there is only one separator, when the paths ;; are concattenated. (string-trim-right p1 (λ (char) (char=? char dir-sep))) ;; Concat the paths with the separator in the ;; middle. (char->string dir-sep) ;; We already know p2 is not an absolute path. p2))])) "" (cons path1 other-path-parts)))) (define path-split (λ (path) "Split a path by the preferred separator of the system." (string-split path (string->char file-name-separator-string)))) (define absolute-path (lambda* (path #:key (working-directory (dirname (or (current-filename) (canonicalize-path "."))))) (cond [(absolute-path? path) path] [else ;; In case the path is not absolute already, we look ;; for it in the current directory. (let next-parent ([path-parts (path-split (path-join working-directory path))]) (debug "current path-parts:" path-parts) (cond ;; WARNING: This part is not OS independent. An ;; absolute path does not have to start with the ;; separator string in all OS. [(null? path-parts) file-name-separator-string] [else (let ([path-str (apply path-join path-parts)]) (debug "current path-str:" path-str) (with-exception-handler (λ (exception) (debug "an exception was raised:" exception) (cond [(and (eq? (exception-kind exception) 'system-error) (string=? (car (exception-irritants exception)) "No such file or directory")) ;; Try to check if the path to the ;; parent directory exists and is an ;; absolute path instead. (debug "the exception is about the path not existing") (apply path-join (list (next-parent (drop-right path-parts 1)) (last path-parts)))] [else (debug "unexpected exception:" exception)])) (λ () (debug "trying to canonicalize-path" path-str) (canonicalize-path path-str)) #:unwind? #t))]))]))) ~~~~~END~~~~~ But then I thought about it and realized, that this is not OS independent. Not every OS must have the convention of starting absolute paths with the separator string. So I wonder: Is there a function in Guile, which translates a path like "/a/b/c" into an equivalent on the current OS? I think in Python 3 the rule is for example to always use "/" as a separator and Python will take care of translating that to the underlying OS. Not sure how it handles making absolute paths, but I could imagine, that one could use this "slash first means absolute path" kind of path language and then Guile internally translates that to the underlying OS' absolute path. Regards, Zelphir -- repositories: https://notabug.org/ZelphirKaltstahl