Blame Network/HTTP/Base.hs

Packit acf257
{-# LANGUAGE ScopedTypeVariables #-}
Packit acf257
-----------------------------------------------------------------------------
Packit acf257
-- |
Packit acf257
-- Module      :  Network.HTTP.Base
Packit acf257
-- Copyright   :  See LICENSE file
Packit acf257
-- License     :  BSD
Packit acf257
-- 
Packit acf257
-- Maintainer  :  Ganesh Sittampalam <ganesh@earth.li>
Packit acf257
-- Stability   :  experimental
Packit acf257
-- Portability :  non-portable (not tested)
Packit acf257
--
Packit acf257
-- Definitions of @Request@ and @Response@ types along with functions
Packit acf257
-- for normalizing them. It is assumed to be an internal module; user
Packit acf257
-- code should, if possible, import @Network.HTTP@ to access the functionality
Packit acf257
-- that this module provides.
Packit acf257
--
Packit acf257
-- Additionally, the module exports internal functions for working with URLs,
Packit acf257
-- and for handling the processing of requests and responses coming back.
Packit acf257
--
Packit acf257
-----------------------------------------------------------------------------
Packit acf257
module Network.HTTP.Base
Packit acf257
       (
Packit acf257
          -- ** Constants
Packit acf257
         httpVersion                 -- :: String
Packit acf257
Packit acf257
          -- ** HTTP
Packit acf257
       , Request(..)
Packit acf257
       , Response(..)
Packit acf257
       , RequestMethod(..)
Packit acf257
       
Packit acf257
       , Request_String
Packit acf257
       , Response_String
Packit acf257
       , HTTPRequest
Packit acf257
       , HTTPResponse
Packit acf257
       
Packit acf257
          -- ** URL Encoding
Packit acf257
       , urlEncode
Packit acf257
       , urlDecode
Packit acf257
       , urlEncodeVars
Packit acf257
Packit acf257
          -- ** URI authority parsing
Packit acf257
       , URIAuthority(..)
Packit acf257
       , parseURIAuthority
Packit acf257
       
Packit acf257
          -- internal
Packit acf257
       , uriToAuthorityString   -- :: URI     -> String
Packit acf257
       , uriAuthToString        -- :: URIAuth -> String
Packit acf257
       , uriAuthPort            -- :: Maybe URI -> URIAuth -> Int
Packit acf257
       , reqURIAuth             -- :: Request ty -> URIAuth
Packit acf257
Packit acf257
       , parseResponseHead      -- :: [String] -> Result ResponseData
Packit acf257
       , parseRequestHead       -- :: [String] -> Result RequestData
Packit acf257
Packit acf257
       , ResponseNextStep(..)
Packit acf257
       , matchResponse
Packit acf257
       , ResponseData
Packit acf257
       , ResponseCode
Packit acf257
       , RequestData
Packit acf257
       
Packit acf257
       , NormalizeRequestOptions(..) 
Packit acf257
       , defaultNormalizeRequestOptions -- :: NormalizeRequestOptions ty
Packit acf257
       , RequestNormalizer
Packit acf257
Packit acf257
       , normalizeRequest   -- :: NormalizeRequestOptions ty -> Request ty -> Request ty
Packit acf257
Packit acf257
       , splitRequestURI
Packit acf257
Packit acf257
       , getAuth
Packit acf257
       , normalizeRequestURI
Packit acf257
       , normalizeHostHeader
Packit acf257
       , findConnClose
Packit acf257
Packit acf257
         -- internal export (for the use by Network.HTTP.{Stream,ByteStream} )
Packit acf257
       , linearTransfer
Packit acf257
       , hopefulTransfer
Packit acf257
       , chunkedTransfer
Packit acf257
       , uglyDeathTransfer
Packit acf257
       , readTillEmpty1
Packit acf257
       , readTillEmpty2
Packit acf257
       
Packit acf257
       , defaultGETRequest
Packit acf257
       , defaultGETRequest_
Packit acf257
       , mkRequest
Packit acf257
       , setRequestBody
Packit acf257
Packit acf257
       , defaultUserAgent
Packit acf257
       , httpPackageVersion
Packit acf257
       , libUA  {- backwards compatibility, will disappear..soon -}
Packit acf257
       
Packit acf257
       , catchIO
Packit acf257
       , catchIO_
Packit acf257
       , responseParseError
Packit acf257
       
Packit acf257
       , getRequestVersion
Packit acf257
       , getResponseVersion
Packit acf257
       , setRequestVersion
Packit acf257
       , setResponseVersion
Packit acf257
Packit acf257
       , failHTTPS
Packit acf257
       
Packit acf257
       ) where
Packit acf257
Packit acf257
import Network.URI
Packit acf257
   ( URI(uriAuthority, uriPath, uriScheme)
Packit acf257
   , URIAuth(URIAuth, uriUserInfo, uriRegName, uriPort)
Packit acf257
   , parseURIReference
Packit acf257
   )
Packit acf257
Packit acf257
import Control.Monad ( guard )
Packit acf257
import Control.Monad.Error ()
Packit acf257
import Data.Bits     ( (.&.), (.|.), shiftL, shiftR )
Packit acf257
import Data.Word     ( Word8 )
Packit acf257
import Data.Char     ( digitToInt, intToDigit, toLower, isDigit,
Packit acf257
                       isAscii, isAlphaNum, ord, chr )
Packit acf257
import Data.List     ( partition, find )
Packit acf257
import Data.Maybe    ( listToMaybe, fromMaybe )
Packit acf257
import Numeric       ( readHex )
Packit acf257
Packit acf257
import Network.Stream
Packit acf257
import Network.BufferType ( BufferOp(..), BufferType(..) )
Packit acf257
import Network.HTTP.Headers
Packit acf257
import Network.HTTP.Utils ( trim, crlf, sp, readsOne )
Packit acf257
import qualified Network.HTTP.Base64 as Base64 (encode)
Packit acf257
Packit acf257
import Text.Read.Lex (readDecP)
Packit acf257
import Text.ParserCombinators.ReadP
Packit acf257
   ( ReadP, readP_to_S, char, (<++), look, munch, munch1 )
Packit acf257
Packit acf257
import Control.Exception as Exception (catch, IOException)
Packit acf257
Packit acf257
import qualified Paths_HTTP as Self (version)
Packit acf257
import Data.Version (showVersion)
Packit acf257
Packit acf257
-----------------------------------------------------------------
Packit acf257
------------------ URI Authority parsing ------------------------
Packit acf257
-----------------------------------------------------------------
Packit acf257
Packit acf257
data URIAuthority = URIAuthority { user :: Maybe String,
Packit acf257
                                   password :: Maybe String,
Packit acf257
                                   host :: String,
Packit acf257
                                   port :: Maybe Int
Packit acf257
                                 } deriving (Eq,Show)
Packit acf257
Packit acf257
-- | Parse the authority part of a URL.
Packit acf257
--
Packit acf257
-- > RFC 1732, section 3.1:
Packit acf257
-- >
Packit acf257
-- >       //<user>:<password>@<host>:<port>/<url-path>
Packit acf257
-- >  Some or all of the parts "<user>:<password>@", ":<password>",
Packit acf257
-- >  ":<port>", and "/<url-path>" may be excluded.
Packit acf257
parseURIAuthority :: String -> Maybe URIAuthority
Packit acf257
parseURIAuthority s = listToMaybe (map fst (readP_to_S pURIAuthority s))
Packit acf257
Packit acf257
Packit acf257
pURIAuthority :: ReadP URIAuthority
Packit acf257
pURIAuthority = do
Packit acf257
                (u,pw) <- (pUserInfo `before` char '@')
Packit acf257
                          <++ return (Nothing, Nothing)
Packit acf257
                h <- rfc2732host <++ munch (/=':')
Packit acf257
                p <- orNothing (char ':' >> readDecP)
Packit acf257
                look >>= guard . null
Packit acf257
                return URIAuthority{ user=u, password=pw, host=h, port=p }
Packit acf257
Packit acf257
-- RFC2732 adds support for '[literal-ipv6-address]' in the host part of a URL
Packit acf257
rfc2732host :: ReadP String
Packit acf257
rfc2732host = do
Packit acf257
    _ <- char '['
Packit acf257
    res <- munch1 (/=']')
Packit acf257
    _ <- char ']'
Packit acf257
    return res
Packit acf257
Packit acf257
pUserInfo :: ReadP (Maybe String, Maybe String)
Packit acf257
pUserInfo = do
Packit acf257
            u <- orNothing (munch (`notElem` ":@"))
Packit acf257
            p <- orNothing (char ':' >> munch (/='@'))
Packit acf257
            return (u,p)
Packit acf257
Packit acf257
before :: Monad m => m a -> m b -> m a
Packit acf257
before a b = a >>= \x -> b >> return x
Packit acf257
Packit acf257
orNothing :: ReadP a -> ReadP (Maybe a)
Packit acf257
orNothing p = fmap Just p <++ return Nothing
Packit acf257
Packit acf257
-- This function duplicates old Network.URI.authority behaviour.
Packit acf257
uriToAuthorityString :: URI -> String
Packit acf257
uriToAuthorityString u = maybe "" uriAuthToString (uriAuthority u)
Packit acf257
Packit acf257
uriAuthToString :: URIAuth -> String
Packit acf257
uriAuthToString ua = 
Packit acf257
  concat [ uriUserInfo ua 
Packit acf257
         , uriRegName ua
Packit acf257
         , uriPort ua
Packit acf257
         ]
Packit acf257
Packit acf257
uriAuthPort :: Maybe URI -> URIAuth -> Int
Packit acf257
uriAuthPort mbURI u = 
Packit acf257
  case uriPort u of
Packit acf257
    (':':s) -> readsOne id (default_port mbURI) s
Packit acf257
    _       -> default_port mbURI
Packit acf257
 where
Packit acf257
  default_port Nothing = default_http
Packit acf257
  default_port (Just url) = 
Packit acf257
    case map toLower $ uriScheme url of
Packit acf257
      "http:" -> default_http
Packit acf257
      "https:" -> default_https
Packit acf257
        -- todo: refine
Packit acf257
      _ -> default_http
Packit acf257
Packit acf257
  default_http  = 80
Packit acf257
  default_https = 443
Packit acf257
Packit acf257
failHTTPS :: Monad m => URI -> m ()
Packit acf257
failHTTPS uri
Packit acf257
  | map toLower (uriScheme uri) == "https:" = fail "https not supported"
Packit acf257
  | otherwise = return ()
Packit acf257
Packit acf257
-- Fish out the authority from a possibly normalized Request, i.e.,
Packit acf257
-- the information may either be in the request's URI or inside
Packit acf257
-- the Host: header.
Packit acf257
reqURIAuth :: Request ty -> URIAuth
Packit acf257
reqURIAuth req = 
Packit acf257
  case uriAuthority (rqURI req) of
Packit acf257
    Just ua -> ua
Packit acf257
    _ -> case lookupHeader HdrHost (rqHeaders req) of
Packit acf257
           Nothing -> error ("reqURIAuth: no URI authority for: " ++ show req)
Packit acf257
           Just h  ->
Packit acf257
              case toHostPort h of
Packit acf257
                (ht,p) -> URIAuth { uriUserInfo = ""
Packit acf257
                                  , uriRegName  = ht
Packit acf257
                                  , uriPort     = p
Packit acf257
                                  }
Packit acf257
  where
Packit acf257
    -- Note: just in case you're wondering..the convention is to include the ':'
Packit acf257
    -- in the port part..
Packit acf257
   toHostPort h = break (==':') h
Packit acf257
Packit acf257
-----------------------------------------------------------------
Packit acf257
------------------ HTTP Messages --------------------------------
Packit acf257
-----------------------------------------------------------------
Packit acf257
Packit acf257
Packit acf257
-- Protocol version
Packit acf257
httpVersion :: String
Packit acf257
httpVersion = "HTTP/1.1"
Packit acf257
Packit acf257
Packit acf257
-- | The HTTP request method, to be used in the 'Request' object.
Packit acf257
-- We are missing a few of the stranger methods, but these are
Packit acf257
-- not really necessary until we add full TLS.
Packit acf257
data RequestMethod = HEAD | PUT | GET | POST | DELETE | OPTIONS | TRACE | CONNECT | Custom String
Packit acf257
    deriving(Eq)
Packit acf257
Packit acf257
instance Show RequestMethod where
Packit acf257
  show x = 
Packit acf257
    case x of
Packit acf257
      HEAD     -> "HEAD"
Packit acf257
      PUT      -> "PUT"
Packit acf257
      GET      -> "GET"
Packit acf257
      POST     -> "POST"
Packit acf257
      DELETE   -> "DELETE"
Packit acf257
      OPTIONS  -> "OPTIONS"
Packit acf257
      TRACE    -> "TRACE"
Packit acf257
      CONNECT  -> "CONNECT"
Packit acf257
      Custom c -> c
Packit acf257
Packit acf257
rqMethodMap :: [(String, RequestMethod)]
Packit acf257
rqMethodMap = [("HEAD",    HEAD),
Packit acf257
               ("PUT",     PUT),
Packit acf257
               ("GET",     GET),
Packit acf257
               ("POST",    POST),
Packit acf257
               ("DELETE",  DELETE),
Packit acf257
               ("OPTIONS", OPTIONS),
Packit acf257
               ("TRACE",   TRACE),
Packit acf257
               ("CONNECT", CONNECT)]
Packit acf257
Packit acf257
-- 
Packit acf257
-- for backwards-ish compatibility; suggest
Packit acf257
-- migrating to new Req/Resp by adding type param.
Packit acf257
-- 
Packit acf257
type Request_String  = Request String
Packit acf257
type Response_String = Response String
Packit acf257
Packit acf257
-- Hmm..I really want to use these for the record
Packit acf257
-- type, but it will upset codebases wanting to
Packit acf257
-- migrate (and live with using pre-HTTPbis versions.)
Packit acf257
type HTTPRequest a  = Request  a
Packit acf257
type HTTPResponse a = Response a
Packit acf257
Packit acf257
-- | An HTTP Request.
Packit acf257
-- The 'Show' instance of this type is used for message serialisation,
Packit acf257
-- which means no body data is output.
Packit acf257
data Request a =
Packit acf257
     Request { rqURI       :: URI   -- ^ might need changing in future
Packit acf257
                                    --  1) to support '*' uri in OPTIONS request
Packit acf257
                                    --  2) transparent support for both relative
