1010-- | Implementation of the @dhall to-directory-tree@ subcommand
1111module Dhall.DirectoryTree
1212 ( -- * Filesystem
13- toDirectoryTree
13+ DirectoryTreeOptions (.. )
14+ , defaultDirectoryTreeOptions
15+ , toDirectoryTree
1416 , FilesystemError (.. )
1517
1618 -- * Low-level types and functions
@@ -38,7 +40,7 @@ import Dhall.Syntax
3840 , RecordField (.. )
3941 , Var (.. )
4042 )
41- import System.FilePath ((</>) )
43+ import System.FilePath ((</>) , isAbsolute , splitDirectories , takeDirectory )
4244import System.PosixCompat.Types (FileMode , GroupID , UserID )
4345
4446import qualified Control.Exception as Exception
@@ -63,11 +65,31 @@ import qualified System.Posix.User as Posix
6365#endif
6466import qualified System.PosixCompat.Files as Posix
6567
68+ {- | Options affecting the interpretation of a directory tree specification.
69+ -}
70+ data DirectoryTreeOptions = DirectoryTreeOptions
71+ { allowAbsolute :: Bool
72+ -- ^ Whether to allow absolute paths in the spec.
73+ , allowParent :: Bool
74+ -- ^ Whether to allow ".." in the spec.
75+ , allowSeparators :: Bool
76+ -- ^ Whether to allow path separators in file names.
77+ }
78+
79+ -- | The default 'DirectoryTreeOptions'. All flags are set to 'False'.
80+ defaultDirectoryTreeOptions :: DirectoryTreeOptions
81+ defaultDirectoryTreeOptions = DirectoryTreeOptions
82+ { allowAbsolute = False
83+ , allowParent = False
84+ , allowSeparators = False
85+ }
86+
6687{-| Attempt to transform a Dhall record into a directory tree where:
6788
6889 * Records are translated into directories
6990
70- * @Map@s are translated into directory trees, if allowSeparators option is enabled
91+ * @Map@s are translated into directories or whole directory trees, if
92+ `allowSeparators` option is enabled
7193
7294 * @Text@ values or fields are translated into files
7395
@@ -122,10 +144,11 @@ import qualified System.PosixCompat.Files as Posix
122144 /Construction of directory trees from maps/
123145
124146 In @Map@s, the keys specify paths relative to the work dir.
125- Only forward slashes (@/@) must be used as directory separators.
126- They will be automatically transformed on Windows.
127- Absolute paths (starting with @/@) and parent directory segments (@..@)
128- are prohibited for security concerns.
147+ Absolute paths and parent directory segments (@..@) are by default
148+ prohibited for security concerns, but these checks can be disabled using
149+ `allowAbsolute` option field (or the @--allow-absolute-paths@ CLI flag) and
150+ `allowParent` option field (or the @--allow-parent-directory@ CLI flag)
151+ respectively.
129152
130153 /Advanced construction of directory trees/
131154
@@ -180,11 +203,11 @@ import qualified System.PosixCompat.Files as Posix
180203 that cannot be converted as-is.
181204-}
182205toDirectoryTree
183- :: Bool -- ^ Whether to allow path separators in file names or not
206+ :: DirectoryTreeOptions
184207 -> FilePath
185208 -> Expr Void Void
186209 -> IO ()
187- toDirectoryTree allowSeparators path expression = case expression of
210+ toDirectoryTree opts path expression = case expression of
188211 RecordLit keyValues ->
189212 Map. unorderedTraverseWithKey_ process $ recordFieldValue <$> keyValues
190213
@@ -200,10 +223,10 @@ toDirectoryTree allowSeparators path expression = case expression of
200223 Text.IO. writeFile path text
201224
202225 Some value ->
203- toDirectoryTree allowSeparators path value
226+ toDirectoryTree opts path value
204227
205228 App (Field (Union _) _) value -> do
206- toDirectoryTree allowSeparators path value
229+ toDirectoryTree opts path value
207230
208231 App None _ ->
209232 return ()
@@ -214,7 +237,7 @@ toDirectoryTree allowSeparators path expression = case expression of
214237 Lam _ _ (Lam _ _ _) -> do
215238 entries <- decodeDirectoryTree expression
216239
217- processFilesystemEntryList allowSeparators path entries
240+ processFilesystemEntryList opts path entries
218241
219242 _ ->
220243 die
@@ -230,31 +253,24 @@ toDirectoryTree allowSeparators path expression = case expression of
230253 empty
231254
232255 process key value = do
256+ let
257+ keyPath = Text. unpack key
258+ keyPathSegments = splitDirectories keyPath
259+ path' = path </> keyPath
260+
233261 -- Fail if path is absolute, which is a security risk.
234- when (FilePath. isAbsolute (Text. unpack key)) die
235-
236- let keyPathSegments =
237- fmap Text. unpack $ Text. splitOn " /" key
262+ when (not (allowAbsolute opts) && isAbsolute keyPath) die
238263
239264 -- Fail if path contains attempts to go to container directory,
240265 -- which is a security risk.
241- when (elem " .." keyPathSegments) die
242-
243- (dirPathSegments, fileName) <- case reverse keyPathSegments of
244- h : t ->
245- return (reverse t, h)
246- _ ->
247- die
248-
249- -- Fail if separators are not allowed by the option but we have directories in the path.
250- when (not allowSeparators && not (null dirPathSegments)) die
266+ when (not (allowParent opts) && " .." `elem` keyPathSegments) die
251267
252- let dirPath =
253- Foldable. foldl' (</>) path dirPathSegments
268+ -- Fail if separators are not allowed by the option.
269+ when ( not (allowSeparators opts) && length keyPathSegments > 1 ) die
254270
255- Directory. createDirectoryIfMissing True dirPath
271+ Directory. createDirectoryIfMissing (allowSeparators opts) (takeDirectory path')
256272
257- toDirectoryTree allowSeparators (dirPath </> fileName) value
273+ toDirectoryTree opts path' value
258274
259275 die = Exception. throwIO FilesystemError {.. }
260276 where
@@ -351,9 +367,9 @@ processEntryWith path entry f = do
351367 applyMetadata entry path'
352368
353369-- | Process a list of `FilesystemEntry`s.
354- processFilesystemEntryList :: Bool -> FilePath -> Seq FilesystemEntry -> IO ()
355- processFilesystemEntryList allowSeparators path = Foldable. traverse_
356- (processFilesystemEntry allowSeparators path)
370+ processFilesystemEntryList :: DirectoryTreeOptions -> FilePath -> Seq FilesystemEntry -> IO ()
371+ processFilesystemEntryList opts path = Foldable. traverse_
372+ (processFilesystemEntry opts path)
357373
358374-- | Does this entry have some metadata set?
359375hasMetadata :: Entry a -> Bool
0 commit comments