From 124d4c833c107edcf7287d0833211768e1067120 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Fri, 1 Jun 2018 15:22:24 -0400 Subject: [PATCH] Support for Servlet request params with HTTP DELETE This commit adds FormContentFilter, which is the same as the HttpPutFormContentFilter but also supports DELETE. The HttpPutFormContentFilter is now deprecated. Issue: SPR-16874 --- .../servlet/samples/spr/FormContentTests.java | 8 +- .../web/filter/FormContentFilter.java | 188 +++++++++++++++ .../web/filter/HttpPutFormContentFilter.java | 5 +- .../web/filter/FormContentFilterTests.java | 218 ++++++++++++++++++ .../filter/HttpPutFormContentFilterTests.java | 218 ------------------ src/docs/asciidoc/web/webmvc.adoc | 8 +- 6 files changed, 418 insertions(+), 227 deletions(-) create mode 100644 spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.java create mode 100644 spring-web/src/test/java/org/springframework/web/filter/FormContentFilterTests.java delete mode 100644 spring-web/src/test/java/org/springframework/web/filter/HttpPutFormContentFilterTests.java diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/FormContentTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/FormContentTests.java index 79aadd5901..824ed9bdef 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/FormContentTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/spr/FormContentTests.java @@ -23,10 +23,10 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.filter.HttpPutFormContentFilter; +import org.springframework.web.filter.FormContentFilter; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; /** * Test for issues related to form content. @@ -39,7 +39,7 @@ public class FormContentTests { public void formContentIsNotDuplicated() throws Exception { MockMvc mockMvc = MockMvcBuilders.standaloneSetup(new Spr15753Controller()) - .addFilter(new HttpPutFormContentFilter()) + .addFilter(new FormContentFilter()) .build(); mockMvc.perform(put("/").content("d1=a&d2=s").contentType(MediaType.APPLICATION_FORM_URLENCODED)) diff --git a/spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.java b/spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.java new file mode 100644 index 0000000000..9e6ef16586 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/filter/FormContentFilter.java @@ -0,0 +1,188 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.filter; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Enumeration; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.http.HttpInputMessage; +import org.springframework.http.MediaType; +import org.springframework.http.converter.FormHttpMessageConverter; +import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; +import org.springframework.http.server.ServletServerHttpRequest; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; + +/** + * {@code Filter} that parses form data for HTTP PUT, PATCH, and DELETE requests + * and exposes it as Servlet request parameters. By default the Servlet spec + * only requires this for HTTP POST. + * + * @author Rossen Stoyanchev + * @since 5.1 + */ +public class FormContentFilter extends OncePerRequestFilter { + + private static final List HTTP_METHODS = Arrays.asList("PUT", "PATCH", "DELETE"); + + + private FormHttpMessageConverter formConverter = new AllEncompassingFormHttpMessageConverter(); + + + /** + * Set the converter to use for parsing form content. + *

By default this is an instance of {@link AllEncompassingFormHttpMessageConverter}. + */ + public void setFormConverter(FormHttpMessageConverter converter) { + Assert.notNull(converter, "FormHttpMessageConverter is required."); + this.formConverter = converter; + } + + public FormHttpMessageConverter getFormConverter() { + return this.formConverter; + } + + /** + * The default character set to use for reading form data. + * This is a shortcut for:
+ * {@code getFormConverter.setCharset(charset)}. + */ + public void setCharset(Charset charset) { + this.formConverter.setCharset(charset); + } + + + @Override + protected void doFilterInternal(final HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + MultiValueMap params = parseIfNecessary(request); + + if (params != null && !params.isEmpty()) { + filterChain.doFilter(new FormContentRequestWrapper(request, params), response); + } + else { + filterChain.doFilter(request, response); + } + } + + @Nullable + private MultiValueMap parseIfNecessary(HttpServletRequest request) throws IOException { + + if (!shouldParse(request)) { + return null; + } + + HttpInputMessage inputMessage = new ServletServerHttpRequest(request) { + + @Override + public InputStream getBody() throws IOException { + return request.getInputStream(); + } + }; + + return this.formConverter.read(null, inputMessage); + } + + private boolean shouldParse(HttpServletRequest request) { + if (!HTTP_METHODS.contains(request.getMethod())) { + return false; + } + try { + MediaType mediaType = MediaType.parseMediaType(request.getContentType()); + return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType); + } + catch (IllegalArgumentException ex) { + return false; + } + } + + + private static class FormContentRequestWrapper extends HttpServletRequestWrapper { + + private MultiValueMap formParams; + + public FormContentRequestWrapper(HttpServletRequest request, MultiValueMap params) { + super(request); + this.formParams = params; + } + + @Override + @Nullable + public String getParameter(String name) { + String queryStringValue = super.getParameter(name); + String formValue = this.formParams.getFirst(name); + return (queryStringValue != null ? queryStringValue : formValue); + } + + @Override + public Map getParameterMap() { + Map result = new LinkedHashMap<>(); + Enumeration names = getParameterNames(); + while (names.hasMoreElements()) { + String name = names.nextElement(); + result.put(name, getParameterValues(name)); + } + return result; + } + + @Override + public Enumeration getParameterNames() { + Set names = new LinkedHashSet<>(); + names.addAll(Collections.list(super.getParameterNames())); + names.addAll(this.formParams.keySet()); + return Collections.enumeration(names); + } + + @Override + @Nullable + public String[] getParameterValues(String name) { + String[] parameterValues = super.getParameterValues(name); + List formParam = this.formParams.get(name); + if (formParam == null) { + return parameterValues; + } + if (parameterValues == null || getQueryString() == null) { + return StringUtils.toStringArray(formParam); + } + else { + List result = new ArrayList<>(parameterValues.length + formParam.size()); + result.addAll(Arrays.asList(parameterValues)); + result.addAll(formParam); + return StringUtils.toStringArray(result); + } + } + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/filter/HttpPutFormContentFilter.java b/spring-web/src/main/java/org/springframework/web/filter/HttpPutFormContentFilter.java index 4e7dc13a81..fae70574c2 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/HttpPutFormContentFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/HttpPutFormContentFilter.java @@ -58,7 +58,10 @@ import org.springframework.util.StringUtils; * * @author Rossen Stoyanchev * @since 3.1 + * @deprecated as of 5.1 in favor of {@link FormContentFilter} which is the same + * but also handles DELETE. */ +@Deprecated public class HttpPutFormContentFilter extends OncePerRequestFilter { private FormHttpMessageConverter formConverter = new AllEncompassingFormHttpMessageConverter(); @@ -66,7 +69,7 @@ public class HttpPutFormContentFilter extends OncePerRequestFilter { /** * Set the converter to use for parsing form content. - *

By default this is an instnace of {@link AllEncompassingFormHttpMessageConverter}. + *

By default this is an instance of {@link AllEncompassingFormHttpMessageConverter}. */ public void setFormConverter(FormHttpMessageConverter converter) { Assert.notNull(converter, "FormHttpMessageConverter is required."); diff --git a/spring-web/src/test/java/org/springframework/web/filter/FormContentFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/FormContentFilterTests.java new file mode 100644 index 0000000000..c15cd11f88 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/web/filter/FormContentFilterTests.java @@ -0,0 +1,218 @@ +/* + * Copyright 2002-2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.filter; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.http.HttpMethod; +import org.springframework.mock.web.test.MockFilterChain; +import org.springframework.mock.web.test.MockHttpServletRequest; +import org.springframework.mock.web.test.MockHttpServletResponse; + +import static org.junit.Assert.*; + +/** + * Test fixture for {@link FormContentFilter}. + * + * @author Rossen Stoyanchev + */ +public class FormContentFilterTests { + + private final FormContentFilter filter = new FormContentFilter(); + + private MockHttpServletRequest request; + + private MockHttpServletResponse response; + + private MockFilterChain filterChain; + + + @Before + public void setup() { + this.request = new MockHttpServletRequest("PUT", "/"); + this.request.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=ISO-8859-1"); + this.request.setContentType("application/x-www-form-urlencoded; charset=ISO-8859-1"); + this.response = new MockHttpServletResponse(); + this.filterChain = new MockFilterChain(); + } + + + @Test + public void wrapPutPatchAndDeleteOnly() throws Exception { + this.request.setContent("foo=bar".getBytes("ISO-8859-1")); + for (HttpMethod method : HttpMethod.values()) { + this.request.setMethod(method.name()); + this.filterChain = new MockFilterChain(); + this.filter.doFilter(this.request, this.response, this.filterChain); + if (method == HttpMethod.PUT || method == HttpMethod.PATCH || method == HttpMethod.DELETE) { + assertNotSame(this.request, this.filterChain.getRequest()); + } + else { + assertSame(this.request, this.filterChain.getRequest()); + } + } + } + + @Test + public void wrapFormEncodedOnly() throws Exception { + this.request.setContent("".getBytes("ISO-8859-1")); + String[] contentTypes = new String[] {"text/plain", "multipart/form-data"}; + for (String contentType : contentTypes) { + this.request.setContentType(contentType); + this.filterChain = new MockFilterChain(); + this.filter.doFilter(this.request, this.response, this.filterChain); + assertSame(this.request, this.filterChain.getRequest()); + } + } + + @Test + public void invalidMediaType() throws Exception { + this.request.setContent("".getBytes("ISO-8859-1")); + this.request.setContentType("foo"); + this.filterChain = new MockFilterChain(); + this.filter.doFilter(this.request, this.response, this.filterChain); + assertSame(this.request, this.filterChain.getRequest()); + } + + @Test + public void getParameter() throws Exception { + this.request.setContent("name=value".getBytes("ISO-8859-1")); + this.filter.doFilter(this.request, this.response, this.filterChain); + + assertEquals("value", this.filterChain.getRequest().getParameter("name")); + } + + @Test + public void getParameterFromQueryString() throws Exception { + this.request.addParameter("name", "value1"); + this.request.setContent("name=value2".getBytes("ISO-8859-1")); + this.filter.doFilter(this.request, this.response, this.filterChain); + + assertNotSame("Request not wrapped", this.request, this.filterChain.getRequest()); + assertEquals("Query string parameters should be listed ahead of form parameters", + "value1", this.filterChain.getRequest().getParameter("name")); + } + + @Test + public void getParameterNullValue() throws Exception { + this.request.setContent("name=value".getBytes("ISO-8859-1")); + this.filter.doFilter(this.request, this.response, this.filterChain); + + assertNotSame("Request not wrapped", this.request, this.filterChain.getRequest()); + assertNull(this.filterChain.getRequest().getParameter("noSuchParam")); + } + + @Test + public void getParameterNames() throws Exception { + this.request.addParameter("name1", "value1"); + this.request.addParameter("name2", "value2"); + this.request.setContent("name1=value1&name3=value3&name4=value4".getBytes("ISO-8859-1")); + + this.filter.doFilter(this.request, this.response, this.filterChain); + List names = Collections.list(this.filterChain.getRequest().getParameterNames()); + + assertNotSame("Request not wrapped", this.request, this.filterChain.getRequest()); + assertEquals(Arrays.asList("name1", "name2", "name3", "name4"), names); + } + + @Test + public void getParameterValues() throws Exception { + this.request.setQueryString("name=value1&name=value2"); + this.request.addParameter("name", "value1"); + this.request.addParameter("name", "value2"); + this.request.setContent("name=value3&name=value4".getBytes("ISO-8859-1")); + + this.filter.doFilter(this.request, this.response, this.filterChain); + String[] values = this.filterChain.getRequest().getParameterValues("name"); + + assertNotSame("Request not wrapped", this.request, filterChain.getRequest()); + assertArrayEquals(new String[]{"value1", "value2", "value3", "value4"}, values); + } + + @Test + public void getParameterValuesFromQueryString() throws Exception { + this.request.setQueryString("name=value1&name=value2"); + this.request.addParameter("name", "value1"); + this.request.addParameter("name", "value2"); + this.request.setContent("anotherName=anotherValue".getBytes("ISO-8859-1")); + + this.filter.doFilter(this.request, this.response, this.filterChain); + String[] values = this.filterChain.getRequest().getParameterValues("name"); + + assertNotSame("Request not wrapped", this.request, this.filterChain.getRequest()); + assertArrayEquals(new String[]{"value1", "value2"}, values); + } + + @Test + public void getParameterValuesFromFormContent() throws Exception { + this.request.addParameter("name", "value1"); + this.request.addParameter("name", "value2"); + this.request.setContent("anotherName=anotherValue".getBytes("ISO-8859-1")); + + this.filter.doFilter(this.request, this.response, this.filterChain); + String[] values = this.filterChain.getRequest().getParameterValues("anotherName"); + + assertNotSame("Request not wrapped", this.request, this.filterChain.getRequest()); + assertArrayEquals(new String[]{"anotherValue"}, values); + } + + @Test + public void getParameterValuesInvalidName() throws Exception { + this.request.addParameter("name", "value1"); + this.request.addParameter("name", "value2"); + this.request.setContent("anotherName=anotherValue".getBytes("ISO-8859-1")); + + this.filter.doFilter(this.request, this.response, this.filterChain); + String[] values = this.filterChain.getRequest().getParameterValues("noSuchParameter"); + + assertNotSame("Request not wrapped", this.request, this.filterChain.getRequest()); + assertNull(values); + } + + @Test + public void getParameterMap() throws Exception { + this.request.setQueryString("name=value1&name=value2"); + this.request.addParameter("name", "value1"); + this.request.addParameter("name", "value2"); + this.request.setContent("name=value3&name4=value4".getBytes("ISO-8859-1")); + + this.filter.doFilter(this.request, this.response, this.filterChain); + Map parameters = this.filterChain.getRequest().getParameterMap(); + + assertNotSame("Request not wrapped", this.request, this.filterChain.getRequest()); + assertEquals(2, parameters.size()); + assertArrayEquals(new String[] {"value1", "value2", "value3"}, parameters.get("name")); + assertArrayEquals(new String[] {"value4"}, parameters.get("name4")); + } + + @Test // SPR-15835 + public void hiddenHttpMethodFilterFollowedByHttpPutFormContentFilter() throws Exception { + this.request.addParameter("_method", "PUT"); + this.request.addParameter("hiddenField", "testHidden"); + this.filter.doFilter(this.request, this.response, this.filterChain); + + assertArrayEquals(new String[]{"testHidden"}, + this.filterChain.getRequest().getParameterValues("hiddenField")); + } + +} diff --git a/spring-web/src/test/java/org/springframework/web/filter/HttpPutFormContentFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/HttpPutFormContentFilterTests.java deleted file mode 100644 index 4f900b6b5a..0000000000 --- a/spring-web/src/test/java/org/springframework/web/filter/HttpPutFormContentFilterTests.java +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.web.filter; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.junit.Before; -import org.junit.Test; - -import org.springframework.http.HttpMethod; -import org.springframework.mock.web.test.MockFilterChain; -import org.springframework.mock.web.test.MockHttpServletRequest; -import org.springframework.mock.web.test.MockHttpServletResponse; - -import static org.junit.Assert.*; - -/** - * Test fixture for {@link HttpPutFormContentFilter}. - * - * @author Rossen Stoyanchev - */ -public class HttpPutFormContentFilterTests { - - private HttpPutFormContentFilter filter; - - private MockHttpServletRequest request; - - private MockHttpServletResponse response; - - private MockFilterChain filterChain; - - - @Before - public void setup() { - filter = new HttpPutFormContentFilter(); - request = new MockHttpServletRequest("PUT", "/"); - request.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=ISO-8859-1"); - request.setContentType("application/x-www-form-urlencoded; charset=ISO-8859-1"); - response = new MockHttpServletResponse(); - filterChain = new MockFilterChain(); - } - - - @Test - public void wrapPutAndPatchOnly() throws Exception { - request.setContent("foo=bar".getBytes("ISO-8859-1")); - for (HttpMethod method : HttpMethod.values()) { - request.setMethod(method.name()); - filterChain = new MockFilterChain(); - filter.doFilter(request, response, filterChain); - if (method == HttpMethod.PUT || method == HttpMethod.PATCH) { - assertNotSame("Should wrap HTTP method " + method, request, filterChain.getRequest()); - } - else { - assertSame("Should not wrap for HTTP method " + method, request, filterChain.getRequest()); - } - } - } - - @Test - public void wrapFormEncodedOnly() throws Exception { - request.setContent("".getBytes("ISO-8859-1")); - String[] contentTypes = new String[] {"text/plain", "multipart/form-data"}; - for (String contentType : contentTypes) { - request.setContentType(contentType); - filterChain = new MockFilterChain(); - filter.doFilter(request, response, filterChain); - assertSame("Should not wrap for content type " + contentType, request, filterChain.getRequest()); - } - } - - @Test - public void invalidMediaType() throws Exception { - request.setContent("".getBytes("ISO-8859-1")); - request.setContentType("foo"); - filterChain = new MockFilterChain(); - filter.doFilter(request, response, filterChain); - assertSame(request, filterChain.getRequest()); - } - - @Test - public void getParameter() throws Exception { - request.setContent("name=value".getBytes("ISO-8859-1")); - filter.doFilter(request, response, filterChain); - - assertEquals("value", filterChain.getRequest().getParameter("name")); - } - - @Test - public void getParameterFromQueryString() throws Exception { - request.addParameter("name", "value1"); - request.setContent("name=value2".getBytes("ISO-8859-1")); - filter.doFilter(request, response, filterChain); - - assertNotSame("Request not wrapped", request, filterChain.getRequest()); - assertEquals("Query string parameters should be listed ahead of form parameters", - "value1", filterChain.getRequest().getParameter("name")); - } - - @Test - public void getParameterNullValue() throws Exception { - request.setContent("name=value".getBytes("ISO-8859-1")); - filter.doFilter(request, response, filterChain); - - assertNotSame("Request not wrapped", request, filterChain.getRequest()); - assertNull(filterChain.getRequest().getParameter("noSuchParam")); - } - - @Test - public void getParameterNames() throws Exception { - request.addParameter("name1", "value1"); - request.addParameter("name2", "value2"); - request.setContent("name1=value1&name3=value3&name4=value4".getBytes("ISO-8859-1")); - - filter.doFilter(request, response, filterChain); - List names = Collections.list(filterChain.getRequest().getParameterNames()); - - assertNotSame("Request not wrapped", request, filterChain.getRequest()); - assertEquals(Arrays.asList("name1", "name2", "name3", "name4"), names); - } - - @Test - public void getParameterValues() throws Exception { - request.setQueryString("name=value1&name=value2"); - request.addParameter("name", "value1"); - request.addParameter("name", "value2"); - request.setContent("name=value3&name=value4".getBytes("ISO-8859-1")); - - filter.doFilter(request, response, filterChain); - String[] values = filterChain.getRequest().getParameterValues("name"); - - assertNotSame("Request not wrapped", request, filterChain.getRequest()); - assertArrayEquals(new String[]{"value1", "value2", "value3", "value4"}, values); - } - - @Test - public void getParameterValuesFromQueryString() throws Exception { - request.setQueryString("name=value1&name=value2"); - request.addParameter("name", "value1"); - request.addParameter("name", "value2"); - request.setContent("anotherName=anotherValue".getBytes("ISO-8859-1")); - - filter.doFilter(request, response, filterChain); - String[] values = filterChain.getRequest().getParameterValues("name"); - - assertNotSame("Request not wrapped", request, filterChain.getRequest()); - assertArrayEquals(new String[]{"value1", "value2"}, values); - } - - @Test - public void getParameterValuesFromFormContent() throws Exception { - request.addParameter("name", "value1"); - request.addParameter("name", "value2"); - request.setContent("anotherName=anotherValue".getBytes("ISO-8859-1")); - - filter.doFilter(request, response, filterChain); - String[] values = filterChain.getRequest().getParameterValues("anotherName"); - - assertNotSame("Request not wrapped", request, filterChain.getRequest()); - assertArrayEquals(new String[]{"anotherValue"}, values); - } - - @Test - public void getParameterValuesInvalidName() throws Exception { - request.addParameter("name", "value1"); - request.addParameter("name", "value2"); - request.setContent("anotherName=anotherValue".getBytes("ISO-8859-1")); - - filter.doFilter(request, response, filterChain); - String[] values = filterChain.getRequest().getParameterValues("noSuchParameter"); - - assertNotSame("Request not wrapped", request, filterChain.getRequest()); - assertNull(values); - } - - @Test - public void getParameterMap() throws Exception { - request.setQueryString("name=value1&name=value2"); - request.addParameter("name", "value1"); - request.addParameter("name", "value2"); - request.setContent("name=value3&name4=value4".getBytes("ISO-8859-1")); - - filter.doFilter(request, response, filterChain); - Map parameters = filterChain.getRequest().getParameterMap(); - - assertNotSame("Request not wrapped", request, filterChain.getRequest()); - assertEquals(2, parameters.size()); - assertArrayEquals(new String[] {"value1", "value2", "value3"}, parameters.get("name")); - assertArrayEquals(new String[] {"value4"}, parameters.get("name4")); - } - - @Test // SPR-15835 - public void hiddenHttpMethodFilterFollowedByHttpPutFormContentFilter() throws Exception { - request.addParameter("_method", "PUT"); - request.addParameter("hiddenField", "testHidden"); - filter.doFilter(request, response, filterChain); - - assertArrayEquals(new String[]{"testHidden"}, filterChain.getRequest().getParameterValues("hiddenField")); - } - -} diff --git a/src/docs/asciidoc/web/webmvc.adoc b/src/docs/asciidoc/web/webmvc.adoc index ca91c6c0a0..e90224c0b9 100644 --- a/src/docs/asciidoc/web/webmvc.adoc +++ b/src/docs/asciidoc/web/webmvc.adoc @@ -1046,14 +1046,14 @@ The `spring-web` module provides some useful filters. [[filters-http-put]] -=== HTTP PUT Form +=== Form Data Browsers can only submit form data via HTTP GET or HTTP POST but non-browser clients can also -use HTTP PUT and PATCH. The Servlet API requires `ServletRequest.getParameter{asterisk}()` +use HTTP PUT, PATCH, and DELETE. The Servlet API requires `ServletRequest.getParameter{asterisk}()` methods to support form field access only for HTTP POST. -The `spring-web` module provides `HttpPutFormContentFilter` that intercepts HTTP PUT and -PATCH requests with content type `application/x-www-form-urlencoded`, reads the form data from +The `spring-web` module provides `FormContentFilter` that intercepts HTTP PUT, PATCH, and DELETE +requests with content type `application/x-www-form-urlencoded`, reads the form data from the body of the request, and wraps the `ServletRequest` in order to make the form data available through the `ServletRequest.getParameter{asterisk}()` family of methods.