Skip to content

Commit

Permalink
Introduce internal RFC3986 helper class that gathers the URIDecoder s…
Browse files Browse the repository at this point in the history
…tatic methods as well as some of HttpUtils URI related methods.
  • Loading branch information
vietj committed Sep 23, 2024
1 parent 479cd8c commit e75a0a1
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 134 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import static io.vertx.core.internal.net.URIDecoder.decodeURIComponent;
import static io.vertx.core.internal.net.RFC3986.decodeURIComponent;

/**
* Sometimes the file resources of an application are bundled into jars, or are somewhere on the classpath but not
Expand Down
113 changes: 5 additions & 108 deletions vertx-core/src/main/java/io/vertx/core/http/impl/HttpUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import io.vertx.core.http.StreamPriority;
import io.vertx.core.internal.ContextInternal;
import io.vertx.core.internal.VertxInternal;
import io.vertx.core.internal.net.RFC3986;
import io.vertx.core.net.HostAndPort;
import io.vertx.core.net.impl.HostAndPortImpl;
import io.vertx.core.spi.tracing.TagExtractor;
Expand Down Expand Up @@ -196,39 +197,6 @@ public StreamPriority setExclusive(boolean exclusive) {
private HttpUtils() {
}

private static int indexOfSlash(CharSequence str, int start) {
for (int i = start; i < str.length(); i++) {
if (str.charAt(i) == '/') {
return i;
}
}

return -1;
}

private static boolean matches(CharSequence path, int start, String what) {
return matches(path, start, what, false);
}

private static boolean matches(CharSequence path, int start, String what, boolean exact) {
if (exact) {
if (path.length() - start != what.length()) {
return false;
}
}

if (path.length() - start >= what.length()) {
for (int i = 0; i < what.length(); i++) {
if (path.charAt(start + i) != what.charAt(i)) {
return false;
}
}
return true;
}

return false;
}

/**
* Normalizes a path as per <a href="http://tools.ietf.org/html/rfc3986#section-5.2.4>rfc3986</a>.
*
Expand Down Expand Up @@ -283,7 +251,7 @@ private static String normalizePathSlow(String pathname, int indexOfFirstPercent
}
// remove dots as described in
// http://tools.ietf.org/html/rfc3986#section-5.2.4
return removeDots(ibuf);
return RFC3986.removeDotSegments(ibuf);
}

private static void decodeUnreservedChars(StringBuilder path, int start) {
Expand Down Expand Up @@ -336,77 +304,6 @@ private static void decodeUnreserved(StringBuilder path, int start) {
}
}

/**
* Removed dots as per <a href="http://tools.ietf.org/html/rfc3986#section-5.2.4>rfc3986</a>.
*
* There is 1 extra transformation that are not part of the spec but kept for backwards compatibility:
*
* double slash // will be converted to single slash.
*
* @param path raw path
* @return normalized path
*/
public static String removeDots(CharSequence path) {

if (path == null) {
return null;
}

final StringBuilder obuf = new StringBuilder(path.length());

int i = 0;
while (i < path.length()) {
// remove dots as described in
// http://tools.ietf.org/html/rfc3986#section-5.2.4
if (matches(path, i, "./")) {
i += 2;
} else if (matches(path, i, "../")) {
i += 3;
} else if (matches(path, i, "/./")) {
// preserve last slash
i += 2;
} else if (matches(path, i,"/.", true)) {
path = "/";
i = 0;
} else if (matches(path, i, "/../")) {
// preserve last slash
i += 3;
int pos = obuf.lastIndexOf("/");
if (pos != -1) {
obuf.delete(pos, obuf.length());
}
} else if (matches(path, i, "/..", true)) {
path = "/";
i = 0;
int pos = obuf.lastIndexOf("/");
if (pos != -1) {
obuf.delete(pos, obuf.length());
}
} else if (matches(path, i, ".", true) || matches(path, i, "..", true)) {
break;
} else {
if (path.charAt(i) == '/') {
i++;
// Not standard!!!
// but common // -> /
if (obuf.length() == 0 || obuf.charAt(obuf.length() - 1) != '/') {
obuf.append('/');
}
}
int pos = indexOfSlash(path, i);
if (pos != -1) {
obuf.append(path, i, pos);
i = pos;
} else {
obuf.append(path, i, path.length());
break;
}
}
}

return obuf.toString();
}

