never executed always true always false
1 module PureClaw.Handles.File
2 ( -- * Handle type
3 FileHandle (..)
4 -- * Implementations
5 , mkFileHandle
6 , mkNoOpFileHandle
7 ) where
8
9 import Data.ByteString (ByteString)
10 import Data.ByteString qualified as BS
11 import Data.Either
12 import System.Directory
13 import System.FilePath
14
15 import PureClaw.Core.Types
16 import PureClaw.Security.Path
17
18 -- | File system capability. Functions that only receive a 'FileHandle'
19 -- cannot shell out or access the network — they can only read and write
20 -- files within the workspace.
21 --
22 -- All operations require a 'SafePath', which guarantees the path has been
23 -- validated against the workspace root and blocked-path list.
24 data FileHandle = FileHandle
25 { _fh_readFile :: SafePath -> IO ByteString
26 , _fh_writeFile :: SafePath -> ByteString -> IO ()
27 , _fh_listDir :: SafePath -> IO [SafePath]
28 }
29
30 -- | Real file handle that performs actual filesystem operations.
31 -- 'listDir' validates each child entry through 'mkSafePath', filtering
32 -- out any blocked paths (e.g. @.env@).
33 mkFileHandle :: WorkspaceRoot -> FileHandle
34 mkFileHandle root = FileHandle
35 { _fh_readFile = BS.readFile . getSafePath
36 , _fh_writeFile = BS.writeFile . getSafePath
37 , _fh_listDir = \sp -> do
38 let dir = getSafePath sp
39 entries <- listDirectory dir
40 results <- mapM (mkSafePath root . (dir </>)) entries
41 pure (rights results)
42 }
43
44 -- | No-op file handle. Read returns empty, write is silent, list returns empty.
45 mkNoOpFileHandle :: FileHandle
46 mkNoOpFileHandle = FileHandle
47 { _fh_readFile = \_ -> pure BS.empty
48 , _fh_writeFile = \_ _ -> pure ()
49 , _fh_listDir = \_ -> pure []
50 }