on master help sign in
add requestSizeCheck
commitb78723420241dcb99fffe50e44e1e022cefdc439 authorwinterland <winterland1989@gmail.com> dateTue 22 Mar 2016, 12:02 (11 years ago) parentdd0e4ce
3 files changed+116−40
wai-extra/Network/Wai/Request.hs+46−1 wai-extra/test/Network/Wai/RequestSpec.hs+69−39 wai-extra/wai-extra.cabal+1
wai-extra/Network/Wai/Request.hs+46−1
@@ -3,15 +3,22 @@
33module Network.Wai.Request
44 ( appearsSecure
55 , guessApproot
6+ , RequestSizeException(..)
7+ , requestSizeCheck
68 ) where
79
810import Data.ByteString (ByteString)
911import Data.Maybe (fromMaybe)
1012import Network.HTTP.Types (HeaderName)
11−import Network.Wai (Request, isSecure, requestHeaders, requestHeaderHost)
13+import Network.Wai
1214
1315import qualified Data.ByteString as S
1416import qualified Data.ByteString.Char8 as C
17+import Control.Exception (Exception, throwIO)
18+import Data.Typeable (Typeable)
19+import Data.Word (Word64)
20+import Data.IORef (atomicModifyIORef', newIORef)
21+
1522
1623-- | Does this request appear to have been made over an SSL connection?
1724--
@@ -49,3 +56,41 @@ guessApproot :: Request -> ByteString
4956guessApproot req =
5057 (if appearsSecure req then "https://" else "http://") `S.append`
5158 (fromMaybe "localhost" $ requestHeaderHost req)
59+
60+-- | see 'requestSizeCheck'
61+data RequestSizeException
62+ = RequestSizeException Word64
63+ deriving (Eq, Ord, Typeable)
64+
65+instance Exception RequestSizeException
66+
67+instance Show RequestSizeException where
68+ showsPrec p (RequestSizeException limit) =
69+ showString ("Request Body is larger than ") . showsPrec p limit . showString " bytes."
70+
71+-- | Check request body size to avoid server crash when request is too large.
72+--
73+-- This function first checks @'requestBodyLength'@, if content-length is known
74+-- but larger than limit, or it's unknown but we have received too many chunks,
75+-- a 'RequestSizeException' are thrown when user use @'requestBody'@ to extract
76+-- request body inside IO.
77+requestSizeCheck :: Word64 -> Request -> IO Request
78+requestSizeCheck maxSize req =
79+ case requestBodyLength req of
80+ KnownLength len ->
81+ if len > maxSize
82+ then return $ req { requestBody = throwIO (RequestSizeException maxSize) }
83+ else return req
84+ ChunkedBody -> do
85+ currentSize <- newIORef 0
86+ return $ req
87+ { requestBody = do
88+ bs <- requestBody req
89+ total <-
90+ atomicModifyIORef' currentSize $ \sz ->
91+ let nextSize = sz + fromIntegral (S.length bs)
92+ in (nextSize, nextSize)
93+ if total > maxSize
94+ then throwIO (RequestSizeException maxSize)
95+ else return bs
96+ }
wai-extra/test/Network/Wai/RequestSpec.hs+69−39
@@ -8,52 +8,82 @@ import Test.Hspec
88
99import Data.ByteString (ByteString)
1010import Network.HTTP.Types (HeaderName)
11−import Network.Wai (Request(..), defaultRequest)
11+import Network.Wai (Request(..), defaultRequest, RequestBodyLength(..))
1212
1313import Network.Wai.Request
14+import Control.Exception (try)
15+import Control.Monad (forever)
1416
1517main :: IO ()
1618main = hspec spec
1719
1820spec :: Spec
19−spec = describe "appearsSecure" $ do
20− let insecureRequest = defaultRequest
21− { isSecure = False
22− , requestHeaders =
23− [ ("HTTPS", "off")
24− , ("HTTP_X_FORWARDED_SSL", "off")
25− , ("HTTP_X_FORWARDED_SCHEME", "http")
26− , ("HTTP_X_FORWARDED_PROTO", "http,xyz")
27− ]
28− }
29−
30− it "returns False for an insecure request" $
31− insecureRequest `shouldSatisfy` not . appearsSecure
32−
33− it "checks if the Request is actually secure" $ do
34− let req = insecureRequest { isSecure = True }
35−
36− req `shouldSatisfy` appearsSecure
37−
38− it "checks for HTTP: on" $ do
39− let req = addHeader "HTTPS" "on" insecureRequest
40−
41− req `shouldSatisfy` appearsSecure
42−
43− it "checks for HTTP_X_FORWARDED_SSL: on" $ do
44− let req = addHeader "HTTP_X_FORWARDED_SSL" "on" insecureRequest
45−
46− req `shouldSatisfy` appearsSecure
47−
48− it "checks for HTTP_X_FORWARDED_SCHEME: https" $ do
49− let req = addHeader "HTTP_X_FORWARDED_SCHEME" "https" insecureRequest
50−
51− req `shouldSatisfy` appearsSecure
52−
53− it "checks for HTTP_X_FORWARDED_PROTO: https,..." $ do
54− let req = addHeader "HTTP_X_FORWARDED_PROTO" "https,xyz" insecureRequest
55−
56− req `shouldSatisfy` appearsSecure
21+spec = do
22+ describe "requestSizeCheck" $ do
23+ it "too large content length should throw RequestSizeException" $ do
24+ let limit = 1024
25+ largeRequest = defaultRequest
26+ { isSecure = False
27+ , requestBodyLength = KnownLength (limit + 1)
28+ , requestBody = return "repeat this chunk"
29+ }
30+ checkedRequest <- requestSizeCheck limit largeRequest
31+ body <- try (requestBody checkedRequest)
32+ case body of
33+ Left (RequestSizeException l) -> l `shouldBe` limit
34+ Right _ -> expectationFailure "request size check failed"
35+
36+ it "too many chunks should throw RequestSizeException" $ do
37+ let limit = 1024
38+ largeRequest = defaultRequest
39+ { isSecure = False
40+ , requestBodyLength = ChunkedBody
41+ , requestBody = return "repeat this chunk"
42+ }
43+ checkedRequest <- requestSizeCheck limit largeRequest
44+ body <- try (forever $ requestBody checkedRequest)
45+ case body of
46+ Left (RequestSizeException l) -> l `shouldBe` limit
47+ Right _ -> expectationFailure "request size check failed"
48+
49+ describe "appearsSecure" $ do
50+ let insecureRequest = defaultRequest
51+ { isSecure = False
52+ , requestHeaders =
53+ [ ("HTTPS", "off")
54+ , ("HTTP_X_FORWARDED_SSL", "off")
55+ , ("HTTP_X_FORWARDED_SCHEME", "http")
56+ , ("HTTP_X_FORWARDED_PROTO", "http,xyz")
57+ ]
58+ }
59+
60+ it "returns False for an insecure request" $
61+ insecureRequest `shouldSatisfy` not . appearsSecure
62+
63+ it "checks if the Request is actually secure" $ do
64+ let req = insecureRequest { isSecure = True }
65+
66+ req `shouldSatisfy` appearsSecure
67+
68+ it "checks for HTTP: on" $ do
69+ let req = addHeader "HTTPS" "on" insecureRequest
70+
71+ req `shouldSatisfy` appearsSecure
72+
73+ it "checks for HTTP_X_FORWARDED_SSL: on" $ do
74+ let req = addHeader "HTTP_X_FORWARDED_SSL" "on" insecureRequest
75+
76+ req `shouldSatisfy` appearsSecure
77+
78+ it "checks for HTTP_X_FORWARDED_SCHEME: https" $ do
79+ let req = addHeader "HTTP_X_FORWARDED_SCHEME" "https" insecureRequest
80+
81+ req `shouldSatisfy` appearsSecure
82+
83+ it "checks for HTTP_X_FORWARDED_PROTO: https,..." $ do
84+ let req = addHeader "HTTP_X_FORWARDED_PROTO" "https,xyz" insecureRequest
85+
86+ req `shouldSatisfy` appearsSecure
5787
5888addHeader :: HeaderName -> ByteString -> Request -> Request
5989addHeader name value req = req
wai-extra/wai-extra.cabal+1
@@ -151,6 +151,7 @@ Library
151151 Network.Wai.EventSource
152152 Network.Wai.EventSource.EventStream
153153 other-modules: Network.Wai.Middleware.RequestLogger.Internal
154+ default-language: Haskell2010
154155 ghc-options: -Wall
155156
156157test-suite spec