@@ -4,7 +4,7 @@
5module Network.Wai.Handler.Warp.Request (
5module Network.Wai.Handler.Warp.Request (
6 recvRequest
6 recvRequest
7− , takeHeaders
7+ , headerLines
10import Control.Applicative
10import Control.Applicative
@@ -28,64 +28,24 @@ import Network.Wai.Handler.Warp.Types
28import Prelude hiding (lines)
28import Prelude hiding (lines)
29import qualified Network.Wai.Handler.Warp.Timeout as Timeout
29import qualified Network.Wai.Handler.Warp.Timeout as Timeout
31+----------------------------------------------------------------
31-- FIXME come up with good values here
33-- FIXME come up with good values here
32maxTotalHeaderLength :: Int
34maxTotalHeaderLength :: Int
33maxTotalHeaderLength = 50 * 1024
35maxTotalHeaderLength = 50 * 1024
37+----------------------------------------------------------------
35recvRequest :: Connection
39recvRequest :: Connection
36 -> Timeout.Handle
40 -> Timeout.Handle
37 -> SockAddr
41 -> SockAddr
38 -> Source IO ByteString
42 -> Source IO ByteString
39 -> IO (Request, IO (ResumableSource IO ByteString))
43 -> IO (Request, IO (ResumableSource IO ByteString))
40−recvRequest conn timeoutHandle remoteHost' src1 = do
44+recvRequest conn timeoutHandle remoteHost' src0 = do
41− (src2, headers') <- src1 $$+ takeHeaders
45+ (src, hdrlines) <- src0 $$+ headerLines
42− recvRequest' conn timeoutHandle headers' remoteHost' src2
44−handleExpect :: Connection
46− -> ([H.Header] -> [H.Header])
49−handleExpect _ _ front [] = return $ front []
50−handleExpect conn hv front (("expect", "100-continue"):rest) = do
53− then "HTTP/1.1 100 Continue\r\n\r\n"
54− else "HTTP/1.0 100 Continue\r\n\r\n"
56−handleExpect conn hv front (x:xs) = handleExpect conn hv (front . (x:)) xs
58−-- | Parse a set of header lines and body into a 'Request'.
59−recvRequest' :: Connection
63− -> ResumableSource IO ByteString -- FIXME was buffered
64− -> IO (Request, IO (ResumableSource IO ByteString))
65−recvRequest' _ _ [] _ _ = throwIO $ NotEnoughLines []
66−recvRequest' conn timeoutHandle (firstLine:otherLines) remoteHost' src = do
67− (method, rpath', gets, httpversion) <- parseFirst firstLine
46+ (method, rpath, gets, httpversion, hdr') <- parseHeaderLines hdrlines
69− | S.null rpath' = "/"
70− | "http://" `S.isPrefixOf` rpath' = snd $ S.breakByte 47 $ S.drop 7 rpath'
73− $ handleExpect conn httpversion id
47+ hdr <- liftIO $ handleExpect conn httpversion id hdr'
74− (map parseHeaderNoAttr otherLines)
76− case lookup H.hContentLength heads of
78− Just bs -> readInt bs
79− let chunked = maybe False ((== "chunked") . CI.foldCase)
80− $ lookup hTransferEncoding heads
81− (rbody, getSource) <-
48+ (rbody, bodyLength, getSource) <- bodyAndSource src hdr
84− ref <- I.newIORef (src, NeedLen)
85− return (chunkedSource ref, fst <$> I.readIORef ref)
87− ibs <- IsolatedBSSource <$> I.newIORef (len0, src)
88− return (ibsIsolate ibs, ibsDone ibs)
90 return (Request
50 return (Request
91 { requestMethod = method
51 { requestMethod = method
@@ -94,29 +54,32 @@ recvRequest' conn timeoutHandle (firstLine:otherLines) remoteHost' src = do
94 , rawPathInfo = rpath
54 , rawPathInfo = rpath
95 , rawQueryString = gets
55 , rawQueryString = gets
96 , queryString = H.parseQuery gets
56 , queryString = H.parseQuery gets
97− , requestHeaders = heads
57+ , requestHeaders = hdr
98 , isSecure = False
58 , isSecure = False
99 , remoteHost = remoteHost'
59 , remoteHost = remoteHost'
100− , requestBody = do
60+ , requestBody = timeoutBody timeoutHandle rbody
101− -- Timeout handling was paused after receiving the full request
102− -- headers. Now we need to resume it to avoid a slowloris
103− -- attack during request body sending.
104− liftIO $ Timeout.resume timeoutHandle
105− -- As soon as we finish receiving the request body, whether
106− -- because the application is not interested in more bytes, or
107− -- because there is no more data available, pause the timeout
109− addCleanup (const $ liftIO $ Timeout.pause timeoutHandle) rbody
110 , vault = mempty
61 , vault = mempty
111−#if MIN_VERSION_wai(1, 4, 0)
112− , requestBodyLength =
62+ , requestBodyLength = bodyLength
115− else KnownLength $ fromIntegral len0
117 }, getSource)
63 }, getSource)
119−{-# INLINE parseFirst #-} -- FIXME is this inline necessary? the function is only called from one place and not exported
65+----------------------------------------------------------------
67+headerLines :: Sink ByteString IO [ByteString]
69+ await >>= maybe (throwIO ConnectionClosedByPeer) (push (THStatus 0 id id))
71+----------------------------------------------------------------
73+parseHeaderLines :: [ByteString] -> IO (ByteString, ByteString, ByteString, H.HttpVersion, [H.Header])
74+parseHeaderLines [] = throwIO $ NotEnoughLines []
75+parseHeaderLines (firstLine:otherLines) = do
76+ (method, rpath', gets, httpversion) <- parseFirst firstLine
77+ let rpath = parseRpath rpath'
78+ hdr = map parseHeaderNoAttr otherLines
79+ return (method, rpath, gets, httpversion, hdr)
81+----------------------------------------------------------------
120parseFirst :: ByteString
83parseFirst :: ByteString
121 -> IO (ByteString, ByteString, ByteString, H.HttpVersion)
84 -> IO (ByteString, ByteString, ByteString, H.HttpVersion)
122parseFirst s =
85parseFirst s =
@@ -134,12 +97,78 @@ parseFirst s =
134 else throwIO NonHttp
97 else throwIO NonHttp
135 _ -> throwIO $ BadFirstLine $ B.unpack s
98 _ -> throwIO $ BadFirstLine $ B.unpack s
100+----------------------------------------------------------------
137parseHeaderNoAttr :: ByteString -> H.Header
102parseHeaderNoAttr :: ByteString -> H.Header
138parseHeaderNoAttr s =
103parseHeaderNoAttr s =
139 let (k, rest) = S.breakByte 58 s -- ':'
104 let (k, rest) = S.breakByte 58 s -- ':'
140 rest' = S.dropWhile (\c -> c == 32 || c == 9) $ S.drop 1 rest
105 rest' = S.dropWhile (\c -> c == 32 || c == 9) $ S.drop 1 rest
141 in (CI.mk k, rest')
106 in (CI.mk k, rest')
108+----------------------------------------------------------------
110+parseRpath :: ByteString -> ByteString
112+ | S.null rpath' = "/"
113+ | "http://" `S.isPrefixOf` rpath' = snd $ S.breakByte 47 $ S.drop 7 rpath'
114+ | otherwise = rpath'
116+----------------------------------------------------------------
118+handleExpect :: Connection
120+ -> ([H.Header] -> [H.Header])
123+handleExpect _ _ front [] = return $ front []
124+handleExpect conn hv front (("expect", "100-continue"):rest) = do
127+ then "HTTP/1.1 100 Continue\r\n\r\n"
128+ else "HTTP/1.0 100 Continue\r\n\r\n"
130+handleExpect conn hv front (x:xs) = handleExpect conn hv (front . (x:)) xs
132+----------------------------------------------------------------
134+bodyAndSource :: ResumableSource IO ByteString
136+ -> IO (Source IO ByteString
138+ ,IO (ResumableSource IO ByteString))
139+bodyAndSource src hdr
141+ ref <- I.newIORef (src, NeedLen)
142+ return (chunkedSource ref, ChunkedBody, fst <$> I.readIORef ref)
144+ ibs <- IsolatedBSSource <$> I.newIORef (len0, src)
145+ return (ibsIsolate ibs, bodyLen, ibsDone ibs)
147+ len0 = case lookup H.hContentLength hdr of
149+ Just bs -> readInt bs
150+ bodyLen = KnownLength $ fromIntegral len0
151+ chunked = isChunked hdr
153+isChunked :: [H.Header] -> Bool
154+isChunked hdr = maybe False ((== "chunked") . CI.foldCase) $ lookup hTransferEncoding hdr
156+----------------------------------------------------------------
158+timeoutBody :: Timeout.Handle -> Source IO ByteString -> Source IO ByteString
159+timeoutBody timeoutHandle rbody = do
160+ -- Timeout handling was paused after receiving the full request
161+ -- headers. Now we need to resume it to avoid a slowloris
162+ -- attack during request body sending.
163+ liftIO $ Timeout.resume timeoutHandle
164+ -- As soon as we finish receiving the request body, whether
165+ -- because the application is not interested in more bytes, or
166+ -- because there is no more data available, pause the timeout
168+ addCleanup (const $ liftIO $ Timeout.pause timeoutHandle) rbody
170+----------------------------------------------------------------
143type BSEndo = ByteString -> ByteString
172type BSEndo = ByteString -> ByteString
144type BSEndoList = [ByteString] -> [ByteString]
173type BSEndoList = [ByteString] -> [ByteString]
@@ -148,10 +177,7 @@ data THStatus = THStatus
148 BSEndoList -- previously parsed lines
177 BSEndoList -- previously parsed lines
149 BSEndo -- bytestrings to be prepended
178 BSEndo -- bytestrings to be prepended
151−{-# INLINE takeHeaders #-}
180+----------------------------------------------------------------
152−takeHeaders :: Sink ByteString IO [ByteString]
154− await >>= maybe (throwIO ConnectionClosedByPeer) (push (THStatus 0 id id))
156close :: Sink ByteString IO a
182close :: Sink ByteString IO a
157close = throwIO IncompleteHeaders
183close = throwIO IncompleteHeaders