{-# LANGUAGE QuasiQuotes #-}{-# LANGUAGE TemplateHaskell #-}{-# LANGUAGE TypeFamilies #-}{-# LANGUAGE CPP #-}{-# LANGUAGE FlexibleInstances #-}{-# LANGUAGE MultiParamTypeClasses #-}------------------------------------------------------------- Module : Yesod.Helpers.Static-- Copyright : Michael Snoyman-- License : BSD3---- Maintainer : Michael Snoyman <michael@snoyman.com>-- Stability : Unstable-- Portability : portable---- | Serve static files from a Yesod app.---- This is most useful for standalone testing. When running on a production-- server (like Apache), just let the server do the static serving.---- In fact, in an ideal setup you'll serve your static files from a separate-- domain name to save time on transmitting cookies. In that case, you may wish-- to use 'urlRenderOverride' to redirect requests to this subsite to a-- separate domain name.moduleYesod.Helpers.Static(-- * SubsiteStatic(..),StaticRoute(..)-- * Lookup files in filesystem,fileLookupDir,staticFiles-- * Hashing,base64md5#if TEST,testSuite#endif)whereimportSystem.DirectoryimportControl.MonadimportData.Maybe(fromMaybe)importYesodhiding(lift)importData.List(intercalate)importLanguage.Haskell.TH.SyntaximportqualifiedData.ByteString.LazyasLimportData.Digest.Pure.MD5importqualifiedCodec.Binary.Base64UrlimportqualifiedData.ByteStringasSimportqualifiedData.SerializeimportYesod.WebRoutes#if TESTimportTest.Framework(testGroup,Test)importTest.Framework.Providers.HUnitimportTest.HUnithiding(Test)#endif-- | A function for looking up file contents. For serving from the file system,-- see 'fileLookupDir'.dataStatic=Static{staticLookup::FilePath->IO(Maybe(EitherFilePathContent))-- | Mapping from file extension to content type. See 'typeByExt'.,staticTypes::[(String,ContentType)]}-- | Manually construct a static route.-- The first argument is a sub-path to the file being served whereas the second argument is the key value pairs in the query string.-- For example, -- > StaticRoute $ StaticR ["thumb001.jpg"] [("foo", "5"), ("bar", "choc")]-- would generate a url such as 'http://site.com/static/thumb001.jpg?foo=5&bar=choc'-- The StaticRoute constructor can be used when url's cannot be statically generated at compile-time.-- E.g. When generating image galleries.dataStaticRoute=StaticRoute[String][(String,String)]deriving(Eq,Show,Read)typeinstanceRouteStatic=StaticRouteinstanceYesodSubSiteStaticmasterwheregetSubSite=Site{handleSite=\_(StaticRouteps_)m->casemof"GET"->Just$fmapchooseRep$getStaticRouteps_->Nothing,formatPathSegments=\(StaticRoutexy)->(x,y),parsePathSegments=\x->Right$StaticRoutex[]}-- | Lookup files in a specific directory.---- If you are just using this in combination with the static subsite (you-- probably are), the handler itself checks that no unsafe paths are being-- requested. In particular, no path segments may begin with a single period,-- so hidden files and parent directories are safe.---- For the second argument to this function, you can just use 'typeByExt'.fileLookupDir::FilePath->[(String,ContentType)]->StaticfileLookupDirdir=Static$\fp->doletfp'=dir++'/':fpexists<-doesFileExistfp'ifexiststhenreturn$Just$Leftfp'elsereturnNothinggetStaticRoute::[String]->GHandlerStaticmaster(ContentType,Content)getStaticRoutefp'=doStaticflctypes<-getYesodSubwhen(anyisUnsafefp')notFoundletfp=intercalate"/"fp'content<-liftIO$flfpcasecontentofNothing->notFoundJust(Leftfp'')->doletctype=fromMaybetypeOctet$lookup(extfp'')ctypessendFilectypefp''Just(Rightbs)->doletctype=fromMaybetypeOctet$lookup(extfp)ctypesreturn(ctype,bs)whereisUnsafe[]=TrueisUnsafe('.':_)=TrueisUnsafe_=FalsenotHidden::FilePath->BoolnotHidden('.':_)=FalsenotHidden_=TruegetFileList::FilePath->IO[[String]]getFileList=flipgoidwherego::String->([String]->[String])->IO[[String]]gofpfront=doallContents<-filternotHidden`fmap`getDirectoryContentsfpletfullPath::String->StringfullPathf=fp++'/':ffiles<-filterM(doesFileExist.fullPath)allContentsletfiles'=map(front.return)filesdirs<-filterM(doesDirectoryExist.fullPath)allContentsdirs'<-mapM(\f->go(fullPathf)(front.(:)f))dirsreturn$concat$files':dirs'-- | This piece of Template Haskell will find all of the files in the given directory and create Haskell identifiers for them. For example, if you have the files \"static\/style.css\" and \"static\/js\/script.js\", it will essentailly create:---- > style_css = StaticRoute ["style.css"] []-- > js_script_js = StaticRoute ["js/script.js"] []staticFiles::FilePath->Q[Dec]staticFilesfp=dofs<-qRunIO$getFileListfpconcat`fmap`mapMgofswherereplace''.'='_'replace''-'='_'replace'c=cgof=doletname=mkName$intercalate"_"$map(mapreplace')ff'<-liftfletsr=ConE$mkName"StaticRoute"hash<-qRunIO$fmapbase64md5$L.readFile$fp++'/':intercalate"/"fletqs=ListE[TupE[LitE$StringLhash,ListE[]]]return[SigDname$ConT''Route`AppT`ConT''Static,FunDname[Clause[](NormalB$sr`AppE`f'`AppE`qs)[]]]#if TESTtestSuite::TesttestSuite=testGroup"Yesod.Helpers.Static"[testCase"get file list"caseGetFileList]caseGetFileList::AssertioncaseGetFileList=dox<-getFileList"test"x@?=[["foo"],["bar","baz"]]#endif-- | md5-hashes the given lazy bytestring and returns the hash as-- base64url-encoded string.---- This function returns the first 8 characters of the hash.base64md5::L.ByteString->Stringbase64md5=take8.Codec.Binary.Base64Url.encode.S.unpack.Data.Serialize.encode.md5