diff --git a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java index 97ab202f0a..6800d73283 100644 --- a/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java +++ b/spring-web/src/main/java/org/springframework/web/util/HierarchicalUriComponents.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -756,6 +756,8 @@ final class HierarchicalUriComponents extends UriComponents { private final StringBuilder currentLiteral = new StringBuilder(); + private final StringBuilder currentVariable = new StringBuilder(); + private final StringBuilder output = new StringBuilder(); @@ -767,22 +769,21 @@ final class HierarchicalUriComponents extends UriComponents { @Override public String apply(String source, Type type) { - // Only URI variable, nothing to encode.. + // Only URI variable (nothing to encode).. if (source.length() > 1 && source.charAt(0) == '{' && source.charAt(source.length() -1) == '}') { return source; } - // Only literal, encode all.. + // Only literal (encode full source).. if (source.indexOf('{') == -1) { return encodeUriComponent(source, this.charset, type); } - // Mixed, encode all except for URI variables.. - + // Mixed literal parts and URI variables, maybe (encode literal parts only).. int level = 0; clear(this.currentLiteral); + clear(this.currentVariable); clear(this.output); - for (char c : source.toCharArray()) { if (c == '{') { level++; @@ -790,21 +791,25 @@ final class HierarchicalUriComponents extends UriComponents { encodeAndAppendCurrentLiteral(type); } } - if (c == '}') { + if (c == '}' && level > 0) { level--; - Assert.isTrue(level >=0, "Mismatched open and close braces"); + this.currentVariable.append('}'); + if (level == 0) { + this.output.append(this.currentVariable); + clear(this.currentVariable); + } } - if (level > 0 || (level == 0 && c == '}')) { - this.output.append(c); + else if (level > 0) { + this.currentVariable.append(c); } else { this.currentLiteral.append(c); } } - - Assert.isTrue(level == 0, "Mismatched open and close braces"); + if (level > 0) { + this.currentLiteral.append(this.currentVariable); + } encodeAndAppendCurrentLiteral(type); - return this.output.toString(); } diff --git a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java index c1e59f2fec..f33c8b2e60 100644 --- a/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java +++ b/spring-web/src/main/java/org/springframework/web/util/UriComponentsBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -385,15 +385,22 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { * @return the URI components */ public UriComponents build(boolean encoded) { + return buildInternal(encoded ? + EncodingHint.FULLY_ENCODED : + this.encodeTemplate ? EncodingHint.ENCODE_TEMPLATE : EncodingHint.NONE); + } + + private UriComponents buildInternal(EncodingHint hint) { UriComponents result; if (this.ssp != null) { result = new OpaqueUriComponents(this.scheme, this.ssp, this.fragment); } else { HierarchicalUriComponents uric = new HierarchicalUriComponents(this.scheme, this.fragment, - this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, encoded); + this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, + hint == EncodingHint.FULLY_ENCODED); - result = this.encodeTemplate ? uric.encodeTemplate(this.charset) : uric; + result = hint == EncodingHint.ENCODE_TEMPLATE ? uric.encodeTemplate(this.charset) : uric; } if (!this.uriVariables.isEmpty()) { result = result.expand(name -> this.uriVariables.getOrDefault(name, UriTemplateVariables.SKIP_VALUE)); @@ -425,24 +432,24 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { @Override public URI build(Object... uriVariables) { - return encode().buildAndExpand(uriVariables).toUri(); + return buildInternal(EncodingHint.ENCODE_TEMPLATE).expand(uriVariables).toUri(); } @Override public URI build(Map uriVariables) { - return encode().buildAndExpand(uriVariables).toUri(); + return buildInternal(EncodingHint.ENCODE_TEMPLATE).expand(uriVariables).toUri(); } - /** - * Build a URI String. This is a shortcut method which combines calls - * to {@link #build()}, then {@link UriComponents#encode()} and finally - * {@link UriComponents#toUriString()}. + * Build a URI String. This is a shortcut for: + *
+	 * String uri = builder.encode().build().toUriString()
+	 * 
* @since 4.1 * @see UriComponents#toUriString() */ public String toUriString() { - return encode().build().toUriString(); + return buildInternal(EncodingHint.ENCODE_TEMPLATE).toUriString(); } @@ -1049,4 +1056,7 @@ public class UriComponentsBuilder implements UriBuilder, Cloneable { } } + + private enum EncodingHint { ENCODE_TEMPLATE, FULLY_ENCODED, NONE } + } diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java index 59f701fa9c..01bd245835 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2018 the original author or authors. + * Copyright 2002-2019 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. @@ -27,12 +27,9 @@ import java.util.Collections; import org.junit.Test; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; -import static org.springframework.web.util.UriComponentsBuilder.fromUriString; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; +import static org.springframework.web.util.UriComponentsBuilder.*; /** * Unit tests for {@link UriComponents}. @@ -127,6 +124,12 @@ public class UriComponentsTests { assertEquals("/myurl/test/show", uriComponents.getPath()); } + @Test // SPR-17630 + public void uirTemplateExpandWithMismatchedCurlyBraces() { + assertEquals("/myurl/?q=%7B%7B%7B%7B", + UriComponentsBuilder.fromUriString("/myurl/?q={{{{").encode().build().toUriString()); + } + @Test // SPR-12123 public void port() { UriComponents uri1 = fromUriString("http://example.com:8080/bar").build();