@@ -4,15 +4,24 @@ module Network.Wai.Parse
4 ( parseQueryString
4 ( parseQueryString
5 , parseCookies
5 , parseCookies
6 , parseHttpAccept
6 , parseHttpAccept
9import qualified Data.ByteString as S
14import qualified Data.ByteString as S
15+import qualified Data.ByteString.Lazy as L
10import qualified Data.ByteString.Char8 as S8
16import qualified Data.ByteString.Char8 as S8
11import Data.Word (Word8)
17import Data.Word (Word8)
12import Data.Bits
18import Data.Bits
13import Data.Maybe (fromMaybe)
19import Data.Maybe (fromMaybe)
14import Data.List (sortBy)
20import Data.List (sortBy)
15import Data.Function (on)
21import Data.Function (on)
22+import System.Directory (removeFile, getTemporaryDirectory)
23+import System.IO (hClose, openBinaryTempFile, Handle)
17uncons :: S.ByteString -> Maybe (Word8, S.ByteString)
26uncons :: S.ByteString -> Maybe (Word8, S.ByteString)
@@ -84,7 +93,7 @@ parseHttpAccept :: S.ByteString -> [S.ByteString]
84parseHttpAccept = map fst
93parseHttpAccept = map fst
85 . sortBy (rcompare `on` snd)
94 . sortBy (rcompare `on` snd)
86 . map grabQ
95 . map grabQ
87− . split
96+ . S.split 44 -- comma
89 rcompare x y = compare y x
98 rcompare x y = compare y x
@@ -95,7 +104,153 @@ parseHttpAccept = map fst
95 (x, _):_ -> x
104 (x, _):_ -> x
97 trimWhite = S.dropWhile (== 32) -- space
106 trimWhite = S.dropWhile (== 32) -- space
99− | S.null s = []
108+-- | A destination for data, the opposite of a 'Source'.
111+ , sinkAppend :: x -> S.ByteString -> IO x
112+ , sinkClose :: x -> IO y
113+ , sinkFinalize :: y -> IO ()
116+lbsSink :: Sink ([S.ByteString] -> [S.ByteString]) L.ByteString
118+ { sinkInit = return id
119+ , sinkAppend = \front bs -> return $ front . (:) bs
120+ , sinkClose = \front -> return $ L.fromChunks $ front []
121+ , sinkFinalize = \_ -> return ()
124+tempFileSink :: Sink (FilePath, Handle) FilePath
127+ tempDir <- getTemporaryDirectory
128+ openBinaryTempFile tempDir "webenc.buf"
129+ , sinkAppend = \(fp, h) bs -> S.hPut h bs >> return (fp, h)
130+ , sinkClose = \(fp, h) -> do
133+ , sinkFinalize = \fp -> removeFile fp
136+-- | Information on an uploaded file.
137+data FileInfo c = FileInfo
138+ { fileName :: S.ByteString
139+ , fileContentType :: S.ByteString
144+type Param = (S.ByteString, S.ByteString)
145+type File y = (S.ByteString, FileInfo y)
147+parseRequestBody :: Sink x y
149+ -> IO ([Param], [File y])
150+parseRequestBody sink req = do
152+ ctype' <- lookup ReqContentType $ requestHeaders req
153+ if urlenc `S.isPrefixOf` ctype'
155+ else if formBound `S.isPrefixOf` ctype'
156+ then Just $ Just $ S.drop (S.length formBound) ctype'
159+ Nothing -> return ([], [])
160+ Just Nothing -> do -- url-encoded
161+ -- NOTE: in general, url-encoded data will be in a single chunk.
162+ -- Therefore, I'm optimizing for the usual case by sticking with
163+ -- strict byte strings here.
164+ bs <- sourceToBs $ requestBody req
165+ return (parseQueryString bs, [])
166+ Just (Just bound) -> -- multi-part
167+ parsePieces sink bound (S.empty, Just $ requestBody req) True
169+ urlenc = S8.pack "application/x-www-form-urlencoded"
170+ formBound = S8.pack "multipart/form-data; boundary="
172+sourceToBs :: Source -> IO S.ByteString
173+sourceToBs = fmap S.concat . go id
175+ go front (Source src) = do
178+ Nothing -> return $ front []
179+ Just (bs, src') -> go (front . (:) bs) src'
181+type Source' = (S.ByteString, Maybe Source)
183+takeLine :: Source' -> IO (Maybe (S.ByteString, Source'))
185+ | S.null s = case msrc of
186+ Nothing -> return Nothing
187+ Just (Source src) -> do
190+ Nothing -> return Nothing
191+ Just (x, y) -> takeLine (x, Just y)
193+ let (x, y) = S.break (== 10) s -- newline
197+ Nothing -> return $ Just (x, (y, msrc))
198+ Just (Source src) -> do
201+ Nothing -> return $ Just (x, (y, Nothing))
203+ takeLine (s `S.append` s', Just src')
204+ else return $ Just (killCarriage x, (S.drop 1 y, msrc))
208+ | S.last bs == 13 = S.init bs -- carriage return
211+takeLines :: Source' -> IO (Maybe ([S.ByteString], Source'))
215+ Nothing -> return Nothing
218+ then return $ Just ([], src')
220+ res' <- takeLines src'
222+ Nothing -> return $ Just ([l], src')
223+ Just (ls, src'') -> return $ Just (l : ls, src'')
225+parsePieces :: Sink x y -> S.ByteString -> Source' -> Bool
226+ -> IO ([Param], [File y])
227+parsePieces sink bound src isFirst = do
228+ print ("bound", bound)
231+ res <- takeLine src -- first line should be bound
233+ Nothing -> return (S.empty, Nothing)
234+ Just (_, x) -> return x
236+ res <- takeLines src'
238+ Nothing -> return ([], [])
239+ Just (ls, src'') -> do
240+ let ls' = map parsePair ls
241+ case (lookup contDisp ls', lookup contType ls') of
242+ (Just cd, Just ct) -> do
243+ return ([], []) -- FIXME
245+ (isEnd, src''') <- dropTillBound bound src''
247+ then return ([], [])
248+ else parsePieces sink bound src''' False
250+ contDisp = S8.pack "Content-Disposition"
251+ contType = S8.pack "Content-Type"
100− | otherwise = let (x, y) = breakDiscard 44 s -- comma
253+ let (x, y) = breakDiscard 58 s -- colon
101− in x : split y
254+ in (x, S.dropWhile (== 32) y) -- space
256+dropTillBound bound (bs, msrc) = error "FIXME dropTillBound"