Skip to content

Commit b487ed6

Browse files
committed
Fix trailing slash issue
Issue: SPR-15201
1 parent ee861e8 commit b487ed6

File tree

3 files changed

+75
-8
lines changed

3 files changed

+75
-8
lines changed

spring-web/src/main/java/org/springframework/web/util/DefaultUriBuilderFactory.java

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public enum EncodingMode {URI_COMPONENT, VALUES_ONLY, NONE };
4848

4949
private EncodingMode encodingMode = EncodingMode.URI_COMPONENT;
5050

51+
private boolean parsePath = true;
52+
5153

5254
/**
5355
* Default constructor without a base URI.
@@ -126,6 +128,28 @@ public EncodingMode getEncodingMode() {
126128
return this.encodingMode;
127129
}
128130

131+
/**
132+
* Whether to parse the path into path segments for the URI string passed
133+
* into {@link #uriString(String)} or one of the expand methods.
134+
* <p>Setting this property to {@code true} ensures that URI variables
135+
* expanded into the path are subject to path segment encoding rules and
136+
* "/" characters are percent-encoded. If set to {@code false} the path is
137+
* kept as a full path and expanded URI variables will have "/" characters
138+
* preserved.
139+
* <p>By default this is set to {@code true}.
140+
* @param parsePath whether to parse the path into path segments
141+
*/
142+
public void setParsePath(boolean parsePath) {
143+
this.parsePath = parsePath;
144+
}
145+
146+
/**
147+
* Whether the handler is configured to parse the path into path segments.
148+
*/
149+
public boolean shouldParsePath() {
150+
return this.parsePath;
151+
}
152+
129153

130154
// UriTemplateHandler
131155

@@ -158,16 +182,22 @@ public DefaultUriBuilder(String uriTemplate) {
158182

159183
private UriComponentsBuilder initUriComponentsBuilder(String uriTemplate) {
160184

161-
// Merge base URI with child URI template
162185
UriComponentsBuilder result = baseUri.cloneBuilder();
163186
UriComponents child = UriComponentsBuilder.fromUriString(uriTemplate).build();
164187
result.uriComponents(child);
165188

166-
// Split path into path segments
167-
List<String> pathList = result.build().getPathSegments();
168-
String[] pathArray = pathList.toArray(new String[pathList.size()]);
169-
result.replacePath(null);
170-
result.pathSegment(pathArray);
189+
if (shouldParsePath()) {
190+
UriComponents uric = result.build();
191+
String path = uric.getPath();
192+
List<String> pathSegments = uric.getPathSegments();
193+
194+
result.replacePath(null);
195+
result.pathSegment(pathSegments.toArray(new String[0]));
196+
197+
if (path != null && path.endsWith("/")) {
198+
result.path("/");
199+
}
200+
}
171201

172202
return result;
173203
}

spring-web/src/test/java/org/springframework/web/client/RestTemplateTests.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2016 the original author or authors.
2+
* Copyright 2002-2017 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -153,6 +153,21 @@ public void mapNullTemplateVariable() throws Exception {
153153
verify(response).close();
154154
}
155155

156+
@Test // SPR-15201
157+
public void uriTemplateWithTrailingSlash() throws Exception {
158+
String url = "http://example.com/spring/";
159+
given(requestFactory.createRequest(new URI(url), HttpMethod.GET)).willReturn(request);
160+
given(request.execute()).willReturn(response);
161+
given(errorHandler.hasError(response)).willReturn(false);
162+
HttpStatus status = HttpStatus.OK;
163+
given(response.getStatusCode()).willReturn(status);
164+
given(response.getStatusText()).willReturn(status.getReasonPhrase());
165+
166+
template.execute(url, HttpMethod.GET, null, null);
167+
168+
verify(response).close();
169+
}
170+
156171
@Test
157172
public void errorHandling() throws Exception {
158173
given(requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET)).willReturn(request);

spring-web/src/test/java/org/springframework/web/util/DefaultUriBuilderFactoryTests.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,10 +127,32 @@ public void encodingNone() throws Exception {
127127
}
128128

129129
@Test
130-
public void initialPathSplitIntoPathSegments() throws Exception {
130+
public void parsePathWithDefaultSettings() throws Exception {
131131
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("/foo/{bar}");
132132
URI uri = factory.uriString("/baz/{id}").build("a/b", "c/d");
133133
assertEquals("/foo/a%2Fb/baz/c%2Fd", uri.toString());
134134
}
135135

136+
@Test
137+
public void parsePathIsTurnedOff() throws Exception {
138+
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory("/foo/{bar}");
139+
factory.setParsePath(false);
140+
URI uri = factory.uriString("/baz/{id}").build("a/b", "c/d");
141+
assertEquals("/foo/a/b/baz/c/d", uri.toString());
142+
}
143+
144+
@Test // SPR-15201
145+
public void pathWithTrailingSlash() throws Exception {
146+
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
147+
URI uri = factory.expand("http://localhost:8080/spring/");
148+
assertEquals("http://localhost:8080/spring/", uri.toString());
149+
}
150+
151+
@Test
152+
public void pathWithDuplicateSlashes() throws Exception {
153+
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory();
154+
URI uri = factory.expand("/foo/////////bar");
155+
assertEquals("/foo/bar", uri.toString());
156+
}
157+
136158
}

0 commit comments

Comments
 (0)