path-handling.scm 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. (define-module (path-handling)
  2. #:export (path-split
  3. path-join
  4. absolute-path?
  5. absolute-path
  6. subpath?
  7. file-extension
  8. upwards-navigating-path?))
  9. (use-modules
  10. (srfi srfi-1))
  11. ;;;
  12. ;;; HELPERS
  13. ;;;
  14. ;;; LOGGING
  15. (define displayln
  16. (lambda* (#:key (output-port (current-output-port)) (verbose #t) . msgs)
  17. (when verbose
  18. (display (string-append
  19. (string-join
  20. (map (lambda (msg) (simple-format #f "~a" msg)) msgs)
  21. " ") "\n")
  22. output-port))))
  23. ;; alias for displayln
  24. (define debug displayln)
  25. ;;; STRINGS
  26. (use-modules
  27. (ice-9 exceptions))
  28. (define char->string
  29. (λ (c)
  30. (list->string
  31. (list c))))
  32. (define string->char
  33. (λ (str)
  34. "Convert a string, which has only one single character
  35. into a character. This is useful, because some functions
  36. expect a characters as input instead of a string."
  37. (cond
  38. [(= (string-length str) 1)
  39. (car (string->list str))]
  40. [else
  41. (raise-exception
  42. (make-exception
  43. (make-non-continuable-error)
  44. (make-exception-with-message "trying to convert string of more than 1 character to char")
  45. (make-exception-with-irritants (list str))
  46. (make-exception-with-origin 'string->char)))])))
  47. #;(define has-prefix?
  48. (λ (str prefix)
  49. (= (string-prefix-length str prefix)
  50. (string-length prefix))))
  51. ;;; LISTS
  52. (define list-prefix?
  53. (λ (lst lst-prefix)
  54. (cond
  55. [(null? lst-prefix) #t]
  56. [(null? lst) #f]
  57. [else
  58. (cond
  59. [(equal? (car lst) (car lst-prefix))
  60. (list-prefix? (cdr lst) (cdr lst-prefix))]
  61. [else #f])])))
  62. ;;;
  63. ;;; PATH FUNCTIONS
  64. ;;;
  65. (define absolute-path?
  66. (λ (path)
  67. "Check, whether the given path is an absolute path."
  68. ;; Guile already offers a function for this, but it is a
  69. ;; little bit strangely named. We only give it an alias.
  70. (absolute-file-name? path)))
  71. (define path-join
  72. (λ (path1 . other-path-parts)
  73. "Join paths using the system preferred separator."
  74. (debug "joining path parts:" (cons path1 other-path-parts))
  75. (fold
  76. (λ (p2 p1)
  77. (cond
  78. [(null? p2) p1]
  79. [(absolute-path? p2) p2]
  80. [else
  81. (let ([dir-sep (car (string->list file-name-separator-string))])
  82. (string-append
  83. ;; Remove any trailing separators to make sure
  84. ;; there is only one separator, when the paths
  85. ;; are concattenated.
  86. (string-trim-right p1 (λ (char) (char=? char dir-sep)))
  87. ;; Concat the paths with the separator in the
  88. ;; middle.
  89. (char->string dir-sep)
  90. ;; We already know p2 is not an absolute path.
  91. p2))]))
  92. ""
  93. (cons path1 other-path-parts))))
  94. (define path-split
  95. (λ (path)
  96. "Split a path by the preferred separator of the system."
  97. (string-split path (string->char file-name-separator-string))))
  98. (define absolute-path
  99. (lambda* (path
  100. #:key
  101. (working-directory
  102. (dirname (or (current-filename)
  103. (canonicalize-path ".")))))
  104. (cond
  105. [(absolute-path? path) path]
  106. [else
  107. ;; In case the path is not absolute already, we look
  108. ;; for it in the current directory.
  109. (let next-parent ([path-parts
  110. (path-split
  111. (path-join working-directory path))])
  112. (debug "current path-parts:" path-parts)
  113. (cond
  114. ;; WARNING: This part is not OS independent. An
  115. ;; absolute path does not have to start with the
  116. ;; separator string in all OS.
  117. [(null? path-parts) file-name-separator-string]
  118. [else
  119. (let ([path-str (apply path-join path-parts)])
  120. (debug "current path-str:" path-str)
  121. (with-exception-handler
  122. (λ (exception)
  123. (debug "an exception was raised:" exception)
  124. (cond
  125. [(and (eq? (exception-kind exception)
  126. 'system-error)
  127. (string=? (car (exception-irritants exception))
  128. "No such file or directory"))
  129. ;; Try to check if the path to the
  130. ;; parent directory exists and is an
  131. ;; absolute path instead.
  132. (debug "the exception is about the path not existing")
  133. (apply path-join
  134. (list (next-parent (drop-right path-parts 1))
  135. (last path-parts)))]
  136. [else
  137. (debug "unexpected exception:" exception)]))
  138. (λ ()
  139. (debug "trying to canonicalize-path" path-str)
  140. (canonicalize-path path-str))
  141. #:unwind? #t))]))])))
  142. (define file-extension
  143. (λ (path)
  144. (cond
  145. ;; An empty string is given, there can be no file
  146. ;; extension.
  147. [(string-null? path) #f]
  148. [else
  149. (let ([path-last-part (last (path-split path))]
  150. [file-extension-separator #\.])
  151. (let ([last-part-split (string-split path-last-part file-extension-separator)])
  152. (cond
  153. ;; If the split did not produce more than one
  154. ;; part, then the split character was not found
  155. ;; and so the path does not have a file
  156. ;; extension.
  157. [(= (length last-part-split) 1) #f]
  158. [else
  159. (let ([perhaps-file-extension (last last-part-split)])
  160. ;; A file name could end with a "." and that
  161. ;; would produce an empty string as file
  162. ;; extension. This procedure does not consider
  163. ;; the empty string to be a file extension.
  164. (if (string-null? perhaps-file-extension)
  165. #f
  166. perhaps-file-extension))])))])))
  167. (define subpath?
  168. (λ (path parent-path)
  169. "Check, whether a path is a sub path of a given parent
  170. path."
  171. (cond
  172. ;; We want to avoid complicated paths for now and
  173. ;; simply claim, that upwards navigating paths are not
  174. ;; in any parent path for security reasons.
  175. [(upwards-navigating-path? path) #f]
  176. [else
  177. (let ([path-parts (path-split path)]
  178. [parent-path-parts (path-split parent-path)])
  179. (list-prefix? (path-split path)
  180. (path-split parent-path)))])))
  181. (define upwards-navigating-path?
  182. (λ (path)
  183. "Check, whether the given path contains anything, which
  184. could be used to navigate upwards in the file system
  185. tree. This is useful, when trying to make sure, that a path
  186. does not point to resources, which the context shall have no
  187. access to."
  188. (cond
  189. ;; contains sub shell
  190. [(string-contains path "`") #t]
  191. ;; contains upwards navigation
  192. [(string-contains path "/../") #t]
  193. ;; ends with 2 or more dots
  194. [(>= (string-suffix-length path "..") 2) #t]
  195. ;; contains tilde
  196. [(string-contains path "~") #t]
  197. ;; contains variables
  198. [(string-contains path "$") #t]
  199. ;; otherwise seems to be safe
  200. [else #f])))