diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index 6f9883b33e..9b29f13762 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -724,6 +724,11 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { * "Forwarded" (RFC 7239, * or "X-Forwarded-Host", "X-Forwarded-Port", and "X-Forwarded-Proto" if * "Forwarded" is not found. + *

Note: 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 diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java index 8f50db6319..237ad3d9e0 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/ServletUriComponentsBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2018 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. @@ -80,12 +80,18 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { /** * Prepare a builder from the host, port, scheme, and context path of the * given HttpServletRequest. + * *

Note: This method extracts values from "Forwarded" * and "X-Forwarded-*" headers if found. See class-level docs. + * + *

As of 4.3.15, this method replaces the contextPath with the value + * of "X-Forwarded-Prefix" rather than prepending, thus aligning with + * {@code ForwardedHeaderFiller}. */ public static ServletUriComponentsBuilder fromContextPath(HttpServletRequest request) { ServletUriComponentsBuilder builder = initFromRequest(request); - builder.replacePath(prependForwardedPrefix(request, request.getContextPath())); + String forwardedPrefix = getForwardedPrefix(request); + builder.replacePath(forwardedPrefix != null ? forwardedPrefix : request.getContextPath()); return builder; } @@ -96,8 +102,13 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { * will end with "/main". If the servlet is mapped otherwise, e.g. * {@code "/"} or {@code "*.do"}, the result will be the same as * if calling {@link #fromContextPath(HttpServletRequest)}. + * *

Note: This method extracts values from "Forwarded" * and "X-Forwarded-*" headers if found. See class-level docs. + * + *

As of 4.3.15, this method replaces the contextPath with the value + * of "X-Forwarded-Prefix" rather than prepending, thus aligning with + * {@code ForwardedHeaderFiller}. */ public static ServletUriComponentsBuilder fromServletMapping(HttpServletRequest request) { ServletUriComponentsBuilder builder = fromContextPath(request); @@ -110,24 +121,34 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { /** * Prepare a builder from the host, port, scheme, and path (but not the query) * of the HttpServletRequest. + * *

Note: This method extracts values from "Forwarded" * and "X-Forwarded-*" headers if found. See class-level docs. + * + *

As of 4.3.15, this method replaces the contextPath with the value + * of "X-Forwarded-Prefix" rather than prepending, thus aligning with + * {@code ForwardedHeaderFiller}. */ public static ServletUriComponentsBuilder fromRequestUri(HttpServletRequest request) { ServletUriComponentsBuilder builder = initFromRequest(request); - builder.initPath(prependForwardedPrefix(request, request.getRequestURI())); + builder.initPath(getRequestUriWithForwardedPrefix(request)); return builder; } /** * Prepare a builder by copying the scheme, host, port, path, and * query string of an HttpServletRequest. + * *

Note: This method extracts values from "Forwarded" * and "X-Forwarded-*" headers if found. See class-level docs. + * + *

As of 4.3.15, this method replaces the contextPath with the value + * of "X-Forwarded-Prefix" rather than prepending, thus aligning with + * {@code ForwardedHeaderFiller}. */ public static ServletUriComponentsBuilder fromRequest(HttpServletRequest request) { ServletUriComponentsBuilder builder = initFromRequest(request); - builder.initPath(prependForwardedPrefix(request, request.getRequestURI())); + builder.initPath(getRequestUriWithForwardedPrefix(request)); builder.query(request.getQueryString()); return builder; } @@ -151,7 +172,8 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { return builder; } - private static String prependForwardedPrefix(HttpServletRequest request, String path) { + @Nullable + private static String getForwardedPrefix(HttpServletRequest request) { String prefix = null; Enumeration names = request.getHeaderNames(); while (names.hasMoreElements()) { @@ -161,7 +183,22 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { } } if (prefix != null) { - path = prefix + path; + while (prefix.endsWith("/")) { + prefix = prefix.substring(0, prefix.length() - 1); + } + } + return prefix; + } + + private static String getRequestUriWithForwardedPrefix(HttpServletRequest request) { + String path = request.getRequestURI(); + String forwardedPrefix = getForwardedPrefix(request); + if (forwardedPrefix != null) { + String contextPath = request.getContextPath(); + if (!StringUtils.isEmpty(contextPath) && !contextPath.equals("/") && path.startsWith(contextPath)) { + path = path.substring(contextPath.length()); + } + path = forwardedPrefix + path; } return path; } @@ -172,8 +209,13 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { /** * Same as {@link #fromContextPath(HttpServletRequest)} except the * request is obtained through {@link RequestContextHolder}. + * *

Note: This method extracts values from "Forwarded" * and "X-Forwarded-*" headers if found. See class-level docs. + * + *

As of 4.3.15, this method replaces the contextPath with the value + * of "X-Forwarded-Prefix" rather than prepending, thus aligning with + * {@code ForwardedHeaderFiller}. */ public static ServletUriComponentsBuilder fromCurrentContextPath() { return fromContextPath(getCurrentRequest()); @@ -182,8 +224,13 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { /** * Same as {@link #fromServletMapping(HttpServletRequest)} except the * request is obtained through {@link RequestContextHolder}. + * *

Note: This method extracts values from "Forwarded" * and "X-Forwarded-*" headers if found. See class-level docs. + * + *

As of 4.3.15, this method replaces the contextPath with the value + * of "X-Forwarded-Prefix" rather than prepending, thus aligning with + * {@code ForwardedHeaderFiller}. */ public static ServletUriComponentsBuilder fromCurrentServletMapping() { return fromServletMapping(getCurrentRequest()); @@ -192,8 +239,13 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { /** * Same as {@link #fromRequestUri(HttpServletRequest)} except the * request is obtained through {@link RequestContextHolder}. + * *

Note: This method extracts values from "Forwarded" * and "X-Forwarded-*" headers if found. See class-level docs. + * + *

As of 4.3.15, this method replaces the contextPath with the value + * of "X-Forwarded-Prefix" rather than prepending, thus aligning with + * {@code ForwardedHeaderFiller}. */ public static ServletUriComponentsBuilder fromCurrentRequestUri() { return fromRequestUri(getCurrentRequest()); @@ -202,8 +254,13 @@ public class ServletUriComponentsBuilder extends UriComponentsBuilder { /** * Same as {@link #fromRequest(HttpServletRequest)} except the * request is obtained through {@link RequestContextHolder}. + * *

Note: This method extracts values from "Forwarded" * and "X-Forwarded-*" headers if found. See class-level docs. + * + *

As of 4.3.15, this method replaces the contextPath with the value + * of "X-Forwarded-Prefix" rather than prepending, thus aligning with + * {@code ForwardedHeaderFiller}. */ public static ServletUriComponentsBuilder fromCurrentRequest() { return fromRequest(getCurrentRequest()); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java index 23f45e603b..605c03ff9c 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/ServletUriComponentsBuilderTests.java @@ -102,20 +102,31 @@ public class ServletUriComponentsBuilderTests { assertEquals("http://localhost/mvc-showcase/data/param", result); } - @Test + @Test // SPR-16650 public void fromRequestWithForwardedPrefix() { - this.request.setRequestURI("/bar"); - this.request.addHeader("X-Forwarded-Prefix", "/foo"); + this.request.addHeader("X-Forwarded-Prefix", "/prefix"); + this.request.setContextPath("/mvc-showcase"); + this.request.setRequestURI("/mvc-showcase/bar"); + UriComponents result = ServletUriComponentsBuilder.fromRequest(this.request).build(); + assertEquals("http://localhost/prefix/bar", result.toUriString()); + } + + @Test // SPR-16650 + public void fromRequestWithForwardedPrefixTrailingSlash() { + this.request.addHeader("X-Forwarded-Prefix", "/foo/"); + this.request.setContextPath("/spring-mvc-showcase"); + this.request.setRequestURI("/spring-mvc-showcase/bar"); UriComponents result = ServletUriComponentsBuilder.fromRequest(this.request).build(); assertEquals("http://localhost/foo/bar", result.toUriString()); } - @Test - public void fromRequestWithForwardedPrefixTrailingSlash() { - this.request.setRequestURI("/bar"); - this.request.addHeader("X-Forwarded-Prefix", "/foo/"); + @Test // SPR-16650 + public void fromRequestWithForwardedPrefixRoot() { + this.request.addHeader("X-Forwarded-Prefix", "/"); + this.request.setContextPath("/mvc-showcase"); + this.request.setRequestURI("/mvc-showcase/bar"); UriComponents result = ServletUriComponentsBuilder.fromRequest(this.request).build(); - assertEquals("http://localhost/foo/bar", result.toUriString()); + assertEquals("http://localhost/bar", result.toUriString()); } @Test @@ -126,13 +137,13 @@ public class ServletUriComponentsBuilderTests { assertEquals("http://localhost/mvc-showcase", result); } - @Test + @Test // SPR-16650 public void fromContextPathWithForwardedPrefix() { this.request.addHeader("X-Forwarded-Prefix", "/prefix"); this.request.setContextPath("/mvc-showcase"); this.request.setRequestURI("/mvc-showcase/simple"); String result = ServletUriComponentsBuilder.fromContextPath(this.request).build().toUriString(); - assertEquals("http://localhost/prefix/mvc-showcase", result); + assertEquals("http://localhost/prefix", result); } @Test @@ -144,14 +155,14 @@ public class ServletUriComponentsBuilderTests { assertEquals("http://localhost/mvc-showcase/app", result); } - @Test + @Test // SPR-16650 public void fromServletMappingWithForwardedPrefix() { this.request.addHeader("X-Forwarded-Prefix", "/prefix"); this.request.setContextPath("/mvc-showcase"); this.request.setServletPath("/app"); this.request.setRequestURI("/mvc-showcase/app/simple"); String result = ServletUriComponentsBuilder.fromServletMapping(this.request).build().toUriString(); - assertEquals("http://localhost/prefix/mvc-showcase/app", result); + assertEquals("http://localhost/prefix/app", result); } @Test @@ -168,9 +179,7 @@ public class ServletUriComponentsBuilderTests { } } - // SPR-10272 - - @Test + @Test // SPR-10272 public void pathExtension() { this.request.setRequestURI("/rest/books/6.json"); ServletUriComponentsBuilder builder = ServletUriComponentsBuilder.fromRequestUri(this.request);