/*
* Copyright 2002-2016 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.filter;
import java.io.IOException;
import java.util.Enumeration;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.test.MockFilterChain;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import static org.junit.Assert.*;
/**
* Unit tests for {@link ForwardedHeaderFilter}.
*
* @author Rossen Stoyanchev
* @author EddĂș MelĂ©ndez
* @author Rob Winch
*/
public class ForwardedHeaderFilterTests {
private static final String X_FORWARDED_PROTO = "x-forwarded-proto"; // SPR-14372 (case insensitive)
private static final String X_FORWARDED_HOST = "x-forwarded-host";
private static final String X_FORWARDED_PORT = "x-forwarded-port";
private static final String X_FORWARDED_PREFIX = "x-forwarded-prefix";
private final ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
private MockHttpServletRequest request;
private MockFilterChain filterChain;
@Before
@SuppressWarnings("serial")
public void setUp() throws Exception {
this.request = new MockHttpServletRequest();
this.request.setScheme("http");
this.request.setServerName("localhost");
this.request.setServerPort(80);
this.filterChain = new MockFilterChain(new HttpServlet() {});
}
@Test
public void contextPathEmpty() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "");
assertEquals("", filterAndGetContextPath());
}
@Test
public void contextPathWithTrailingSlash() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "/foo/bar/");
assertEquals("/foo/bar", filterAndGetContextPath());
}
@Test
public void contextPathWithTrailingSlashes() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "/foo/bar/baz///");
assertEquals("/foo/bar/baz", filterAndGetContextPath());
}
@Test
public void contextPathWithForwardedPrefix() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix");
this.request.setContextPath("/mvc-showcase");
String actual = filterAndGetContextPath();
assertEquals("/prefix", actual);
}
@Test
public void contextPathWithForwardedPrefixTrailingSlash() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix/");
this.request.setContextPath("/mvc-showcase");
String actual = filterAndGetContextPath();
assertEquals("/prefix", actual);
}
@Test
public void contextPathPreserveEncoding() throws Exception {
this.request.setContextPath("/app%20");
this.request.setRequestURI("/app%20/path/");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertEquals("/app%20", actual.getContextPath());
assertEquals("/app%20/path/", actual.getRequestURI());
assertEquals("http://localhost/app%20/path/", actual.getRequestURL().toString());
}
@Test
public void requestUri() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "/");
this.request.setContextPath("/app");
this.request.setRequestURI("/app/path");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertEquals("", actual.getContextPath());
assertEquals("/path", actual.getRequestURI());
}
@Test
public void requestUriWithTrailingSlash() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "/");
this.request.setContextPath("/app");
this.request.setRequestURI("/app/path/");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertEquals("", actual.getContextPath());
assertEquals("/path/", actual.getRequestURI());
}
@Test
public void requestUriPreserveEncoding() throws Exception {
this.request.setContextPath("/app");
this.request.setRequestURI("/app/path%20with%20spaces/");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertEquals("/app", actual.getContextPath());
assertEquals("/app/path%20with%20spaces/", actual.getRequestURI());
assertEquals("http://localhost/app/path%20with%20spaces/", actual.getRequestURL().toString());
}
@Test
public void requestUriEqualsContextPath() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "/");
this.request.setContextPath("/app");
this.request.setRequestURI("/app");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertEquals("", actual.getContextPath());
assertEquals("/", actual.getRequestURI());
}
@Test
public void requestUriRootUrl() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "/");
this.request.setContextPath("/app");
this.request.setRequestURI("/app/");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertEquals("", actual.getContextPath());
assertEquals("/", actual.getRequestURI());
}
@Test
public void requestUriPreserveSemicolonContent() throws Exception {
this.request.setContextPath("");
this.request.setRequestURI("/path;a=b/with/semicolon");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertEquals("", actual.getContextPath());
assertEquals("/path;a=b/with/semicolon", actual.getRequestURI());
assertEquals("http://localhost/path;a=b/with/semicolon", actual.getRequestURL().toString());
}
@Test
public void caseInsensitiveForwardedPrefix() throws Exception {
this.request = new MockHttpServletRequest() {
// Make it case-sensitive (SPR-14372)
@Override
public String getHeader(String header) {
Enumeration<String> names = getHeaderNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
if (name.equals(header)) {
return super.getHeader(header);
}
}
return null;
}
};
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix");
this.request.setRequestURI("/path");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertEquals("/prefix/path", actual.getRequestURI());
}
@Test
public void shouldFilter() throws Exception {
testShouldFilter("Forwarded");
testShouldFilter(X_FORWARDED_HOST);
testShouldFilter(X_FORWARDED_PORT);
testShouldFilter(X_FORWARDED_PROTO);
}
@Test
public void shouldNotFilter() throws Exception {
assertTrue(this.filter.shouldNotFilter(new MockHttpServletRequest()));
}
@Test
public void forwardedRequest() throws Exception {
this.request.setRequestURI("/mvc-showcase");
this.request.addHeader(X_FORWARDED_PROTO, "https");
this.request.addHeader(X_FORWARDED_HOST, "84.198.58.199");
this.request.addHeader(X_FORWARDED_PORT, "443");
this.request.addHeader("foo", "bar");
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
HttpServletRequest actual = (HttpServletRequest) this.filterChain.getRequest();
assertEquals("https://84.198.58.199/mvc-showcase", actual.getRequestURL().toString());
assertEquals("https", actual.getScheme());
assertEquals("84.198.58.199", actual.getServerName());
assertEquals(443, actual.getServerPort());
assertTrue(actual.isSecure());
assertNull(actual.getHeader(X_FORWARDED_PROTO));
assertNull(actual.getHeader(X_FORWARDED_HOST));
assertNull(actual.getHeader(X_FORWARDED_PORT));
assertEquals("bar", actual.getHeader("foo"));
}
@Test
public void requestUriWithForwardedPrefix() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix");
this.request.setRequestURI("/mvc-showcase");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertEquals("http://localhost/prefix/mvc-showcase", actual.getRequestURL().toString());
}
@Test
public void requestUriWithForwardedPrefixTrailingSlash() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix/");
this.request.setRequestURI("/mvc-showcase");
HttpServletRequest actual = filterAndGetWrappedRequest();
assertEquals("http://localhost/prefix/mvc-showcase", actual.getRequestURL().toString());
}
@Test
public void requestURLNewStringBuffer() throws Exception {
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix/");
this.request.setRequestURI("/mvc-showcase");
HttpServletRequest actual = filterAndGetWrappedRequest();
actual.getRequestURL().append("?key=value");
assertEquals("http://localhost/prefix/mvc-showcase", actual.getRequestURL().toString());
}
@Test
public void sendRedirectWithAbsolutePath() throws Exception {
this.request.addHeader(X_FORWARDED_PROTO, "https");
this.request.addHeader(X_FORWARDED_HOST, "example.com");
this.request.addHeader(X_FORWARDED_PORT, "443");
String redirectedUrl = sendRedirect("/foo/bar");
assertEquals("https://example.com/foo/bar", redirectedUrl);
}
@Test
public void sendRedirectWithContextPath() throws Exception {
this.request.addHeader(X_FORWARDED_PROTO, "https");
this.request.addHeader(X_FORWARDED_HOST, "example.com");
this.request.addHeader(X_FORWARDED_PORT, "443");
this.request.setContextPath("/context");
String redirectedUrl = sendRedirect("/context/foo/bar");
assertEquals("https://example.com/context/foo/bar", redirectedUrl);
}
@Test
public void sendRedirectWithRelativePath() throws Exception {
this.request.addHeader(X_FORWARDED_PROTO, "https");
this.request.addHeader(X_FORWARDED_HOST, "example.com");
this.request.addHeader(X_FORWARDED_PORT, "443");
this.request.setRequestURI("/parent/");
String redirectedUrl = sendRedirect("foo/bar");
assertEquals("https://example.com/parent/foo/bar", redirectedUrl);
}
@Test
public void sendRedirectWithFileInPathAndRelativeRedirect() throws Exception {
this.request.addHeader(X_FORWARDED_PROTO, "https");
this.request.addHeader(X_FORWARDED_HOST, "example.com");
this.request.addHeader(X_FORWARDED_PORT, "443");
this.request.setRequestURI("/context/a");
String redirectedUrl = sendRedirect("foo/bar");
assertEquals("https://example.com/context/foo/bar", redirectedUrl);
}
@Test
public void sendRedirectWithRelativePathIgnoresFile() throws Exception {
this.request.addHeader(X_FORWARDED_PROTO, "https");
this.request.addHeader(X_FORWARDED_HOST, "example.com");
this.request.addHeader(X_FORWARDED_PORT, "443");
this.request.setRequestURI("/parent");
String redirectedUrl = sendRedirect("foo/bar");
assertEquals("https://example.com/foo/bar", redirectedUrl);
}
@Test
public void sendRedirectWithLocationDotDotPath() throws Exception {
this.request.addHeader(X_FORWARDED_PROTO, "https");
this.request.addHeader(X_FORWARDED_HOST, "example.com");
this.request.addHeader(X_FORWARDED_PORT, "443");
String redirectedUrl = sendRedirect("parent/../foo/bar");
assertEquals("https://example.com/foo/bar", redirectedUrl);
}
@Test
public void sendRedirectWithLocationHasScheme() throws Exception {
this.request.addHeader(X_FORWARDED_PROTO, "https");
this.request.addHeader(X_FORWARDED_HOST, "example.com");
this.request.addHeader(X_FORWARDED_PORT, "443");
String location = "http://other.info/foo/bar";
String redirectedUrl = sendRedirect(location);
assertEquals(location, redirectedUrl);
}
@Test
public void sendRedirectWithLocationSlashSlash() throws Exception {
this.request.addHeader(X_FORWARDED_PROTO, "https");
this.request.addHeader(X_FORWARDED_HOST, "example.com");
this.request.addHeader(X_FORWARDED_PORT, "443");
String location = "//other.info/foo/bar";
String redirectedUrl = sendRedirect(location);
assertEquals("https:" + location, redirectedUrl);
}
@Test
public void sendRedirectWithLocationSlashSlashParentDotDot() throws Exception {
this.request.addHeader(X_FORWARDED_PROTO, "https");
this.request.addHeader(X_FORWARDED_HOST, "example.com");
this.request.addHeader(X_FORWARDED_PORT, "443");
String location = "//other.info/parent/../foo/bar";
String redirectedUrl = sendRedirect(location);
assertEquals("https:" + location, redirectedUrl);
}
@Test
public void sendRedirectWithNoXForwardedAndAbsolutePath() throws Exception {
String redirectedUrl = sendRedirect("/foo/bar");
assertEquals("/foo/bar", redirectedUrl);
}
@Test
public void sendRedirectWithNoXForwardedAndDotDotPath() throws Exception {
String redirectedUrl = sendRedirect("../foo/bar");
assertEquals("../foo/bar", redirectedUrl);
}
private String sendRedirect(final String location) throws ServletException, IOException {
MockHttpServletResponse response = doWithFiltersAndGetResponse(this.filter, new OncePerRequestFilter() {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
response.sendRedirect(location);
}
});
return response.getRedirectedUrl();
}
@SuppressWarnings("serial")
private MockHttpServletResponse doWithFiltersAndGetResponse(Filter... filters) throws ServletException, IOException {
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = new MockFilterChain(new HttpServlet() {}, filters);
filterChain.doFilter(request, response);
return response;
}
private String filterAndGetContextPath() throws ServletException, IOException {
return filterAndGetWrappedRequest().getContextPath();
}
private HttpServletRequest filterAndGetWrappedRequest() throws ServletException, IOException {
MockHttpServletResponse response = new MockHttpServletResponse();
this.filter.doFilterInternal(this.request, response, this.filterChain);
return (HttpServletRequest) this.filterChain.getRequest();
}
private void testShouldFilter(String headerName) throws ServletException {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader(headerName, "1");
assertFalse(this.filter.shouldNotFilter(request));
}
}