diff --git a/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java b/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java index 8daaf768a01..cfb5e7ac3f8 100644 --- a/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java +++ b/spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java @@ -48,6 +48,8 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory { private EncodingMode encodingMode = EncodingMode.URI_COMPONENT; + private boolean parsePath = true; + /** * Default constructor without a base URI. @@ -126,6 +128,28 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory { return this.encodingMode; } + /** + * Whether to parse the path into path segments for the URI string passed + * into {@link #uriString(String)} or one of the expand methods. + *

Setting this property to {@code true} ensures that URI variables + * expanded into the path are subject to path segment encoding rules and + * "/" characters are percent-encoded. If set to {@code false} the path is + * kept as a full path and expanded URI variables will have "/" characters + * preserved. + *

By default this is set to {@code true}. + * @param parsePath whether to parse the path into path segments + */ + public void setParsePath(boolean parsePath) { + this.parsePath = parsePath; + } + + /** + * Whether the handler is configured to parse the path into path segments. + */ + public boolean shouldParsePath() { + return this.parsePath; + } + // UriTemplateHandler @@ -158,16 +182,22 @@ public class DefaultUriBuilderFactory implements UriBuilderFactory { private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) { - // Merge base URI with child URI template UriComponentsBuilder result = baseUri.cloneBuilder(); UriComponents child = UriComponentsBuilder.fromUriString(uriTemplate).build(); result.uriComponents(child); - // Split path into path segments - List pathList = result.build().getPathSegments(); - String[] pathArray = pathList.toArray(new String[pathList.size()]); - result.replacePath(null); - result.pathSegment(pathArray); + if (shouldParsePath()) { + UriComponents uric = result.build(); + String path = uric.getPath(); + List pathSegments = uric.getPathSegments(); + + result.replacePath(null); + result.pathSegment(pathSegments.toArray(new String[0])); + + if (path != null && path.endsWith("/")) { + result.path("/"); + } + } return result; } diff --git a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java index 1ada39213a4..f153174f576 100644 --- a/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java +++ b/spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2017 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. @@ -153,6 +153,21 @@ public class RestTemplateTests { verify(response).close(); } + @Test // SPR-15201 + public void uriTemplateWithTrailingSlash() throws Exception { + String url = "http://example.com/spring/"; + given(requestFactory.createRequest(new URI(url), HttpMethod.GET)).willReturn(request); + given(request.execute()).willReturn(response); + given(errorHandler.hasError(response)).willReturn(false); + HttpStatus status = HttpStatus.OK; + given(response.getStatusCode()).willReturn(status); + given(response.getStatusText()).willReturn(status.getReasonPhrase()); + + template.execute(url, HttpMethod.GET, null, null); + + verify(response).close(); + } + @Test public void errorHandling() throws Exception { given(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET)).willReturn(request); diff --git a/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java b/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java index c7481d7de51..cab2ad614b3 100644 --- a/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java @@ -127,10 +127,32 @@ public class DefaultUriBuilderFactoryTests { } @Test - public void initialPathSplitIntoPathSegments() throws Exception { + public void parsePathWithDefaultSettings() throws Exception { DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("/foo/{bar}"); URI uri = factory.uriString("/baz/{id}").build("a/b", "c/d"); assertEquals("/foo/a%2Fb/baz/c%2Fd", uri.toString()); } + @Test + public void parsePathIsTurnedOff() throws Exception { + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("/foo/{bar}"); + factory.setParsePath(false); + URI uri = factory.uriString("/baz/{id}").build("a/b", "c/d"); + assertEquals("/foo/a/b/baz/c/d", uri.toString()); + } + + @Test // SPR-15201 + public void pathWithTrailingSlash() throws Exception { + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(); + URI uri = factory.expand("http://localhost:8080/spring/"); + assertEquals("http://localhost:8080/spring/", uri.toString()); + } + + @Test + public void pathWithDuplicateSlashes() throws Exception { + DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(); + URI uri = factory.expand("/foo/////////bar"); + assertEquals("/foo/bar", uri.toString()); + } + }