wai-extra 3.1.0 → 3.1.1
raw patch · 4 files changed
+138/−1 lines, 4 files
Files
- ChangeLog.md +4/−0
- Network/Wai/Middleware/RequestSizeLimit.hs +73/−0
- Network/Wai/Middleware/RequestSizeLimit/Internal.hs +57/−0
- wai-extra.cabal +4/−1
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