/**
* Resolve an URI reference as per <a href="http://tools.ietf.org/html/rfc3986#section-5.2.4>rfc3986</a>
*/
Expand All @@ -426,7 +323,7 @@ public static URI resolveURIReference(URI base, String ref) throws URISyntaxExce
if (_ref.getScheme() != null) {
scheme = _ref.getScheme();
authority = _ref.getAuthority();
path = removeDots(_ref.getRawPath());
path = RFC3986.removeDotSegments(_ref.getRawPath());
query = _ref.getRawQuery();
} else {
if (_ref.getAuthority() != null) {
Expand All @@ -443,7 +340,7 @@ public static URI resolveURIReference(URI base, String ref) throws URISyntaxExce
}
} else {
if (_ref.getRawPath().startsWith("/")) {
path = removeDots(_ref.getRawPath());
path = RFC3986.removeDotSegments(_ref.getRawPath());
} else {
// Merge paths
String mergedPath;
Expand All @@ -458,7 +355,7 @@ public static URI resolveURIReference(URI base, String ref) throws URISyntaxExce
mergedPath = _ref.getRawPath();
}
}
path = removeDots(mergedPath);
path = RFC3986.removeDotSegments(mergedPath);
}
query = _ref.getRawQuery();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import java.net.URL;
import java.util.Collections;

import static io.vertx.core.internal.net.URIDecoder.decodeURIComponent;
import static io.vertx.core.internal.net.RFC3986.decodeURIComponent;

