|
| 1 | +module Z.Data.HTTP.Request where |
| 2 | + |
| 3 | +import Control.Monad |
| 4 | +import Data.IORef |
| 5 | +import qualified Data.CaseInsensitive as CI |
| 6 | +import qualified Z.Data.Builder as B |
| 7 | +import qualified Z.Data.Parser as P |
| 8 | +import qualified Z.Data.Parser as P |
| 9 | +import qualified Z.Data.Text as T |
| 10 | +import qualified Z.Data.Vector as V |
| 11 | +import qualified Z.Data.Vector.Base as V |
| 12 | +import qualified Z.Data.Vector.Search as V |
| 13 | +import Z.Data.ASCII |
| 14 | +import Z.Data.PrimRef |
| 15 | +import Z.IO |
| 16 | +import Z.IO.Network |
| 17 | + |
| 18 | + |
| 19 | +data HTTPException |
| 20 | + = BadHeaderLine V.Bytes |
| 21 | + | EmptyOrBadContentLength |
| 22 | + | NoHostHeader |
| 23 | + | ClientExpectedShutDown |
| 24 | + deriving Show |
| 25 | + |
| 26 | +instance Exception HTTPException |
| 27 | + |
| 28 | +-- | HTTP standard method (as defined by RFC 2616, and PATCH which is defined |
| 29 | +-- by RFC 5789). |
| 30 | +data Method |
| 31 | + = GET |
| 32 | + | POST |
| 33 | + | HEAD |
| 34 | + | PUT |
| 35 | + | DELETE |
| 36 | + | TRACE |
| 37 | + | CONNECT |
| 38 | + | OPTIONS |
| 39 | + | PATCH |
| 40 | + | CUSTOM_METHOD V.Bytes |
| 41 | + deriving (Eq, Ord) |
| 42 | + |
| 43 | +instance T.Print Method where |
| 44 | + toUTF8BuilderP _ GET = "GET" |
| 45 | + toUTF8BuilderP _ POST = "POST" |
| 46 | + toUTF8BuilderP _ HEAD = "HEAD" |
| 47 | + toUTF8BuilderP _ PUT = "PUT" |
| 48 | + toUTF8BuilderP _ DELETE = "DELETE" |
| 49 | + toUTF8BuilderP _ TRACE = "TRACE" |
| 50 | + toUTF8BuilderP _ CONNECT = "CONNECT" |
| 51 | + toUTF8BuilderP _ OPTIONS = "OPTIONS" |
| 52 | + toUTF8BuilderP _ PATCH = "PATCH" |
| 53 | + toUTF8BuilderP _ (CUSTOM_METHOD bs) = B.bytes bs |
| 54 | + |
| 55 | + |
| 56 | +data Version = Version {-# UNPACK #-} !Int {-# UNPACK #-} !Int |
| 57 | + deriving (Eq, Ord) |
| 58 | + |
| 59 | +instance T.Print Version where |
| 60 | + toUTF8BuilderP _ (Version maj min) = do |
| 61 | + "HTTP" |
| 62 | + B.int maj |
| 63 | + B.encodePrim DOT |
| 64 | + B.int min |
| 65 | + |
| 66 | +data Request = Request |
| 67 | + { requestHost :: V.Bytes |
| 68 | + , requestSecure :: Bool |
| 69 | + , requestRemote :: SocketAddr |
| 70 | + |
| 71 | + , requestMethod :: !Method |
| 72 | + , requestVersion :: !Version |
| 73 | + , requestPathRaw :: !V.Bytes |
| 74 | + , requestPathQuery :: ([V.Bytes], V.Vector (V.Bytes, V.Bytes)) |
| 75 | + |
| 76 | + , requestHeaders :: V.Vector (V.Bytes, V.Bytes) |
| 77 | + |
| 78 | + , requestBody :: Either V.Bytes (Source V.Bytes) |
| 79 | + } |
| 80 | + |
| 81 | + |
| 82 | +requestLineParser :: P.Parser (Method, V.Bytes, Version) |
| 83 | +requestLineParser = do |
| 84 | + -- method |
| 85 | + mbs <- P.takeWhile1 (/= SPACE) |
| 86 | + let !method = case mbs of |
| 87 | + "GET" -> GET |
| 88 | + "POST" -> POST |
| 89 | + "HEAD" -> HEAD |
| 90 | + "PUT" -> PUT |
| 91 | + "DELETE" -> DELETE |
| 92 | + "TRACE" -> TRACE |
| 93 | + "CONNECT" -> CONNECT |
| 94 | + "OPTIONS" -> OPTIONS |
| 95 | + "PATCH" -> PATCH |
| 96 | + _ -> CUSTOM_METHOD mbs |
| 97 | + P.skipWord8 |
| 98 | + |
| 99 | + -- path |
| 100 | + !rawpath <- P.takeWhile1 (/= SPACE) |
| 101 | + P.skipWord8 |
| 102 | + |
| 103 | + -- version |
| 104 | + vbs <- P.bytes "HTTP/" |
| 105 | + majv <- P.satisfy isDigit |
| 106 | + P.word8 DOT |
| 107 | + minv <- P.satisfy isDigit |
| 108 | + let !version = Version (fromIntegral $ majv - DIGIT_0) (fromIntegral $ minv - DIGIT_0) |
| 109 | + |
| 110 | + -- request line end |
| 111 | + P.word8 CARRIAGE_RETURN |
| 112 | + P.word8 NEWLINE |
| 113 | + |
| 114 | + pure (method, rawpath, version) |
| 115 | + |
| 116 | +readRequest :: HasCallStack |
| 117 | + => SocketAddr |
| 118 | + -> Bool |
| 119 | + -> BufferedInput |
| 120 | + -> IO (Bool, Request) -- ^ (keep-alive, request) |
| 121 | +readRequest remoteAddr secure bi = do |
| 122 | + -- some special headers |
| 123 | + contentLenRef <- newCounter 0 |
| 124 | + transferEncodingRef <- newIORef V.empty |
| 125 | + hostRef <- newIORef V.empty |
| 126 | + connectionRef <- newIORef False |
| 127 | + |
| 128 | + (method, rawpath, version) <- readParser requestLineParser bi |
| 129 | + printStdLn (method, rawpath, version) |
| 130 | + |
| 131 | + headers <- readHeaderLines contentLenRef transferEncodingRef hostRef connectionRef |
| 132 | + |
| 133 | + host <- readIORef hostRef |
| 134 | + when (V.null host) $ throwIO NoHostHeader |
| 135 | + |
| 136 | + contentLen <- readPrimIORef contentLenRef |
| 137 | + transferEncoding <- readIORef transferEncodingRef |
| 138 | + keepAlive <- readIORef connectionRef |
| 139 | + |
| 140 | + body <- |
| 141 | + if CI.foldCase transferEncoding == "chunked" |
| 142 | + then sourceChunkedEncoding bi |
| 143 | + else if contentLen > 0 |
| 144 | + then Left <$> readExactly contentLen bi |
| 145 | + else if contentLen == 0 |
| 146 | + then return (Left V.empty) |
| 147 | + else throwIO EmptyOrBadContentLength |
| 148 | + |
| 149 | + pure (keepAlive, Request host secure remoteAddr method version |
| 150 | + rawpath (parsePathQuery rawpath) headers body) |
| 151 | + |
| 152 | + where |
| 153 | + parsePathQuery x = undefined |
| 154 | + sourceChunkedEncoding bi = pure $ Right undefined |
| 155 | + |
| 156 | + readHeaderLines contentLenRef transferEncodingRef hostRef connectionRef = |
| 157 | + let loop !i acc = do |
| 158 | + mhdr <- readLine bi |
| 159 | + case mhdr of |
| 160 | + Just hdr@(V.PrimVector arr s l) -> |
| 161 | + if l == 0 |
| 162 | + then return $! V.packRN i acc |
| 163 | + else do |
| 164 | + let (!n, _) = V.findByte COLON hdr |
| 165 | + if n == l |
| 166 | + then throwIO (BadHeaderLine hdr) |
| 167 | + else do |
| 168 | + let !hdrK = CI.foldCase (V.PrimVector arr s n) |
| 169 | + !hdrV = V.PrimVector arr (s+n+1) (l-n-1) |
| 170 | + |
| 171 | + |
| 172 | + when (hdrK == "content-length") $ |
| 173 | + case P.parse' P.uint hdrV of |
| 174 | + Right l -> writePrimIORef contentLenRef l |
| 175 | + _ -> throwIO (BadHeaderLine hdr) |
| 176 | + |
| 177 | + when (hdrK == "transfer-encoding") $ |
| 178 | + writeIORef transferEncodingRef hdrV |
| 179 | + |
| 180 | + when (hdrK == "host") $ |
| 181 | + writeIORef hostRef hdrV |
| 182 | + |
| 183 | + when (hdrK == "connection") $ |
| 184 | + writeIORef connectionRef $! (hdrV == "keep-alive") |
| 185 | + |
| 186 | + loop (i+1) ((hdrK, hdrV) : acc) |
| 187 | + _ -> throwIO ClientExpectedShutDown |
| 188 | + in loop 0 [] |
| 189 | + |
0 commit comments