From f64fa3dea10af125d612d3a997aece93d21bc875 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Fri, 18 May 2018 10:56:50 +0200 Subject: [PATCH] Restrict HTTP methods on Servlet HiddenHttpMethodFilter This commit restricts the allowed HTTP methods on HiddenHttpMethodFilter (Servlet variant) to the following: PUT, DELETE, PATCH. This filter is meant to be used to simulate those methods from HTML forms sent by browsers, so no other methods are allowed. Issue: SPR-16836 --- .../web/filter/HiddenHttpMethodFilter.java | 18 +++++++-- .../filter/HiddenHttpMethodFilterTests.java | 39 +++++++++++-------- 2 files changed, 38 insertions(+), 19 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/web/filter/HiddenHttpMethodFilter.java b/spring-web/src/main/java/org/springframework/web/filter/HiddenHttpMethodFilter.java index 8e13979c01..6e2834288a 100644 --- a/spring-web/src/main/java/org/springframework/web/filter/HiddenHttpMethodFilter.java +++ b/spring-web/src/main/java/org/springframework/web/filter/HiddenHttpMethodFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,9 @@ package org.springframework.web.filter; import java.io.IOException; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Locale; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -24,6 +27,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; +import org.springframework.http.HttpMethod; import org.springframework.util.Assert; import org.springframework.util.StringUtils; import org.springframework.web.util.WebUtils; @@ -35,6 +39,7 @@ import org.springframework.web.util.WebUtils; * is to use a normal POST with an additional hidden form field ({@code _method}) * to pass the "real" HTTP method along. This filter reads that parameter and changes * the {@link HttpServletRequestWrapper#getMethod()} return value accordingly. + * Only {@code "PUT"}, {@code "DELETE"} and {@code "PATCH"} HTTP methods are allowed. * *

The name of the request parameter defaults to {@code _method}, but can be * adapted via the {@link #setMethodParam(String) methodParam} property. @@ -50,6 +55,10 @@ import org.springframework.web.util.WebUtils; */ public class HiddenHttpMethodFilter extends OncePerRequestFilter { + private static final List ALLOWED_METHODS = + Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), + HttpMethod.DELETE.name(), HttpMethod.PATCH.name())); + /** Default method parameter: {@code _method} */ public static final String DEFAULT_METHOD_PARAM = "_method"; @@ -74,7 +83,10 @@ public class HiddenHttpMethodFilter extends OncePerRequestFilter { if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) { String paramValue = request.getParameter(this.methodParam); if (StringUtils.hasLength(paramValue)) { - requestToUse = new HttpMethodRequestWrapper(request, paramValue); + String method = paramValue.toUpperCase(Locale.ENGLISH); + if (ALLOWED_METHODS.contains(method)) { + requestToUse = new HttpMethodRequestWrapper(request, method); + } } } @@ -92,7 +104,7 @@ public class HiddenHttpMethodFilter extends OncePerRequestFilter { public HttpMethodRequestWrapper(HttpServletRequest request, String method) { super(request); - this.method = method.toUpperCase(Locale.ENGLISH); + this.method = method; } @Override diff --git a/spring-web/src/test/java/org/springframework/web/filter/HiddenHttpMethodFilterTests.java b/spring-web/src/test/java/org/springframework/web/filter/HiddenHttpMethodFilterTests.java index 219314664f..d97f291b32 100644 --- a/spring-web/src/test/java/org/springframework/web/filter/HiddenHttpMethodFilterTests.java +++ b/spring-web/src/test/java/org/springframework/web/filter/HiddenHttpMethodFilterTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2014 the original author or authors. + * Copyright 2002-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,10 @@ import org.springframework.mock.web.test.MockHttpServletResponse; import static org.junit.Assert.*; /** + * Tests for {@link HiddenHttpMethodFilter}. + * * @author Arjen Poutsma + * @author Brian Clozel */ public class HiddenHttpMethodFilterTests { @@ -39,25 +42,29 @@ public class HiddenHttpMethodFilterTests { @Test public void filterWithParameter() throws IOException, ServletException { - MockHttpServletRequest request = new MockHttpServletRequest("POST", "/hotels"); - request.addParameter("_method", "delete"); - MockHttpServletResponse response = new MockHttpServletResponse(); - - FilterChain filterChain = new FilterChain() { + filterWithParameterForMethod("delete", "DELETE"); + filterWithParameterForMethod("put", "PUT"); + filterWithParameterForMethod("patch", "PATCH"); + } - @Override - public void doFilter(ServletRequest filterRequest, - ServletResponse filterResponse) throws IOException, ServletException { - assertEquals("Invalid method", "DELETE", - ((HttpServletRequest) filterRequest).getMethod()); - } - }; - filter.doFilter(request, response, filterChain); + @Test + public void filterWithParameterDisallowedMethods() throws IOException, ServletException { + filterWithParameterForMethod("trace", "POST"); + filterWithParameterForMethod("head", "POST"); + filterWithParameterForMethod("options", "POST"); } @Test public void filterWithNoParameter() throws IOException, ServletException { + filterWithParameterForMethod(null, "POST"); + } + + private void filterWithParameterForMethod(String methodParam, String expectedMethod) + throws IOException, ServletException { MockHttpServletRequest request = new MockHttpServletRequest("POST", "/hotels"); + if(methodParam != null) { + request.addParameter("_method", methodParam); + } MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = new FilterChain() { @@ -65,11 +72,11 @@ public class HiddenHttpMethodFilterTests { @Override public void doFilter(ServletRequest filterRequest, ServletResponse filterResponse) throws IOException, ServletException { - assertEquals("Invalid method", "POST", + assertEquals("Invalid method", expectedMethod, ((HttpServletRequest) filterRequest).getMethod()); } }; - filter.doFilter(request, response, filterChain); + this.filter.doFilter(request, response, filterChain); } } \ No newline at end of file