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