Skip to content

Commit f67b106

Browse files
committed
Merge branch '4.3.x'
2 parents 75c5b42 + 745c6e3 commit f67b106

File tree

3 files changed

+136
-8
lines changed

3 files changed

+136
-8
lines changed

spring-cloud-gateway-server-webmvc/src/main/java/org/springframework/cloud/gateway/server/mvc/common/MvcUtils.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.io.InputStream;
2222
import java.io.UncheckedIOException;
2323
import java.net.URI;
24+
import java.net.URLDecoder;
25+
import java.nio.charset.Charset;
2426
import java.nio.charset.StandardCharsets;
2527
import java.util.Arrays;
2628
import java.util.Collection;
@@ -40,11 +42,13 @@
4042
import org.springframework.http.HttpHeaders;
4143
import org.springframework.http.HttpInputMessage;
4244
import org.springframework.http.converter.HttpMessageConverter;
45+
import org.springframework.lang.Nullable;
4346
import org.springframework.util.Assert;
4447
import org.springframework.util.CollectionUtils;
4548
import org.springframework.util.LinkedMultiValueMap;
4649
import org.springframework.util.MultiValueMap;
4750
import org.springframework.util.StreamUtils;
51+
import org.springframework.util.StringUtils;
4852
import org.springframework.web.context.WebApplicationContext;
4953
import org.springframework.web.servlet.function.ServerRequest;
5054
import org.springframework.web.servlet.support.RequestContextUtils;
@@ -284,6 +288,23 @@ public static MultiValueMap<String, String> encodeQueryParams(MultiValueMap<Stri
284288
return CollectionUtils.unmodifiableMultiValueMap(encodedQueryParams);
285289
}
286290

