Skip to content

Commit 5b965ec

Browse files
committed
Make multiple slash sanitization optional in web util
Closes gh-34076 Signed-off-by: vishnugt <[email protected]>
1 parent 8c03d55 commit 5b965ec

File tree

4 files changed

+66
-15
lines changed

4 files changed

+66
-15
lines changed

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

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -284,32 +284,42 @@ public UriComponentsBuilder encode(Charset charset) {
284284
* @return the URI components
285285
*/
286286
public UriComponents build() {
287-
return build(false);
287+
return build(false, true);
288+
}
289+
290+
public UriComponents build(boolean encoded) {
291+
return build(encoded, true);
288292
}
289293

290294
/**
291295
* Variant of {@link #build()} to create a {@link UriComponents} instance
292296
* when components are already fully encoded. This is useful for example if
293297
* the builder was created via {@link UriComponentsBuilder#fromUri(URI)}.
294298
* @param encoded whether the components in this builder are already encoded
299+
* @param sanitizePath whether the double slashes should be replaced with single slash
295300
* @return the URI components
296301
* @throws IllegalArgumentException if any of the components contain illegal
297302
* characters that should have been encoded.
298303
*/
299-
public UriComponents build(boolean encoded) {
300-
return buildInternal(encoded ? EncodingHint.FULLY_ENCODED :
301-
(this.encodeTemplate ? EncodingHint.ENCODE_TEMPLATE : EncodingHint.NONE));
304+
public UriComponents build(boolean encoded, boolean sanitizePath) {
305+
EncodingHint hint = encoded ? EncodingHint.FULLY_ENCODED :
306+
(this.encodeTemplate ? EncodingHint.ENCODE_TEMPLATE : EncodingHint.NONE);
307+
return buildInternal(hint, sanitizePath);
302308
}
303309

304310
private UriComponents buildInternal(EncodingHint hint) {
311+
return buildInternal(hint, true);
312+
}
313+
314+
private UriComponents buildInternal(EncodingHint hint, boolean sanitizePath) {
305315
UriComponents result;
306316
if (this.ssp != null) {
307317
result = new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
308318
}
309319
else {
310320
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(this.queryParams);
311321
HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment,
312-
this.userInfo, this.host, this.port, this.pathBuilder.build(), queryParams,
322+
this.userInfo, this.host, this.port, this.pathBuilder.build(sanitizePath), queryParams,
313323
hint == EncodingHint.FULLY_ENCODED);
314324
result = (hint == EncodingHint.ENCODE_TEMPLATE ? uric.encodeTemplate(this.charset) : uric);
315325
}
@@ -771,7 +781,7 @@ public enum ParserType {
771781

772782
private interface PathComponentBuilder {
773783

774-
@Nullable PathComponent build();
784+
@Nullable PathComponent build(boolean sanitizePath);
775785

776786
PathComponentBuilder cloneBuilder();
777787
}
@@ -823,11 +833,11 @@ public void addPath(String path) {
823833
}
824834

825835
@Override
826-
public PathComponent build() {
836+
public PathComponent build(boolean sanitizePath) {
827837
int size = this.builders.size();
828838
List<PathComponent> components = new ArrayList<>(size);
829839
for (PathComponentBuilder componentBuilder : this.builders) {
830-
PathComponent pathComponent = componentBuilder.build();
840+
PathComponent pathComponent = componentBuilder.build(sanitizePath);
831841
if (pathComponent != null) {
832842
components.add(pathComponent);
833843
}
@@ -861,12 +871,12 @@ public void append(String path) {
861871
}
862872

863873
@Override
864-
public @Nullable PathComponent build() {
874+
public @Nullable PathComponent build(boolean sanitizePath) {
865875
if (this.path.isEmpty()) {
866876
return null;
867877
}
868-
String sanitized = getSanitizedPath(this.path);
869-
return new HierarchicalUriComponents.FullPathComponent(sanitized);
878+
String path = sanitizePath ? getSanitizedPath(this.path) : this.path.toString();
879+
return new HierarchicalUriComponents.FullPathComponent(path);
870880
}
871881

872882
private static String getSanitizedPath(final StringBuilder path) {
@@ -911,7 +921,7 @@ public void append(String... pathSegments) {
911921
}
912922

913923
@Override
914-
public @Nullable PathComponent build() {
924+
public @Nullable PathComponent build(boolean sanitizePath) {
915925
return (this.pathSegments.isEmpty() ? null :
916926
new HierarchicalUriComponents.PathSegmentComponent(this.pathSegments));
917927
}

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

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ public class UrlPathHelper {
7979

8080
private boolean removeSemicolonContent = true;
8181

82+
private boolean sanitizePath = true;
83+
8284
private String defaultEncoding = WebUtils.DEFAULT_CHARACTER_ENCODING;
8385

8486
private boolean readOnly = false;
@@ -145,6 +147,22 @@ public boolean shouldRemoveSemicolonContent() {
145147
return this.removeSemicolonContent;
146148
}
147149

150+
/**
151+
* Set if double slashes to be replaced by single slash in the request URI.
152+
* <p>Default is "true".
153+
*/
154+
public void setSanitizePath(boolean sanitizePath) {
155+
checkReadOnly();
156+
this.sanitizePath = sanitizePath;
157+
}
158+
159+
/**
160+
* Whether configured to replace double slashes with single slash from the request URI.
161+
*/
162+
public boolean shouldSanitizePath() {
163+
return this.sanitizePath;
164+
}
165+
148166
/**
149167
* Set the default character encoding to use for URL decoding.
150168
* Default is ISO-8859-1, according to the Servlet spec.
@@ -392,12 +410,16 @@ else if (index1 == requestUri.length()) {
392410
}
393411

394412
/**
395-
* Sanitize the given path. Uses the following rules:
413+
* Sanitize the given path if {code getSanitizedPath()} is true.
414+
* Uses the following rules:
396415
* <ul>
397416
* <li>replace all "//" by "/"</li>
398417
* </ul>
399418
*/
400-
private static String getSanitizedPath(final String path) {
419+
private String getSanitizedPath(final String path) {
420+
if (!shouldSanitizePath()) {
421+
return path;
422+
}
401423
int start = path.indexOf("//");
402424
if (start == -1) {
403425
return path;

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

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,11 +849,23 @@ void toUriStringWithCurlyBraces(ParserType parserType) {
849849
}
850850

851851
@Test // gh-26012
852-
void verifyDoubleSlashReplacedWithSingleOne() {
852+
void verifyDoubleSlashReplacedWithSingleDefault() {
853853
String path = UriComponentsBuilder.fromPath("/home/").path("/path").build().getPath();
854854
assertThat(path).isEqualTo("/home/path");
855855
}
856856

857+
@Test // gh-34076
858+
void verifyDoubleSlashNotReplacedAsSingleSlash() {
859+
String path = UriComponentsBuilder.fromPath("/home/").path("/path").build(true, false).getPath();
860+
assertThat(path).isEqualTo("/home//path");
861+
}
862+
863+
@Test // gh-34076
864+
void verifyDoubleSlashReplacedAsSingleSlashWithConfig() {
865+
String path = UriComponentsBuilder.fromPath("/home/").path("/path").build(true, true).getPath();
866+
assertThat(path).isEqualTo("/home/path");
867+
}
868+
857869
@ParameterizedTest
858870
@EnumSource
859871
void validPort(ParserType parserType) {

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,13 @@ void getRequestUri() {
110110
assertThat(helper.getRequestUri(request)).isEqualTo("/home/path");
111111
}
112112

113+
@Test // gh-34076
114+
void getRequestUriWithSanitizingDisabled() {
115+
helper.setSanitizePath(false);
116+
request.setRequestURI("/home/" + "/path");
117+
assertThat(helper.getRequestUri(request)).isEqualTo("/home//path");
118+
}
119+
113120
@Test
114121
void getRequestRemoveSemicolonContent() {
115122
helper.setRemoveSemicolonContent(true);

0 commit comments

Comments
 (0)