View Javadoc
1   /*
2    * Copyright 2002-2012 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    *      http://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.web.filter;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.nio.charset.Charset;
22  import java.util.ArrayList;
23  import java.util.Arrays;
24  import java.util.Collections;
25  import java.util.Enumeration;
26  import java.util.LinkedHashMap;
27  import java.util.LinkedHashSet;
28  import java.util.List;
29  import java.util.Map;
30  import java.util.Set;
31  import javax.servlet.FilterChain;
32  import javax.servlet.ServletException;
33  import javax.servlet.http.HttpServletRequest;
34  import javax.servlet.http.HttpServletRequestWrapper;
35  import javax.servlet.http.HttpServletResponse;
36  
37  import org.springframework.http.HttpInputMessage;
38  import org.springframework.http.MediaType;
39  import org.springframework.http.converter.FormHttpMessageConverter;
40  import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
41  import org.springframework.http.server.ServletServerHttpRequest;
42  import org.springframework.util.LinkedMultiValueMap;
43  import org.springframework.util.MultiValueMap;
44  
45  /**
46   * {@link javax.servlet.Filter} that makes form encoded data available through
47   * the {@code ServletRequest.getParameter*()} family of methods during HTTP PUT
48   * or PATCH requests.
49   *
50   * <p>The Servlet spec requires form data to be available for HTTP POST but
51   * not for HTTP PUT or PATCH requests. This filter intercepts HTTP PUT and PATCH
52   * requests where content type is {@code 'application/x-www-form-urlencoded'},
53   * reads form encoded content from the body of the request, and wraps the ServletRequest
54   * in order to make the form data available as request parameters just like
55   * it is for HTTP POST requests.
56   *
57   * @author Rossen Stoyanchev
58   * @since 3.1
59   */
60  public class HttpPutFormContentFilter extends OncePerRequestFilter {
61  
62  	private final FormHttpMessageConverter formConverter = new AllEncompassingFormHttpMessageConverter();
63  
64  	/**
65  	 * The default character set to use for reading form data.
66  	 */
67  	public void setCharset(Charset charset) {
68  		this.formConverter.setCharset(charset);
69  	}
70  
71  	@Override
72  	protected void doFilterInternal(final HttpServletRequest request, HttpServletResponse response,
73  			FilterChain filterChain) throws ServletException, IOException {
74  
75  		if (("PUT".equals(request.getMethod()) || "PATCH".equals(request.getMethod())) && isFormContentType(request)) {
76  			HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
77  				@Override
78  				public InputStream getBody() throws IOException {
79  					return request.getInputStream();
80  				}
81  			};
82  			MultiValueMap<String, String> formParameters = formConverter.read(null, inputMessage);
83  			HttpServletRequest wrapper = new HttpPutFormContentRequestWrapper(request, formParameters);
84  			filterChain.doFilter(wrapper, response);
85  		}
86  		else {
87  			filterChain.doFilter(request, response);
88  		}
89  	}
90  
91  	private boolean isFormContentType(HttpServletRequest request) {
92  		String contentType = request.getContentType();
93  		if (contentType != null) {
94  			try {
95  				MediaType mediaType = MediaType.parseMediaType(contentType);
96  				return (MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType));
97  			}
98  			catch (IllegalArgumentException ex) {
99  				return false;
100 			}
101 		}
102 		else {
103 			return false;
104 		}
105 	}
106 
107 	private static class HttpPutFormContentRequestWrapper extends HttpServletRequestWrapper {
108 
109 		private MultiValueMap<String, String> formParameters;
110 
111 		public HttpPutFormContentRequestWrapper(HttpServletRequest request, MultiValueMap<String, String> parameters) {
112 			super(request);
113 			this.formParameters = (parameters != null) ? parameters : new LinkedMultiValueMap<String, String>();
114 		}
115 
116 		@Override
117 		public String getParameter(String name) {
118 			String queryStringValue = super.getParameter(name);
119 			String formValue = this.formParameters.getFirst(name);
120 			return (queryStringValue != null) ?  queryStringValue : formValue;
121 		}
122 
123 		@Override
124 		public Map<String, String[]> getParameterMap() {
125 			Map<String, String[]> result = new LinkedHashMap<String, String[]>();
126 			Enumeration<String> names = this.getParameterNames();
127 			while (names.hasMoreElements()) {
128 				String name = names.nextElement();
129 				result.put(name, this.getParameterValues(name));
130 			}
131 			return result;
132 		}
133 
134 		@Override
135 		public Enumeration<String> getParameterNames() {
136 			Set<String> names = new LinkedHashSet<String>();
137 			names.addAll(Collections.list(super.getParameterNames()));
138 			names.addAll(this.formParameters.keySet());
139 			return Collections.enumeration(names);
140 		}
141 
142 		@Override
143 		public String[] getParameterValues(String name) {
144 			String[] queryStringValues = super.getParameterValues(name);
145 			List<String> formValues = this.formParameters.get(name);
146 			if (formValues == null) {
147 				return queryStringValues;
148 			}
149 			else if (queryStringValues == null) {
150 				return formValues.toArray(new String[formValues.size()]);
151 			}
152 			else {
153 				List<String> result = new ArrayList<String>();
154 				result.addAll(Arrays.asList(queryStringValues));
155 				result.addAll(formValues);
156 				return result.toArray(new String[result.size()]);
157 			}
158 		}
159 	}
160 
161 }