{-# LANGUAGE NamedFieldPuns, RecordWildCards, BangPatterns #-} module Distribution.Server.Features.StaticFiles ( initStaticFilesFeature ) where import Distribution.Server.Framework import Distribution.Server.Framework.Templating import Text.XHtml.Strict (Html, toHtml, anchor, (<<), (!), href, paragraph) import qualified Text.XHtml.Strict as XHtml import Data.List hiding (find) import System.FilePath import System.Directory (getDirectoryContents) -- | A feature to provide the top level files on the site (using templates) -- and also serve the genuinely static files. -- initStaticFilesFeature :: ServerEnv -> IO HackageFeature initStaticFilesFeature env@ServerEnv{serverTemplatesDir, serverTemplatesMode} = do -- Page templates templates <- loadTemplates serverTemplatesMode [serverTemplatesDir] ["index.html", "hackageErrorPage.txt", "hackageErrorPage.html"] staticFiles <- find (isSuffixOf ".html.st") serverTemplatesDir let feature = staticFilesFeature env templates staticFiles return feature -- Simpler version of Syhstem.FilePath.Find (which requires unix-compat) find :: (FilePath -> Bool) -> FilePath -> IO [FilePath] find p dirPath = do contents <- getDirectoryContents dirPath return (filter p contents) staticFilesFeature :: ServerEnv -> Templates -> [FilePath] -> HackageFeature staticFilesFeature ServerEnv{serverStaticDir} templates staticFiles = (emptyHackageFeature "static-files") { featureResources = [ (resourceAt "/") { resourceGet = [("", \_ -> serveStaticIndexTemplate)] } -- TODO: we currently cannot use /.. here because then we cannot use it for -- the legacy redirects feature. -- , (resourceAt "/..") { -- resourceGet = [("", \_ -> serveStaticTemplates)] -- } , (resourceAt "/static/..") { resourceGet = [("", \_ -> serveStaticDirFiles)] } ] ++ [ (resourceAt ("/" ++ filename)) { resourceGet = [("", \_ -> serveStaticToplevelFile mimetype filename)] } | (filename, mimetype) <- toplevelFiles ] ++ [ (resourceAt ("/" ++ dropExtension name)) { resourceGet = [("", \_ -> serveStaticTemplate name)] } | name <- toplevelTemplates ] , featureState = [] , featureErrHandlers = [("txt", textErrorPage) ,("html", htmlErrorPage)] } where serveStaticDirFiles :: ServerPartE Response serveStaticDirFiles = serveDirectory DisableBrowsing [] serverStaticDir serveStaticToplevelFile :: String -> FilePath -> ServerPartE Response serveStaticToplevelFile mimetype filename = serveFile (asContentType mimetype) (serverStaticDir filename) serveStaticTemplate :: String -> ServerPartE Response serveStaticTemplate = serveTemplate -- serveStaticTemplates :: ServerPartE Response -- serveStaticTemplates = -- path $ \name -> do -- nullDir -- noTrailingSlash --TODO: redirect to non-slash version -- serveTemplate (name ++ ".html") serveStaticIndexTemplate :: ServerPartE Response serveStaticIndexTemplate = serveTemplate "index.html" serveTemplate :: String -> ServerPartE Response serveTemplate name = do mtemplate <- tryGetTemplate templates name case mtemplate of Nothing -> mzero Just template -> ok $ toResponse $ template [] textErrorPage (ErrorResponse errCode hdrs errTitle message) = do template <- getTemplate templates "hackageErrorPage.txt" let formattedMessage = messageToText message response = toResponse $ template [ "errorTitle" $= errTitle , "errorMessage" $= formattedMessage ] return $ response { rsCode = errCode, rsHeaders = addHeaders (rsHeaders response) hdrs } htmlErrorPage :: ErrorResponse -> ServerPartE Response htmlErrorPage (ErrorResponse errCode hdrs errTitle message) = do template <- getTemplate templates "hackageErrorPage.html" let formattedMessage = paragraph << errorToHtml message response = toResponse $ template [ "errorTitle" $= errTitle , "errorMessage" $= XHtml.showHtmlFragment formattedMessage ] return $ response { rsCode = errCode, rsHeaders = addHeaders (rsHeaders response) hdrs } toplevelFiles = [("favicon.ico", "image/x-icon")] toplevelTemplates = map dropExtension staticFiles addHeaders :: Headers -> [(String, String)] -> Headers addHeaders hdrs hdrs' = foldl' (\h (k,v) -> addHeader k v h) hdrs (reverse hdrs') errorToHtml :: [MessageSpan] -> [Html] errorToHtml [] = [] errorToHtml (MText x :xs) = toHtml x: errorToHtml xs errorToHtml (MLink x url:xs) = (anchor ! [href url] << x): errorToHtml xs