hastatic/src/Main.hs

97 lines
3.3 KiB
Haskell

{-# LANGUAGE OverloadedStrings #-}
module Main where
import qualified Data.ByteString.Char8 as C8
import Data.Maybe (fromMaybe)
import qualified Data.List as List
import qualified Data.Text as T
import Data.Version (showVersion)
import Network.Wai
import qualified Network.Wai.Handler.WarpTLS as TLS
import Network.Wai.Middleware.Static
import Network.HTTP.Types (status404)
import Network.Wai.Handler.Warp ( runSettings
, defaultSettings
, setPort
, setFdCacheDuration
, setFileInfoCacheDuration
, setServerName
)
import System.Exit (die)
import System.Environment (lookupEnv)
import Text.Read (readMaybe)
import Paths_hastatic (version)
data TLS = Okay TLS.TLSSettings | Error String | None
addSecureHeaders :: Middleware
addSecureHeaders = modifyResponse $ mapResponseHeaders (++ secureHeaders)
where
secureHeaders = [ ("Referrer-Policy", "strict-origin-when-cross-origin")
, ("X-Frame-Options", "SAMEORIGIN")
, ("X-XSS-Protection", "1; mode=block")
]
indexHTML :: T.Text -> Middleware
indexHTML indexFile app req respond =
let path = pathInfo req
in flip app respond $ req { pathInfo = fixPath path }
where
fixPath path = case path of
[] -> [indexFile]
[""] -> [indexFile]
[x] -> if "." `T.isInfixOf` x then [x] else [x, indexFile]
(x:xs) -> x : fixPath xs
notFoundHandler :: FilePath -> Application
notFoundHandler notFoundFile _ respond = respond $
responseFile status404 [("Content-Type", "text/html")] notFoundFile Nothing
getTLSSettings :: IO TLS
getTLSSettings = do
tlsCertFile <- lookupEnv "TLS_CERT_FILE"
tlsKeyFile <- lookupEnv "TLS_KEY_FILE"
case (tlsCertFile, tlsKeyFile) of
(Nothing, Nothing) -> return None
(Just cert, Just key) -> return $ Okay $ TLS.tlsSettings cert key
_ -> return $ Error "Certificate file or Key file is missing"
application :: [FilePath] -> IO Application
application excludedPaths = do
notFoundFile <- fromMaybe "404.html" <$> lookupEnv "NF_FILE"
indexFile <- T.pack . fromMaybe "index.html" <$> lookupEnv "IDX_FILE"
cache <- initCaching PublicStaticCaching
return
. addSecureHeaders
. indexHTML indexFile
. staticPolicy' cache polcy
. notFoundHandler
$ notFoundFile
where
noDot = not . List.isPrefixOf "."
polcy = predicate noDot >-> predicate (not . flip elem excludedPaths)
main :: IO ()
main = do
mPort <- lookupEnv "PORT"
let port = fromMaybe 3000 (readMaybe =<< mPort)
tlsSettings <- getTLSSettings
let settings = setPort port
. setFdCacheDuration 10
. setFileInfoCacheDuration 10
. setServerName ("hastatic-" <> C8.pack (showVersion version))
$ defaultSettings
case tlsSettings of
Okay tls -> do
app <- application [TLS.certFile tls, TLS.keyFile tls]
putStrLn $ "Starting HTTPS server on port: " <> show port
TLS.runTLS tls settings app
None -> do
app <- application []
putStrLn $ "Starting HTTP server on port: " <> show port
runSettings settings app
Error msg ->
die $ "Error starting server: " <> msg