7 -- | Handling static files on the filesystem.
8 module Network.HTTP.Lucu.StaticFile
14 import Control.Monad.Unicode
15 import Control.Monad.Trans
16 import Data.ByteString (ByteString)
17 import qualified Data.ByteString.Lazy.Char8 as LBS
19 import qualified Data.Text as T
20 import qualified Data.Text.Encoding as T
21 import Network.HTTP.Lucu.Abortion
22 import Network.HTTP.Lucu.Config
23 import Network.HTTP.Lucu.MIMEType hiding (mimeType)
24 import Network.HTTP.Lucu.MIMEType.Guess
25 import Network.HTTP.Lucu.MIMEType.TH
26 import Network.HTTP.Lucu.Resource
27 import Network.HTTP.Lucu.Resource.Internal
28 import Network.HTTP.Lucu.Response
29 import Network.HTTP.Lucu.Utils
30 import Prelude.Unicode
31 import System.Directory
32 import System.FilePath
34 -- | @'staticFile' fpath@ is a 'ResourceDef' which serves the file at
35 -- @fpath@ on the filesystem.
36 staticFile ∷ FilePath → ResourceDef
39 resGet = Just $ handleStaticFile True path
40 , resHead = Just $ handleStaticFile False path
43 octetStream ∷ MIMEType
44 octetStream = [mimeType| application/octet-stream |]
46 handleStaticFile ∷ Bool → FilePath → Resource ()
47 handleStaticFile sendContent path
48 = do exists ← liftIO $ doesFileExist path
52 perms ← liftIO $ getPermissions path
53 unless (readable perms)
55 $ mkAbortion Forbidden [] Nothing
57 lastMod ← liftIO $ getLastModified path
58 foundTimeStamp lastMod
61 case guessTypeByFileName (cnfExtToMIMEType conf) path of
62 Nothing → setContentType octetStream
63 Just mime → setContentType mime
66 $ liftIO (LBS.readFile path) ≫= putChunks
68 -- | @'staticDir' dir@ is a 'ResourceDef' which maps all files in
69 -- @dir@ and its subdirectories on the filesystem to the
70 -- 'Network.HTTP.Lucu.Resource.Tree.ResTree'.
72 -- Note that 'staticDir' currently doesn't have a directory-listing
73 -- capability. Requesting the content of a directory will end up being
74 -- replied with /403 Forbidden/.
75 staticDir ∷ FilePath → ResourceDef
79 , resGet = Just $ handleStaticDir True path
80 , resHead = Just $ handleStaticDir False path
83 -- TODO: implement directory listing.
84 handleStaticDir ∷ Bool → FilePath → Resource ()
85 handleStaticDir sendContent basePath
86 = do extraPath ← getPathInfo
87 securityCheck extraPath
88 let path = basePath </> joinPath (map dec8 extraPath)
89 handleStaticFile sendContent path
91 dec8 ∷ ByteString → String
92 dec8 = T.unpack ∘ T.decodeUtf8
94 securityCheck ∷ (Eq s, Show s, IsString s, Monad m) ⇒ [s] → m ()
95 securityCheck pathElems
96 = when (any (≡ "..") pathElems)
97 $ fail ("security error: " ⧺ show pathElems)