DoAndIfThenElse
, FlexibleContexts
, OverloadedStrings
+ , QuasiQuotes
, RecordWildCards
, ScopedTypeVariables
, UnicodeSyntax
, ViewPatterns
#-}
-- |Parse \"multipart/form-data\" based on RFC 2388:
--- <http://www.faqs.org/rfcs/rfc2388.html>
---
--- You usually don't have to use this module directly.
+-- <http://tools.ietf.org/html/rfc2388>
module Network.HTTP.Lucu.MultipartForm
( FormData(..)
, parseMultipartFormData
where
import Control.Applicative hiding (many)
import Control.Applicative.Unicode hiding ((∅))
-import Control.Monad.Error
+import Control.Monad.Error (MonadError, throwError)
import Control.Monad.Unicode
import Data.Ascii (Ascii, CIAscii)
import qualified Data.Ascii as A
import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as LS
import Data.ByteString.Lazy.Search
-import Data.Foldable
-import Data.List
-import Data.Map (Map)
-import qualified Data.Map as M
+import Data.Collections
+import Data.List (intercalate)
import Data.Maybe
import Data.Monoid.Unicode
import Data.Sequence (Seq)
-import Data.Sequence.Unicode hiding ((∅))
import Data.Text (Text)
import qualified Data.Text as T
import Network.HTTP.Lucu.Headers
-import Network.HTTP.Lucu.MIMEType
+import Network.HTTP.Lucu.MIMEParams
+import Network.HTTP.Lucu.MIMEType (MIMEType)
+import qualified Network.HTTP.Lucu.MIMEType as MT
+import Network.HTTP.Lucu.MIMEType.TH
+import Network.HTTP.Lucu.Parser
import Network.HTTP.Lucu.Parser.Http
-import Network.HTTP.Lucu.RFC2231
+import Network.HTTP.Lucu.Utils
+import Prelude hiding (lookup, mapM)
import Prelude.Unicode
-- |'FormData' represents a form value and possibly an uploaded file
-- name.
data FormData
= FormData {
+ -- | @'Nothing'@ for non-file values.
fdFileName ∷ !(Maybe Text)
+ -- | MIME Type of this value, defaulted to \"text/plain\".
, fdMIMEType ∷ !MIMEType
+ -- | The form value.
, fdContent ∷ !(LS.ByteString)
}
data ContDispo
= ContDispo {
dType ∷ !CIAscii
- , dParams ∷ !(Map CIAscii Text)
+ , dParams ∷ !MIMEParams
}
printContDispo ∷ ContDispo → Ascii
-- limitations:
--
-- * Multiple files embedded as \"multipart/mixed\" within the
--- \"multipart/form-data\" aren't decomposed.
+-- \"multipart/form-data\" won't be decomposed.
--
--- * \"Content-Transfer-Encoding\"s are always ignored.
+-- * \"Content-Transfer-Encoding\" is always ignored.
--
--- * RFC 2388 says that non-ASCII field names are encoded according
--- to the method in RFC 2047
--- <http://www.faqs.org/rfcs/rfc2047.html>, but they aren't
--- decoded.
+-- * 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 they won't
+-- be decoded.
parseMultipartFormData ∷ Ascii -- ^boundary
→ LS.ByteString -- ^input
→ Either String [(Ascii, FormData)]
"prologue"
epilogue ∷ Parser ()
-epilogue = ( (string "--" <?> "suffix")
- *>
- crlf
- *>
- endOfInput
- )
+epilogue = finishOff ((string "--" <?> "suffix") *> crlf)
<?>
"epilogue"
⧺ e
where
defaultCType ∷ MIMEType
- defaultCType = parseMIMEType "text/plain"
+ defaultCType = [mimeType| text/plain |]
partHeader ∷ Parser Headers
partHeader = crlf *> headers
Nothing
→ throwError "Content-Disposition is missing"
Just str
- → case parseOnly p $ A.toByteString str of
+ → case parseOnly (finishOff contentDisposition) $ A.toByteString str of
Right d → return d
Left err → throwError $ "malformed Content-Disposition: "
⧺ A.toString str
⧺ ": "
⧺ err
- where
- p = do dispo ← contentDisposition
- endOfInput
- return dispo
contentDisposition ∷ Parser ContDispo
contentDisposition
Nothing
→ return Nothing
Just str
- → case parseOnly p $ A.toByteString str of
+ → case parseOnly (finishOff MT.mimeType) $ A.toByteString str of
Right d → return $ Just d
Left err → throwError $ "malformed Content-Type: "
⧺ A.toString str
⧺ ": "
⧺ err
- where
- p = do t ← mimeType
- endOfInput
- return t
getBody ∷ MonadError String m
⇒ Ascii
partName ∷ MonadError String m ⇒ Part → m Ascii
{-# INLINEABLE partName #-}
partName (Part {..})
- = case M.lookup "name" $ dParams ptContDispo of
+ = case lookup "name" $ dParams ptContDispo of
Just name
→ case A.fromText name of
Just a → return a
⧺ A.toString (printContDispo ptContDispo)
partFileName ∷ Part → Maybe Text
-partFileName (Part {..})
- = M.lookup "filename" $ dParams ptContDispo
+partFileName (ptContDispo → ContDispo {..})
+ = lookup "filename" dParams