Skip to content

Commit 12bf5e7

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

File tree

4 files changed

+82
-14
lines changed

4 files changed

+82
-14
lines changed

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

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,7 @@ public UriComponentsBuilder encode(Charset charset) {
284284
* @return the URI components
285285
*/
286286
public UriComponents build() {
287-
return build(false);
287+
return build(false, true);
288288
}
289289

290290
/**
@@ -297,19 +297,39 @@ public UriComponents build() {
297297
* characters that should have been encoded.
298298
*/
299299
public UriComponents build(boolean encoded) {
300-
return buildInternal(encoded ? EncodingHint.FULLY_ENCODED :
301-
(this.encodeTemplate ? EncodingHint.ENCODE_TEMPLATE : EncodingHint.NONE));
300+
return build(encoded, true);
301+
}
302+
303+
/**
304+
* Variant of {@link #build()} to create a {@link UriComponents} instance
305+
* when components are already fully encoded. In addition, this method allows
306+
* to control whether the double slashes in the path should be replaced with
307+
* a single slash.
308+
* @param encoded whether the components in this builder are already encoded
309+
* @param sanitizePath whether the double slashes should be replaced with single slash
310+
* @return the URI components
311+
* @throws IllegalArgumentException if any of the components contain illegal
312+
* characters that should have been encoded.
313+
*/
314+
public UriComponents build(boolean encoded, boolean sanitizePath) {
315+
EncodingHint hint = encoded ? EncodingHint.FULLY_ENCODED :
316+
(this.encodeTemplate ? EncodingHint.ENCODE_TEMPLATE : EncodingHint.NONE);
317+
return buildInternal(hint, sanitizePath);
302318
}
303319

304320
private UriComponents buildInternal(EncodingHint hint) {
321+
return buildInternal(hint, true);
322+
}
323+
324+
private UriComponents buildInternal(EncodingHint hint, boolean sanitizePath) {
305325
UriComponents result;
306326
if (this.ssp != null) {
307327
result = new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
308328
}
309329
else {
310330
MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(this.queryParams);
311331
HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment,
312-
this.userInfo, this.host, this.port, this.pathBuilder.build(), queryParams,
332+
this.userInfo, this.host, this.port, this.pathBuilder.build(sanitizePath), queryParams,
313333
hint == EncodingHint.FULLY_ENCODED);
314334
result = (hint == EncodingHint.ENCODE_TEMPLATE ? uric.encodeTemplate(this.charset) : uric);
315335
}
@@ -771,7 +791,7 @@ public enum ParserType {
771791

772792
private interface PathComponentBuilder {
773793

774-
@Nullable PathComponent build();
794+
@Nullable PathComponent build(boolean sanitizePath);
775795

776796
PathComponentBuilder cloneBuilder();
777797
}
@@ -823,11 +843,11 @@ public void addPath(String path) {
823843
}
824844

825845
@Override
826-
public PathComponent build() {
846+
public PathComponent build(boolean sanitizePath) {
827847
int size = this.builders.size();
828848
List<PathComponent> components = new ArrayList<>(size);
829849
for (PathComponentBuilder componentBuilder : this.builders) {
830-
PathComponent pathComponent = componentBuilder.build();
850+
PathComponent pathComponent = componentBuilder.build(sanitizePath);
831851
if (pathComponent != null) {
832852
components.add(pathComponent);
833853
}
@@ -861,12 +881,12 @@ public void append(String path) {
861881
}
862882

863883
@Override
864-
public @Nullable PathComponent build() {
884+
public @Nullable PathComponent build(boolean sanitizePath) {
865885
if (this.path.isEmpty()) {
866886
return null;
867887
}
868-
String sanitized = getSanitizedPath(this.path);
869-
return new HierarchicalUriComponents.FullPathComponent(sanitized);
888+
String path = sanitizePath ? getSanitizedPath(this.path) : this.path.toString();
889+
return new HierarchicalUriComponents.FullPathComponent(path);
870890
}
871891

872892
private static String getSanitizedPath(final StringBuilder path) {
@@ -911,7 +931,7 @@ public void append(String... pathSegments) {
911931
}
912932

913933
@Override
914-
public @Nullable PathComponent build() {
934+
public @Nullable PathComponent build(boolean sanitizePath) {
915935
return (this.pathSegments.isEmpty() ? null :
916936
new HierarchicalUriComponents.PathSegmentComponent(this.pathSegments));
917937
}

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 shouldSanitizePath()} 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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,20 @@ 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+
120+
@Test // gh-34076
121+
void getRequestUriWithSanitizingEnabled() {
122+
helper.setSanitizePath(true);
123+
request.setRequestURI("/home/" + "/path");
124+
assertThat(helper.getRequestUri(request)).isEqualTo("/home/path");
125+
}
126+
113127
@Test
114128
void getRequestRemoveSemicolonContent() {
115129
helper.setRemoveSemicolonContent(true);

0 commit comments

Comments
 (0)