Skip to content

Commit d115de5

Browse files
committed
group file uploads
this fix allows uploads with similar names i.e. <input multiple type="file" name="pictures" />
1 parent 40d028b commit d115de5

File tree

4 files changed

+89
-5
lines changed

4 files changed

+89
-5
lines changed

Spock-core/src/Web/Spock/Internal/CoreAction.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ jsonBody' =
149149
{-# INLINE jsonBody' #-}
150150

151151
-- | Get uploaded files
152-
files :: MonadIO m => ActionCtxT ctx m (HM.HashMap T.Text UploadedFile)
152+
files :: MonadIO m => ActionCtxT ctx m (HM.HashMap T.Text [UploadedFile])
153153
files =
154154
do
155155
b <- asks ri_reqBody

Spock-core/src/Web/Spock/Internal/Wire.hs

+4-4
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ loadCacheVar (CacheVar lock makeVal valRef readV) =
142142
data RequestBody = RequestBody
143143
{ rb_value :: CacheVar BS.ByteString,
144144
rb_postParams :: CacheVar [(T.Text, T.Text)],
145-
rb_files :: CacheVar (HM.HashMap T.Text UploadedFile)
145+
rb_files :: CacheVar (HM.HashMap T.Text [UploadedFile])
146146
}
147147

148148
data RequestInfo ctx = RequestInfo
@@ -418,13 +418,13 @@ makeActionEnvironment st stdMethod req =
418418
(bodyParams, bodyFiles) <-
419419
P.sinkRequestBody (P.tempFileBackEnd st) rbt loader
420420
let uploadedFiles =
421-
HM.fromList $
421+
HM.fromListWith (<>) $
422422
flip map bodyFiles $ \(k, fileInfo) ->
423423
( T.decodeUtf8 k,
424-
UploadedFile
424+
[UploadedFile
425425
(T.decodeUtf8 $ P.fileName fileInfo)
426426
(T.decodeUtf8 $ P.fileContentType fileInfo)
427-
(P.fileContent fileInfo)
427+
(P.fileContent fileInfo)]
428428
)
429429
postParams =
430430
map (T.decodeUtf8 *** T.decodeUtf8) bodyParams

Spock-core/test/Web/Spock/FrameworkSpecHelper.hs

+74
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ frameworkSpec app =
6363
actionSpec
6464
headerTest
6565
cookieTest
66+
fileTest
6667

6768
routingSpec :: SpecWith (st, Wai.Application)
6869
routingSpec =
@@ -233,3 +234,76 @@ matchCookie name val =
233234
then Nothing
234235
else loop xs
235236
in loop relevantHeaders
237+
238+
fileTest :: SpecWith (st, Wai.Application)
239+
fileTest =
240+
describe "" $
241+
do
242+
it "receives a single file" $
243+
do
244+
request methodPost "file/upload" headers bodySingle `shouldRespondWith` "1" {matchStatus = 200}
245+
it "receives multiple files with different names" $
246+
do
247+
request methodPost "file/upload" headers bodyUnique `shouldRespondWith` "2" {matchStatus = 200}
248+
it "receives multiple files with similar names" $
249+
do
250+
request methodPost "file/upload/multi" headers bodyMulti `shouldRespondWith` "2" {matchStatus = 200}
251+
where
252+
bodySingle = BSLC.pack $
253+
boundary <> crlf
254+
<> "Content-Disposition: form-data; name=\"name\"" <> crlf <> crlf
255+
<> "file1.pdf" <> crlf
256+
<> boundary <> crlf
257+
<> "Content-Disposition: form-data; name=\"file\"; filename=\"file1.pdf\"" <> crlf
258+
<> "Content-Type: application/pdf" <> crlf
259+
<> "Content-Transfer-Encoding: base64" <> crlf <> crlf
260+
<> "aGFza2VsbA==" <> crlf
261+
<> boundary <> "--" <> crlf
262+
263+
bodyUnique = BSLC.pack $
264+
boundary <> crlf
265+
<> "Content-Disposition: form-data; name=\"names\"" <> crlf <> crlf
266+
<> "file1.pdf; file2.pdf" <> crlf
267+
<> boundary <> crlf
268+
<> "Content-Disposition: form-data; name=\"file1\"; filename=\"file1.pdf\"" <> crlf
269+
<> "Content-Type: application/pdf" <> crlf
270+
<> "Content-Transfer-Encoding: base64" <> crlf <> crlf
271+
<> "aGFza2VsbA==" <> crlf
272+
<> boundary <> crlf
273+
<> "Content-Disposition: form-data; name=\"file2\"; filename=\"file2.pdf\"" <> crlf
274+
<> "Content-Type: application/pdf" <> crlf
275+
<> "Content-Transfer-Encoding: base64" <> crlf <> crlf
276+
<> "c3BvY2s=" <> crlf
277+
<> boundary <> "--" <> crlf
278+
279+
bodyMulti = BSLC.pack $
280+
boundary <> crlf
281+
<> "Content-Disposition: form-data; name=\"name1\"" <> crlf <> crlf
282+
<> "file1.pdf" <> crlf
283+
<> "Content-Disposition: form-data; name=\"name2\"" <> crlf <> crlf
284+
<> "file2.pdf" <> crlf
285+
<> boundary <> crlf
286+
<> "Content-Disposition: form-data; name=\"file\"; filename=\"file1.pdf\"" <> crlf
287+
<> "Content-Type: application/pdf" <> crlf
288+
<> "Content-Transfer-Encoding: base64" <> crlf <> crlf
289+
<> "aGFza2VsbA==" <> crlf
290+
<> boundary <> crlf
291+
<> "Content-Disposition: form-data; name=\"file\"; filename=\"file2.pdf\"" <> crlf
292+
<> "Content-Type: application/pdf" <> crlf
293+
<> "Content-Transfer-Encoding: base64" <> crlf <> crlf
294+
<> "c3BvY2s=" <> crlf
295+
<> boundary <> "--" <> crlf
296+
297+
boundary :: String
298+
boundary = "--__boundary"
299+
300+
crlf :: String
301+
crlf = "\r\n"
302+
303+
headers :: [Header]
304+
headers = [ mkHeader "Content-Type" "multipart/form-data; boundary=__boundary"
305+
, mkHeader "Accept-Encoding" "gzip"
306+
]
307+
308+
mkHeader :: HeaderName -> BS.ByteString -> Header
309+
mkHeader key val = (key, val)

Spock-core/test/Web/Spock/SafeSpec.hs

+10
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import Data.Aeson
2222
import qualified Data.ByteString.Lazy.Char8 as BSLC
2323
import qualified Data.Text as T
2424
import qualified Data.Text.Encoding as T
25+
import qualified Data.HashMap.Strict as HM
2526
import GHC.Generics
2627
import Network.HTTP.Types.Status
2728
import qualified Network.Wai as Wai
@@ -114,6 +115,15 @@ app =
114115
hookAnyCustom "MYVERB" $ text . T.intercalate "/"
115116
get ("wai" <//> wildcard) $ \_ ->
116117
respondApp dummyWai
118+
post "file/upload" $
119+
do
120+
f <- files
121+
text (T.pack $ show $ HM.size f)
122+
post "file/upload/multi" $
123+
do
124+
f <- files
125+
let uploadFiles = f HM.! "file"
126+
text (T.pack $ show $ length $ uploadFiles)
117127

118128
dummyWai :: Wai.Application
119129
dummyWai req respond =

0 commit comments

Comments
 (0)