Skip to content

Commit

Permalink
Don't be tricked by anchors containing a question mark, close #1455
Browse files Browse the repository at this point in the history
Motivation:

AHC can be tricked into connecting to a different host.

Modification:

* Make sure we don’t interpret `?` in the anchor as the beginning of
the query and the end of the path.
* Update tests to check org.asynchttpclient.uri.Uri returns the same
results as java.net.URI.

Result:

AHC no longer tricked by anchors containing question mark.
  • Loading branch information
slandelle committed Aug 28, 2017
1 parent 62b5a1d commit eb9e334
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 323 deletions.
184 changes: 102 additions & 82 deletions client/src/main/java/org/asynchttpclient/uri/UriParser.java
Expand Up @@ -25,24 +25,27 @@ final class UriParser {
public String path; public String path;
public String userInfo; public String userInfo;


private int start, end = 0; private String originalUrl;
private String urlWithoutQuery; private int start, end, currentIndex = 0;


private void trimRight(String originalUrl) { private void trimLeft() {
end = originalUrl.length(); while (start < end && originalUrl.charAt(start) <= ' ') {
while (end > 0 && originalUrl.charAt(end - 1) <= ' ')
end--;
}

private void trimLeft(String originalUrl) {
while (start < end && originalUrl.charAt(start) <= ' ')
start++; start++;
}


if (originalUrl.regionMatches(true, start, "url:", 0, 4)) if (originalUrl.regionMatches(true, start, "url:", 0, 4)) {
start += 4; start += 4;
}
} }


private boolean isFragmentOnly(String originalUrl) { private void trimRight() {
end = originalUrl.length();
while (end > 0 && originalUrl.charAt(end - 1) <= ' ') {
end--;
}
}

private boolean isFragmentOnly() {
return start < originalUrl.length() && originalUrl.charAt(start) == '#'; return start < originalUrl.length() && originalUrl.charAt(start) == '#';
} }


Expand All @@ -52,8 +55,9 @@ private boolean isValidProtocolChar(char c) {


private boolean isValidProtocolChars(String protocol) { private boolean isValidProtocolChars(String protocol) {
for (int i = 1; i < protocol.length(); i++) { for (int i = 1; i < protocol.length(); i++) {
if (!isValidProtocolChar(protocol.charAt(i))) if (!isValidProtocolChar(protocol.charAt(i))) {
return false; return false;
}
} }
return true; return true;
} }
Expand All @@ -62,32 +66,34 @@ private boolean isValidProtocol(String protocol) {
return protocol.length() > 0 && Character.isLetter(protocol.charAt(0)) && isValidProtocolChars(protocol); return protocol.length() > 0 && Character.isLetter(protocol.charAt(0)) && isValidProtocolChars(protocol);
} }


private void computeInitialScheme(String originalUrl) { private void computeInitialScheme() {
for (int i = start; i < end; i++) { for (int i = currentIndex; i < end; i++) {
char c = originalUrl.charAt(i); char c = originalUrl.charAt(i);
if (c == ':') { if (c == ':') {
String s = originalUrl.substring(start, i); String s = originalUrl.substring(currentIndex, i);
if (isValidProtocol(s)) { if (isValidProtocol(s)) {
scheme = s.toLowerCase(); scheme = s.toLowerCase();
start = i + 1; currentIndex = i + 1;
} }
break; break;
} else if (c == '/') } else if (c == '/') {
break; break;
}
} }
} }


private boolean overrideWithContext(Uri context, String originalUrl) { private boolean overrideWithContext(Uri context) {


boolean isRelative = false; boolean isRelative = false;


// only use context if the schemes match // use context only if schemes match
if (context != null && (scheme == null || scheme.equalsIgnoreCase(context.getScheme()))) { if (context != null && (scheme == null || scheme.equalsIgnoreCase(context.getScheme()))) {


// see RFC2396 5.2.3 // see RFC2396 5.2.3
String contextPath = context.getPath(); String contextPath = context.getPath();
if (isNonEmpty(contextPath) && contextPath.charAt(0) == '/') if (isNonEmpty(contextPath) && contextPath.charAt(0) == '/') {
scheme = null; scheme = null;
}


if (scheme == null) { if (scheme == null) {
scheme = context.getScheme(); scheme = context.getScheme();
Expand All @@ -101,63 +107,67 @@ private boolean overrideWithContext(Uri context, String originalUrl) {
return isRelative; return isRelative;
} }


private void computeFragment(String originalUrl) { private int findWithinCurrentRange(char c) {
int charpPosition = originalUrl.indexOf('#', start); int pos = originalUrl.indexOf(c, currentIndex);
return pos > end ? -1 : pos;
}

private void trimFragment() {
int charpPosition = findWithinCurrentRange('#');
if (charpPosition >= 0) { if (charpPosition >= 0) {
end = charpPosition; end = charpPosition;
} }
} }


private void inheritContextQuery(Uri context, boolean isRelative) { private void inheritContextQuery(Uri context, boolean isRelative) {
// see RFC2396 5.2.2: query and fragment inheritance // see RFC2396 5.2.2: query and fragment inheritance
if (isRelative && start == end) { if (isRelative && currentIndex == end) {
query = context.getQuery(); query = context.getQuery();
} }
} }


private boolean splitUrlAndQuery(String originalUrl) { private boolean computeQuery() {
boolean queryOnly = false; if (currentIndex < end) {
urlWithoutQuery = originalUrl; int askPosition = findWithinCurrentRange('?');
if (start < end) { if (askPosition != -1) {
int askPosition = originalUrl.indexOf('?');
queryOnly = askPosition == start;
if (askPosition != -1 && askPosition < end) {
query = originalUrl.substring(askPosition + 1, end); query = originalUrl.substring(askPosition + 1, end);
if (end > askPosition) if (end > askPosition) {
end = askPosition; end = askPosition;
urlWithoutQuery = originalUrl.substring(0, askPosition); }
return askPosition == currentIndex;
} }
} }

return false;
return queryOnly;
} }


private boolean currentPositionStartsWith4Slashes() { private boolean currentPositionStartsWith4Slashes() {
return urlWithoutQuery.regionMatches(start, "////", 0, 4); return originalUrl.regionMatches(currentIndex, "////", 0, 4);
} }


private boolean currentPositionStartsWith2Slashes() { private boolean currentPositionStartsWith2Slashes() {
return urlWithoutQuery.regionMatches(start, "//", 0, 2); return originalUrl.regionMatches(currentIndex, "//", 0, 2);
} }


private void computeAuthority() { private void computeAuthority() {
int authorityEndPosition = urlWithoutQuery.indexOf('/', start); int authorityEndPosition = findWithinCurrentRange('/');
if (authorityEndPosition < 0) { if (authorityEndPosition == -1) {
authorityEndPosition = urlWithoutQuery.indexOf('?', start); authorityEndPosition = findWithinCurrentRange('?');
if (authorityEndPosition < 0) if (authorityEndPosition == -1) {
authorityEndPosition = end; authorityEndPosition = end;
}
} }
host = authority = urlWithoutQuery.substring(start, authorityEndPosition); host = authority = originalUrl.substring(currentIndex, authorityEndPosition);
start = authorityEndPosition; currentIndex = authorityEndPosition;
} }


private void computeUserInfo() { private void computeUserInfo() {
int atPosition = authority.indexOf('@'); int atPosition = authority.indexOf('@');
if (atPosition != -1) { if (atPosition != -1) {
userInfo = authority.substring(0, atPosition); userInfo = authority.substring(0, atPosition);
host = authority.substring(atPosition + 1); host = authority.substring(atPosition + 1);
} else } else {
userInfo = null; userInfo = null;
}
} }


private boolean isMaybeIPV6() { private boolean isMaybeIPV6() {
Expand All @@ -179,14 +189,16 @@ private void computeIPV6() {
if (host.length() > portPosition) { if (host.length() > portPosition) {
port = Integer.parseInt(host.substring(portPosition)); port = Integer.parseInt(host.substring(portPosition));
} }
} else } else {
throw new IllegalArgumentException("Invalid authority field: " + authority); throw new IllegalArgumentException("Invalid authority field: " + authority);
}
} }


host = host.substring(0, positionAfterClosingSquareBrace); host = host.substring(0, positionAfterClosingSquareBrace);


} else } else {
throw new IllegalArgumentException("Invalid authority field: " + authority); throw new IllegalArgumentException("Invalid authority field: " + authority);
}
} }


private void computeRegularHostPort() { private void computeRegularHostPort() {
Expand Down Expand Up @@ -218,39 +230,44 @@ private void removeEmbedded2Dots() {
} else if (end == 0) { } else if (end == 0) {
break; break;
} }
} else } else {
i = i + 3; i = i + 3;
}
} }
} }


