Skip to content

Commit

Permalink
Restrict HTTP methods on Servlet HiddenHttpMethodFilter
Browse files Browse the repository at this point in the history
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
  • Loading branch information
bclozel committed Jun 11, 2018
1 parent d196cdc commit f64fa3d
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 19 deletions.
@@ -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.
Expand All @@ -17,13 +17,17 @@
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;
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;
Expand All @@ -35,6 +39,7 @@
* 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.
*
* <p>The name of the request parameter defaults to {@code _method}, but can be
* adapted via the {@link #setMethodParam(String) methodParam} property.
Expand All @@ -50,6 +55,10 @@
*/
public class HiddenHttpMethodFilter extends OncePerRequestFilter {

private static final List<String> 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";

Expand All @@ -74,7 +83,10 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
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);
}
}
}

Expand All @@ -92,7 +104,7 @@ private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper

public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method.toUpperCase(Locale.ENGLISH);
this.method = method;
}

@Override
Expand Down
@@ -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.
Expand Down Expand Up @@ -31,45 +31,52 @@
import static org.junit.Assert.*;

/**
* Tests for {@link HiddenHttpMethodFilter}.
*
* @author Arjen Poutsma
* @author Brian Clozel
*/
public class HiddenHttpMethodFilterTests {

private final HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();

@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() {

@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);
}

}

0 comments on commit f64fa3d

Please sign in to comment.