+ boundaryP ∷ Parser BS.ByteString
+ boundaryP = string ("--" ⊕ A.toByteString boundary)
+ <?>
+ "boundaryP"
+
+partP ∷ Parser α → Parser Part
+partP boundaryP
+ = do crlf
+ hs ← headersP
+ d ← getContDispo hs
+ body ← bodyP boundaryP
+ return $ Part hs d body
+ <?>
+ "partP"
+
+bodyP ∷ Parser α → Parser LS.ByteString
+bodyP boundaryP
+ = manyOctetsTill anyWord8 (try $ crlf *> boundaryP)
+ <?>
+ "bodyP"
+
+partToFormPair ∷ Monad m ⇒ Part → m (Maybe (Text, FormData))
+{-# INLINEABLE partToFormPair #-}
+partToFormPair pt
+ | dType (ptContDispo pt) ≡ "form-data"
+ = do name ← partName pt
+ let fname = partFileName pt
+ let fd = FormData {
+ fdFileName = fname
+ , fdContent = ptBody pt
+ }
+ return $ Just (name, fd)
+ | otherwise
+ = return Nothing
+
+partName ∷ Monad m ⇒ Part → m Text
+{-# INLINEABLE partName #-}
+partName (Part {..})
+ = case M.lookup "name" $ dParams ptContDispo of
+ Just name
+ → return name
+ Nothing
+ → fail ("form-data without name: " ⧺
+ A.toString (printContDispo ptContDispo))
+
+partFileName ∷ Part → Maybe Text
+{-# INLINEABLE partFileName #-}
+partFileName (Part {..})
+ = M.lookup "filename" $ dParams ptContDispo
+
+getContDispo ∷ Monad m ⇒ Headers → m ContDispo
+{-# INLINEABLE getContDispo #-}
+getContDispo hdr
+ = case getHeader "Content-Disposition" hdr of
+ Nothing
+ → fail "There is a part without Content-Disposition in the multipart/form-data."
+ Just str
+ → let p = do d ← contDispoP
+ endOfInput
+ return d
+ bs = A.toByteString str
+ in
+ case parseOnly p bs of
+ Right d → return d
+ Left err → fail (concat [ "Unparsable Content-Disposition: "
+ , BS.unpack bs
+ , ": "
+ , err
+ ])
+
+contDispoP ∷ Parser ContDispo
+{-# INLINEABLE contDispoP #-}
+contDispoP
+ = do dispoType ← A.toCIAscii <$> token
+ params ← paramsP
+ return $ ContDispo dispoType params
+ <?>
+ "contDispoP"