Skip to content

Commit 4611d05

Browse files
poutsmarstoyanchev
authored andcommitted
Support Forwarded-Header in UriComponentsBuilder
This commit introduces support for RFC 7239: Forwarded HTTP Extension in the UriComponentsBuilder. Unfortunately, RFC 7239 is not a complete replacement for the X-Forwarded-* headers: specifically, there is not direct replacement for X-Forwarded-Port. The JIRA contains more information. Issue: SPR-11856
1 parent b02352d commit 4611d05

File tree

2 files changed

+109
-21
lines changed

2 files changed

+109
-21
lines changed

spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ public class UriComponentsBuilder implements Cloneable {
8888
"^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" +
8989
PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
9090

91+
private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("host=\"?([^;,\"]+)\"?");
92+
93+
private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("proto=\"?([^;,\"]+)\"?");
94+
9195

9296
private String scheme;
9397

@@ -267,7 +271,9 @@ public static UriComponentsBuilder fromHttpUrl(String httpUrl) {
267271
/**
268272
* Create a new {@code UriComponents} object from the URI associated with
269273
* the given HttpRequest while also overlaying with values from the headers
270-
* "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if present.
274+
* "Forwarded" (<a href="http://tools.ietf.org/html/rfc7239">RFC 7239</a>, or
275+
* "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if "Forwarded" is
276+
* not found.
271277
* @param request the source request
272278
* @return the URI components of the URI
273279
* @since 4.1.5
@@ -280,31 +286,45 @@ public static UriComponentsBuilder fromHttpRequest(HttpRequest request) {
280286
String host = uri.getHost();
281287
int port = uri.getPort();
282288

283-
String hostHeader = request.getHeaders().getFirst("X-Forwarded-Host");
284-
if (StringUtils.hasText(hostHeader)) {
285-
String[] hosts = StringUtils.commaDelimitedListToStringArray(hostHeader);
286-
String hostToUse = hosts[0];
287-
if (hostToUse.contains(":")) {
288-
String[] hostAndPort = StringUtils.split(hostToUse, ":");
289-
host = hostAndPort[0];
290-
port = Integer.parseInt(hostAndPort[1]);
289+
String forwardedHeader = request.getHeaders().getFirst("Forwarded");
290+
if (StringUtils.hasText(forwardedHeader)) {
291+
String forwardedToUse = StringUtils.commaDelimitedListToStringArray(forwardedHeader)[0];
292+
Matcher m = FORWARDED_HOST_PATTERN.matcher(forwardedToUse);
293+
if (m.find()) {
294+
host = m.group(1).trim();
291295
}
292-
else {
293-
host = hostToUse;
294-
port = -1;
296+
m = FORWARDED_PROTO_PATTERN.matcher(forwardedToUse);
297+
if (m.find()) {
298+
scheme = m.group(1).trim();
295299
}
296300
}
301+
else {
302+
String hostHeader = request.getHeaders().getFirst("X-Forwarded-Host");
303+
if (StringUtils.hasText(hostHeader)) {
304+
String[] hosts = StringUtils.commaDelimitedListToStringArray(hostHeader);
305+
String hostToUse = hosts[0];
306+
if (hostToUse.contains(":")) {
307+
String[] hostAndPort = StringUtils.split(hostToUse, ":");
308+
host = hostAndPort[0];
309+
port = Integer.parseInt(hostAndPort[1]);
310+
}
311+
else {
312+
host = hostToUse;
313+
port = -1;
314+
}
315+
}
297316

298-
String portHeader = request.getHeaders().getFirst("X-Forwarded-Port");
299-
if (StringUtils.hasText(portHeader)) {
300-
String[] ports = StringUtils.commaDelimitedListToStringArray(portHeader);
301-
port = Integer.parseInt(ports[0]);
302-
}
317+
String portHeader = request.getHeaders().getFirst("X-Forwarded-Port");
318+
if (StringUtils.hasText(portHeader)) {
319+
String[] ports = StringUtils.commaDelimitedListToStringArray(portHeader);
320+
port = Integer.parseInt(ports[0]);
321+
}
303322

304-
String protocolHeader = request.getHeaders().getFirst("X-Forwarded-Proto");
305-
if (StringUtils.hasText(protocolHeader)) {
306-
String[] protocols = StringUtils.commaDelimitedListToStringArray(protocolHeader);
307-
scheme = protocols[0];
323+
String protocolHeader = request.getHeaders().getFirst("X-Forwarded-Proto");
324+
if (StringUtils.hasText(protocolHeader)) {
325+
String[] protocols = StringUtils.commaDelimitedListToStringArray(protocolHeader);
326+
scheme = protocols[0];
327+
}
308328
}
309329

310330
builder.scheme(scheme);

spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -673,4 +673,72 @@ public void testClone() throws URISyntaxException {
673673
assertEquals("f2", result2.getFragment());
674674
}
675675

676+
// SPR-11856
677+
678+
@Test
679+
public void fromHttpRequestForwardedHeader() throws Exception {
680+
MockHttpServletRequest request = new MockHttpServletRequest();
681+
request.addHeader("Forwarded", "proto=https; host=84.198.58.199");
682+
request.setScheme("http");
683+
request.setServerName("example.com");
684+
request.setRequestURI("/rest/mobile/users/1");
685+
686+
HttpRequest httpRequest = new ServletServerHttpRequest(request);
687+
UriComponents result = UriComponentsBuilder.fromHttpRequest(httpRequest).build();
688+
689+
assertEquals("https", result.getScheme());
690+
assertEquals("84.198.58.199", result.getHost());
691+
assertEquals("/rest/mobile/users/1", result.getPath());
692+
}
693+
694+
@Test
695+
public void fromHttpRequestForwardedHeaderQuoted() throws Exception {
696+
MockHttpServletRequest request = new MockHttpServletRequest();
697+
request.addHeader("Forwarded", "proto=\"https\"; host=\"84.198.58.199\"");
698+
request.setScheme("http");
699+
request.setServerName("example.com");
700+
request.setRequestURI("/rest/mobile/users/1");
701+
702+
HttpRequest httpRequest = new ServletServerHttpRequest(request);
703+
UriComponents result = UriComponentsBuilder.fromHttpRequest(httpRequest).build();
704+
705+
assertEquals("https", result.getScheme());
706+
assertEquals("84.198.58.199", result.getHost());
707+
assertEquals("/rest/mobile/users/1", result.getPath());
708+
}
709+
710+
@Test
711+
public void fromHttpRequestMultipleForwardedHeader() throws Exception {
712+
MockHttpServletRequest request = new MockHttpServletRequest();
713+
request.addHeader("Forwarded", "host=84.198.58.199;proto=https");
714+
request.addHeader("Forwarded", "proto=ftp; host=1.2.3.4");
715+
request.setScheme("http");
716+
request.setServerName("example.com");
717+
request.setRequestURI("/rest/mobile/users/1");
718+
719+
HttpRequest httpRequest = new ServletServerHttpRequest(request);
720+
UriComponents result = UriComponentsBuilder.fromHttpRequest(httpRequest).build();
721+
722+
assertEquals("https", result.getScheme());
723+
assertEquals("84.198.58.199", result.getHost());
724+
assertEquals("/rest/mobile/users/1", result.getPath());
725+
}
726+
727+
@Test
728+
public void fromHttpRequestMultipleForwardedHeaderComma() throws Exception {
729+
MockHttpServletRequest request = new MockHttpServletRequest();
730+
request.addHeader("Forwarded", "host=84.198.58.199 ;proto=https, proto=ftp; host=1.2.3.4");
731+
request.setScheme("http");
732+
request.setServerName("example.com");
733+
request.setRequestURI("/rest/mobile/users/1");
734+
735+
HttpRequest httpRequest = new ServletServerHttpRequest(request);
736+
UriComponents result = UriComponentsBuilder.fromHttpRequest(httpRequest).build();
737+
738+
assertEquals("https", result.getScheme());
739+
assertEquals("84.198.58.199", result.getHost());
740+
assertEquals("/rest/mobile/users/1", result.getPath());
741+
}
742+
743+
676744
}

0 commit comments

Comments
 (0)