Skip to content

Commit 1fd0069

Browse files
committed
Remove dependency on deprecated system-filepath
This patch removes `system-filepath` and tries to replace any existing functionality that that package provides. `system-filepath` has been deprecated in favor of `filepath`: https://hackage.haskell.org/package/system-filepath
1 parent 4c641cc commit 1fd0069

File tree

2 files changed

+42
-14
lines changed

2 files changed

+42
-14
lines changed

happstack-server.cabal

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,6 @@ Library
8484
process,
8585
semigroups >= 0.16,
8686
sendfile >= 0.7.1 && < 0.8,
87-
system-filepath >= 0.3.1,
8887
syb,
8988
text >= 0.10 && < 1.3,
9089
time,

src/Happstack/Server/FileServe/BuildingBlocks.hs

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -60,16 +60,18 @@ import Control.Monad.Trans (MonadIO(liftIO))
6060
import qualified Data.ByteString.Lazy.Char8 as L
6161
import qualified Data.ByteString.Char8 as S
6262
import Data.Data (Data, Typeable)
63-
import Data.List (sort)
63+
import Data.Foldable (toList)
64+
import Data.List (sort, isPrefixOf)
6465
import Data.Maybe (fromMaybe)
6566
import Data.Map (Map)
6667
import qualified Data.Map as Map
67-
import Filesystem.Path.CurrentOS (commonPrefix, encodeString, decodeString, collapse, append)
68+
import Data.Sequence (Seq ((:|>), (:<|)), (|>), (<|))
69+
import qualified Data.Sequence as Seq
6870
import Happstack.Server.Monads (ServerMonad(askRq), FilterMonad, WebMonad)
6971
import Happstack.Server.Response (ToMessage(toResponse), ifModifiedSince, forbidden, ok, seeOther)
7072
import Happstack.Server.Types (Length(ContentLength), Request(rqPaths, rqUri), Response(SendFile), RsFlags(rsfLength), nullRsFlags, result, resultBS, setHeader)
71-
import System.Directory (doesDirectoryExist, doesFileExist, getDirectoryContents, getModificationTime)
72-
import System.FilePath ((</>), addTrailingPathSeparator, hasDrive, isPathSeparator, joinPath, takeExtension, isValid)
73+
import System.Directory (doesDirectoryExist, doesFileExist, getDirectoryContents, getModificationTime, makeAbsolute)
74+
import System.FilePath ((</>), addTrailingPathSeparator, hasDrive, isPathSeparator, joinPath, takeExtension, isValid, normalise, splitPath, isAbsolute)
7375
import System.IO (IOMode(ReadMode), hFileSize, hClose, openBinaryFile, withBinaryFile)
7476
import System.Log.Logger (Priority(DEBUG), logM)
7577
import Text.Blaze.Html ((!))
@@ -319,7 +321,7 @@ serveFileFrom :: (ServerMonad m, FilterMonad Response m, MonadIO m, MonadPlus m)
319321
-> FilePath -- ^ path to the file to serve
320322
-> m Response
321323
serveFileFrom root mimeFn fp =
322-
maybe no yes $ combineSafe root fp
324+
combineSafe root fp >>= maybe no yes
323325
where
324326
no = forbidden $ toResponse "Directory traversal forbidden"
325327
yes = serveFile mimeFn
@@ -383,15 +385,42 @@ fileServe' serveFn mimeFn indexFn localPath = do
383385
-- Nothing
384386
-- >>> combineSafe "/var/uploads/" "../uploads/home/../etc/passwd"
385387
-- Just "/var/uploads/etc/passwd"
386-
combineSafe :: FilePath -> FilePath -> Maybe FilePath
387-
combineSafe root path =
388-
if commonPrefix [root', joined] == root'
389-
then Just $ encodeString joined
390-
else Nothing
388+
combineSafe :: MonadIO m => FilePath -> FilePath -> m (Maybe FilePath)
389+
combineSafe root path = do
390+
root' <- liftIO $ makeAbsolute root
391+
let path' = normalise path
392+
pure $ do
393+
combined <- combineReduce root' path'
394+
if root' `isPrefixOf` combined
395+
then Just combined
396+
else Nothing
391397
where
392-
root' = decodeString root
393-
path' = decodeString path
394-
joined = collapse $ append root' path'
398+
-- Combine an absolute path with another path, reducing any @..@ elements
399+
combineReduce :: FilePath -> FilePath -> Maybe FilePath
400+
combineReduce r p
401+
| isAbsolute r = Just $
402+
let splitP = splitPath p
403+
in joinPath $ toList $
404+
-- If @p@ is absolute, then process it against the root path, dropping @r@ completely
405+
if isAbsolute p
406+
-- Split off the head and re-add it after processing the tail with @go@
407+
then let headP :<| tailP = Seq.fromList splitP
408+
in headP <| go Seq.Empty (toList tailP)
409+
else let headR :<| tailR = Seq.fromList (splitPath r)
410+
in headR <| go tailR splitP
411+
-- If the root is not absolute, it is unclear how to handle arbitrary @..@ elements in a safe way
412+
| otherwise = Nothing
413+
414+
-- | Build up a 'Seq' representation of @path@, reducing any @..@ elements
415+
-- This function assumes the 'Seq' is a split absolute path, with the beginning part removed.
416+
--
417+
-- Note that this functionality has been removed from the filepath package
418+
-- See: <https://neilmitchell.blogspot.com/2015/10/filepaths-are-subtle-symlinks-are-hard.html>
419+
go :: Seq FilePath -> [FilePath] -> Seq FilePath
420+
go p [] = p
421+
go Seq.Empty ("..":_ ) = Seq.Empty -- Going up beyond the top level does nothing
422+
go (s :|> _) ("..":ps) = go s ps -- Going up a level pops an element off the right side of the Seq
423+
go s (p :ps) = go (s |> p) ps -- Just add an element to the right side of the Seq
395424

396425
isSafePath :: [FilePath] -> Bool
397426
isSafePath [] = True

0 commit comments

Comments
 (0)