Skip to content

Commit 473dd5e

Browse files
committed
Unwrap if necessary for MultipartHttpServletRequest
Before this commit RequestPartServletServerHttpRequest simply did an instanceof check for MultipartHttpServletRequest. That hasn't failed because request wrapping typically happens in filters before the DispatcherServlet calls the MultipartResolver. With Spring MVC Test and the Spring Security integraiton however, this order is reversed since there we prepare the multipart request upfront, i.e. there is no actual parsing. The commit unwraps the request if necessary. Issue: SPR-13317
1 parent 22948bd commit 473dd5e

File tree

3 files changed

+96
-16
lines changed

3 files changed

+96
-16
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2013 the original author or authors.
2+
* Copyright 2002-2015 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,44 +17,102 @@
1717
package org.springframework.test.web.servlet.samples.standalone;
1818

1919
import java.io.IOException;
20+
import java.nio.charset.Charset;
21+
import java.util.Collections;
22+
import java.util.Map;
23+
import javax.servlet.Filter;
24+
import javax.servlet.FilterChain;
25+
import javax.servlet.ServletException;
26+
import javax.servlet.http.HttpServletRequest;
27+
import javax.servlet.http.HttpServletRequestWrapper;
28+
import javax.servlet.http.HttpServletResponse;
2029

2130
import org.junit.Test;
2231

2332
import org.springframework.mock.web.MockMultipartFile;
2433
import org.springframework.stereotype.Controller;
34+
import org.springframework.test.web.servlet.MockMvc;
2535
import org.springframework.ui.Model;
2636
import org.springframework.web.bind.annotation.RequestMapping;
2737
import org.springframework.web.bind.annotation.RequestMethod;
2838
import org.springframework.web.bind.annotation.RequestParam;
39+
import org.springframework.web.bind.annotation.RequestPart;
40+
import org.springframework.web.filter.OncePerRequestFilter;
2941
import org.springframework.web.multipart.MultipartFile;
3042

31-
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
32-
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
33-
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.*;
43+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.fileUpload;
44+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
45+
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standaloneSetup;
3446

