|
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