Skip to content

Commit

Permalink
fix: Make URLUtil.encodeURI behave like encodeURI in JavaScript and a…
Browse files Browse the repository at this point in the history
…dd encodeURIComponent (#12291)
  • Loading branch information
Artur- committed Nov 8, 2021
1 parent eff51de commit 2e9c915
Show file tree
Hide file tree
Showing 4 changed files with 70 additions and 16 deletions.
48 changes: 40 additions & 8 deletions flow-server/src/main/java/com/vaadin/flow/internal/UrlUtil.java
Expand Up @@ -49,25 +49,57 @@ public static boolean isExternal(String url) {
}

/**
* Encode a path of a URL.
* Encodes a full URI.
* <p>
* The path can contain {@code /} characters and these will interpreted as
* path segment separators and not be encoded.
* Corresponds to encodeURI in JavaScript
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI
* <p>
* Note that ? and # are not encoded so you should not pass a URL path that
* includes a query string or a fragment
* The path can contain {@code /} and other special URL characters as these
* will not be encoded. See {@link #encodeURIComponent(String)} if you want
* to encode all special characters.
* <p>
* The following characters are not escaped:
* {@literal A-Za-z0-9;,/?:@&=+$-_.!~*'()#}
*
* @param uri
* the uri to encode
*/
public static String encodeURI(String uri) {
try {
return URLEncoder.encode(uri, StandardCharsets.UTF_8.name())
.replace("+", "%20").replace("%2F", "/").replace("%40", "@")
.replace("%3B", ";").replace("%2C", ",").replace("%3F", "?")
.replace("%3A", ":").replace("%26", "&").replace("%3D", "=")
.replace("%2B", "+").replace("%24", "$").replace("%21", "!")
.replace("%7E", "~").replace("%27", "'").replace("%28", "(")
.replace("%29", ")").replace("%23", "#");
} catch (UnsupportedEncodingException e) {
// Runtime exception as this doesn't really happen
throw new RuntimeException("Encoding the URI failed", e); // NOSONAR
}
}

/**
* Encodes a path segment of a URI.
* <p>
* Corresponds to encodeURIComponent in JavaScript
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
* <p>
* The following characters are not escaped: {@literal A-Za-z0-9-_.!~*'()}
*
* @param path
* the path to encode
*/
public static String encodeURI(String path) {
public static String encodeURIComponent(String path) {
try {
return URLEncoder.encode(path, StandardCharsets.UTF_8.name())
.replace("+", "%20").replace("%2F", "/")
.replace("%40", "@");
.replace("+", "%20").replace("%21", "!").replace("%7E", "~")
.replace("%27", "'").replace("%28", "(")
.replace("%29", ")");
} catch (UnsupportedEncodingException e) {
// Runtime exception as this doesn't really happen
throw new RuntimeException("Encoding the URI failed", e); // NOSONAR
}
}

}
Expand Up @@ -51,7 +51,9 @@ public static void verifyRelativePath(String path) {
// Ignore forbidden chars supported in route definitions
String strippedPath = path.replaceAll("[{}*]", "");

URI uri = new URI(UrlUtil.encodeURI(strippedPath));
// : is completely valid in a path but URI will think that defines a
// protocol so skip it for the check
URI uri = new URI(UrlUtil.encodeURI(strippedPath).replace(":", ""));
if (uri.isAbsolute()) {
// "A URI is absolute if, and only if, it has a scheme
// component"
Expand Down
Expand Up @@ -148,7 +148,7 @@ public static String generateURI(String name, String id) {

builder.append(UI.getCurrent().getUIId()).append(PATH_SEPARATOR);
builder.append(id).append(PATH_SEPARATOR);
builder.append(UrlUtil.encodeURI(name));
builder.append(UrlUtil.encodeURIComponent(name));
return builder.toString();
}

Expand All @@ -162,7 +162,7 @@ private static Optional<URI> getPathUri(String path) {
String prefix = path.substring(0, index + 1);
String name = path.substring(prefix.length());
try {
URI uri = new URI(prefix + UrlUtil.encodeURI(name));
URI uri = new URI(prefix + UrlUtil.encodeURIComponent(name));
return Optional.of(uri);
} catch (URISyntaxException e) {
getLogger().info(
Expand Down
Expand Up @@ -20,7 +20,8 @@

public class UrlUtilTest {

private String shouldNotBeEscaped = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789/_-.*@";
private String encodeURIShouldNotBeEscaped = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789;,/?:@&=+$-_.!~*'()#";
private String encodeURIComponentShouldNotBeEscaped = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()";

@Test
public void isExternal_URLStartsWithTwoSlashes_returnsTrue() {
Expand All @@ -42,24 +43,43 @@ public void isExternal_URLDoesnotContainSchema_returnsFalse() {

@Test
public void plusAndSpaceHandledCorrectly() {
Assert.assertEquals("Plus%2BSpa%20%2B%20ce",
Assert.assertEquals("Plus+Spa%20+%20ce",
UrlUtil.encodeURI("Plus+Spa + ce"));
Assert.assertEquals("Plus%2BSpa%20%2B%20ce",
UrlUtil.encodeURIComponent("Plus+Spa + ce"));
}

@Test
public void encodeURI_shouldNotBeEscaped() {
Assert.assertEquals(shouldNotBeEscaped,
UrlUtil.encodeURI(shouldNotBeEscaped));
Assert.assertEquals(encodeURIShouldNotBeEscaped,
UrlUtil.encodeURI(encodeURIShouldNotBeEscaped));
}

@Test
public void encodeURI_mustBeEscaped() {
for (char c = 0; c < 255; c++) {
String s = String.valueOf(c);
if (shouldNotBeEscaped.contains(s)) {
if (encodeURIShouldNotBeEscaped.contains(s)) {
continue;
}
Assert.assertNotEquals(UrlUtil.encodeURI(s), s);
}
}

@Test
public void encodeURIComponent_shouldNotBeEscaped() {
Assert.assertEquals(encodeURIComponentShouldNotBeEscaped, UrlUtil
.encodeURIComponent(encodeURIComponentShouldNotBeEscaped));
}

@Test
public void encodeURIComponent_mustBeEscaped() {
for (char c = 0; c < 255; c++) {
String s = String.valueOf(c);
if (encodeURIComponentShouldNotBeEscaped.contains(s)) {
continue;
}
Assert.assertNotEquals(UrlUtil.encodeURIComponent(s), s);
}
}
}

0 comments on commit 2e9c915

Please sign in to comment.