packages feed

wai-extra 3.1.0 → 3.1.1

raw patch · 4 files changed

+138/−1 lines, 4 files

Files

ChangeLog.md view
@@ -1,5 +1,9 @@ # Changelog for wai-extra +## 3.1.1++* 'Network.Wai.Middleware.RequestSizeLimit': Add a new middleware to reject request bodies above a certain size. [#818](https://github.com/yesodweb/wai/pull/818/files)+ ## 3.1.0  * `Network.Wai.Test`: Add support for source locations to assertion primitives [#817](https://github.com/yesodweb/wai/pull/817)
+ Network/Wai/Middleware/RequestSizeLimit.hs view
@@ -0,0 +1,73 @@+-- | The functions in this module allow you to limit the total size of incoming request bodies.+--+-- Limiting incoming request body size helps protect your server against denial-of-service (DOS) attacks, +-- in which an attacker sends huge bodies to your server.+module Network.Wai.Middleware.RequestSizeLimit +    ( +    -- * Middleware+      requestSizeLimitMiddleware+    -- * Constructing 'RequestSizeLimitSettings'+    , defaultRequestSizeLimitSettings+    -- * 'RequestSizeLimitSettings' and accessors+    , RequestSizeLimitSettings+    , setMaxLengthForRequest+    , setOnLengthExceeded+    ) where++import Network.Wai+import Network.Wai.Request+import qualified Data.ByteString.Lazy as BSL+import Data.Word (Word64)+import Network.HTTP.Types.Status (requestEntityTooLarge413)+import qualified Data.ByteString.Lazy.Char8 as LS8+import Control.Exception (try, catch)+import Data.Monoid ((<>))+import Network.Wai.Middleware.RequestSizeLimit.Internal (RequestSizeLimitSettings(..), setMaxLengthForRequest, setOnLengthExceeded)++-- | Create a 'RequestSizeLimitSettings' with these settings:+--+-- * 2MB size limit for all requests+-- * When the limit is exceeded, return a plain text response describing the error, with a 413 status code.+--+-- @since 3.1.1+defaultRequestSizeLimitSettings :: RequestSizeLimitSettings+defaultRequestSizeLimitSettings = RequestSizeLimitSettings+    { maxLengthForRequest = \_req -> pure $ Just $ 2 * 1024 * 1024+    , onLengthExceeded = \maxLen _app req sendResponse -> sendResponse (tooLargeResponse maxLen (requestBodyLength req))+    }++-- | Middleware to limit request bodies to a certain size.+--+-- This uses 'requestSizeCheck' under the hood; see that function for details.+--+-- @since 3.1.1+requestSizeLimitMiddleware :: RequestSizeLimitSettings -> Middleware+requestSizeLimitMiddleware settings app req sendResponse = do+    maybeMaxLen <- (maxLengthForRequest settings) req++    case maybeMaxLen of+        Nothing -> app req sendResponse+        Just maxLen -> do+            eitherSizeExceptionOrNewReq <- try (requestSizeCheck maxLen req)+            case eitherSizeExceptionOrNewReq of+                -- In the case of a known-length request, RequestSizeException will be thrown immediately+                Left (RequestSizeException _maxLen) -> handleLengthExceeded maxLen+                -- In the case of a chunked request (unknown length), RequestSizeException will be thrown during the processing of a body+                Right newReq -> app newReq sendResponse `catch` \(RequestSizeException _maxLen) -> handleLengthExceeded maxLen++    where+        handleLengthExceeded maxLen = (onLengthExceeded settings) maxLen app req sendResponse++tooLargeResponse :: Word64 -> RequestBodyLength -> Response+tooLargeResponse maxLen bodyLen = responseLBS+    requestEntityTooLarge413+    [("Content-Type", "text/plain")]+    (BSL.concat +        [ "Request body too large to be processed. The maximum size is "+        , (LS8.pack (show maxLen))+        , " bytes; your request body was "+        , case bodyLen of+            KnownLength knownLen -> (LS8.pack (show knownLen)) <> " bytes."+            ChunkedBody -> "split into chunks, whose total size is unknown, but exceeded the limit."+        , " . If you're the developer of this site, you can configure the maximum length with `requestSizeLimitMiddleware`."+        ])
+ Network/Wai/Middleware/RequestSizeLimit/Internal.hs view
@@ -0,0 +1,57 @@+-- | Internal constructors and helper functions. Note that no guarantees are given for stability of these interfaces.+module Network.Wai.Middleware.RequestSizeLimit.Internal+    ( RequestSizeLimitSettings(..)+    , setMaxLengthForRequest+    , setOnLengthExceeded+    ) where++import Network.Wai+import Data.Word (Word64)++-- | Settings to configure 'requestSizeLimitMiddleware'.+--+-- This type (but not the constructor, or record fields) is exported from "Network.Wai.Middleware.RequestSizeLimit".+-- Since the constructor isn't exported, create a default value with 'defaultRequestSizeLimitSettings' first,+-- then set the values using 'setMaxLengthForRequest' and 'setOnLengthExceeded' (See the examples below).+--+-- If you need to access the constructor directly, it's exported from "Network.Wai.Middleware.RequestSizeLimit.Internal".+--+-- ==== __Examples__+--+-- ===== Conditionally setting the limit based on the request+-- > {-# LANGUAGE OverloadedStrings #-}+-- > import Network.Wai+-- > import Network.Wai.Middleware.RequestSizeLimit+-- >+-- > let megabyte = 1024 * 1024+-- > let sizeForReq req = if pathInfo req == ["upload", "image"] then pure $ Just $ megabyte * 20 else pure $ Just $ megabyte * 2+-- > let finalSettings = setMaxLengthForRequest sizeForReq defaultRequestSizeLimitSettings+--+-- ===== JSON response+-- > {-# LANGUAGE OverloadedStrings #-}+-- > import Network.Wai+-- > import Network.Wai.Middleware.RequestSizeLimit+-- > import Network.HTTP.Types.Status (requestEntityTooLarge413)+-- > import Data.Aeson+-- > import Data.Text (Text)+-- >+-- > let jsonResponse = \_maxLen _app _req sendResponse -> sendResponse $ responseLBS requestEntityTooLarge413 [("Content-Type", "application/json")] (encode $ object ["error" .= ("request size too large" :: Text)])+-- > let finalSettings = setOnLengthExceeded jsonResponse defaultRequestSizeLimitSettings+--+-- @since 3.1.1+data RequestSizeLimitSettings = RequestSizeLimitSettings+    { maxLengthForRequest :: Request -> IO (Maybe Word64) -- ^ Function to determine the maximum request size in bytes for the request. Return 'Nothing' for no limit. Since 3.1.1+    , onLengthExceeded :: Word64 -> Middleware -- ^ Callback function when maximum length is exceeded. The 'Word64' argument is the limit computed by 'maxLengthForRequest'. Since 3.1.1+    }++-- | Function to determine the maximum request size in bytes for the request. Return 'Nothing' for no limit.+--+-- @since 3.1.1+setMaxLengthForRequest :: (Request -> IO (Maybe Word64)) -> RequestSizeLimitSettings -> RequestSizeLimitSettings+setMaxLengthForRequest fn settings = settings { maxLengthForRequest = fn }++-- | Callback function when maximum length is exceeded. The 'Word64' argument is the limit computed by 'setMaxLengthForRequest'.+--+-- @since 3.1.1+setOnLengthExceeded :: (Word64 -> Middleware) -> RequestSizeLimitSettings -> RequestSizeLimitSettings+setOnLengthExceeded fn settings = settings { onLengthExceeded = fn }
wai-extra.cabal view
@@ -1,5 +1,5 @@ Name:                wai-extra-Version:             3.1.0+Version:             3.1.1 Synopsis:            Provides some basic WAI handlers and middleware. description:   Provides basic WAI handler and middleware functionality:@@ -157,6 +157,8 @@                      Network.Wai.Test.Internal                      Network.Wai.EventSource                      Network.Wai.EventSource.EventStream+                     Network.Wai.Middleware.RequestSizeLimit+                     Network.Wai.Middleware.RequestSizeLimit.Internal   other-modules:     Network.Wai.Middleware.RequestLogger.Internal   default-language:          Haskell2010   ghc-options:       -Wall@@ -206,6 +208,7 @@                    , time                    , case-insensitive                    , http2+                   , aeson     ghc-options:     -Wall     default-language:          Haskell2010