Static file serving: extensible mime-type dictionary
This commit is contained in:
parent
6ce79d673f
commit
e32c5b9a53
2
Yesod.hs
2
Yesod.hs
@ -13,6 +13,7 @@ module Yesod
|
|||||||
, module Yesod.Formable
|
, module Yesod.Formable
|
||||||
, Application
|
, Application
|
||||||
, liftIO
|
, liftIO
|
||||||
|
, mempty
|
||||||
) where
|
) where
|
||||||
|
|
||||||
#if TEST
|
#if TEST
|
||||||
@ -33,3 +34,4 @@ import Yesod.Handler hiding (runHandler)
|
|||||||
import Network.Wai (Application)
|
import Network.Wai (Application)
|
||||||
import Yesod.Hamlet
|
import Yesod.Hamlet
|
||||||
import "transformers" Control.Monad.IO.Class (liftIO)
|
import "transformers" Control.Monad.IO.Class (liftIO)
|
||||||
|
import Data.Monoid (mempty)
|
||||||
|
|||||||
@ -85,9 +85,14 @@ data Content = ContentFile FilePath
|
|||||||
-> a
|
-> a
|
||||||
-> IO (Either a a))
|
-> IO (Either a a))
|
||||||
|
|
||||||
|
-- | Zero-length enumerator.
|
||||||
emptyContent :: Content
|
emptyContent :: Content
|
||||||
emptyContent = ContentEnum $ \_ -> return . Right
|
emptyContent = ContentEnum $ \_ -> return . Right
|
||||||
|
|
||||||
|
-- | Anything which can be converted into 'Content'. Most of the time, you will
|
||||||
|
-- want to use the 'ContentEnum' constructor. An easier approach will be to use
|
||||||
|
-- a pre-defined 'toContent' function, such as converting your data into a lazy
|
||||||
|
-- bytestring and then calling 'toContent' on that.
|
||||||
class ToContent a where
|
class ToContent a where
|
||||||
toContent :: a -> Content
|
toContent :: a -> Content
|
||||||
|
|
||||||
@ -140,6 +145,9 @@ instance HasReps ChooseRep where
|
|||||||
instance HasReps () where
|
instance HasReps () where
|
||||||
chooseRep = defChooseRep [(typePlain, const $ return $ toContent "")]
|
chooseRep = defChooseRep [(typePlain, const $ return $ toContent "")]
|
||||||
|
|
||||||
|
instance HasReps (ContentType, Content) where
|
||||||
|
chooseRep = const . return
|
||||||
|
|
||||||
instance HasReps [(ContentType, Content)] where
|
instance HasReps [(ContentType, Content)] where
|
||||||
chooseRep a cts = return $
|
chooseRep a cts = return $
|
||||||
case filter (\(ct, _) -> go ct `elem` map go cts) a of
|
case filter (\(ct, _) -> go ct `elem` map go cts) a of
|
||||||
@ -218,19 +226,20 @@ typeOctet = "application/octet-stream"
|
|||||||
simpleContentType :: String -> String
|
simpleContentType :: String -> String
|
||||||
simpleContentType = fst . span (/= ';')
|
simpleContentType = fst . span (/= ';')
|
||||||
|
|
||||||
-- | Determine a mime-type based on the file extension.
|
-- | A default extension to mime-type dictionary.
|
||||||
typeByExt :: String -> ContentType
|
typeByExt :: [(String, ContentType)]
|
||||||
typeByExt "jpg" = typeJpeg
|
typeByExt =
|
||||||
typeByExt "jpeg" = typeJpeg
|
[ ("jpg", typeJpeg)
|
||||||
typeByExt "js" = typeJavascript
|
, ("jpeg", typeJpeg)
|
||||||
typeByExt "css" = typeCss
|
, ("js", typeJavascript)
|
||||||
typeByExt "html" = typeHtml
|
, ("css", typeCss)
|
||||||
typeByExt "png" = typePng
|
, ("html", typeHtml)
|
||||||
typeByExt "gif" = typeGif
|
, ("png", typePng)
|
||||||
typeByExt "txt" = typePlain
|
, ("gif", typeGif)
|
||||||
typeByExt "flv" = typeFlv
|
, ("txt", typePlain)
|
||||||
typeByExt "ogv" = typeOgv
|
, ("flv", typeFlv)
|
||||||
typeByExt _ = typeOctet
|
, ("ogv", typeOgv)
|
||||||
|
]
|
||||||
|
|
||||||
-- | Get a file extension (everything after last period).
|
-- | Get a file extension (everything after last period).
|
||||||
ext :: String -> String
|
ext :: String -> String
|
||||||
|
|||||||
@ -38,6 +38,7 @@ module Yesod.Helpers.Static
|
|||||||
|
|
||||||
import System.Directory
|
import System.Directory
|
||||||
import Control.Monad
|
import Control.Monad
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
|
||||||
import Yesod
|
import Yesod
|
||||||
import Data.List (intercalate)
|
import Data.List (intercalate)
|
||||||
@ -51,7 +52,11 @@ import Test.HUnit hiding (Test)
|
|||||||
|
|
||||||
-- | A function for looking up file contents. For serving from the file system,
|
-- | A function for looking up file contents. For serving from the file system,
|
||||||
-- see 'fileLookupDir'.
|
-- see 'fileLookupDir'.
|
||||||
data Static = Static (FilePath -> IO (Maybe (Either FilePath Content)))
|
data Static = Static
|
||||||
|
{ staticLookup :: FilePath -> IO (Maybe (Either FilePath Content))
|
||||||
|
-- | Mapping from file extension to content type. See 'typeByExt'.
|
||||||
|
, staticTypes :: [(String, ContentType)]
|
||||||
|
}
|
||||||
|
|
||||||
mkYesodSub "Static" [] [$parseRoutes|
|
mkYesodSub "Static" [] [$parseRoutes|
|
||||||
*Strings StaticRoute GET
|
*Strings StaticRoute GET
|
||||||
@ -63,7 +68,7 @@ mkYesodSub "Static" [] [$parseRoutes|
|
|||||||
-- probably are), the handler itself checks that no unsafe paths are being
|
-- probably are), the handler itself checks that no unsafe paths are being
|
||||||
-- requested. In particular, no path segments may begin with a single period,
|
-- requested. In particular, no path segments may begin with a single period,
|
||||||
-- so hidden files and parent directories are safe.
|
-- so hidden files and parent directories are safe.
|
||||||
fileLookupDir :: FilePath -> Static
|
fileLookupDir :: FilePath -> [(String, ContentType)] -> Static
|
||||||
fileLookupDir dir = Static $ \fp -> do
|
fileLookupDir dir = Static $ \fp -> do
|
||||||
let fp' = dir ++ '/' : fp
|
let fp' = dir ++ '/' : fp
|
||||||
exists <- doesFileExist fp'
|
exists <- doesFileExist fp'
|
||||||
@ -72,16 +77,20 @@ fileLookupDir dir = Static $ \fp -> do
|
|||||||
else return Nothing
|
else return Nothing
|
||||||
|
|
||||||
getStaticRoute :: [String]
|
getStaticRoute :: [String]
|
||||||
-> GHandler Static master [(ContentType, Content)]
|
-> GHandler Static master (ContentType, Content)
|
||||||
getStaticRoute fp' = do
|
getStaticRoute fp' = do
|
||||||
Static fl <- getYesodSub
|
Static fl ctypes <- getYesodSub
|
||||||
when (any isUnsafe fp') notFound
|
when (any isUnsafe fp') notFound
|
||||||
let fp = intercalate "/" fp'
|
let fp = intercalate "/" fp'
|
||||||
content <- liftIO $ fl fp
|
content <- liftIO $ fl fp
|
||||||
case content of
|
case content of
|
||||||
Nothing -> notFound
|
Nothing -> notFound
|
||||||
Just (Left fp'') -> sendFile (typeByExt $ ext fp'') fp''
|
Just (Left fp'') -> do
|
||||||
Just (Right bs) -> return [(typeByExt $ ext fp, bs)]
|
let ctype = fromMaybe typeOctet $ lookup (ext fp'') ctypes
|
||||||
|
sendFile ctype fp''
|
||||||
|
Just (Right bs) -> do
|
||||||
|
let ctype = fromMaybe typeOctet $ lookup (ext fp) ctypes
|
||||||
|
return (ctype, bs)
|
||||||
where
|
where
|
||||||
isUnsafe [] = True
|
isUnsafe [] = True
|
||||||
isUnsafe ('.':_) = True
|
isUnsafe ('.':_) = True
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user