/**
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import java.util.List;
import java.util.jar.JarEntry;

import static io.vertx.core.internal.net.URIDecoder.decodeURIComponent;
import static io.vertx.core.internal.net.RFC3986.decodeURIComponent;

/**
* @author Janne Hietam&auml;ki
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,59 +11,48 @@
package io.vertx.core.internal.net;

import java.nio.charset.StandardCharsets;
import java.util.Objects;

/**
* Various RFC3986 util methods.
*
* @author <a href="mailto:plopes@redhat.com">Paulo Lopes</a>
*/
public final class URIDecoder {
public final class RFC3986 {

private URIDecoder() {
throw new RuntimeException("Static Class");
private RFC3986() {
}

/**
* Decodes a segment of a URI encoded by a browser.
*
* The string is expected to be encoded as per RFC 3986, Section 2. This is the encoding used by JavaScript functions
* <p>The string is expected to be encoded as per RFC 3986, Section 2. This is the encoding used by JavaScript functions
* encodeURI and encodeURIComponent, but not escape. For example in this encoding, é (in Unicode U+00E9 or in
* UTF-8 0xC3 0xA9) is encoded as %C3%A9 or %c3%a9.
*
* Plus signs '+' will be handled as spaces and encoded using the default JDK URLEncoder class.
* <p>Plus signs '+' will be handled as spaces and encoded using the default JDK URLEncoder class.
*
* @param s string to decode
*
* @return decoded string
*/
public static String decodeURIComponent(String s) {
return decodeURIComponent(s, true);
}

private static int indexOfPercentOrPlus(String s) {
for (int i = 0, size = s.length(); i < size; i++) {
final char c = s.charAt(i);
if (c == '%' || c == '+') {
return i;
}
}
return -1;
}

/**
* Decodes a segment of an URI encoded by a browser.
*
* The string is expected to be encoded as per RFC 3986, Section 2. This is the encoding used by JavaScript functions
* <p>The string is expected to be encoded as per RFC 3986, Section 2. This is the encoding used by JavaScript functions
* encodeURI and encodeURIComponent, but not escape. For example in this encoding, é (in Unicode U+00E9 or in
* UTF-8 0xC3 0xA9) is encoded as %C3%A9 or %c3%a9.
*
* @param s string to decode
* @param plus weather or not to transform plus signs into spaces
* @param plus whether to convert plus char into spaces
*
* @return decoded string
*/
public static String decodeURIComponent(String s, boolean plus) {
if (s == null) {
return null;
}
Objects.requireNonNull(s);
int i = !plus ? s.indexOf('%') : indexOfPercentOrPlus(s);
if (i == -1) {
return s;
Expand All @@ -72,6 +61,16 @@ public static String decodeURIComponent(String s, boolean plus) {
return decodeAndTransformURIComponent(s, i, plus);
}

private static int indexOfPercentOrPlus(String s) {
for (int i = 0, size = s.length(); i < size; i++) {
final char c = s.charAt(i);
if (c == '%' || c == '+') {
return i;
}
}
return -1;
}

private static String decodeAndTransformURIComponent(String s, int i, boolean plus) {
final byte[] buf = s.getBytes(StandardCharsets.UTF_8);
int pos = i; // position in `buf'.
Expand Down Expand Up @@ -127,4 +126,100 @@ private static char decodeHexNibble(final char c) {
return Character.MAX_VALUE;
}
}

/**
* Removed dots as per <a href="http://tools.ietf.org/html/rfc3986#section-5.2.4">rfc3986</a>.
*
* <p>There is 1 extra transformation that are not part of the spec but kept for backwards compatibility:
* double slash // will be converted to single slash.
*
* @param path raw path
* @return normalized path
*/
public static String removeDotSegments(CharSequence path) {
Objects.requireNonNull(path);
final StringBuilder obuf = new StringBuilder(path.length());
int i = 0;
while (i < path.length()) {
// remove dots as described in
// http://tools.ietf.org/html/rfc3986#section-5.2.4
if (matches(path, i, "./")) {
i += 2;
} else if (matches(path, i, "../")) {
i += 3;
} else if (matches(path, i, "/./")) {
// preserve last slash
i += 2;
} else if (matches(path, i,"/.", true)) {
path = "/";
i = 0;
} else if (matches(path, i, "/../")) {
// preserve last slash
i += 3;
int pos = obuf.lastIndexOf("/");
if (pos != -1) {
obuf.delete(pos, obuf.length());
}
} else if (matches(path, i, "/..", true)) {
path = "/";
i = 0;
int pos = obuf.lastIndexOf("/");
if (pos != -1) {
obuf.delete(pos, obuf.length());
}
} else if (matches(path, i, ".", true) || matches(path, i, "..", true)) {
break;
} else {
if (path.charAt(i) == '/') {
i++;
// Not standard!!!
// but common // -> /
if (obuf.length() == 0 || obuf.charAt(obuf.length() - 1) != '/') {
obuf.append('/');
}
}
int pos = indexOfSlash(path, i);
if (pos != -1) {
obuf.append(path, i, pos);
i = pos;
} else {
obuf.append(path, i, path.length());
break;
}
}
}
return obuf.toString();
}

private static boolean matches(CharSequence path, int start, String what) {
return matches(path, start, what, false);
}

private static boolean matches(CharSequence path, int start, String what, boolean exact) {
if (exact) {
if (path.length() - start != what.length()) {
return false;
}
}

if (path.length() - start >= what.length()) {
for (int i = 0; i < what.length(); i++) {
if (path.charAt(start + i) != what.charAt(i)) {
return false;
}
}
return true;
}

return false;
}

private static int indexOfSlash(CharSequence str, int start) {
for (int i = start; i < str.length(); i++) {
if (str.charAt(i) == '/') {
return i;
}
}
return -1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@

import java.net.URLEncoder;

import static io.vertx.core.internal.net.URIDecoder.decodeURIComponent;
import static io.vertx.core.internal.net.RFC3986.decodeURIComponent;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

/**
* @author <a href="mailto:plopes@redhat.com">Paulo Lopes</a>
*/
public class URIDecoderTest {
public class UriUtilsTest {

@Test
public void testDecode() throws Exception {
Expand Down

0 comments on commit e75a0a1

Please sign in to comment.