Skip to content

Commit d627f60

Browse files
committed
Update "Forwarded: for" contribution
Closes gh-23582
1 parent 883ad09 commit d627f60

File tree

5 files changed

+507
-575
lines changed

5 files changed

+507
-575
lines changed

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

+11-38
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,14 @@
1717
package org.springframework.web.filter;
1818

1919
import java.io.IOException;
20+
import java.net.InetSocketAddress;
2021
import java.util.Collections;
2122
import java.util.Enumeration;
2223
import java.util.List;
2324
import java.util.Locale;
2425
import java.util.Map;
2526
import java.util.Set;
2627
import java.util.function.Supplier;
27-
import java.util.regex.Pattern;
2828

2929
import javax.servlet.FilterChain;
3030
import javax.servlet.ServletException;
@@ -33,9 +33,8 @@
3333
import javax.servlet.http.HttpServletResponse;
3434
import javax.servlet.http.HttpServletResponseWrapper;
3535

36-
import org.springframework.http.HttpHeaders;
37-
import org.springframework.http.HttpRequest;
3836
import org.springframework.http.HttpStatus;
37+
import org.springframework.http.server.ServerHttpRequest;
3938
import org.springframework.http.server.ServletServerHttpRequest;
4039
import org.springframework.lang.Nullable;
4140
import org.springframework.util.CollectionUtils;
@@ -220,10 +219,6 @@ public Enumeration<String> getHeaderNames() {
220219
*/
221220
private static class ForwardedHeaderExtractingRequest extends ForwardedHeaderRemovingRequest {
222221

223-
private static final String X_FORWARDED_FOR_HEADER = "X-Forwarded-For";
224-
private static final String FORWARDED_HEADER = "Forwarded";
225-
private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:^[^,]*for=.+)");
226-
227222
@Nullable
228223
private final String scheme;
229224

@@ -235,46 +230,24 @@ private static class ForwardedHeaderExtractingRequest extends ForwardedHeaderRem
235230
private final int port;
236231

237232
@Nullable
238-
private final String remoteHost;
239-
240-
@Nullable
241-
private final String remoteAddr;
242-
243-
private final int remotePort;
233+
private final InetSocketAddress remoteAddress;
244234

245235
private final ForwardedPrefixExtractor forwardedPrefixExtractor;
246236

247237

248-
ForwardedHeaderExtractingRequest(HttpServletRequest request, UrlPathHelper pathHelper) {
249-
super(request);
238+
ForwardedHeaderExtractingRequest(HttpServletRequest servletRequest, UrlPathHelper pathHelper) {
239+
super(servletRequest);
250240

251-
HttpRequest httpRequest = new ServletServerHttpRequest(request);
252-
UriComponents uriComponents = UriComponentsBuilder.fromHttpRequest(httpRequest).build();
241+
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
242+
UriComponents uriComponents = UriComponentsBuilder.fromHttpRequest(request).build();
253243
int port = uriComponents.getPort();
254244

255245
this.scheme = uriComponents.getScheme();
256246
this.secure = "https".equals(this.scheme);
257247
this.host = uriComponents.getHost();
258248
this.port = (port == -1 ? (this.secure ? 443 : 80) : port);
259249

260-
HttpHeaders headers = httpRequest.getHeaders();
261-
boolean hasForwardedFor = StringUtils.hasText(headers.getFirst(X_FORWARDED_FOR_HEADER)) ||
262-
(StringUtils.hasText(headers.getFirst(FORWARDED_HEADER)) &&
263-
FORWARDED_FOR_PATTERN.matcher(headers.getFirst(FORWARDED_HEADER)).matches());
264-
if (hasForwardedFor) {
265-
UriComponents remoteUriComponents = UriComponentsBuilder.newInstance()
266-
.host(request.getRemoteHost())
267-
.port(request.getRemotePort())
268-
.adaptFromForwardedForHeader(headers)
269-
.build();
270-
this.remoteHost = remoteUriComponents.getHost();
271-
this.remoteAddr = this.remoteHost;
272-
this.remotePort = remoteUriComponents.getPort();
273-
} else {
274-
this.remoteHost = request.getRemoteHost();
275-
this.remoteAddr = request.getRemoteAddr();
276-
this.remotePort = request.getRemotePort();
277-
}
250+
this.remoteAddress = UriComponentsBuilder.parseForwardedFor(request, request.getRemoteAddress());
278251

279252
String baseUrl = this.scheme + "://" + this.host + (port == -1 ? "" : ":" + port);
280253
Supplier<HttpServletRequest> delegateRequest = () -> (HttpServletRequest) getRequest();
@@ -322,18 +295,18 @@ public StringBuffer getRequestURL() {
322295
@Override
323296
@Nullable
324297
public String getRemoteHost() {
325-
return this.remoteHost;
298+
return (this.remoteAddress != null ? this.remoteAddress.getHostString() : super.getRemoteHost());
326299
}
327300

328301
@Override
329302
@Nullable
330303
public String getRemoteAddr() {
331-
return this.remoteAddr;
304+
return (this.remoteAddress != null ? this.remoteAddress.getHostString() : super.getRemoteAddr());
332305
}
333306

334307
@Override
335308
public int getRemotePort() {
336-
return remotePort;
309+
return (this.remoteAddress != null ? this.remoteAddress.getPort() : super.getRemotePort());
337310
}
338311
}
339312

Diff for: spring-web/src/main/java/org/springframework/web/server/adapter/ForwardedHeaderTransformer.java

+2-23
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,13 @@
2222
import java.util.Locale;
2323
import java.util.Set;
2424
import java.util.function.Function;
25-
import java.util.regex.Pattern;
2625

2726
import org.springframework.context.ApplicationContext;
2827
import org.springframework.http.HttpHeaders;
2928
import org.springframework.http.server.reactive.ServerHttpRequest;
3029
import org.springframework.lang.Nullable;
3130
import org.springframework.util.LinkedCaseInsensitiveMap;
3231
import org.springframework.util.StringUtils;
33-
import org.springframework.web.util.UriComponents;
3432
import org.springframework.web.util.UriComponentsBuilder;
3533

3634
/**
@@ -53,9 +51,6 @@
5351
*/
5452
public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, ServerHttpRequest> {
5553

56-
private static final String X_FORWARDED_FOR_HEADER = "X-Forwarded-For";
57-
private static final String FORWARDED_HEADER = "Forwarded";
58-
private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:^[^,]*for=.+)");
5954
static final Set<String> FORWARDED_HEADER_NAMES =
6055
Collections.newSetFromMap(new LinkedCaseInsensitiveMap<>(10, Locale.ENGLISH));
6156

@@ -108,24 +103,8 @@ public ServerHttpRequest apply(ServerHttpRequest request) {
108103
builder.contextPath(prefix);
109104
}
110105
InetSocketAddress remoteAddress = request.getRemoteAddress();
111-
HttpHeaders headers = request.getHeaders();
112-
boolean hasForwardedFor = StringUtils.hasText(headers.getFirst(X_FORWARDED_FOR_HEADER)) ||
113-
(StringUtils.hasText(headers.getFirst(FORWARDED_HEADER)) &&
114-
FORWARDED_FOR_PATTERN.matcher(headers.getFirst(FORWARDED_HEADER)).matches());
115-
if (hasForwardedFor) {
116-
String originalRemoteHost = ((remoteAddress != null) ? remoteAddress.getHostName() : null);
117-
int originalRemotePort = ((remoteAddress != null) ? remoteAddress.getPort() : -1);
118-
UriComponents remoteUriComponents = UriComponentsBuilder.newInstance()
119-
.host(originalRemoteHost)
120-
.port(originalRemotePort)
121-
.adaptFromForwardedForHeader(headers)
122-
.build();
123-
String remoteHost = remoteUriComponents.getHost();
124-
int remotePort = (remoteUriComponents.getPort() != -1 ? remoteUriComponents.getPort() : 0);
125-
if (remoteHost != null) {
126-
builder.remoteAddress(InetSocketAddress.createUnresolved(remoteHost, remotePort));
127-
}
128-
} else {
106+
remoteAddress = UriComponentsBuilder.parseForwardedFor(request, remoteAddress);
107+
if (remoteAddress != null) {
129108
builder.remoteAddress(remoteAddress);
130109
}
131110
}

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

+54-55
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.web.util;
1818

19+
import java.net.InetSocketAddress;
1920
import java.net.URI;
2021
import java.nio.charset.Charset;
2122
import java.nio.charset.StandardCharsets;
@@ -97,13 +98,13 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
9798
"^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" +
9899
PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
99100

100-
private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("(?i:host)=\"?([^;,\"]+)\"?");
101+
private static final String FORWARDED_VALUE = "\"?([^;,\"]+)\"?";
101102

102-
private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("(?i:proto)=\"?([^;,\"]+)\"?");
103+
private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("(?i:host)=" + FORWARDED_VALUE);
103104

104-
private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:for)=\"?([^;,\"]+)\"?");
105+
private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("(?i:proto)=" + FORWARDED_VALUE);
105106

106-
private static final String FORWARDED_FOR_NUMERIC_PORT_PATTERN = "^(\\d{1,5})$";
107+
private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:for)=" + FORWARDED_VALUE);
107108

