@@ -0,0 +1,172 @@
1+{-# LANGUAGE DeriveDataTypeable #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE BangPatterns #-}
5+module Network.Wai.Handler.Warp.RequestHeader (
10+import Control.Exception (Exception, throwIO)
11+import Control.Monad (when)
12+import Data.Typeable (Typeable)
13+import qualified Data.ByteString as S
14+import qualified Data.ByteString.Char8 as B (unpack, readInteger)
15+import Data.ByteString.Internal (ByteString(..), memchr)
16+import qualified Data.CaseInsensitive as CI
17+import Data.Word (Word8)
18+import Foreign.ForeignPtr (withForeignPtr)
19+import Foreign.Ptr (Ptr, plusPtr, minusPtr, nullPtr)
20+import Foreign.Storable (peek)
21+import qualified Network.HTTP.Types as H
22+-- import Network.Wai.Handler.Warp.Types
23+import qualified Network.HTTP.Types.Header as HH
25+-- >>> :set -XOverloadedStrings
27+data InvalidRequest = NotEnoughLines [String]
28+ | BadFirstLine String
31+ | ConnectionClosedByPeer
33+ deriving (Eq, Typeable, Show)
35+instance Exception InvalidRequest
37+----------------------------------------------------------------
39+parseHeaderLines :: [ByteString]
42+ ,ByteString -- Path, parsed
47+parseHeaderLines [] = throwIO $ NotEnoughLines []
48+parseHeaderLines (firstLine:otherLines) = do
49+ (method, path', query, httpversion) <- parseRequestLine firstLine
50+ let path = H.extractPath path'
51+ hdr = map parseHeader otherLines
52+ return (method, path', path, query, httpversion, hdr)
54+----------------------------------------------------------------
58+-- >>> parseRequestLine "GET / HTTP/1.1"
59+-- ("GET","/","",HTTP/1.1)
60+-- >>> parseRequestLine "POST /cgi/search.cgi?key=foo HTTP/1.0"
61+-- ("POST","/cgi/search.cgi","?key=foo",HTTP/1.0)
62+-- >>> parseRequestLine "GET "
63+-- *** Exception: Warp: Invalid first line of request: "GET "
64+-- >>> parseRequestLine "GET /NotHTTP UNKNOWN/1.1"
65+-- *** Exception: Warp: Request line specified a non-HTTP request
66+parseRequestLine :: ByteString
71+parseRequestLine requestLine@(PS fptr off len) = withForeignPtr fptr $ \ptr -> do
72+ when (len < 14) $ throwIO baderr
73+ let methodptr = ptr `plusPtr` off
74+ limptr = methodptr `plusPtr` len
75+ lim0 = fromIntegral len
77+ pathptr0 <- memchr methodptr 32 lim0 -- ' '
78+ when (pathptr0 == nullPtr || (limptr `minusPtr` pathptr0) < 11) $
80+ let pathptr = pathptr0 `plusPtr` 1
81+ lim1 = fromIntegral (limptr `minusPtr` pathptr0)
83+ httpptr0 <- memchr pathptr 32 lim1 -- ' '
84+ when (httpptr0 == nullPtr || (limptr `minusPtr` httpptr0) < 9) $
86+ let httpptr = httpptr0 `plusPtr` 1
87+ lim2 = fromIntegral (httpptr0 `minusPtr` pathptr)
90+ !hv <- httpVersion httpptr
91+ queryptr <- memchr pathptr 63 lim2 -- '?'
93+ let !method = bs ptr methodptr pathptr0
95+ | queryptr == nullPtr = bs ptr pathptr httpptr0
96+ | otherwise = bs ptr pathptr queryptr
98+ | queryptr == nullPtr = S.empty
99+ | otherwise = bs ptr queryptr httpptr0
101+ return (method,path,query,hv)
103+ baderr = BadFirstLine $ B.unpack requestLine
104+ check :: Ptr Word8 -> Int -> Word8 -> IO ()
106+ w0 <- peek $ p `plusPtr` n
107+ when (w0 /= w) $ throwIO NonHttp
108+ checkHTTP httpptr = do
109+ check httpptr 0 72 -- 'H'
110+ check httpptr 1 84 -- 'T'
111+ check httpptr 2 84 -- 'T'
112+ check httpptr 3 80 -- 'P'
113+ check httpptr 4 47 -- '/'
114+ check httpptr 6 46 -- '.'
115+ httpVersion httpptr = do
116+ major <- peek $ httpptr `plusPtr` 5
117+ minor <- peek $ httpptr `plusPtr` 7
118+ return $ if major == (49 :: Word8) && minor == (49 :: Word8) then
122+ bs ptr p0 p1 = PS fptr o l
124+ o = p0 `minusPtr` ptr
125+ l = p1 `minusPtr` p0
127+----------------------------------------------------------------
131+-- >>> parseHeader "Content-Length:47"
132+-- ("Content-Length","47")
133+-- >>> parseHeader "Accept-Ranges: bytes"
134+-- ("Accept-Ranges","bytes")
135+-- >>> parseHeader "Host: example.com:8080"
136+-- ("Host","example.com:8080")
137+-- >>> parseHeader "NoSemiColon"
138+-- ("NoSemiColon","")
140+parseHeader :: ByteString -> H.Header
142+ let (k, rest) = S.breakByte 58 s -- ':'
143+ rest' = S.dropWhile (\c -> c == 32 || c == 9) $ S.drop 1 rest
146+parseByteRanges :: S.ByteString -> Maybe HH.ByteRanges
147+parseByteRanges bs1 = do
148+ bs2 <- stripPrefix "bytes=" bs1
149+ (r, bs3) <- range bs2
153+ case stripPrefix "-" bs2 of
155+ (i, bs4) <- B.readInteger bs3
156+ Just (HH.ByteRangeSuffix i, bs4)
158+ (i, bs3) <- B.readInteger bs2
159+ bs4 <- stripPrefix "-" bs3
160+ case B.readInteger bs4 of
161+ Nothing -> Just (HH.ByteRangeFrom i, bs4)
162+ Just (j, bs5) -> Just (HH.ByteRangeFromTo i j, bs5)
164+ case stripPrefix "," bs3 of
165+ Nothing -> Just (front [])
167+ (r, bs5) <- range bs4
168+ ranges (front . (r:)) bs5
171+ | x `S.isPrefixOf` y = Just (S.drop (S.length x) y)
172+ | otherwise = Nothing