Packit acf257
                                    --     & absolute uris, although this should
Packit acf257
                                    --     already work (leave scheme & host parts empty).
Packit acf257
             , rqMethod    :: RequestMethod
Packit acf257
             , rqHeaders   :: [Header]
Packit acf257
             , rqBody      :: a
Packit acf257
             }
Packit acf257
Packit acf257
-- Notice that request body is not included,
Packit acf257
-- this show function is used to serialise
Packit acf257
-- a request for the transport link, we send
Packit acf257
-- the body separately where possible.
Packit acf257
instance Show (Request a) where
Packit acf257
    show req@(Request u m h _) =
Packit acf257
        show m ++ sp ++ alt_uri ++ sp ++ ver ++ crlf
Packit acf257
        ++ foldr (++) [] (map show (dropHttpVersion h)) ++ crlf
Packit acf257
        where
Packit acf257
            ver = fromMaybe httpVersion (getRequestVersion req)
Packit acf257
            alt_uri = show $ if null (uriPath u) || head (uriPath u) /= '/' 
Packit acf257
                        then u { uriPath = '/' : uriPath u } 
Packit acf257
                        else u
Packit acf257
Packit acf257
instance HasHeaders (Request a) where
Packit acf257
    getHeaders = rqHeaders
Packit acf257
    setHeaders rq hdrs = rq { rqHeaders=hdrs }
