11{-# LANGUAGE TypeApplications #-}
22{-# LANGUAGE BangPatterns #-}
3+ {-# LANGUAGE ViewPatterns #-}
4+ {-# LANGUAGE QuasiQuotes #-}
35
46module System.File.OsPath.Internal where
57
68
79import qualified System.File.Platform as P
810
9- import Prelude ((.) , ($) , String , IO , ioError , pure , either , const , flip , Maybe (.. ), fmap , (<$>) , id , Bool (.. ), FilePath , (++) , return , show , (>>=) )
11+ import Prelude ((.) , ($) , String , IO , ioError , pure , either , const , flip , Maybe (.. ), fmap , (<$>) , id , Bool (.. ), FilePath , (++) , return , show , (>>=) , (==) , otherwise , errorWithoutStackTrace )
1012import GHC.IO (catchException )
1113import GHC.IO.Exception (IOException (.. ))
1214import GHC.IO.Handle (hClose_help )
@@ -18,11 +20,15 @@ import Control.DeepSeq (force)
1820import Control.Exception (SomeException , try , evaluate , mask , onException )
1921import System.IO (IOMode (.. ), hSetBinaryMode , hClose )
2022import System.IO.Unsafe (unsafePerformIO )
23+ import System.OsString (osstr )
2124import System.OsPath as OSP
2225import System.OsString.Internal.Types
2326
2427import qualified Data.ByteString as BS
2528import qualified Data.ByteString.Lazy as BSL
29+ import qualified System.OsString as OSS
30+ import System.Posix.Types (CMode )
31+ import GHC.Base (failIO )
2632
2733-- | Like 'openFile', but open the file in binary mode.
2834-- On Windows, reading a file in text mode (which is the default)
@@ -127,6 +133,56 @@ openFileWithCloseOnExec osfp iomode = augmentError "openFileWithCloseOnExec" osf
127133openExistingFileWithCloseOnExec :: OsPath -> IOMode -> IO Handle
128134openExistingFileWithCloseOnExec osfp iomode = augmentError " openExistingFileWithCloseOnExec" osfp $ withOpenFile' osfp iomode False True True pure False
129135
136+
137+ -- | The function creates a temporary file in ReadWrite mode.
138+ -- The created file isn\'t deleted automatically, so you need to delete it manually.
139+ --
140+ -- The file is created with permissions such that only the current
141+ -- user can read\/write it.
142+ --
143+ -- With some exceptions (see below), the file will be created securely
144+ -- in the sense that an attacker should not be able to cause
145+ -- openTempFile to overwrite another file on the filesystem using your
146+ -- credentials, by putting symbolic links (on Unix) in the place where
147+ -- the temporary file is to be created. On Unix the @O_CREAT@ and
148+ -- @O_EXCL@ flags are used to prevent this attack, but note that
149+ -- @O_EXCL@ is sometimes not supported on NFS filesystems, so if you
150+ -- rely on this behaviour it is best to use local filesystems only.
151+ --
152+ -- @since 0.1.3
153+ openTempFile :: OsPath -- ^ Directory in which to create the file
154+ -> OsString -- ^ File name template. If the template is \"foo.ext\" then
155+ -- the created file will be \"fooXXX.ext\" where XXX is some
156+ -- random number. Note that this should not contain any path
157+ -- separator characters. On Windows, the template prefix may
158+ -- be truncated to 3 chars, e.g. \"foobar.ext\" will be
159+ -- \"fooXXX.ext\".
160+ -> IO (OsPath , Handle )
161+ openTempFile tmp_dir template = openTempFile' " openTempFile" tmp_dir template False 0o600
162+
163+ -- | Like 'openTempFile', but opens the file in binary mode. See 'openBinaryFile' for more comments.
164+ --
165+ -- @since 0.1.3
166+ openBinaryTempFile :: OsPath -> OsString -> IO (OsPath , Handle )
167+ openBinaryTempFile tmp_dir template
168+ = openTempFile' " openBinaryTempFile" tmp_dir template True 0o600
169+
170+ -- | Like 'openTempFile', but uses the default file permissions
171+ --
172+ -- @since 0.1.3
173+ openTempFileWithDefaultPermissions :: OsPath -> OsString
174+ -> IO (OsPath , Handle )
175+ openTempFileWithDefaultPermissions tmp_dir template
176+ = openTempFile' " openTempFileWithDefaultPermissions" tmp_dir template False 0o666
177+
178+ -- | Like 'openBinaryTempFile', but uses the default file permissions
179+ --
180+ -- @since 0.1.3
181+ openBinaryTempFileWithDefaultPermissions :: OsPath -> OsString
182+ -> IO (OsPath , Handle )
183+ openBinaryTempFileWithDefaultPermissions tmp_dir template
184+ = openTempFile' " openBinaryTempFileWithDefaultPermissions" tmp_dir template True 0o666
185+
130186-- ---------------------------------------------------------------------------
131187-- Internals
132188
@@ -173,3 +229,33 @@ addFilePathToIOError fun fp ioe = unsafePerformIO $ do
173229augmentError :: String -> OsPath -> IO a -> IO a
174230augmentError str osfp = flip catchException (ioError . addFilePathToIOError str osfp)
175231
232+
233+ openTempFile' :: String -> OsPath -> OsString -> Bool -> CMode
234+ -> IO (OsPath , Handle )
235+ openTempFile' loc (OsString tmp_dir) template@ (OsString tmpl) binary mode
236+ | OSS. any (== OSP. pathSeparator) template
237+ = failIO $ " openTempFile': Template string must not contain path separator characters: " ++ P. lenientDecode tmpl
238+ | otherwise = do
239+ (fp, hdl) <- P. findTempName (prefix, suffix) loc tmp_dir mode
240+ when binary $ hSetBinaryMode hdl True
241+ pure (OsString fp, hdl)
242+ where
243+ -- We split off the last extension, so we can use .foo.ext files
244+ -- for temporary files (hidden on Unix OSes). Unfortunately we're
245+ -- below filepath in the hierarchy here.
246+ (OsString prefix, OsString suffix) =
247+ case OSS. break (== OSS. unsafeFromChar ' .' ) $ OSS. reverse template of
248+ -- First case: template contains no '.'s. Just re-reverse it.
249+ (rev_suffix, [osstr ||]) -> (OSS. reverse rev_suffix, OSS. empty)
250+ -- Second case: template contains at least one '.'. Strip the
251+ -- dot from the prefix and prepend it to the suffix (if we don't
252+ -- do this, the unique number will get added after the '.' and
253+ -- thus be part of the extension, which is wrong.)
254+ (rev_suffix, xs)
255+ | (h: rest) <- OSS. unpack xs
256+ , h == unsafeFromChar ' .' -> (OSS. reverse (OSS. pack rest), OSS. cons (unsafeFromChar ' .' ) $ OSS. reverse rev_suffix)
257+ -- Otherwise, something is wrong, because (break (== '.')) should
258+ -- always return a pair with either the empty string or a string
259+ -- beginning with '.' as the second component.
260+ _ -> errorWithoutStackTrace " bug in System.IO.openTempFile"
261+
0 commit comments