|
16 | 16 |
|
17 | 17 | package org.springframework.web.util;
|
18 | 18 |
|
| 19 | +import java.net.InetSocketAddress; |
19 | 20 | import java.net.URI;
|
20 | 21 | import java.nio.charset.Charset;
|
21 | 22 | import java.nio.charset.StandardCharsets;
|
@@ -97,13 +98,13 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable {
|
97 | 98 | "^" + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" +
|
98 | 99 | PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");
|
99 | 100 |
|
100 |
| - private static final Pattern FORWARDED_HOST_PATTERN = Pattern.compile("(?i:host)=\"?([^;,\"]+)\"?"); |
| 101 | + private static final String FORWARDED_VALUE = "\"?([^;,\"]+)\"?"; |
101 | 102 |
|
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); |
103 | 104 |
|
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); |
105 | 106 |
|
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); |
107 | 108 |
|
108 | 109 | private static final Object[] EMPTY_VALUES = new Object[0];
|
109 | 110 |
|
@@ -308,11 +309,54 @@ public static UriComponentsBuilder fromHttpUrl(String httpUrl) {
|
308 | 309 | * @param request the source request
|
309 | 310 | * @return the URI components of the URI
|
310 | 311 | * @since 4.1.5
|
| 312 | + * @see #parseForwardedFor(HttpRequest, InetSocketAddress) |
311 | 313 | */
|
312 | 314 | public static UriComponentsBuilder fromHttpRequest(HttpRequest request) {
|
313 | 315 | return fromUri(request.getURI()).adaptFromForwardedHeaders(request.getHeaders());
|
314 | 316 | }
|
315 | 317 |
|
| 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 | + |
316 | 360 | /**
|
317 | 361 | * Create an instance by parsing the "Origin" header of an HTTP request.
|
318 | 362 | * @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454</a>
|
@@ -727,33 +771,6 @@ public UriComponentsBuilder uriVariables(Map<String, Object> uriVariables) {
|
727 | 771 | return this;
|
728 | 772 | }
|
729 | 773 |
|
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 |
| - |
757 | 774 | /**
|
758 | 775 | * Adapt this builder's scheme+host+port from the given headers, specifically
|
759 | 776 | * "Forwarded" (<a href="https://tools.ietf.org/html/rfc7239">RFC 7239</a>,
|
@@ -828,36 +845,18 @@ private boolean isForwardedSslOn(HttpHeaders headers) {
|
828 | 845 | return StringUtils.hasText(forwardedSsl) && forwardedSsl.equalsIgnoreCase("on");
|
829 | 846 | }
|
830 | 847 |
|
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))); |
836 | 853 | }
|
837 | 854 | else {
|
838 |
| - host(hostToUse); |
| 855 | + host(rawValue); |
839 | 856 | port(null);
|
840 | 857 | }
|
841 | 858 | }
|
842 | 859 |
|
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 |
| - |
861 | 860 | private void resetHierarchicalComponents() {
|
862 | 861 | this.userInfo = null;
|
863 | 862 | this.host = null;
|
|
0 commit comments