Packit acf257
Packit acf257
-- | For easy pattern matching, HTTP response codes @xyz@ are
Packit acf257
-- represented as @(x,y,z)@.
Packit acf257
type ResponseCode  = (Int,Int,Int)
Packit acf257
Packit acf257
-- | @ResponseData@ contains the head of a response payload;
Packit acf257
-- HTTP response code, accompanying text description + header
Packit acf257
-- fields.
Packit acf257
type ResponseData  = (ResponseCode,String,[Header])
Packit acf257
Packit acf257
-- | @RequestData@ contains the head of a HTTP request; method,
Packit acf257
-- its URL along with the auxillary/supporting header data.
Packit acf257
type RequestData   = (RequestMethod,URI,[Header])
Packit acf257
Packit acf257
-- | An HTTP Response.
Packit acf257
-- The 'Show' instance of this type is used for message serialisation,
Packit acf257
-- which means no body data is output, additionally the output will
Packit acf257
-- show an HTTP version of 1.1 instead of the actual version returned
Packit acf257
-- by a server.
Packit acf257
data Response a =
Packit acf257
    Response { rspCode     :: ResponseCode
Packit acf257
             , rspReason   :: String
Packit acf257
             , rspHeaders  :: [Header]
Packit acf257
             , rspBody     :: a
Packit acf257
             }
Packit acf257
                   
Packit acf257
-- This is an invalid representation of a received response, 
Packit acf257
-- since we have made the assumption that all responses are HTTP/1.1
Packit acf257
instance Show (Response a) where
Packit acf257
    show rsp@(Response (a,b,c) reason headers _) =
Packit acf257
        ver ++ ' ' : map intToDigit [a,b,c] ++ ' ' : reason ++ crlf
Packit acf257
        ++ foldr (++) [] (map show (dropHttpVersion headers)) ++ crlf
Packit acf257
     where
Packit acf257
      ver = fromMaybe httpVersion (getResponseVersion rsp)
Packit acf257
Packit acf257
instance HasHeaders (Response a) where
Packit acf257
    getHeaders = rspHeaders
Packit acf257
    setHeaders rsp hdrs = rsp { rspHeaders=hdrs }
