{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Network.HTTP.Client.Response
( getRedirectedRequest
, getResponse
, lbsResponse
) where
import Data.ByteString (ByteString)
import qualified Data.ByteString.Char8 as S8
import qualified Data.ByteString.Lazy as L
import qualified Data.CaseInsensitive as CI
import Control.Arrow (second)
import Data.Monoid (mempty)
import qualified Network.HTTP.Types as W
import Network.URI (parseURIReference, escapeURIString, isAllowedInURI)
import Network.HTTP.Client.Types
import Network.HTTP.Client.Request
import Network.HTTP.Client.Util
import Network.HTTP.Client.Body
import Network.HTTP.Client.Headers
import Data.KeyedPool
getRedirectedRequest :: Request -> W.ResponseHeaders -> CookieJar -> Int -> Maybe Request
getRedirectedRequest :: Request -> ResponseHeaders -> CookieJar -> Int -> Maybe Request
getRedirectedRequest Request
req ResponseHeaders
hs CookieJar
cookie_jar Int
code
| Int
300 Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
code Bool -> Bool -> Bool
&& Int
code Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
400 = do
ByteString
l' <- HeaderName -> ResponseHeaders -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup HeaderName
"location" ResponseHeaders
hs
let l :: String
l = (Char -> Bool) -> String -> String
escapeURIString Char -> Bool
isAllowedInURI (ByteString -> String
S8.unpack ByteString
l')
stripHeaders :: Request -> Request
stripHeaders Request
r =
Request
r{requestHeaders :: ResponseHeaders
requestHeaders =
((HeaderName, ByteString) -> Bool)
-> ResponseHeaders -> ResponseHeaders
forall a. (a -> Bool) -> [a] -> [a]
filter (Bool -> Bool
not (Bool -> Bool)
-> ((HeaderName, ByteString) -> Bool)
-> (HeaderName, ByteString)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Request -> HeaderName -> Bool
shouldStripHeaderOnRedirect Request
req (HeaderName -> Bool)
-> ((HeaderName, ByteString) -> HeaderName)
-> (HeaderName, ByteString)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (HeaderName, ByteString) -> HeaderName
forall a b. (a, b) -> a
fst) (ResponseHeaders -> ResponseHeaders)
-> ResponseHeaders -> ResponseHeaders
forall a b. (a -> b) -> a -> b
$
Request -> ResponseHeaders
requestHeaders Request
r}
Request
req' <- (Request -> Request) -> Maybe Request -> Maybe Request
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Request -> Request
stripHeaders (Maybe Request -> Maybe Request)
-> (URI -> Maybe Request) -> URI -> Maybe Request
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Request -> URI -> Maybe Request
forall (m :: * -> *). MonadThrow m => Request -> URI -> m Request
setUriRelative Request
req (URI -> Maybe Request) -> Maybe URI -> Maybe Request
forall (m :: * -> *) a b. Monad m => (a -> m b) -> m a -> m b
=<< String -> Maybe URI
parseURIReference String
l
Request -> Maybe Request
forall (m :: * -> *) a. Monad m => a -> m a
return (Request -> Maybe Request) -> Request -> Maybe Request
forall a b. (a -> b) -> a -> b
$
if Int
code Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
302 Bool -> Bool -> Bool
|| Int
code Int -> Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int
303
then Request
req'
{ method :: ByteString
method = ByteString
"GET"
, requestBody :: RequestBody
requestBody = ByteString -> RequestBody
RequestBodyBS ByteString
""
, cookieJar :: Maybe CookieJar
cookieJar = Maybe CookieJar
cookie_jar'
, requestHeaders :: ResponseHeaders
requestHeaders = ((HeaderName, ByteString) -> Bool)
-> ResponseHeaders -> ResponseHeaders
forall a. (a -> Bool) -> [a] -> [a]
filter ((HeaderName -> HeaderName -> Bool
forall a. Eq a => a -> a -> Bool
/= HeaderName
W.hContentType) (HeaderName -> Bool)
-> ((HeaderName, ByteString) -> HeaderName)
-> (HeaderName, ByteString)
-> Bool
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (HeaderName, ByteString) -> HeaderName
forall a b. (a, b) -> a
fst) (ResponseHeaders -> ResponseHeaders)
-> ResponseHeaders -> ResponseHeaders
forall a b. (a -> b) -> a -> b
$ Request -> ResponseHeaders
requestHeaders Request
req'
}
else Request
req' {cookieJar :: Maybe CookieJar
cookieJar = Maybe CookieJar
cookie_jar'}
| Bool
otherwise = Maybe Request
forall a. Maybe a
Nothing
where
cookie_jar' :: Maybe CookieJar
cookie_jar' = (CookieJar -> CookieJar) -> Maybe CookieJar -> Maybe CookieJar
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (CookieJar -> CookieJar -> CookieJar
forall a b. a -> b -> a
const CookieJar
cookie_jar) (Maybe CookieJar -> Maybe CookieJar)
-> Maybe CookieJar -> Maybe CookieJar
forall a b. (a -> b) -> a -> b
$ Request -> Maybe CookieJar
cookieJar Request
req
lbsResponse :: Response BodyReader -> IO (Response L.ByteString)
lbsResponse :: Response BodyReader -> IO (Response ByteString)
lbsResponse Response BodyReader
res = do
[ByteString]
bss <- BodyReader -> IO [ByteString]
brConsume (BodyReader -> IO [ByteString]) -> BodyReader -> IO [ByteString]
forall a b. (a -> b) -> a -> b
$ Response BodyReader -> BodyReader
forall body. Response body -> body
responseBody Response BodyReader
res
Response ByteString -> IO (Response ByteString)
forall (m :: * -> *) a. Monad m => a -> m a
return Response BodyReader
res
{ responseBody :: ByteString
responseBody = [ByteString] -> ByteString
L.fromChunks [ByteString]
bss
}
getResponse :: Maybe Int
-> Request
-> Managed Connection
-> Maybe (IO ())
-> IO (Response BodyReader)
getResponse :: Maybe Int
-> Request
-> Managed Connection
-> Maybe (IO ())
-> IO (Response BodyReader)
getResponse Maybe Int
timeout' req :: Request
req@(Request {Bool
Int
ResponseHeaders
Maybe HostAddress
Maybe Manager
Maybe Proxy
Maybe CookieJar
ByteString
HttpVersion
ResponseTimeout
RequestBody
ProxySecureMode
ByteString -> Bool
SomeException -> IO ()
HeaderName -> Bool
Request -> Response BodyReader -> IO ()
proxySecureMode :: Request -> ProxySecureMode
requestManagerOverride :: Request -> Maybe Manager
onRequestBodyException :: Request -> SomeException -> IO ()
requestVersion :: Request -> HttpVersion
responseTimeout :: Request -> ResponseTimeout
checkResponse :: Request -> Request -> Response BodyReader -> IO ()
redirectCount :: Request -> Int
decompress :: Request -> ByteString -> Bool
rawBody :: Request -> Bool
hostAddress :: Request -> Maybe HostAddress
proxy :: Request -> Maybe Proxy
queryString :: Request -> ByteString
path :: Request -> ByteString
port :: Request -> Int
host :: Request -> ByteString
secure :: Request -> Bool
proxySecureMode :: ProxySecureMode
shouldStripHeaderOnRedirect :: HeaderName -> Bool
requestManagerOverride :: Maybe Manager
onRequestBodyException :: SomeException -> IO ()
requestVersion :: HttpVersion
cookieJar :: Maybe CookieJar
responseTimeout :: ResponseTimeout
checkResponse :: Request -> Response BodyReader -> IO ()
redirectCount :: Int
decompress :: ByteString -> Bool
rawBody :: Bool
hostAddress :: Maybe HostAddress
proxy :: Maybe Proxy
requestBody :: RequestBody
requestHeaders :: ResponseHeaders
queryString :: ByteString
path :: ByteString
port :: Int
host :: ByteString
secure :: Bool
method :: ByteString
cookieJar :: Request -> Maybe CookieJar
requestBody :: Request -> RequestBody
method :: Request -> ByteString
shouldStripHeaderOnRedirect :: Request -> HeaderName -> Bool
requestHeaders :: Request -> ResponseHeaders
..}) Managed Connection
mconn Maybe (IO ())
cont = do
let conn :: Connection
conn = Managed Connection -> Connection
forall resource. Managed resource -> resource
managedResource Managed Connection
mconn
StatusHeaders Status
s HttpVersion
version ResponseHeaders
hs <- Connection -> Maybe Int -> Maybe (IO ()) -> IO StatusHeaders
parseStatusHeaders Connection
conn Maybe Int
timeout' Maybe (IO ())
cont
let mcl :: Maybe Int
mcl = HeaderName -> ResponseHeaders -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup HeaderName
"content-length" ResponseHeaders
hs Maybe ByteString -> (ByteString -> Maybe Int) -> Maybe Int
forall (m :: * -> *) a b. Monad m => m a -> (a -> m b) -> m b
>>= String -> Maybe Int
readPositiveInt (String -> Maybe Int)
-> (ByteString -> String) -> ByteString -> Maybe Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> String
S8.unpack
isChunked :: Bool
isChunked = (HeaderName
"transfer-encoding", ByteString -> HeaderName
forall s. FoldCase s => s -> CI s
CI.mk ByteString
"chunked") (HeaderName, HeaderName) -> [(HeaderName, HeaderName)] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ((HeaderName, ByteString) -> (HeaderName, HeaderName))
-> ResponseHeaders -> [(HeaderName, HeaderName)]
forall a b. (a -> b) -> [a] -> [b]
map ((ByteString -> HeaderName)
-> (HeaderName, ByteString) -> (HeaderName, HeaderName)
forall (a :: * -> * -> *) b c d.
Arrow a =>
a b c -> a (d, b) (d, c)
second ByteString -> HeaderName
forall s. FoldCase s => s -> CI s
CI.mk) ResponseHeaders
hs
toPut :: Bool
toPut = ByteString -> Maybe ByteString
forall a. a -> Maybe a
Just ByteString
"close" Maybe ByteString -> Maybe ByteString -> Bool
forall a. Eq a => a -> a -> Bool
/= HeaderName -> ResponseHeaders -> Maybe ByteString
forall a b. Eq a => a -> [(a, b)] -> Maybe b
lookup HeaderName
"connection" ResponseHeaders
hs Bool -> Bool -> Bool
&& HttpVersion
version HttpVersion -> HttpVersion -> Bool
forall a. Ord a => a -> a -> Bool
> Int -> Int -> HttpVersion
W.HttpVersion Int
1 Int
0
cleanup :: Bool -> IO ()
cleanup Bool
bodyConsumed = Managed Connection -> Reuse -> IO ()
forall resource. Managed resource -> Reuse -> IO ()
managedRelease Managed Connection
mconn (Reuse -> IO ()) -> Reuse -> IO ()
forall a b. (a -> b) -> a -> b
$ if Bool
toPut Bool -> Bool -> Bool
&& Bool
bodyConsumed then Reuse
Reuse else Reuse
DontReuse
BodyReader
body <-
if ByteString -> Int -> Bool
hasNoBody ByteString
method (Status -> Int
W.statusCode Status
s) Bool -> Bool -> Bool
|| (Maybe Int
mcl Maybe Int -> Maybe Int -> Bool
forall a. Eq a => a -> a -> Bool
== Int -> Maybe Int
forall a. a -> Maybe a
Just Int
0 Bool -> Bool -> Bool
&& Bool -> Bool
not Bool
isChunked)
then do
Bool -> IO ()
cleanup Bool
True
BodyReader -> IO BodyReader
forall (m :: * -> *) a. Monad m => a -> m a
return BodyReader
brEmpty
else do
BodyReader
body1 <-
if Bool
isChunked
then IO () -> Bool -> Connection -> IO BodyReader
makeChunkedReader (Bool -> IO ()
cleanup Bool
True) Bool
rawBody Connection
conn
else
case Maybe Int
mcl of
Just Int
len -> IO () -> Int -> Connection -> IO BodyReader
makeLengthReader (Bool -> IO ()
cleanup Bool
True) Int
len Connection
conn
Maybe Int
Nothing -> IO () -> Connection -> IO BodyReader
makeUnlimitedReader (Bool -> IO ()
cleanup Bool
True) Connection
conn
if Request -> ResponseHeaders -> Bool
needsGunzip Request
req ResponseHeaders
hs
then BodyReader -> IO BodyReader
makeGzipReader BodyReader
body1
else BodyReader -> IO BodyReader
forall (m :: * -> *) a. Monad m => a -> m a
return BodyReader
body1
Response BodyReader -> IO (Response BodyReader)
forall (m :: * -> *) a. Monad m => a -> m a
return Response :: forall body.
Status
-> HttpVersion
-> ResponseHeaders
-> body
-> CookieJar
-> ResponseClose
-> Response body
Response
{ responseStatus :: Status
responseStatus = Status
s
, responseVersion :: HttpVersion
responseVersion = HttpVersion
version
, responseHeaders :: ResponseHeaders
responseHeaders = ResponseHeaders
hs
, responseBody :: BodyReader
responseBody = BodyReader
body
, responseCookieJar :: CookieJar
responseCookieJar = CookieJar
forall a. Monoid a => a
Data.Monoid.mempty
, responseClose' :: ResponseClose
responseClose' = IO () -> ResponseClose
ResponseClose (Bool -> IO ()
cleanup Bool
False)
}
hasNoBody :: ByteString
-> Int
-> Bool
hasNoBody :: ByteString -> Int -> Bool
hasNoBody ByteString
"HEAD" Int
_ = Bool
True
hasNoBody ByteString
_ Int
204 = Bool
True
hasNoBody ByteString
_ Int
304 = Bool
True
hasNoBody ByteString
_ Int
i = Int
100 Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
<= Int
i Bool -> Bool -> Bool
&& Int
i Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
< Int
200