Skip to content

Commit 39b8355

Browse files
committed
fix: preserve query string params for multipart requests in Gateway MVC
Fixes gh-3220 Signed-off-by: Arindam Singh <96876969+ar249@users.noreply.github.com>
1 parent 15bd55a commit 39b8355

2 files changed

Lines changed: 121 additions & 2 deletions

File tree

spring-cloud-gateway-server-webmvc/src/main/java/org/springframework/cloud/gateway/server/mvc/handler/GatewayMvcMultipartResolver.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616

1717
package org.springframework.cloud.gateway.server.mvc.handler;
1818

19-
import java.util.Collections;
19+
import java.nio.charset.StandardCharsets;
2020
import java.util.Map;
21+
import java.util.stream.Collectors;
2122

2223
import jakarta.servlet.http.HttpServletRequest;
2324

2425
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
26+
import org.springframework.util.MultiValueMap;
2527
import org.springframework.web.multipart.MultipartException;
2628
import org.springframework.web.multipart.MultipartHttpServletRequest;
2729
import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest;
@@ -67,7 +69,13 @@ protected void initializeMultipart() {
6769
@Override
6870
public Map<String, String[]> getParameterMap() {
6971
if (isGatewayRequest(getRequest())) {
70-
return Collections.emptyMap();
72+
MultiValueMap<String, String> queryParams =
73+
MvcUtils.decodeQueryString(getRequest().getQueryString(), StandardCharsets.UTF_8);
74+
75+
return queryParams.entrySet()
76+
.stream()
77+
.collect(Collectors.toMap(
78+
Map.Entry::getKey, e -> e.getValue().toArray(new String[0])));
7179
}
7280
return super.getParameterMap();
7381
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package org.springframework.cloud.gateway.server.mvc.handler;
2+
3+
import org.apache.commons.lang3.StringUtils;
4+
import org.junit.jupiter.api.BeforeEach;
5+
import org.junit.jupiter.api.Test;
6+
7+
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
8+
import org.springframework.mock.web.MockHttpServletRequest;
9+
10+
import java.nio.charset.StandardCharsets;
11+
import java.util.Map;
12+
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
import static org.mockito.Mockito.never;
15+
import static org.mockito.Mockito.spy;
16+
import static org.mockito.Mockito.verify;
17+
18+
public class GatewayMvcMultipartResolverTest {
19+
private MockHttpServletRequest mockRequest;
20+
21+
@BeforeEach
22+
public void setUp() {
23+
mockRequest = new MockHttpServletRequest();
24+
mockRequest.setContentType("multipart/form-data; boundary=----boundary");
25+
}
26+
27+
private GatewayMvcMultipartResolver.GatewayMultipartHttpServletRequest buildWrapper() {
28+
return new GatewayMvcMultipartResolver.GatewayMultipartHttpServletRequest(mockRequest);
29+
}
30+
31+
private void makeGatewayRequest() {
32+
mockRequest.setAttribute(MvcUtils.GATEWAY_ROUTE_ID_ATTR, "my-route");
33+
}
34+
35+
@Test
36+
public void getParameterMapGatewayRequestMultipleDistinctParams() {
37+
makeGatewayRequest();
38+
mockRequest.setQueryString("foo=bar&baz=qux");
39+
40+
Map<String, String[]> result = buildWrapper().getParameterMap();
41+
42+
assertThat(result).containsKeys("foo", "baz");
43+
assertThat(result.get("foo")).containsExactly("bar");
44+
assertThat(result.get("baz")).containsExactly("qux");
45+
}
46+
47+
@Test
48+
public void getParameterMapGatewayRequestNullQueryStringReturnsEmptyMap() {
49+
makeGatewayRequest();
50+
mockRequest.setQueryString(null);
51+
52+
Map<String, String[]> result = buildWrapper().getParameterMap();
53+
54+
assertThat(result).isEmpty();
55+
}
56+
57+
@Test
58+
public void getParameterMapGatewayRequestEmptyQueryStringReturnsEmptyMap() {
59+
makeGatewayRequest();
60+
mockRequest.setQueryString(StringUtils.EMPTY);
61+
62+
Map<String, String[]> result = buildWrapper().getParameterMap();
63+
64+
assertThat(result).isEmpty();
65+
}
66+
67+
@Test
68+
public void getParameterMap_gatewayRequest_multipartBodyIsNotParsed() {
69+
makeGatewayRequest();
70+
71+
String body = """
72+
------TestBoundary\r
73+
Content-Disposition: form-data; name="file"; filename="test.txt"\r
74+
Content-Type: text/plain\r
75+
\r
76+
file content here\r
77+
------TestBoundary\r
78+
Content-Disposition: form-data; name="field1"\r
79+
\r
80+
value1\r
81+
------TestBoundary--\r
82+
""";
83+
84+
mockRequest.setContentType("multipart/form-data; boundary=----TestBoundary");
85+
mockRequest.setContent(body.getBytes(StandardCharsets.UTF_8));
86+
mockRequest.setQueryString("queryParam=fromQuery");
87+
88+
GatewayMvcMultipartResolver.GatewayMultipartHttpServletRequest wrapper =
89+
spy(new GatewayMvcMultipartResolver.GatewayMultipartHttpServletRequest(mockRequest));
90+
91+
Map<String, String[]> params = wrapper.getParameterMap();
92+
93+
// multipart parsing isn't triggered
94+
verify(wrapper, never()).initializeMultipart();
95+
96+
// Body parts must not appear — only query string should be visible
97+
assertThat(params).containsOnlyKeys("queryParam");
98+
assertThat(params.get("queryParam")).containsExactly("fromQuery");
99+
assertThat(params).doesNotContainKey("field1").doesNotContainKey("file");
100+
}
101+
102+
@Test
103+
public void getParameterMapNonGatewayRequestNoAttributesDelegatesToSuper() {
104+
mockRequest.setParameter("superKey", "superValue");
105+
106+
Map<String, String[]> result = buildWrapper().getParameterMap();
107+
108+
assertThat(result).containsKey("superKey");
109+
assertThat(result.get("superKey")).containsExactly("superValue");
110+
}
111+
}

0 commit comments

Comments
 (0)