-printContDispo ∷ ContDispo → Ascii
-printContDispo d
- = A.fromAsciiBuilder $
- ( A.toAsciiBuilder (A.fromCIAscii $ dType d)
- ⊕
- printParams (dParams d) )
-
-multipartFormP ∷ Ascii → Parser [(Text, FormData)]
-multipartFormP boundary
- = do parts ← many $ try $ partP boundary
- _ ← string "--"
- _ ← string $ A.toByteString boundary
- _ ← string "--"
- crlf
- catMaybes <$> mapM partToFormPair parts
-
-partP ∷ Ascii → Parser Part
-partP boundary
- = do _ ← string "--"
- _ ← string $ A.toByteString boundary
- crlf
- hs ← headersP
- d ← getContDispo hs
- body ← bodyP boundary
- return $ Part hs d body
-
-bodyP ∷ Ascii → Parser LS.ByteString
-bodyP boundary
- = do body ← manyCharsTill anyChar $
- try $
- do crlf
- _ ← string "--"
- _ ← string $ A.toByteString boundary
- return ()
- crlf
- return body
-
-partToFormPair ∷ Monad m ⇒ Part → m (Maybe (Text, FormData))
+instance ConvertSuccess ContDispo Ascii where
+ {-# INLINE convertSuccess #-}
+ convertSuccess = convertSuccessVia ((⊥) ∷ AsciiBuilder)
+
+instance ConvertSuccess ContDispo AsciiBuilder where
+ {-# INLINE convertSuccess #-}
+ convertSuccess (ContDispo {..})
+ = cs dType ⊕ cs dParams
+
+deriveAttempts [ ([t| ContDispo |], [t| Ascii |])
+ , ([t| ContDispo |], [t| AsciiBuilder |])
+ ]
+
+-- |Parse \"multipart/form-data\" to a list of @(name,
+-- formData)@. Note that there are currently the following
+-- limitations:
+--
+-- * Multiple files embedded as \"multipart/mixed\" within the
+-- \"multipart/form-data\" won't be decomposed.
+--
+-- * \"Content-Transfer-Encoding\" is always ignored.
+--
+-- * RFC 2388 (<http://tools.ietf.org/html/rfc2388#section-3>) says
+-- that non-ASCII field names are encoded according to the method
+-- in RFC 2047 (<http://tools.ietf.org/html/rfc2047>), but this
+-- function currently doesn't decode them.
+parseMultipartFormData ∷ Ascii -- ^boundary
+ → LS.ByteString -- ^input
+ → Either String [(Ascii, FormData)]
+parseMultipartFormData boundary = (mapM partToFormPair =≪) ∘ go
+ where
+ go ∷ (Functor m, MonadError String m)
+ ⇒ LS.ByteString
+ → m [Part]
+ {-# INLINEABLE go #-}
+ go src
+ = case LP.parse (prologue boundary) src of
+ LP.Done src' _
+ → go' src' (∅)
+ LP.Fail _ eCtx e
+ → throwError $ "Unparsable multipart/form-data: "
+ ⧺ intercalate ", " eCtx
+ ⧺ ": "
+ ⧺ e
+ go' ∷ (Functor m, MonadError String m)
+ ⇒ LS.ByteString
+ → Seq Part
+ → m [Part]
+ {-# INLINEABLE go' #-}
+ go' src xs
+ = case LP.parse epilogue src of
+ LP.Done _ _
+ → return $ toList xs
+ LP.Fail _ _ _
+ → do (src', x) ← parsePart boundary src
+ go' src' $ xs ⊳ x
+
+prologue ∷ Ascii → Parser ()
+prologue boundary
+ = ( (string "--" <?> "prefix")
+ *>
+ (string (cs boundary) <?> "boundary")
+ *>
+ pure ()
+ )
+ <?>
+ "prologue"
+
+epilogue ∷ Parser ()
+epilogue = finishOff ((string "--" <?> "suffix") *> crlf)
+ <?>
+ "epilogue"
+
+parsePart ∷ (Functor m, MonadError String m)
+ ⇒ Ascii
+ → LS.ByteString
+ → m (LS.ByteString, Part)
+{-# INLINEABLE parsePart #-}
+parsePart boundary src
+ = case LP.parse partHeader src of
+ LP.Done src' hdrs
+ → do dispo ← getContDispo hdrs
+ cType ← fromMaybe defaultCType <$> getContType hdrs
+ (body, src'')
+ ← getBody boundary src'
+ return (src'', Part dispo cType body)
+ LP.Fail _ eCtx e
+ → throwError $ "unparsable part: "
+ ⧺ intercalate ", " eCtx
+ ⧺ ": "
+ ⧺ e
+ where
+ defaultCType ∷ MIMEType
+ defaultCType = [mimeType| text/plain |]
+
+partHeader ∷ Parser Headers
+{-# INLINE partHeader #-}
+partHeader = crlf *> def
+
+getContDispo ∷ MonadError String m ⇒ Headers → m ContDispo
+{-# INLINEABLE getContDispo #-}
+getContDispo hdrs
+ = case getHeader "Content-Disposition" hdrs of
+ Nothing
+ → throwError "Content-Disposition is missing"
+ Just str
+ → case parseOnly (finishOff contentDisposition) $ cs str of
+ Right d → return d
+ Left err → throwError $ "malformed Content-Disposition: "
+ ⊕ cs str
+ ⊕ ": "
+ ⊕ err
+
+contentDisposition ∷ Parser ContDispo
+{-# INLINEABLE contentDisposition #-}
+contentDisposition
+ = (ContDispo <$> (cs <$> token) ⊛ def)
+ <?>
+ "contentDisposition"
+
+getContType ∷ MonadError String m ⇒ Headers → m (Maybe MIMEType)
+{-# INLINEABLE getContType #-}
+getContType hdrs
+ = case getHeader "Content-Type" hdrs of
+ Nothing
+ → return Nothing
+ Just str
+ → case parseOnly (finishOff def) $ cs str of
+ Right d → return $ Just d
+ Left err → throwError $ "malformed Content-Type: "
+ ⊕ cs str
+ ⊕ ": "
+ ⊕ err
+
+getBody ∷ MonadError String m
+ ⇒ Ascii
+ → LS.ByteString
+ → m (LS.ByteString, LS.ByteString)
+{-# INLINEABLE getBody #-}
+getBody (("\r\n--" ⊕) ∘ cs → boundary) src
+ = case breakOn boundary src of
+ (before, after)
+ | LS.null after
+ → throwError "missing boundary"
+ | otherwise
+ → let len = fromIntegral $ BS.length boundary
+ after' = LS.drop len after
+ in
+ return (before, after')
+
+partToFormPair ∷ MonadError String m ⇒ Part → m (Ascii, FormData)