tighttp-0.0.0.10: src/Network/TigHTTP/HttpTypes.hs
{-# LANGUAGE OverloadedStrings, PackageImports #-}
module Network.TigHTTP.HttpTypes (
Version(..),
Request(..), RequestType(..), Path(..), Get(..), CacheControl(..),
Accept(..), AcceptLanguage(..), Qvalue(..),
Host(..), Product(..), Connection(..),
Response(..), StatusCode(..), ContentLength(..), contentLength,
ContentType(..), Type(..), Subtype(..), Parameter(..), Charset(..),
TransferEncoding(..),
parseReq, parseResponse, putRequest, putResponse, (+++),
myLast, requestBodyLength, postAddBody,
Post(..),
putPostBody,
requestBody,
requestPath,
AcceptEncoding(..),
HostName,
SetCookie(..), setCookie,
) where
import "monads-tf" Control.Monad.Trans
import Data.Maybe
import Data.List
import Text.Read (readMaybe)
import Data.Char
import qualified Data.ByteString as BS
import qualified Data.ByteString.Char8 as BSC
import Data.Time
import Data.Pipe
import Data.Pipe.List
import Network.TigHTTP.Papillon
import Network.TigHTTP.CookieTime
import Data.HandleLike
type HostName = String
(+++) :: BS.ByteString -> BS.ByteString -> BS.ByteString
(+++) = BS.append
(-:-) :: Char -> BS.ByteString -> BS.ByteString
(-:-) = BSC.cons
data Request h
= RequestGet Path Version Get
| RequestPost Path Version (Post h)
| RequestRaw RequestType Path Version [(BS.ByteString, BS.ByteString)]
requestBodyLength :: Request h -> Maybe Int
requestBodyLength (RequestPost _ _ p) = contentLength <$> postContentLength p
requestBodyLength _ = Nothing
postAddBody :: HandleLike h => Request h -> BS.ByteString -> Request h
postAddBody (RequestPost u v p) b = RequestPost u v $ p { postBody = pp }
where
pp = fromList [b]
postAddBody r _ = r
putPostBody :: HandleLike h => h -> Request h ->
Pipe () BS.ByteString (HandleMonad h) () -> Request h
putPostBody _ (RequestPost u v p) pp = RequestPost u v $ p { postBody = pp }
putPostBody _ r _ = r
requestBody :: HandleLike h => Request h -> Pipe () BS.ByteString (HandleMonad h) ()
requestBody (RequestPost _ _ p) = postBody p
requestBody _ = return ()
requestPath :: Request h -> Path
requestPath (RequestGet p _ _) = p
requestPath (RequestPost p _ _) = p
requestPath (RequestRaw _ p _ _) = p
putRequest :: HandleLike h => h -> Request h -> HandleMonad h ()
putRequest sv (RequestGet uri vsn g) = do
let r = [
Just $ "GET " +++ showPath uri +++ " " +++ showVersion vsn,
("Host: " +++) . showHost <$> getHost g,
("User-Agent: " +++) . BSC.unwords .
map showProduct <$> getUserAgent g,
("Accept: " +++) . BSC.intercalate "," .
map showAccept <$> getAccept g,
("Accept-Language: " +++) . BSC.intercalate "," .
map showAcceptLanguage <$> getAcceptLanguage g,
("Accept-Encoding: " +++) . BSC.intercalate "," .
map showAcceptEncoding <$> getAcceptEncoding g,
("Connection: " +++) . BSC.intercalate "," .
map showConnection <$> getConnection g,
("Cache-Control: " +++) . BSC.intercalate "," .
map showCacheControl <$> getCacheControl g,
("Cookie : " +++) . BSC.intercalate "; " .
map (\(k, v) -> k +++ "=" +++ v) <$>
if null $ getCookie g
then Nothing
else Just $ getCookie g,
Just ""
]
hlDebug sv "medium" . crlf $ catMaybes r
hlPut sv . crlf $ catMaybes r
putRequest sv (RequestPost uri vsn p) = do
let hd = [
Just $ "POST " +++ showPath uri +++ " " +++ showVersion vsn,
("Host: " +++) . showHost <$> postHost p,
("User-Agent: " +++) . BSC.unwords . map showProduct <$> postUserAgent p,
("Accept: " +++) . BSC.intercalate "," . map showAccept <$> postAccept p,
("Accept-Language: " +++) . BSC.intercalate "," .
map showAcceptLanguage <$> postAcceptLanguage p,
("Accept-Encoding: " +++) . BSC.intercalate "," .
map showAcceptEncoding <$> postAcceptEncoding p,
("Connection: " +++) . BSC.intercalate "," .
map showConnection <$> postConnection p,
("Cache-Control: " +++) . BSC.intercalate "," .
map showCacheControl <$> postCacheControl p,
("Content-Type: " +++) . showContentType <$> postContentType p,
("Content-Length: " +++) . showContentLength <$> postContentLength p,
("Cookie : " +++) . BSC.intercalate "; " .
map (\(k, v) -> k +++ "=" +++ v) <$>
if null $ postCookie p
then Nothing
else Just $ postCookie p
] ++ map (\(k, v) -> Just $ k +++ ": " +++ v) (postOthers p) ++ [
Just "" ]
hlDebug sv "medium" . crlf $ catMaybes hd
hlPut sv . crlf $ catMaybes hd
_ <- runPipe $ postBody p =$= putAll sv
return ()
putRequest sv (RequestRaw rt uri vsn kvs) = do
let r = [
Just $ showRequestType rt +++
" " +++ showPath uri +++ " " +++ showVersion vsn ] ++
map (\(k, v) -> Just $ k +++ ": " +++ v) kvs ++ [ Just "" ]
hlPut sv . crlf $ catMaybes r
data Get = Get {
getCacheControl :: Maybe [CacheControl],
getConnection :: Maybe [Connection],
getAccept :: Maybe [Accept],
getAcceptEncoding :: Maybe [AcceptEncoding],
getAcceptLanguage :: Maybe [AcceptLanguage],
getHost :: Maybe Host,
getUserAgent :: Maybe [Product],
getCookie :: [(BS.ByteString, BS.ByteString)],
getOthers :: [(BS.ByteString, BS.ByteString)]
} deriving Show
data Post h = Post {
postCacheControl :: Maybe [CacheControl],
postConnection :: Maybe [Connection],
postTransferEncoding :: Maybe TransferEncoding,
postAccept :: Maybe [Accept],
postAcceptEncoding :: Maybe [AcceptEncoding],
postAcceptLanguage :: Maybe [AcceptLanguage],
postHost :: Maybe Host,
postUserAgent :: Maybe [Product],
postContentLength :: Maybe ContentLength,
postContentType :: Maybe ContentType,
postCookie :: [(BS.ByteString, BS.ByteString)],
postOthers :: [(BS.ByteString, BS.ByteString)],
postBody :: Pipe () BS.ByteString (HandleMonad h) () }
data RequestType
= RequestTypeGet
| RequestTypePost
| RequestTypeRaw BS.ByteString
deriving Show
showRequestType :: RequestType -> BS.ByteString
showRequestType RequestTypeGet = "GET"
showRequestType RequestTypePost = "POST"
showRequestType (RequestTypeRaw rt) = rt
data Path = Path BS.ByteString deriving Show
showPath :: Path -> BS.ByteString
showPath (Path uri) = uri
data Version = Version Int Int deriving Show
showVersion :: Version -> BS.ByteString
showVersion (Version vmjr vmnr) =
"HTTP/" +++ BSC.pack (show vmjr) +++ "." +++ BSC.pack (show vmnr)
parseReq :: HandleLike h => [BS.ByteString] -> Request h
parseReq (h : t) = let
(rt, uri, v) = parseRequestLine h in
parseSep rt uri v $ map separate t
where
separate i = let (k, csv) = BSC.span (/= ':') i in
case BS.splitAt 2 csv of
(": ", v) -> (k, v)
_ -> error "parse: bad"
parseReq [] = error "parse: null request"
parseSep :: HandleLike h => RequestType ->
Path -> Version -> [(BS.ByteString, BS.ByteString)] -> Request h
parseSep RequestTypeGet uri v kvs = RequestGet uri v $ parseGet kvs
parseSep RequestTypePost uri v kvs = RequestPost uri v $ parsePost kvs
parseSep rt uri v kvs = RequestRaw rt uri v kvs
parseRequestLine :: BS.ByteString -> (RequestType, Path, Version)
parseRequestLine rl = let
(rts, uris, vs) = case BSC.words rl of
[r, u, v] -> (r, u, v)
_ -> error $ "Network.TigHTTP.HttpTypes.parseRequestLine: " ++
show rl
rt = case rts of
"GET" -> RequestTypeGet
"POST" -> RequestTypePost
_ -> RequestTypeRaw rts in
(rt, Path uris, parseVersion vs)
parseVersion :: BS.ByteString -> Version
parseVersion httpVns
| ("HTTP/", vns) <- BS.splitAt 5 httpVns = let
(vmjrs, dvmnrs) = BSC.span (/= '.') vns in case BSC.uncons dvmnrs of
Just ('.', vmnrs) -> Version
(read $ BSC.unpack vmjrs) (read $ BSC.unpack vmnrs)
_ -> error "parseVersion: bad http version"
parseVersion _ = error "parseVersion: bad http version"
parseGet :: [(BS.ByteString, BS.ByteString)] -> Get
parseGet kvs = Get {
getHost = parseHost <$> lookup "Host" kvs,
getUserAgent = map parseProduct . sepTkn <$> lookup "User-Agent" kvs,
getAccept = map parseAccept . unlist <$> lookup "Accept" kvs,
getAcceptLanguage =
map parseAcceptLanguage . unlist <$> lookup "Accept-Language" kvs,
getAcceptEncoding =
map parseAcceptEncoding . unlist <$> lookup "Accept-Encoding" kvs,
getConnection = map parseConnection . unlist <$> lookup "Connection" kvs,
getCacheControl =
map parseCacheControl . unlist <$> lookup "Cache-Control" kvs,
getCookie = maybe [] parseCookie $ lookup "Cookie" kvs,
getOthers = filter ((`notElem` getKeys) . fst) kvs
}
parsePost :: HandleLike h => [(BS.ByteString, BS.ByteString)] -> Post h
parsePost kvs = Post {
postHost = parseHost <$> lookup "Host" kvs,
postUserAgent = map parseProduct . sepTkn <$> lookup "User-Agent" kvs,
postAccept = map parseAccept . unlist <$> lookup "Accept" kvs,
postAcceptLanguage =
map parseAcceptLanguage . unlist <$> lookup "Accept-Language" kvs,
postAcceptEncoding =
map parseAcceptEncoding . unlist <$> lookup "Accept-Encoding" kvs,
postConnection = map parseConnection . unlist <$> lookup "Connection" kvs,
postCacheControl =
map parseCacheControl . unlist <$> lookup "Cache-Control" kvs,
postContentType = parseContentType <$> lookup "Content-Type" kvs,
postContentLength = ContentLength . read . BSC.unpack <$>
lookup "Content-Length" kvs,
postTransferEncoding = case lookup "Transfer-Encoding" kvs of
Just "chunked" -> Just Chunked
Nothing -> Nothing
_ -> error "bad Transfer-Encoding",
postCookie = maybe [] parseCookie $ lookup "Cookie" kvs,
postOthers = filter ((`notElem` postKeys) . fst) kvs,
postBody = return ()
}
postKeys :: [BS.ByteString]
postKeys = [
"Host", "User-Agent", "Accept", "Accept-Language", "Accept-Encoding",
"Connection", "Cache-Control", "Content-Type", "Content-Length",
"Transfer-Encoding"
]
sepTkn :: BS.ByteString -> [BS.ByteString]
sepTkn "" = []
sepTkn psrc
| Just ('(', src) <- BSC.uncons psrc = let
(cm, src') = let (c_, s_) = BSC.span (/= ')') src in
case BSC.uncons s_ of
Just (')', s__) -> (c_, s__)
_ -> error "setTkn: bad comment" in
('(' -:- cm +++ ")") : sepTkn (BSC.dropWhile isSpace src')
-- sepTkn ('(' : src) = ('(' : cm ++ ")") : sepTkn (dropWhile isSpace src')
-- where
-- (cm, ')' : src') = span (/= ')') src
sepTkn src = tk : sepTkn (BSC.dropWhile isSpace src')
where
(tk, src') = BSC.span (not . isSpace) src
getKeys :: [BS.ByteString]
getKeys = [
"Host", "User-Agent", "Accept", "Accept-Language", "Accept-Encoding",
"Connection", "Cache-Control", "Cookie"
]
data Host = Host BS.ByteString (Maybe Int) deriving Show
parseHost :: BS.ByteString -> Host
parseHost src = case BSC.span (/= ':') src of
(h, cp) -> case BSC.uncons cp of
Just (':', p) -> Host h (Just . read $ BSC.unpack p)
Nothing -> Host h Nothing
_ -> error "parseHost: never occur"
showHost :: Host -> BS.ByteString
showHost (Host h p) = h +++ maybe "" ((':' -:-) . BSC.pack . show) p
data Product
= Product BS.ByteString (Maybe BS.ByteString)
| ProductComment BS.ByteString
deriving (Show, Eq)
showProduct :: Product -> BS.ByteString
showProduct (Product pn mpv) = pn +++ case mpv of
Just v -> '/' -:- v
_ -> ""
showProduct (ProductComment cm) = "(" +++ cm +++ ")"
parseProduct :: BS.ByteString -> Product
parseProduct pcm
| Just ('(', cm) <- BSC.uncons pcm = case myLast "here" cm of
')' -> ProductComment $ BS.init cm
_ -> error "parseProduct: bad comment"
parseProduct pnv = case BSC.span (/= '/') pnv of
(pn, sv) -> case BSC.uncons sv of
Just ('/', v) -> Product pn $ Just v
_ -> Product pnv Nothing
myLast :: String -> BS.ByteString -> Char
myLast err bs
| BS.null bs = error err
| otherwise = BSC.last bs
data Accept
= Accept (BS.ByteString, BS.ByteString) Qvalue
deriving Show
showAccept :: Accept -> BS.ByteString
showAccept (Accept (t, st) qv) = ((t +++ "/" +++ st) +++) $ showQvalue qv
parseAccept :: BS.ByteString -> Accept
parseAccept src = case BSC.span (/= ';') src of
(mr, sqv) -> case BSC.uncons sqv of
Just (';', qv) -> Accept (parseMediaRange mr) $ parseQvalue qv
Nothing -> Accept (parseMediaRange mr) $ Qvalue 1
_ -> error "parseAccept: never occur"
parseMediaRange :: BS.ByteString -> (BS.ByteString, BS.ByteString)
parseMediaRange src = case BSC.span (/= '/') src of
(t, sst) -> case BSC.uncons sst of
Just ('/', st) -> (t, st)
_ -> error "parseMediaRange: bad media range"
unlist :: BS.ByteString -> [BS.ByteString]
unlist "" = []
unlist src = case BSC.span (/= ',') src of
(h, "") -> [h]
(h, ct) -> case BSC.uncons ct of
Just (',', t) -> h : unlist (BSC.dropWhile isSpace t)
_ -> error "unlist: never occur"
data Qvalue
= Qvalue Double
deriving Show
showQvalue :: Qvalue -> BS.ByteString
showQvalue (Qvalue 1.0) = ""
showQvalue (Qvalue qv) = ";q=" +++ BSC.pack (show qv)
parseQvalue :: BS.ByteString -> Qvalue
parseQvalue qeqv
| ("q=", qv) <- BS.splitAt 2 qeqv = Qvalue . read $ BSC.unpack qv
parseQvalue _ = error "parseQvalue: bad qvalue"
data AcceptLanguage
= AcceptLanguage BS.ByteString Qvalue
deriving Show
showAcceptLanguage :: AcceptLanguage -> BS.ByteString
showAcceptLanguage (AcceptLanguage al qv) = al +++ showQvalue qv
parseAcceptLanguage :: BS.ByteString -> AcceptLanguage
parseAcceptLanguage src = case BSC.span (/= ';') src of
(al, sqv) -> case BSC.uncons sqv of
Just (';', qv) -> AcceptLanguage al $ parseQvalue qv
Nothing -> AcceptLanguage al $ Qvalue 1
_ -> error "parseAcceptLanguage: never occur"
data AcceptEncoding
= AcceptEncoding BS.ByteString Qvalue
deriving Show
showAcceptEncoding :: AcceptEncoding -> BS.ByteString
showAcceptEncoding (AcceptEncoding ae qv) = ae +++ showQvalue qv
parseAcceptEncoding :: BS.ByteString -> AcceptEncoding
parseAcceptEncoding src = case BSC.span (/= ';') src of
(ae, sqv) -> case BSC.uncons sqv of
Just (';', qv) -> AcceptEncoding ae $ parseQvalue qv
Nothing -> AcceptEncoding ae $ Qvalue 1
_ -> error "parseAcceptEncoding: never occur"
data Connection
= Connection BS.ByteString
deriving Show
showConnection :: Connection -> BS.ByteString
showConnection (Connection c) = c
parseConnection :: BS.ByteString -> Connection
parseConnection = Connection
data CacheControl
= MaxAge Int
| CacheControlRaw BS.ByteString
deriving Show
showCacheControl :: CacheControl -> BS.ByteString
showCacheControl (MaxAge ma) = "max-age=" +++ BSC.pack (show ma)
showCacheControl (CacheControlRaw cc) = cc
parseCacheControl :: BS.ByteString -> CacheControl
parseCacheControl ccma
| ("max-age", ema) <- BSC.span (/= '=') ccma = case BSC.uncons ema of
Just ('=', ma) -> MaxAge . read $ BSC.unpack ma
_ -> error "parseCacheControl: bad"
parseCacheControl cc = CacheControlRaw cc
parseCookie :: BS.ByteString -> [(BS.ByteString, BS.ByteString)]
parseCookie bs =
map ((\[k, v] -> (BSC.dropWhile isSpace k, BSC.dropWhile isSpace v))
. BSC.split '=')
$ BSC.split ';' bs
data Response p h = Response {
responseVersion :: Version,
responseStatusCode :: StatusCode,
responseConnection :: Maybe BS.ByteString,
responseDate :: Maybe UTCTime,
responseTransferEncoding :: Maybe TransferEncoding,
responseAcceptRanges :: Maybe BS.ByteString,
responseETag :: Maybe BS.ByteString,
responseServer :: Maybe [Product],
responseContentLength :: Maybe ContentLength,
responseContentType :: ContentType,
responseLastModified :: Maybe UTCTime,
responseSetCookie :: [SetCookie],
responseOthers :: [(BS.ByteString, BS.ByteString)],
responseBody :: p () BS.ByteString (HandleMonad h) ()
}
data SetCookie = SetCookie {
cookieName :: BS.ByteString,
cookieValue :: BS.ByteString,
cookieExpires :: Maybe UTCTime,
cookieMaxAge :: Maybe DiffTime,
cookieDomain :: Maybe BS.ByteString,
cookiePath :: Maybe BS.ByteString,
cookieSecure :: Bool,
cookieHttpOnly :: Bool,
cookieExtension :: [BS.ByteString]
} deriving Show
setCookie :: BS.ByteString -> BS.ByteString -> SetCookie
setCookie n v = SetCookie {
cookieName = n,
cookieValue = v,
cookieExpires = Nothing,
cookieMaxAge = Nothing,
cookieDomain = Nothing,
cookiePath = Nothing,
cookieSecure = True,
cookieHttpOnly = True,
cookieExtension = []
}
parseResponse :: (
Monad (p () BS.ByteString (HandleMonad h)),
HandleLike h) =>
[BS.ByteString] -> Response p h
parseResponse (h : t) = let (v, sc) = parseResponseLine h in
parseResponseSep v sc $ map separate t
where
separate i = let (k, csv) = BSC.span (/= ':') i in
case BS.splitAt 2 csv of
(": ", v) -> (k, v)
_ -> error $ "parseResponse: bad response: " ++ show csv
-- let (k, ':' : ' ' : v) = span (/= ':') i in (k, v)
parseResponse _ = error "parseResponse: null response"
parseResponseSep :: (
Monad (p () BS.ByteString (HandleMonad h)),
HandleLike h) =>
Version -> StatusCode ->
[(BS.ByteString, BS.ByteString)] -> Response p h
parseResponseSep v sc kvs = Response {
responseVersion = v,
responseStatusCode = sc,
responseDate = readTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S" .
BSC.unpack . initN 4 <$> lookup "Date" kvs,
responseContentLength = ContentLength . read . BSC.unpack <$>
lookup "Content-Length" kvs,
responseTransferEncoding = case lookup "Transfer-Encoding" kvs of
Just "chunked" -> Just Chunked
Nothing -> Nothing
_ -> error "bad Transfer-Encoding",
responseContentType = parseContentType . fromJust $
lookup "Content-Type" kvs,
responseServer = map parseProduct . sepTkn <$> lookup "Server" kvs,
responseLastModified = readTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S" .
BSC.unpack . initN 4 <$> lookup "Last-Modified" kvs,
responseETag = lookup "ETag" kvs,
responseAcceptRanges = lookup "Accept-Ranges" kvs,
responseConnection = lookup "Connection" kvs,
responseSetCookie = map parseSetCookie . map snd $ filter ((== "Set-Cookie") . fst) kvs ,
responseOthers = filter ((`notElem` responseKeys) . fst) kvs,
responseBody = return ()
}
parseSetCookie :: BS.ByteString -> SetCookie
parseSetCookie bs = SetCookie {
cookieName = nm,
cookieValue = vl,
cookieExpires = (\(CookieExpires e) -> e) <$> find isCookieExpires cas,
cookieMaxAge = (\(CookieMaxAge ma) -> ma) <$> find isCookieMaxAge cas,
cookieDomain = (\(CookieDomain d) -> d) <$> find isCookieDomain cas,
cookiePath = (\(CookiePath p) -> p) <$> find isCookiePath cas,
cookieSecure = CookieSecure `elem` cas,
cookieHttpOnly = CookieHttpOnly `elem` cas,
cookieExtension = (\(CookieExtension e) -> e) <$> filter isCookieExtension cas
}
where
(nmvl) : kvs = map (BSC.dropWhile isSpace) $ BSC.split ';' bs
[nm, vl] = BSC.split '=' nmvl
cas = map cookieAttr kvs
cookieAttr :: BS.ByteString -> CookieAttr
cookieAttr ca
| ["Expires", v] <- av, Just d <- parseCookieExpiresTime v =
CookieExpires d
| ["Max-Age", v] <- av, Just ma <- fromInteger <$> readMaybe (BSC.unpack v) =
CookieMaxAge ma
| ["Domain", v] <- av = CookieDomain v
| ["Path", v] <- av = CookiePath v
where
av = BSC.split '=' ca
cookieAttr "Secure" = CookieSecure
cookieAttr "HttpOnly" = CookieHttpOnly
cookieAttr ca = CookieExtension ca
data CookieAttr
= CookieExpires UTCTime
| CookieMaxAge DiffTime
| CookieDomain BS.ByteString
| CookiePath BS.ByteString
| CookieSecure
| CookieHttpOnly
| CookieExtension BS.ByteString
deriving (Show, Eq)
isCookieExpires, isCookieMaxAge, isCookieDomain,
isCookiePath, isCookieExtension :: CookieAttr -> Bool
isCookieExpires (CookieExpires _) = True; isCookieExpires _ = False
isCookieMaxAge (CookieMaxAge _) = True; isCookieMaxAge _ = False
isCookieDomain (CookieDomain _) = True; isCookieDomain _ = False
isCookiePath (CookiePath _) = True; isCookiePath _ = False
isCookieExtension (CookieExtension _) = True; isCookieExtension _ = False
responseKeys :: [BS.ByteString]
responseKeys = [
"Date", "Content-Length", "Content-Type", "Server", "Last-Modified",
"ETag", "Accept-Ranges", "Connection", "Transfer-Encoding", "Set-Cookie" ]
initN :: Int -> BS.ByteString -> BS.ByteString
initN n lst = BS.take (BS.length lst - n) lst
parseResponseLine :: BS.ByteString -> (Version, StatusCode)
parseResponseLine src = case BSC.span (/= ' ') src of
(vs, sscs) -> case BSC.uncons sscs of
Just (' ', scs) -> (parseVersion vs, parseStatusCode scs)
_ -> error "parseResponseLine: bad response line"
parseStatusCode :: BS.ByteString -> StatusCode
parseStatusCode sc
| ("100", _) <- BSC.span (not . isSpace) sc = Continue
| ("101", _) <- BSC.span (not . isSpace) sc = SwitchingProtocols
| ("200", _) <- BSC.span (not . isSpace) sc = OK
| ("201", _) <- BSC.span (not . isSpace) sc = Created
| ("202", _) <- BSC.span (not . isSpace) sc = Accepted
| ("203", _) <- BSC.span (not . isSpace) sc = NonAuthoritativeInformation
| ("204", _) <- BSC.span (not . isSpace) sc = NoContent
| ("205", _) <- BSC.span (not . isSpace) sc = ResetContent
| ("206", _) <- BSC.span (not . isSpace) sc = PartialContent
| ("301", _) <- BSC.span (not . isSpace) sc = MovedPermanently
| ("302", _) <- BSC.span (not . isSpace) sc = Found
| ("303", _) <- BSC.span (not . isSpace) sc = SeeOther
| ("304", _) <- BSC.span (not . isSpace) sc = NotModified
| ("305", _) <- BSC.span (not . isSpace) sc = UseProxy
| ("307", _) <- BSC.span (not . isSpace) sc = TemporaryRedirect
| ("400", _) <- BSC.span (not . isSpace) sc = BadRequest
| ("401", _) <- BSC.span (not . isSpace) sc = Unauthorized
| ("402", _) <- BSC.span (not . isSpace) sc = PaymentRequired
| ("403", _) <- BSC.span (not . isSpace) sc = Forbidden
| ("404", _) <- BSC.span (not . isSpace) sc = NotFound
| ("405", _) <- BSC.span (not . isSpace) sc = MethodNotAllowed
| ("406", _) <- BSC.span (not . isSpace) sc = NotAcceptable
| ("407", _) <- BSC.span (not . isSpace) sc = ProxyAuthenticationRequired
| ("408", _) <- BSC.span (not . isSpace) sc = RequestTimeout
| ("409", _) <- BSC.span (not . isSpace) sc = Conflict
| ("500", _) <- BSC.span (not . isSpace) sc = InternalServerError
| ("501", _) <- BSC.span (not . isSpace) sc = NotImplemented
| ("502", _) <- BSC.span (not . isSpace) sc = BadGateway
| ("503", _) <- BSC.span (not . isSpace) sc = ServiceUnavailable
| ("504", _) <- BSC.span (not . isSpace) sc = GatewayTimeout
| ("505", _) <- BSC.span (not . isSpace) sc = HttpVersionNotSupported
parseStatusCode sc = error $ "parseStatusCode: bad status code: " ++ BSC.unpack sc
putResponse :: (
PipeClass p, MonadTrans (p BS.ByteString ()),
Monad (p BS.ByteString () (HandleMonad h)),
HandleLike h) =>
h -> Response p h -> HandleMonad h ()
putResponse cl r = do
let hd = [
Just $ showVersion (responseVersion r) +++ " " +++
showStatusCode (responseStatusCode r),
Just $ "Date: " +++ maybe "" showTime (responseDate r),
("Content-Length: " +++) . showContentLength <$>
responseContentLength r,
("Transfer-Encoding: " +++) . showTransferEncoding <$>
responseTransferEncoding r,
Just $ "Content-Type: " +++
showContentType (responseContentType r),
("Server: " +++) . BSC.unwords . map showProduct <$> responseServer r,
("Last-Modified: " +++) . showTime <$> responseLastModified r,
("ETag: " +++) <$> responseETag r,
("Accept-Ranges: " +++) <$> responseAcceptRanges r,
("Connection: " +++) <$> responseConnection r
] ++
map (Just . ("Set-Cookie: " +++) . showSetCookie)
(responseSetCookie r) ++
map (\(k, v) -> Just $ k +++ ": " +++ v) (responseOthers r) ++
[ Just "" ]
p = responseBody r
hlPut cl . crlf $ catMaybes hd
_ <- runPipe $ p =$= putAll cl
return ()
showSetCookie :: SetCookie -> BS.ByteString
showSetCookie sc = BS.intercalate "; " $ catMaybes [
Just $ cookieName sc +++ "=" +++ cookieValue sc,
("Expires=" +++) . cookieExpiresTime <$> cookieExpires sc,
("Max-Age=" +++) . BSC.pack . init . show <$> cookieMaxAge sc,
("Domain=" +++) <$> cookieDomain sc,
("Path=" +++) <$> cookiePath sc,
if cookieSecure sc then Just "Secure" else Nothing,
if cookieHttpOnly sc then Just "HttpOnly" else Nothing
]
putAll :: (
PipeClass p, MonadTrans (p BS.ByteString ()),
Monad (p BSC.ByteString () (HandleMonad h)),
HandleLike h) =>
h -> p BS.ByteString () (HandleMonad h) ()
putAll cl = do
ms <- await
case ms of
Just s -> do
-- lift (hlPut cl s >> hlPut cl "\r\n")
lift $ hlPut cl s
putAll cl
_ -> return ()
crlf :: [BS.ByteString] -> BS.ByteString
crlf = BS.concat . map (`BS.append` "\r\n")
data StatusCode
= Continue
| SwitchingProtocols
| OK
| Created
| Accepted
| NonAuthoritativeInformation
| NoContent
| ResetContent
| PartialContent
| MultipleChoices
| MovedPermanently
| Found
| SeeOther
| NotModified
| UseProxy
| TemporaryRedirect
| BadRequest
| Unauthorized
| PaymentRequired
| Forbidden
| NotFound
| MethodNotAllowed
| NotAcceptable
| ProxyAuthenticationRequired
| RequestTimeout
| Conflict
| InternalServerError
| NotImplemented
| BadGateway
| ServiceUnavailable
| GatewayTimeout
| HttpVersionNotSupported
deriving Show
showStatusCode :: StatusCode -> BS.ByteString
showStatusCode Continue = "100 Continue"
showStatusCode SwitchingProtocols = "101 SwitchingProtocols"
showStatusCode OK = "200 OK"
showStatusCode Created = "201 Created"
showStatusCode Accepted = "202 Accepted"
showStatusCode NonAuthoritativeInformation = "203 Non-Authoritative Information"
showStatusCode NoContent = "204 No Content"
showStatusCode ResetContent = "205 Reset Content"
showStatusCode PartialContent = "206 Partial Content"
showStatusCode MultipleChoices = "300 Multiple Choices"
showStatusCode MovedPermanently = "301 Moved Permanently"
showStatusCode Found = "302 Found"
showStatusCode SeeOther = "303 See Other"
showStatusCode NotModified = "304 Not Modified"
showStatusCode UseProxy = "305 Use Proxy"
showStatusCode TemporaryRedirect = "307 Temporary Redirect"
showStatusCode BadRequest = "400 Bad Request"
showStatusCode Unauthorized = "401 Unauthorized"
showStatusCode PaymentRequired = "402 Payment Required"
showStatusCode Forbidden = "403 Forbidden"
showStatusCode NotFound = "404 Not Found"
showStatusCode MethodNotAllowed = "405 Method Not Allowd"
showStatusCode NotAcceptable = "406 Not Acceptable"
showStatusCode ProxyAuthenticationRequired = "407 Proxy Authentication Required"
showStatusCode RequestTimeout = "408 Request Timeout"
showStatusCode Conflict = "409 Conflict"
showStatusCode InternalServerError = "500 Internal Server Error"
showStatusCode NotImplemented = "501 Not Implemented"
showStatusCode BadGateway = "502 Bad Gateway"
showStatusCode ServiceUnavailable = "503 Service Unavailable"
showStatusCode GatewayTimeout = "504 Gateway Timeout"
showStatusCode HttpVersionNotSupported = "505 HTTP version Not Supported"
data ContentLength = ContentLength Int deriving Show
showContentLength :: ContentLength -> BS.ByteString
showContentLength (ContentLength n) = BSC.pack $ show n
contentLength :: ContentLength -> Int
contentLength (ContentLength n) = n
data TransferEncoding = Chunked deriving Show
showTransferEncoding :: TransferEncoding -> BS.ByteString
showTransferEncoding Chunked = "chunked"
showTime :: UTCTime -> BS.ByteString
showTime = BSC.pack . formatTime defaultTimeLocale "%a, %d %b %Y %H:%M:%S GMT"