Skip to content

Commit 919f6c9

Browse files
committed
ForwardedHeaderFilter is case-insensitive
Issue: SPR-14372
1 parent 981a748 commit 919f6c9

File tree

3 files changed

+88
-42
lines changed

3 files changed

+88
-42
lines changed

spring-web/src/main/java/org/springframework/web/filter/ForwardedHeaderFilter.java

+26-20
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,8 @@
1919
import java.io.IOException;
2020
import java.util.Collections;
2121
import java.util.Enumeration;
22-
import java.util.HashSet;
23-
import java.util.LinkedHashMap;
2422
import java.util.List;
23+
import java.util.Locale;
2524
import java.util.Map;
2625
import java.util.Set;
2726
import javax.servlet.FilterChain;
@@ -33,6 +32,7 @@
3332
import org.springframework.http.HttpRequest;
3433
import org.springframework.http.server.ServletServerHttpRequest;
3534
import org.springframework.util.CollectionUtils;
35+
import org.springframework.util.LinkedCaseInsensitiveMap;
3636
import org.springframework.web.util.UriComponents;
3737
import org.springframework.web.util.UriComponentsBuilder;
3838
import org.springframework.web.util.UrlPathHelper;
@@ -52,10 +52,10 @@
5252
*/
5353
public class ForwardedHeaderFilter extends OncePerRequestFilter {
5454

55-
private static final Set<String> FORWARDED_HEADER_NAMES;
55+
private static final Set<String> FORWARDED_HEADER_NAMES =
56+
Collections.newSetFromMap(new LinkedCaseInsensitiveMap<Boolean>(5, Locale.ENGLISH));
5657

5758
static {
58-
FORWARDED_HEADER_NAMES = new HashSet<String>(5);
5959
FORWARDED_HEADER_NAMES.add("Forwarded");
6060
FORWARDED_HEADER_NAMES.add("X-Forwarded-Host");
6161
FORWARDED_HEADER_NAMES.add("X-Forwarded-Port");
@@ -69,9 +69,9 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
6969

7070
@Override
7171
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
72-
Enumeration<String> headerNames = request.getHeaderNames();
73-
while (headerNames.hasMoreElements()) {
74-
String name = headerNames.nextElement();
72+
Enumeration<String> names = request.getHeaderNames();
73+
while (names.hasMoreElements()) {
74+
String name = names.nextElement();
7575
if (FORWARDED_HEADER_NAMES.contains(name)) {
7676
return false;
7777
}
@@ -136,27 +136,33 @@ public ForwardedHeaderRequestWrapper(HttpServletRequest request, UrlPathHelper p
136136
}
137137

138138
private static String getForwardedPrefix(HttpServletRequest request) {
139-
String header = request.getHeader("X-Forwarded-Prefix");
140-
if (header != null) {
141-
while (header.endsWith("/")) {
142-
header = header.substring(0, header.length() - 1);
139+
String prefix = null;
140+
Enumeration<String> names = request.getHeaderNames();
141+
while (names.hasMoreElements()) {
142+
String name = names.nextElement();
143+
if ("X-Forwarded-Prefix".equalsIgnoreCase(name)) {
144+
prefix = request.getHeader(name);
143145
}
144146
}
145-
return header;
147+
if (prefix != null) {
148+
while (prefix.endsWith("/")) {
149+
prefix = prefix.substring(0, prefix.length() - 1);
150+
}
151+
}
152+
return prefix;
146153
}
147154

148155
/**
149156
* Copy the headers excluding any {@link #FORWARDED_HEADER_NAMES}.
150157
*/
151158
private static Map<String, List<String>> initHeaders(HttpServletRequest request) {
152-
Map<String, List<String>> headers = new LinkedHashMap<String, List<String>>();
153-
Enumeration<String> headerNames = request.getHeaderNames();
154-
while (headerNames.hasMoreElements()) {
155-
String name = headerNames.nextElement();
156-
headers.put(name, Collections.list(request.getHeaders(name)));
157-
}
158-
for (String name : FORWARDED_HEADER_NAMES) {
159-
headers.remove(name);
159+
Map<String, List<String>> headers = new LinkedCaseInsensitiveMap<List<String>>(Locale.ENGLISH);
160+
Enumeration<String> names = request.getHeaderNames();
161+
while (names.hasMoreElements()) {
162+
String name = names.nextElement();
163+
if (!FORWARDED_HEADER_NAMES.contains(name)) {
164+
headers.put(name, Collections.list(request.getHeaders(name)));
165+
}
160166
}
161167
return headers;
162168
}

spring-web/src/test/java/org/springframework/web/filter/ForwardedHeaderFilterTests.java

+52-20
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.web.filter;
1717

1818
import java.io.IOException;
19+
import java.util.Enumeration;
1920
import javax.servlet.ServletException;
2021
import javax.servlet.http.HttpServlet;
2122
import javax.servlet.http.HttpServletRequest;
@@ -39,6 +40,12 @@
3940
*/
4041
public class ForwardedHeaderFilterTests {
4142

43+
private static final String X_FORWARDED_PROTO = "x-forwarded-proto"; // SPR-14372 (case insensitive)
44+
private static final String X_FORWARDED_HOST = "x-forwarded-host";
45+
private static final String X_FORWARDED_PORT = "x-forwarded-port";
46+
private static final String X_FORWARDED_PREFIX = "x-forwarded-prefix";
47+
48+
4249
private final ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
4350

4451
private MockHttpServletRequest request;
@@ -59,25 +66,25 @@ public void setUp() throws Exception {
5966

6067
@Test
6168
public void contextPathEmpty() throws Exception {
62-
this.request.addHeader("X-Forwarded-Prefix", "");
69+
this.request.addHeader(X_FORWARDED_PREFIX, "");
6370
assertEquals("", filterAndGetContextPath());
6471
}
6572

6673
@Test
6774
public void contextPathWithTrailingSlash() throws Exception {
68-
this.request.addHeader("X-Forwarded-Prefix", "/foo/bar/");
75+
this.request.addHeader(X_FORWARDED_PREFIX, "/foo/bar/");
6976
assertEquals("/foo/bar", filterAndGetContextPath());
7077
}
7178

7279
@Test
7380
public void contextPathWithTrailingSlashes() throws Exception {
74-
this.request.addHeader("X-Forwarded-Prefix", "/foo/bar/baz///");
81+
this.request.addHeader(X_FORWARDED_PREFIX, "/foo/bar/baz///");
7582
assertEquals("/foo/bar/baz", filterAndGetContextPath());
7683
}
7784

7885
@Test
7986
public void requestUri() throws Exception {
80-
this.request.addHeader("X-Forwarded-Prefix", "/");
87+
this.request.addHeader(X_FORWARDED_PREFIX, "/");
8188
this.request.setContextPath("/app");
8289
this.request.setRequestURI("/app/path");
8390
HttpServletRequest actual = filterAndGetWrappedRequest();
@@ -88,7 +95,7 @@ public void requestUri() throws Exception {
8895

8996
@Test
9097
public void requestUriWithTrailingSlash() throws Exception {
91-
this.request.addHeader("X-Forwarded-Prefix", "/");
98+
this.request.addHeader(X_FORWARDED_PREFIX, "/");
9299
this.request.setContextPath("/app");
93100
this.request.setRequestURI("/app/path/");
94101
HttpServletRequest actual = filterAndGetWrappedRequest();
@@ -98,7 +105,7 @@ public void requestUriWithTrailingSlash() throws Exception {
98105
}
99106
@Test
100107
public void requestUriEqualsContextPath() throws Exception {
101-
this.request.addHeader("X-Forwarded-Prefix", "/");
108+
this.request.addHeader(X_FORWARDED_PREFIX, "/");
102109
this.request.setContextPath("/app");
103110
this.request.setRequestURI("/app");
104111
HttpServletRequest actual = filterAndGetWrappedRequest();
@@ -109,7 +116,7 @@ public void requestUriEqualsContextPath() throws Exception {
109116

110117
@Test
111118
public void requestUriRootUrl() throws Exception {
112-
this.request.addHeader("X-Forwarded-Prefix", "/");
119+
this.request.addHeader(X_FORWARDED_PREFIX, "/");
113120
this.request.setContextPath("/app");
114121
this.request.setRequestURI("/app/");
115122
HttpServletRequest actual = filterAndGetWrappedRequest();
@@ -118,12 +125,37 @@ public void requestUriRootUrl() throws Exception {
118125
assertEquals("/", actual.getRequestURI());
119126
}
120127

128+
@Test
129+
public void caseInsensitiveForwardedPrefix() throws Exception {
130+
this.request = new MockHttpServletRequest() {
131+
132+
// Make it case-sensitive (SPR-14372)
133+
134+
@Override
135+
public String getHeader(String header) {
136+
Enumeration<String> names = getHeaderNames();
137+
while (names.hasMoreElements()) {
138+
String name = names.nextElement();
139+
if (name.equals(header)) {
140+
return super.getHeader(header);
141+
}
142+
}
143+
return null;
144+
}
145+
};
146+
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix");
147+
this.request.setRequestURI("/path");
148+
HttpServletRequest actual = filterAndGetWrappedRequest();
149+
150+
assertEquals("/prefix/path", actual.getRequestURI());
151+
}
152+
121153
@Test
122154
public void shouldFilter() throws Exception {
123155
testShouldFilter("Forwarded");
124-
testShouldFilter("X-Forwarded-Host");
125-
testShouldFilter("X-Forwarded-Port");
126-
testShouldFilter("X-Forwarded-Proto");
156+
testShouldFilter(X_FORWARDED_HOST);
157+
testShouldFilter(X_FORWARDED_PORT);
158+
testShouldFilter(X_FORWARDED_PROTO);
127159
}
128160

129161
@Test
@@ -134,9 +166,9 @@ public void shouldNotFilter() throws Exception {
134166
@Test
135167
public void forwardedRequest() throws Exception {
136168
this.request.setRequestURI("/mvc-showcase");
137-
this.request.addHeader("X-Forwarded-Proto", "https");
138-
this.request.addHeader("X-Forwarded-Host", "84.198.58.199");
139-
this.request.addHeader("X-Forwarded-Port", "443");
169+
this.request.addHeader(X_FORWARDED_PROTO, "https");
170+
this.request.addHeader(X_FORWARDED_HOST, "84.198.58.199");
171+
this.request.addHeader(X_FORWARDED_PORT, "443");
140172
this.request.addHeader("foo", "bar");
141173

142174
this.filter.doFilter(this.request, new MockHttpServletResponse(), this.filterChain);
@@ -148,15 +180,15 @@ public void forwardedRequest() throws Exception {
148180
assertEquals(443, actual.getServerPort());
149181
assertTrue(actual.isSecure());
150182

151-
assertNull(actual.getHeader("X-Forwarded-Proto"));
152-
assertNull(actual.getHeader("X-Forwarded-Host"));
153-
assertNull(actual.getHeader("X-Forwarded-Port"));
183+
assertNull(actual.getHeader(X_FORWARDED_PROTO));
184+
assertNull(actual.getHeader(X_FORWARDED_HOST));
185+
assertNull(actual.getHeader(X_FORWARDED_PORT));
154186
assertEquals("bar", actual.getHeader("foo"));
155187
}
156188

157189
@Test
158190
public void requestUriWithForwardedPrefix() throws Exception {
159-
this.request.addHeader("X-Forwarded-Prefix", "/prefix");
191+
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix");
160192
this.request.setRequestURI("/mvc-showcase");
161193

162194
HttpServletRequest actual = filterAndGetWrappedRequest();
@@ -165,7 +197,7 @@ public void requestUriWithForwardedPrefix() throws Exception {
165197

166198
@Test
167199
public void requestUriWithForwardedPrefixTrailingSlash() throws Exception {
168-
this.request.addHeader("X-Forwarded-Prefix", "/prefix/");
200+
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix/");
169201
this.request.setRequestURI("/mvc-showcase");
170202

171203
HttpServletRequest actual = filterAndGetWrappedRequest();
@@ -174,7 +206,7 @@ public void requestUriWithForwardedPrefixTrailingSlash() throws Exception {
174206

175207
@Test
176208
public void contextPathWithForwardedPrefix() throws Exception {
177-
this.request.addHeader("X-Forwarded-Prefix", "/prefix");
209+
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix");
178210
this.request.setContextPath("/mvc-showcase");
179211

180212
String actual = filterAndGetContextPath();
@@ -183,7 +215,7 @@ public void contextPathWithForwardedPrefix() throws Exception {
183215

184216
@Test
185217
public void contextPathWithForwardedPrefixTrailingSlash() throws Exception {
186-
this.request.addHeader("X-Forwarded-Prefix", "/prefix/");
218+
this.request.addHeader(X_FORWARDED_PREFIX, "/prefix/");
187219
this.request.setContextPath("/mvc-showcase");
188220

189221
String actual = filterAndGetContextPath();

spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java

+10-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.web.servlet.support;
1818

19+
import java.util.Enumeration;
1920
import javax.servlet.http.HttpServletRequest;
2021

2122
import org.springframework.http.HttpRequest;
@@ -133,8 +134,15 @@ private static ServletUriComponentsBuilder initFromRequest(HttpServletRequest re
133134
}
134135

135136
private static String prependForwardedPrefix(HttpServletRequest request, String path) {
136-
String prefix = request.getHeader("X-Forwarded-Prefix");
137-
if (StringUtils.hasText(prefix)) {
137+
String prefix = null;
138+
Enumeration<String> names = request.getHeaderNames();
139+
while (names.hasMoreElements()) {
140+
String name = names.nextElement();
141+
if ("X-Forwarded-Prefix".equalsIgnoreCase(name)) {
142+
prefix = request.getHeader(name);
143+
}
144+
}
145+
if (prefix != null) {
138146
path = prefix + path;
139147
}
140148
return path;

0 commit comments

Comments
 (0)