Skip to content

Commit 124e817

Browse files
committed
Lenient URI template encoding
URI template encoding ignores mismatched curly braces, treating them as literal parts instead. Issue: SPR-17630
1 parent 0742160 commit 124e817

File tree

3 files changed

+45
-24
lines changed

3 files changed

+45
-24
lines changed

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

+18-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -756,6 +756,8 @@ private static class UriTemplateEncoder implements BiFunction<String, Type, Stri
756756

757757
private final StringBuilder currentLiteral = new StringBuilder();
758758

759+
private final StringBuilder currentVariable = new StringBuilder();
760+
759761
private final StringBuilder output = new StringBuilder();
760762

761763

@@ -767,44 +769,47 @@ public UriTemplateEncoder(Charset charset) {
767769
@Override
768770
public String apply(String source, Type type) {
769771

770-
// Only URI variable, nothing to encode..
772+
// Only URI variable (nothing to encode)..
771773
if (source.length() > 1 && source.charAt(0) == '{' && source.charAt(source.length() -1) == '}') {
772774
return source;
773775
}
774776

775-
// Only literal, encode all..
777+
// Only literal (encode full source)..
776778
if (source.indexOf('{') == -1) {
777779
return encodeUriComponent(source, this.charset, type);
778780
}
779781

780-
// Mixed, encode all except for URI variables..
781-
782+
// Mixed literal parts and URI variables, maybe (encode literal parts only)..
782783
int level = 0;
783784
clear(this.currentLiteral);
785+
clear(this.currentVariable);
784786
clear(this.output);
785-
786787
for (char c : source.toCharArray()) {
787788
if (c == '{') {
788789
level++;
789790
if (level == 1) {
790791
encodeAndAppendCurrentLiteral(type);
791792
}
792793
}
793-
if (c == '}') {
794+
if (c == '}' && level > 0) {
794795
level--;
795-
Assert.isTrue(level >=0, "Mismatched open and close braces");
796+
this.currentVariable.append('}');
797+
if (level == 0) {
798+
this.output.append(this.currentVariable);
799+
clear(this.currentVariable);
800+
}
796801
}
797-
if (level > 0 || (level == 0 && c == '}')) {
798-
this.output.append(c);
802+
else if (level > 0) {
803+
this.currentVariable.append(c);
799804
}
800805
else {
801806
this.currentLiteral.append(c);
802807
}
803808
}
804-
805-
Assert.isTrue(level == 0, "Mismatched open and close braces");
809+
if (level > 0) {
810+
this.currentLiteral.append(this.currentVariable);
811+
}
806812
encodeAndAppendCurrentLiteral(type);
807-
808813
return this.output.toString();
809814
}
810815

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

+20-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -385,15 +385,22 @@ public UriComponents build() {
385385
* @return the URI components
386386
*/
387387
public UriComponents build(boolean encoded) {
388+
return buildInternal(encoded ?
389+
EncodingHint.FULLY_ENCODED :
390+
this.encodeTemplate ? EncodingHint.ENCODE_TEMPLATE : EncodingHint.NONE);
391+
}
392+
393+
private UriComponents buildInternal(EncodingHint hint) {
388394
UriComponents result;
389395
if (this.ssp != null) {
390396
result = new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
391397
}
392398
else {
393399
HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment,
394-
this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, encoded);
400+
this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams,
401+
hint == EncodingHint.FULLY_ENCODED);
395402

396-
result = this.encodeTemplate ? uric.encodeTemplate(this.charset) : uric;
403+
result = hint == EncodingHint.ENCODE_TEMPLATE ? uric.encodeTemplate(this.charset) : uric;
397404
}
398405
if (!this.uriVariables.isEmpty()) {
399406
result = result.expand(name -> this.uriVariables.getOrDefault(name, UriTemplateVariables.SKIP_VALUE));
@@ -425,24 +432,24 @@ public UriComponents buildAndExpand(Object... uriVariableValues) {
425432

426433
@Override
427434
public URI build(Object... uriVariables) {
428-
return encode().buildAndExpand(uriVariables).toUri();
435+
return buildInternal(EncodingHint.ENCODE_TEMPLATE).expand(uriVariables).toUri();
429436
}
430437

431438
@Override
432439
public URI build(Map<String, ?> uriVariables) {
433-
return encode().buildAndExpand(uriVariables).toUri();
440+
return buildInternal(EncodingHint.ENCODE_TEMPLATE).expand(uriVariables).toUri();
434441
}
435442

436-
437443
/**
438-
* Build a URI String. This is a shortcut method which combines calls
439-
* to {@link #build()}, then {@link UriComponents#encode()} and finally
440-
* {@link UriComponents#toUriString()}.
444+
* Build a URI String. This is a shortcut for:
445+
* <pre>
446+
* String uri = builder.encode().build().toUriString()
447+
* </pre>
441448
* @since 4.1
442449
* @see UriComponents#toUriString()
443450
*/
444451
public String toUriString() {
445-
return encode().build().toUriString();
452+
return buildInternal(EncodingHint.ENCODE_TEMPLATE).toUriString();
446453
}
447454

448455

@@ -1036,4 +1043,7 @@ public PathSegmentComponentBuilder cloneBuilder() {
10361043
}
10371044
}
10381045

1046+
1047+
private enum EncodingHint { ENCODE_TEMPLATE, FULLY_ENCODED, NONE }
1048+
10391049
}

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

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 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.
@@ -131,6 +131,12 @@ public void expandWithRegexVar() {
131131
assertEquals("/myurl/test/show", uriComponents.getPath());
132132
}
133133

134+
@Test // SPR-17630
135+
public void uirTemplateExpandWithMismatchedCurlyBraces() {
136+
assertEquals("/myurl/?q=%7B%7B%7B%7B",
137+
UriComponentsBuilder.fromUriString("/myurl/?q={{{{").encode().build().toUriString());
138+
}
139+
134140
@Test // SPR-12123
135141
public void port() {
136142
UriComponents uri1 = fromUriString("http://example.com:8080/bar").build();

0 commit comments

Comments
 (0)