9 -- |Parse \"multipart/form-data\" based on RFC 2388:
10 -- <http://www.faqs.org/rfcs/rfc2388.html>
12 -- You usually don't have to use this module directly.
13 module Network.HTTP.Lucu.MultipartForm
15 , parseMultipartFormData
18 import Control.Applicative hiding (many)
19 import Control.Applicative.Unicode hiding ((∅))
20 import Control.Monad.Error
21 import Control.Monad.Unicode
22 import Data.Ascii (Ascii, CIAscii)
23 import qualified Data.Ascii as A
24 import Data.Attoparsec
25 import qualified Data.Attoparsec.Lazy as LP
26 import qualified Data.ByteString.Lazy.Char8 as LS
27 import Data.ByteString.Lazy.Search
31 import qualified Data.Map as M
33 import Data.Monoid.Unicode
34 import Data.Sequence (Seq)
35 import Data.Sequence.Unicode hiding ((∅))
36 import Data.Text (Text)
37 import qualified Data.Text as T
38 import Network.HTTP.Lucu.Headers
39 import Network.HTTP.Lucu.MIMEType
40 import Network.HTTP.Lucu.Parser.Http
41 import Network.HTTP.Lucu.RFC2231
42 import Prelude.Unicode
44 -- |'FormData' represents a form value and possibly an uploaded file
48 fdFileName ∷ !(Maybe Text)
49 , fdMIMEType ∷ !MIMEType
50 , fdContent ∷ !(LS.ByteString)
55 ptContDispo ∷ !ContDispo
56 , ptContType ∷ !MIMEType
57 , ptBody ∷ !LS.ByteString
63 , dParams ∷ !(Map CIAscii Text)
66 printContDispo ∷ ContDispo → Ascii
69 ( A.toAsciiBuilder (A.fromCIAscii $ dType d)
71 printMIMEParams (dParams d) )
73 -- |Parse \"multipart/form-data\" and return either @'Left' err@ or
74 -- @'Right' result@. Note that there are currently the following
77 -- * Multiple files embedded as \"multipart/mixed\" within the
78 -- \"multipart/form-data\" aren't decomposed.
80 -- * \"Content-Transfer-Encoding\"s are always ignored.
82 -- * RFC 2388 says that non-ASCII field names are encoded according
83 -- to the method in RFC 2047
84 -- <http://www.faqs.org/rfcs/rfc2047.html>, but they aren't
86 parseMultipartFormData ∷ Ascii -- ^boundary
87 → LS.ByteString -- ^input
88 → Either String [(Ascii, FormData)]
89 parseMultipartFormData boundary = (mapM partToFormPair =≪) ∘ go
91 go ∷ (Functor m, MonadError String m)
96 = case LP.parse (prologue boundary) src of
100 → throwError $ "Unparsable multipart/form-data: "
101 ⧺ intercalate ", " eCtx
104 go' ∷ (Functor m, MonadError String m)
108 {-# INLINEABLE go' #-}
110 = case LP.parse epilogue src of
114 → do (src', x) ← parsePart boundary src
117 prologue ∷ Ascii → Parser ()
119 = ( (string "--" <?> "prefix")
121 (string (A.toByteString boundary) <?> "boundary")
129 epilogue = ( (string "--" <?> "suffix")
138 parsePart ∷ (Functor m, MonadError String m)
141 → m (LS.ByteString, Part)
142 {-# INLINEABLE parsePart #-}
143 parsePart boundary src
144 = case LP.parse partHeader src of
146 → do dispo ← getContDispo hdrs
147 cType ← fromMaybe defaultCType <$> getContType hdrs
149 ← getBody boundary src'
150 return (src'', Part dispo cType body)
152 → throwError $ "unparsable part: "
153 ⧺ intercalate ", " eCtx
157 defaultCType ∷ MIMEType
158 defaultCType = parseMIMEType "text/plain"
160 partHeader ∷ Parser Headers
161 partHeader = crlf *> headers
163 getContDispo ∷ MonadError String m ⇒ Headers → m ContDispo
164 {-# INLINEABLE getContDispo #-}
166 = case getHeader "Content-Disposition" hdrs of
168 → throwError "Content-Disposition is missing"
170 → case parseOnly p $ A.toByteString str of
172 Left err → throwError $ "malformed Content-Disposition: "
177 p = do dispo ← contentDisposition
181 contentDisposition ∷ Parser ContDispo
183 = (ContDispo <$> (A.toCIAscii <$> token) ⊛ mimeParams)
187 getContType ∷ MonadError String m ⇒ Headers → m (Maybe MIMEType)
188 {-# INLINEABLE getContType #-}
190 = case getHeader "Content-Type" hdrs of
194 → case parseOnly p $ A.toByteString str of
195 Right d → return $ Just d
196 Left err → throwError $ "malformed Content-Type: "
205 getBody ∷ MonadError String m
208 → m (LS.ByteString, LS.ByteString)
209 {-# INLINEABLE getBody #-}
211 = case breakFindAfter (A.toByteString boundary) src of
212 ((before, after), True)
213 → return (before, after)
214 _ → throwError "missing boundary"
216 partToFormPair ∷ MonadError String m ⇒ Part → m (Ascii, FormData)
217 {-# INLINEABLE partToFormPair #-}
218 partToFormPair pt@(Part {..})
219 | dType ptContDispo ≡ "form-data"
220 = do name ← partName pt
222 fdFileName = partFileName pt
223 , fdMIMEType = ptContType
228 = throwError $ "disposition type is not \"form-data\": "
229 ⧺ A.toString (A.fromCIAscii $ dType ptContDispo)
231 partName ∷ MonadError String m ⇒ Part → m Ascii
232 {-# INLINEABLE partName #-}
234 = case M.lookup "name" $ dParams ptContDispo of
236 → case A.fromText name of
238 Nothing → throwError $ "Non-ascii part name: "
241 → throwError $ "form-data without name: "
242 ⧺ A.toString (printContDispo ptContDispo)
244 partFileName ∷ Part → Maybe Text
245 partFileName (Part {..})
246 = M.lookup "filename" $ dParams ptContDispo