packages feed

http-enumerator 0.7.1.4 → 0.7.1.5

raw patch · 7 files changed

+1249/−1249 lines, 7 filesdep ~attoparsecdep ~case-insensitivedep ~containerssetup-changedPVP ok

version bump matches the API change (PVP)

Dependency ranges changed: attoparsec, case-insensitive, containers

API changes (from Hackage documentation)

Files

LICENSE view
@@ -1,25 +1,25 @@-The following license covers this documentation, and the source code, except
-where otherwise indicated.
-
-Copyright 2010, Michael Snoyman. All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
-  list of conditions and the following disclaimer.
-
-* Redistributions in binary form must reproduce the above copyright notice,
-  this list of conditions and the following disclaimer in the documentation
-  and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR
-IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
-EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,
-INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
-OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
-LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
-OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
-ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+The following license covers this documentation, and the source code, except+where otherwise indicated.++Copyright 2010, Michael Snoyman. All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++* Redistributions of source code must retain the above copyright notice, this+  list of conditions and the following disclaimer.++* Redistributions in binary form must reproduce the above copyright notice,+  this list of conditions and the following disclaimer in the documentation+  and/or other materials provided with the distribution.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO+EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,+OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Network/HTTP/Enumerator.hs view
@@ -1,885 +1,885 @@-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE RecordWildCards #-}
-{-# LANGUAGE DeriveDataTypeable #-}
-{-# LANGUAGE FlexibleContexts #-}
-{-# LANGUAGE CPP #-}
--- | This module contains everything you need to initiate HTTP connections.  If
--- you want a simple interface based on URLs, you can use 'simpleHttp'. If you
--- want raw power, 'http' is the underlying workhorse of this package. Some
--- examples:
---
--- > -- Just download an HTML document and print it.
--- > import Network.HTTP.Enumerator
--- > import qualified Data.ByteString.Lazy as L
--- >
--- > main = simpleHttp "http://www.haskell.org/" >>= L.putStr
---
--- This example uses interleaved IO to write the response body to a file in
--- constant memory space. By using 'httpRedirect', it will automatically
--- follow 3xx redirects.
---
--- > import Data.Enumerator
--- > import Data.Enumerator.Binary
--- > import Network.HTTP.Enumerator
--- > import System.IO
--- >
--- > main :: IO ()
--- > main = withFile "google.html" WriteMode $ \handle -> do
--- >     request <- parseUrl "http://google.com/"
--- >     withManager $ \manager -> do
--- >         run_ $ httpRedirect request (\_ _ -> iterHandle handle) manager
---
--- The following headers are automatically set by this module, and should not
--- be added to 'requestHeaders':
---
--- * Content-Length
---
--- * Host
---
--- * Accept-Encoding (not currently set, but client usage of this variable /will/ cause breakage).
---
--- Any network code on Windows requires some initialization, and the network
--- library provides withSocketsDo to perform it. Therefore, proper usage of
--- this library will always involve calling that function at some point.  The
--- best approach is to simply call them at the beginning of your main function,
--- such as:
---
--- > import Network.HTTP.Enumerator
--- > import qualified Data.ByteString.Lazy as L
--- > import Network (withSocketsDo)
--- >
--- > main = withSocketsDo
--- >      $ simpleHttp "http://www.haskell.org/" >>= L.putStr
-module Network.HTTP.Enumerator
-    ( -- * Perform a request
-      simpleHttp
-    , httpLbs
-    , httpLbsRedirect
-    , http
-    , httpRedirect
-    , redirectIter
-      -- * Datatypes
-    , Proxy (..)
-    , RequestBody (..)
-    , Response (..)
-      -- ** Request
-    , Request
-    , def
-    , method
-    , secure
-    , checkCerts
-    , host
-    , port
-    , path
-    , queryString
-    , requestHeaders
-    , requestBody
-    , proxy
-    , rawBody
-    , decompress
-      -- *** Defaults
-    , defaultCheckCerts
-      -- * Manager
-    , Manager
-    , newManager
-    , closeManager
-    , withManager
-      -- * Utility functions
-    , parseUrl
-    , applyBasicAuth
-    , addProxy
-    , semiParseUrl
-    , lbsIter
-      -- * Decompression predicates
-    , alwaysDecompress
-    , browserDecompress
-      -- * Request bodies
-    , urlEncodedBody
-      -- * Exceptions
-    , HttpException (..)
-    ) where
-
-import qualified Network.TLS.Client.Enumerator as TLS
-import Network (connectTo, PortID (PortNumber))
-
-import qualified Network.Socket as NS
-import qualified Data.ByteString as S
-import qualified Data.ByteString.Lazy as L
-import qualified Data.ByteString.Char8 as S8
-import Data.Enumerator
-    ( Iteratee (..), Stream (..), catchError, throwError
-    , yield, Step (..), Enumeratee, ($$), joinI, Enumerator, run_
-    , returnI, (>==>), (>>==), continue, checkDone, enumEOF
-    , (=$)
-    )
-import qualified Data.Enumerator.List as EL
-import Network.HTTP.Enumerator.HttpParser
-import Control.Exception (Exception, bracket, throwIO, SomeException, try)
-import Control.Arrow (first)
-import Control.Monad.IO.Class (MonadIO (liftIO))
-import Control.Monad.Trans.Class (lift)
-import Control.Failure (Failure (failure))
-import Data.Typeable (Typeable)
-import Codec.Binary.UTF8.String (encodeString)
-import qualified Blaze.ByteString.Builder as Blaze
-import Blaze.ByteString.Builder.Enumerator (builderToByteString)
-import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator)
-import Data.Monoid (Monoid (..))
-import qualified Network.HTTP.Types as W
-import qualified Data.CaseInsensitive as CI
-import Data.Int (Int64)
-import qualified Codec.Zlib.Enum as Z
-import Control.Monad.IO.Control (MonadControlIO, liftIOOp)
-import Data.Map (Map)
-import qualified Data.Map as Map
-import qualified Data.IORef as I
-import Control.Applicative ((<$>))
-import Data.Certificate.X509 (X509)
-import Network.TLS.Extra (certificateVerifyChain, certificateVerifyDomain)
-import qualified Data.ByteString.Base64 as B64
-import System.IO (hClose, hFlush)
-import Blaze.ByteString.Builder (toByteString)
-import Data.Maybe (fromMaybe)
-import Data.Default (Default (def))
-import Numeric (showHex)
-#if !MIN_VERSION_base(4,3,0)
-import GHC.IO.Handle.Types
-import System.IO                (hWaitForInput, hIsEOF)
-import System.IO.Error          (mkIOError, illegalOperationErrorType)
-
--- | Like 'hGet', except that a shorter 'ByteString' may be returned
--- if there are not enough bytes immediately available to satisfy the
--- whole request.  'hGetSome' only blocks if there is no data
--- available, and EOF has not yet been reached.
---
-hGetSome :: Handle -> Int -> IO S.ByteString
-hGetSome hh i
-    | i >  0    = let
-                   loop = do
-                     s <- S.hGetNonBlocking hh i
-                     if not (S.null s)
-                        then return s
-                        else do eof <- hIsEOF hh
-                                if eof then return s
-                                       else hWaitForInput hh (-1) >> loop
-                                         -- for this to work correctly, the
-                                         -- Handle should be in binary mode
-                                         -- (see GHC ticket #3808)
-                  in loop
-    | i == 0    = return S.empty
-    | otherwise = illegalBufferSize hh "hGetSome" i
-
-illegalBufferSize :: Handle -> String -> Int -> IO a
-illegalBufferSize handle fn sz =
-    ioError (mkIOError illegalOperationErrorType msg (Just handle) Nothing)
-    --TODO: System.IO uses InvalidArgument here, but it's not exported :-(
-    where
-      msg = fn ++ ": illegal ByteString size " ++ showsPrec 9 sz []
-#endif
-
-getSocket :: String -> Int -> IO NS.Socket
-getSocket host' port' = do
-    let hints = NS.defaultHints {
-                          NS.addrFlags = [NS.AI_ADDRCONFIG]
-                        , NS.addrSocketType = NS.Stream
-                        }
-    (addr:_) <- NS.getAddrInfo (Just hints) (Just host') (Just $ show port')
-    sock <- NS.socket (NS.addrFamily addr) (NS.addrSocketType addr)
-                      (NS.addrProtocol addr)
-    ee <- try' $ NS.connect sock (NS.addrAddress addr)
-    case ee of
-        Left e -> NS.sClose sock >> throwIO e
-        Right () -> return sock
-  where
-    try' :: IO a -> IO (Either SomeException a)
-    try' = try
-
-withSocketConn :: MonadIO m
-               => Manager
-               -> String
-               -> Int
-               -> Enumerator Blaze.Builder m ()
-               -> Step S.ByteString m (Bool, a)
-               -> Iteratee S.ByteString m a
-withSocketConn man host' port' =
-    withManagedConn man (host', port', False) $
-        fmap TLS.socketConn $ getSocket host' port'
-
-withManagedConn
-    :: MonadIO m
-    => Manager
-    -> ConnKey
-    -> IO TLS.ConnInfo
-    -> Enumerator Blaze.Builder m ()
-    -> Step S.ByteString m (Bool, a) -- ^ Bool indicates if the connection should go back in the manager
-    -> Iteratee S.ByteString m a
-withManagedConn man key open req step = do
-    mci <- liftIO $ takeInsecureSocket man key
-    (ci, isManaged) <-
-        case mci of
-            Nothing -> do
-                ci <- liftIO open
-                return (ci, False)
-            Just ci -> return (ci, True)
-    catchError
-        (do
-            (toPut, a) <- withCI ci req step
-            liftIO $ if toPut
-                then putInsecureSocket man key ci
-                else TLS.connClose ci
-            return a)
-        (\se -> if isManaged
-                    then withManagedConn man key open req step
-                    else throwError se)
-
-withSslConn :: MonadIO m
-            => ([X509] -> IO TLS.TLSCertificateUsage)
-            -> Manager
-            -> String -- ^ host
-            -> Int -- ^ port
-            -> Enumerator Blaze.Builder m () -- ^ request
-            -> Step S.ByteString m (Bool, a) -- ^ response
-            -> Iteratee S.ByteString m a -- ^ response
-withSslConn checkCert man host' port' =
-    withManagedConn man (host', port', True) $
-        (connectTo host' (PortNumber $ fromIntegral port') >>= TLS.sslClientConn checkCert)
-
-withSslProxyConn :: MonadIO m
-            => ([X509] -> IO TLS.TLSCertificateUsage)
-            -> S8.ByteString -- ^ Target host
-            -> Int -- ^ Target port
-            -> Manager
-            -> String -- ^ Proxy host
-            -> Int -- ^ Proxy port
-            -> Enumerator Blaze.Builder m () -- ^ request
-            -> Step S.ByteString m (Bool, a) -- ^ response
-            -> Iteratee S.ByteString m a -- ^ response
-withSslProxyConn checkCert thost tport man phost pport =
-    withManagedConn man (phost, pport, True) $
-        doConnect >>= TLS.sslClientConn checkCert
-  where
-    doConnect = do
-        h <- connectTo phost (PortNumber $ fromIntegral pport)
-        S8.hPutStr h $ toByteString connectRequest
-        hFlush h
-#if MIN_VERSION_base(4,3,0)
-        r <- S.hGetSome h 2048
-#else
-        r <- hGetSome h 2048
-#endif
-        res <- parserHeadersFromByteString r
-        case res of
-            Right ((_, 200, _), _) -> return h
-            Right ((_, _, msg), _) -> hClose h >> proxyError (S8.unpack msg)
-            Left s -> hClose h >> proxyError s
-
-    connectRequest =
-        Blaze.fromByteString "CONNECT "
-            `mappend` Blaze.fromByteString thost
-            `mappend` Blaze.fromByteString (S8.pack (':' : show tport))
-            `mappend` Blaze.fromByteString " HTTP/1.1\r\n\r\n"
-    proxyError s =
-        error $ "Proxy failed to CONNECT to '"
-                ++ S8.unpack thost ++ ":" ++ show tport ++ "' : " ++ s
-
-withCI :: MonadIO m => TLS.ConnInfo -> Enumerator Blaze.Builder m () -> Enumerator S.ByteString m a
-withCI ci req step0 = do
-    lift $ run_ $ req $$ joinI $ builderToByteString $$ TLS.connIter ci
-    a <- TLS.connEnum ci step0
-    -- FIXME liftIO $ hClose handle
-    return a
-
-
--- | Define a HTTP proxy, consisting of a hostname and port number.
-
-data Proxy = Proxy
-    { proxyHost :: W.Ascii -- ^ The host name of the HTTP proxy.
-    , proxyPort :: Int -- ^ The port numner of the HTTP proxy.
-    }
-
-type ContentType = S.ByteString
-
--- | All information on how to connect to a host and what should be sent in the
--- HTTP request.
---
--- If you simply wish to download from a URL, see 'parseUrl'.
---
--- The constructor for this data type is not exposed. Instead, you should use
--- either the 'def' method to retrieve a default instance, or 'parseUrl' to
--- construct from a URL, and then use the records below to make modifications.
--- This approach allows http-enumerator to add configuration options without
--- breaking backwards compatibility.
-data Request m = Request
-    { method :: W.Method -- ^ HTTP request method, eg GET, POST.
-    , secure :: Bool -- ^ Whether to use HTTPS (ie, SSL).
-    , checkCerts :: W.Ascii -> [X509] -> IO TLS.TLSCertificateUsage -- ^ Check if the server certificate is valid. Only relevant for HTTPS.
-    , host :: W.Ascii
-    , port :: Int
-    , path :: W.Ascii -- ^ Everything from the host to the query string.
-    , queryString :: W.Query -- ^ Automatically escaped for your convenience.
-    , requestHeaders :: W.RequestHeaders
-    , requestBody :: RequestBody m
-    , proxy :: Maybe Proxy -- ^ Optional HTTP proxy.
-    , rawBody :: Bool -- ^ If True, a chunked and/or gzipped body will not be decoded. Use with caution.
-    , decompress :: ContentType -> Bool -- ^ Predicate to specify whether gzipped data should be decompressed on the fly.
-    }
-
--- | When using the 'RequestBodyEnum' constructor and any function which calls
--- 'redirectIter', you must ensure that the 'Enumerator' can be called multiple
--- times.
---
--- The 'RequestBodyEnumChunked' will send a chunked request body, note
--- that not all servers support this. Only use 'RequestBodyEnumChunked'
--- if you know the server you're sending to supports chunked request
--- bodies.
-data RequestBody m
-    = RequestBodyLBS L.ByteString
-    | RequestBodyBS S.ByteString
-    | RequestBodyBuilder Int64 Blaze.Builder
-    | RequestBodyEnum Int64 (Enumerator Blaze.Builder m ())
-    | RequestBodyEnumChunked (Enumerator Blaze.Builder m ())
-
-
--- | Add a Basic Auth header (with the specified user name and password) to the
--- given Request. Ignore error handling:
---
---    applyBasicAuth "user" "pass" $ fromJust $ parseUrl url
-
-applyBasicAuth :: S.ByteString -> S.ByteString -> Request m -> Request m
-applyBasicAuth user passwd req =
-    req { requestHeaders = authHeader : requestHeaders req }
-  where
-    authHeader = (CI.mk "Authorization", basic)
-    basic = S8.append "Basic " (B64.encode $ S8.concat [ user, ":", passwd ])
-
-
--- | Add a proxy to the the Request so that the Request when executed will use
--- the provided proxy.
-addProxy :: S.ByteString -> Int -> Request m -> Request m
-addProxy hst prt req =
-    req { proxy = Just $ Proxy hst prt }
-
-
--- | A simple representation of the HTTP response created by 'lbsIter'.
-data Response = Response
-    { statusCode :: Int
-    , responseHeaders :: W.ResponseHeaders
-    , responseBody :: L.ByteString
-    }
-    deriving (Show, Read, Eq, Typeable)
-
-enumSingle :: Monad m => a -> Enumerator a m b
-enumSingle x (Continue k) = k $ Chunks [x]
-enumSingle _ step = returnI step
-
-
--- | Always decompress a compressed stream.
-alwaysDecompress :: ContentType -> Bool
-alwaysDecompress = const True
-
--- | Decompress a compressed stream unless the content-type is 'application/x-tar'.
-browserDecompress :: ContentType -> Bool
-browserDecompress = (/= "application/x-tar")
-
-
--- | The most low-level function for initiating an HTTP request.
---
--- The first argument to this function gives a full specification on the
--- request: the host to connect to, whether to use SSL, headers, etc. Please
--- see 'Request' for full details.
---
--- The second argument specifies how the response should be handled. It's a
--- function that takes two arguments: the first is the HTTP status code of the
--- response, and the second is a list of all response headers. This module
--- exports 'lbsIter', which generates a 'Response' value.
---
--- Note that this allows you to have fully interleaved IO actions during your
--- HTTP download, making it possible to download very large responses in
--- constant memory.
-http
-     :: MonadIO m
-     => Request m
-     -> (W.Status -> W.ResponseHeaders -> Iteratee S.ByteString m a)
-     -> Manager
-     -> Iteratee S.ByteString m a
-http req@(Request {..}) bodyStep m =
-    withConn m connhost connport requestEnum $$ getResponse req bodyStep
-  where
-    (useProxy, connhost, connport) =
-        case proxy of
-            Just p -> (True, S8.unpack (proxyHost p), proxyPort p)
-            Nothing -> (False, S8.unpack host, port)
-    withConn =
-        case (secure, useProxy) of
-            (False, _) -> withSocketConn
-            (True, False) -> withSslConn $ checkCerts host
-            (True, True) -> withSslProxyConn (checkCerts host) host port
-    (contentLength, bodyEnum) =
-        case requestBody of
-            RequestBodyLBS lbs -> (Just $ L.length lbs, enumSingle $ Blaze.fromLazyByteString lbs)
-            RequestBodyBS bs -> (Just $ fromIntegral $ S.length bs, enumSingle $ Blaze.fromByteString bs)
-            RequestBodyBuilder i b -> (Just $ i, enumSingle b)
-            RequestBodyEnum i enum -> (Just i, enum)
-            RequestBodyEnumChunked enum -> (Nothing, \step -> enum $$ joinI $ chunkIt step)
-    hh
-        | port == 80 && not secure = host
-        | port == 443 && secure = host
-        | otherwise = host `mappend` S8.pack (':' : show port)
-    contentLengthHeader (Just contentLength') =
-            if method `elem` ["GET", "HEAD"] && contentLength' == 0
-                then id
-                else (:) ("Content-Length", S8.pack $ show contentLength')
-    contentLengthHeader Nothing = (:) ("Transfer-Encoding", "chunked")
-    headers' = ("Host", hh)
-                 : (contentLengthHeader contentLength)
-                 (("Accept-Encoding", "gzip") : requestHeaders)
-    requestHeaders' =
-            Blaze.fromByteString method
-            `mappend` Blaze.fromByteString " "
-            `mappend`
-                (if useProxy
-                    then Blaze.fromByteString (if secure then "https://" else "http://")
-                            `mappend` Blaze.fromByteString hh
-                    else mempty)
-            `mappend`
-                (case S8.uncons path of
-                    Just ('/', _) -> Blaze.fromByteString path
-                    _ -> Blaze.fromByteString "/"
-                            `mappend` Blaze.fromByteString path)
-            `mappend` (if null queryString
-                        then mempty
-                        else W.renderQueryBuilder True queryString)
-            `mappend` Blaze.fromByteString " HTTP/1.1\r\n"
-            `mappend` mconcat (flip map headers' $ \(k, v) ->
-                Blaze.fromByteString (CI.original k)
-                `mappend`  Blaze.fromByteString ": "
-                `mappend` Blaze.fromByteString v
-                `mappend` Blaze.fromByteString "\r\n")
-            `mappend` Blaze.fromByteString "\r\n"
-    requestEnum = enumSingle requestHeaders' >==> bodyEnum
-
-getResponse :: MonadIO m
-            => Request m
-            -> (W.Status -> [W.Header] -> Iteratee S8.ByteString m a)
-            -> Iteratee S8.ByteString m (Bool, a)
-getResponse Request {..} bodyStep = do
-    ((_, sc, sm), hs) <- iterHeaders
-    let s = W.Status sc sm
-    let hs' = map (first CI.mk) hs
-    let mcl = lookup "content-length" hs'
-    let body' =
-            case (rawBody, ("transfer-encoding", "chunked") `elem` hs') of
-                (False, True) -> (chunkedEnumeratee =$)
-                (True , True) -> (chunkedTerminator =$)
-                (_    , False) -> case mcl >>= readMay . S8.unpack of
-                                      Just len -> (takeLBS len =$)
-                                      Nothing  -> id
-    let decompresser =
-            if needsGunzip hs'
-                then (Z.ungzip =$)
-                else id
-    -- RFC 2616 section 4.4_1 defines responses that must not include a body
-    res <-
-        if hasNoBody method sc
-            then enumEOF $$ bodyStep s hs'
-            else body' $ decompresser $ do
-                    x <- bodyStep s hs'
-                    flushStream
-                    return x
-
-    -- should we put this connection back into the connection manager?
-    let toPut = Just "close" /= lookup "connection" hs'
-    return (toPut, res)
-  where
-    hasNoBody :: S8.ByteString -- ^ request method
-              -> Int -- ^ status code
-              -> Bool
-    hasNoBody "HEAD" _ = True
-    hasNoBody _ 204 = True
-    hasNoBody _ 304 = True
-    hasNoBody _ i = 100 <= i && i < 200
-
-    needsGunzip :: [W.Header] -> Bool
-    needsGunzip hs' =
-            not rawBody
-         && ("content-encoding", "gzip") `elem` hs'
-         && decompress (fromMaybe "" $ lookup "content-type" hs')
-
-flushStream :: Monad m => Iteratee a m ()
-flushStream = do
-    x <- EL.head
-    case x of
-        Nothing -> return ()
-        Just _ -> flushStream
-
-chunkedEnumeratee :: MonadIO m => Enumeratee S.ByteString S.ByteString m a
-chunkedEnumeratee k@(Continue _) = do
-    len <- catchParser "Chunk header" iterChunkHeader
-    if len == 0
-        then return k
-        else do
-            k' <- takeLBS len k
-            catchParser "End of chunk newline" iterNewline
-            chunkedEnumeratee k'
-chunkedEnumeratee step = return step
-
-chunkedTerminator :: MonadIO m => Enumeratee S.ByteString S.ByteString m a
-chunkedTerminator (Continue k) = do
-    len <- catchParser "Chunk header" iterChunkHeader
-    k' <- sendCont k $ S8.pack $ showHex len "\r\n"
-    if len == 0
-        then return k'
-        else do
-            step <- takeLBS len k'
-            catchParser "End of chunk newline" iterNewline
-            case step of
-                Continue k'' -> do
-                    k''' <- sendCont k'' "\r\n"
-                    chunkedTerminator k'''
-                _ -> return step
-chunkedTerminator step = return step
-
-sendCont :: Monad m
-         => (Stream S8.ByteString -> Iteratee S8.ByteString m a)
-         -> S8.ByteString
-         -> Iteratee S8.ByteString m (Step S8.ByteString m a)
-sendCont k bs = lift $ runIteratee $ k $ Chunks [bs]
-
-chunkIt :: Monad m => Enumeratee Blaze.Builder Blaze.Builder m a
-chunkIt = checkDone $ continue . step
-  where
-    step k EOF = k (Chunks [chunkedTransferTerminator]) >>== return
-    step k (Chunks []) = continue $ step k
-    step k (Chunks xs) = k (Chunks [chunkedTransferEncoding $ mconcat xs])
-                         >>== chunkIt
-
-
-takeLBS :: MonadIO m => Int -> Enumeratee S.ByteString S.ByteString m a
-takeLBS 0 step = return step
-takeLBS len (Continue k) = do
-    mbs <- EL.head
-    case mbs of
-        Nothing -> return $ Continue k
-        Just bs -> do
-            let (len', chunk, rest) =
-                    if S.length bs > len
-                        then (0, S.take len bs,
-                                if S.length bs == len
-                                    then Chunks []
-                                    else Chunks [S.drop len bs])
-                        else (len - S.length bs, bs, Chunks [])
-            step' <- lift $ runIteratee $ k $ Chunks [chunk]
-            if len' == 0
-                then yield step' rest
-                else takeLBS len' step'
-takeLBS _ step = return step
-
-encodeUrlCharPI :: Bool -> Char -> String
-encodeUrlCharPI _ '/' = "/"
-encodeUrlCharPI False '?' = "?"
-encodeUrlCharPI False '&' = "&"
-encodeUrlCharPI False '=' = "="
-encodeUrlCharPI _ c = encodeUrlChar c
-
-encodeUrlChar :: Char -> String
-encodeUrlChar c
-    -- List of unreserved characters per RFC 3986
-    -- Gleaned from http://en.wikipedia.org/wiki/Percent-encoding
-    | 'A' <= c && c <= 'Z' = [c]
-    | 'a' <= c && c <= 'z' = [c]
-    | '0' <= c && c <= '9' = [c]
-encodeUrlChar c@'-' = [c]
-encodeUrlChar c@'_' = [c]
-encodeUrlChar c@'.' = [c]
-encodeUrlChar c@'~' = [c]
-encodeUrlChar ' ' = "+"
-encodeUrlChar y =
-    let (a, c) = fromEnum y `divMod` 16
-        b = a `mod` 16
-        showHex' x
-            | x < 10 = toEnum $ x + (fromEnum '0')
-            | x < 16 = toEnum $ x - 10 + (fromEnum 'A')
-            | otherwise = error $ "Invalid argument to showHex: " ++ show x
-     in ['%', showHex' b, showHex' c]
-
--- | Convert a URL into a 'Request'.
---
--- This defaults some of the values in 'Request', such as setting 'method' to
--- GET and 'requestHeaders' to @[]@.
---
--- Since this function uses 'Failure', the return monad can be anything that is
--- an instance of 'Failure', such as 'IO' or 'Maybe'.
-parseUrl :: Failure HttpException m => String -> m (Request m')
-parseUrl = parseUrlHelper True
-
--- | Same as 'parseUrl', with one distinction: this function will not attempt
--- to parse the query string, but instead leave it with the path info. This can
--- be useful if you need precise control of the rendering of the query string,
--- such as using semicolons instead of ampersands.
-semiParseUrl :: Failure HttpException m => String -> m (Request m')
-semiParseUrl = parseUrlHelper False
-
-parseUrlHelper :: Failure HttpException m => Bool -> String -> m (Request m')
-parseUrlHelper parsePath s@('h':'t':'t':'p':':':'/':'/':rest) = parseUrl1 s False parsePath rest
-parseUrlHelper parsePath s@('h':'t':'t':'p':'s':':':'/':'/':rest) = parseUrl1 s True parsePath rest
-parseUrlHelper _ x = failure $ InvalidUrlException x "Invalid scheme"
-
-parseUrl1 :: Failure HttpException m
-          => String -> Bool -> Bool -> String -> m (Request m')
-parseUrl1 full sec parsePath s =
-    parseUrl2 full sec parsePath s'
-  where
-    s' = encodeString s
-
-defaultCheckCerts :: W.Ascii -> [X509] -> IO TLS.TLSCertificateUsage
-defaultCheckCerts host' certs =
-    case certificateVerifyDomain (S8.unpack host') certs of
-        TLS.CertificateUsageAccept -> certificateVerifyChain certs
-        _                          -> return TLS.CertificateUsageAccept
-
-instance Default (Request m) where
-    def = Request
-        { host = "localhost"
-        , port = 80
-        , secure = False
-        , checkCerts = defaultCheckCerts
-        , requestHeaders = []
-        , path = "/"
-        , queryString = []
-        , requestBody = RequestBodyLBS L.empty
-        , method = "GET"
-        , proxy = Nothing
-        , rawBody = False
-        , decompress = alwaysDecompress
-        }
-
-parseUrl2 :: Failure HttpException m
-          => String -> Bool -> Bool -> String -> m (Request m')
-parseUrl2 full sec parsePath s = do
-    port' <- mport
-    return def
-        { host = S8.pack hostname
-        , port = port'
-        , secure = sec
-        , path = S8.pack
-                    $ if null path''
-                            then "/"
-                            else concatMap (encodeUrlCharPI parsePath) path''
-        , queryString = if parsePath
-                            then W.parseQuery $ S8.pack qstring
-                            else []
-        }
-  where
-    (beforeSlash, afterSlash) = break (== '/') s
-    (hostname, portStr) = break (== ':') beforeSlash
-    (path', qstring') = break (== '?') afterSlash
-    path'' = if parsePath then path' else afterSlash
-    qstring'' = case qstring' of
-                '?':x -> x
-                _ -> qstring'
-    qstring = takeWhile (/= '#') qstring''
-    mport =
-        case (portStr, sec) of
-            ("", False) -> return 80
-            ("", True) -> return 443
-            (':':rest, _) ->
-                case readMay rest of
-                    Just i -> return i
-                    Nothing -> failure $ InvalidUrlException full "Invalid port"
-            x -> error $ "parseUrl1: this should never happen: " ++ show x
-
--- | Convert the HTTP response into a 'Response' value.
---
--- Even though a 'Response' contains a lazy bytestring, this function does
--- /not/ utilize lazy I/O, and therefore the entire response body will live in
--- memory. If you want constant memory usage, you'll need to write your own
--- iteratee and use 'http' or 'httpRedirect' directly.
-lbsIter :: Monad m => W.Status -> W.ResponseHeaders
-        -> Iteratee S.ByteString m Response
-lbsIter (W.Status sc _) hs = do
-    lbs <- fmap L.fromChunks EL.consume
-    return $ Response sc hs lbs
-
--- | Download the specified 'Request', returning the results as a 'Response'.
---
--- This is a simplified version of 'http' for the common case where you simply
--- want the response data as a simple datatype. If you want more power, such as
--- interleaved actions on the response body during download, you'll need to use
--- 'http' directly. This function is defined as:
---
--- @httpLbs = http lbsIter@
---
--- Please see 'lbsIter' for more information on how the 'Response' value is
--- created.
---
--- Even though a 'Response' contains a lazy bytestring, this function does
--- /not/ utilize lazy I/O, and therefore the entire response body will live in
--- memory. If you want constant memory usage, you'll need to write your own
--- iteratee and use 'http' or 'httpRedirect' directly.
-httpLbs :: MonadIO m => Request m -> Manager -> m Response
-httpLbs req = run_ . http req lbsIter
-
--- | Download the specified URL, following any redirects, and return the
--- response body.
---
--- This function will 'failure' an 'HttpException' for any response with a
--- non-2xx status code. It uses 'parseUrl' to parse the input. This function
--- essentially wraps 'httpLbsRedirect'.
---
--- Note: Even though this function returns a lazy bytestring, it does /not/
--- utilize lazy I/O, and therefore the entire response body will live in
--- memory. If you want constant memory usage, you'll need to write your own
--- iteratee and use 'http' or 'httpRedirect' directly.
-simpleHttp :: (MonadIO m, Failure HttpException m) => String -> m L.ByteString
-simpleHttp url = do
-    url' <- parseUrl url
-    Response sc _ b <- liftIO $ withManager $ httpLbsRedirect
-                                            $ url' { decompress = browserDecompress }
-    if 200 <= sc && sc < 300
-        then return b
-        else failure $ StatusCodeException sc b
-
-data HttpException = StatusCodeException Int L.ByteString
-                   | InvalidUrlException String String
-                   | TooManyRedirects
-                   | HttpParserException String
-    deriving (Show, Typeable)
-instance Exception HttpException
-
--- | Same as 'http', but follows all 3xx redirect status codes that contain a
--- location header.
-httpRedirect
-    :: (MonadIO m, Failure HttpException m)
-    => Request m
-    -> (W.Status -> W.ResponseHeaders -> Iteratee S.ByteString m a)
-    -> Manager
-    -> Iteratee S.ByteString m a
-httpRedirect req bodyStep manager =
-    http req (redirectIter 10 req bodyStep manager) manager
-
--- | Make a request automatically follow 3xx redirects.
---
--- Used internally by 'httpRedirect' and family.
-redirectIter :: (MonadIO m, Failure HttpException m)
-             => Int -- ^ number of redirects to attempt
-             -> Request m -- ^ Original request
-             -> (W.Status -> W.ResponseHeaders -> Iteratee S.ByteString m a)
-             -> Manager
-             -> (W.Status -> W.ResponseHeaders -> Iteratee S.ByteString m a)
-redirectIter redirects req bodyStep manager s@(W.Status code _) hs
-    | 300 <= code && code < 400 =
-        case lookup "location" hs of
-            Just l'' -> do
-                -- Prepend scheme, host and port if missing
-                let l' =
-                        case S8.uncons l'' of
-                            Just ('/', _) -> concat
-                                [ "http"
-                                , if secure req then "s" else ""
-                                , "://"
-                                , S8.unpack $ host req
-                                , ":"
-                                , show $ port req
-                                , S8.unpack l''
-                                ]
-                            _ -> S8.unpack l''
-                l <- lift $ parseUrl l'
-                let req' = req
-                        { host = host l
-                        , port = port l
-                        , secure = secure l
-                        , path = path l
-                        , queryString = queryString l
-                        , method =
-                            if code == 303
-                                then "GET"
-                                else method l
-                        }
-                if redirects == 0
-                    then lift $ failure TooManyRedirects
-                    else (http req') (redirectIter (redirects - 1) req' bodyStep manager) manager
-            Nothing -> bodyStep s hs
-    | otherwise = bodyStep s hs
-
--- | Download the specified 'Request', returning the results as a 'Response'
--- and automatically handling redirects.
---
--- This is a simplified version of 'httpRedirect' for the common case where you
--- simply want the response data as a simple datatype. If you want more power,
--- such as interleaved actions on the response body during download, you'll
--- need to use 'httpRedirect' directly. This function is defined as:
---
--- @httpLbsRedirect = httpRedirect lbsIter@
---
--- Please see 'lbsIter' for more information on how the 'Response' value is
--- created.
---
--- Even though a 'Response' contains a lazy bytestring, this function does
--- /not/ utilize lazy I/O, and therefore the entire response body will live in
--- memory. If you want constant memory usage, you'll need to write your own
--- iteratee and use 'http' or 'httpRedirect' directly.
-httpLbsRedirect :: (MonadIO m, Failure HttpException m) => Request m -> Manager -> m Response
-httpLbsRedirect req = run_ . httpRedirect req lbsIter
-
-readMay :: Read a => String -> Maybe a
-readMay s = case reads s of
-                [] -> Nothing
-                (x, _):_ -> Just x
-
--- FIXME add a helper for generating POST bodies
-
--- | Add url-encoded paramters to the 'Request'.
---
--- This sets a new 'requestBody', adds a content-type request header and
--- changes the 'method' to POST.
-urlEncodedBody :: Monad m => [(S.ByteString, S.ByteString)] -> Request m' -> Request m
-urlEncodedBody headers req = req
-    { requestBody = RequestBodyLBS body
-    , method = "POST"
-    , requestHeaders =
-        (ct, "application/x-www-form-urlencoded")
-      : filter (\(x, _) -> x /= ct) (requestHeaders req)
-    }
-  where
-    ct = "Content-Type"
-    body = L.fromChunks . return $ W.renderSimpleQuery False headers
-
-catchParser :: Monad m => String -> Iteratee a m b -> Iteratee a m b
-catchParser s i = catchError i (const $ throwError $ HttpParserException s)
-
--- | Keeps track of open connections for keep-alive.
-newtype Manager = Manager
-    { mConns :: I.IORef (Map ConnKey TLS.ConnInfo)
-    }
-
--- | ConnKey consists of a hostname, a port and a Bool specifying whether to
---   use keepalive.
-type ConnKey = (String, Int, Bool)
-
-takeInsecureSocket :: Manager -> ConnKey -> IO (Maybe TLS.ConnInfo)
-takeInsecureSocket man key =
-    I.atomicModifyIORef (mConns man) go
-  where
-    go m = (Map.delete key m, Map.lookup key m)
-
-putInsecureSocket :: Manager -> ConnKey -> TLS.ConnInfo -> IO ()
-putInsecureSocket man key ci = do
-    msock <- I.atomicModifyIORef (mConns man) go
-    maybe (return ()) TLS.connClose msock
-  where
-    go m = (Map.insert key ci m, Map.lookup key m)
-
--- | Create a new 'Manager' with no open connection.
-newManager :: IO Manager
-newManager = Manager <$> I.newIORef Map.empty
-
--- | Close all connections in a 'Manager'. Afterwards, the 'Manager' can be
--- reused if desired.
-closeManager :: Manager -> IO ()
-closeManager (Manager i) = do
-    m <- I.atomicModifyIORef i $ \x -> (Map.empty, x)
-    mapM_ TLS.connClose $ Map.elems m
-
--- | Create a new 'Manager', call the supplied function and then close it.
-withManager :: MonadControlIO m => (Manager -> m a) -> m a
-withManager = liftIOOp $ bracket newManager closeManager
+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DeriveDataTypeable #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE CPP #-}+-- | This module contains everything you need to initiate HTTP connections.  If+-- you want a simple interface based on URLs, you can use 'simpleHttp'. If you+-- want raw power, 'http' is the underlying workhorse of this package. Some+-- examples:+--+-- > -- Just download an HTML document and print it.+-- > import Network.HTTP.Enumerator+-- > import qualified Data.ByteString.Lazy as L+-- >+-- > main = simpleHttp "http://www.haskell.org/" >>= L.putStr+--+-- This example uses interleaved IO to write the response body to a file in+-- constant memory space. By using 'httpRedirect', it will automatically+-- follow 3xx redirects.+--+-- > import Data.Enumerator+-- > import Data.Enumerator.Binary+-- > import Network.HTTP.Enumerator+-- > import System.IO+-- >+-- > main :: IO ()+-- > main = withFile "google.html" WriteMode $ \handle -> do+-- >     request <- parseUrl "http://google.com/"+-- >     withManager $ \manager -> do+-- >         run_ $ httpRedirect request (\_ _ -> iterHandle handle) manager+--+-- The following headers are automatically set by this module, and should not+-- be added to 'requestHeaders':+--+-- * Content-Length+--+-- * Host+--+-- * Accept-Encoding (not currently set, but client usage of this variable /will/ cause breakage).+--+-- Any network code on Windows requires some initialization, and the network+-- library provides withSocketsDo to perform it. Therefore, proper usage of+-- this library will always involve calling that function at some point.  The+-- best approach is to simply call them at the beginning of your main function,+-- such as:+--+-- > import Network.HTTP.Enumerator+-- > import qualified Data.ByteString.Lazy as L+-- > import Network (withSocketsDo)+-- >+-- > main = withSocketsDo+-- >      $ simpleHttp "http://www.haskell.org/" >>= L.putStr+module Network.HTTP.Enumerator+    ( -- * Perform a request+      simpleHttp+    , httpLbs+    , httpLbsRedirect+    , http+    , httpRedirect+    , redirectIter+      -- * Datatypes+    , Proxy (..)+    , RequestBody (..)+    , Response (..)+      -- ** Request+    , Request+    , def+    , method+    , secure+    , checkCerts+    , host+    , port+    , path+    , queryString+    , requestHeaders+    , requestBody+    , proxy+    , rawBody+    , decompress+      -- *** Defaults+    , defaultCheckCerts+      -- * Manager+    , Manager+    , newManager+    , closeManager+    , withManager+      -- * Utility functions+    , parseUrl+    , applyBasicAuth+    , addProxy+    , semiParseUrl+    , lbsIter+      -- * Decompression predicates+    , alwaysDecompress+    , browserDecompress+      -- * Request bodies+    , urlEncodedBody+      -- * Exceptions+    , HttpException (..)+    ) where++import qualified Network.TLS.Client.Enumerator as TLS+import Network (connectTo, PortID (PortNumber))++import qualified Network.Socket as NS+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Char8 as S8+import Data.Enumerator+    ( Iteratee (..), Stream (..), catchError, throwError+    , yield, Step (..), Enumeratee, ($$), joinI, Enumerator, run_+    , returnI, (>==>), (>>==), continue, checkDone, enumEOF+    , (=$)+    )+import qualified Data.Enumerator.List as EL+import Network.HTTP.Enumerator.HttpParser+import Control.Exception (Exception, bracket, throwIO, SomeException, try)+import Control.Arrow (first)+import Control.Monad.IO.Class (MonadIO (liftIO))+import Control.Monad.Trans.Class (lift)+import Control.Failure (Failure (failure))+import Data.Typeable (Typeable)+import Codec.Binary.UTF8.String (encodeString)+import qualified Blaze.ByteString.Builder as Blaze+import Blaze.ByteString.Builder.Enumerator (builderToByteString)+import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator)+import Data.Monoid (Monoid (..))+import qualified Network.HTTP.Types as W+import qualified Data.CaseInsensitive as CI+import Data.Int (Int64)+import qualified Codec.Zlib.Enum as Z+import Control.Monad.IO.Control (MonadControlIO, liftIOOp)+import Data.Map (Map)+import qualified Data.Map as Map+import qualified Data.IORef as I+import Control.Applicative ((<$>))+import Data.Certificate.X509 (X509)+import Network.TLS.Extra (certificateVerifyChain, certificateVerifyDomain)+import qualified Data.ByteString.Base64 as B64+import System.IO (hClose, hFlush)+import Blaze.ByteString.Builder (toByteString)+import Data.Maybe (fromMaybe)+import Data.Default (Default (def))+import Numeric (showHex)+#if !MIN_VERSION_base(4,3,0)+import GHC.IO.Handle.Types+import System.IO                (hWaitForInput, hIsEOF)+import System.IO.Error          (mkIOError, illegalOperationErrorType)++-- | Like 'hGet', except that a shorter 'ByteString' may be returned+-- if there are not enough bytes immediately available to satisfy the+-- whole request.  'hGetSome' only blocks if there is no data+-- available, and EOF has not yet been reached.+--+hGetSome :: Handle -> Int -> IO S.ByteString+hGetSome hh i+    | i >  0    = let+                   loop = do+                     s <- S.hGetNonBlocking hh i+                     if not (S.null s)+                        then return s+                        else do eof <- hIsEOF hh+                                if eof then return s+                                       else hWaitForInput hh (-1) >> loop+                                         -- for this to work correctly, the+                                         -- Handle should be in binary mode+                                         -- (see GHC ticket #3808)+                  in loop+    | i == 0    = return S.empty+    | otherwise = illegalBufferSize hh "hGetSome" i++illegalBufferSize :: Handle -> String -> Int -> IO a+illegalBufferSize handle fn sz =+    ioError (mkIOError illegalOperationErrorType msg (Just handle) Nothing)+    --TODO: System.IO uses InvalidArgument here, but it's not exported :-(+    where+      msg = fn ++ ": illegal ByteString size " ++ showsPrec 9 sz []+#endif++getSocket :: String -> Int -> IO NS.Socket+getSocket host' port' = do+    let hints = NS.defaultHints {+                          NS.addrFlags = [NS.AI_ADDRCONFIG]+                        , NS.addrSocketType = NS.Stream+                        }+    (addr:_) <- NS.getAddrInfo (Just hints) (Just host') (Just $ show port')+    sock <- NS.socket (NS.addrFamily addr) (NS.addrSocketType addr)+                      (NS.addrProtocol addr)+    ee <- try' $ NS.connect sock (NS.addrAddress addr)+    case ee of+        Left e -> NS.sClose sock >> throwIO e+        Right () -> return sock+  where+    try' :: IO a -> IO (Either SomeException a)+    try' = try++withSocketConn :: MonadIO m+               => Manager+               -> String+               -> Int+               -> Enumerator Blaze.Builder m ()+               -> Step S.ByteString m (Bool, a)+               -> Iteratee S.ByteString m a+withSocketConn man host' port' =+    withManagedConn man (host', port', False) $+        fmap TLS.socketConn $ getSocket host' port'++withManagedConn+    :: MonadIO m+    => Manager+    -> ConnKey+    -> IO TLS.ConnInfo+    -> Enumerator Blaze.Builder m ()+    -> Step S.ByteString m (Bool, a) -- ^ Bool indicates if the connection should go back in the manager+    -> Iteratee S.ByteString m a+withManagedConn man key open req step = do+    mci <- liftIO $ takeInsecureSocket man key+    (ci, isManaged) <-+        case mci of+            Nothing -> do+                ci <- liftIO open+                return (ci, False)+            Just ci -> return (ci, True)+    catchError+        (do+            (toPut, a) <- withCI ci req step+            liftIO $ if toPut+                then putInsecureSocket man key ci+                else TLS.connClose ci+            return a)+        (\se -> if isManaged+                    then withManagedConn man key open req step+                    else throwError se)++withSslConn :: MonadIO m+            => ([X509] -> IO TLS.TLSCertificateUsage)+            -> Manager+            -> String -- ^ host+            -> Int -- ^ port+            -> Enumerator Blaze.Builder m () -- ^ request+            -> Step S.ByteString m (Bool, a) -- ^ response+            -> Iteratee S.ByteString m a -- ^ response+withSslConn checkCert man host' port' =+    withManagedConn man (host', port', True) $+        (connectTo host' (PortNumber $ fromIntegral port') >>= TLS.sslClientConn checkCert)++withSslProxyConn :: MonadIO m+            => ([X509] -> IO TLS.TLSCertificateUsage)+            -> S8.ByteString -- ^ Target host+            -> Int -- ^ Target port+            -> Manager+            -> String -- ^ Proxy host+            -> Int -- ^ Proxy port+            -> Enumerator Blaze.Builder m () -- ^ request+            -> Step S.ByteString m (Bool, a) -- ^ response+            -> Iteratee S.ByteString m a -- ^ response+withSslProxyConn checkCert thost tport man phost pport =+    withManagedConn man (phost, pport, True) $+        doConnect >>= TLS.sslClientConn checkCert+  where+    doConnect = do+        h <- connectTo phost (PortNumber $ fromIntegral pport)+        S8.hPutStr h $ toByteString connectRequest+        hFlush h+#if MIN_VERSION_base(4,3,0)+        r <- S.hGetSome h 2048+#else+        r <- hGetSome h 2048+#endif+        res <- parserHeadersFromByteString r+        case res of+            Right ((_, 200, _), _) -> return h+            Right ((_, _, msg), _) -> hClose h >> proxyError (S8.unpack msg)+            Left s -> hClose h >> proxyError s++    connectRequest =+        Blaze.fromByteString "CONNECT "+            `mappend` Blaze.fromByteString thost+            `mappend` Blaze.fromByteString (S8.pack (':' : show tport))+            `mappend` Blaze.fromByteString " HTTP/1.1\r\n\r\n"+    proxyError s =+        error $ "Proxy failed to CONNECT to '"+                ++ S8.unpack thost ++ ":" ++ show tport ++ "' : " ++ s++withCI :: MonadIO m => TLS.ConnInfo -> Enumerator Blaze.Builder m () -> Enumerator S.ByteString m a+withCI ci req step0 = do+    lift $ run_ $ req $$ joinI $ builderToByteString $$ TLS.connIter ci+    a <- TLS.connEnum ci step0+    -- FIXME liftIO $ hClose handle+    return a+++-- | Define a HTTP proxy, consisting of a hostname and port number.++data Proxy = Proxy+    { proxyHost :: W.Ascii -- ^ The host name of the HTTP proxy.+    , proxyPort :: Int -- ^ The port numner of the HTTP proxy.+    }++type ContentType = S.ByteString++-- | All information on how to connect to a host and what should be sent in the+-- HTTP request.+--+-- If you simply wish to download from a URL, see 'parseUrl'.+--+-- The constructor for this data type is not exposed. Instead, you should use+-- either the 'def' method to retrieve a default instance, or 'parseUrl' to+-- construct from a URL, and then use the records below to make modifications.+-- This approach allows http-enumerator to add configuration options without+-- breaking backwards compatibility.+data Request m = Request+    { method :: W.Method -- ^ HTTP request method, eg GET, POST.+    , secure :: Bool -- ^ Whether to use HTTPS (ie, SSL).+    , checkCerts :: W.Ascii -> [X509] -> IO TLS.TLSCertificateUsage -- ^ Check if the server certificate is valid. Only relevant for HTTPS.+    , host :: W.Ascii+    , port :: Int+    , path :: W.Ascii -- ^ Everything from the host to the query string.+    , queryString :: W.Query -- ^ Automatically escaped for your convenience.+    , requestHeaders :: W.RequestHeaders+    , requestBody :: RequestBody m+    , proxy :: Maybe Proxy -- ^ Optional HTTP proxy.+    , rawBody :: Bool -- ^ If True, a chunked and/or gzipped body will not be decoded. Use with caution.+    , decompress :: ContentType -> Bool -- ^ Predicate to specify whether gzipped data should be decompressed on the fly.+    }++-- | When using the 'RequestBodyEnum' constructor and any function which calls+-- 'redirectIter', you must ensure that the 'Enumerator' can be called multiple+-- times.+--+-- The 'RequestBodyEnumChunked' will send a chunked request body, note+-- that not all servers support this. Only use 'RequestBodyEnumChunked'+-- if you know the server you're sending to supports chunked request+-- bodies.+data RequestBody m+    = RequestBodyLBS L.ByteString+    | RequestBodyBS S.ByteString+    | RequestBodyBuilder Int64 Blaze.Builder+    | RequestBodyEnum Int64 (Enumerator Blaze.Builder m ())+    | RequestBodyEnumChunked (Enumerator Blaze.Builder m ())+++-- | Add a Basic Auth header (with the specified user name and password) to the+-- given Request. Ignore error handling:+--+--    applyBasicAuth "user" "pass" $ fromJust $ parseUrl url++applyBasicAuth :: S.ByteString -> S.ByteString -> Request m -> Request m+applyBasicAuth user passwd req =+    req { requestHeaders = authHeader : requestHeaders req }+  where+    authHeader = (CI.mk "Authorization", basic)+    basic = S8.append "Basic " (B64.encode $ S8.concat [ user, ":", passwd ])+++-- | Add a proxy to the the Request so that the Request when executed will use+-- the provided proxy.+addProxy :: S.ByteString -> Int -> Request m -> Request m+addProxy hst prt req =+    req { proxy = Just $ Proxy hst prt }+++-- | A simple representation of the HTTP response created by 'lbsIter'.+data Response = Response+    { statusCode :: Int+    , responseHeaders :: W.ResponseHeaders+    , responseBody :: L.ByteString+    }+    deriving (Show, Read, Eq, Typeable)++enumSingle :: Monad m => a -> Enumerator a m b+enumSingle x (Continue k) = k $ Chunks [x]+enumSingle _ step = returnI step+++-- | Always decompress a compressed stream.+alwaysDecompress :: ContentType -> Bool+alwaysDecompress = const True++-- | Decompress a compressed stream unless the content-type is 'application/x-tar'.+browserDecompress :: ContentType -> Bool+browserDecompress = (/= "application/x-tar")+++-- | The most low-level function for initiating an HTTP request.+--+-- The first argument to this function gives a full specification on the+-- request: the host to connect to, whether to use SSL, headers, etc. Please+-- see 'Request' for full details.+--+-- The second argument specifies how the response should be handled. It's a+-- function that takes two arguments: the first is the HTTP status code of the+-- response, and the second is a list of all response headers. This module+-- exports 'lbsIter', which generates a 'Response' value.+--+-- Note that this allows you to have fully interleaved IO actions during your+-- HTTP download, making it possible to download very large responses in+-- constant memory.+http+     :: MonadIO m+     => Request m+     -> (W.Status -> W.ResponseHeaders -> Iteratee S.ByteString m a)+     -> Manager+     -> Iteratee S.ByteString m a+http req@(Request {..}) bodyStep m =+    withConn m connhost connport requestEnum $$ getResponse req bodyStep+  where+    (useProxy, connhost, connport) =+        case proxy of+            Just p -> (True, S8.unpack (proxyHost p), proxyPort p)+            Nothing -> (False, S8.unpack host, port)+    withConn =+        case (secure, useProxy) of+            (False, _) -> withSocketConn+            (True, False) -> withSslConn $ checkCerts host+            (True, True) -> withSslProxyConn (checkCerts host) host port+    (contentLength, bodyEnum) =+        case requestBody of+            RequestBodyLBS lbs -> (Just $ L.length lbs, enumSingle $ Blaze.fromLazyByteString lbs)+            RequestBodyBS bs -> (Just $ fromIntegral $ S.length bs, enumSingle $ Blaze.fromByteString bs)+            RequestBodyBuilder i b -> (Just $ i, enumSingle b)+            RequestBodyEnum i enum -> (Just i, enum)+            RequestBodyEnumChunked enum -> (Nothing, \step -> enum $$ joinI $ chunkIt step)+    hh+        | port == 80 && not secure = host+        | port == 443 && secure = host+        | otherwise = host `mappend` S8.pack (':' : show port)+    contentLengthHeader (Just contentLength') =+            if method `elem` ["GET", "HEAD"] && contentLength' == 0+                then id+                else (:) ("Content-Length", S8.pack $ show contentLength')+    contentLengthHeader Nothing = (:) ("Transfer-Encoding", "chunked")+    headers' = ("Host", hh)+                 : (contentLengthHeader contentLength)+                 (("Accept-Encoding", "gzip") : requestHeaders)+    requestHeaders' =+            Blaze.fromByteString method+            `mappend` Blaze.fromByteString " "+            `mappend`+                (if useProxy+                    then Blaze.fromByteString (if secure then "https://" else "http://")+                            `mappend` Blaze.fromByteString hh+                    else mempty)+            `mappend`+                (case S8.uncons path of+                    Just ('/', _) -> Blaze.fromByteString path+                    _ -> Blaze.fromByteString "/"+                            `mappend` Blaze.fromByteString path)+            `mappend` (if null queryString+                        then mempty+                        else W.renderQueryBuilder True queryString)+            `mappend` Blaze.fromByteString " HTTP/1.1\r\n"+            `mappend` mconcat (flip map headers' $ \(k, v) ->+                Blaze.fromByteString (CI.original k)+                `mappend`  Blaze.fromByteString ": "+                `mappend` Blaze.fromByteString v+                `mappend` Blaze.fromByteString "\r\n")+            `mappend` Blaze.fromByteString "\r\n"+    requestEnum = enumSingle requestHeaders' >==> bodyEnum++getResponse :: MonadIO m+            => Request m+            -> (W.Status -> [W.Header] -> Iteratee S8.ByteString m a)+            -> Iteratee S8.ByteString m (Bool, a)+getResponse Request {..} bodyStep = do+    ((_, sc, sm), hs) <- iterHeaders+    let s = W.Status sc sm+    let hs' = map (first CI.mk) hs+    let mcl = lookup "content-length" hs'+    let body' =+            case (rawBody, ("transfer-encoding", "chunked") `elem` hs') of+                (False, True) -> (chunkedEnumeratee =$)+                (True , True) -> (chunkedTerminator =$)+                (_    , False) -> case mcl >>= readMay . S8.unpack of+                                      Just len -> (takeLBS len =$)+                                      Nothing  -> id+    let decompresser =+            if needsGunzip hs'+                then (Z.ungzip =$)+                else id+    -- RFC 2616 section 4.4_1 defines responses that must not include a body+    res <-+        if hasNoBody method sc+            then enumEOF $$ bodyStep s hs'+            else body' $ decompresser $ do+                    x <- bodyStep s hs'+                    flushStream+                    return x++    -- should we put this connection back into the connection manager?+    let toPut = Just "close" /= lookup "connection" hs'+    return (toPut, res)+  where+    hasNoBody :: S8.ByteString -- ^ request method+              -> Int -- ^ status code+              -> Bool+    hasNoBody "HEAD" _ = True+    hasNoBody _ 204 = True+    hasNoBody _ 304 = True+    hasNoBody _ i = 100 <= i && i < 200++    needsGunzip :: [W.Header] -> Bool+    needsGunzip hs' =+            not rawBody+         && ("content-encoding", "gzip") `elem` hs'+         && decompress (fromMaybe "" $ lookup "content-type" hs')++flushStream :: Monad m => Iteratee a m ()+flushStream = do+    x <- EL.head+    case x of+        Nothing -> return ()+        Just _ -> flushStream++chunkedEnumeratee :: MonadIO m => Enumeratee S.ByteString S.ByteString m a+chunkedEnumeratee k@(Continue _) = do+    len <- catchParser "Chunk header" iterChunkHeader+    if len == 0+        then return k+        else do+            k' <- takeLBS len k+            catchParser "End of chunk newline" iterNewline+            chunkedEnumeratee k'+chunkedEnumeratee step = return step++chunkedTerminator :: MonadIO m => Enumeratee S.ByteString S.ByteString m a+chunkedTerminator (Continue k) = do+    len <- catchParser "Chunk header" iterChunkHeader+    k' <- sendCont k $ S8.pack $ showHex len "\r\n"+    if len == 0+        then return k'+        else do+            step <- takeLBS len k'+            catchParser "End of chunk newline" iterNewline+            case step of+                Continue k'' -> do+                    k''' <- sendCont k'' "\r\n"+                    chunkedTerminator k'''+                _ -> return step+chunkedTerminator step = return step++sendCont :: Monad m+         => (Stream S8.ByteString -> Iteratee S8.ByteString m a)+         -> S8.ByteString+         -> Iteratee S8.ByteString m (Step S8.ByteString m a)+sendCont k bs = lift $ runIteratee $ k $ Chunks [bs]++chunkIt :: Monad m => Enumeratee Blaze.Builder Blaze.Builder m a+chunkIt = checkDone $ continue . step+  where+    step k EOF = k (Chunks [chunkedTransferTerminator]) >>== return+    step k (Chunks []) = continue $ step k+    step k (Chunks xs) = k (Chunks [chunkedTransferEncoding $ mconcat xs])+                         >>== chunkIt+++takeLBS :: MonadIO m => Int -> Enumeratee S.ByteString S.ByteString m a+takeLBS 0 step = return step+takeLBS len (Continue k) = do+    mbs <- EL.head+    case mbs of+        Nothing -> return $ Continue k+        Just bs -> do+            let (len', chunk, rest) =+                    if S.length bs > len+                        then (0, S.take len bs,+                                if S.length bs == len+                                    then Chunks []+                                    else Chunks [S.drop len bs])+                        else (len - S.length bs, bs, Chunks [])+            step' <- lift $ runIteratee $ k $ Chunks [chunk]+            if len' == 0+                then yield step' rest+                else takeLBS len' step'+takeLBS _ step = return step++encodeUrlCharPI :: Bool -> Char -> String+encodeUrlCharPI _ '/' = "/"+encodeUrlCharPI False '?' = "?"+encodeUrlCharPI False '&' = "&"+encodeUrlCharPI False '=' = "="+encodeUrlCharPI _ c = encodeUrlChar c++encodeUrlChar :: Char -> String+encodeUrlChar c+    -- List of unreserved characters per RFC 3986+    -- Gleaned from http://en.wikipedia.org/wiki/Percent-encoding+    | 'A' <= c && c <= 'Z' = [c]+    | 'a' <= c && c <= 'z' = [c]+    | '0' <= c && c <= '9' = [c]+encodeUrlChar c@'-' = [c]+encodeUrlChar c@'_' = [c]+encodeUrlChar c@'.' = [c]+encodeUrlChar c@'~' = [c]+encodeUrlChar ' ' = "+"+encodeUrlChar y =+    let (a, c) = fromEnum y `divMod` 16+        b = a `mod` 16+        showHex' x+            | x < 10 = toEnum $ x + (fromEnum '0')+            | x < 16 = toEnum $ x - 10 + (fromEnum 'A')+            | otherwise = error $ "Invalid argument to showHex: " ++ show x+     in ['%', showHex' b, showHex' c]++-- | Convert a URL into a 'Request'.+--+-- This defaults some of the values in 'Request', such as setting 'method' to+-- GET and 'requestHeaders' to @[]@.+--+-- Since this function uses 'Failure', the return monad can be anything that is+-- an instance of 'Failure', such as 'IO' or 'Maybe'.+parseUrl :: Failure HttpException m => String -> m (Request m')+parseUrl = parseUrlHelper True++-- | Same as 'parseUrl', with one distinction: this function will not attempt+-- to parse the query string, but instead leave it with the path info. This can+-- be useful if you need precise control of the rendering of the query string,+-- such as using semicolons instead of ampersands.+semiParseUrl :: Failure HttpException m => String -> m (Request m')+semiParseUrl = parseUrlHelper False++parseUrlHelper :: Failure HttpException m => Bool -> String -> m (Request m')+parseUrlHelper parsePath s@('h':'t':'t':'p':':':'/':'/':rest) = parseUrl1 s False parsePath rest+parseUrlHelper parsePath s@('h':'t':'t':'p':'s':':':'/':'/':rest) = parseUrl1 s True parsePath rest+parseUrlHelper _ x = failure $ InvalidUrlException x "Invalid scheme"++parseUrl1 :: Failure HttpException m+          => String -> Bool -> Bool -> String -> m (Request m')+parseUrl1 full sec parsePath s =+    parseUrl2 full sec parsePath s'+  where+    s' = encodeString s++defaultCheckCerts :: W.Ascii -> [X509] -> IO TLS.TLSCertificateUsage+defaultCheckCerts host' certs =+    case certificateVerifyDomain (S8.unpack host') certs of+        TLS.CertificateUsageAccept -> certificateVerifyChain certs+        _                          -> return TLS.CertificateUsageAccept++instance Default (Request m) where+    def = Request+        { host = "localhost"+        , port = 80+        , secure = False+        , checkCerts = defaultCheckCerts+        , requestHeaders = []+        , path = "/"+        , queryString = []+        , requestBody = RequestBodyLBS L.empty+        , method = "GET"+        , proxy = Nothing+        , rawBody = False+        , decompress = alwaysDecompress+        }++parseUrl2 :: Failure HttpException m+          => String -> Bool -> Bool -> String -> m (Request m')+parseUrl2 full sec parsePath s = do+    port' <- mport+    return def+        { host = S8.pack hostname+        , port = port'+        , secure = sec+        , path = S8.pack+                    $ if null path''+                            then "/"+                            else concatMap (encodeUrlCharPI parsePath) path''+        , queryString = if parsePath+                            then W.parseQuery $ S8.pack qstring+                            else []+        }+  where+    (beforeSlash, afterSlash) = break (== '/') s+    (hostname, portStr) = break (== ':') beforeSlash+    (path', qstring') = break (== '?') afterSlash+    path'' = if parsePath then path' else afterSlash+    qstring'' = case qstring' of+                '?':x -> x+                _ -> qstring'+    qstring = takeWhile (/= '#') qstring''+    mport =+        case (portStr, sec) of+            ("", False) -> return 80+            ("", True) -> return 443+            (':':rest, _) ->+                case readMay rest of+                    Just i -> return i+                    Nothing -> failure $ InvalidUrlException full "Invalid port"+            x -> error $ "parseUrl1: this should never happen: " ++ show x++-- | Convert the HTTP response into a 'Response' value.+--+-- Even though a 'Response' contains a lazy bytestring, this function does+-- /not/ utilize lazy I/O, and therefore the entire response body will live in+-- memory. If you want constant memory usage, you'll need to write your own+-- iteratee and use 'http' or 'httpRedirect' directly.+lbsIter :: Monad m => W.Status -> W.ResponseHeaders+        -> Iteratee S.ByteString m Response+lbsIter (W.Status sc _) hs = do+    lbs <- fmap L.fromChunks EL.consume+    return $ Response sc hs lbs++-- | Download the specified 'Request', returning the results as a 'Response'.+--+-- This is a simplified version of 'http' for the common case where you simply+-- want the response data as a simple datatype. If you want more power, such as+-- interleaved actions on the response body during download, you'll need to use+-- 'http' directly. This function is defined as:+--+-- @httpLbs = http lbsIter@+--+-- Please see 'lbsIter' for more information on how the 'Response' value is+-- created.+--+-- Even though a 'Response' contains a lazy bytestring, this function does+-- /not/ utilize lazy I/O, and therefore the entire response body will live in+-- memory. If you want constant memory usage, you'll need to write your own+-- iteratee and use 'http' or 'httpRedirect' directly.+httpLbs :: MonadIO m => Request m -> Manager -> m Response+httpLbs req = run_ . http req lbsIter++-- | Download the specified URL, following any redirects, and return the+-- response body.+--+-- This function will 'failure' an 'HttpException' for any response with a+-- non-2xx status code. It uses 'parseUrl' to parse the input. This function+-- essentially wraps 'httpLbsRedirect'.+--+-- Note: Even though this function returns a lazy bytestring, it does /not/+-- utilize lazy I/O, and therefore the entire response body will live in+-- memory. If you want constant memory usage, you'll need to write your own+-- iteratee and use 'http' or 'httpRedirect' directly.+simpleHttp :: (MonadIO m, Failure HttpException m) => String -> m L.ByteString+simpleHttp url = do+    url' <- parseUrl url+    Response sc _ b <- liftIO $ withManager $ httpLbsRedirect+                                            $ url' { decompress = browserDecompress }+    if 200 <= sc && sc < 300+        then return b+        else failure $ StatusCodeException sc b++data HttpException = StatusCodeException Int L.ByteString+                   | InvalidUrlException String String+                   | TooManyRedirects+                   | HttpParserException String+    deriving (Show, Typeable)+instance Exception HttpException++-- | Same as 'http', but follows all 3xx redirect status codes that contain a+-- location header.+httpRedirect+    :: (MonadIO m, Failure HttpException m)+    => Request m+    -> (W.Status -> W.ResponseHeaders -> Iteratee S.ByteString m a)+    -> Manager+    -> Iteratee S.ByteString m a+httpRedirect req bodyStep manager =+    http req (redirectIter 10 req bodyStep manager) manager++-- | Make a request automatically follow 3xx redirects.+--+-- Used internally by 'httpRedirect' and family.+redirectIter :: (MonadIO m, Failure HttpException m)+             => Int -- ^ number of redirects to attempt+             -> Request m -- ^ Original request+             -> (W.Status -> W.ResponseHeaders -> Iteratee S.ByteString m a)+             -> Manager+             -> (W.Status -> W.ResponseHeaders -> Iteratee S.ByteString m a)+redirectIter redirects req bodyStep manager s@(W.Status code _) hs+    | 300 <= code && code < 400 =+        case lookup "location" hs of+            Just l'' -> do+                -- Prepend scheme, host and port if missing+                let l' =+                        case S8.uncons l'' of+                            Just ('/', _) -> concat+                                [ "http"+                                , if secure req then "s" else ""+                                , "://"+                                , S8.unpack $ host req+                                , ":"+                                , show $ port req+                                , S8.unpack l''+                                ]+                            _ -> S8.unpack l''+                l <- lift $ parseUrl l'+                let req' = req+                        { host = host l+                        , port = port l+                        , secure = secure l+                        , path = path l+                        , queryString = queryString l+                        , method =+                            if code == 303+                                then "GET"+                                else method l+                        }+                if redirects == 0+                    then lift $ failure TooManyRedirects+                    else (http req') (redirectIter (redirects - 1) req' bodyStep manager) manager+            Nothing -> bodyStep s hs+    | otherwise = bodyStep s hs++-- | Download the specified 'Request', returning the results as a 'Response'+-- and automatically handling redirects.+--+-- This is a simplified version of 'httpRedirect' for the common case where you+-- simply want the response data as a simple datatype. If you want more power,+-- such as interleaved actions on the response body during download, you'll+-- need to use 'httpRedirect' directly. This function is defined as:+--+-- @httpLbsRedirect = httpRedirect lbsIter@+--+-- Please see 'lbsIter' for more information on how the 'Response' value is+-- created.+--+-- Even though a 'Response' contains a lazy bytestring, this function does+-- /not/ utilize lazy I/O, and therefore the entire response body will live in+-- memory. If you want constant memory usage, you'll need to write your own+-- iteratee and use 'http' or 'httpRedirect' directly.+httpLbsRedirect :: (MonadIO m, Failure HttpException m) => Request m -> Manager -> m Response+httpLbsRedirect req = run_ . httpRedirect req lbsIter++readMay :: Read a => String -> Maybe a+readMay s = case reads s of+                [] -> Nothing+                (x, _):_ -> Just x++-- FIXME add a helper for generating POST bodies++-- | Add url-encoded paramters to the 'Request'.+--+-- This sets a new 'requestBody', adds a content-type request header and+-- changes the 'method' to POST.+urlEncodedBody :: Monad m => [(S.ByteString, S.ByteString)] -> Request m' -> Request m+urlEncodedBody headers req = req+    { requestBody = RequestBodyLBS body+    , method = "POST"+    , requestHeaders =+        (ct, "application/x-www-form-urlencoded")+      : filter (\(x, _) -> x /= ct) (requestHeaders req)+    }+  where+    ct = "Content-Type"+    body = L.fromChunks . return $ W.renderSimpleQuery False headers++catchParser :: Monad m => String -> Iteratee a m b -> Iteratee a m b+catchParser s i = catchError i (const $ throwError $ HttpParserException s)++-- | Keeps track of open connections for keep-alive.+newtype Manager = Manager+    { mConns :: I.IORef (Map ConnKey TLS.ConnInfo)+    }++-- | ConnKey consists of a hostname, a port and a Bool specifying whether to+--   use keepalive.+type ConnKey = (String, Int, Bool)++takeInsecureSocket :: Manager -> ConnKey -> IO (Maybe TLS.ConnInfo)+takeInsecureSocket man key =+    I.atomicModifyIORef (mConns man) go+  where+    go m = (Map.delete key m, Map.lookup key m)++putInsecureSocket :: Manager -> ConnKey -> TLS.ConnInfo -> IO ()+putInsecureSocket man key ci = do+    msock <- I.atomicModifyIORef (mConns man) go+    maybe (return ()) TLS.connClose msock+  where+    go m = (Map.insert key ci m, Map.lookup key m)++-- | Create a new 'Manager' with no open connection.+newManager :: IO Manager+newManager = Manager <$> I.newIORef Map.empty++-- | Close all connections in a 'Manager'. Afterwards, the 'Manager' can be+-- reused if desired.+closeManager :: Manager -> IO ()+closeManager (Manager i) = do+    m <- I.atomicModifyIORef i $ \x -> (Map.empty, x)+    mapM_ TLS.connClose $ Map.elems m++-- | Create a new 'Manager', call the supplied function and then close it.+withManager :: MonadControlIO m => (Manager -> m a) -> m a+withManager = liftIOOp $ bracket newManager closeManager
Network/HTTP/Enumerator/HttpParser.hs view
@@ -1,148 +1,148 @@-{-# LANGUAGE OverloadedStrings #-}
-module Network.HTTP.Enumerator.HttpParser
-    ( iterHeaders
-    , iterChunkHeader
-    , iterNewline
-    , parserHeadersFromByteString
-    ) where
-
-import Prelude hiding (take, takeWhile)
-import Data.Attoparsec
-import Data.Attoparsec.Enumerator
-import Data.Enumerator (Iteratee)
-import qualified Data.ByteString as S
-import qualified Data.ByteString.Char8 as S8
-import Control.Applicative
-import Data.Word (Word8)
-
-type Header = (S.ByteString, S.ByteString)
-
-parseHeader :: Parser Header
-parseHeader = do
-    k <- takeWhile1 notNewlineColon
-    _ <- word8 58 -- colon
-    skipWhile isSpace
-    v <- takeWhile notNewline
-    newline
-    return (k, v)
-
-notNewlineColon, isSpace, notNewline :: Word8 -> Bool
-
-notNewlineColon 10 = False -- LF
-notNewlineColon 13 = False -- CR
-notNewlineColon 58 = False -- colon
-notNewlineColon _  = True
-
-isSpace 32 = True
-isSpace _  = False
-
-notNewline 10 = False
-notNewline 13 = False
-notNewline _  = True
-
-newline :: Parser ()
-newline =
-    lf <|> (cr >> lf)
-  where
-    word8' x = word8 x >> return ()
-    lf = word8' 10
-    cr = word8' 13
-
-parseHeaders :: Parser (Status, [Header])
-parseHeaders = do
-    s <- parseStatus <?> "HTTP status line"
-    h <- manyTill parseHeader newline <?> "Response headers"
-    return (s, h)
-
-iterHeaders :: Monad m => Iteratee S.ByteString m (Status, [Header])
-iterHeaders = iterParser parseHeaders
-
-
-parserHeadersFromByteString :: Monad m => S.ByteString -> m (Either String (Status, [Header]))
-parserHeadersFromByteString s = return $ parseOnly parseHeaders s
-
-
-type Status = (S.ByteString, Int, S.ByteString)
-
-parseStatus :: Parser Status
-parseStatus = do
-    _ <- string "HTTP/"
-    ver <- takeWhile1 $ not . isSpace
-    _ <- word8 32 -- space
-    statCode <- takeWhile1 $ not . isSpace
-    statCode' <-
-        case reads $ S8.unpack statCode of
-            [] -> fail $ "Invalid status code: " ++ S8.unpack statCode
-            (x, _):_ -> return x
-    _ <- word8 32
-    statMsg <- takeWhile1 $ notNewline
-    newline
-    if (statCode == "100")
-        then newline >> parseStatus
-        else return (ver, statCode', statMsg)
-
-parseChunkHeader :: Parser Int
-parseChunkHeader = do
-    len <- hexs
-    skipWhile isSpace
-    newline <|> attribs
-    return len
-
-iterChunkHeader :: Monad m => Iteratee S.ByteString m Int
-iterChunkHeader =
-    iterParser (parseChunkHeader <?> "Chunked transfer encoding header")
-
-iterNewline :: Monad m => Iteratee S.ByteString m ()
-iterNewline = iterParser newline
-
-attribs :: Parser ()
-attribs = do
-    _ <- word8 59 -- colon
-    skipWhile notNewline
-    newline
-
-hexs :: Parser Int
-hexs = do
-    ws <- many1 hex
-    return $ foldl1 (\a b -> a * 16 + b) $ map fromIntegral ws
-
-hex :: Parser Word8
-hex =
-    (digit <|> upper <|> lower) <?> "Hexadecimal digit"
-  where
-    digit = do
-        d <- satisfy $ \w -> (w >= 48 && w <= 57)
-        return $ d - 48
-    upper = do
-        d <- satisfy $ \w -> (w >= 65 && w <= 70)
-        return $ d - 55
-    lower = do
-        d <- satisfy $ \w -> (w >= 97 && w <= 102)
-        return $ d - 87
-
-{-
-iterParserTill :: Monad m
-               => Parser a
-               -> Parser end
-               -> E.Enumeratee a S.ByteString m b
-iterParserTill p pend =
-    E.continue $ step $ parse p
-  where
-    step parse (E.Chunks xs) = parseLoop parse xs
-    step parse E.EOF = case parse S.empty of
-        Done extra a -> E.yield a $ if S.null extra
-            then E.Chunks []
-            else E.Chunks [extra]
-        Partial _ -> err [] "iterParser: divergent parser"
-        Fail _ ctx msg -> err ctx msg
-
-    parseLoop parse [] = E.continue (step parse)
-    parseLoop parse (x:xs) = case parse x of
-        Done extra a -> E.yield a $ if S.null extra
-            then E.Chunks xs
-            else E.Chunks (extra:xs)
-        Partial parse' -> parseLoop parse' xs
-        Fail _ ctx msg -> err ctx msg
-
-    err ctx msg = E.throwError (ParseError ctx msg)
--}
+{-# LANGUAGE OverloadedStrings #-}+module Network.HTTP.Enumerator.HttpParser+    ( iterHeaders+    , iterChunkHeader+    , iterNewline+    , parserHeadersFromByteString+    ) where++import Prelude hiding (take, takeWhile)+import Data.Attoparsec+import Data.Attoparsec.Enumerator+import Data.Enumerator (Iteratee)+import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8+import Control.Applicative+import Data.Word (Word8)++type Header = (S.ByteString, S.ByteString)++parseHeader :: Parser Header+parseHeader = do+    k <- takeWhile1 notNewlineColon+    _ <- word8 58 -- colon+    skipWhile isSpace+    v <- takeWhile notNewline+    newline+    return (k, v)++notNewlineColon, isSpace, notNewline :: Word8 -> Bool++notNewlineColon 10 = False -- LF+notNewlineColon 13 = False -- CR+notNewlineColon 58 = False -- colon+notNewlineColon _  = True++isSpace 32 = True+isSpace _  = False++notNewline 10 = False+notNewline 13 = False+notNewline _  = True++newline :: Parser ()+newline =+    lf <|> (cr >> lf)+  where+    word8' x = word8 x >> return ()+    lf = word8' 10+    cr = word8' 13++parseHeaders :: Parser (Status, [Header])+parseHeaders = do+    s <- parseStatus <?> "HTTP status line"+    h <- manyTill parseHeader newline <?> "Response headers"+    return (s, h)++iterHeaders :: Monad m => Iteratee S.ByteString m (Status, [Header])+iterHeaders = iterParser parseHeaders+++parserHeadersFromByteString :: Monad m => S.ByteString -> m (Either String (Status, [Header]))+parserHeadersFromByteString s = return $ parseOnly parseHeaders s+++type Status = (S.ByteString, Int, S.ByteString)++parseStatus :: Parser Status+parseStatus = do+    _ <- string "HTTP/"+    ver <- takeWhile1 $ not . isSpace+    _ <- word8 32 -- space+    statCode <- takeWhile1 $ not . isSpace+    statCode' <-+        case reads $ S8.unpack statCode of+            [] -> fail $ "Invalid status code: " ++ S8.unpack statCode+            (x, _):_ -> return x+    _ <- word8 32+    statMsg <- takeWhile1 $ notNewline+    newline+    if (statCode == "100")+        then newline >> parseStatus+        else return (ver, statCode', statMsg)++parseChunkHeader :: Parser Int+parseChunkHeader = do+    len <- hexs+    skipWhile isSpace+    newline <|> attribs+    return len++iterChunkHeader :: Monad m => Iteratee S.ByteString m Int+iterChunkHeader =+    iterParser (parseChunkHeader <?> "Chunked transfer encoding header")++iterNewline :: Monad m => Iteratee S.ByteString m ()+iterNewline = iterParser newline++attribs :: Parser ()+attribs = do+    _ <- word8 59 -- colon+    skipWhile notNewline+    newline++hexs :: Parser Int+hexs = do+    ws <- many1 hex+    return $ foldl1 (\a b -> a * 16 + b) $ map fromIntegral ws++hex :: Parser Word8+hex =+    (digit <|> upper <|> lower) <?> "Hexadecimal digit"+  where+    digit = do+        d <- satisfy $ \w -> (w >= 48 && w <= 57)+        return $ d - 48+    upper = do+        d <- satisfy $ \w -> (w >= 65 && w <= 70)+        return $ d - 55+    lower = do+        d <- satisfy $ \w -> (w >= 97 && w <= 102)+        return $ d - 87++{-+iterParserTill :: Monad m+               => Parser a+               -> Parser end+               -> E.Enumeratee a S.ByteString m b+iterParserTill p pend =+    E.continue $ step $ parse p+  where+    step parse (E.Chunks xs) = parseLoop parse xs+    step parse E.EOF = case parse S.empty of+        Done extra a -> E.yield a $ if S.null extra+            then E.Chunks []+            else E.Chunks [extra]+        Partial _ -> err [] "iterParser: divergent parser"+        Fail _ ctx msg -> err ctx msg++    parseLoop parse [] = E.continue (step parse)+    parseLoop parse (x:xs) = case parse x of+        Done extra a -> E.yield a $ if S.null extra+            then E.Chunks xs+            else E.Chunks (extra:xs)+        Partial parse' -> parseLoop parse' xs+        Fail _ ctx msg -> err ctx msg++    err ctx msg = E.throwError (ParseError ctx msg)+-}
Network/TLS/Client/Enumerator.hs view
@@ -1,86 +1,86 @@-{-# LANGUAGE ScopedTypeVariables #-}
-module Network.TLS.Client.Enumerator
-    ( ConnInfo
-    , connClose
-    , connIter
-    , connEnum
-    , sslClientConn
-    , socketConn
-    , TLSCertificateRejectReason(..)
-    , TLSCertificateUsage(..)
-    ) where
-
-import Data.ByteString (ByteString)
-import qualified Data.ByteString as S
-import qualified Data.ByteString.Lazy as L
-import System.IO (Handle, hClose)
-import Network.Socket (Socket, sClose)
-import Network.Socket.ByteString (recv, sendAll)
-import Network.TLS
-import Control.Monad.IO.Class (MonadIO)
-import Control.Monad.Trans.Class (lift)
-import Data.Enumerator
-    ( Iteratee (..), Enumerator, Step (..), Stream (..), continue, returnI
-    , tryIO
-    )
-import Data.Certificate.X509 (X509)
-import Network.TLS.Extra (ciphersuite_all)
-import Crypto.Random.AESCtr (makeSystem)
-
-data ConnInfo = ConnInfo
-    { connRead :: IO [ByteString]
-    , connWrite :: [ByteString] -> IO ()
-    , connClose :: IO ()
-    }
-
-connIter :: MonadIO m => ConnInfo -> Iteratee ByteString m ()
-connIter ConnInfo { connWrite = write } =
-    continue go
-  where
-    go EOF = return ()
-    go (Chunks bss) = do
-        tryIO $ write bss
-        continue go
-
-connEnum :: MonadIO m => ConnInfo -> Enumerator ByteString m b
-connEnum ConnInfo { connRead = read' } =
-    go
-  where
-    go (Continue k) = do
-        bs <- tryIO read'
-        if all S.null bs
-            then continue k
-            else do
-                step <- lift $ runIteratee $ k $ Chunks bs
-                go step
-    go step = returnI step
-
-socketConn :: Socket -> ConnInfo
-socketConn sock = ConnInfo
-    { connRead = fmap return $ recv sock 4096
-    , connWrite = mapM_ (sendAll sock)
-    , connClose = sClose sock
-    }
-
-sslClientConn :: ([X509] -> IO TLSCertificateUsage) -> Handle -> IO ConnInfo
-sslClientConn onCerts h = do
-    let tcp = defaultParams
-            { pConnectVersion = TLS10
-            , pAllowedVersions = [ TLS10, TLS11 ]
-            , pCiphers = ciphersuite_all
-            , onCertificatesRecv = onCerts
-            }
-    gen <- makeSystem
-    istate <- client tcp gen h
-    _ <- handshake istate
-    return ConnInfo
-        { connRead = recvD istate
-        , connWrite = sendData istate . L.fromChunks
-        , connClose = bye istate >> hClose h
-        }
-  where
-    recvD istate = do
-        x <- recvData istate
-        if L.null x
-            then recvD istate
-            else return $ L.toChunks x
+{-# LANGUAGE ScopedTypeVariables #-}+module Network.TLS.Client.Enumerator+    ( ConnInfo+    , connClose+    , connIter+    , connEnum+    , sslClientConn+    , socketConn+    , TLSCertificateRejectReason(..)+    , TLSCertificateUsage(..)+    ) where++import Data.ByteString (ByteString)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import System.IO (Handle, hClose)+import Network.Socket (Socket, sClose)+import Network.Socket.ByteString (recv, sendAll)+import Network.TLS+import Control.Monad.IO.Class (MonadIO)+import Control.Monad.Trans.Class (lift)+import Data.Enumerator+    ( Iteratee (..), Enumerator, Step (..), Stream (..), continue, returnI+    , tryIO+    )+import Data.Certificate.X509 (X509)+import Network.TLS.Extra (ciphersuite_all)+import Crypto.Random.AESCtr (makeSystem)++data ConnInfo = ConnInfo+    { connRead :: IO [ByteString]+    , connWrite :: [ByteString] -> IO ()+    , connClose :: IO ()+    }++connIter :: MonadIO m => ConnInfo -> Iteratee ByteString m ()+connIter ConnInfo { connWrite = write } =+    continue go+  where+    go EOF = return ()+    go (Chunks bss) = do+        tryIO $ write bss+        continue go++connEnum :: MonadIO m => ConnInfo -> Enumerator ByteString m b+connEnum ConnInfo { connRead = read' } =+    go+  where+    go (Continue k) = do+        bs <- tryIO read'+        if all S.null bs+            then continue k+            else do+                step <- lift $ runIteratee $ k $ Chunks bs+                go step+    go step = returnI step++socketConn :: Socket -> ConnInfo+socketConn sock = ConnInfo+    { connRead = fmap return $ recv sock 4096+    , connWrite = mapM_ (sendAll sock)+    , connClose = sClose sock+    }++sslClientConn :: ([X509] -> IO TLSCertificateUsage) -> Handle -> IO ConnInfo+sslClientConn onCerts h = do+    let tcp = defaultParams+            { pConnectVersion = TLS10+            , pAllowedVersions = [ TLS10, TLS11 ]+            , pCiphers = ciphersuite_all+            , onCertificatesRecv = onCerts+            }+    gen <- makeSystem+    istate <- client tcp gen h+    _ <- handshake istate+    return ConnInfo+        { connRead = recvD istate+        , connWrite = sendData istate . L.fromChunks+        , connClose = bye istate >> hClose h+        }+  where+    recvD istate = do+        x <- recvData istate+        if L.null x+            then recvD istate+            else return $ L.toChunks x
Setup.lhs view
@@ -1,8 +1,8 @@-#!/usr/bin/env runhaskell
-
-> module Main where
-> import Distribution.Simple
-> import System.Cmd (system)
-
-> main :: IO ()
-> main = defaultMain
+#!/usr/bin/env runhaskell++> module Main where+> import Distribution.Simple+> import System.Cmd (system)++> main :: IO ()+> main = defaultMain
http-enumerator.cabal view
@@ -1,64 +1,64 @@-name:            http-enumerator
-version:         0.7.1.4
-license:         BSD3
-license-file:    LICENSE
-author:          Michael Snoyman <michael@snoyman.com>
-maintainer:      Michael Snoyman <michael@snoyman.com>
-synopsis:        HTTP client package with enumerator interface and HTTPS support.
-description:
-    This package uses attoparsec for parsing the actual contents of the HTTP connection. It also provides higher-level functions which allow you to avoid direct usage of enumerators.
-category:        Web, Enumerator
-stability:       Stable
-cabal-version:   >= 1.6
-build-type:      Simple
-homepage:        http://github.com/snoyberg/http-enumerator
-
-flag test
-  description: Build the test executable.
-  default: False
-flag network-bytestring
-  default: False
-
-library
-    build-depends: base                  >= 4       && < 5
-                 , bytestring            >= 0.9.1.4 && < 0.10
-                 , transformers          >= 0.2     && < 0.3
-                 , failure               >= 0.1     && < 0.2
-                 , enumerator            >= 0.4.9   && < 0.5
-                 , attoparsec            >= 0.8.0.2 && < 0.10
-                 , attoparsec-enumerator >= 0.2.0.4 && < 0.3
-                 , utf8-string           >= 0.3.4   && < 0.4
-                 , blaze-builder         >= 0.2.1   && < 0.4
-                 , zlib-enum             >= 0.2     && < 0.3
-                 , http-types            >= 0.6     && < 0.7
-                 , blaze-builder-enumerator >= 0.2  && < 0.3
-                 , cprng-aes             >= 0.2     && < 0.3
-                 , tls                   >= 0.8.1   && < 0.9
-                 , tls-extra             >= 0.3     && < 0.5
-                 , monad-control         >= 0.2     && < 0.3
-                 , containers            >= 0.2     && < 0.5
-                 , certificate           >= 0.7     && < 1.1
-                 , case-insensitive      >= 0.2     && < 0.4
-                 , base64-bytestring     >= 0.1     && < 0.2
-                 , asn1-data             >= 0.5.1   && < 0.7
-                 , data-default          >= 0.3     && < 0.4
-    if flag(network-bytestring)
-        build-depends: network               >= 2.2.1   && < 2.2.3
-                     , network-bytestring    >= 0.1.3   && < 0.1.4
-    else
-        build-depends: network               >= 2.3     && < 2.4
-    exposed-modules: Network.HTTP.Enumerator
-    other-modules:   Network.HTTP.Enumerator.HttpParser
-                     Network.TLS.Client.Enumerator
-    ghc-options:     -Wall
-
-executable http-enumerator
-    main-is: test.hs
-    if flag(test)
-        Buildable: True
-    else
-        Buildable: False
-
-source-repository head
-  type:     git
-  location: git://github.com/snoyberg/http-enumerator.git
+name:            http-enumerator+version:         0.7.1.5+license:         BSD3+license-file:    LICENSE+author:          Michael Snoyman <michael@snoyman.com>+maintainer:      Michael Snoyman <michael@snoyman.com>+synopsis:        HTTP client package with enumerator interface and HTTPS support.+description:+    This package uses attoparsec for parsing the actual contents of the HTTP connection. It also provides higher-level functions which allow you to avoid direct usage of enumerators.+category:        Web, Enumerator+stability:       Stable+cabal-version:   >= 1.6+build-type:      Simple+homepage:        http://github.com/snoyberg/http-enumerator++flag test+  description: Build the test executable.+  default: False+flag network-bytestring+  default: False++library+    build-depends: base                  >= 4       && < 5+                 , bytestring            >= 0.9.1.4 && < 0.10+                 , transformers          >= 0.2     && < 0.3+                 , failure               >= 0.1     && < 0.2+                 , enumerator            >= 0.4.9   && < 0.5+                 , attoparsec            >= 0.8.0.2 && < 0.11+                 , attoparsec-enumerator >= 0.2.0.4 && < 0.3+                 , utf8-string           >= 0.3.4   && < 0.4+                 , blaze-builder         >= 0.2.1   && < 0.4+                 , zlib-enum             >= 0.2     && < 0.3+                 , http-types            >= 0.6     && < 0.7+                 , blaze-builder-enumerator >= 0.2  && < 0.3+                 , cprng-aes             >= 0.2     && < 0.3+                 , tls                   >= 0.8.1   && < 0.9+                 , tls-extra             >= 0.3     && < 0.5+                 , monad-control         >= 0.2     && < 0.3+                 , containers            >= 0.2+                 , certificate           >= 0.7     && < 1.1+                 , case-insensitive      >= 0.2+                 , base64-bytestring     >= 0.1     && < 0.2+                 , asn1-data             >= 0.5.1   && < 0.7+                 , data-default          >= 0.3     && < 0.4+    if flag(network-bytestring)+        build-depends: network               >= 2.2.1   && < 2.2.3+                     , network-bytestring    >= 0.1.3   && < 0.1.4+    else+        build-depends: network               >= 2.3     && < 2.4+    exposed-modules: Network.HTTP.Enumerator+    other-modules:   Network.HTTP.Enumerator.HttpParser+                     Network.TLS.Client.Enumerator+    ghc-options:     -Wall++executable http-enumerator+    main-is: test.hs+    if flag(test)+        Buildable: True+    else+        Buildable: False++source-repository head+  type:     git+  location: git://github.com/snoyberg/http-enumerator.git
test.hs view
@@ -1,33 +1,33 @@-{-# LANGUAGE OverloadedStrings #-}
-{-# LANGUAGE CPP #-}
-import Network.HTTP.Enumerator
-import Network
-import qualified Data.ByteString as S
-import qualified Data.ByteString.Char8 as S8
-import qualified Data.ByteString.Lazy as L
-import System.Environment.UTF8 (getArgs)
-import Data.CaseInsensitive (original)
-
-main :: IO ()
-main = withSocketsDo $ do
-    [url] <- getArgs
-    _req2 <- parseUrl url
-    {-
-    let req = urlEncodedBody
-                [ ("foo", "bar")
-                , ("baz%%38**.8fn", "bin")
-                ] _req2
-    -}
-    Response sc hs b <- withManager $ httpLbsRedirect _req2
-#if DEBUG
-    return ()
-#else
-    print sc
-    mapM_ (\(x, y) -> do
-        S.putStr $ original x
-        putStr ": "
-        S.putStr y
-        putStrLn "") hs
-    putStrLn ""
-    L.putStr b
-#endif
+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE CPP #-}+import Network.HTTP.Enumerator+import Network+import qualified Data.ByteString as S+import qualified Data.ByteString.Char8 as S8+import qualified Data.ByteString.Lazy as L+import System.Environment.UTF8 (getArgs)+import Data.CaseInsensitive (original)++main :: IO ()+main = withSocketsDo $ do+    [url] <- getArgs+    _req2 <- parseUrl url+    {-+    let req = urlEncodedBody+                [ ("foo", "bar")+                , ("baz%%38**.8fn", "bin")+                ] _req2+    -}+    Response sc hs b <- withManager $ httpLbsRedirect _req2+#if DEBUG+    return ()+#else+    print sc+    mapM_ (\(x, y) -> do+        S.putStr $ original x+        putStr ": "+        S.putStr y+        putStrLn "") hs+    putStrLn ""+    L.putStr b+#endif