{-# LANGUAGE CPP #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# OPTIONS_GHC -fno-warn-deprecations #-}
module Network.Wai.Handler.Warp.Request (
recvRequest
, headerLines
, pauseTimeoutKey
, getFileInfoKey
#ifdef MIN_VERSION_crypton_x509
, getClientCertificateKey
#endif
, NoKeepAliveRequest (..)
) where
import qualified Control.Concurrent as Conc (yield)
import UnliftIO (throwIO, Exception)
import Data.Array ((!))
import qualified Data.ByteString as S
import qualified Data.ByteString.Unsafe as SU
import qualified Data.CaseInsensitive as CI
import qualified Data.IORef as I
import Data.Typeable (Typeable)
import qualified Data.Vault.Lazy as Vault
#ifdef MIN_VERSION_crypton_x509
import Data.X509
#endif
import qualified Network.HTTP.Types as H
import Network.Socket (SockAddr)
import Network.Wai
import Network.Wai.Handler.Warp.Types
import Network.Wai.Internal
import Prelude hiding (lines)
import System.IO.Unsafe (unsafePerformIO)
import qualified System.TimeManager as Timeout
import Network.Wai.Handler.Warp.Conduit
import Network.Wai.Handler.Warp.FileInfoCache
import Network.Wai.Handler.Warp.Header
import Network.Wai.Handler.Warp.Imports hiding (readInt)
import Network.Wai.Handler.Warp.ReadInt
import Network.Wai.Handler.Warp.RequestHeader
import Network.Wai.Handler.Warp.Settings (Settings, settingsNoParsePath, settingsMaxTotalHeaderLength)
recvRequest :: Bool
-> Settings
-> Connection
-> InternalInfo
-> Timeout.Handle
-> SockAddr
-> Source
-> Transport
-> IO (Request
,Maybe (I.IORef Int)
,IndexedHeader
,IO ByteString)
recvRequest :: Bool
-> Settings
-> Connection
-> InternalInfo
-> Handle
-> SockAddr
-> Source
-> Transport
-> IO (Request, Maybe (IORef Int), IndexedHeader, IO HeaderValue)
recvRequest Bool
firstRequest Settings
settings Connection
conn InternalInfo
ii Handle
th SockAddr
addr Source
src Transport
transport = do
[HeaderValue]
hdrlines <- Int -> Bool -> Source -> IO [HeaderValue]
headerLines (Settings -> Int
settingsMaxTotalHeaderLength Settings
settings) Bool
firstRequest Source
src
(HeaderValue
method, HeaderValue
unparsedPath, HeaderValue
path, HeaderValue
query, HttpVersion
httpversion, RequestHeaders
hdr) <- [HeaderValue]
-> IO
(HeaderValue, HeaderValue, HeaderValue, HeaderValue, HttpVersion,
RequestHeaders)
parseHeaderLines [HeaderValue]
hdrlines
let idxhdr :: IndexedHeader
idxhdr = RequestHeaders -> IndexedHeader
indexRequestHeader RequestHeaders
hdr
expect :: Maybe HeaderValue
expect = IndexedHeader
idxhdr forall i e. Ix i => Array i e -> i -> e
! forall a. Enum a => a -> Int
fromEnum RequestHeaderIndex
ReqExpect
cl :: Maybe HeaderValue
cl = IndexedHeader
idxhdr forall i e. Ix i => Array i e -> i -> e
! forall a. Enum a => a -> Int
fromEnum RequestHeaderIndex
ReqContentLength
te :: Maybe HeaderValue
te = IndexedHeader
idxhdr forall i e. Ix i => Array i e -> i -> e
! forall a. Enum a => a -> Int
fromEnum RequestHeaderIndex
ReqTransferEncoding
handle100Continue :: IO ()
handle100Continue = Connection -> HttpVersion -> Maybe HeaderValue -> IO ()
handleExpect Connection
conn HttpVersion
httpversion Maybe HeaderValue
expect
rawPath :: HeaderValue
rawPath = if Settings -> Bool
settingsNoParsePath Settings
settings then HeaderValue
unparsedPath else HeaderValue
path
vaultValue :: Vault
vaultValue = forall a. Key a -> a -> Vault -> Vault
Vault.insert Key (IO ())
pauseTimeoutKey (Handle -> IO ()
Timeout.pause Handle
th)
forall a b. (a -> b) -> a -> b
$ forall a. Key a -> a -> Vault -> Vault
Vault.insert Key (FilePath -> IO FileInfo)
getFileInfoKey (InternalInfo -> FilePath -> IO FileInfo
getFileInfo InternalInfo
ii)
#ifdef MIN_VERSION_crypton_x509
forall a b. (a -> b) -> a -> b
$ forall a. Key a -> a -> Vault -> Vault
Vault.insert Key (Maybe CertificateChain)
getClientCertificateKey (Transport -> Maybe CertificateChain
getTransportClientCertificate Transport
transport)
#endif
Vault
Vault.empty
(IO HeaderValue
rbody, Maybe (IORef Int)
remainingRef, RequestBodyLength
bodyLength) <- Source
-> Maybe HeaderValue
-> Maybe HeaderValue
-> IO (IO HeaderValue, Maybe (IORef Int), RequestBodyLength)
bodyAndSource Source
src Maybe HeaderValue
cl Maybe HeaderValue
te
IO HeaderValue
rbody' <- Maybe (IORef Int)
-> Handle -> IO HeaderValue -> IO () -> IO (IO HeaderValue)
timeoutBody Maybe (IORef Int)
remainingRef Handle
th IO HeaderValue
rbody IO ()
handle100Continue
IO HeaderValue
rbodyFlush <- Maybe (IORef Int)
-> Handle -> IO HeaderValue -> IO () -> IO (IO HeaderValue)
timeoutBody Maybe (IORef Int)
remainingRef Handle
th IO HeaderValue
rbody (forall (m :: * -> *) a. Monad m => a -> m a
return ())
let req :: Request
req = Request {
requestMethod :: HeaderValue
requestMethod = HeaderValue
method
, httpVersion :: HttpVersion
httpVersion = HttpVersion
httpversion
, pathInfo :: [Text]
pathInfo = HeaderValue -> [Text]
H.decodePathSegments HeaderValue
path
, rawPathInfo :: HeaderValue
rawPathInfo = HeaderValue
rawPath
, rawQueryString :: HeaderValue
rawQueryString = HeaderValue
query
, queryString :: Query
queryString = HeaderValue -> Query
H.parseQuery HeaderValue
query
, requestHeaders :: RequestHeaders
requestHeaders = RequestHeaders
hdr
, isSecure :: Bool
isSecure = Transport -> Bool
isTransportSecure Transport
transport
, remoteHost :: SockAddr
remoteHost = SockAddr
addr
, requestBody :: IO HeaderValue
requestBody = IO HeaderValue
rbody'
, vault :: Vault
vault = Vault
vaultValue
, requestBodyLength :: RequestBodyLength
requestBodyLength = RequestBodyLength
bodyLength
, requestHeaderHost :: Maybe HeaderValue
requestHeaderHost = IndexedHeader
idxhdr forall i e. Ix i => Array i e -> i -> e
! forall a. Enum a => a -> Int
fromEnum RequestHeaderIndex
ReqHost
, requestHeaderRange :: Maybe HeaderValue
requestHeaderRange = IndexedHeader
idxhdr forall i e. Ix i => Array i e -> i -> e
! forall a. Enum a => a -> Int
fromEnum RequestHeaderIndex
ReqRange
, requestHeaderReferer :: Maybe HeaderValue
requestHeaderReferer = IndexedHeader
idxhdr forall i e. Ix i => Array i e -> i -> e
! forall a. Enum a => a -> Int
fromEnum RequestHeaderIndex
ReqReferer
, requestHeaderUserAgent :: Maybe HeaderValue
requestHeaderUserAgent = IndexedHeader
idxhdr forall i e. Ix i => Array i e -> i -> e
! forall a. Enum a => a -> Int
fromEnum RequestHeaderIndex
ReqUserAgent
}
forall (m :: * -> *) a. Monad m => a -> m a
return (Request
req, Maybe (IORef Int)
remainingRef, IndexedHeader
idxhdr, IO HeaderValue
rbodyFlush)
headerLines :: Int -> Bool -> Source -> IO [ByteString]
Int
maxTotalHeaderLength Bool
firstRequest Source
src = do
HeaderValue
bs <- Source -> IO HeaderValue
readSource Source
src
if HeaderValue -> Bool
S.null HeaderValue
bs
then if Bool
firstRequest then forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO InvalidRequest
ConnectionClosedByPeer else forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO NoKeepAliveRequest
NoKeepAliveRequest
else Int -> Source -> THStatus -> HeaderValue -> IO [HeaderValue]
push Int
maxTotalHeaderLength Source
src (Int -> Int -> BSEndoList -> BSEndo -> THStatus
THStatus Int
0 Int
0 forall a. a -> a
id forall a. a -> a
id) HeaderValue
bs
data NoKeepAliveRequest = NoKeepAliveRequest
deriving (Int -> NoKeepAliveRequest -> ShowS
[NoKeepAliveRequest] -> ShowS
NoKeepAliveRequest -> FilePath
forall a.
(Int -> a -> ShowS) -> (a -> FilePath) -> ([a] -> ShowS) -> Show a
showList :: [NoKeepAliveRequest] -> ShowS
$cshowList :: [NoKeepAliveRequest] -> ShowS
show :: NoKeepAliveRequest -> FilePath
$cshow :: NoKeepAliveRequest -> FilePath
showsPrec :: Int -> NoKeepAliveRequest -> ShowS
$cshowsPrec :: Int -> NoKeepAliveRequest -> ShowS
Show, Typeable)
instance Exception NoKeepAliveRequest
handleExpect :: Connection
-> H.HttpVersion
-> Maybe HeaderValue
-> IO ()
handleExpect :: Connection -> HttpVersion -> Maybe HeaderValue -> IO ()
handleExpect Connection
conn HttpVersion
ver (Just HeaderValue
"100-continue") = do
Connection -> HeaderValue -> IO ()
connSendAll Connection
conn HeaderValue
continue
IO ()
Conc.yield
where
continue :: HeaderValue
continue
| HttpVersion
ver forall a. Eq a => a -> a -> Bool
== HttpVersion
H.http11 = HeaderValue
"HTTP/1.1 100 Continue\r\n\r\n"
| Bool
otherwise = HeaderValue
"HTTP/1.0 100 Continue\r\n\r\n"
handleExpect Connection
_ HttpVersion
_ Maybe HeaderValue
_ = forall (m :: * -> *) a. Monad m => a -> m a
return ()
bodyAndSource :: Source
-> Maybe HeaderValue
-> Maybe HeaderValue
-> IO (IO ByteString
,Maybe (I.IORef Int)
,RequestBodyLength
)
bodyAndSource :: Source
-> Maybe HeaderValue
-> Maybe HeaderValue
-> IO (IO HeaderValue, Maybe (IORef Int), RequestBodyLength)
bodyAndSource Source
src Maybe HeaderValue
cl Maybe HeaderValue
te
| Bool
chunked = do
CSource
csrc <- Source -> IO CSource
mkCSource Source
src
forall (m :: * -> *) a. Monad m => a -> m a
return (CSource -> IO HeaderValue
readCSource CSource
csrc, forall a. Maybe a
Nothing, RequestBodyLength
ChunkedBody)
| Bool
otherwise = do
isrc :: ISource
isrc@(ISource Source
_ IORef Int
remaining) <- Source -> Int -> IO ISource
mkISource Source
src Int
len
forall (m :: * -> *) a. Monad m => a -> m a
return (ISource -> IO HeaderValue
readISource ISource
isrc, forall a. a -> Maybe a
Just IORef Int
remaining, RequestBodyLength
bodyLen)
where
len :: Int
len = Maybe HeaderValue -> Int
toLength Maybe HeaderValue
cl
bodyLen :: RequestBodyLength
bodyLen = Word64 -> RequestBodyLength
KnownLength forall a b. (a -> b) -> a -> b
$ forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
len
chunked :: Bool
chunked = Maybe HeaderValue -> Bool
isChunked Maybe HeaderValue
te
toLength :: Maybe HeaderValue -> Int
toLength :: Maybe HeaderValue -> Int
toLength Maybe HeaderValue
Nothing = Int
0
toLength (Just HeaderValue
bs) = forall a. Integral a => HeaderValue -> a
readInt HeaderValue
bs
isChunked :: Maybe HeaderValue -> Bool
isChunked :: Maybe HeaderValue -> Bool
isChunked (Just HeaderValue
bs) = forall s. FoldCase s => s -> s
CI.foldCase HeaderValue
bs forall a. Eq a => a -> a -> Bool
== HeaderValue
"chunked"
isChunked Maybe HeaderValue
_ = Bool
False
timeoutBody :: Maybe (I.IORef Int)
-> Timeout.Handle
-> IO ByteString
-> IO ()
-> IO (IO ByteString)
timeoutBody :: Maybe (IORef Int)
-> Handle -> IO HeaderValue -> IO () -> IO (IO HeaderValue)
timeoutBody Maybe (IORef Int)
remainingRef Handle
timeoutHandle IO HeaderValue
rbody IO ()
handle100Continue = do
IORef Bool
isFirstRef <- forall a. a -> IO (IORef a)
I.newIORef Bool
True
let checkEmpty :: HeaderValue -> IO Bool
checkEmpty =
case Maybe (IORef Int)
remainingRef of
Maybe (IORef Int)
Nothing -> forall (m :: * -> *) a. Monad m => a -> m a
return forall b c a. (b -> c) -> (a -> b) -> a -> c
. HeaderValue -> Bool
S.null
Just IORef Int
ref -> \HeaderValue
bs -> if HeaderValue -> Bool
S.null HeaderValue
bs
then forall (m :: * -> *) a. Monad m => a -> m a
return Bool
True
else do
Int
x <- forall a. IORef a -> IO a
I.readIORef IORef Int
ref
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$! Int
x forall a. Ord a => a -> a -> Bool
<= Int
0
forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ do
Bool
isFirst <- forall a. IORef a -> IO a
I.readIORef IORef Bool
isFirstRef
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
isFirst forall a b. (a -> b) -> a -> b
$ do
IO ()
handle100Continue
Handle -> IO ()
Timeout.resume Handle
timeoutHandle
forall a. IORef a -> a -> IO ()
I.writeIORef IORef Bool
isFirstRef Bool
False
HeaderValue
bs <- IO HeaderValue
rbody
Bool
isEmpty <- HeaderValue -> IO Bool
checkEmpty HeaderValue
bs
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when Bool
isEmpty (Handle -> IO ()
Timeout.pause Handle
timeoutHandle)
forall (m :: * -> *) a. Monad m => a -> m a
return HeaderValue
bs
type BSEndo = ByteString -> ByteString
type BSEndoList = [ByteString] -> [ByteString]
data THStatus = THStatus
!Int
!Int
BSEndoList
BSEndo
push :: Int -> Source -> THStatus -> ByteString -> IO [ByteString]
push :: Int -> Source -> THStatus -> HeaderValue -> IO [HeaderValue]
push Int
maxTotalHeaderLength Source
src (THStatus Int
totalLen Int
chunkLen BSEndoList
lines BSEndo
prepend) HeaderValue
bs'
| Int
currentTotal forall a. Ord a => a -> a -> Bool
> Int
maxTotalHeaderLength = forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO InvalidRequest
OverLargeHeader
| Bool
otherwise = Maybe (Int, Int, Bool) -> IO [HeaderValue]
push' Maybe (Int, Int, Bool)
mNL
where
currentTotal :: Int
currentTotal = Int
totalLen forall a. Num a => a -> a -> a
+ Int
chunkLen
bs :: HeaderValue
bs = BSEndo
prepend HeaderValue
bs'
bsLen :: Int
bsLen = HeaderValue -> Int
S.length HeaderValue
bs
mNL :: Maybe (Int, Int, Bool)
mNL = do
Int
chunkNL <- Word8 -> HeaderValue -> Maybe Int
S.elemIndex Word8
10 HeaderValue
bs'
let headerNL :: Int
headerNL = Int
chunkNL forall a. Num a => a -> a -> a
+ HeaderValue -> Int
S.length (BSEndo
prepend HeaderValue
"")
chunkNLlen :: Int
chunkNLlen = Int
chunkNL forall a. Num a => a -> a -> a
+ Int
1
if Int
bsLen forall a. Ord a => a -> a -> Bool
> Int
headerNL forall a. Num a => a -> a -> a
+ Int
1 then
let c :: Word8
c = HasCallStack => HeaderValue -> Int -> Word8
S.index HeaderValue
bs (Int
headerNL forall a. Num a => a -> a -> a
+ Int
1)
b :: Bool
b = case Int
headerNL of
Int
0 -> Bool
True
Int
1 -> HasCallStack => HeaderValue -> Int -> Word8
S.index HeaderValue
bs Int
0 forall a. Eq a => a -> a -> Bool
== Word8
13
Int
_ -> Bool
False
isMultiline :: Bool
isMultiline = Bool -> Bool
not Bool
b Bool -> Bool -> Bool
&& (Word8
c forall a. Eq a => a -> a -> Bool
== Word8
32 Bool -> Bool -> Bool
|| Word8
c forall a. Eq a => a -> a -> Bool
== Word8
9)
in forall a. a -> Maybe a
Just (Int
chunkNLlen, Int
headerNL, Bool
isMultiline)
else
forall a. a -> Maybe a
Just (Int
chunkNLlen, Int
headerNL, Bool
False)
{-# INLINE push' #-}
push' :: Maybe (Int, Int, Bool) -> IO [ByteString]
push' :: Maybe (Int, Int, Bool) -> IO [HeaderValue]
push' Maybe (Int, Int, Bool)
Nothing = do
HeaderValue
bst <- Source -> IO HeaderValue
readSource' Source
src
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (HeaderValue -> Bool
S.null HeaderValue
bst) forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO InvalidRequest
IncompleteHeaders
Int -> Source -> THStatus -> HeaderValue -> IO [HeaderValue]
push Int
maxTotalHeaderLength Source
src THStatus
status HeaderValue
bst
where
prepend' :: BSEndo
prepend' = HeaderValue -> BSEndo
S.append HeaderValue
bs
thisChunkLen :: Int
thisChunkLen = HeaderValue -> Int
S.length HeaderValue
bs'
newChunkLen :: Int
newChunkLen = Int
chunkLen forall a. Num a => a -> a -> a
+ Int
thisChunkLen
status :: THStatus
status = Int -> Int -> BSEndoList -> BSEndo -> THStatus
THStatus Int
totalLen Int
newChunkLen BSEndoList
lines BSEndo
prepend'
push' (Just (Int
chunkNLlen, Int
end, Bool
True)) =
Int -> Source -> THStatus -> HeaderValue -> IO [HeaderValue]
push Int
maxTotalHeaderLength Source
src THStatus
status HeaderValue
rest
where
rest :: HeaderValue
rest = Int -> BSEndo
S.drop (Int
end forall a. Num a => a -> a -> a
+ Int
1) HeaderValue
bs
prepend' :: BSEndo
prepend' = HeaderValue -> BSEndo
S.append (Int -> BSEndo
SU.unsafeTake (HeaderValue -> Int -> Int
checkCR HeaderValue
bs Int
end) HeaderValue
bs)
newChunkLen :: Int
newChunkLen = Int
chunkLen forall a. Num a => a -> a -> a
+ Int
chunkNLlen
status :: THStatus
status = Int -> Int -> BSEndoList -> BSEndo -> THStatus
THStatus Int
totalLen Int
newChunkLen BSEndoList
lines BSEndo
prepend'
push' (Just (Int
chunkNLlen, Int
end, Bool
False))
| HeaderValue -> Bool
S.null HeaderValue
line = do
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Int
start forall a. Ord a => a -> a -> Bool
< Int
bsLen) forall a b. (a -> b) -> a -> b
$ Source -> HeaderValue -> IO ()
leftoverSource Source
src (Int -> BSEndo
SU.unsafeDrop Int
start HeaderValue
bs)
forall (m :: * -> *) a. Monad m => a -> m a
return (BSEndoList
lines [])
| Bool
otherwise = let lines' :: BSEndoList
lines' = BSEndoList
lines forall b c a. (b -> c) -> (a -> b) -> a -> c
. (HeaderValue
lineforall a. a -> [a] -> [a]
:)
newTotalLength :: Int
newTotalLength = Int
totalLen forall a. Num a => a -> a -> a
+ Int
chunkLen forall a. Num a => a -> a -> a
+ Int
chunkNLlen
status :: THStatus
status = Int -> Int -> BSEndoList -> BSEndo -> THStatus
THStatus Int
newTotalLength Int
0 BSEndoList
lines' forall a. a -> a
id
in if Int
start forall a. Ord a => a -> a -> Bool
< Int
bsLen then
let bs'' :: HeaderValue
bs'' = Int -> BSEndo
SU.unsafeDrop Int
start HeaderValue
bs
in Int -> Source -> THStatus -> HeaderValue -> IO [HeaderValue]
push Int
maxTotalHeaderLength Source
src THStatus
status HeaderValue
bs''
else do
HeaderValue
bst <- Source -> IO HeaderValue
readSource' Source
src
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (HeaderValue -> Bool
S.null HeaderValue
bs) forall a b. (a -> b) -> a -> b
$ forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO InvalidRequest
IncompleteHeaders
Int -> Source -> THStatus -> HeaderValue -> IO [HeaderValue]
push Int
maxTotalHeaderLength Source
src THStatus
status HeaderValue
bst
where
start :: Int
start = Int
end forall a. Num a => a -> a -> a
+ Int
1
line :: HeaderValue
line = Int -> BSEndo
SU.unsafeTake (HeaderValue -> Int -> Int
checkCR HeaderValue
bs Int
end) HeaderValue
bs
{-# INLINE checkCR #-}
checkCR :: ByteString -> Int -> Int
checkCR :: HeaderValue -> Int -> Int
checkCR HeaderValue
bs Int
pos = if Int
pos forall a. Ord a => a -> a -> Bool
> Int
0 Bool -> Bool -> Bool
&& Word8
13 forall a. Eq a => a -> a -> Bool
== HasCallStack => HeaderValue -> Int -> Word8
S.index HeaderValue
bs Int
p then Int
p else Int
pos
where
!p :: Int
p = Int
pos forall a. Num a => a -> a -> a
- Int
1
pauseTimeoutKey :: Vault.Key (IO ())
pauseTimeoutKey :: Key (IO ())
pauseTimeoutKey = forall a. IO a -> a
unsafePerformIO forall a. IO (Key a)
Vault.newKey
{-# NOINLINE pauseTimeoutKey #-}
getFileInfoKey :: Vault.Key (FilePath -> IO FileInfo)
getFileInfoKey :: Key (FilePath -> IO FileInfo)
getFileInfoKey = forall a. IO a -> a
unsafePerformIO forall a. IO (Key a)
Vault.newKey
{-# NOINLINE getFileInfoKey #-}
#ifdef MIN_VERSION_crypton_x509
getClientCertificateKey :: Vault.Key (Maybe CertificateChain)
getClientCertificateKey :: Key (Maybe CertificateChain)
getClientCertificateKey = forall a. IO a -> a
unsafePerformIO forall a. IO (Key a)
Vault.newKey
{-# NOINLINE getClientCertificateKey #-}
#endif