108109
private static final Object[] EMPTY_VALUES = new Object[0];
109110

@@ -308,11 +309,54 @@ public static UriComponentsBuilder fromHttpUrl(String httpUrl) {
308309
* @param request the source request
309310
* @return the URI components of the URI
310311
* @since 4.1.5
312+
* @see #parseForwardedFor(HttpRequest, InetSocketAddress)
311313
*/
312314
public static UriComponentsBuilder fromHttpRequest(HttpRequest request) {
313315
return fromUri(request.getURI()).adaptFromForwardedHeaders(request.getHeaders());
314316
}
315317

318+
/**
319+
* Parse the first "Forwarded: for=..." or "X-Forwarded-For" header value to
320+
* an {@code InetSocketAddress} representing the address of the client.
321+
* @param request a request with headers that may contain forwarded headers
322+
* @param remoteAddress the current remoteAddress
323+
* @return an {@code InetSocketAddress} with the extracted host and port, or
324+
* {@code null} if the headers are not present.
325+
* @since 5.3
326+
* @see <a href="https://tools.ietf.org/html/rfc7239#section-5.2">RFC 7239, Section 5.2</a>
327+
*/
328+
@Nullable
329+
public static InetSocketAddress parseForwardedFor(
330+
HttpRequest request, @Nullable InetSocketAddress remoteAddress) {
331+
332+
int port = (remoteAddress != null ?
333+
remoteAddress.getPort() : "https".equals(request.getURI().getScheme()) ? 443 : 80);
334+
335+
String forwardedHeader = request.getHeaders().getFirst("Forwarded");
336+
if (StringUtils.hasText(forwardedHeader)) {
337+
String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0];
338+
Matcher matcher = FORWARDED_FOR_PATTERN.matcher(forwardedToUse);
339+
if (matcher.find()) {
340+
String value = matcher.group(1).trim();
341+
String host = value;
342+
int portSeparatorIdx = value.lastIndexOf(':');
343+
if (portSeparatorIdx > value.lastIndexOf(']')) {
344+
host = value.substring(0, portSeparatorIdx);
345+
port = Integer.parseInt(value.substring(portSeparatorIdx + 1));
346+
}
347+
return new InetSocketAddress(host, port);
348+
}
349+
}
350+
351+
String forHeader = request.getHeaders().getFirst("X-Forwarded-For");
352+
if (StringUtils.hasText(forHeader)) {
353+
String host = StringUtils.tokenizeToStringArray(forHeader, ",")[0];
354+
return new InetSocketAddress(host, port);
355+
}
356+
357+
return null;
358+
}
359+
316360
/**
317361
* Create an instance by parsing the "Origin" header of an HTTP request.
318362
* @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454</a>
@@ -727,33 +771,6 @@ public UriComponentsBuilder uriVariables(Map<String, Object> uriVariables) {
727771
return this;
728772
}
729773

730-
/**
731-
* Adapt this builders's host+port from the "for" parameter of the "Forwarded"
732-
* header or from "X-Forwarded-For" if "Forwarded" is not found. If neither
733-
* "Forwarded" nor "X-Forwarded-For" is found no changes are made to the
734-
* builder.
735-
* @param headers the HTTP headers to consider
736-
* @return this UriComponentsBuilder
737-
*/
738-
public UriComponentsBuilder adaptFromForwardedForHeader(HttpHeaders headers) {
739-
String forwardedHeader = headers.getFirst("Forwarded");
740-
if (StringUtils.hasText(forwardedHeader)) {
741-
String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0];
742-
Matcher matcher = FORWARDED_FOR_PATTERN.matcher(forwardedToUse);
743-
if (matcher.find()) {
744-
adaptForwardedForHost(matcher.group(1).trim());
745-
}
746-
}
747-
else {
748-
String forHeader = headers.getFirst("X-Forwarded-For");
749-
if (StringUtils.hasText(forHeader)) {
750-
String forwardedForToUse = StringUtils.tokenizeToStringArray(forHeader, ",")[0];
751-
host(forwardedForToUse);
752-
}
753-
}
754-
return this;
755-
}
756-
757774
/**
758775
* Adapt this builder's scheme+host+port from the given headers, specifically
759776
* "Forwarded" (<a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>,
@@ -828,36 +845,18 @@ private boolean isForwardedSslOn(HttpHeaders headers) {
828845
return StringUtils.hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on");
829846
}
830847

831-
private void adaptForwardedHost(String hostToUse) {
832-
int portSeparatorIdx = hostToUse.lastIndexOf(':');
833-
if (portSeparatorIdx > hostToUse.lastIndexOf(']')) {
834-
host(hostToUse.substring(0, portSeparatorIdx));
835-
port(Integer.parseInt(hostToUse.substring(portSeparatorIdx + 1)));
848+
private void adaptForwardedHost(String rawValue) {
849+
int portSeparatorIdx = rawValue.lastIndexOf(':');
850+
if (portSeparatorIdx > rawValue.lastIndexOf(']')) {
851+
host(rawValue.substring(0, portSeparatorIdx));
852+
port(Integer.parseInt(rawValue.substring(portSeparatorIdx + 1)));
836853
}
837854
else {
838-
host(hostToUse);
855+
host(rawValue);
839856
port(null);
840857
}
841858
}
842859

843-
private void adaptForwardedForHost(String hostToUse) {
844-
String hostName = hostToUse;
845-
int portSeparatorIdx = hostToUse.lastIndexOf(':');
846-
if (portSeparatorIdx > hostToUse.lastIndexOf(']')) {
847-
String hostPort = hostToUse.substring(portSeparatorIdx + 1);
848-
// check if port is not obfuscated
849-
if (hostPort.matches(FORWARDED_FOR_NUMERIC_PORT_PATTERN)) {
850-
port(Integer.parseInt(hostPort));
851-
}
852-
hostName = hostToUse.substring(0, portSeparatorIdx);
853-
}
854-
if (hostName.matches(HOST_IPV6_PATTERN)) {
855-
host(hostName.substring(hostName.indexOf('[') + 1, hostName.indexOf(']')));
856-
} else {
857-
host(hostName);
858-
}
859-
}
860-
861860
private void resetHierarchicalComponents() {
862861
this.userInfo = null;
863862
this.host = null;

0 commit comments

Comments
 (0)