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,16 +203,16 @@ 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 options path expression = case expression of
188211 RecordLit keyValues ->
189212 Map. unorderedTraverseWithKey_ process $ recordFieldValue <$> keyValues
190213
191214 ListLit (Just (App List (Record [ (" mapKey" , recordFieldValue -> Text ), (" mapValue" , _) ]))) [] ->
192- Directory. createDirectoryIfMissing allowSeparators path
215+ Directory. createDirectoryIfMissing ( allowSeparators options) path
193216
194217 ListLit _ records
195218 | not (null records)
@@ -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 options path value
204227
205228 App (Field (Union _) _) value -> do
206- toDirectoryTree allowSeparators path value
229+ toDirectoryTree options path value
207230
208231 App None _ ->
209232 return ()
@@ -214,7 +237,8 @@ toDirectoryTree allowSeparators path expression = case expression of
214237 Lam _ _ (Lam _ _ _) -> do
215238 entries <- decodeDirectoryTree expression
216239
217- processFilesystemEntryList allowSeparators path entries
240+ -- TODO: Support allowAbsolute and allowParent as well ?
241+ processFilesystemEntryList (allowSeparators options) path entries
218242
219243 _ ->
220244 die
@@ -230,31 +254,24 @@ toDirectoryTree allowSeparators path expression = case expression of
230254 empty
231255
232256 process key value = do
257+ let
258+ keyPath = Text. unpack key
259+ keyPathSegments = splitDirectories keyPath
260+ path' = path </> keyPath
261+
233262 -- 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
263+ when (not (allowAbsolute options) && isAbsolute keyPath) die
238264
239265 -- Fail if path contains attempts to go to container directory,
240266 -- 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
267+ when (not (allowParent options) && " .." `elem` keyPathSegments) die
251268
252- let dirPath =
253- Foldable. foldl' (</>) path dirPathSegments
269+ -- Fail if separators are not allowed by the option.
270+ when ( not (allowSeparators options) && length keyPathSegments > 1 ) die
254271
255- Directory. createDirectoryIfMissing True dirPath
272+ Directory. createDirectoryIfMissing (allowSeparators options) (takeDirectory path')
256273
257- toDirectoryTree allowSeparators (dirPath </> fileName) value
274+ toDirectoryTree options path' value
258275
259276 die = Exception. throwIO FilesystemError {.. }
260277 where
0 commit comments