8 module Network.HTTP.Lucu.MultipartForm
13 import Control.Applicative hiding (many)
14 import Data.Ascii (Ascii, CIAscii, AsciiBuilder)
15 import qualified Data.Ascii as A
16 import Data.Attoparsec.Char8
17 import qualified Data.ByteString.Char8 as BS
18 import qualified Data.ByteString.Lazy.Char8 as LS
22 import Data.Monoid.Unicode
23 import Data.Text (Text)
24 import Network.HTTP.Lucu.Headers
25 import Network.HTTP.Lucu.Parser.Http
26 import Network.HTTP.Lucu.Response
27 import Network.HTTP.Lucu.Utils
28 import Prelude.Unicode
30 -- |This data type represents a form value and possibly an uploaded
34 fdFileName ∷ Maybe Text
35 , fdContent ∷ LS.ByteString
41 , ptContDispo ∷ ContDispo
42 , ptBody ∷ LS.ByteString
45 instance HasHeaders Part where
46 getHeaders = ptHeaders
47 setHeaders pt hs = pt { ptHeaders = hs }
52 , dParams ∷ ![(CIAscii, Ascii)]
55 printContDispo ∷ ContDispo → Ascii
57 = A.fromAsciiBuilder $
58 ( A.toAsciiBuilder (A.fromCIAscii $ dType d)
60 ( if null $ dParams d then
63 A.toAsciiBuilder "; " ⊕
64 joinWith "; " (map printPair $ dParams d) ) )
66 printPair ∷ (CIAscii, Ascii) → AsciiBuilder
67 printPair (name, value)
68 = A.toAsciiBuilder (A.fromCIAscii name) ⊕
69 A.toAsciiBuilder "=" ⊕
70 ( if BS.any ((¬) ∘ isToken) $ A.toByteString value then
73 A.toAsciiBuilder value )
75 multipartFormP ∷ Ascii → Parser [(Text, FormData)]
76 multipartFormP boundary
78 do parts ← many (partP boundary)
80 _ ← string $ A.toByteString boundary
83 catMaybes <$> mapM partToFormPair parts
85 partP ∷ Ascii → Parser Part
89 _ ← string $ A.toByteString boundary
94 return $ Part hs d body
96 bodyP ∷ Ascii → Parser LS.ByteString
99 do body ← manyCharsTill anyChar $
103 _ ← string $ A.toByteString boundary
108 partToFormPair ∷ Monad m ⇒ Part → m (Maybe (Text, FormData))
109 {-# INLINEABLE partToFormPair #-}
111 | dType (ptContDispo pt) ≡ "form-data"
112 = do name ← partName pt
113 let fname = partFileName pt
116 , fdContent = ptBody pt
118 return $ Just (name, fd)
122 partName ∷ Monad m ⇒ Part → m Text
123 {-# INLINEABLE partName #-}
125 = case find ((≡ "name") ∘ fst) $ dParams $ ptContDispo pt of
129 → fail ("form-data without name: " ⧺
130 A.toString (printContDispo $ ptContDispo pt))
132 partFileName ∷ Part → Maybe Text
133 {-# INLINEABLE partFileName #-}
135 = snd <$> (find ((== "filename") ∘ fst) $ dParams $ ptContDispo pt)
137 getContDispo ∷ Monad m ⇒ Headers → m ContDispo
138 {-# INLINEABLE getContDispo #-}
140 = case getHeader "Content-Disposition" hdr of
142 → fail ("There is a part without Content-Disposition in the multipart/form-data.")
144 → let p = do d ← contDispoP
147 bs = A.toByteString str
149 case parseOnly p bs of
151 Left err → fail (concat [ "Unparsable Content-Disposition: "
157 contDispoP ∷ Parser ContDispo
159 do dispoType ← A.toCIAscii <$> token
161 return $ ContDispo dispoType params
163 paramP ∷ Parser (CIAscii, Ascii)
164 paramP = do skipMany lws
167 name ← A.toCIAscii <$> token
169 value ← token <|> quotedStr