Add ForwardedHeaderUtils
Closes gh-30886
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -97,37 +97,47 @@ public class ServletServerHttpRequest implements ServerHttpRequest {
|
||||
@Override
|
||||
public URI getURI() {
|
||||
if (this.uri == null) {
|
||||
String urlString = null;
|
||||
boolean hasQuery = false;
|
||||
try {
|
||||
StringBuffer url = this.servletRequest.getRequestURL();
|
||||
String query = this.servletRequest.getQueryString();
|
||||
hasQuery = StringUtils.hasText(query);
|
||||
if (hasQuery) {
|
||||
url.append('?').append(query);
|
||||
}
|
||||
urlString = url.toString();
|
||||
this.uri = new URI(urlString);
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
if (!hasQuery) {
|
||||
throw new IllegalStateException(
|
||||
"Could not resolve HttpServletRequest as URI: " + urlString, ex);
|
||||
}
|
||||
// Maybe a malformed query string... try plain request URL
|
||||
try {
|
||||
urlString = this.servletRequest.getRequestURL().toString();
|
||||
this.uri = new URI(urlString);
|
||||
}
|
||||
catch (URISyntaxException ex2) {
|
||||
throw new IllegalStateException(
|
||||
"Could not resolve HttpServletRequest as URI: " + urlString, ex2);
|
||||
}
|
||||
}
|
||||
this.uri = initURI(this.servletRequest);
|
||||
}
|
||||
return this.uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize a URI from the given Servet request.
|
||||
* @param servletRequest the request
|
||||
* @return the initialized URI
|
||||
* @since 6.1
|
||||
*/
|
||||
public static URI initURI(HttpServletRequest servletRequest) {
|
||||
String urlString = null;
|
||||
boolean hasQuery = false;
|
||||
try {
|
||||
StringBuffer url = servletRequest.getRequestURL();
|
||||
String query = servletRequest.getQueryString();
|
||||
hasQuery = StringUtils.hasText(query);
|
||||
if (hasQuery) {
|
||||
url.append('?').append(query);
|
||||
}
|
||||
urlString = url.toString();
|
||||
return new URI(urlString);
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
if (!hasQuery) {
|
||||
throw new IllegalStateException(
|
||||
"Could not resolve HttpServletRequest as URI: " + urlString, ex);
|
||||
}
|
||||
// Maybe a malformed query string... try plain request URL
|
||||
try {
|
||||
urlString = servletRequest.getRequestURL().toString();
|
||||
return new URI(urlString);
|
||||
}
|
||||
catch (URISyntaxException ex2) {
|
||||
throw new IllegalStateException(
|
||||
"Could not resolve HttpServletRequest as URI: " + urlString, ex2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpHeaders getHeaders() {
|
||||
if (this.headers == null) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.web.filter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Locale;
|
||||
@@ -31,12 +32,14 @@ import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletResponseWrapper;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.ForwardedHeaderUtils;
|
||||
import org.springframework.web.util.UriComponents;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
@@ -236,7 +239,9 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
|
||||
super(servletRequest);
|
||||
|
||||
ServerHttpRequest request = new ServletServerHttpRequest(servletRequest);
|
||||
UriComponents uriComponents = UriComponentsBuilder.fromHttpRequest(request).build();
|
||||
URI uri = request.getURI();
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
UriComponents uriComponents = ForwardedHeaderUtils.adaptFromForwardedHeaders(uri, headers).build();
|
||||
int port = uriComponents.getPort();
|
||||
|
||||
this.scheme = uriComponents.getScheme();
|
||||
@@ -244,7 +249,7 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
|
||||
this.host = uriComponents.getHost();
|
||||
this.port = (port == -1 ? (this.secure ? 443 : 80) : port);
|
||||
|
||||
this.remoteAddress = UriComponentsBuilder.parseForwardedFor(request, request.getRemoteAddress());
|
||||
this.remoteAddress = ForwardedHeaderUtils.parseForwardedFor(uri, headers, request.getRemoteAddress());
|
||||
|
||||
String baseUrl = this.scheme + "://" + this.host + (port == -1 ? "" : ":" + port);
|
||||
Supplier<HttpServletRequest> delegateRequest = () -> (HttpServletRequest) getRequest();
|
||||
@@ -453,8 +458,11 @@ public class ForwardedHeaderFilter extends OncePerRequestFilter {
|
||||
StringUtils.applyRelativePath(this.request.getRequestURI(), path));
|
||||
}
|
||||
|
||||
String result = UriComponentsBuilder
|
||||
.fromHttpRequest(new ServletServerHttpRequest(this.request))
|
||||
ServletServerHttpRequest httpRequest = new ServletServerHttpRequest(this.request);
|
||||
URI uri = httpRequest.getURI();
|
||||
HttpHeaders headers = httpRequest.getHeaders();
|
||||
|
||||
String result = ForwardedHeaderUtils.adaptFromForwardedHeaders(uri, headers)
|
||||
.replacePath(path)
|
||||
.replaceQuery(uriComponents.getQuery())
|
||||
.fragment(uriComponents.getFragment())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -29,7 +29,7 @@ import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
import org.springframework.web.util.ForwardedHeaderUtils;
|
||||
|
||||
/**
|
||||
* Extract values from "Forwarded" and "X-Forwarded-*" headers to override
|
||||
@@ -100,7 +100,9 @@ public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, S
|
||||
if (hasForwardedHeaders(request)) {
|
||||
ServerHttpRequest.Builder builder = request.mutate();
|
||||
if (!this.removeOnly) {
|
||||
URI uri = UriComponentsBuilder.fromHttpRequest(request).build(true).toUri();
|
||||
URI originalUri = request.getURI();
|
||||
HttpHeaders headers = request.getHeaders();
|
||||
URI uri = ForwardedHeaderUtils.adaptFromForwardedHeaders(originalUri, headers).build(true).toUri();
|
||||
builder.uri(uri);
|
||||
String prefix = getForwardedPrefix(request);
|
||||
if (prefix != null) {
|
||||
@@ -108,7 +110,7 @@ public class ForwardedHeaderTransformer implements Function<ServerHttpRequest, S
|
||||
builder.contextPath(prefix);
|
||||
}
|
||||
InetSocketAddress remoteAddress = request.getRemoteAddress();
|
||||
remoteAddress = UriComponentsBuilder.parseForwardedFor(request, remoteAddress);
|
||||
remoteAddress = ForwardedHeaderUtils.parseForwardedFor(originalUri, headers, remoteAddress);
|
||||
if (remoteAddress != null) {
|
||||
builder.remoteAddress(remoteAddress);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2002-2023 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
|
||||
*
|
||||
* https://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.util;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.URI;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Utility class to assist with processing "Forwarded" and "X-Forwarded-*" headers.
|
||||
*
|
||||
* <p><strong>Note:</strong>There are security considerations surrounding the use
|
||||
* of forwarded headers. Those should not be used unless the application is
|
||||
* behind a trusted proxy that inserts them and also explicitly removes any such
|
||||
* headers coming from an external source.
|
||||
*
|
||||
* <p>In most cases, should not use this class directly but rely on
|
||||
* {@link org.springframework.web.filter.ForwardedHeaderFilter} for Spring MVC, or
|
||||
* {@link org.springframework.web.server.adapter.ForwardedHeaderTransformer} in
|
||||
* order to extract the information from them as early as possible, and discard
|
||||
* such headers. Underlying servers such as Tomcat, Jetty, Reactor Netty, also
|
||||
* provides options to handle forwarded headers even earlier.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 6.1
|
||||
*/
|
||||
public abstract class ForwardedHeaderUtils {
|
||||
|
||||
private static final String FORWARDED_VALUE = "\"?([^;,\"]+)\"?";
|
||||
|
||||
private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("(?i:host)=" + FORWARDED_VALUE);
|
||||
|
||||
private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("(?i:proto)=" + FORWARDED_VALUE);
|
||||
|
||||
private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:for)=" + FORWARDED_VALUE);
|
||||
|
||||
|
||||
/**
|
||||
* Adapt the scheme+host+port of the given {@link URI} from the "Forwarded" header,
|
||||
* see <a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>, or
|
||||
* "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if "Forwarded"
|
||||
* is not present.
|
||||
* @param headers the HTTP headers to consider
|
||||
* @return a {@link UriComponentsBuilder} that reflects the request URI and
|
||||
* additional updates from forwarded headers
|
||||
*/
|
||||
public static UriComponentsBuilder adaptFromForwardedHeaders(URI uri, HttpHeaders headers) {
|
||||
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromUri(uri);
|
||||
try {
|
||||
String forwardedHeader = headers.getFirst("Forwarded");
|
||||
if (StringUtils.hasText(forwardedHeader)) {
|
||||
Matcher matcher = FORWARDED_PROTO_PATTERN.matcher(forwardedHeader);
|
||||
if (matcher.find()) {
|
||||
uriComponentsBuilder.scheme(matcher.group(1).trim());
|
||||
uriComponentsBuilder.port(null);
|
||||
}
|
||||
else if (isForwardedSslOn(headers)) {
|
||||
uriComponentsBuilder.scheme("https");
|
||||
uriComponentsBuilder.port(null);
|
||||
}
|
||||
matcher = FORWARDED_HOST_PATTERN.matcher(forwardedHeader);
|
||||
if (matcher.find()) {
|
||||
adaptForwardedHost(uriComponentsBuilder, matcher.group(1).trim());
|
||||
}
|
||||
}
|
||||
else {
|
||||
String protocolHeader = headers.getFirst("X-Forwarded-Proto");
|
||||
if (StringUtils.hasText(protocolHeader)) {
|
||||
uriComponentsBuilder.scheme(StringUtils.tokenizeToStringArray(protocolHeader, ",")[0]);
|
||||
uriComponentsBuilder.port(null);
|
||||
}
|
||||
else if (isForwardedSslOn(headers)) {
|
||||
uriComponentsBuilder.scheme("https");
|
||||
uriComponentsBuilder.port(null);
|
||||
}
|
||||
String hostHeader = headers.getFirst("X-Forwarded-Host");
|
||||
if (StringUtils.hasText(hostHeader)) {
|
||||
adaptForwardedHost(uriComponentsBuilder, StringUtils.tokenizeToStringArray(hostHeader, ",")[0]);
|
||||
}
|
||||
String portHeader = headers.getFirst("X-Forwarded-Port");
|
||||
if (StringUtils.hasText(portHeader)) {
|
||||
uriComponentsBuilder.port(Integer.parseInt(StringUtils.tokenizeToStringArray(portHeader, ",")[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException("Failed to parse a port from \"forwarded\"-type headers. " +
|
||||
"If not behind a trusted proxy, consider using ForwardedHeaderFilter " +
|
||||
"with the removeOnly=true. Request headers: " + headers);
|
||||
}
|
||||
|
||||
uriComponentsBuilder.resetPortIfDefaultForScheme();
|
||||
|
||||
return uriComponentsBuilder;
|
||||
}
|
||||
|
||||
private static boolean isForwardedSslOn(HttpHeaders headers) {
|
||||
String forwardedSsl = headers.getFirst("X-Forwarded-Ssl");
|
||||
return StringUtils.hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on");
|
||||
}
|
||||
|
||||
private static void adaptForwardedHost(UriComponentsBuilder uriComponentsBuilder, String rawValue) {
|
||||
int portSeparatorIdx = rawValue.lastIndexOf(':');
|
||||
int squareBracketIdx = rawValue.lastIndexOf(']');
|
||||
if (portSeparatorIdx > squareBracketIdx) {
|
||||
if (squareBracketIdx == -1 && rawValue.indexOf(':') != portSeparatorIdx) {
|
||||
throw new IllegalArgumentException("Invalid IPv4 address: " + rawValue);
|
||||
}
|
||||
uriComponentsBuilder.host(rawValue.substring(0, portSeparatorIdx));
|
||||
uriComponentsBuilder.port(Integer.parseInt(rawValue, portSeparatorIdx + 1, rawValue.length(), 10));
|
||||
}
|
||||
else {
|
||||
uriComponentsBuilder.host(rawValue);
|
||||
uriComponentsBuilder.port(null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the first "Forwarded: for=..." or "X-Forwarded-For" header value to
|
||||
* an {@code InetSocketAddress} representing the address of the client.
|
||||
* @param uri the request URI
|
||||
* @param headers the request headers that may contain forwarded headers
|
||||
* @param remoteAddress the current remoteAddress
|
||||
* @return an {@code InetSocketAddress} with the extracted host and port, or
|
||||
* {@code null} if the headers are not present.
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7239#section-5.2">RFC 7239, Section 5.2</a>
|
||||
*/
|
||||
@Nullable
|
||||
public static InetSocketAddress parseForwardedFor(
|
||||
URI uri, HttpHeaders headers, @Nullable InetSocketAddress remoteAddress) {
|
||||
|
||||
int port = (remoteAddress != null ?
|
||||
remoteAddress.getPort() : "https".equals(uri.getScheme()) ? 443 : 80);
|
||||
|
||||
String forwardedHeader = headers.getFirst("Forwarded");
|
||||
if (StringUtils.hasText(forwardedHeader)) {
|
||||
String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0];
|
||||
Matcher matcher = FORWARDED_FOR_PATTERN.matcher(forwardedToUse);
|
||||
if (matcher.find()) {
|
||||
String value = matcher.group(1).trim();
|
||||
String host = value;
|
||||
int portSeparatorIdx = value.lastIndexOf(':');
|
||||
int squareBracketIdx = value.lastIndexOf(']');
|
||||
if (portSeparatorIdx > squareBracketIdx) {
|
||||
if (squareBracketIdx == -1 && value.indexOf(':') != portSeparatorIdx) {
|
||||
throw new IllegalArgumentException("Invalid IPv4 address: " + value);
|
||||
}
|
||||
host = value.substring(0, portSeparatorIdx);
|
||||
try {
|
||||
port = Integer.parseInt(value, portSeparatorIdx + 1, value.length(), 10);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to parse a port from \"forwarded\"-type header value: " + value);
|
||||
}
|
||||
}
|
||||
return InetSocketAddress.createUnresolved(host, port);
|
||||
}
|
||||
}
|
||||
|
||||
String forHeader = headers.getFirst("X-Forwarded-For");
|
||||
if (StringUtils.hasText(forHeader)) {
|
||||
String host = StringUtils.tokenizeToStringArray(forHeader, ",")[0];
|
||||
return InetSocketAddress.createUnresolved(host, port);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
* Copyright 2002-2023 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.
|
||||
@@ -104,12 +104,6 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
||||
|
||||
private static final String FORWARDED_VALUE = "\"?([^;,\"]+)\"?";
|
||||
|
||||
private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("(?i:host)=" + FORWARDED_VALUE);
|
||||
|
||||
private static final Pattern FORWARDED_PROTO_PATTERN = Pattern.compile("(?i:proto)=" + FORWARDED_VALUE);
|
||||
|
||||
private static final Pattern FORWARDED_FOR_PATTERN = Pattern.compile("(?i:for)=" + FORWARDED_VALUE);
|
||||
|
||||
private static final Object[] EMPTY_VALUES = new Object[0];
|
||||
|
||||
|
||||
@@ -326,10 +320,12 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
||||
* @param request the source request
|
||||
* @return the URI components of the URI
|
||||
* @since 4.1.5
|
||||
* @see #parseForwardedFor(HttpRequest, InetSocketAddress)
|
||||
* @deprecated in favor of {@link ForwardedHeaderUtils#adaptFromForwardedHeaders};
|
||||
* to be removed in 6.2
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
public static UriComponentsBuilder fromHttpRequest(HttpRequest request) {
|
||||
return fromUri(request.getURI()).adaptFromForwardedHeaders(request.getHeaders());
|
||||
return ForwardedHeaderUtils.adaptFromForwardedHeaders(request.getURI(), request.getHeaders());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -340,48 +336,16 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
||||
* @return an {@code InetSocketAddress} with the extracted host and port, or
|
||||
* {@code null} if the headers are not present.
|
||||
* @since 5.3
|
||||
* @see <a href="https://tools.ietf.org/html/rfc7239#section-5.2">RFC 7239, Section 5.2</a>
|
||||
* @deprecated in favor of {@link ForwardedHeaderUtils#adaptFromForwardedHeaders};
|
||||
* to be removed in 6.2
|
||||
*/
|
||||
@Deprecated(since = "6.1", forRemoval = true)
|
||||
@Nullable
|
||||
public static InetSocketAddress parseForwardedFor(
|
||||
HttpRequest request, @Nullable InetSocketAddress remoteAddress) {
|
||||
|
||||
int port = (remoteAddress != null ?
|
||||
remoteAddress.getPort() : "https".equals(request.getURI().getScheme()) ? 443 : 80);
|
||||
|
||||
String forwardedHeader = request.getHeaders().getFirst("Forwarded");
|
||||
if (StringUtils.hasText(forwardedHeader)) {
|
||||
String forwardedToUse = StringUtils.tokenizeToStringArray(forwardedHeader, ",")[0];
|
||||
Matcher matcher = FORWARDED_FOR_PATTERN.matcher(forwardedToUse);
|
||||
if (matcher.find()) {
|
||||
String value = matcher.group(1).trim();
|
||||
String host = value;
|
||||
int portSeparatorIdx = value.lastIndexOf(':');
|
||||
int squareBracketIdx = value.lastIndexOf(']');
|
||||
if (portSeparatorIdx > squareBracketIdx) {
|
||||
if (squareBracketIdx == -1 && value.indexOf(':') != portSeparatorIdx) {
|
||||
throw new IllegalArgumentException("Invalid IPv4 address: " + value);
|
||||
}
|
||||
host = value.substring(0, portSeparatorIdx);
|
||||
try {
|
||||
port = Integer.parseInt(value, portSeparatorIdx + 1, value.length(), 10);
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException(
|
||||
"Failed to parse a port from \"forwarded\"-type header value: " + value);
|
||||
}
|
||||
}
|
||||
return InetSocketAddress.createUnresolved(host, port);
|
||||
}
|
||||
}
|
||||
|
||||
String forHeader = request.getHeaders().getFirst("X-Forwarded-For");
|
||||
if (StringUtils.hasText(forHeader)) {
|
||||
String host = StringUtils.tokenizeToStringArray(forHeader, ",")[0];
|
||||
return InetSocketAddress.createUnresolved(host, port);
|
||||
}
|
||||
|
||||
return null;
|
||||
return ForwardedHeaderUtils.parseForwardedFor(
|
||||
request.getURI(), request.getHeaders(), remoteAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -824,94 +788,6 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adapt this builder's scheme+host+port from the given headers, specifically
|
||||
* "Forwarded" (<a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>,
|
||||
* or "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if
|
||||
* "Forwarded" is not found.
|
||||
* <p><strong>Note:</strong> this method uses values from forwarded headers,
|
||||
* if present, in order to reflect the client-originated protocol and address.
|
||||
* Consider using the {@code ForwardedHeaderFilter} in order to choose from a
|
||||
* central place whether to extract and use, or to discard such headers.
|
||||
* See the Spring Framework reference for more on this filter.
|
||||
* @param headers the HTTP headers to consider
|
||||
* @return this UriComponentsBuilder
|
||||
* @since 4.2.7
|
||||
*/
|
||||
UriComponentsBuilder adaptFromForwardedHeaders(HttpHeaders headers) {
|
||||
try {
|
||||
String forwardedHeader = headers.getFirst("Forwarded");
|
||||
if (StringUtils.hasText(forwardedHeader)) {
|
||||
Matcher matcher = FORWARDED_PROTO_PATTERN.matcher(forwardedHeader);
|
||||
if (matcher.find()) {
|
||||
scheme(matcher.group(1).trim());
|
||||
port(null);
|
||||
}
|
||||
else if (isForwardedSslOn(headers)) {
|
||||
scheme("https");
|
||||
port(null);
|
||||
}
|
||||
matcher = FORWARDED_HOST_PATTERN.matcher(forwardedHeader);
|
||||
if (matcher.find()) {
|
||||
adaptForwardedHost(matcher.group(1).trim());
|
||||
}
|
||||
}
|
||||
else {
|
||||
String protocolHeader = headers.getFirst("X-Forwarded-Proto");
|
||||
if (StringUtils.hasText(protocolHeader)) {
|
||||
scheme(StringUtils.tokenizeToStringArray(protocolHeader, ",")[0]);
|
||||
port(null);
|
||||
}
|
||||
else if (isForwardedSslOn(headers)) {
|
||||
scheme("https");
|
||||
port(null);
|
||||
}
|
||||
String hostHeader = headers.getFirst("X-Forwarded-Host");
|
||||
if (StringUtils.hasText(hostHeader)) {
|
||||
adaptForwardedHost(StringUtils.tokenizeToStringArray(hostHeader, ",")[0]);
|
||||
}
|
||||
String portHeader = headers.getFirst("X-Forwarded-Port");
|
||||
if (StringUtils.hasText(portHeader)) {
|
||||
port(Integer.parseInt(StringUtils.tokenizeToStringArray(portHeader, ",")[0]));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException ex) {
|
||||
throw new IllegalArgumentException("Failed to parse a port from \"forwarded\"-type headers. " +
|
||||
"If not behind a trusted proxy, consider using ForwardedHeaderFilter " +
|
||||
"with the removeOnly=true. Request headers: " + headers);
|
||||
}
|
||||
|
||||
if (this.scheme != null &&
|
||||
(((this.scheme.equals("http") || this.scheme.equals("ws")) && "80".equals(this.port)) ||
|
||||
((this.scheme.equals("https") || this.scheme.equals("wss")) && "443".equals(this.port)))) {
|
||||
port(null);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private boolean isForwardedSslOn(HttpHeaders headers) {
|
||||
String forwardedSsl = headers.getFirst("X-Forwarded-Ssl");
|
||||
return StringUtils.hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on");
|
||||
}
|
||||
|
||||
private void adaptForwardedHost(String rawValue) {
|
||||
int portSeparatorIdx = rawValue.lastIndexOf(':');
|
||||
int squareBracketIdx = rawValue.lastIndexOf(']');
|
||||
if (portSeparatorIdx > squareBracketIdx) {
|
||||
if (squareBracketIdx == -1 && rawValue.indexOf(':') != portSeparatorIdx) {
|
||||
throw new IllegalArgumentException("Invalid IPv4 address: " + rawValue);
|
||||
}
|
||||
host(rawValue.substring(0, portSeparatorIdx));
|
||||
port(Integer.parseInt(rawValue, portSeparatorIdx + 1, rawValue.length(), 10));
|
||||
}
|
||||
else {
|
||||
host(rawValue);
|
||||
port(null);
|
||||
}
|
||||
}
|
||||
|
||||
private void resetHierarchicalComponents() {
|
||||
this.userInfo = null;
|
||||
this.host = null;
|
||||
@@ -924,6 +800,14 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
||||
this.ssp = null;
|
||||
}
|
||||
|
||||
void resetPortIfDefaultForScheme() {
|
||||
if (this.scheme != null &&
|
||||
(((this.scheme.equals("http") || this.scheme.equals("ws")) && "80".equals(this.port)) ||
|
||||
((this.scheme.equals("https") || this.scheme.equals("wss")) && "443".equals(this.port)))) {
|
||||
port(null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Public declaration of Object's {@code clone()} method.
|
||||
|
||||
Reference in New Issue
Block a user