3547
/**
3648
* @author Rossen Stoyanchev
3749
*/
3850
public class FileUploadControllerTests {
3951

52+
private static final Charset CHARSET = Charset.forName("UTF-8");
53+
54+
4055
@Test
41-
public void readString() throws Exception {
42-
MockMultipartFile file = new MockMultipartFile("file", "orig", null, "bar".getBytes());
43-
standaloneSetup(new FileUploadController()).build()
44-
.perform(fileUpload("/fileupload").file(file))
45-
.andExpect(model().attribute("message", "File 'orig' uploaded successfully"));
56+
public void multipartRequest() throws Exception {
57+
58+
byte[] fileContent = "bar".getBytes(CHARSET);
59+
MockMultipartFile filePart = new MockMultipartFile("file", "orig", null, fileContent);
60+
61+
byte[] json = "{\"name\":\"yeeeah\"}".getBytes(CHARSET);
62+
MockMultipartFile jsonPart = new MockMultipartFile("json", "json", "application/json", json);
63+
64+
MockMvc mockMvc = standaloneSetup(new MultipartController()).build();
65+
mockMvc.perform(fileUpload("/test").file(filePart).file(jsonPart))
66+
.andExpect(model().attribute("fileContent", fileContent))
67+
.andExpect(model().attribute("jsonContent", Collections.singletonMap("name", "yeeeah")));
4668
}
4769

70+
// SPR-13317
71+
72+
@Test
73+
public void multipartRequestWrapped() throws Exception {
74+
75+
byte[] json = "{\"name\":\"yeeeah\"}".getBytes(CHARSET);
76+
MockMultipartFile jsonPart = new MockMultipartFile("json", "json", "application/json", json);
77+
78+
Filter filter = new RequestWrappingFilter();
79+
MockMvc mockMvc = standaloneSetup(new MultipartController()).addFilter(filter).build();
80+
81+
Map<String, String> jsonMap = Collections.singletonMap("name", "yeeeah");
82+
mockMvc.perform(fileUpload("/testJson").file(jsonPart)).andExpect(model().attribute("json", jsonMap));
83+
}
4884

85+
86+
@SuppressWarnings("unused")
4987
@Controller
50-
private static class FileUploadController {
88+
private static class MultipartController {
89+
90+
@RequestMapping(value = "/test", method = RequestMethod.POST)
91+
public String processMultipart(@RequestParam MultipartFile file,
92+
@RequestPart Map<String, String> json, Model model) throws IOException {
93+
94+
model.addAttribute("jsonContent", json);
95+
model.addAttribute("fileContent", file.getBytes());
96+
97+
return "redirect:/index";
98+
}
5199

52-
@RequestMapping(value="/fileupload", method=RequestMethod.POST)
53-
public String processUpload(@RequestParam MultipartFile file, Model model) throws IOException {
54-
model.addAttribute("message", "File '" + file.getOriginalFilename() + "' uploaded successfully");
100+
@RequestMapping(value = "/testJson", method = RequestMethod.POST)
101+
public String processMultipart(@RequestPart Map<String, String> json, Model model) {
102+
model.addAttribute("json", json);
55103
return "redirect:/index";
56104
}
105+
}
106+
107+
private static class RequestWrappingFilter extends OncePerRequestFilter {
57108

109+
@Override
110+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
111+
FilterChain filterChain) throws IOException, ServletException {
112+
113+
request = new HttpServletRequestWrapper(request);
114+
filterChain.doFilter(request, response);
115+
}
58116
}
59117

60-
}
118+
}

spring-web/src/main/java/org/springframework/web/multipart/support/RequestPartServletServerHttpRequest.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
import org.springframework.web.multipart.MultipartFile;
3232
import org.springframework.web.multipart.MultipartHttpServletRequest;
3333
import org.springframework.web.multipart.MultipartResolver;
34+
import org.springframework.web.util.WebUtils;
35+
3436

3537
/**
3638
* {@link ServerHttpRequest} implementation that accesses one part of a multipart
@@ -81,8 +83,9 @@ public RequestPartServletServerHttpRequest(HttpServletRequest request, String pa
8183
}
8284

8385
private static MultipartHttpServletRequest asMultipartRequest(HttpServletRequest request) {
84-
if (request instanceof MultipartHttpServletRequest) {
85-
return (MultipartHttpServletRequest) request;
86+
MultipartHttpServletRequest unwrapped = WebUtils.getNativeRequest(request, MultipartHttpServletRequest.class);
87+
if (unwrapped != null) {
88+
return unwrapped;
8689
}
8790
else if (ClassUtils.hasMethod(HttpServletRequest.class, "getParts")) {
8891
// Servlet 3.0 available ..
@@ -91,11 +94,13 @@ else if (ClassUtils.hasMethod(HttpServletRequest.class, "getParts")) {
9194
throw new IllegalArgumentException("Expected MultipartHttpServletRequest: is a MultipartResolver configured?");
9295
}
9396

97+
9498
@Override
9599
public HttpHeaders getHeaders() {
96100
return this.headers;
97101
}
98102

103+
99104
@Override
100105
public InputStream getBody() throws IOException {
101106
if (this.multipartRequest instanceof StandardMultipartHttpServletRequest) {

spring-web/src/test/java/org/springframework/web/multipart/support/RequestPartServletServerHttpRequestTests.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import java.net.URI;
2020
import java.nio.charset.Charset;
2121

22+
import javax.servlet.http.HttpServletRequest;
23+
import javax.servlet.http.HttpServletRequestWrapper;
24+
2225
import org.junit.Test;
2326

2427
import org.springframework.http.HttpHeaders;
@@ -86,6 +89,20 @@ public void getBody() throws Exception {
8689
assertArrayEquals(bytes, result);
8790
}
8891

92+
// SPR-13317
93+
94+
@Test
95+
public void getBodyWithWrappedRequest() throws Exception {
96+
byte[] bytes = "content".getBytes("UTF-8");
97+
MultipartFile part = new MockMultipartFile("part", "", "application/json", bytes);
98+
this.mockRequest.addFile(part);
99+
HttpServletRequest wrapped = new HttpServletRequestWrapper(this.mockRequest);
100+
ServerHttpRequest request = new RequestPartServletServerHttpRequest(wrapped, "part");
101+
102+
byte[] result = FileCopyUtils.copyToByteArray(request.getBody());
103+
assertArrayEquals(bytes, result);
104+
}
105+
89106
// SPR-13096
90107

91108
@Test

0 commit comments

Comments
 (0)