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.util.Arrays; 021import java.util.Collections; 022import java.util.List; 023import java.util.Locale; 024import javax.servlet.FilterChain; 025import javax.servlet.ServletException; 026import javax.servlet.http.HttpServletRequest; 027import javax.servlet.http.HttpServletRequestWrapper; 028import javax.servlet.http.HttpServletResponse; 029 030import org.springframework.http.HttpMethod; 031import org.springframework.util.Assert; 032import org.springframework.util.StringUtils; 033import org.springframework.web.util.WebUtils; 034 035/** 036 * {@link javax.servlet.Filter} that converts posted method parameters into HTTP methods, 037 * retrievable via {@link HttpServletRequest#getMethod()}. Since browsers currently only 038 * support GET and POST, a common technique - used by the Prototype library, for instance - 039 * is to use a normal POST with an additional hidden form field ({@code _method}) 040 * to pass the "real" HTTP method along. This filter reads that parameter and changes 041 * the {@link HttpServletRequestWrapper#getMethod()} return value accordingly. 042 * Only {@code "PUT"}, {@code "DELETE"} and {@code "PATCH"} HTTP methods are allowed. 043 * 044 * <p>The name of the request parameter defaults to {@code _method}, but can be 045 * adapted via the {@link #setMethodParam(String) methodParam} property. 046 * 047 * <p><b>NOTE: This filter needs to run after multipart processing in case of a multipart 048 * POST request, due to its inherent need for checking a POST body parameter.</b> 049 * So typically, put a Spring {@link org.springframework.web.multipart.support.MultipartFilter} 050 * <i>before</i> this HiddenHttpMethodFilter in your {@code web.xml} filter chain. 051 * 052 * @author Arjen Poutsma 053 * @author Juergen Hoeller 054 * @since 3.0 055 */ 056public class HiddenHttpMethodFilter extends OncePerRequestFilter { 057 058 private static final List<String> ALLOWED_METHODS = 059 Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), 060 HttpMethod.DELETE.name(), HttpMethod.PATCH.name())); 061 062 /** Default method parameter: {@code _method} */ 063 public static final String DEFAULT_METHOD_PARAM = "_method"; 064 065 private String methodParam = DEFAULT_METHOD_PARAM; 066 067 068 /** 069 * Set the parameter name to look for HTTP methods. 070 * @see #DEFAULT_METHOD_PARAM 071 */ 072 public void setMethodParam(String methodParam) { 073 Assert.hasText(methodParam, "'methodParam' must not be empty"); 074 this.methodParam = methodParam; 075 } 076 077 @Override 078 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) 079 throws ServletException, IOException { 080 081 HttpServletRequest requestToUse = request; 082 083 if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) { 084 String paramValue = request.getParameter(this.methodParam); 085 if (StringUtils.hasLength(paramValue)) { 086 String method = paramValue.toUpperCase(Locale.ENGLISH); 087 if (ALLOWED_METHODS.contains(method)) { 088 requestToUse = new HttpMethodRequestWrapper(request, method); 089 } 090 } 091 } 092 093 filterChain.doFilter(requestToUse, response); 094 } 095 096 097 /** 098 * Simple {@link HttpServletRequest} wrapper that returns the supplied method for 099 * {@link HttpServletRequest#getMethod()}. 100 */ 101 private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper { 102 103 private final String method; 104 105 public HttpMethodRequestWrapper(HttpServletRequest request, String method) { 106 super(request); 107 this.method = method; 108 } 109 110 @Override 111 public String getMethod() { 112 return this.method; 113 } 114 } 115 116}