private void removeTailing2Dots() { private void removeTailing2Dots() {
while (path.endsWith("/..")) { while (path.endsWith("/..")) {
end = path.lastIndexOf('/', path.length() - 4); end = path.lastIndexOf('/', path.length() - 4);
if (end >= 0) if (end >= 0) {
path = path.substring(0, end + 1); path = path.substring(0, end + 1);
else } else {
break; break;
}
} }
} }


private void removeStartingDot() { private void removeStartingDot() {
if (path.startsWith("./") && path.length() > 2) if (path.startsWith("./") && path.length() > 2) {
path = path.substring(2); path = path.substring(2);
}
} }


private void removeTrailingDot() { private void removeTrailingDot() {
if (path.endsWith("/.")) if (path.endsWith("/.")) {
path = path.substring(0, path.length() - 1); path = path.substring(0, path.length() - 1);
}
} }


private void handleRelativePath() { private void handleRelativePath() {
int lastSlashPosition = path.lastIndexOf('/'); int lastSlashPosition = path.lastIndexOf('/');
String pathEnd = urlWithoutQuery.substring(start, end); String pathEnd = originalUrl.substring(currentIndex, end);


if (lastSlashPosition == -1) if (lastSlashPosition == -1) {
path = authority != null ? "/" + pathEnd : pathEnd; path = authority != null ? "/" + pathEnd : pathEnd;
else } else {
path = path.substring(0, lastSlashPosition + 1) + pathEnd; path = path.substring(0, lastSlashPosition + 1) + pathEnd;
}
} }


