diff --git a/spring-web/src/main/java/org/springframework/web/util/RfcUriParser.java b/spring-web/src/main/java/org/springframework/web/util/RfcUriParser.java index 3a281bc5ea7b..383014d97013 100644 --- a/spring-web/src/main/java/org/springframework/web/util/RfcUriParser.java +++ b/spring-web/src/main/java/org/springframework/web/util/RfcUriParser.java @@ -482,6 +482,7 @@ public void advanceTo(State state) { "index=" + this.index + ", componentIndex=" + this.componentIndex); } this.state = state; + this.openCurlyBracketCount = 0; } public void advanceTo(State state, int componentIndex) { diff --git a/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java b/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java index a4015c410300..b7316ac03882 100644 --- a/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java +++ b/spring-web/src/main/java/org/springframework/web/util/WhatWgUrlParser.java @@ -47,12 +47,9 @@ *

Comments in this class correlate to the parsing algorithm. * The implementation differs from the spec in the following ways: *

* All of these modifications have been indicated through comments that start * with {@code EXTRA}. @@ -89,9 +86,6 @@ final class WhatWgUrlParser { @Nullable private State state; - @Nullable - private State previousState; - @Nullable private State stateOverride; @@ -101,6 +95,8 @@ final class WhatWgUrlParser { private boolean insideBrackets; + private int openCurlyBracketCount; + private boolean stopMainLoop = false; @@ -235,12 +231,22 @@ private void setState(State newState) { else { c = "EOF"; } - logger.trace("Changing state from " + this.state + " to " + - newState + " (cur: " + c + " prev: " + this.previousState + ")"); + logger.trace("Changing state from " + this.state + " to " + newState + " (cur: " + c + ")"); } - // EXTRA: we keep the previous state, to ensure that the parser can escape from malformed URI templates - this.previousState = this.state; this.state = newState; + this.openCurlyBracketCount = 0; + } + + private boolean processCurlyBrackets(int c) { + if (c == '{') { + this.openCurlyBracketCount++; + return true; + } + if (c == '}') { + this.openCurlyBracketCount--; + return true; + } + return (this.openCurlyBracketCount > 0 && c != EOF); } private static LinkedList strictSplit(String input, int delimiter) { @@ -755,12 +761,11 @@ public void handle(int c, UrlRecord url, WhatWgUrlParser p) { p.append(Character.toLowerCase((char) c)); p.setState(SCHEME); } - // EXTRA: if c is '{', then append c to buffer, set previous state to scheme state, - // and state to url template state. - else if (p.previousState != URL_TEMPLATE && c == '{') { + // EXTRA: if c is '{', append to buffer and continue as SCHEME + else if (c == '{') { + p.openCurlyBracketCount++; p.append(c); - p.previousState = SCHEME; - p.state = URL_TEMPLATE; + p.setState(SCHEME); } // Otherwise, if state override is not given, // set state to no scheme state and decrease pointer by 1. @@ -781,11 +786,6 @@ public void handle(int c, UrlRecord url, WhatWgUrlParser p) { if (isAsciiAlphaNumeric(c) || (c == '+' || c == '-' || c == '.')) { p.append(Character.toLowerCase((char) c)); } - // EXTRA: if c is '{', then append c to buffer, set state to url template state. - else if (p.previousState != URL_TEMPLATE && c == '{') { - p.append(c); - p.setState(URL_TEMPLATE); - } // Otherwise, if c is U+003A (:), then: else if (c == ':') { // If state override is given, then: @@ -858,6 +858,10 @@ else if (p.remaining(0) == '/') { p.setState(OPAQUE_PATH); } } + // EXTRA: if c is within URI variable, keep appending + else if (p.processCurlyBrackets(c)) { + p.append(c); + } // Otherwise, if state override is not given, set buffer to the empty string, // state to no scheme state, and start over (from the first code point in input). else if (p.stateOverride == null) { @@ -1225,11 +1229,6 @@ public void handle(int c, UrlRecord url, WhatWgUrlParser p) { if (isAsciiDigit(c)) { p.append(c); } - // EXTRA: if c is '{', then append c to buffer, set state to url template state. - else if (p.previousState != URL_TEMPLATE && c == '{') { - p.append(c); - p.setState(URL_TEMPLATE); - } // Otherwise, if one of the following is true: // - c is the EOF code point, U+002F (/), U+003F (?), or U+0023 (#) // - url is special and c is U+005C (\) @@ -1279,6 +1278,10 @@ else if (c == EOF || c == '/' || c == '?' || c == '#' || p.setState(PATH_START); p.pointer--; } + // EXTRA: if c is within URI variable, keep appending + else if (p.processCurlyBrackets(c)) { + p.append(c); + } // Otherwise, port-invalid validation error, return failure. else { p.failure("Invalid port: \"" + Character.toString(c) + "\""); @@ -1547,11 +1550,6 @@ else if (!singlePathSegment) { p.setState(FRAGMENT); } } - // EXTRA: Otherwise, if c is '{', then append c to buffer, set state to url template state. - else if (p.previousState != URL_TEMPLATE && c == '{') { - p.append(c); - p.setState(URL_TEMPLATE); - } // Otherwise, run these steps: else { if (p.validate()) { @@ -1582,12 +1580,6 @@ else if (c == '%' && OPAQUE_PATH { @Override public void handle(int c, UrlRecord url, WhatWgUrlParser p) { - // EXTRA: if previous state is URL Template and the buffer is empty, - // append buffer to url's path and empty the buffer - if (p.previousState == URL_TEMPLATE && !p.buffer.isEmpty()) { - url.path.append(p.buffer.toString()); - p.emptyBuffer(); - } // If c is U+003F (?), then set url’s query to the empty string and state to query state. if (c == '?') { url.query = new StringBuilder(); @@ -1599,11 +1591,6 @@ else if (c == '#') { url.fragment = new StringBuilder(); p.setState(FRAGMENT); } - // EXTRA: Otherwise, if c is '{', then append c to buffer, set state to url template state. - else if (p.previousState != URL_TEMPLATE && c == '{') { - p.append(c); - p.setState(URL_TEMPLATE); - } // Otherwise: else { if (p.validate()) { @@ -1668,11 +1655,6 @@ public void handle(int c, UrlRecord url, WhatWgUrlParser p) { p.setState(FRAGMENT); } } - // EXTRA: Otherwise, if c is '{', then append c to buffer, set state to url template state. - else if (p.previousState != URL_TEMPLATE && c == '{') { - p.append(c); - p.setState(URL_TEMPLATE); - } // Otherwise, if c is not the EOF code point: else if (c != EOF) { if (p.validate()) { @@ -1725,24 +1707,6 @@ else if (c == '%' && } } } - }, - URL_TEMPLATE { - @Override - public void handle(int c, UrlRecord url, WhatWgUrlParser p) { - Assert.state(p.previousState != null, "No previous state set"); - if (c == '}') { - p.append(c); - p.setState(p.previousState); - } - else if (c == EOF) { - p.pointer -= p.buffer.length() + 1; - p.emptyBuffer(); - p.setState(p.previousState); - } - else { - p.append(c); - } - } }; public abstract void handle(int c, UrlRecord url, WhatWgUrlParser p); diff --git a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java index f97457b5c63e..ded7176bbe99 100644 --- a/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java +++ b/spring-web/src/test/java/org/springframework/web/util/UriComponentsBuilderTests.java @@ -603,6 +603,18 @@ void buildAndExpandHierarchical() { assertThat(result.toUriString()).isEqualTo("/fooValue/barValue"); } + @ParameterizedTest + @EnumSource(value = ParserType.class) + void parseBuildAndExpandHierarchical(ParserType parserType) { + URI uri = UriComponentsBuilder + .fromUriString("{scheme}://{host}:{port}/{segment}?{query}#{fragment}", parserType) + .buildAndExpand(Map.of( + "scheme", "ws", "host", "example.org", "port", "7777", "segment", "path", + "query", "q=1", "fragment", "foo")) + .toUri(); + assertThat(uri.toString()).isEqualTo("ws://example.org:7777/path?q=1#foo"); + } + @ParameterizedTest @EnumSource(value = ParserType.class) void buildAndExpandOpaque(ParserType parserType) {