291+
public static MultiValueMap<String, String> decodeQueryString(@Nullable String queryString, Charset charset) {
292+
String[] pairs = StringUtils.tokenizeToStringArray(queryString, "&");
293+
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(pairs.length);
294+
for (String pair : pairs) {
295+
int idx = pair.indexOf('=');
296+
if (idx == -1) {
297+
result.add(URLDecoder.decode(pair, charset), null);
298+
}
299+
else {
300+
String name = URLDecoder.decode(pair.substring(0, idx), charset);
301+
String value = URLDecoder.decode(pair.substring(idx + 1), charset);
302+
result.add(name, value);
303+
}
304+
}
305+
return result;
306+
}
307+
287308
private record ByteArrayInputMessage(ServerRequest request, ByteArrayInputStream body) implements HttpInputMessage {
288309

289310
@Override

spring-cloud-gateway-server-webmvc/src/main/java/org/springframework/cloud/gateway/server/mvc/filter/FormFilter.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@
4242
import jakarta.servlet.http.HttpServletRequest;
4343
import jakarta.servlet.http.HttpServletRequestWrapper;
4444

45+
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
4546
import org.springframework.core.Ordered;
4647
import org.springframework.http.HttpMethod;
4748
import org.springframework.http.MediaType;
4849
import org.springframework.lang.Nullable;
4950
import org.springframework.util.MultiValueMap;
5051
import org.springframework.util.StringUtils;
51-
import org.springframework.web.util.UriComponentsBuilder;
5252

5353
/**
5454
* Filter that rebuilds the body for form urlencoded posts. Serlvets treat query
@@ -113,13 +113,7 @@ static HttpServletRequest getRequestWithBodyFromRequestParameters(HttpServletReq
113113
Writer writer = new OutputStreamWriter(bos, FORM_CHARSET);
114114

115115
Map<String, String[]> form = request.getParameterMap();
116-
String queryString = request.getQueryString();
117-
StringBuffer requestURL = request.getRequestURL();
118-
if (StringUtils.hasText(queryString)) {
119-
requestURL.append('?').append(queryString);
120-
}
121-
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUriString(requestURL.toString());
122-
MultiValueMap<String, String> queryParams = uriComponentsBuilder.build().getQueryParams();
116+
MultiValueMap<String, String> queryParams = MvcUtils.decodeQueryString(request.getQueryString(), FORM_CHARSET);
123117
for (Iterator<Map.Entry<String, String[]>> entryIterator = form.entrySet().iterator(); entryIterator
124118
.hasNext();) {
125119
Map.Entry<String, String[]> entry = entryIterator.next();
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright 2025-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.cloud.gateway.server.mvc.filter;
18+
19+
import java.io.IOException;
20+
import java.net.URI;
21+
import java.net.URLDecoder;
22+
import java.nio.charset.StandardCharsets;
23+
import java.util.ArrayList;
24+
import java.util.Enumeration;
25+
import java.util.List;
26+
27+
import jakarta.servlet.FilterChain;
28+
import jakarta.servlet.ServletException;
29+
import jakarta.servlet.ServletRequest;
30+
import jakarta.servlet.http.HttpServletRequest;
31+
import jakarta.servlet.http.HttpServletResponse;
32+
import org.junit.jupiter.api.Test;
33+
import org.mockito.ArgumentCaptor;
34+
import org.mockito.Mockito;
35+
36+
import org.springframework.mock.web.MockHttpServletRequest;
37+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
38+
import org.springframework.util.LinkedMultiValueMap;
39+
import org.springframework.util.MultiValueMap;
40+
import org.springframework.util.StreamUtils;
41+
import org.springframework.util.StringUtils;
42+
43+
import static org.assertj.core.api.Assertions.assertThat;
44+
import static org.mockito.Mockito.verify;
45+
46+
/**
47+
* @author shawyeok
48+
*/
49+
class FormFilterTests {
50+
51+
@Test
52+
void hideFormParameterFromParameterMap() throws ServletException, IOException {
53+
FormFilter filter = new FormFilter();
54+
MockHttpServletRequest request = MockMvcRequestBuilders
55+
.post(URI.create("http://localhost/test?queryArg1=foo&queryArg2=%E4%BD%A0%E5%A5%BD"))
56+
.contentType("application/x-www-form-urlencoded")
57+
.content("formArg1=bar&formArg2=%7B%7D")
58+
.buildRequest(null);
59+
HttpServletResponse response = Mockito.mock(HttpServletResponse.class);
60+
FilterChain chain = Mockito.mock(FilterChain.class);
61+
filter.doFilter(request, response, chain);
62+
63+
ArgumentCaptor<ServletRequest> captor = ArgumentCaptor.forClass(ServletRequest.class);
64+
verify(chain).doFilter(captor.capture(), Mockito.eq(response));
65+
HttpServletRequest servletRequest = (HttpServletRequest) captor.getValue();
66+
assertThat(servletRequest.getParameter("queryArg1")).isEqualTo("foo");
67+
assertThat(servletRequest.getParameterValues("queryArg1")).containsExactly("foo");
68+
// "你好" is hello in Chinese
69+
assertThat(servletRequest.getParameterValues("queryArg2")).containsExactly("你好");
70+
assertThat(servletRequest.getParameter("queryArg2")).isEqualTo("你好");
71+
assertThat(servletRequest.getParameter("formArg1")).isNull();
72+
assertThat(servletRequest.getParameter("formArg2")).isNull();
73+
assertThat(servletRequest.getParameterMap().size()).isEqualTo(2);
74+
assertThat(toList(servletRequest.getParameterNames())).containsExactly("queryArg1", "queryArg2");
75+
assertThat(servletRequest.getHeader("Content-Type")).isEqualTo("application/x-www-form-urlencoded");
76+
MultiValueMap<String, String> form = readForm(servletRequest);
77+
assertThat(form.size()).isEqualTo(2);
78+
assertThat(form.get("formArg1")).containsExactly("bar");
79+
assertThat(form.get("formArg2")).containsExactly("{}");
80+
}
81+
82+
static MultiValueMap<String, String> readForm(HttpServletRequest request) {
83+
try {
84+
String body = StreamUtils.copyToString(request.getInputStream(), StandardCharsets.UTF_8);
85+
String[] pairs = StringUtils.tokenizeToStringArray(body, "&");
86+
MultiValueMap<String, String> result = new LinkedMultiValueMap<>(pairs.length);
87+
for (String pair : pairs) {
88+
int idx = pair.indexOf('=');
89+
if (idx == -1) {
90+
result.add(URLDecoder.decode(pair, StandardCharsets.UTF_8), null);
91+
}
92+
else {
93+
String name = URLDecoder.decode(pair.substring(0, idx), StandardCharsets.UTF_8);
94+
String value = URLDecoder.decode(pair.substring(idx + 1), StandardCharsets.UTF_8);
95+
result.add(name, value);
96+
}
97+
}
98+
return result;
99+
}
100+
catch (IOException e) {
101+
throw new RuntimeException(e);
102+
}
103+
}
104+
105+
static List<String> toList(Enumeration<String> values) {
106+
List<String> list = new ArrayList<>();
107+
while (values.hasMoreElements()) {
108+
list.add(values.nextElement());
109+
}
110+
return list;
111+
}
112+
113+
}

0 commit comments

Comments
 (0)