mighttpd2-4.0.11: test/WaiAppSpec.hs
{-# LANGUAGE OverloadedStrings #-}
module WaiAppSpec where
import Control.Exception (bracket)
import Data.ByteString (ByteString)
import qualified Network.HTTP.Client as H
import Network.HTTP.Types (Status, statusCode)
import Network.Wai.Application.Classic hiding ((</>))
import Network.Wai.Handler.Warp (testWithApplication)
import System.Directory
import System.FilePath ((</>))
import Test.Hspec
import Program.Mighty
import WaiApp
spec :: Spec
spec = do
describe "isHiddenPath" $ do
it "detects segments starting with a dot" $ do
isHiddenPath "/foo/bar.html" `shouldBe` False
isHiddenPath "/a.d/b" `shouldBe` False
isHiddenPath "/.git/config" `shouldBe` True
isHiddenPath "/foo/.htaccess" `shouldBe` True
isHiddenPath "/foo/../bar" `shouldBe` True
it "splits segments by backslash too" $
isHiddenPath "/foo\\.git\\config" `shouldBe` True
it "allows /.well-known/ but not hidden segments under it" $ do
isHiddenPath "/.well-known/acme-challenge/x" `shouldBe` False
isHiddenPath "/.well-known/.git/x" `shouldBe` True
isHiddenPath "/foo/.well-known/x" `shouldBe` True
describe "getBlock" $ do
it "finds the block of the domain or the wildcard" $ do
getBlock "localhost" db `shouldBe` Just [fileRoute]
getBlock "example.org" db `shouldBe` Just [cgiRoute]
getBlock "example.org" (take 1 db) `shouldBe` Nothing
describe "getRoute" $ do
let routes = [cgiRoute, fileRoute]
it "finds the route whose source is a prefix" $ do
getRoute "/cgi-bin/foo" routes `shouldBe` Found cgiRoute
getRoute "/index.html" routes `shouldBe` Found fileRoute
it "redirects to the mount point" $
getRoute "/cgi-bin" routes `shouldBe` Redirect
it "fails if no route matches" $
getRoute "/foo" [cgiRoute] `shouldBe` Fail
describe "fileCgiApp" $ around withSite $ do
it "matches the domain case-insensitively" $ \site -> do
get site "localhost" "/" `shouldReturn` 200
get site "LOCALHOST" "/" `shouldReturn` 200
get site "localhost." "/" `shouldReturn` 200
get site "LocalHost:8080" "/" `shouldReturn` 200
it "returns 412 for an unknown domain" $ \site ->
get site "example.org" "/" `shouldReturn` 412
it "rejects hidden files even if encoded" $ \site -> do
get site "localhost" "/.git/config" `shouldReturn` 400
get site "localhost" "/%2egit/config" `shouldReturn` 400
get site "localhost" "/%5c.git/config" `shouldReturn` 400
it "allows /.well-known/" $ \site ->
get site "localhost" "/.well-known/acme/x" `shouldReturn` 200
----------------------------------------------------------------
fileRoute, cgiRoute :: Route
fileRoute = RouteFile "/" "/var/www/"
cgiRoute = RouteCGI "/cgi-bin/" "/var/cgi-bin/"
db :: RouteDB
db = [Block ["localhost"] [fileRoute], Block ["*"] [cgiRoute]]
-- | The port of 'fileCgiApp' and a manager shared by requests.
type Site = (Int, H.Manager)
-- | Running 'fileCgiApp' on a temporary document root.
withSite :: (Site -> IO ()) -> IO ()
withSite body = do
tmp <- getTemporaryDirectory
let root = tmp </> "mighty-waiapp-spec"
bracket (mkSite root) removeDirectoryRecursive $ \_ -> do
rdr <- newRouteDBRef [Block ["localhost"] [RouteFile "/" (fromString (root ++ "/"))]]
mgr <- H.newManager H.defaultManagerSettings
let cspec = ClassicAppSpec "Test" "/nonexistent/"
filespec = FileAppSpec "index.html" (".html" `isSuffixOf`)
cgispec = CgiAppSpec "index.cgi"
app = fileCgiApp cspec filespec cgispec (RevProxyAppSpec mgr) rdr
testWithApplication (return app) $ \port -> body (port, mgr)
where
mkSite root = do
removePathForcibly root
createDirectoryIfMissing True $ root </> ".git"
createDirectoryIfMissing True $ root </> ".well-known" </> "acme"
writeFile (root </> "index.html") "top\n"
writeFile (root </> ".git" </> "config") "secret\n"
writeFile (root </> ".well-known" </> "acme" </> "x") "token\n"
return root
-- | Sending GET with the Host header and the raw path.
get :: Site -> ByteString -> ByteString -> IO Int
get (port, mgr) host path = do
req <- H.parseRequest $ "http://127.0.0.1:" ++ show port
let req' = req{H.path = path, H.requestHeaders = [("Host", host)]}
statusCode' . H.responseStatus <$> H.httpLbs req' mgr
where
statusCode' :: Status -> Int
statusCode' = statusCode