packages feed

wai-middleware-delegate 0.1.4.1 → 0.1.4.2

raw patch · 13 files changed

+626/−325 lines, 13 filesdep +directorydep +filepathdep +mustachedep ~bytestringdep ~crypton-connectiondep ~http-clientPVP: minor bump suggested

API additions: PVP suggests at least a minor version bump

Dependencies added: directory, filepath, mustache, temporary, test-certs, unix

Dependency ranges changed: bytestring, crypton-connection, http-client, http-client-tls, http-types, streaming-commons, text, tmp-proc

API changes (from Hackage documentation)

+ Network.Wai.Middleware.Delegate: defaultSettings :: ProxySettings

Files

ChangeLog.md view
@@ -2,6 +2,12 @@  `wai-middleware-delegate` uses [PVP Versioning][1]. +## 0.1.4.2 -- 2024-10-28++* Export defaultSettings as an alternative to Default#def++* Add comments 'deprecating' the Default instance of ProxySettings+ ## 0.1.4.1 -- 2024-03-17  * Relax the upper bounds on bytestring to allow bytestring 0.12.1
src/Network/Wai/Middleware/Delegate.hs view
@@ -30,6 +30,7 @@      -- * Configuration   , ProxySettings (..)+  , defaultSettings      -- * Aliases   , RequestPredicate@@ -51,8 +52,7 @@ import qualified Data.ByteString.Lazy.Char8 as LC8 import Data.CaseInsensitive (mk) import Data.Conduit-  ( ConduitM-  , ConduitT+  ( ConduitT   , Flush (..)   , Void   , await@@ -67,13 +67,11 @@ import Data.Default (Default (..)) import Data.IORef (newIORef, readIORef, writeIORef) import Data.Int (Int64)-import Data.Monoid ((<>)) import Data.Streaming.Network   ( ClientSettings   , clientSettingsTCP   , runTCPClient   )-import Data.String (IsString) import Network.HTTP.Client   ( BodyReader   , GivesPopper@@ -86,11 +84,9 @@   , withResponse   ) import Network.HTTP.Types-  ( Header-  , HeaderName+  ( HeaderName   , hContentType   , internalServerError500-  , status304   , status500   ) import Network.HTTP.Types.Header (hHost)@@ -137,25 +133,36 @@   }  +{- | This instance is DEPRECATED and will removed in a later release.+please use 'defaultSettings' instead+-} instance Default ProxySettings where-  def =-    ProxySettings-      { -- defaults to returning internal server error showing the error in the body-        proxyOnException = onException-      , -- default to 15 seconds-        proxyTimeout = 15-      , proxyHost = "localhost"-      , proxyRedirectCount = 0-      }-    where-      onException :: SomeException -> Wai.Response-      onException e =-        Wai.responseLBS-          internalServerError500-          [(hContentType, "text/plain; charset=utf-8")]-          $ LC8.fromChunks [C8.pack $ show e]+  -- This is DEPRECATED, please use 'defaultSettings' instead+  def = defaultSettings  +{- | A default 'ProxySettings' that makes simplistic assumptions, e.g, that+target host is @localhost@+-}+defaultSettings :: ProxySettings+defaultSettings =+  ProxySettings+    { -- defaults to returning internal server error showing the error in the body+      proxyOnException = onException+    , -- default to 15 seconds+      proxyTimeout = 15+    , proxyHost = "localhost"+    , proxyRedirectCount = 0+    }+  where+    onException :: SomeException -> Wai.Response+    onException e =+      Wai.responseLBS+        internalServerError500+        [(hContentType, "text/plain; charset=utf-8")]+        $ LC8.fromChunks [C8.pack $ show e]++ -- | A Wai Application that acts as a http/https proxy. simpleProxy ::   ProxySettings ->@@ -229,10 +236,10 @@ toClientSettings :: Wai.Request -> ClientSettings toClientSettings req =   case C8.break (== ':') $ Wai.rawPathInfo req of-    (host, "") -> clientSettingsTCP (defaultClientPort req) host-    (host, port') -> case C8.readInt $ C8.drop 1 port' of-      Just (port, _) -> clientSettingsTCP port host-      Nothing -> clientSettingsTCP (defaultClientPort req) host+    (h, "") -> clientSettingsTCP (defaultClientPort req) h+    (h, p') -> case C8.readInt $ C8.drop 1 p' of+      Just (p, _) -> clientSettingsTCP p h+      Nothing -> clientSettingsTCP (defaultClientPort req) h   dropUpstreamHeaders :: (HeaderName, b) -> Bool
+ templates/nginx-test.conf.mustache view
@@ -0,0 +1,42 @@+server {+  listen 80;+  listen [::]:80;+  server_name {{common_name}};+  location / {+    rewrite ^ https://$host$request_uri? permanent;+  }+}++server {+  listen 443 ssl;+  listen [::]:443 ssl;+  server_name {{commonName}};+  server_tokens off;+  ssl_certificate /etc/tmp-proc/certs/certificate.pem;+  ssl_certificate_key /etc/tmp-proc/certs/key.pem;+  ssl_buffer_size 8k;+  ssl_protocols TLSv1.2;+  ssl_prefer_server_ciphers on;+  ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;+  ssl_ecdh_curve secp384r1;+  ssl_session_tickets off;+  ssl_stapling on;+  ssl_stapling_verify on;+  resolver 8.8.8.8;++  location / {+    try_files $uri @tmp-proc-target;+  }++  location @tmp-proc-target {+    proxy_pass http://{{targetName}}:{{targetPort}};+    add_header X-Frame-Options "SAMEORIGIN" always;+    add_header X-XSS-Protection "1; mode=block" always;+    add_header X-Content-Type-Options "nosniff" always;+    add_header Referrer-Policy "no-referrer-when-downgrade" always;+    add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always;+  }++  root /var/www/html;+  index index.html index.htm index.nginx-debian.html;+}
test/IntegrationTest.hs view
@@ -1,40 +1,46 @@ {-# LANGUAGE DataKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-}  module Main where -import Control.Monad (when)+import Control.Monad (forM_, when) import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as C8-import Data.Default (Default (..)) import Data.Foldable (for_)+import Data.Maybe (isJust) import Data.Proxy (Proxy (..)) import Data.Text (Text) import qualified Data.Text as Text import Data.Text.Encoding (encodeUtf8) import qualified Network.HTTP.Client as HC-import Network.HTTP.Client.TLS (newTlsManager) import Network.HTTP.Types (status500, statusCode) import Network.Wai   ( Application   , rawPathInfo   , responseLBS   )-import Network.Wai.Handler.Warp (Port, testWithApplication)+import Network.Wai.Handler.Warp (Port)+import Network.Wai.Handler.WarpTLS (tlsSettings) import Network.Wai.Middleware.Delegate   ( ProxySettings (..)+  , defaultSettings   , delegateToProxy   ) import System.Environment (lookupEnv) import System.IO+import Test.Certs.Temp (certificatePath, keyPath, withCertPathsInTmp') import Test.Fetch (fetch) import Test.Hspec import Test.Hspec.TmpProc   ( HList (..)+  , HandlesOf   , HostIpAddress   , Pinged (..)   , Proc (..)@@ -42,102 +48,65 @@   , SvcURI   , hAddr   , handleOf-  , startupAll   , tdescribe-  , terminateAll   , toPinged   , (&:)+  , (&:&)   ) import qualified Test.Hspec.TmpProc as TmpProc import Test.HttpReply+import Test.NginxGateway (NginxGateway (..), mkBadTlsManager) import Test.TestRequests   ( RequestBuilder (..)   , buildRequest+  , nil+  , secure   , testNotProxiedRequests   , testOverRedirectedRequests   , testRequests   )-import Test.WithExtras-  ( defaultTlsSettings-  , testWithTLSApplication-  )   defaultTestSettings :: ProxySettings-defaultTestSettings = def {proxyHost = "httpbin.org", proxyTimeout = 2}+defaultTestSettings = defaultSettings {proxyHost = "httpbin.org", proxyTimeout = 2}   redirectTestSettings :: ProxySettings redirectTestSettings = defaultTestSettings {proxyRedirectCount = 2}  -tmpHostSettings :: ByteString -> ProxySettings -> ProxySettings-tmpHostSettings tmpHost settings = settings {proxyHost = tmpHost}+setProxyHost :: ByteString -> ProxySettings -> ProxySettings+setProxyHost proxyHost ps = ps {proxyHost}   main :: IO () main = do   hSetBuffering stdin NoBuffering   hSetBuffering stdout NoBuffering-  dumpDebug' <- lookupEnv "DEBUG"-  let dumpDebug = maybe False (const True) dumpDebug'-  hspec $ tdescribe "when accessing http-bin in docker" $ do-    -- TODO: reenable when a secure proxy is added to the docker tests-    -- secureProxyTest dumpDebug-    -- secureNotProxiedTest dumpDebug-    insecureRedirectTest dumpDebug-    insecureNotProxiedTest dumpDebug-    insecureProxyTest dumpDebug+  dumpDebug <- isJust <$> lookupEnv "DEBUG"+  hspec $ tdescribe "accessing http-bin in docker" $ do+    forM_ insecureRequestSpecs $ flip runRequestSpecsTest dumpDebug+    forM_ secureRequestSpecs $ flip runRequestSpecsTest dumpDebug  -defaultTestDelegate :: ProxySettings -> IO Application-defaultTestDelegate s = do+mkSampleApp :: ProxySettings -> IO Application+mkSampleApp s = do   -- delegate everything but /status/418   let handleFunnyStatus req = rawPathInfo req /= "/status/418"       dummyApp _ respond = respond $ responseLBS status500 [] "I should have been proxied"--  manager <- newTlsManager+  manager <- mkBadTlsManager   return $ delegateToProxy s manager handleFunnyStatus dummyApp  -toHost :: HttpBinFixture -> ByteString-toHost fixture = encodeUtf8 $ hAddr $ handleOf @"tmp-http-bin" Proxy fixture---fixtureApp :: HttpBinFixture -> IO Application-fixtureApp fixture = defaultTestDelegate $ tmpHostSettings (toHost fixture) defaultTestSettings---redirectApp :: HttpBinFixture -> IO Application-redirectApp fixture = defaultTestDelegate $ tmpHostSettings (toHost fixture) redirectTestSettings---testWithInsecureProxy' :: ((HttpBinFixture, Port) -> IO a) -> IO a-testWithInsecureProxy' = TmpProc.testWithApplication onlyHttpBin fixtureApp---testWithSecureProxy' :: ((HttpBinFixture, Port) -> IO a) -> IO a-testWithSecureProxy' action = do-  tls <- defaultTlsSettings-  TmpProc.testWithTLSApplication tls onlyHttpBin fixtureApp action---testWithInsecureProxy :: (Port -> IO ()) -> IO ()-testWithInsecureProxy = testWithApplication (defaultTestDelegate defaultTestSettings)---testWithSecureProxy :: (Port -> IO ()) -> IO ()-testWithSecureProxy withPort = do-  tls <- defaultTlsSettings-  testWithTLSApplication tls (defaultTestDelegate defaultTestSettings) withPort---testWithInsecureRedirects' :: ((HttpBinFixture, Port) -> IO ()) -> IO ()-testWithInsecureRedirects' = TmpProc.testWithApplication onlyHttpBin redirectApp+mkSampleApp' :: (HostOf a) => ProxySettings -> HandlesOf a -> IO Application+mkSampleApp' settings f = mkSampleApp $ setProxyHost (hostOf f) settings  -tmpHostBuilder :: HttpBinFixture -> RequestBuilder -> RequestBuilder-tmpHostBuilder fixture builder = builder {rbHost = toHost fixture}+testWithSecureProxy :: ((ReverseProxyFixture, Port) -> IO a) -> IO a+testWithSecureProxy action = withCertPathsInTmp' $ \cp -> do+  let tls = tlsSettings (certificatePath cp) (keyPath cp)+      app = mkSampleApp' defaultTestSettings+  TmpProc.testWithTLSApplication tls nginxAndHttpBin app action   onDirectAndProxy :: (HttpReply -> HttpReply -> IO ()) -> Bool -> Int -> RequestBuilder -> IO ()@@ -167,88 +136,209 @@   f direct proxied  -insecureNotProxiedTest :: Bool -> Spec-insecureNotProxiedTest debug =-  let scheme = "HTTP"-      desc = "Proxy on " ++ scheme ++ " should fail"-      assertNeq = onDirectAndProxy assertHttpRepliesDiffer debug-   in aroundAll testWithInsecureProxy' $ describe desc $ do-        for_ testNotProxiedRequests $ \(title, modifier) -> do-          let shouldNotMatch (f, p) = assertNeq p $ modifier $ tmpHostBuilder f def-          it (scheme ++ " " ++ title) shouldNotMatch+check ::+  (HostOf a) =>+  (HttpReply -> HttpReply -> IO ()) ->+  (RequestBuilder -> RequestBuilder) ->+  Bool ->+  RequestBuilder ->+  (HandlesOf a, Int) ->+  IO ()+check assertReplies modifier debug core (f, p) =+  let+    builder = modifier $ hostBuilder f core+   in+    onDirectAndProxy assertReplies debug p builder  -insecureRedirectTest :: Bool -> Spec-insecureRedirectTest debug =-  let scheme = "HTTP"-      desc = "Proxy over " ++ scheme ++ " with too many redirects differs"-      assertNeq = onDirectAndProxy assertHttpRepliesDiffer debug-   in aroundAll testWithInsecureRedirects' $ describe desc $ do-        for_ testOverRedirectedRequests $ \(title, modifier) -> do-          let shouldNotMatch (f, p) = assertNeq p $ modifier $ tmpHostBuilder f def-          it (scheme ++ " " ++ title) shouldNotMatch+type RequestSpecs = [(String, RequestBuilder -> RequestBuilder)]  -insecureProxyTest :: Bool -> Spec-insecureProxyTest debug =-  let scheme = "HTTP"-      desc = "Simple " ++ scheme ++ " proxying:"-      assertEq = onDirectAndProxy assertHttpRepliesAreEq debug-   in aroundAll testWithInsecureProxy' $ describe desc $ do-        for_ testRequests $ \(title, modifier) -> do-          let shouldMatch (f, p) = assertEq p $ modifier $ tmpHostBuilder f def-          it (scheme ++ " " ++ title) shouldMatch+data RequestSpecsTest a = RequestSpecsTest+  { stToDesc :: String -> String+  , stSettings :: ProxySettings+  , stWithApp :: forall b. HList a -> ProxySettings -> ((HandlesOf a, Port) -> IO b) -> IO b+  , stAssertReplies :: HttpReply -> HttpReply -> IO ()+  , stProc :: HList a+  , stScheme :: String+  , stCore :: RequestBuilder+  , stRequestSpecs :: RequestSpecs+  }  -secureNotProxiedTest :: Bool -> Spec-secureNotProxiedTest debug =-  let scheme = "HTTPS"-      desc = "Proxy on " ++ scheme ++ " should fail"-      assertNeq = onDirectAndProxy assertHttpRepliesDiffer debug-      def' = def {rbSecure = True}-   in aroundAll testWithSecureProxy' $ describe desc $ do-        for_ testNotProxiedRequests $ \(title, modifier) -> do-          let shouldNotMatch (f, p) = assertNeq p $ modifier $ tmpHostBuilder f def'-          it (scheme ++ " " ++ title) shouldNotMatch+withSecureApp ::+  (HostOf a, TmpProc.AreProcs a) =>+  HList a ->+  ProxySettings ->+  ((HandlesOf a, Port) -> IO b) ->+  IO b+withSecureApp procs settings action = withCertPathsInTmp' $ \cp -> do+  let tls = tlsSettings (certificatePath cp) (keyPath cp)+      app = mkSampleApp' settings+  TmpProc.testWithTLSApplication tls procs app action  -secureProxyTest :: Bool -> Spec-secureProxyTest debug =-  let scheme = "HTTPS"-      desc = "Simple " ++ scheme ++ " proxying:"-      assertEq = onDirectAndProxy assertHttpRepliesAreEq debug-      def' = def {rbSecure = True}-   in aroundAll testWithSecureProxy' $ describe desc $ do-        for_ testRequests $ \(title, modifier) -> do-          let shouldMatch (f, p) = assertEq p $ modifier $ tmpHostBuilder f def'-          it (scheme ++ " " ++ title) shouldMatch+withInsecureApp ::+  (HostOf a, TmpProc.AreProcs a) =>+  HList a ->+  ProxySettings ->+  ((HandlesOf a, Port) -> IO b) ->+  IO b+withInsecureApp procs settings = TmpProc.testWithApplication procs $ mkSampleApp' settings  --- | A data type representing a connection to a HttpBin server.-data HttpBin = HttpBin+runRequestSpecsTest ::+  (TmpProc.AreProcs procs, HostOf procs) =>+  RequestSpecsTest procs ->+  Bool ->+  Spec+runRequestSpecsTest st debug =+  let RequestSpecsTest+        { stToDesc+        , stSettings+        , stAssertReplies+        , stProc+        , stScheme+        , stCore+        , stRequestSpecs+        , stWithApp+        } = st+      desc = stToDesc stScheme+      withApp = stWithApp stProc stSettings+   in aroundAll withApp $ describe desc $ do+        for_ stRequestSpecs $ \(title, modifier) -> do+          it (stScheme ++ " " ++ title) $ check stAssertReplies modifier debug stCore  -type HttpBinFixture = HList '[ProcHandle HttpBin]+insecureRequestSpecs :: [RequestSpecsTest '[HttpBin]]+insecureRequestSpecs = [insecureRedirects, insecureNotProxied, insecureProxy]  -onlyHttpBin :: HList '[HttpBin]-onlyHttpBin = HttpBin &: HNil+insecureProxy :: RequestSpecsTest '[HttpBin]+insecureProxy =+  RequestSpecsTest+    { stToDesc = \s -> "Simple " ++ s ++ " proxying:"+    , stSettings = defaultTestSettings+    , stAssertReplies = assertHttpRepliesAreEq+    , stProc = onlyHttpBin+    , stScheme = "HTTP"+    , stCore = nil+    , stRequestSpecs = testRequests+    , stWithApp = withInsecureApp+    }  -setupHttpBin :: IO HttpBinFixture-setupHttpBin = startupAll onlyHttpBin+insecureRedirects :: RequestSpecsTest '[HttpBin]+insecureRedirects =+  RequestSpecsTest+    { stToDesc = \s -> "Proxy over " ++ s ++ " with too many redirects differs"+    , stSettings = redirectTestSettings+    , stAssertReplies = assertHttpRepliesDiffer+    , stProc = onlyHttpBin+    , stScheme = "HTTP"+    , stCore = nil+    , stRequestSpecs = testOverRedirectedRequests+    , stWithApp = withInsecureApp+    }  -withHttpBin :: SpecWith HttpBinFixture -> Spec-withHttpBin = beforeAll setupHttpBin . afterAll terminateAll+insecureNotProxied :: RequestSpecsTest '[HttpBin]+insecureNotProxied =+  RequestSpecsTest+    { stToDesc = \s -> "Proxy on " ++ s ++ " should fail"+    , stSettings = defaultTestSettings+    , stAssertReplies = assertHttpRepliesDiffer+    , stProc = onlyHttpBin+    , stScheme = "HTTP"+    , stCore = nil+    , stRequestSpecs = testNotProxiedRequests+    , stWithApp = withInsecureApp+    }  +secureRequestSpecs :: [RequestSpecsTest '[NginxGateway, HttpBin]]+secureRequestSpecs = [secureNotProxied, secureProxy]+++secureNotProxied :: RequestSpecsTest '[NginxGateway, HttpBin]+secureNotProxied =+  RequestSpecsTest+    { stToDesc = \s -> "Proxy on " ++ s ++ " should fail"+    , stSettings = defaultTestSettings+    , stAssertReplies = assertHttpRepliesDiffer+    , stProc = nginxAndHttpBin+    , stScheme = "HTTPS"+    , stCore = sNil+    , stRequestSpecs = testNotProxiedRequests+    , stWithApp = withSecureApp+    }+++secureProxy :: RequestSpecsTest '[NginxGateway, HttpBin]+secureProxy =+  RequestSpecsTest+    { stToDesc = \s -> "Simple " ++ s ++ " proxying:"+    , stSettings = defaultTestSettings+    , stAssertReplies = assertHttpRepliesAreEq+    , stProc = nginxAndHttpBin+    , stScheme = "HTTPS"+    , stCore = sNil+    , stRequestSpecs = testRequests+    , stWithApp = withSecureApp+    }+++sNil :: RequestBuilder+sNil = secure nil+++class HostOf a where+  hostOf :: HandlesOf a -> ByteString+++instance HostOf '[HttpBin] where+  hostOf f = encodeUtf8 $ hAddr $ handleOf @"tmp-http-bin" Proxy f+++instance HostOf '[NginxGateway, HttpBin] where+  hostOf f = encodeUtf8 $ hAddr $ handleOf @"nginx-test" Proxy f+++hostBuilder :: (HostOf a) => HandlesOf a -> RequestBuilder -> RequestBuilder+hostBuilder f builder = builder {rbHost = hostOf f}+++type ReverseProxyFixture = HandlesOf '[NginxGateway, HttpBin]+++aGateway :: NginxGateway+aGateway =+  NginxGateway+    { ngCommonName = "localhost"+    , ngTargetPort = 80+    , ngTargetName = "tmp-http-bin"+    }+++nginxAndHttpBin :: HList '[NginxGateway, HttpBin]+nginxAndHttpBin = aGateway &:& HttpBin+++-- | A data type representing a connection to a HttpBin server.+data HttpBin = HttpBin+++type HttpBinFixture = HandlesOf '[HttpBin]+++onlyHttpBin :: HList '[HttpBin]+onlyHttpBin = HttpBin &: HNil++ -- | Run HttpBin using tmp-proc. instance Proc HttpBin where   type Image HttpBin = "kennethreitz/httpbin"   type Name HttpBin = "tmp-http-bin"--   uriOf = mkUri'   runArgs = []   reset _ = pure ()
+ test/Network/Connection/CPP.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE CPP #-}++{- |+Module      : Network.Connection.CPP+Copyright   : (c) 2022 Tim Emiola+Maintainer  : Tim Emiola <adetokunbo@emio.la>+SPDX-License-Identifier: BSD3+-}+module Network.Connection.CPP (noCheckSettings) where++import Network.Connection (TLSSettings (..))+++#if MIN_VERSION_crypton_connection(0,4,0)+import Data.Default (def)+#endif++#if MIN_VERSION_crypton_connection(0,4,0)+noCheckSettings :: TLSSettings+noCheckSettings = TLSSettingsSimple True False False def+#else+noCheckSettings :: TLSSettings+noCheckSettings = TLSSettingsSimple True False False+#endif
test/Test/Fetch.hs view
@@ -24,7 +24,7 @@ import qualified Data.Conduit.Binary as CB import Data.Int (Int64) import Data.Maybe (fromMaybe)-import Network.Connection (TLSSettings (..))+import Network.Connection.CPP (noCheckSettings) import Network.HTTP.Client   ( BodyReader   , Request@@ -39,14 +39,13 @@   , withResponse   ) import Network.HTTP.Client.TLS (mkManagerSettings)--- import Network.HTTP.Simple (setRequestManager, withResponse) import Network.HTTP.Types (hContentLength, statusCode) import Test.HttpReply   fetch :: Request -> IO HttpReply fetch req = do-  let mSettings = mkManagerSettings (TLSSettingsSimple True False False) Nothing+  let mSettings = mkManagerSettings noCheckSettings Nothing       mSettings' = managerSetProxy proxyFromRequest mSettings   m <- newManager mSettings'   withResponse req m getSrc
+ test/Test/NginxGateway.hs view
@@ -0,0 +1,231 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}+{-# LANGUAGE TypeFamilies #-}++{- |+Module      : Test.NginxGateway+Copyright   : (c) 2022 Tim Emiola+Maintainer  : Tim Emiola <adetokunbo@emio.la>+SPDX-License-Identifier: BSD3+-}+module Test.NginxGateway+  ( -- * data types+    NginxGateway (..)+  , NginxPrep (..)++    -- * ping via https+  , pingHttps++    -- * a TLS manager that ignores errors+  , mkBadTlsManager+  )+where++import qualified Data.ByteString.Char8 as C8+import Data.Data (Proxy (..))+import Data.List (find)+import Data.Text (Text)+import qualified Data.Text as Text+import qualified Data.Text.IO as Text+import Network.Connection.CPP (noCheckSettings)+import qualified Network.HTTP.Client as HC+import qualified Network.HTTP.Client.TLS as HC+import Network.HTTP.Types.Header (hHost)+import Network.HTTP.Types.Status (statusCode)+import Paths_wai_middleware_delegate (getDataDir)+import System.Directory (createDirectory, removeDirectoryRecursive)+import System.FilePath ((</>))+import System.IO.Temp (createTempDirectory, getCanonicalTemporaryDirectory)+import System.Posix.ByteString (getEffectiveGroupID, getEffectiveUserID)+import System.Posix.Types (GroupID, UserID)+import System.TmpProc+  ( HostIpAddress+  , Pinged (..)+  , Preparer (..)+  , Proc (..)+  , ProcHandle (..)+  , SlimHandle (..)+  , SvcURI+  , ToRunCmd (..)+  , toPinged+  )+import Test.Certs.Temp (CertPaths (..), defaultConfig, generateAndStore)+import Text.Mustache+  ( ToMustache (..)+  , automaticCompile+  , object+  , substitute+  , (~>)+  )+++-- | Run Nginx as a temporary process.+instance Proc NginxGateway where+  -- use this linuxserver.io nginx as it is setup to allow easy override of+  -- config+  type Image NginxGateway = "lscr.io/linuxserver/nginx"+  type Name NginxGateway = "nginx-test"+  uriOf = httpUri+  runArgs = []+  reset _ = pure ()+  ping = pingHttps+++instance ToRunCmd NginxGateway NginxPrep where+  toRunCmd = toRunCmd'+++instance Preparer NginxGateway NginxPrep where+  prepare = prepare'+  tidy = tidy'+++{- | Configures launch of a container thats uses nginx as a gateway (a.k.a+reverse proxy).+-}+data NginxGateway = NginxGateway+  { ngCommonName :: !Text+  , ngTargetPort :: !Int+  , ngTargetName :: !Text+  }+  deriving (Eq, Show)+++instance ToMustache NginxGateway where+  toMustache nt =+    object+      [ "commonName" ~> ngCommonName nt+      , "targetPort" ~> ngTargetPort nt+      , "targetName" ~> ngTargetName nt+      ]+++-- | Values obtained while preparing to launch the nginx container+data NginxPrep = NginxPrep+  { npUserID :: !UserID+  , npGroupID :: !GroupID+  , npVolumeRoot :: !FilePath+  }+  deriving (Eq, Show)+++instance ToMustache NginxPrep where+  toMustache np =+    object+      [ "targetDir" ~> npVolumeRoot np+      ]+++templateName :: FilePath+templateName = "nginx-test.conf.mustache"+++toConfCertsDirs :: FilePath -> (FilePath, FilePath)+toConfCertsDirs topDir = (topDir </> "conf", topDir </> "certs")+++dockerCertsDir :: FilePath+dockerCertsDir = "/etc/tmp-proc/certs"+++dockerConf :: FilePath+dockerConf = "/data/conf/nginx.conf"+++createWorkingDirs :: IO FilePath+createWorkingDirs = do+  tmpDir <- getCanonicalTemporaryDirectory+  topDir <- createTempDirectory tmpDir "nginx-test"+  let (confDir, certsDir) = toConfCertsDirs topDir+  createDirectory confDir+  createDirectory certsDir+  pure topDir+++tidy' :: NginxGateway -> NginxPrep -> IO ()+tidy' _ np = removeDirectoryRecursive $ npVolumeRoot np+++toRunCmd' :: NginxGateway -> NginxPrep -> [Text]+toRunCmd' _ np =+  -- specify user ID and group ID to fix volume mount permissions+  -- mount volume /etc/tmp-proc/certs as target-dir/certs+  -- mount volume /etc/tmp-proc/nginx as target-dir/nginx+  let (confDir, certsDir) = toConfCertsDirs $ npVolumeRoot np+      confPath = confDir </> "nginx.conf"+      envArg name v =+        [ "-e"+        , name ++ "=" ++ show v+        ]+      volumeArg actualPath hostedPath =+        [ "-v"+        , actualPath ++ ":" ++ hostedPath+        ]+      confArg = volumeArg confPath $ dockerConf ++ ":ro"+      certsArg = volumeArg certsDir dockerCertsDir+      puidArg = envArg "PUID" $ npUserID np+      guidArg = envArg "GUID" $ npGroupID np+   in Text.pack <$> confArg ++ certsArg ++ puidArg ++ guidArg+++-- Prepare+-- expand the template with commonName to target-dir/nginx+-- create certs with commonName to target-dir/certs+-- used fixed cert basenames (certificate.pem and key.pem)+prepare' :: [SlimHandle] -> NginxGateway -> IO NginxPrep+prepare' views nt@NginxGateway {ngTargetName = name} = do+  case find ((== name) . shName) views of+    Nothing -> error $ "could not find host " <> show name+    Just _ -> do+      templateDir <- (</> "templates") <$> getDataDir+      compiled <- automaticCompile [templateDir] templateName+      case compiled of+        Left err -> error $ "the template did not compile:" ++ show err+        Right template -> do+          npVolumeRoot <- createWorkingDirs+          npUserID <- getEffectiveUserID+          npGroupID <- getEffectiveGroupID+          let (confDir, cpDir) = toConfCertsDirs npVolumeRoot+              cp =+                CertPaths+                  { cpKey = "key.pem"+                  , cpCert = "certificate.pem"+                  , cpDir+                  }+              np = NginxPrep {npUserID, npGroupID, npVolumeRoot}+          generateAndStore cp defaultConfig+          Text.writeFile (confDir </> "nginx.conf") $ substitute template (nt, np)+          pure np+++-- | Make a uri access the http-bin server.+httpUri :: HostIpAddress -> SvcURI+httpUri ip = "http://" <> C8.pack (Text.unpack ip) <> "/"+++pingHttps :: ProcHandle a -> IO Pinged+pingHttps handle = toPinged @HC.HttpException Proxy $ do+  gotStatus <- httpsGet handle "/status/200"+  if gotStatus == 200 then pure OK else pure NotOK+++-- use TLS settings that disable hostname verification. What's not+-- currently possible is to actually specify the hostname to use for SNI+-- that differs from the connection IP address, that's not supported by+-- http-client-tls+mkBadTlsManager :: IO HC.Manager+mkBadTlsManager =+  HC.newTlsManagerWith $ HC.mkManagerSettings noCheckSettings Nothing+++-- | Determine the status from a secure Get to host localhost.+httpsGet :: ProcHandle a -> Text -> IO Int+httpsGet handle urlPath = do+  let theUri = "https://" <> hAddr handle <> "/" <> Text.dropWhile (== '/') urlPath+  manager <- mkBadTlsManager+  getReq <- HC.parseRequest $ Text.unpack theUri+  let withHost = getReq {HC.requestHeaders = [(hHost, "localhost")]}+  statusCode . HC.responseStatus <$> HC.httpLbs withHost manager
test/Test/TestRequests.hs view
@@ -9,12 +9,13 @@   , testPostRequests   , testNotProxiedRequests   , testOverRedirectedRequests+  , nil+  , secure   ) where  import qualified Data.ByteString as BS import qualified Data.ByteString.Char8 as C8-import Data.Default (Default (..)) import Data.Maybe (fromMaybe) import Network.HTTP.Client   ( Request@@ -38,18 +39,22 @@   }  -instance Default RequestBuilder where-  def =-    RequestBuilder-      { rbMethod = methodGet-      , rbSecure = False-      , rbPath = "/"-      , rbBody = Nothing-      , rbHost = "httpbin.org"-      , rbPort = Nothing-      }+nil :: RequestBuilder+nil =+  RequestBuilder+    { rbMethod = methodGet+    , rbSecure = False+    , rbPath = "/"+    , rbBody = Nothing+    , rbHost = "httpbin.org"+    , rbPort = Nothing+    }  +secure :: RequestBuilder -> RequestBuilder+secure x = x {rbSecure = True}++ testRequests :: [(String, RequestBuilder -> RequestBuilder)] testRequests = testGetRequests <> testPostRequests @@ -58,32 +63,31 @@ testGetRequests =   [     ( "GET"-    , (\builder -> builder {rbPath = "/get"})+    , \builder -> builder {rbPath = "/get"}     )   ,     ( "GET (with a query)"-    , (\builder -> builder {rbPath = "/get?a=10&b=whatever"})+    , \builder -> builder {rbPath = "/get?a=10&b=whatever"}     )   ,     ( "GET (multiple redirects)"-    , (\builder -> builder {rbPath = "/redirect/3"})+    , \builder -> builder {rbPath = "/redirect/3"}     )   ,     ( "GET (with a body)"-    , ( \builder ->-          builder-            { rbPath = "/get"-            , rbBody = Just $ RequestBodyBS "Hello httpbin!"-            }-      )+    , \builder ->+        builder+          { rbPath = "/get"+          , rbBody = Just $ RequestBodyBS "Hello httpbin!"+          }     )   ,     ( "GET (forbidden resource)"-    , (\builder -> builder {rbPath = "/status/403"})+    , \builder -> builder {rbPath = "/status/403"}     )   ,     ( "GET (missing resource)"-    , (\builder -> builder {rbPath = "/status/404"})+    , \builder -> builder {rbPath = "/status/404"}     )   ] @@ -92,7 +96,7 @@ testNotProxiedRequests =   [     ( "GET (funny resource - differs on proxy)"-    , (\builder -> builder {rbPath = "/status/418"})+    , \builder -> builder {rbPath = "/status/418"}     )   ] @@ -101,7 +105,7 @@ testOverRedirectedRequests =   [     ( "GET (multiple redirects)"-    , (\builder -> builder {rbPath = "/redirect/3"})+    , \builder -> builder {rbPath = "/redirect/3"}     )   ] @@ -110,29 +114,28 @@ testPostRequests =   [     ( "POST"-    , (\builder -> builder {rbMethod = methodPost, rbPath = "/post"})+    , \builder -> builder {rbMethod = methodPost, rbPath = "/post"}     )   ,     ( "POST (with a query)"-    , (\builder -> builder {rbMethod = methodPost, rbPath = "/post?a=10&b=whatever"})+    , \builder -> builder {rbMethod = methodPost, rbPath = "/post?a=10&b=whatever"}     )   ,     ( "POST (with a body)"-    , ( \builder ->-          builder-            { rbPath = "/post"-            , rbBody = Just $ RequestBodyBS "Hello httpbin!"-            , rbMethod = methodPost-            }-      )+    , \builder ->+        builder+          { rbPath = "/post"+          , rbBody = Just $ RequestBodyBS "Hello httpbin!"+          , rbMethod = methodPost+          }     )   ,     ( "POST (forbidden resource)"-    , (\builder -> builder {rbMethod = methodPost, rbPath = "/status/403"})+    , \builder -> builder {rbMethod = methodPost, rbPath = "/status/403"}     )   ,     ( "POST (missing resource)"-    , (\builder -> builder {rbMethod = methodPost, rbPath = "/status/404"})+    , \builder -> builder {rbMethod = methodPost, rbPath = "/status/404"}     )   ] @@ -144,7 +147,7 @@         | rbSecure = "https"         | otherwise = "http"       portStr = maybe "" (\x -> ":" ++ show x) rbPort-      url = scheme ++ "://" ++ (C8.unpack rbHost) ++ portStr ++ rbPath+      url = scheme ++ "://" ++ C8.unpack rbHost ++ portStr ++ rbPath   req <- parseRequest url   return $     req
test/Test/WithExtras.hs view
@@ -1,6 +1,5 @@ module Test.WithExtras-  ( defaultTlsSettings-  , testWithTLSApplication+  ( testWithTLSApplication   , testWithTLSApplicationSettings   , withTLSApplication   , withTLSApplicationSettings@@ -22,34 +21,31 @@   ) import Network.Wai.Handler.Warp.Internal (settingsBeforeMainLoop) import Network.Wai.Handler.WarpTLS-  ( TLSSettings-  , runTLSSocket+  ( runTLSSocket   , tlsSettings   )-import Paths_wai_middleware_delegate (getDataFileName)+import Test.Certs.Temp (certificatePath, keyPath, withCertPathsInTmp')  --- | The settings used in the integration tests-defaultTlsSettings :: IO TLSSettings-defaultTlsSettings =-  tlsSettings-    <$> (getDataFileName "test/certificate.pem")-    <*> (getDataFileName "test/key.pem")+runTLSSocket' :: Settings -> Socket -> Application -> IO ()+runTLSSocket' settings sock app = withCertPathsInTmp' $ \cp -> do+  let tls = tlsSettings (certificatePath cp) (keyPath cp)+  runTLSSocket tls settings sock app   {- | Runs the given 'Application' on a free port. Passes the port to the given  operation and executes it, while the 'Application' is running. Shuts down the  server before returning. -}-withTLSApplication :: TLSSettings -> IO Application -> (Port -> IO a) -> IO a+withTLSApplication :: IO Application -> (Port -> IO a) -> IO a withTLSApplication = withTLSApplicationSettings defaultSettings   {- | 'withTLSApplication' with given 'Settings'. This will ignore the port value  set by 'setPort' in 'Settings'. -}-withTLSApplicationSettings :: Settings -> TLSSettings -> IO Application -> (Port -> IO a) -> IO a-withTLSApplicationSettings settings' tlsSettings' mkApp action = do+withTLSApplicationSettings :: Settings -> IO Application -> (Port -> IO a) -> IO a+withTLSApplicationSettings settings' mkApp action = do   app <- mkApp   withFreePort $ \(port, sock) -> do     started <- mkWaiter@@ -60,19 +56,19 @@             }     result <-       race-        (runTLSSocket tlsSettings' settings sock app)+        (runTLSSocket' settings sock app)         (waitFor started >> action port)     case result of       Left () -> throwIO $ ErrorCall "Unexpected: runSettingsSocket exited"       Right x -> return x  -testWithTLSApplication :: TLSSettings -> IO Application -> (Port -> IO a) -> IO a+testWithTLSApplication :: IO Application -> (Port -> IO a) -> IO a testWithTLSApplication = testWithTLSApplicationSettings defaultSettings  -testWithTLSApplicationSettings :: Settings -> TLSSettings -> IO Application -> (Port -> IO a) -> IO a-testWithTLSApplicationSettings settings tlsSettings' mkApp action = do+testWithTLSApplicationSettings :: Settings -> IO Application -> (Port -> IO a) -> IO a+testWithTLSApplicationSettings settings mkApp action = do   callingThread <- myThreadId   app <- mkApp   let wrappedApp request respond =@@ -81,7 +77,7 @@             (defaultShouldDisplayException e)             (throwTo callingThread e)           throwIO e-  withTLSApplicationSettings settings tlsSettings' (return wrappedApp) action+  withTLSApplicationSettings settings (return wrappedApp) action   data Waiter a = Waiter
− test/certificate.csr
@@ -1,27 +0,0 @@------BEGIN CERTIFICATE REQUEST------MIIElTCCAn0CAQAwUDELMAkGA1UEBhMCSlAxDDAKBgNVBAgTA0ZVSzERMA8GA1UE-BxMISXRvc2hpbWExDDAKBgNVBAoTA0RpczESMBAGA1UEAxMJbG9jYWxob3N0MIIC-IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAyVm9wmE7aMKsQB+ZEhH/p8F+-hiYLSZNLmvCF+oHmCQ5/swiK/M4jY4XFsPXSqow4RND2bHw/YW2ZYK5EDj8VYiyZ-MVRm0oNTzbBH6BNkQhGsqS+5h/8rtFmijatD7E4XQ2n9PW/anGH8dC9TXPm9Z3h9-mjbmw/eG/LcVxl3WZho/ryMWBtFVB27MpWVhBVI0n0AkhkWLntlJKMAj6HJqOwDy-A9tCf2tauAxCFTXslE4zUrKRMRADP2xO+Gu3A1upzTWO9jpLzbr6z7efOrEJda4O-0N7f7tUmOgOvNEOiAKkJDjtOECZ8IATGkkdQb+6a72P5/dcg667IZhMpAJC6bJs9-c6Qkn0bm+kMa336dGhLIFw6WcZSrAsjGCva2XRQZ9TSQIAOmMe9eqah8TxSrffuc-s6+xdJ1kpsa5j8aezBQ7Pm0dXVWQms5KmsgXW5GF3cVW9PFooSOdeMvGhDH5Igt9-YPX3ai/xiOSJw9Hm/NtUK+P1B4O7vTGkmKYszkjm9oYsan50WTvhAYMmg2l1V5d9-LgZTo+o1KOnpLB89xXcYry+WXtDKIXqW/f2tzEl4QmXzNgyMrTFGkSLyX09EmOLY-Wp0qqTSjAYh761/o8iP4T72OHSlE1vExLwQkrze+RnrqEbj1LU4MeX94nwcUvaWZ-amhbwI36QePCcebhGdcCAwEAAaAAMA0GCSqGSIb3DQEBBQUAA4ICAQBVayoYDWsg-oA3x0OaVdfz44El8kc2sTsB96C28vVRVotjkuUqmgakJatpCwFz4pS5HUO9ykJb9-SqopqNF13dX562HOiuP0CtX1SfqlSO6jQ8lQ03neHe7m4rYUyOKqIiEtHNZHBCyR-2GHbGqBxW/7VOk0iF6UWNHUhsQMG+muabIgyJECsXxoWJU6nO34dnjBhiNtsAX5n-84glwKq2eTF+Z3IyoI8UVRBKPcxO6Y8G66zvPo7Sc2LX2lhtRM9crC628a7U7WkF-kK6w1vAANbgh0gBHcqPN1fhqIG7VWdeZKRrt3S8d01wrP6o5GXvtGeIj7SUCUOr2-RzL3nOSNFLDyWZlKgGZsOjZqz3W846Zma6maNWiQAxyzA/w4lP97S5NL0ZgUQ88P-US12y4o3ZJoo5j4SwHSDeGx+jI7ZPttXPxGyaeFQhqPf5KEUPqUIOKy10MCMNvE1-3u/iu/nY+PtlKQ5pW0QWkLS92CQ9Gthhj6bL/iWG+o8+j31f1JuBkArKIX/ORNYZ-/i+Xw4a1ZuUuix79smW0ZM9hxHJvyDeO8duvQKBSYnW5ufieRj7XCCbBl/2UabYZ-nabKHAu4S0qCgG1XSSP+blpFYGV6RMCEqe7xeBpXy3Mg2dP9Trx5K8rxvPeZm5AW-cRqVssVWMITl9jBzDHoeRlPO/zmgqND/nw==------END CERTIFICATE REQUEST-----
− test/certificate.pem
@@ -1,30 +0,0 @@------BEGIN CERTIFICATE------MIIFHDCCAwQCCQCskmvu2cKsdTANBgkqhkiG9w0BAQUFADBQMQswCQYDVQQGEwJK-UDEMMAoGA1UECBMDRlVLMREwDwYDVQQHEwhJdG9zaGltYTEMMAoGA1UEChMDRGlz-MRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTgwODAzMDM1NjA4WhcNMTgwOTAyMDM1-NjA4WjBQMQswCQYDVQQGEwJKUDEMMAoGA1UECBMDRlVLMREwDwYDVQQHEwhJdG9z-aGltYTEMMAoGA1UEChMDRGlzMRIwEAYDVQQDEwlsb2NhbGhvc3QwggIiMA0GCSqG-SIb3DQEBAQUAA4ICDwAwggIKAoICAQDJWb3CYTtowqxAH5kSEf+nwX6GJgtJk0ua-8IX6geYJDn+zCIr8ziNjhcWw9dKqjDhE0PZsfD9hbZlgrkQOPxViLJkxVGbSg1PN-sEfoE2RCEaypL7mH/yu0WaKNq0PsThdDaf09b9qcYfx0L1Nc+b1neH2aNubD94b8-txXGXdZmGj+vIxYG0VUHbsylZWEFUjSfQCSGRYue2UkowCPocmo7APID20J/a1q4-DEIVNeyUTjNSspExEAM/bE74a7cDW6nNNY72OkvNuvrPt586sQl1rg7Q3t/u1SY6-A680Q6IAqQkOO04QJnwgBMaSR1Bv7prvY/n91yDrrshmEykAkLpsmz1zpCSfRub6-Qxrffp0aEsgXDpZxlKsCyMYK9rZdFBn1NJAgA6Yx716pqHxPFKt9+5yzr7F0nWSm-xrmPxp7MFDs+bR1dVZCazkqayBdbkYXdxVb08WihI514y8aEMfkiC31g9fdqL/GI-5InD0eb821Qr4/UHg7u9MaSYpizOSOb2hixqfnRZO+EBgyaDaXVXl30uBlOj6jUo-6eksHz3FdxivL5Ze0Mohepb9/a3MSXhCZfM2DIytMUaRIvJfT0SY4thanSqpNKMB-iHvrX+jyI/hPvY4dKUTW8TEvBCSvN75GeuoRuPUtTgx5f3ifBxS9pZlqaFvAjfpB-48Jx5uEZ1wIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQAaPy1vsDxmDuJbBTg2rj2N-jyRoA6wAYLrX+uVu3uoWlQNuKLuv0VHDMjAa0K2pGpLT0txRCP/fsxeK7ygCG2T5-rvLr44W4tr7OBw9NBdjk9Z95VSbDHqGcW79Zzi2RBeQtnmwo0psROH6mq9dUOHD9-R4DLnNajg8Hd+jeoPXSo8QZtVi9I6XzjJIZ4BP1HuZ2ol4HUmZ87/kHRvPI0zKmM-DKNuzSpQ4/SXwI+onUMVSdc0KaVtaAexcQDZJQmjEgVFUiee5gkrHlMGV9JvwMUw-QW1E40Pz7MjitUhSBIPm9NXXorSAZJxRdXGaGkH1iYBObVvHLuc8FdoOB8VUF7fy-SkazzOczcLtBB82pyI8HIzOeinySZ3L6GQP40ZFcbC6WaCujEgl2h6G4kYrtcNar-Xz7NJOlqUl6l7hC1sDfT+8PpImFTb3gwLFHY0wVRY7iTdpSGBiM7kOs8RKcSeuwc-3ZOcO7+WpfREwnLLiYulxgBQbOCLXOy6F+6jEUYlsrkO5NmefIfhCUBIl9nhEMN1-30//qcre0uNtSN4b9DdfUnXqkH1RxvrUPQ5nZlVqs/zum6b/Mpns8dFkmXOOVaY4-advpJtviXpf9+acbA3s020bspNaFah4WcIxhOBv+q/be0EL9xvQ9r4V4Ee7oSMNE-t7YrGY8rPiubWMWRZw5UwA==------END CERTIFICATE-----
− test/key.pem
@@ -1,51 +0,0 @@------BEGIN RSA PRIVATE KEY------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------END RSA PRIVATE KEY-----
wai-middleware-delegate.cabal view
@@ -1,6 +1,6 @@ cabal-version:      3.0 name:               wai-middleware-delegate-version:            0.1.4.1+version:            0.1.4.2 synopsis:           WAI middleware that delegates handling of requests. description:   [WAI](http://hackage.haskell.org/package/wai) middleware that intercepts requests@@ -23,8 +23,7 @@ build-type:         Simple extra-source-files: ChangeLog.md data-files:-  test/*.csr-  test/*.pem+  templates/*.mustache  source-repository head   type:     git@@ -35,21 +34,24 @@   hs-source-dirs:   src   build-depends:     , async              ^>=2.2.1-    , base               >=4.10      && <5+    , base               >=4.10 && <5     , blaze-builder      ^>=0.4.1.0-    , bytestring         >=0.10.8.2  && <0.11 || >=0.11.3.1 && <0.13.0+    , bytestring         >=0.10.8.2 && <0.11 || >=0.11.3 && <0.13     , case-insensitive   ^>=1.2.0.11     , conduit            ^>=1.3.0.3     , conduit-extra      ^>=1.3.0     , data-default       ^>=0.7.1.1-    , http-client        >=0.5.13.1  && <0.8.0.0-    , http-types         >=0.12.1    && <0.13.0-    , streaming-commons  >=0.2.1.0   && <0.3.0.0-    , text               >=1.2.3     && <2.2+    , http-client        >=0.5.13 && <0.8+    , http-client-tls    >=0.3 && <0.4+    , http-types         >=0.12.1 && <0.13+    , streaming-commons  >=0.2.1 && <0.3+    , text               >=1.2.3 && <2.1.2     , wai                ^>=3.2     , wai-conduit        ^>=3.0.0.4    default-language: Haskell2010+  ghc-options:+    -Wall -Wincomplete-uni-patterns -Wpartial-fields -fwarn-tabs  test-suite integration-test   type:             exitcode-stdio-1.0@@ -59,8 +61,10 @@     Paths_wai_middleware_delegate     Test.Fetch     Test.HttpReply+    Test.NginxGateway     Test.TestRequests     Test.WithExtras+    Network.Connection.CPP    hs-source-dirs:   test   build-depends:@@ -72,24 +76,31 @@     , case-insensitive     , conduit     , conduit-extra-    , crypton-connection       >=0.3.1+    , crypton-connection       >=0.3.1 && < 0.5     , data-default+    , directory                >=1.3 && <1.4+    , filepath                 >=1.4 && <1.6     , hspec                    >=2.1     , hspec-tmp-proc     , http-client     , http-client-tls     , http-types+    , mustache                 >=2.3 && <2.5     , network     , random                   >=1.1     , resourcet+    , temporary                >=1.2 && <1.5+    , test-certs               >=0.1 && <0.2     , text-    , tmp-proc                 >=0.5.2+    , tmp-proc                 >=0.7     , vault     , wai     , wai-conduit     , wai-middleware-delegate     , warp     , warp-tls                 >=3.4+    , unix                     >=2.7 && <2.9    default-language: Haskell2010-  ghc-options:      -Wall -fwarn-tabs -threaded+  ghc-options:+    -Wall -Wincomplete-uni-patterns -Wpartial-fields -fwarn-tabs -threaded