private void handlePathDots() { private void handlePathDots() {
Expand All @@ -265,36 +282,37 @@ private void handlePathDots() {


private void parseAuthority() { private void parseAuthority() {
if (!currentPositionStartsWith4Slashes() && currentPositionStartsWith2Slashes()) { if (!currentPositionStartsWith4Slashes() && currentPositionStartsWith2Slashes()) {
start += 2; currentIndex += 2;


computeAuthority(); computeAuthority();
computeUserInfo(); computeUserInfo();


if (host != null) { if (host != null) {
if (isMaybeIPV6()) if (isMaybeIPV6()) {
computeIPV6(); computeIPV6();
else } else {
computeRegularHostPort(); computeRegularHostPort();
}
} }


if (port < -1) if (port < -1) {
throw new IllegalArgumentException("Invalid port number :" + port); throw new IllegalArgumentException("Invalid port number :" + port);
}


// see RFC2396 5.2.4: ignore context path if authority is defined // see RFC2396 5.2.4: ignore context path if authority is defined
if (isNonEmpty(authority)) if (isNonEmpty(authority)) {
path = ""; path = "";
}
} }
} }


private void computeRegularPath() { private void computeRegularPath() {
if (urlWithoutQuery.charAt(start) == '/') if (originalUrl.charAt(currentIndex) == '/') {
path = urlWithoutQuery.substring(start, end); path = originalUrl.substring(currentIndex, end);

} else if (isNonEmpty(path)) {
else if (isNonEmpty(path))
handleRelativePath(); handleRelativePath();

} else {
else { String pathEnd = originalUrl.substring(currentIndex, end);
String pathEnd = urlWithoutQuery.substring(start, end);
path = isNonEmpty(pathEnd) && pathEnd.charAt(0) != '/' ? "/" + pathEnd : pathEnd; path = isNonEmpty(pathEnd) && pathEnd.charAt(0) != '/' ? "/" + pathEnd : pathEnd;
} }
handlePathDots(); handlePathDots();
Expand All @@ -307,29 +325,31 @@ private void computeQueryOnlyPath() {


private void computePath(boolean queryOnly) { private void computePath(boolean queryOnly) {
// Parse the file path if any // Parse the file path if any
if (start < end) if (currentIndex < end) {
computeRegularPath(); computeRegularPath();
else if (queryOnly && path != null) } else if (queryOnly && path != null) {
computeQueryOnlyPath(); computeQueryOnlyPath();
else if (path == null) } else if (path == null) {
path = ""; path = "";
}
} }


public void parse(Uri context, final String originalUrl) { public void parse(Uri context, final String originalUrl) {


assertNotNull(originalUrl, "orginalUri"); assertNotNull(originalUrl, "orginalUri");

this.originalUrl = originalUrl;
boolean isRelative = false; this.end = originalUrl.length();


trimRight(originalUrl); trimLeft();
trimLeft(originalUrl); trimRight();
if (!isFragmentOnly(originalUrl)) currentIndex = start;
computeInitialScheme(originalUrl); if (!isFragmentOnly()) {
overrideWithContext(context, originalUrl); computeInitialScheme();
computeFragment(originalUrl); }
boolean isRelative = overrideWithContext(context);
trimFragment();
inheritContextQuery(context, isRelative); inheritContextQuery(context, isRelative);

boolean queryOnly = computeQuery();
boolean queryOnly = splitUrlAndQuery(originalUrl);
parseAuthority(); parseAuthority();
computePath(queryOnly); computePath(queryOnly);
} }
Expand Down

0 comments on commit eb9e334

Please sign in to comment.