@@ -12,7 +12,6 @@ module Network.Wai.Parse
12 , RequestBodyType (..)
12 , RequestBodyType (..)
13 , getRequestBodyType
13 , getRequestBodyType
14 , sinkRequestBody
14 , sinkRequestBody
17 , lbsBackEnd
16 , lbsBackEnd
18 , tempFileBackEnd
17 , tempFileBackEnd
@@ -38,22 +37,14 @@ import qualified Data.ByteString.Char8 as S8
38import Data.Word (Word8)
37import Data.Word (Word8)
39import Data.Maybe (fromMaybe)
38import Data.Maybe (fromMaybe)
40import Data.List (sortBy)
39import Data.List (sortBy)
41−import Data.Function (on)
40+import Data.Function (on, fix)
42import System.Directory (removeFile, getTemporaryDirectory)
41import System.Directory (removeFile, getTemporaryDirectory)
43import System.IO (hClose, openBinaryTempFile)
42import System.IO (hClose, openBinaryTempFile)
44import Network.Wai
43import Network.Wai
46−import Data.Conduit.Internal ()
47−import qualified Data.Conduit.List as CL
48−import qualified Data.Conduit.Binary as CB
49−import Control.Monad.IO.Class (liftIO)
50import qualified Network.HTTP.Types as H
44import qualified Network.HTTP.Types as H
51−import Data.Either (partitionEithers)
52import Control.Monad (when, unless)
45import Control.Monad (when, unless)
53−import Control.Monad.Trans.Class (lift)
54import Control.Monad.Trans.Resource (allocate, release, register, InternalState, runInternalState)
46import Control.Monad.Trans.Resource (allocate, release, register, InternalState, runInternalState)
55−import Data.Conduit.Internal (Pipe (NeedInput, HaveOutput), (>+>), withUpstream, injectLeftovers, ConduitM (..))
47+import Data.IORef
56−import Data.Void (Void)
58breakDiscard :: Word8 -> S.ByteString -> (S.ByteString, S.ByteString)
49breakDiscard :: Word8 -> S.ByteString -> (S.ByteString, S.ByteString)
59breakDiscard w s =
50breakDiscard w s =
@@ -85,15 +76,22 @@ parseHttpAccept = map fst
87-- | Store uploaded files in memory
78-- | Store uploaded files in memory
88−lbsBackEnd :: Monad m => ignored1 -> ignored2 -> Sink S.ByteString m L.ByteString
79+lbsBackEnd :: Monad m => ignored1 -> ignored2 -> m S.ByteString -> m L.ByteString
89−lbsBackEnd _ _ = fmap L.fromChunks CL.consume
80+lbsBackEnd _ _ popper =
86+ then return $ L.fromChunks $ front []
87+ else loop $ front . (bs:)
91-- | Save uploaded files on disk as temporary files
89-- | Save uploaded files on disk as temporary files
93-- Note: starting with version 2.0, removal of temp files is registered with
91-- Note: starting with version 2.0, removal of temp files is registered with
94-- the provided @InternalState@. It is the responsibility of the caller to
92-- the provided @InternalState@. It is the responsibility of the caller to
95-- ensure that this @InternalState@ gets cleaned up.
93-- ensure that this @InternalState@ gets cleaned up.
96−tempFileBackEnd :: InternalState -> ignored1 -> ignored2 -> Sink S.ByteString IO FilePath
94+tempFileBackEnd :: InternalState -> ignored1 -> ignored2 -> IO S.ByteString -> IO FilePath
97tempFileBackEnd = tempFileBackEndOpts getTemporaryDirectory "webenc.buf"
95tempFileBackEnd = tempFileBackEndOpts getTemporaryDirectory "webenc.buf"
99-- | Same as 'tempFileSink', but use configurable temp folders and patterns.
97-- | Same as 'tempFileSink', but use configurable temp folders and patterns.
@@ -102,14 +100,19 @@ tempFileBackEndOpts :: IO FilePath -- ^ get temporary directory
102 -> InternalState
100 -> InternalState
103 -> ignored1
101 -> ignored1
104 -> ignored2
102 -> ignored2
105− -> Sink S.ByteString IO FilePath
103+ -> IO S.ByteString
106−tempFileBackEndOpts getTmpDir pattern internalState _ _ = do
105+tempFileBackEndOpts getTmpDir pattern internalState _ _ popper = do
107 (key, (fp, h)) <- flip runInternalState internalState $ allocate (do
106 (key, (fp, h)) <- flip runInternalState internalState $ allocate (do
108 tempDir <- getTmpDir
107 tempDir <- getTmpDir
109 openBinaryTempFile tempDir pattern) (\(_, h) -> hClose h)
108 openBinaryTempFile tempDir pattern) (\(_, h) -> hClose h)
110 _ <- runInternalState (register $ removeFile fp) internalState
109 _ <- runInternalState (register $ removeFile fp) internalState
111− CB.sinkHandle h
110+ fix $ \loop -> do
112+ unless (S.null bs) $ do
112− lift $ release key
115+ release key
113 return fp
116 return fp
115-- | Information on an uploaded file.
118-- | Information on an uploaded file.
@@ -126,11 +129,12 @@ type Param = (S.ByteString, S.ByteString)
126-- | Post parameter name and associated file information.
129-- | Post parameter name and associated file information.
127type File y = (S.ByteString, FileInfo y)
130type File y = (S.ByteString, FileInfo y)
129−-- | A file uploading backend. Takes the parameter name, file name, and content
132+-- | A file uploading backend. Takes the parameter name, file name, and a
130−-- type, and returns a `Sink` for storing the contents.
133+-- stream of data.
131type BackEnd a = S.ByteString -- ^ parameter name
134type BackEnd a = S.ByteString -- ^ parameter name
132 -> FileInfo ()
135 -> FileInfo ()
133− -> Sink S.ByteString IO a
136+ -> IO S.ByteString
135data RequestBodyType = UrlEncoded | Multipart S.ByteString
139data RequestBodyType = UrlEncoded | Multipart S.ByteString
@@ -173,60 +177,99 @@ parseRequestBody :: BackEnd y
173parseRequestBody s r =
177parseRequestBody s r =
174 case getRequestBodyType r of
178 case getRequestBodyType r of
175 Nothing -> return ([], [])
179 Nothing -> return ([], [])
176− Just rbt -> fmap partitionEithers $ requestBody r $$ conduitRequestBody s rbt =$ CL.consume
180+ Just rbt -> sinkRequestBody s rbt (requestBody r)
178sinkRequestBody :: BackEnd y
182sinkRequestBody :: BackEnd y
179 -> RequestBodyType
183 -> RequestBodyType
180− -> Sink S.ByteString IO ([Param], [File y])
185+ -> IO ([Param], [File y])
186+sinkRequestBody s r body = do
187+ ref <- newIORef (id, id)
188+ let add x = atomicModifyIORef ref $ \(y, z) ->
190+ Left y' -> ((y . (y':), z), ())
191+ Right z' -> ((y, z . (z':)), ())
181−sinkRequestBody s r = fmap partitionEithers $ conduitRequestBody s r =$ CL.consume
192+ conduitRequestBody s r body add
193+ (x, y) <- readIORef ref
183conduitRequestBody :: BackEnd y
196conduitRequestBody :: BackEnd y
184 -> RequestBodyType
197 -> RequestBodyType
185− -> Conduit S.ByteString IO (Either Param (File y))
199+ -> (Either Param (File y) -> IO ())
186−conduitRequestBody _ UrlEncoded = do
201+conduitRequestBody _ UrlEncoded rbody add = do
187 -- NOTE: in general, url-encoded data will be in a single chunk.
202 -- NOTE: in general, url-encoded data will be in a single chunk.
188 -- Therefore, I'm optimizing for the usual case by sticking with
203 -- Therefore, I'm optimizing for the usual case by sticking with
189 -- strict byte strings here.
204 -- strict byte strings here.
190− bs <- CL.consume
205+ let loop front = do
208+ then return $ S.concat $ front []
209+ else loop $ front . (bs:)
191− mapM_ yield $ map Left $ H.parseSimpleQuery $ S.concat bs
211+ mapM_ (add . Left) $ H.parseSimpleQuery bs
192−conduitRequestBody backend (Multipart bound) =
212+conduitRequestBody backend (Multipart bound) rbody add =
193− parsePieces backend $ S8.pack "--" `S.append` bound
213+ parsePieces backend (S8.pack "--" `S.append` bound) rbody add
195−takeLine :: Monad m => Consumer S.ByteString m (Maybe S.ByteString)
215+takeLine :: Source -> IO (Maybe S.ByteString)
196−takeLine =
216+takeLine src =
220+ bs <- readSource src
199− go front = await >>= maybe (close front) (push front)
222+ then close front
201− close front = leftover (front S.empty) >> return Nothing
225+ close front = leftover src (front S.empty) >> return Nothing
202 push front bs = do
226 push front bs = do
203 let (x, y) = S.break (== 10) $ front bs -- LF
227 let (x, y) = S.break (== 10) $ front bs -- LF
204 in if S.null y
228 in if S.null y
205 then go $ S.append x
229 then go $ S.append x
207− when (S.length y > 1) $ leftover $ S.drop 1 y
231+ when (S.length y > 1) $ leftover src $ S.drop 1 y
208 return $ Just $ killCR x
232 return $ Just $ killCR x
210−takeLines :: Consumer S.ByteString IO [S.ByteString]
234+takeLines :: Source -> IO [S.ByteString]
211−takeLines = do
235+takeLines src = do
212− res <- takeLine
236+ res <- takeLine src
213 case res of
237 case res of
214 Nothing -> return []
238 Nothing -> return []
216 | S.null l -> return []
240 | S.null l -> return []
217 | otherwise -> do
241 | otherwise -> do
218− ls <- takeLines
242+ ls <- takeLines src
219 return $ l : ls
243 return $ l : ls
245+data Source = Source (IO S.ByteString) (IORef S.ByteString)
247+mkSource :: IO S.ByteString -> IO Source
249+ ref <- newIORef S.empty
250+ return $ Source f ref
252+readSource :: Source -> IO S.ByteString
253+readSource (Source f ref) = do
254+ bs <- atomicModifyIORef ref $ \bs -> (S.empty, bs)
259+leftover :: Source -> S.ByteString -> IO ()
260+leftover (Source _ ref) bs = writeIORef ref bs
221parsePieces :: BackEnd y
262parsePieces :: BackEnd y
222 -> S.ByteString
263 -> S.ByteString
223− -> ConduitM S.ByteString (Either Param (File y)) IO ()
265+ -> (Either Param (File y) -> IO ())
224−parsePieces sink bound =
267+parsePieces sink bound rbody add =
225− loop
268+ mkSource rbody >>= loop
227− loop = do
270+ loop src = do
228− _boundLine <- takeLine
271+ _boundLine <- takeLine src
229− res' <- takeLines
272+ res' <- takeLines src
230 unless (null res') $ do
273 unless (null res') $ do
231 let ls' = map parsePair res'
274 let ls' = map parsePair res'
232 let x = do
275 let x = do
@@ -239,23 +282,23 @@ parsePieces sink bound =
239 Just (mct, name, Just filename) -> do
282 Just (mct, name, Just filename) -> do
240 let ct = fromMaybe "application/octet-stream" mct
283 let ct = fromMaybe "application/octet-stream" mct
241 fi0 = FileInfo filename ct ()
284 fi0 = FileInfo filename ct ()
242− (wasFound, y) <- sinkTillBound' bound name fi0 sink
285+ (wasFound, y) <- sinkTillBound' bound name fi0 sink src
243− yield $ Right (name, fi0 { fileContent = y })
286+ add $ Right (name, fi0 { fileContent = y })
244− when wasFound loop
287+ when wasFound (loop src)
245 Just (_ct, name, Nothing) -> do
288 Just (_ct, name, Nothing) -> do
246 let seed = id
289 let seed = id
247 let iter front bs = return $ front . (:) bs
290 let iter front bs = return $ front . (:) bs
248− (wasFound, front) <- sinkTillBound bound iter seed
291+ (wasFound, front) <- sinkTillBound bound iter seed src
249 let bs = S.concat $ front []
292 let bs = S.concat $ front []
250 let x' = (name, bs)
293 let x' = (name, bs)
251− yield $ Left x'
294+ add $ Left x'
252− when wasFound loop
295+ when wasFound (loop src)
254 -- ignore this part
297 -- ignore this part
255 let seed = ()
298 let seed = ()
256 iter () _ = return ()
299 iter () _ = return ()
257− (wasFound, ()) <- sinkTillBound bound iter seed
300+ (wasFound, ()) <- sinkTillBound bound iter seed src
258− when wasFound loop
301+ when wasFound (loop src)
260 contDisp = S8.pack "Content-Disposition"
303 contDisp = S8.pack "Content-Disposition"
261 contType = S8.pack "Content-Type"
304 contType = S8.pack "Content-Type"
@@ -295,64 +338,78 @@ sinkTillBound' :: S.ByteString
295 -> S.ByteString
338 -> S.ByteString
296 -> FileInfo ()
339 -> FileInfo ()
297 -> BackEnd y
340 -> BackEnd y
298− -> ConduitM S.ByteString o IO (Bool, y)
342+ -> IO (Bool, y)
299−sinkTillBound' bound name fi sink =
343+sinkTillBound' bound name fi sink src = do
300− ConduitM $ anyOutput $
344+ (next, final) <- wrapTillBound bound src
301− conduitTillBound bound >+> withUpstream (fix $ sink name fi)
345+ y <- sink name fi next
302− where
346+ b <- final
303− fix :: Sink S8.ByteString IO y -> Pipe Void S8.ByteString Void Bool IO y
347+ return (b, y)
304− fix p = ignoreTerm >+> injectLeftovers (unConduitM p)
348+
305− ignoreTerm = await' >>= maybe (return ()) (\x -> yield' x >> ignoreTerm)
349+data WTB = WTBWorking (S.ByteString -> S.ByteString)
306− await' = NeedInput (return . Just) (const $ return Nothing)
350+ | WTBDone Bool
307− yield' = HaveOutput (return ()) (return ())
309− anyOutput p = p >+> dropInput
310− dropInput = NeedInput (const dropInput) return
312−conduitTillBound :: Monad m
313− => S.ByteString -- bound
351+wrapTillBound :: S.ByteString -- ^ bound
314− -> Pipe S.ByteString S.ByteString S.ByteString () m Bool
352+ -> Source
315−conduitTillBound bound =
353+ -> IO (IO S.ByteString, IO Bool) -- ^ Bool indicates if the bound was found
316− unConduitM $
354+wrapTillBound bound src = do
317− go id
355+ ref <- newIORef $ WTBWorking id
356+ return (go ref, final ref)
319− go front = await >>= maybe (close front) (push front)
358+ final ref = do
320− close front = do
359+ x <- readIORef ref
321− let bs = front S.empty
360+ case x of
361+ WTBWorking _ -> error "wrapTillBound did not finish"
362+ WTBDone y -> return y
365+ state <- readIORef ref
367+ WTBDone _ -> return S.empty
368+ WTBWorking front -> do
369+ bs <- readSource src
322− unless (S.null bs) $ yield bs
370+ if S.null bs
323− return False
371+ then do
372+ writeIORef ref $ WTBDone False
324− push front bs' = do
373+ return $ front bs
325− let bs = front bs'
374+ else push $ front bs
326− case findBound bound bs of
377+ case findBound bound bs of
327− FoundBound before after -> do
378+ FoundBound before after -> do
328− let before' = killCRLF before
379+ let before' = killCRLF before
330− leftover after
380+ leftover src after
331− return True
381+ writeIORef ref $ WTBDone True
332− NoBound -> do
383+ NoBound -> do
333− -- don't emit newlines, in case it's part of a bound
384+ -- don't emit newlines, in case it's part of a bound
334− let (toEmit, front') =
385+ let (toEmit, front') =
335− if not (S8.null bs) && S8.last bs `elem` "\r\n"
386+ if not (S8.null bs) && S8.last bs `elem` "\r\n"
336− then let (x, y) = S.splitAt (S.length bs - 2) bs
387+ then let (x, y) = S.splitAt (S.length bs - 2) bs
337− in (x, S.append y)
388+ in (x, S.append y)
338− else (bs, id)
389+ else (bs, id)
390+ writeIORef ref $ WTBWorking front'
339− yield toEmit
391+ if S.null toEmit
340− go front'
392+ then go ref
341− PartialBound -> go $ S.append bs
394+ PartialBound -> do
395+ writeIORef ref $ WTBWorking $ S.append bs
343sinkTillBound :: S.ByteString
398sinkTillBound :: S.ByteString
344 -> (x -> S.ByteString -> IO x)
399 -> (x -> S.ByteString -> IO x)
346− -> Consumer S.ByteString IO (Bool, x)
402+ -> IO (Bool, x)
347−sinkTillBound bound iter seed0 =
403+sinkTillBound bound iter seed0 src = do
348− ConduitM $
404+ (next, final) <- wrapTillBound bound src
349− (conduitTillBound bound >+> (withUpstream $ ij $ CL.foldM iter' seed0))
405+ let loop seed = do
350− where
406+ bs <- next
351− iter' a b = liftIO $ iter a b
407+ if S.null bs
352− ij (ConduitM p) = ignoreTerm >+> injectLeftovers p
408+ then return seed
353− ignoreTerm = await' >>= maybe (return ()) (\x -> yield' x >> ignoreTerm)
409+ else iter seed bs >>= loop
354− await' = NeedInput (return . Just) (const $ return Nothing)
410+ seed <- loop seed0
355− yield' = HaveOutput (return ()) (return ())
411+ b <- final
357parseAttrs :: S.ByteString -> [(S.ByteString, S.ByteString)]
414parseAttrs :: S.ByteString -> [(S.ByteString, S.ByteString)]
358parseAttrs = map go . S.split 59 -- semicolon
415parseAttrs = map go . S.split 59 -- semicolon