Packit acf257
Packit acf257
Packit acf257
------------------------------------------------------------------
Packit acf257
------------------ Request Building ------------------------------
Packit acf257
------------------------------------------------------------------
Packit acf257
Packit acf257
-- | Deprecated. Use 'defaultUserAgent'
Packit acf257
libUA :: String
Packit acf257
libUA = "hs-HTTP-4000.0.9"
Packit acf257
{-# DEPRECATED libUA "Use defaultUserAgent instead (but note the user agent name change)" #-}
Packit acf257
Packit acf257
-- | A default user agent string. The string is @\"haskell-HTTP/$version\"@
Packit acf257
-- where @$version@ is the version of this HTTP package.
Packit acf257
--
Packit acf257
defaultUserAgent :: String
Packit acf257
defaultUserAgent = "haskell-HTTP/" ++ httpPackageVersion
Packit acf257
Packit acf257
-- | The version of this HTTP package as a string, e.g. @\"4000.1.2\"@. This
Packit acf257
-- may be useful to include in a user agent string so that you can determine
Packit acf257
-- from server logs what version of this package HTTP clients are using.
Packit acf257
-- This can be useful for tracking down HTTP compatibility quirks.
Packit acf257
--
Packit acf257
httpPackageVersion :: String
Packit acf257
httpPackageVersion = showVersion Self.version
Packit acf257
Packit acf257
defaultGETRequest :: URI -> Request_String
Packit acf257
defaultGETRequest uri = defaultGETRequest_ uri
Packit acf257
Packit acf257
defaultGETRequest_ :: BufferType a => URI -> Request a
Packit acf257
defaultGETRequest_ uri = mkRequest GET uri 
Packit acf257
Packit acf257
-- | 'mkRequest method uri' constructs a well formed
Packit acf257
-- request for the given HTTP method and URI. It does not
Packit acf257
-- normalize the URI for the request _nor_ add the required 
Packit acf257
-- Host: header. That is done either explicitly by the user
Packit acf257
-- or when requests are normalized prior to transmission.
Packit acf257
mkRequest :: BufferType ty => RequestMethod -> URI -> Request ty
Packit acf257
mkRequest meth uri = req
Packit acf257
 where
Packit acf257
  req = 
Packit acf257
    Request { rqURI      = uri
Packit acf257
            , rqBody     = empty
Packit acf257
            , rqHeaders  = [ Header HdrContentLength "0"
Packit acf257
                           , Header HdrUserAgent     defaultUserAgent
Packit acf257
                           ]
Packit acf257
            , rqMethod   = meth
Packit acf257
            }
Packit acf257
Packit acf257
  empty = buf_empty (toBufOps req)
Packit acf257
Packit acf257
-- set rqBody, Content-Type and Content-Length headers.
Packit acf257
setRequestBody :: Request_String -> (String, String) -> Request_String
Packit acf257
setRequestBody req (typ, body) = req' { rqBody=body }
Packit acf257
  where
Packit acf257
    req' = replaceHeader HdrContentType typ .
Packit acf257
           replaceHeader HdrContentLength (show $ length body) $
Packit acf257
           req
Packit acf257
Packit acf257
{-
Packit acf257
    -- stub out the user info.
Packit acf257
  updAuth = fmap (\ x -> x{uriUserInfo=""}) (uriAuthority uri)
Packit acf257
Packit acf257
  withHost = 
Packit acf257
    case uriToAuthorityString uri{uriAuthority=updAuth} of
Packit acf257
      "" -> id
Packit acf257
      h  -> ((Header HdrHost h):)
Packit acf257
Packit acf257
  uri_req 
Packit acf257
   | forProxy  = uri
Packit acf257
   | otherwise = snd (splitRequestURI uri)
Packit acf257
-}
Packit acf257
Packit acf257
Packit acf257
toBufOps :: BufferType a => Request a -> BufferOp a
Packit acf257
toBufOps _ = bufferOps
Packit acf257
Packit acf257
-----------------------------------------------------------------
Packit acf257
------------------ Parsing --------------------------------------
Packit acf257
-----------------------------------------------------------------
Packit acf257
Packit acf257
-- Parsing a request
Packit acf257
parseRequestHead :: [String] -> Result RequestData
Packit acf257
parseRequestHead         [] = Left ErrorClosed
Packit acf257
parseRequestHead (com:hdrs) = do
Packit acf257
  (version,rqm,uri) <- requestCommand com (words com)
Packit acf257
  hdrs'              <- parseHeaders hdrs
Packit acf257
  return (rqm,uri,withVer version hdrs')
Packit acf257
 where
Packit acf257
  withVer [] hs = hs
Packit acf257
  withVer (h:_) hs = withVersion h hs
Packit acf257
Packit acf257
  requestCommand l _yes@(rqm:uri:version) =
Packit acf257
    case (parseURIReference uri, lookup rqm rqMethodMap) of
Packit acf257
     (Just u, Just r) -> return (version,r,u)
Packit acf257
     (Just u, Nothing) -> return (version,Custom rqm,u)
Packit acf257
     _                -> parse_err l
Packit acf257
  requestCommand l _
Packit acf257
   | null l    = failWith ErrorClosed
Packit acf257
   | otherwise = parse_err l
Packit acf257
Packit acf257
  parse_err l = responseParseError "parseRequestHead"
Packit acf257
                   ("Request command line parse failure: " ++ l)
Packit acf257
Packit acf257
-- Parsing a response
Packit acf257
parseResponseHead :: [String] -> Result ResponseData
Packit acf257
parseResponseHead []         = failWith ErrorClosed
Packit acf257
parseResponseHead (sts:hdrs) = do
Packit acf257
  (version,code,reason)  <- responseStatus sts (words sts)
Packit acf257
  hdrs'                  <- parseHeaders hdrs
Packit acf257
  return (code,reason, withVersion version hdrs')
Packit acf257
 where
Packit acf257
  responseStatus _l _yes@(version:code:reason) =
Packit acf257
    return (version,match code,concatMap (++" ") reason)
Packit acf257
  responseStatus l _no 
Packit acf257
    | null l    = failWith ErrorClosed  -- an assumption
Packit acf257
    | otherwise = parse_err l
Packit acf257
Packit acf257
  parse_err l = 
Packit acf257
    responseParseError 
Packit acf257
        "parseResponseHead"
Packit acf257
        ("Response status line parse failure: " ++ l)
Packit acf257
Packit acf257
  match [a,b,c] = (digitToInt a,
Packit acf257
                   digitToInt b,
Packit acf257
                   digitToInt c)
Packit acf257
  match _ = (-1,-1,-1)  -- will create appropriate behaviour
Packit acf257
Packit acf257
-- To avoid changing the @RequestData@ and @ResponseData@ types
Packit acf257
-- just for this (and the upstream backwards compat. woes that
Packit acf257
-- will result in), encode version info as a custom header.
Packit acf257
-- Used by 'parseResponseData' and 'parseRequestData'.
Packit acf257
--
Packit acf257
-- Note: the Request and Response types do not currently represent
Packit acf257
-- the version info explicitly in their record types. You have to use
Packit acf257
-- {get,set}{Request,Response}Version for that.
Packit acf257
withVersion :: String -> [Header] -> [Header]
Packit acf257
withVersion v hs 
Packit acf257
 | v == httpVersion = hs  -- don't bother adding it if the default.
Packit acf257
 | otherwise        = (Header (HdrCustom "X-HTTP-Version") v) : hs
Packit acf257
Packit acf257
-- | @getRequestVersion req@ returns the HTTP protocol version of
Packit acf257
-- the request @req@. If @Nothing@, the default 'httpVersion' can be assumed.
Packit acf257
getRequestVersion :: Request a -> Maybe String
Packit acf257
getRequestVersion r = getHttpVersion r
Packit acf257
Packit acf257
-- | @setRequestVersion v req@ returns a new request, identical to
Packit acf257
-- @req@, but with its HTTP version set to @v@.
Packit acf257
setRequestVersion :: String -> Request a -> Request a
Packit acf257
setRequestVersion s r = setHttpVersion r s
Packit acf257
Packit acf257
Packit acf257
-- | @getResponseVersion rsp@ returns the HTTP protocol version of
Packit acf257
-- the response @rsp@. If @Nothing@, the default 'httpVersion' can be 
Packit acf257
-- assumed.
Packit acf257
getResponseVersion :: Response a -> Maybe String
Packit acf257
getResponseVersion r = getHttpVersion r
Packit acf257
Packit acf257
-- | @setResponseVersion v rsp@ returns a new response, identical to
Packit acf257
-- @rsp@, but with its HTTP version set to @v@.
Packit acf257
setResponseVersion :: String -> Response a -> Response a
Packit acf257
setResponseVersion s r = setHttpVersion r s
Packit acf257
Packit acf257
-- internal functions for accessing HTTP-version info in
Packit acf257
-- requests and responses. Not exported as it exposes ho
Packit acf257
-- version info is represented internally.
Packit acf257
Packit acf257
getHttpVersion :: HasHeaders a => a -> Maybe String
Packit acf257
getHttpVersion r = 
Packit acf257
  fmap toVersion      $
Packit acf257
   find isHttpVersion $
Packit acf257
    getHeaders r
Packit acf257
 where
Packit acf257
  toVersion (Header _ x) = x
Packit acf257
Packit acf257
setHttpVersion :: HasHeaders a => a -> String -> a
Packit acf257
setHttpVersion r v = 
Packit acf257
  setHeaders r $
Packit acf257
   withVersion v  $
Packit acf257
    dropHttpVersion $
Packit acf257
     getHeaders r
Packit acf257
Packit acf257
dropHttpVersion :: [Header] -> [Header]
Packit acf257
dropHttpVersion hs = filter (not.isHttpVersion) hs
Packit acf257
Packit acf257
isHttpVersion :: Header -> Bool
Packit acf257
isHttpVersion (Header (HdrCustom "X-HTTP-Version") _) = True
Packit acf257
isHttpVersion _ = False    
Packit acf257
Packit acf257
Packit acf257
Packit acf257
-----------------------------------------------------------------
Packit acf257
------------------ HTTP Send / Recv ----------------------------------
Packit acf257
-----------------------------------------------------------------
Packit acf257
Packit acf257
data ResponseNextStep
Packit acf257
 = Continue
Packit acf257
 | Retry
Packit acf257
 | Done
Packit acf257
 | ExpectEntity
Packit acf257
 | DieHorribly String
Packit acf257
Packit acf257
matchResponse :: RequestMethod -> ResponseCode -> ResponseNextStep
Packit acf257
matchResponse rqst rsp =
Packit acf257
    case rsp of
Packit acf257
        (1,0,0) -> Continue
Packit acf257
        (1,0,1) -> Done        -- upgrade to TLS
Packit acf257
        (1,_,_) -> Continue    -- default
Packit acf257
        (2,0,4) -> Done
Packit acf257
        (2,0,5) -> Done
Packit acf257
        (2,_,_) -> ans
Packit acf257
        (3,0,4) -> Done
Packit acf257
        (3,0,5) -> Done
Packit acf257
        (3,_,_) -> ans
Packit acf257
        (4,1,7) -> Retry       -- Expectation failed
Packit acf257
        (4,_,_) -> ans
Packit acf257
        (5,_,_) -> ans
Packit acf257
        (a,b,c) -> DieHorribly ("Response code " ++ map intToDigit [a,b,c] ++ " not recognised")
Packit acf257
    where
Packit acf257
        ans | rqst == HEAD = Done
Packit acf257
            | otherwise    = ExpectEntity
Packit acf257
        
Packit acf257
Packit acf257
        
Packit acf257
-----------------------------------------------------------------
Packit acf257
------------------ A little friendly funtionality ---------------
Packit acf257
-----------------------------------------------------------------
Packit acf257
Packit acf257
Packit acf257
{-
Packit acf257
    I had a quick look around but couldn't find any RFC about
Packit acf257
    the encoding of data on the query string.  I did find an
Packit acf257
    IETF memo, however, so this is how I justify the urlEncode
Packit acf257
    and urlDecode methods.
Packit acf257
Packit acf257
    Doc name: draft-tiwari-appl-wxxx-forms-01.txt  (look on www.ietf.org)
Packit acf257
Packit acf257
    Reserved chars:  ";", "/", "?", ":", "@", "&", "=", "+", ",", and "$" are reserved.
Packit acf257
    Unwise: "{" | "}" | "|" | "\" | "^" | "[" | "]" | "`"
Packit acf257
    URI delims: "<" | ">" | "#" | "%" | <">
Packit acf257
    Unallowed ASCII: <US-ASCII coded characters 00-1F and 7F hexadecimal>
Packit acf257
                     <US-ASCII coded character 20 hexadecimal>
Packit acf257
    Also unallowed:  any non-us-ascii character
Packit acf257
Packit acf257
    Escape method: char -> '%' a b  where a, b :: Hex digits
Packit acf257
-}
Packit acf257
Packit acf257
replacement_character :: Char
Packit acf257
replacement_character = '\xfffd'
Packit acf257
Packit acf257
-- | Encode a single Haskell Char to a list of Word8 values, in UTF8 format.
Packit acf257
--
Packit acf257
-- Shamelessly stolen from utf-8string-0.3.7
Packit acf257
encodeChar :: Char -> [Word8]
Packit acf257
encodeChar = map fromIntegral . go . ord
Packit acf257
 where
Packit acf257
  go oc
Packit acf257
   | oc <= 0x7f       = [oc]
Packit acf257
Packit acf257
   | oc <= 0x7ff      = [ 0xc0 + (oc `shiftR` 6)
Packit acf257
                        , 0x80 + oc .&. 0x3f
Packit acf257
                        ]
Packit acf257
Packit acf257
   | oc <= 0xffff     = [ 0xe0 + (oc `shiftR` 12)
Packit acf257
                        , 0x80 + ((oc `shiftR` 6) .&. 0x3f)
Packit acf257
                        , 0x80 + oc .&. 0x3f
Packit acf257
                        ]
Packit acf257
   | otherwise        = [ 0xf0 + (oc `shiftR` 18)
Packit acf257
                        , 0x80 + ((oc `shiftR` 12) .&. 0x3f)
Packit acf257
                        , 0x80 + ((oc `shiftR` 6) .&. 0x3f)
Packit acf257
                        , 0x80 + oc .&. 0x3f
Packit acf257
                        ]
Packit acf257
Packit acf257
-- | Decode a UTF8 string packed into a list of Word8 values, directly to String
Packit acf257
--
Packit acf257
-- Shamelessly stolen from utf-8string-0.3.7
Packit acf257
decode :: [Word8] -> String
Packit acf257
decode [    ] = ""
Packit acf257
decode (c:cs)
Packit acf257
  | c < 0x80  = chr (fromEnum c) : decode cs
Packit acf257
  | c < 0xc0  = replacement_character : decode cs
Packit acf257
  | c < 0xe0  = multi1
Packit acf257
  | c < 0xf0  = multi_byte 2 0xf  0x800
Packit acf257
  | c < 0xf8  = multi_byte 3 0x7  0x10000
Packit acf257
  | c < 0xfc  = multi_byte 4 0x3  0x200000
Packit acf257
  | c < 0xfe  = multi_byte 5 0x1  0x4000000
Packit acf257
  | otherwise = replacement_character : decode cs
Packit acf257
  where
Packit acf257
    multi1 = case cs of
Packit acf257
      c1 : ds | c1 .&. 0xc0 == 0x80 ->
Packit acf257
        let d = ((fromEnum c .&. 0x1f) `shiftL` 6) .|.  fromEnum (c1 .&. 0x3f)
Packit acf257
        in if d >= 0x000080 then toEnum d : decode ds
Packit acf257
                            else replacement_character : decode ds
Packit acf257
      _ -> replacement_character : decode cs
Packit acf257
Packit acf257
    multi_byte :: Int -> Word8 -> Int -> [Char]
Packit acf257
    multi_byte i mask overlong = aux i cs (fromEnum (c .&. mask))
Packit acf257
      where
Packit acf257
        aux 0 rs acc
Packit acf257
          | overlong <= acc && acc <= 0x10ffff &&
Packit acf257
            (acc < 0xd800 || 0xdfff < acc)     &&
Packit acf257
            (acc < 0xfffe || 0xffff < acc)      = chr acc : decode rs
Packit acf257
          | otherwise = replacement_character : decode rs
Packit acf257
Packit acf257
        aux n (r:rs) acc
Packit acf257
          | r .&. 0xc0 == 0x80 = aux (n-1) rs
Packit acf257
                               $ shiftL acc 6 .|. fromEnum (r .&. 0x3f)
Packit acf257
Packit acf257
        aux _ rs     _ = replacement_character : decode rs
Packit acf257
Packit acf257
Packit acf257
-- This function is a bit funny because potentially the input String could contain some actual Unicode
Packit acf257
-- characters (though this shouldn't happen for most use cases), so we have to preserve those characters
Packit acf257
-- while simultaneously decoding any UTF-8 data
Packit acf257
urlDecode :: String -> String
Packit acf257
urlDecode = go []
Packit acf257
  where
Packit acf257
    go bs ('%':a:b:rest)           = go (fromIntegral (16 * digitToInt a + digitToInt b) : bs) rest
Packit acf257
    go bs (h:t) | fromEnum h < 256 = go (fromIntegral (fromEnum h) : bs) t -- Treat ASCII as just another byte of UTF-8
Packit acf257
    go [] []                       = []
Packit acf257
    go [] (h:t)                    = h : go [] t -- h >= 256, so can't be part of any UTF-8 byte sequence
Packit acf257
    go bs rest                     = decode (reverse bs) ++ go [] rest
Packit acf257
Packit acf257
Packit acf257
urlEncode :: String -> String
Packit acf257
urlEncode     [] = []
Packit acf257
urlEncode (ch:t) 
Packit acf257
  | (isAscii ch && isAlphaNum ch) || ch `elem` "-_.~" = ch : urlEncode t
Packit acf257
  | not (isAscii ch) = foldr escape (urlEncode t) (encodeChar ch)
Packit acf257
  | otherwise = escape (fromIntegral (fromEnum ch)) (urlEncode t)
Packit acf257
    where
Packit acf257
     escape b rs = '%':showH (b `div` 16) (showH (b `mod` 16) rs)
Packit acf257
Packit acf257
     showH :: Word8 -> String -> String
Packit acf257
     showH x xs
Packit acf257
       | x <= 9    = to (o_0 + x) : xs
Packit acf257
       | otherwise = to (o_A + (x-10)) : xs
Packit acf257
      where
Packit acf257
       to  = toEnum  .  fromIntegral
Packit acf257
       fro = fromIntegral . fromEnum
Packit acf257
Packit acf257
       o_0 = fro '0'
Packit acf257
       o_A = fro 'A'
Packit acf257
Packit acf257
-- Encode form variables, useable in either the
Packit acf257
-- query part of a URI, or the body of a POST request.
Packit acf257
-- I have no source for this information except experience,
Packit acf257
-- this sort of encoding worked fine in CGI programming.
Packit acf257
urlEncodeVars :: [(String,String)] -> String
Packit acf257
urlEncodeVars ((n,v):t) =
Packit acf257
    let (same,diff) = partition ((==n) . fst) t
Packit acf257
    in urlEncode n ++ '=' : foldl (\x y -> x ++ ',' : urlEncode y) (urlEncode $ v) (map snd same)
Packit acf257
       ++ urlEncodeRest diff
Packit acf257
       where urlEncodeRest [] = []
Packit acf257
             urlEncodeRest diff = '&' : urlEncodeVars diff
Packit acf257
urlEncodeVars [] = []
Packit acf257
Packit acf257
-- | @getAuth req@ fishes out the authority portion of the URL in a request's @Host@
Packit acf257
-- header.
Packit acf257
getAuth :: Monad m => Request ty -> m URIAuthority
Packit acf257
getAuth r = 
Packit acf257
   -- ToDo: verify that Network.URI functionality doesn't take care of this (now.)
Packit acf257
  case parseURIAuthority auth of
Packit acf257
    Just x -> return x 
Packit acf257
    Nothing -> fail $ "Network.HTTP.Base.getAuth: Error parsing URI authority '" ++ auth ++ "'"
Packit acf257
 where 
Packit acf257
  auth = maybe (uriToAuthorityString uri) id (findHeader HdrHost r)
Packit acf257
  uri  = rqURI r
Packit acf257
Packit acf257
{-# DEPRECATED normalizeRequestURI "Please use Network.HTTP.Base.normalizeRequest instead" #-}
Packit acf257
normalizeRequestURI :: Bool{-do close-} -> {-URI-}String -> Request ty -> Request ty
Packit acf257
normalizeRequestURI doClose h r = 
Packit acf257
  (if doClose then replaceHeader HdrConnection "close" else id) $
Packit acf257
  insertHeaderIfMissing HdrHost h $
Packit acf257
    r { rqURI = (rqURI r){ uriScheme = ""
Packit acf257
                         , uriAuthority = Nothing
Packit acf257
                         }}
Packit acf257
Packit acf257
-- | @NormalizeRequestOptions@ brings together the various defaulting\/normalization options
Packit acf257
-- over 'Request's. Use 'defaultNormalizeRequestOptions' for the standard selection of option
Packit acf257
data NormalizeRequestOptions ty
Packit acf257
 = NormalizeRequestOptions
Packit acf257
     { normDoClose   :: Bool
Packit acf257
     , normForProxy  :: Bool
Packit acf257
     , normUserAgent :: Maybe String
Packit acf257
     , normCustoms   :: [RequestNormalizer ty]
Packit acf257
     }
Packit acf257
Packit acf257
-- | @RequestNormalizer@ is the shape of a (pure) function that rewrites
Packit acf257
-- a request into some normalized form.
Packit acf257
type RequestNormalizer ty = NormalizeRequestOptions ty -> Request ty -> Request ty
Packit acf257
Packit acf257
defaultNormalizeRequestOptions :: NormalizeRequestOptions ty
Packit acf257
defaultNormalizeRequestOptions = NormalizeRequestOptions
Packit acf257
     { normDoClose   = False
Packit acf257
     , normForProxy  = False
Packit acf257
     , normUserAgent = Just defaultUserAgent
Packit acf257
     , normCustoms   = []
Packit acf257
     }
Packit acf257
Packit acf257
-- | @normalizeRequest opts req@ is the entry point to use to normalize your
Packit acf257
-- request prior to transmission (or other use.) Normalization is controlled
Packit acf257
-- via the @NormalizeRequestOptions@ record.
Packit acf257
normalizeRequest :: NormalizeRequestOptions ty
Packit acf257
                 -> Request ty
Packit acf257
                 -> Request ty
Packit acf257
normalizeRequest opts req = foldr (\ f -> f opts) req normalizers
Packit acf257
 where
Packit acf257
  --normalizers :: [RequestNormalizer ty]
Packit acf257
  normalizers = 
Packit acf257
     ( normalizeHostURI
Packit acf257
     : normalizeBasicAuth
Packit acf257
     : normalizeConnectionClose
Packit acf257
     : normalizeUserAgent 
Packit acf257
     : normCustoms opts
Packit acf257
     )
Packit acf257
Packit acf257
-- | @normalizeUserAgent ua x req@ augments the request @req@ with 
Packit acf257
-- a @User-Agent: ua@ header if @req@ doesn't already have a 
Packit acf257
-- a @User-Agent:@ set.
Packit acf257
normalizeUserAgent :: RequestNormalizer ty
Packit acf257
normalizeUserAgent opts req = 
Packit acf257
  case normUserAgent opts of
Packit acf257
    Nothing -> req
Packit acf257
    Just ua -> 
Packit acf257
     case findHeader HdrUserAgent req of
Packit acf257
       Just u  | u /= defaultUserAgent -> req
Packit acf257
       _ -> replaceHeader HdrUserAgent ua req
Packit acf257
Packit acf257
-- | @normalizeConnectionClose opts req@ sets the header @Connection: close@ 
Packit acf257
-- to indicate one-shot behavior iff @normDoClose@ is @True@. i.e., it then
Packit acf257
-- _replaces_ any an existing @Connection:@ header in @req@.
Packit acf257
normalizeConnectionClose :: RequestNormalizer ty
Packit acf257
normalizeConnectionClose opts req 
Packit acf257
 | normDoClose opts = replaceHeader HdrConnection "close" req
Packit acf257
 | otherwise        = req
Packit acf257
Packit acf257
-- | @normalizeBasicAuth opts req@ sets the header @Authorization: Basic...@
Packit acf257
-- if the "user:pass@" part is present in the "http://user:pass@host/path"
Packit acf257
-- of the URI. If Authorization header was present already it is not replaced.
Packit acf257
normalizeBasicAuth :: RequestNormalizer ty
Packit acf257
normalizeBasicAuth _ req =
Packit acf257
  case getAuth req of
Packit acf257
    Just uriauth ->
Packit acf257
      case (user uriauth, password uriauth) of
Packit acf257
        (Just u, Just p) ->
Packit acf257
          insertHeaderIfMissing HdrAuthorization astr req
Packit acf257
            where
Packit acf257
              astr = "Basic " ++ base64encode (u ++ ":" ++ p)
Packit acf257
              base64encode = Base64.encode . stringToOctets :: String -> String
Packit acf257
              stringToOctets = map (fromIntegral . fromEnum) :: String -> [Word8]
Packit acf257
        (_, _) -> req
Packit acf257
    Nothing ->req
Packit acf257
Packit acf257
-- | @normalizeHostURI forProxy req@ rewrites your request to have it
Packit acf257
-- follow the expected formats by the receiving party (proxy or server.)
Packit acf257
-- 
Packit acf257
normalizeHostURI :: RequestNormalizer ty
Packit acf257
normalizeHostURI opts req = 
Packit acf257
  case splitRequestURI uri of
Packit acf257
    ("",_uri_abs)
Packit acf257
      | forProxy -> 
Packit acf257
         case findHeader HdrHost req of
Packit acf257
           Nothing -> req -- no host/authority in sight..not much we can do.
Packit acf257
           Just h  -> req{rqURI=uri{ uriAuthority=Just URIAuth{uriUserInfo="", uriRegName=hst, uriPort=pNum}
Packit acf257
                                   , uriScheme=if (null (uriScheme uri)) then "http" else uriScheme uri
Packit acf257
                                   }}
Packit acf257
            where 
Packit acf257
              hst = case span (/='@') user_hst of
Packit acf257
                       (as,'@':bs) ->
Packit acf257
                          case span (/=':') as of
Packit acf257
                            (_,_:_) -> bs
Packit acf257
                            _ -> user_hst
Packit acf257
                       _ -> user_hst
Packit acf257
Packit acf257
              (user_hst, pNum) =
Packit acf257
                 case span isDigit (reverse h) of
Packit acf257
                   (ds,':':bs) -> (reverse bs, ':':reverse ds)
Packit acf257
                   _ -> (h,"")
Packit acf257
      | otherwise -> 
Packit acf257
         case findHeader HdrHost req of
Packit acf257
           Nothing -> req -- no host/authority in sight..not much we can do...complain?
Packit acf257
           Just{}  -> req
Packit acf257
    (h,uri_abs) 
Packit acf257
      | forProxy  -> insertHeaderIfMissing HdrHost h req 
Packit acf257
      | otherwise -> replaceHeader HdrHost h req{rqURI=uri_abs} -- Note: _not_ stubbing out user:pass
Packit acf257
 where
Packit acf257
   uri0     = rqURI req 
Packit acf257
     -- stub out the user:pass 
Packit acf257
   uri      = uri0{uriAuthority=fmap (\ x -> x{uriUserInfo=""}) (uriAuthority uri0)}
Packit acf257
Packit acf257
   forProxy = normForProxy opts
Packit acf257
Packit acf257
{- Comments re: above rewriting:
Packit acf257
    RFC 2616, section 5.1.2:
Packit acf257
     "The most common form of Request-URI is that used to identify a
Packit acf257
      resource on an origin server or gateway. In this case the absolute
Packit acf257
      path of the URI MUST be transmitted (see section 3.2.1, abs_path) as
Packit acf257
      the Request-URI, and the network location of the URI (authority) MUST
Packit acf257
      be transmitted in a Host header field." 
Packit acf257
   We assume that this is the case, so we take the host name from
Packit acf257
   the Host header if there is one, otherwise from the request-URI.
Packit acf257
   Then we make the request-URI an abs_path and make sure that there
Packit acf257
   is a Host header.
Packit acf257
-}
Packit acf257
Packit acf257
splitRequestURI :: URI -> ({-authority-}String, URI)
Packit acf257
splitRequestURI uri = (uriToAuthorityString uri, uri{uriScheme="", uriAuthority=Nothing})
Packit acf257
Packit acf257
-- Adds a Host header if one is NOT ALREADY PRESENT..
Packit acf257
{-# DEPRECATED normalizeHostHeader "Please use Network.HTTP.Base.normalizeRequest instead" #-}
Packit acf257
normalizeHostHeader :: Request ty -> Request ty
Packit acf257
normalizeHostHeader rq = 
Packit acf257
  insertHeaderIfMissing HdrHost
Packit acf257
                        (uriToAuthorityString $ rqURI rq)
Packit acf257
                        rq
Packit acf257
                                     
Packit acf257
-- Looks for a "Connection" header with the value "close".
Packit acf257
-- Returns True when this is found.
Packit acf257
findConnClose :: [Header] -> Bool
Packit acf257
findConnClose hdrs =
Packit acf257
  maybe False
Packit acf257
        (\ x -> map toLower (trim x) == "close")
Packit acf257
        (lookupHeader HdrConnection hdrs)
Packit acf257
Packit acf257
-- | Used when we know exactly how many bytes to expect.
Packit acf257
linearTransfer :: (Int -> IO (Result a)) -> Int -> IO (Result ([Header],a))
Packit acf257
linearTransfer readBlk n = fmapE (\str -> Right ([],str)) (readBlk n)
Packit acf257
Packit acf257
-- | Used when nothing about data is known,
Packit acf257
--   Unfortunately waiting for a socket closure
Packit acf257
--   causes bad behaviour.  Here we just
Packit acf257
--   take data once and give up the rest.
Packit acf257
hopefulTransfer :: BufferOp a
Packit acf257
                -> IO (Result a)
Packit acf257
                -> [a]
Packit acf257
                -> IO (Result ([Header],a))
Packit acf257
hopefulTransfer bufOps readL strs 
Packit acf257
    = readL >>= 
Packit acf257
      either (\v -> return $ Left v)
Packit acf257
             (\more -> if (buf_isEmpty bufOps more)
Packit acf257
                         then return (Right ([], buf_concat bufOps $ reverse strs))
Packit acf257
                         else hopefulTransfer bufOps readL (more:strs))
Packit acf257
Packit acf257
-- | A necessary feature of HTTP\/1.1
Packit acf257
--   Also the only transfer variety likely to
Packit acf257
--   return any footers.
Packit acf257
chunkedTransfer :: BufferOp a
Packit acf257
                -> IO (Result a)
Packit acf257
                -> (Int -> IO (Result a))
Packit acf257
                -> IO (Result ([Header], a))
Packit acf257
chunkedTransfer bufOps readL readBlk = chunkedTransferC bufOps readL readBlk [] 0
Packit acf257
Packit acf257
chunkedTransferC :: BufferOp a
Packit acf257
                 -> IO (Result a)
Packit acf257
                 -> (Int -> IO (Result a))
Packit acf257
                 -> [a]
Packit acf257
                 -> Int
Packit acf257
                 -> IO (Result ([Header], a))
Packit acf257
chunkedTransferC bufOps readL readBlk acc n = do
Packit acf257
  v <- readL
Packit acf257
  case v of
Packit acf257
    Left e -> return (Left e)
Packit acf257
    Right line 
Packit acf257
     | size == 0 -> 
Packit acf257
         -- last chunk read; look for trailing headers..
Packit acf257
        fmapE (\ strs -> do
Packit acf257
                 ftrs <- parseHeaders (map (buf_toStr bufOps) strs)
Packit acf257
                  -- insert (computed) Content-Length header.
Packit acf257
                 let ftrs' = Header HdrContentLength (show n) : ftrs
Packit acf257
                 return (ftrs',buf_concat bufOps (reverse acc)))
Packit acf257
Packit acf257
              (readTillEmpty2 bufOps readL [])
Packit acf257
Packit acf257
     | otherwise -> do
Packit acf257
         some <- readBlk size
Packit acf257
         case some of
Packit acf257
           Left e -> return (Left e)
Packit acf257
           Right cdata -> do
Packit acf257
               _ <- readL -- CRLF is mandated after the chunk block; ToDo: check that the line is empty.?
Packit acf257
               chunkedTransferC bufOps readL readBlk (cdata:acc) (n+size)
Packit acf257
     where
Packit acf257
      size 
Packit acf257
       | buf_isEmpty bufOps line = 0
Packit acf257
       | otherwise = 
Packit acf257
         case readHex (buf_toStr bufOps line) of
Packit acf257
          (hx,_):_ -> hx
Packit acf257
          _        -> 0
Packit acf257
Packit acf257
-- | Maybe in the future we will have a sensible thing
Packit acf257
--   to do here, at that time we might want to change
Packit acf257
--   the name.
Packit acf257
uglyDeathTransfer :: String -> IO (Result ([Header],a))
Packit acf257
uglyDeathTransfer loc = return (responseParseError loc "Unknown Transfer-Encoding")
Packit acf257
Packit acf257
-- | Remove leading crlfs then call readTillEmpty2 (not required by RFC)
Packit acf257
readTillEmpty1 :: BufferOp a
Packit acf257
               -> IO (Result a)
Packit acf257
               -> IO (Result [a])
Packit acf257
readTillEmpty1 bufOps readL =
Packit acf257
  readL >>=
Packit acf257
    either (return . Left)
Packit acf257
           (\ s -> 
Packit acf257
               if buf_isLineTerm bufOps s
Packit acf257
                then readTillEmpty1 bufOps readL
Packit acf257
                else readTillEmpty2 bufOps readL [s])
Packit acf257
Packit acf257
-- | Read lines until an empty line (CRLF),
Packit acf257
--   also accepts a connection close as end of
Packit acf257
--   input, which is not an HTTP\/1.1 compliant
Packit acf257
--   thing to do - so probably indicates an
Packit acf257
--   error condition.
Packit acf257
readTillEmpty2 :: BufferOp a
Packit acf257
               -> IO (Result a)
Packit acf257
               -> [a]
Packit acf257
               -> IO (Result [a])
Packit acf257
readTillEmpty2 bufOps readL list =
Packit acf257
    readL >>=
Packit acf257
      either (return . Left)
Packit acf257
             (\ s ->
Packit acf257
                if buf_isLineTerm bufOps s || buf_isEmpty bufOps s
Packit acf257
                 then return (Right $ reverse (s:list))
Packit acf257
                 else readTillEmpty2 bufOps readL (s:list))
Packit acf257
Packit acf257
--
Packit acf257
-- Misc
Packit acf257
--
Packit acf257
Packit acf257
-- | @catchIO a h@ handles IO action exceptions throughout codebase; version-specific
Packit acf257
-- tweaks better go here.
Packit acf257
catchIO :: IO a -> (IOException -> IO a) -> IO a
Packit acf257
catchIO a h = Exception.catch a h
Packit acf257
Packit acf257
catchIO_ :: IO a -> IO a -> IO a
Packit acf257
catchIO_ a h = Exception.catch a (\(_ :: IOException) -> h)
Packit acf257
Packit acf257
responseParseError :: String -> String -> Result a
Packit acf257
responseParseError loc v = failWith (ErrorParse (loc ++ ' ':v))