packages feed

http-conduit 1.8.6.3 → 1.8.7

raw patch · 10 files changed

+415/−29 lines, 10 filesdep +arraydep +filepathdep +mime-typesdep ~http-typesbinary-addedPVP ok

version bump matches the API change (PVP)

Dependencies added: array, filepath, mime-types, random

Dependency ranges changed: http-types

API changes (from Hackage documentation)

+ Network.HTTP.Conduit: ExpectedBlankAfter100Continue :: HttpException
+ Network.HTTP.Conduit: InvalidHeader :: ByteString -> HttpException
+ Network.HTTP.Conduit: InvalidStatusLine :: ByteString -> HttpException
+ Network.HTTP.Conduit.MultipartFormData: Part :: Text -> Maybe String -> Maybe MimeType -> m (RequestBody m') -> Part m m'
+ Network.HTTP.Conduit.MultipartFormData: data Part m m'
+ Network.HTTP.Conduit.MultipartFormData: formDataBody :: (MonadIO m, Monad m') => [Part m m'] -> Request m' -> m (Request m')
+ Network.HTTP.Conduit.MultipartFormData: formDataBodyPure :: Monad m => ByteString -> [Part Identity m] -> Request m -> Request m
+ Network.HTTP.Conduit.MultipartFormData: formDataBodyWithBoundary :: (Monad m, Monad m') => ByteString -> [Part m m'] -> Request m' -> m (Request m')
+ Network.HTTP.Conduit.MultipartFormData: instance Show (Part m m')
+ Network.HTTP.Conduit.MultipartFormData: partBS :: (Monad m, Monad m') => Text -> ByteString -> Part m m'
+ Network.HTTP.Conduit.MultipartFormData: partContentType :: Part m m' -> Maybe MimeType
+ Network.HTTP.Conduit.MultipartFormData: partFile :: (MonadIO m, Monad m') => Text -> FilePath -> Part m m'
+ Network.HTTP.Conduit.MultipartFormData: partFileRequestBody :: (Monad m, Monad m') => Text -> FilePath -> RequestBody m' -> Part m m'
+ Network.HTTP.Conduit.MultipartFormData: partFileRequestBodyM :: Monad m' => Text -> FilePath -> m (RequestBody m') -> Part m m'
+ Network.HTTP.Conduit.MultipartFormData: partFileSource :: (MonadIO m, MonadResource m') => Text -> FilePath -> Part m m'
+ Network.HTTP.Conduit.MultipartFormData: partFileSourceChunked :: (Monad m, MonadResource m') => Text -> FilePath -> Part m m'
+ Network.HTTP.Conduit.MultipartFormData: partFilename :: Part m m' -> Maybe String
+ Network.HTTP.Conduit.MultipartFormData: partGetBody :: Part m m' -> m (RequestBody m')
+ Network.HTTP.Conduit.MultipartFormData: partLBS :: (Monad m, Monad m') => Text -> ByteString -> Part m m'
+ Network.HTTP.Conduit.MultipartFormData: partName :: Part m m' -> Text
+ Network.HTTP.Conduit.MultipartFormData: renderPart :: (Monad m, Monad m') => ByteString -> Part m m' -> m (RequestBody m')
+ Network.HTTP.Conduit.MultipartFormData: renderParts :: (Monad m, Monad m') => ByteString -> [Part m m'] -> m (RequestBody m')
+ Network.HTTP.Conduit.MultipartFormData: webkitBoundary :: IO ByteString
+ Network.HTTP.Conduit.MultipartFormData: webkitBoundaryPure :: RandomGen g => g -> (ByteString, g)

Files

Network/HTTP/Conduit/Manager.hs view
@@ -408,9 +408,9 @@ -- fails. failedConnectionException :: Request m -> HttpException failedConnectionException req =-    FailedConnectionException host port+    FailedConnectionException host' port'   where-    (_, host, port) = getConnDest req+    (_, host', port') = getConnDest req  getConnDest :: Request m -> (Bool, String, Int) getConnDest req =
+ Network/HTTP/Conduit/MultipartFormData.hs view
@@ -0,0 +1,223 @@+{-# LANGUAGE CPP, OverloadedStrings #-}+-- | This module handles building multipart/form-data. Example usage:+--+-- > {-# LANGUAGE OverloadedStrings #-}+-- > import Network+-- > import Network.HTTP.Conduit+-- > import Network.HTTP.Conduit.MultipartFormData+-- >+-- > import Data.Text.Encoding as TE+-- >+-- > import Control.Monad+-- >+-- > main = withSocketsDo $ withManager $ \m -> do+-- >     Response{responseBody=cat} <- flip httpLbs m $ fromJust $ parseUrl "http://random-cat-photo.net/cat.jpg"+-- >     flip httpLbs m =<<+-- >         (formDataBody [partBS "title" "Bleaurgh"+-- >                       ,partBS "text" $ TE.encodeUtf8 "矢田矢田矢田矢田矢田"+-- >                       ,partFileSource "file1" "/home/friedrich/Photos/MyLittlePony.jpg"+-- >                       ,partFileRequestBody "file2" "cat.jpg" $ RequestBodyLBS cat]+-- >             $ fromJust $ parseUrl "http://example.org/~friedrich/blog/addPost.hs")+module Network.HTTP.Conduit.MultipartFormData+    (+    -- * Part type+     Part(..)+    -- * Constructing parts+    ,partBS+    ,partLBS+    ,partFile+    ,partFileSource+    ,partFileSourceChunked+    ,partFileRequestBody+    ,partFileRequestBodyM+    -- * Building form data+    ,formDataBody+    ,formDataBodyPure+    ,formDataBodyWithBoundary+    -- * Boundary+    ,webkitBoundary+    ,webkitBoundaryPure+    -- * Misc+    ,renderParts+    ,renderPart+    ) where++import Network.HTTP.Conduit.Request+import Network.HTTP.Conduit.Util+import Network.Mime+import Network.HTTP.Types (hContentType, methodPost)++import Blaze.ByteString.Builder+import qualified Data.Conduit.List as CL+import qualified Data.Conduit.Binary as CB+import Data.Conduit++import Data.Text+import qualified Data.Text.Encoding as TE+import qualified Data.ByteString.Lazy as BL+import qualified Data.ByteString as BS++import Control.Monad.Trans.State.Strict (state, runState)+import Control.Monad.IO.Class+import System.FilePath+import System.Random+import Data.Array.Base+import System.IO+import Data.Bits+import Data.Word+import Data.Functor.Identity+import Data.Monoid (Monoid(..))+import Control.Monad++{-# INLINE _mmap #-}+-- | Kludge to get rid of 'Functor' constraint+infixl 4 `_mmap`+_mmap :: Monad m => (a -> b) -> m a -> m b+_mmap = \f m -> m >>= return . f++-- | A single part of a multipart message.+data Part m m' = Part+    { partName :: Text -- ^ Name of the corresponding \<input\>+    , partFilename :: Maybe String -- ^ A file name, if this is an attached file+    , partContentType :: Maybe MimeType -- ^ Content type+    , partGetBody :: m (RequestBody m') -- ^ Action in m which returns the body+                                        -- of a message.+    }++instance Show (Part m m') where+    showsPrec d (Part n f c _) =+        showParen (d>=11) $ showString "Part "+            . showsPrec 11 n+            . showString " "+            . showsPrec 11 f+            . showString " "+            . showsPrec 11 c+            . showString " "+            . showString "<m (RequestBody m)>"++partBS :: (Monad m, Monad m') => Text -> BS.ByteString -> Part m m'+partBS n b = Part n mempty mempty $ return $ RequestBodyBS b++partLBS :: (Monad m, Monad m') => Text -> BL.ByteString -> Part m m'+partLBS n b = Part n mempty mempty $ return $ RequestBodyLBS b++-- | Make a 'Part' from a file, the entire file will reside in memory at once.+-- If you want constant memory usage use 'partFileSource'+partFile :: (MonadIO m, Monad m') => Text -> FilePath -> Part m m'+partFile n f =+    partFileRequestBodyM n f $ do+        _mmap RequestBodyBS $ liftIO $ BS.readFile f++-- | Stream 'Part' from a file.+partFileSource :: (MonadIO m, MonadResource m') => Text -> FilePath -> Part m m'+partFileSource n f =+    partFileRequestBodyM n f $ do+        size <- liftIO $ withBinaryFile f ReadMode hFileSize+        return $ RequestBodySource (fromInteger size) $+            CB.sourceFile f $= CL.map fromByteString++-- | 'partFileSourceChunked' will read a file and send it in chunks.+--+-- Note that not all servers support this. Only use 'partFileSourceChunked'+-- if you know the server you're sending to supports chunked request bodies. +partFileSourceChunked :: (Monad m, MonadResource m') => Text -> FilePath -> Part m m'+partFileSourceChunked n f =+    partFileRequestBody n f $ do+        RequestBodySourceChunked $ CB.sourceFile f $= CL.map fromByteString++-- | Construct a 'Part' from form name, filepath and a 'RequestBody'+--+-- > partFileRequestBody "who_calls" "caller.json" $ RequestBodyBS "{\"caller\":\"Jason J Jason\"}"+partFileRequestBody :: (Monad m, Monad m') => Text -> FilePath -> RequestBody m' -> Part m m'+partFileRequestBody n f rqb =+    partFileRequestBodyM n f $ return rqb++-- | Construct a 'Part' from action returning the 'RequestBody'+--+-- > partFileRequestBodyM "cat_photo" "haskell-the-cat.jpg" $ do+-- >     size <- fromInteger <$> withBinaryFile "haskell-the-cat.jpg" ReadMode hFileSize+-- >     return $ RequestBodySource size $ CB.sourceFile "haskell-the-cat.jpg" $= CL.map fromByteString+partFileRequestBodyM :: Monad m' => Text -> FilePath -> m (RequestBody m') -> Part m m'+partFileRequestBodyM n f rqb =+    Part n (Just f) (Just $ defaultMimeLookup $ pack f) rqb++{-# INLINABLE cp #-}+cp :: BS.ByteString -> RequestBody m+cp bs = RequestBodyBuilder (fromIntegral $ BS.length bs) $ copyByteString bs++renderPart :: (Monad m, Monad m') => BS.ByteString -> Part m m' -> m (RequestBody m')+renderPart boundary (Part name mfilename mcontenttype get) = _mmap render get+  where render renderBody =+            cp "--" <> cp boundary <> cp "\r\n"+         <> cp "Content-Disposition: form-data; name=\""+         <> RequestBodyBS (TE.encodeUtf8 name)+         <> (case mfilename of+                 Just f -> cp "\"; filename=\""+                        <> RequestBodyBS (TE.encodeUtf8 $ pack $ takeFileName f)+                 _ -> mempty)+         <> cp "\""+         <> (case mcontenttype of+                Just ct -> cp "\r\n"+                        <> cp "Content-Type: "+                        <> cp ct+                _ -> mempty)+         <> cp "\r\n\r\n"+         <> renderBody <> cp "\r\n"++-- | Combine the 'Part's to form multipart/form-data body+renderParts :: (Monad m, Monad m') => BS.ByteString -> [Part m m'] -> m (RequestBody m')+renderParts boundary parts = fin . mconcat `_mmap` mapM (renderPart boundary) parts+  where fin = (<> cp "--" <> cp boundary <> cp "--\r\n")++-- | Generate a boundary simillar to those generated by WebKit-based browsers.+webkitBoundary :: IO BS.ByteString+webkitBoundary = getStdRandom webkitBoundaryPure++webkitBoundaryPure :: RandomGen g => g -> (BS.ByteString, g)+webkitBoundaryPure g = (`runState` g) $ do+    fmap (BS.append prefix . BS.pack . Prelude.concat) $ replicateM 4 $ do+        randomness <- state $ random+        return [unsafeAt alphaNumericEncodingMap $ randomness `shiftR` 24 .&. 0x3F+               ,unsafeAt alphaNumericEncodingMap $ randomness `shiftR` 16 .&. 0x3F+               ,unsafeAt alphaNumericEncodingMap $ randomness `shiftR` 8 .&. 0x3F+               ,unsafeAt alphaNumericEncodingMap $ randomness .&. 0x3F]+  where+    prefix = "----WebKitFormBoundary"+    alphaNumericEncodingMap :: UArray Int Word8+    alphaNumericEncodingMap = listArray (0, 63)+        [0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,+         0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,+         0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,+         0x59, 0x5A, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66,+         0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E,+         0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76,+         0x77, 0x78, 0x79, 0x7A, 0x30, 0x31, 0x32, 0x33,+         0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, 0x42]++-- | Add form data to the 'Request'.+--+-- This sets a new 'requestBody', adds a content-type request header and changes the method to POST.+formDataBody :: (MonadIO m, Monad m') => [Part m m'] -> Request m' -> m (Request m')+formDataBody a b = do+    boundary <- liftIO webkitBoundary+    formDataBodyWithBoundary boundary a b++{-# INLINE formDataBodyPure #-}+-- | Add form data to request without doing any IO. Your form data should only+-- contain pure parts ('partBS', 'partLBS', 'partFileRequestBody'). You'll have+-- to supply your own boundary (for example one generated by 'webkitBoundary')+formDataBodyPure :: Monad m => BS.ByteString -> [Part Identity m] -> Request m -> Request m+formDataBodyPure = \boundary parts req ->+    runIdentity $ formDataBodyWithBoundary boundary parts req++-- | Add form data with supplied boundary+formDataBodyWithBoundary :: (Monad m, Monad m') => BS.ByteString -> [Part m m'] -> Request m' -> m (Request m')+formDataBodyWithBoundary boundary parts req = do+    body <- renderParts boundary parts+    return $ req+        { method = methodPost+        , requestHeaders =+            (hContentType, "multipart/form-data; boundary=" <> boundary)+          : Prelude.filter (\(x, _) -> x /= hContentType) (requestHeaders req)+        , requestBody = body+        }
Network/HTTP/Conduit/Request.hs view
@@ -227,13 +227,11 @@ requestBuilder req =     CL.sourceList [builder] `mappend` bodySource   where-    sourceSingle = CL.sourceList . return-     (contentLength, bodySource) =         case requestBody req of-            RequestBodyLBS lbs -> (Just $ L.length lbs, sourceSingle $ fromLazyByteString lbs)-            RequestBodyBS bs -> (Just $ fromIntegral $ S.length bs, sourceSingle $ fromByteString bs)-            RequestBodyBuilder i b -> (Just $ i, sourceSingle b)+            RequestBodyLBS lbs -> (Just $ L.length lbs, C.yield $ fromLazyByteString lbs)+            RequestBodyBS bs -> (Just $ fromIntegral $ S.length bs, C.yield $ fromByteString bs)+            RequestBodyBuilder i b -> (Just $ i, C.yield b)             RequestBodySource i source -> (Just i, source)             RequestBodySourceChunked source -> (Nothing, source C.$= chunkIt) 
Network/HTTP/Conduit/Response.hs view
@@ -8,18 +8,20 @@     , lbsResponse     ) where +import Control.Applicative ((<$>), (<*>)) import Control.Arrow (first)-import Control.Monad (liftM)+import Control.Monad (liftM, unless, when)  import Control.Exception (throwIO) import Control.Monad.IO.Class (liftIO) +import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as S8 import qualified Data.ByteString.Lazy as L  import qualified Data.CaseInsensitive as CI -import Data.Conduit hiding (Sink, Conduit)+import Data.Conduit hiding (Conduit) import Data.Conduit.Internal (ResumableSource (..), Pipe (..)) import qualified Data.Conduit.Zlib as CZ import qualified Data.Conduit.Binary as CB@@ -33,13 +35,11 @@ import Network.HTTP.Conduit.Manager import Network.HTTP.Conduit.Request import Network.HTTP.Conduit.Util-import Network.HTTP.Conduit.Parser import Network.HTTP.Conduit.Chunk  import Data.Void (Void, absurd)  import System.Timeout.Lifted (timeout)-import Control.Monad.Trans.Control (MonadBaseControl)  -- | If a request is a redirection (status code 3xx) this function will create -- a new request from the old request, the server headers returned with the@@ -99,7 +99,7 @@ checkHeaderLength len (PipeM msink) = PipeM (liftM (checkHeaderLength len) msink) checkHeaderLength _ s@Done{} = s checkHeaderLength _ (HaveOutput _ _ o) = absurd o-checkHeaderLength len (Leftover p i) = Leftover (checkHeaderLength len p) i+checkHeaderLength len (Leftover p i) = Leftover (checkHeaderLength (len + S.length i) p) i  getResponse :: (MonadResource m, MonadBaseControl IO m)             => ConnRelease m@@ -115,14 +115,14 @@                     case x of                         Nothing -> liftIO $ throwIO ResponseTimeout                         Just y -> return y-    (src2, ((vbs, sc, sm), hs)) <- timeout' $ src1 $$+ checkHeaderLength 4096 sinkHeaders+    (src2, ((vbs, sc, sm), hs)) <- timeout' $ src1 $$+ checkHeaderLength 4096 sinkHeaders'     let version = if vbs == "1.1" then W.http11 else W.http10     let s = W.Status sc sm     let hs' = map (first CI.mk) hs     let mcl = lookup "content-length" hs' >>= readDec . S8.unpack      -- should we put this connection back into the connection manager?-    let toPut = Just "close" /= lookup "connection" hs'+    let toPut = Just "close" /= lookup "connection" hs' && vbs /= "1.0"     let cleanup bodyConsumed = connRelease $ if toPut && bodyConsumed then Reuse else DontReuse      -- RFC 2616 section 4.4_1 defines responses that must not include a body@@ -150,3 +150,73 @@   where     fmapResume f (ResumableSource src m) = ResumableSource (f src) m     addCleanup' f (ResumableSource src m) = ResumableSource (addCleanup f src) (m >> f False)++-- | New version of @sinkHeaders@ that doesn't use attoparsec. Should create+-- more meaningful exceptions.+--+-- Since 1.8.7+sinkHeaders' :: (MonadThrow m, MonadResource m) => Sink S.ByteString m (Status, [Header])+sinkHeaders' = do+    status <- getStatusLine+    headers <- parseHeaders id+    return (status, headers)+  where+    getStatusLine = do+        status@(_, code, _) <- sinkLine >>= parseStatus+        if code == 100+            then newline ExpectedBlankAfter100Continue >> getStatusLine+            else return status++    newline exc = do+        line <- sinkLine+        unless (S.null line) $ monadThrow exc++    sinkLine = do+        bs <- fmap (killCR . S.concat) $ CB.takeWhile (/= charLF) =$ CL.consume+        CB.drop 1+        return bs+    charLF = 10+    charCR = 13+    charSpace = 32+    charColon = 58+    killCR bs+        | S.null bs = bs+        | S.last bs == charCR = S.init bs+        | otherwise = bs++    parseStatus :: MonadThrow m => S.ByteString -> m Status+    parseStatus bs = do+        let (ver, bs2) = S.breakByte charSpace bs+            (code, bs3) = S.breakByte charSpace $ S.dropWhile (== charSpace) bs2+            msg = S.dropWhile (== charSpace) bs3+        case (,) <$> parseVersion ver <*> parseCode code of+            Just (ver', code') -> return (ver', code', msg)+            _ -> monadThrow $ InvalidStatusLine bs++    stripPrefixBS x y+        | x `S.isPrefixOf` y = Just $ S.drop (S.length x) y+        | otherwise = Nothing+    parseVersion = stripPrefixBS "HTTP/"+    parseCode bs =+        case S8.readInt bs of+            Just (i, "") -> Just i+            _ -> Nothing++    parseHeaders front = do+        line <- sinkLine+        if S.null line+            then return $ front []+            else do+                header <- parseHeader line+                parseHeaders $ front . (header:)++    parseHeader :: MonadThrow m => S.ByteString -> m Header+    parseHeader bs = do+        let (key, bs2) = S.breakByte charColon bs+        when (S.null bs2) $ monadThrow $ InvalidHeader bs+        return (strip key, strip $ S.drop 1 bs2)++    strip = S.dropWhile (== charSpace) . fst . S.spanEnd (== charSpace)++type Header = (S.ByteString, S.ByteString)+type Status = (S.ByteString, Int, S.ByteString)
Network/HTTP/Conduit/Types.hs view
@@ -16,7 +16,7 @@ import Data.Int (Int64) import Data.Typeable (Typeable) -import qualified Blaze.ByteString.Builder as Blaze+import Blaze.ByteString.Builder  import qualified Data.Conduit as C @@ -31,7 +31,10 @@ import Data.Certificate.X509 (X509) import Network.TLS (PrivateKey) import Network.HTTP.Conduit.ConnInfo (ConnInfo)+import Network.HTTP.Conduit.Util +import Data.Monoid (Monoid(..))+ type ContentType = S.ByteString  -- | All information on how to connect to a host and what should be sent in the@@ -143,9 +146,9 @@ data RequestBody m     = RequestBodyLBS L.ByteString     | RequestBodyBS S.ByteString-    | RequestBodyBuilder Int64 Blaze.Builder-    | RequestBodySource Int64 (C.Source m Blaze.Builder)-    | RequestBodySourceChunked (C.Source m Blaze.Builder)+    | RequestBodyBuilder Int64 Builder+    | RequestBodySource Int64 (C.Source m Builder)+    | RequestBodySourceChunked (C.Source m Builder)  -- | Define a HTTP proxy, consisting of a hostname and port number. @@ -165,6 +168,9 @@                    | OverlongHeaders                    | ResponseTimeout                    | FailedConnectionException String Int -- ^ host/port+                   | ExpectedBlankAfter100Continue+                   | InvalidStatusLine S.ByteString+                   | InvalidHeader S.ByteString     deriving (Show, Typeable) instance Exception HttpException @@ -180,3 +186,46 @@ -- | Since 1.1.2. instance Functor Response where     fmap f (Response status v headers body) = Response status v headers (f body)++-- | Since 1.8.7+instance Show (RequestBody m) where+    showsPrec d (RequestBodyBS a) =+        showParen (d>=11) $ showString "RequestBodyBS " . showsPrec 11 a+    showsPrec d (RequestBodyLBS a) =+        showParen (d>=11) $ showString "RequestBodyLBS " . showsPrec 11 a+    showsPrec d (RequestBodyBuilder l _) =+        showParen (d>=11) $ showString "RequestBodyBuilder " . showsPrec 11 l .+            showString " " . showString "<Builder>"+    showsPrec d (RequestBodySource l _) =+        showParen (d>=11) $ showString "RequestBodySource " . showsPrec 11 l .+            showString " <Source m Builder>"+    showsPrec d (RequestBodySourceChunked _) =+        showParen (d>=11) $ showString "RequestBodySource <Source m Builder>"++-- | Since 1.8.7+instance Monad m => Monoid (RequestBody m) where+    mempty = RequestBodyLBS mempty++    mappend a b =+        case (simplify a, simplify b) of+            (SBuilder l1 b1, SBuilder l2 b2) -> RequestBodyBuilder (l1 + l2) (b1 <> b2)+            (SBuilder l1 b1, SSource l2 s2) -> RequestBodySource (l1 + l2) (C.yield b1 <> s2)+            (SSource l1 s1, SBuilder l2 b2) -> RequestBodySource (l1 + l2) (s1 <> C.yield b2)+            (SSource l1 s1, SSource l2 s2) -> RequestBodySource (l1 + l2) (s1 <> s2)+            (a', b') -> RequestBodySourceChunked (toChunked a' <> toChunked b')++data Simplified m = SBuilder Int64 Builder+                  | SSource Int64 (C.Source m Builder)+                  | SChunked (C.Source m Builder)++simplify :: Monad m => RequestBody m -> Simplified m+simplify (RequestBodyBS a) = SBuilder (fromIntegral $ S.length a) (fromByteString a)+simplify (RequestBodyLBS a) = SBuilder (fromIntegral $ L.length a) (fromLazyByteString a)+simplify (RequestBodyBuilder l a) = SBuilder l a+simplify (RequestBodySource l a) = SSource l a+simplify (RequestBodySourceChunked a) = SChunked a++toChunked :: Monad m => Simplified m -> C.Source m Builder+toChunked (SBuilder _ b) = C.yield b+toChunked (SSource _ s) = s+toChunked (SChunked s) = s
Network/HTTP/Conduit/Util.hs view
@@ -6,11 +6,18 @@     , (<>)     , readDec     , hasNoBody+    , fromStrict     ) where  import Data.Monoid (Monoid, mappend)  import qualified Data.ByteString.Char8 as S8+#if MIN_VERSION_bytestring(0,10,0)+import Data.ByteString.Lazy (fromStrict)+#else+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString as S+#endif  import qualified Data.Text as T import qualified Data.Text.Read@@ -69,3 +76,9 @@ hasNoBody _ 204 = True hasNoBody _ 304 = True hasNoBody _ i = 100 <= i && i < 200++#if !MIN_VERSION_bytestring(0,10,0)+{-# INLINE fromStrict #-}+fromStrict :: S.ByteString -> L.ByteString+fromStrict x = L.fromChunks [x]+#endif
http-conduit.cabal view
@@ -1,5 +1,5 @@ name:            http-conduit-version:         1.8.6.3+version:         1.8.7 license:         BSD3 license-file:    LICENSE author:          Michael Snoyman <michael@snoyman.com>@@ -14,7 +14,10 @@ cabal-version:   >= 1.8 build-type:      Simple homepage:        http://www.yesodweb.com/book/http-conduit-extra-source-files: test/main.hs, test/CookieTest.hs+extra-source-files: test/main.hs+                  , test/CookieTest.hs+                  , multipart-example.bin+                  , nyan.gif  flag network-bytestring   default: False@@ -32,7 +35,8 @@                  , attoparsec            >= 0.8.0.2                  , utf8-string           >= 0.3.4                  , blaze-builder         >= 0.2.1-                 , http-types            >= 0.6+                 , http-types            >= 0.7+                 , mime-types            >= 0.1                  , cprng-aes             >= 0.2                  , tls                   >= 1.0.0                  , tls-extra             >= 0.5.0@@ -53,6 +57,9 @@                  , regex-compat                  , mtl                  , deepseq+                 , array                 >= 0.4+                 , random+                 , filepath     if flag(network-bytestring)         build-depends: network               >= 2.2.1   && < 2.2.3                      , network-bytestring    >= 0.1.3   && < 0.1.4@@ -60,6 +67,7 @@         build-depends: network               >= 2.3     exposed-modules: Network.HTTP.Conduit                      Network.HTTP.Conduit.Internal+                     Network.HTTP.Conduit.MultipartFormData     other-modules:   Network.HTTP.Conduit.Parser                      Network.HTTP.Conduit.ConnInfo                      Network.HTTP.Conduit.Request@@ -118,6 +126,10 @@                  , void                  , deepseq                  , mtl+                 , array+                 , random+                 , filepath+                 , mime-types  source-repository head   type:     git
+ multipart-example.bin view

binary file changed (absent → 4267 bytes)

+ nyan.gif view

binary file changed (absent → 3422 bytes)

test/main.hs view
@@ -9,6 +9,7 @@ import qualified Network.Wai as Wai import Network.Wai.Handler.Warp (runSettings, defaultSettings, settingsPort, settingsBeforeMainLoop) import Network.HTTP.Conduit hiding (port)+import Network.HTTP.Conduit.MultipartFormData import Control.Concurrent (forkIO, killThread, putMVar, takeMVar, newEmptyMVar) import Network.HTTP.Types import Control.Exception.Lifted (try, SomeException, bracket, onException, IOException)@@ -21,7 +22,7 @@ import Data.Conduit.Network (runTCPServer, serverSettings, HostPreference (..), appSink, appSource, bindPort, serverAfterBind, ServerSettings) import qualified Data.Conduit.Network import System.IO.Unsafe (unsafePerformIO)-import Data.Conduit (($$), yield, Flush (Chunk))+import Data.Conduit (($$), yield, Flush (Chunk), runResourceT, await) import Control.Monad (void, forever) import Control.Monad.IO.Class (liftIO) import Data.ByteString.UTF8 (fromString)@@ -32,8 +33,9 @@ import qualified Data.Text as T import qualified Data.Text.Encoding as TE import qualified Data.ByteString.Lazy as L-import Blaze.ByteString.Builder (fromByteString)+import Blaze.ByteString.Builder (fromByteString, toByteString) import System.IO+import Data.Monoid (mconcat)  app :: Application app req =@@ -49,12 +51,6 @@             in return $ responseLBS status303                     [(hLocation, S.append "/infredir/" $ S8.pack $ show $ i+1)]                     (L8.pack $ show i)-        ["infredirrepeat", i'] ->-            let i = read $ T.unpack i' :: Int-            in return $ responseLBS status303-                    [(hLocation, S.append "/infredirrepeat/" $ S8.pack $ show $ i + 1)-                    ,(hContentLength, "2048")]-                    (L8.pack $ take 2048 $ unwords $ repeat $ show i)         _ -> return $ responseLBS status404 [] "not found"      where tastyCookie = (mk (fromString "Set-Cookie"), fromString "flavor=chocolate-chip;")@@ -198,6 +194,31 @@                 liftIO $ do                     Network.HTTP.Conduit.responseStatus res `shouldBe` status200                     responseBody res `shouldBe` "Hello World!"+    describe "multipart/form-data" $ do+        it "formats correctly" $ do+            let bd = "---------------------------190723902820679116301912680260"+            (RequestBodySource _ src) <- renderParts bd+                [partBS "email" ""+                ,partBS "parent_id" "70488"+                ,partBS "captcha" ""+                ,partBS "homeboard" "0chan.hk"+                ,partBS "text" $ TE.encodeUtf8 ">>72127\r\nМы работаем над этим."+                ,partFileSource "upload" "nyan.gif"+                ]+            mfd <- fmap (toByteString . mconcat) $ runResourceT $ src $$ CL.consume+            exam <- S.readFile "multipart-example.bin"+            mfd @?= exam++    describe "HTTP/1.0" $ do+        it "BaseHTTP" $ do+            let baseHTTP app' = do+                    appSource app' $$ await+                    yield "HTTP/1.0 200 OK\r\n\r\nThis is it!" $$ appSink app'+            withCApp baseHTTP $ \port -> withManager $ \manager -> do+                req <- parseUrl $ "http://127.0.0.1:" ++ show port+                res1 <- httpLbs req manager+                res2 <- httpLbs req manager+                liftIO $ res1 @?= res2  withCApp :: Data.Conduit.Network.Application IO -> (Int -> IO ()) -> IO () withCApp app' f = do