7 -- | Handling static files on the filesystem.
8 module Network.HTTP.Lucu.StaticFile
12 , generateETagFromFile
15 import qualified Blaze.ByteString.Builder.ByteString as BB
16 import qualified Blaze.Text.Int as BT
18 import Control.Monad.Unicode
19 import Control.Monad.Trans
20 import qualified Data.Ascii as A
21 import Data.ByteString (ByteString)
22 import qualified Data.ByteString.Lazy.Char8 as LBS
23 import Data.Monoid.Unicode
25 import qualified Data.Text as T
26 import qualified Data.Text.Encoding as T
27 import Data.Time.Clock.POSIX
28 import Network.HTTP.Lucu.Abortion
29 import Network.HTTP.Lucu.Config
30 import Network.HTTP.Lucu.ETag
31 import Network.HTTP.Lucu.MIMEType hiding (mimeType)
32 import Network.HTTP.Lucu.MIMEType.Guess
33 import Network.HTTP.Lucu.MIMEType.TH
34 import Network.HTTP.Lucu.Resource
35 import Network.HTTP.Lucu.Resource.Internal
36 import Network.HTTP.Lucu.Response
37 import Prelude.Unicode
38 import System.FilePath
39 import System.Posix.Files
41 -- | @'staticFile' fpath@ is a 'ResourceDef' which serves the file at
42 -- @fpath@ on the filesystem.
43 staticFile ∷ FilePath → ResourceDef
46 resGet = Just $ handleStaticFile True path
47 , resHead = Just $ handleStaticFile False path
50 octetStream ∷ MIMEType
51 octetStream = [mimeType| application/octet-stream |]
53 handleStaticFile ∷ Bool → FilePath → Resource ()
54 handleStaticFile sendContent path
55 = do exists ← liftIO $ fileExist path
59 readable ← liftIO $ fileAccess path True False False
62 $ mkAbortion Forbidden [] Nothing
64 stat ← liftIO $ getFileStatus path
65 when (isDirectory stat)
67 $ mkAbortion Forbidden [] Nothing
69 -- FIXME: Forget about ETags of a static file.
70 tag ← liftIO $ generateETagFromFile path
71 let lastMod = posixSecondsToUTCTime
74 $ modificationTime stat
75 foundEntity tag lastMod
78 case guessTypeByFileName (cnfExtToMIMEType conf) path of
79 Nothing → setContentType octetStream
80 Just mime → setContentType mime
83 $ liftIO (LBS.readFile path) ≫= putChunks
85 -- |@'generateETagFromFile' fpath@ generates a strong entity tag from
86 -- a file. The file doesn't necessarily have to be a regular file; it
87 -- may be a FIFO or a device file. The tag is made of inode ID, size
88 -- and modification time.
90 -- Note that the tag is not strictly strong because the file could be
91 -- modified twice at a second without changing inode ID or size, but
92 -- it's not really possible to generate a strictly strong ETag from a
93 -- file as we don't want to simply grab the entire file and use it as
94 -- an ETag. It is indeed possible to hash it with SHA-1 or MD5 to
95 -- increase strictness, but it's too inefficient if the file is really
96 -- large (say, 1 TiB).
97 generateETagFromFile ∷ FilePath → IO ETag
98 generateETagFromFile path
99 = do stat ← getFileStatus path
100 let inode = fileID stat
102 lastMod = fromEnum $ modificationTime stat
103 tag = A.fromAsciiBuilder
104 $ A.unsafeFromBuilder
106 ⊕ BB.fromByteString "-"
108 ⊕ BB.fromByteString "-"
109 ⊕ BT.integral lastMod
110 return $ strongETag tag
112 -- | @'staticDir' dir@ is a 'ResourceDef' which maps all files in
113 -- @dir@ and its subdirectories on the filesystem to the
114 -- 'Network.HTTP.Lucu.Resource.Tree.ResTree'.
116 -- Note that 'staticDir' currently doesn't have a directory-listing
117 -- capability. Requesting the content of a directory will end up being
118 -- replied with /403 Forbidden/.
119 staticDir ∷ FilePath → ResourceDef
123 , resGet = Just $ handleStaticDir True path
124 , resHead = Just $ handleStaticDir False path
127 -- TODO: implement directory listing.
128 handleStaticDir ∷ Bool → FilePath → Resource ()
129 handleStaticDir sendContent basePath
130 = do extraPath ← getPathInfo
131 securityCheck extraPath
132 let path = basePath </> joinPath (map dec8 extraPath)
133 handleStaticFile sendContent path
135 dec8 ∷ ByteString → String
136 dec8 = T.unpack ∘ T.decodeUtf8
138 securityCheck ∷ (Eq s, Show s, IsString s, Monad m) ⇒ [s] → m ()
139 securityCheck pathElems
140 = when (any (≡ "..") pathElems)
141 $ fail ("security error: " ⧺ show pathElems)