001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.filter; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.nio.charset.Charset; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.Enumeration; 026import java.util.LinkedHashMap; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Set; 031import javax.servlet.FilterChain; 032import javax.servlet.ServletException; 033import javax.servlet.http.HttpServletRequest; 034import javax.servlet.http.HttpServletRequestWrapper; 035import javax.servlet.http.HttpServletResponse; 036 037import org.springframework.http.HttpInputMessage; 038import org.springframework.http.MediaType; 039import org.springframework.http.converter.FormHttpMessageConverter; 040import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; 041import org.springframework.http.server.ServletServerHttpRequest; 042import org.springframework.util.Assert; 043import org.springframework.util.LinkedMultiValueMap; 044import org.springframework.util.MultiValueMap; 045import org.springframework.util.StringUtils; 046 047/** 048 * {@link javax.servlet.Filter} that makes form encoded data available through 049 * the {@code ServletRequest.getParameter*()} family of methods during HTTP PUT 050 * or PATCH requests. 051 * 052 * <p>The Servlet spec requires form data to be available for HTTP POST but 053 * not for HTTP PUT or PATCH requests. This filter intercepts HTTP PUT and PATCH 054 * requests where content type is {@code 'application/x-www-form-urlencoded'}, 055 * reads form encoded content from the body of the request, and wraps the ServletRequest 056 * in order to make the form data available as request parameters just like 057 * it is for HTTP POST requests. 058 * 059 * @author Rossen Stoyanchev 060 * @since 3.1 061 */ 062public class HttpPutFormContentFilter extends OncePerRequestFilter { 063 064 private FormHttpMessageConverter formConverter = new AllEncompassingFormHttpMessageConverter(); 065 066 067 /** 068 * Set the converter to use for parsing form content. 069 * <p>By default this is an instnace of {@link AllEncompassingFormHttpMessageConverter}. 070 */ 071 public void setFormConverter(FormHttpMessageConverter converter) { 072 Assert.notNull(converter, "FormHttpMessageConverter is required."); 073 this.formConverter = converter; 074 } 075 076 public FormHttpMessageConverter getFormConverter() { 077 return this.formConverter; 078 } 079 080 /** 081 * The default character set to use for reading form data. 082 * This is a shortcut for:<br> 083 * {@code getFormConverter.setCharset(charset)}. 084 */ 085 public void setCharset(Charset charset) { 086 this.formConverter.setCharset(charset); 087 } 088 089 090 @Override 091 protected void doFilterInternal(final HttpServletRequest request, HttpServletResponse response, 092 FilterChain filterChain) throws ServletException, IOException { 093 094 if (("PUT".equals(request.getMethod()) || "PATCH".equals(request.getMethod())) && isFormContentType(request)) { 095 HttpInputMessage inputMessage = new ServletServerHttpRequest(request) { 096 @Override 097 public InputStream getBody() throws IOException { 098 return request.getInputStream(); 099 } 100 }; 101 MultiValueMap<String, String> formParameters = this.formConverter.read(null, inputMessage); 102 if (!formParameters.isEmpty()) { 103 HttpServletRequest wrapper = new HttpPutFormContentRequestWrapper(request, formParameters); 104 filterChain.doFilter(wrapper, response); 105 return; 106 } 107 } 108 109 filterChain.doFilter(request, response); 110 } 111 112 private boolean isFormContentType(HttpServletRequest request) { 113 String contentType = request.getContentType(); 114 if (contentType != null) { 115 try { 116 MediaType mediaType = MediaType.parseMediaType(contentType); 117 return (MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)); 118 } 119 catch (IllegalArgumentException ex) { 120 return false; 121 } 122 } 123 else { 124 return false; 125 } 126 } 127 128 129 private static class HttpPutFormContentRequestWrapper extends HttpServletRequestWrapper { 130 131 private MultiValueMap<String, String> formParameters; 132 133 public HttpPutFormContentRequestWrapper(HttpServletRequest request, MultiValueMap<String, String> parameters) { 134 super(request); 135 this.formParameters = (parameters != null ? parameters : new LinkedMultiValueMap<String, String>()); 136 } 137 138 @Override 139 public String getParameter(String name) { 140 String queryStringValue = super.getParameter(name); 141 String formValue = this.formParameters.getFirst(name); 142 return (queryStringValue != null ? queryStringValue : formValue); 143 } 144 145 @Override 146 public Map<String, String[]> getParameterMap() { 147 Map<String, String[]> result = new LinkedHashMap<String, String[]>(); 148 Enumeration<String> names = getParameterNames(); 149 while (names.hasMoreElements()) { 150 String name = names.nextElement(); 151 result.put(name, getParameterValues(name)); 152 } 153 return result; 154 } 155 156 @Override 157 public Enumeration<String> getParameterNames() { 158 Set<String> names = new LinkedHashSet<String>(); 159 names.addAll(Collections.list(super.getParameterNames())); 160 names.addAll(this.formParameters.keySet()); 161 return Collections.enumeration(names); 162 } 163 164 @Override 165 public String[] getParameterValues(String name) { 166 String[] parameterValues = super.getParameterValues(name); 167 List<String> formParam = this.formParameters.get(name); 168 if (formParam == null) { 169 return parameterValues; 170 } 171 if (parameterValues == null || getQueryString() == null) { 172 return StringUtils.toStringArray(formParam); 173 } 174 else { 175 List<String> result = new ArrayList<String>(parameterValues.length + formParam.size()); 176 result.addAll(Arrays.asList(parameterValues)); 177 result.addAll(formParam); 178 return StringUtils.toStringArray(result); 179 } 180 } 181 } 182 183}