Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance URI parsing exceptions #2126

Merged
merged 1 commit into from
Mar 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@
import java.nio.charset.Charset;
import javax.annotation.Nullable;

import static io.servicetalk.http.api.Uri3986.newDuplicateHost;
import static io.servicetalk.http.api.Uri3986.newEmptyIPv6Literal;
import static io.servicetalk.http.api.Uri3986.newInvalidPortNumberPosition;
import static io.servicetalk.http.api.Uri3986.newMissingClosingBracket;
import static io.servicetalk.http.api.Uri3986.newUnexpectedCloseBracket;
import static io.servicetalk.http.api.Uri3986.newUnexpectedCloseBracketAfterIPv6;
import static io.servicetalk.http.api.Uri3986.newUnexpectedOpenBracket;
import static io.servicetalk.http.api.UriComponentType.HOST_NON_IP;
import static io.servicetalk.http.api.UriUtils.decodeComponent;
import static io.servicetalk.http.api.UriUtils.encodeComponent;
Expand All @@ -42,15 +49,19 @@ final class HttpAuthorityFormUri implements Uri {
final char c = uri.charAt(i);
if (c == '[') {
if (parsingIPv6 != 0 || parsedHost != null) {
throw new IllegalArgumentException("unexpected [");
throw newUnexpectedOpenBracket(i, begin, parsedHost, uri);
}
parsingIPv6 = 1;
begin = i++; // post increment, preserve the '[' for original uri for pathEndIndex.
} else if (c == ']') {
if (parsingIPv6 == 0) {
throw new IllegalArgumentException("unexpected ]");
} else if (i - 1 <= begin) {
throw new IllegalArgumentException("empty ip literal");
throw newUnexpectedCloseBracket(i, parsedHost, uri);
}
if (parsingIPv6 == 2) {
throw newUnexpectedCloseBracketAfterIPv6(i, parsedHost, uri);
}
if (i - 1 <= begin) {
throw newEmptyIPv6Literal(begin, uri);
}
// Copy the '[' and ']' characters. pathEndIndex depends upon retaining the uri contents.
parsedHost = uri.substring(begin, i + 1);
Expand All @@ -60,33 +71,38 @@ final class HttpAuthorityFormUri implements Uri {
} else if (c == ':') {
if (parsingIPv6 == 0) {
if (parsedHost != null) {
throw new IllegalArgumentException("duplicate/invalid host");
throw newDuplicateHost(i, parsedHost, uri);
}
parsedHost = uri.substring(begin, i);
} else if (parsingIPv6 == 2 && begin != i) {
throw new IllegalArgumentException("Port must be immediately after IPv6address");
throw newInvalidPortNumberPosition(i, begin, parsedHost, uri);
}
++i;
if (parsingIPv6 != 1) {
begin = i;
foundColonForPort = true;
}
} else if (c == '@' || c == '?' || c == '#' || c == '/') {
throw new IllegalArgumentException("authority-form URI doesn't allow userinfo, path, query, fragment");
throw new IllegalArgumentException("Invalid URI format: authority-form URI doesn't allow userinfo, " +
"path, query, fragment, but found '" + c + "' character at index " + i +
(parsedHost != null ? ". Parsed host: " + parsedHost : "") +
". Total URI length: " + uri.length());
} else {
++i;
}
}

if (parsedHost == null) {
if (parsingIPv6 == 1) {
throw new IllegalArgumentException("missing closing ] for IP-literal");
throw newMissingClosingBracket(i, begin, uri);
}
parsedHost = uri;
} else if (foundColonForPort) {
parsedPort = parsePort(uri, begin, uri.length());
} else if (parsedHost.length() != uri.length()) {
throw new IllegalArgumentException("authority-form URI only supports the host component");
throw new IllegalArgumentException("Invalid URI format: Authority-form URI only supports the host " +
"component but found more characters. Parsed host: " + parsedHost +
", total URI length to parse: " + uri.length());
}

host = parsedHost;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,23 +88,27 @@ final class Uri3986 implements Uri {
final char c2 = uri.charAt(i);
if (c2 == '@') {
if (parsedUserInfo != null) {
throw new IllegalArgumentException("duplicate userinfo");
throw newDuplicateUserInfo(i, uri);
}
// Userinfo has `:` as valid. If we previously parsed the host throw it away.
parsedUserInfo = uri.substring(authorityBegin, i);
parsedHost = null;
begin = ++i;
} else if (c2 == '[') {
if (parsingIPv6 != 0 || parsedHost != null) {
throw new IllegalArgumentException("unexpected [");
throw newUnexpectedOpenBracket(i, begin, parsedHost, uri);
}
parsingIPv6 = 1;
begin = i++; // post increment, preserve the '[' for original uri for pathEndIndex.
} else if (c2 == ']') {
if (parsingIPv6 == 0) {
throw new IllegalArgumentException("unexpected ]");
} else if (i - 1 <= begin) {
throw new IllegalArgumentException("empty ip literal");
throw newUnexpectedCloseBracket(i, parsedHost, uri);
}
if (parsingIPv6 == 2) {
throw newUnexpectedCloseBracketAfterIPv6(i, parsedHost, uri);
}
if (i - 1 <= begin) {
throw newEmptyIPv6Literal(begin, uri);
}
// Copy the '[' and ']' characters. pathEndIndex depends upon retaining the uri contents.
parsedHost = uri.substring(begin, i + 1);
Expand All @@ -114,11 +118,11 @@ final class Uri3986 implements Uri {
} else if (c2 == ':') {
if (parsingIPv6 == 0) {
if (parsedHost != null) {
throw new IllegalArgumentException("duplicate/invalid host");
throw newDuplicateHost(i, parsedHost, uri);
}
parsedHost = uri.substring(begin, i);
} else if (parsingIPv6 == 2 && begin != i) {
throw new IllegalArgumentException("Port must be immediately after IPv6address");
throw newInvalidPortNumberPosition(i, begin, parsedHost, uri);
}
++i;
if (parsingIPv6 != 1) {
Expand All @@ -128,7 +132,7 @@ final class Uri3986 implements Uri {
} else if (c2 == '?' || c2 == '#' || c2 == '/') {
if (parsedHost == null) {
if (parsingIPv6 == 1) {
throw new IllegalArgumentException("missing closing ] for IP-literal");
throw newNoClosingBracket(i, begin, c2, uri);
}
parsedHost = uri.substring(begin, i);
} else if (foundColonForPort) {
Expand All @@ -149,7 +153,7 @@ final class Uri3986 implements Uri {
if (i == uri.length()) {
if (parsedHost == null) {
if (parsingIPv6 == 1) {
throw new IllegalArgumentException("missing closing ] for IP-literal");
throw newMissingClosingBracket(i, begin, uri);
}
parsedHost = uri.substring(begin);
} else if (foundColonForPort) {
Expand All @@ -163,7 +167,7 @@ final class Uri3986 implements Uri {
}
} else if (c == ':' && begin == 0 && parsedScheme == null && eligibleToParseScheme) {
if (i == 0) {
throw new IllegalArgumentException("empty scheme");
throw new IllegalArgumentException("Invalid URI format: no scheme before colon (':')");
}
parsedScheme = uri.substring(0, i);
begin = ++i;
Expand Down Expand Up @@ -433,6 +437,66 @@ private static int numberOfDigits(int port) {
} else if (port <= 65535) {
return 5;
}
throw new IllegalArgumentException("port out of bounds: " + port);
throw new IllegalArgumentException("Invalid URI format: port number out of bounds: " + port +
", expected [0-65535]");
}

private static IllegalArgumentException newDuplicateUserInfo(final int index, final String uri) {
return new IllegalArgumentException("Invalid URI format: duplicate or invalid userinfo. "
+ "Already parsed userinfo, but found another '@' character at index " + index +
". Total URI length: " + uri.length());
}

private static IllegalArgumentException newNoClosingBracket(final int index, final int begin, final char c,
final String uri) {
return new IllegalArgumentException("Invalid URI format: expected closing ']' for IPv6-literal: " +
uri.substring(begin, index) + ", but found '" + c + "' character at index " + index +
". Total URI length: " + uri.length());
}

static IllegalArgumentException newUnexpectedOpenBracket(final int index, final int ipv6Index,
@Nullable final String parsedHost,
final String uri) {
return new IllegalArgumentException("Invalid URI format: unexpected '[' character at index " + index +
" after " + (parsedHost != null ? "parsed host: " + parsedHost :
("started parsing IPv6-literal from index " + ipv6Index)) +
". Total URI length: " + uri.length());
}

static IllegalArgumentException newUnexpectedCloseBracket(final int index, @Nullable final String parsedHost,
final String uri) {
return new IllegalArgumentException("Invalid URI format: unexpected ']' character at index " + index +
(parsedHost != null ? ". Parsed host: " + parsedHost : "") +
". Total URI length: " + uri.length());
}

static IllegalArgumentException newUnexpectedCloseBracketAfterIPv6(final int index, final String parsedHost,
final String uri) {
return new IllegalArgumentException("Invalid URI format: unexpected ']' character at index " + index +
" after IPv6-address is already parsed: " + parsedHost + ". Total URI length: " + uri.length());
}

static IllegalArgumentException newEmptyIPv6Literal(final int index, final String uri) {
return new IllegalArgumentException("Invalid URI format: empty IPv6-literal [] at index " + index +
". Total URI length: " + uri.length());
}

static IllegalArgumentException newDuplicateHost(final int index, final String parsedHost, final String uri) {
return new IllegalArgumentException("Invalid URI format: duplicate or invalid host. "
+ "Already parsed host: " + parsedHost + ", but found ':' character at index " + index +
". Total URI length: " + uri.length());
}

static IllegalArgumentException newInvalidPortNumberPosition(final int index, final int colonIndex,
final String parsedHost, final String uri) {
return new IllegalArgumentException("Invalid URI format: port number must be " +
"immediately after IPv6-address: " + parsedHost + ", but found " +
(index - colonIndex) + " more characters before colon (':') at index " + index +
". Total URI length: " + uri.length());
}

static IllegalArgumentException newMissingClosingBracket(final int index, final int begin, final String uri) {
return new IllegalArgumentException("Invalid URI format: missing closing ']' for IPv6-literal: " +
uri.substring(begin, index));
}
}