module Web.Herringbone.Adapter.Wai (toApplication) where import Control.Monad import qualified Data.ByteString as B import qualified Data.ByteString.Lazy as BL hiding (pack) import qualified Data.ByteString.Lazy.Char8 as BL import qualified Data.Text.Encoding as T import qualified Data.Text.Lazy as T import Data.Time import Data.Time.Clock.POSIX import System.Posix.Types (EpochTime) import Data.Monoid import Data.List import Data.Maybe import Network.Wai import Network.Wai.Application.Static import WaiAppStatic.Types import Network.HTTP.Types import Prelude hiding (FilePath) import Filesystem.Path.CurrentOS (FilePath) import qualified Filesystem.Path.CurrentOS as F import qualified Filesystem as F import Web.Herringbone -- | Convert a 'Herringbone' to a WAI 'Application'. toApplication :: Herringbone -> Application toApplication hb@(hbDestDir -> dest) = staticApp $ (defaultWebAppSettings dest) { ssLookupFile = lookupFile hb } lookupFile :: Herringbone -> Pieces -> IO LookupResult lookupFile hb pieces = do asset <- findAsset hb (toLogicalPath pieces) either assetErrorToLR bundledAssetToLR asset where assetErrorToLR = return . go go AssetNotFound = LRNotFound go (AssetCompileError err) = LRFile . assetCompileError $ err go (AmbiguousSources xs) = LRFile . ambiguousSources $ xs bundledAssetToLR asset = do file <- toFile (assetSourcePath asset) (assetFilePath asset) (last pieces) return . LRFile $ file -- WaiAppStatic takes care of directory traversal attacks for us toLogicalPath :: Pieces -> LogicalPath toLogicalPath = unsafeMakeLogicalPath . map fromPiece -- This is just given to wai-app-static which takes care of serving it. toFile :: FilePath -- ^ source path -> FilePath -- ^ dest path -> Piece -- ^ file name -> IO File toFile source dest name = do size <- F.getSize dest mtime <- F.getModified source let strDest = F.encodeString dest return File { fileGetSize = fromIntegral size , fileToResponse = \s h -> responseFile s h strDest Nothing , fileName = name , fileGetHash = return Nothing -- TODO , fileGetModified = Just . toEpochTime $ mtime } toEpochTime :: UTCTime -> EpochTime toEpochTime = fromIntegral . toSecs where toSecs :: UTCTime -> Int toSecs = floor . utcTimeToPOSIXSeconds assetCompileError :: CompileError -> File assetCompileError err = fileError (BL.fromStrict err) (unsafeToPiece "compile-error.html") ambiguousSources :: [FilePath] -> File ambiguousSources sources = let toLazyBS = BL.pack . F.encodeString htmlListItem item = "
List of possible asset sources:
" <> "