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; 031 032import javax.servlet.FilterChain; 033import javax.servlet.ServletException; 034import javax.servlet.http.HttpServletRequest; 035import javax.servlet.http.HttpServletRequestWrapper; 036import javax.servlet.http.HttpServletResponse; 037 038import org.springframework.http.HttpInputMessage; 039import org.springframework.http.MediaType; 040import org.springframework.http.converter.FormHttpMessageConverter; 041import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; 042import org.springframework.http.server.ServletServerHttpRequest; 043import org.springframework.lang.Nullable; 044import org.springframework.util.Assert; 045import org.springframework.util.CollectionUtils; 046import org.springframework.util.MultiValueMap; 047import org.springframework.util.StringUtils; 048 049/** 050 * {@code Filter} that parses form data for HTTP PUT, PATCH, and DELETE requests 051 * and exposes it as Servlet request parameters. By default the Servlet spec 052 * only requires this for HTTP POST. 053 * 054 * @author Rossen Stoyanchev 055 * @since 5.1 056 */ 057public class FormContentFilter extends OncePerRequestFilter { 058 059 private static final List<String> HTTP_METHODS = Arrays.asList("PUT", "PATCH", "DELETE"); 060 061 private FormHttpMessageConverter formConverter = new AllEncompassingFormHttpMessageConverter(); 062 063 064 /** 065 * Set the converter to use for parsing form content. 066 * <p>By default this is an instance of {@link AllEncompassingFormHttpMessageConverter}. 067 */ 068 public void setFormConverter(FormHttpMessageConverter converter) { 069 Assert.notNull(converter, "FormHttpMessageConverter is required"); 070 this.formConverter = converter; 071 } 072 073 /** 074 * The default character set to use for reading form data. 075 * This is a shortcut for:<br> 076 * {@code getFormConverter.setCharset(charset)}. 077 */ 078 public void setCharset(Charset charset) { 079 this.formConverter.setCharset(charset); 080 } 081 082 083 @Override 084 protected void doFilterInternal( 085 HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 086 throws ServletException, IOException { 087 088 MultiValueMap<String, String> params = parseIfNecessary(request); 089 if (!CollectionUtils.isEmpty(params)) { 090 filterChain.doFilter(new FormContentRequestWrapper(request, params), response); 091 } 092 else { 093 filterChain.doFilter(request, response); 094 } 095 } 096 097 @Nullable 098 private MultiValueMap<String, String> parseIfNecessary(HttpServletRequest request) throws IOException { 099 if (!shouldParse(request)) { 100 return null; 101 } 102 103 HttpInputMessage inputMessage = new ServletServerHttpRequest(request) { 104 @Override 105 public InputStream getBody() throws IOException { 106 return request.getInputStream(); 107 } 108 }; 109 return this.formConverter.read(null, inputMessage); 110 } 111 112 private boolean shouldParse(HttpServletRequest request) { 113 String contentType = request.getContentType(); 114 String method = request.getMethod(); 115 if (StringUtils.hasLength(contentType) && HTTP_METHODS.contains(method)) { 116 try { 117 MediaType mediaType = MediaType.parseMediaType(contentType); 118 return MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType); 119 } 120 catch (IllegalArgumentException ex) { 121 } 122 } 123 return false; 124 } 125 126 127 private static class FormContentRequestWrapper extends HttpServletRequestWrapper { 128 129 private MultiValueMap<String, String> formParams; 130 131 public FormContentRequestWrapper(HttpServletRequest request, MultiValueMap<String, String> params) { 132 super(request); 133 this.formParams = params; 134 } 135 136 @Override 137 @Nullable 138 public String getParameter(String name) { 139 String queryStringValue = super.getParameter(name); 140 String formValue = this.formParams.getFirst(name); 141 return (queryStringValue != null ? queryStringValue : formValue); 142 } 143 144 @Override 145 public Map<String, String[]> getParameterMap() { 146 Map<String, String[]> result = new LinkedHashMap<>(); 147 Enumeration<String> names = getParameterNames(); 148 while (names.hasMoreElements()) { 149 String name = names.nextElement(); 150 result.put(name, getParameterValues(name)); 151 } 152 return result; 153 } 154 155 @Override 156 public Enumeration<String> getParameterNames() { 157 Set<String> names = new LinkedHashSet<>(); 158 names.addAll(Collections.list(super.getParameterNames())); 159 names.addAll(this.formParams.keySet()); 160 return Collections.enumeration(names); 161 } 162 163 @Override 164 @Nullable 165 public String[] getParameterValues(String name) { 166 String[] parameterValues = super.getParameterValues(name); 167 List<String> formParam = this.formParams.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<>(parameterValues.length + formParam.size()); 176 result.addAll(Arrays.asList(parameterValues)); 177 result.addAll(formParam); 178 return StringUtils.toStringArray(result); 179 } 180 } 181 } 182 183}