123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- (define-module (path-handling)
- #:export (absolute-path
- absolute-path?
- path-join
- path-split
- file-extension
- subpath?
- complex-path?
- get-current-directory))
- (use-modules
- ;; for fold, last
- (srfi srfi-1)
- (ice-9 exceptions)
- ;; custom modules
- (string-utils)
- (list-utils)
- ((logging) #:prefix log:)
- (file-system))
- (define path-join
- (λ (path1 . other-path-parts)
- "Join paths using the system preferred separator."
- (let ([dir-sep (car (string->list file-name-separator-string))])
- (fold
- (λ (current-elem accumulated)
- (cond
- ;; If a later path is an absolute path, then it is
- ;; used as the new accumulated value. Basically a
- ;; later absolute path overrides the already
- ;; accumulated path, because it cannot be joined in
- ;; a useful way.
- [(absolute-path? current-elem) current-elem]
- ;; We know, that the current-elem is not an absolute
- ;; path and so it can be usefully joined with the
- ;; already accumulated path.
- [else
- ;; If the first element is the empty string, then
- ;; we should make an absolute path. We know the
- ;; first element by looking at what is already
- ;; accumulated. If the accumulated path is also
- ;; still empty, then we are at the beginning of
- ;; path parts.
- (cond
- ;; Are we at the beginning?
- [(string-null? accumulated)
- (cond
- ;; Is the first element the empty string? Then
- ;; make an absolute path.
- ;; NOTE: WARNING: This is not OS independent!
- ;; Absolute paths do not have to start with
- ;; the directory separator on all OS.
- [(string-null? current-elem) (char->string dir-sep)]
- ;; Otherwise use the first element as
- ;; accumulated path and go on with the rest.
- [else current-elem])]
- ;; If we are not at the beginning, then the path
- ;; cannot become absolute any longer.
- [else
- (string-append
- ;; Remove any trailing separators to make sure
- ;; there is only one separator, when the paths
- ;; are concattenated.
- (string-trim-right accumulated
- (λ (char)
- (char=? char dir-sep)))
- ;; Concat the paths with the separator in the
- ;; middle.
- (char->string dir-sep)
- ;; We already know current-elem is not an
- ;; absolute path.
- current-elem)])]))
- ""
- (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
- ;; We give the working directory as a keyword
- ;; argument, so that this procedure does not
- ;; need to make the decision on its own and the
- ;; resulting absolute paths for non-absolute
- ;; paths do not necessarily depend on where
- ;; exactly this module is located in the file
- ;; system.
- #:key
- (working-directory (get-current-directory)))
- "Return the absolute path of a given absolute or non-absolute path."
- (cond
- ;; If the path is already an absolute path, simply
- ;; return that.
- [(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))])
- (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)]
- [canon-abs-path (false-if-exception (canonicalize-path path-str))])
- (cond
- [(not canon-abs-path)
- (apply path-join
- (list (next-parent (drop-right path-parts 1))
- (last path-parts)))]
- [else
- canon-abs-path]))]))])))
- (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, as it can be used for
- ;; files and directories, not only for files. We only
- ;; give it an alias.
- (absolute-file-name? path)))
- (define file-extension
- (λ (path)
- "Get the file extension of the given path or #f if there
- is no file extension."
- (cond
- ;; An empty string is given, there can be no file
- ;; extension.
- [(string-null? path) #f]
- [else
- (let ([path-last-part (basename path)]
- [file-extension-separator #\.])
- (let ([last-part-split (string-split path-last-part file-extension-separator)])
- (cond
- ;; If the split did not produce more than one
- ;; part, then the split character was not found
- ;; and so the path does not have a file
- ;; extension.
- [(= (length last-part-split) 1) #f]
- [else
- (let ([perhaps-file-extension (last last-part-split)])
- ;; A file name could end with a "." and that
- ;; would produce an empty string as file
- ;; extension. This procedure does not consider
- ;; the empty string to be a file extension.
- (if (string-null? perhaps-file-extension)
- #f
- perhaps-file-extension))])))])))
- (define subpath?
- (λ (path parent-path)
- "Check, whether a path is a sub path of a given parent
- path."
- (cond
- ;; We want to avoid complicated paths for now and
- ;; simply claim, that upwards navigating paths are not
- ;; in any parent path for security reasons.
- [(complex-path? path) #f]
- [else
- (let ([path-parts (path-split path)]
- [parent-path-parts (path-split parent-path)])
- (list-prefix? (path-split path)
- (path-split parent-path)))])))
- (define complex-path?
- (λ (path)
- "Check, whether the given path contains anything, which
- could be used to navigate upwards in the file system tree or
- is in any way complex.
- This is useful, when trying to make sure, that a path does
- not point to resources, which the context shall have no
- access to."
- (cond
- ;; contains sub shell
- [(string-contains path "`") #t]
- ;; contains upwards navigation
- [(string-contains path "/../") #t]
- ;; ends with 2 or more dots (a file could be named 3 or more dots)
- [(>= (string-suffix-length path "...") 3) #t]
- ;; contains tilde
- [(string-contains path "~") #t]
- ;; contains variables
- [(string-contains path "$") #t]
- ;; otherwise seems to be safe
- [else #f])))
- (define get-current-directory
- (λ ()
- (dirname
- (or (current-filename)
- (canonicalize-path ".")))))
|