Skip to content

Commit eb9e334

Browse files
committed
Don't be tricked by anchors containing a question mark, close AsyncHttpClient#1455
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.
1 parent 62b5a1d commit eb9e334

File tree

3 files changed

+211
-323
lines changed

3 files changed

+211
-323
lines changed

client/src/main/java/org/asynchttpclient/uri/UriParser.java

Lines changed: 102 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -25,24 +25,27 @@ final class UriParser {
2525
public String path;
2626
public String userInfo;
2727

28-
private int start, end = 0;
29-
private String urlWithoutQuery;
28+
private String originalUrl;
29+
private int start, end, currentIndex = 0;
3030

31-
private void trimRight(String originalUrl) {
32-
end = originalUrl.length();
33-
while (end > 0 && originalUrl.charAt(end - 1) <= ' ')
34-
end--;
35-
}
36-
37-
private void trimLeft(String originalUrl) {
38-
while (start < end && originalUrl.charAt(start) <= ' ')
31+
private void trimLeft() {
32+
while (start < end && originalUrl.charAt(start) <= ' ') {
3933
start++;
34+
}
4035

41-
if (originalUrl.regionMatches(true, start, "url:", 0, 4))
36+
if (originalUrl.regionMatches(true, start, "url:", 0, 4)) {
4237
start += 4;
38+
}
4339
}
4440

45-
private boolean isFragmentOnly(String originalUrl) {
41+
private void trimRight() {
42+
end = originalUrl.length();
43+
while (end > 0 && originalUrl.charAt(end - 1) <= ' ') {
44+
end--;
45+
}
46+
}
47+
48+
private boolean isFragmentOnly() {
4649
return start < originalUrl.length() && originalUrl.charAt(start) == '#';
4750
}
4851

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

5356
private boolean isValidProtocolChars(String protocol) {
5457
for (int i = 1; i < protocol.length(); i++) {
55-
if (!isValidProtocolChar(protocol.charAt(i)))
58+
if (!isValidProtocolChar(protocol.charAt(i))) {
5659
return false;
60+
}
5761
}
5862
return true;
5963
}
@@ -62,32 +66,34 @@ private boolean isValidProtocol(String protocol) {
6266
return protocol.length() > 0 && Character.isLetter(protocol.charAt(0)) && isValidProtocolChars(protocol);
6367
}
6468

65-
private void computeInitialScheme(String originalUrl) {
66-
for (int i = start; i < end; i++) {
69+
private void computeInitialScheme() {
70+
for (int i = currentIndex; i < end; i++) {
6771
char c = originalUrl.charAt(i);
6872
if (c == ':') {
69-
String s = originalUrl.substring(start, i);
73+
String s = originalUrl.substring(currentIndex, i);
7074
if (isValidProtocol(s)) {
71-
scheme = s.toLowerCase();
72-
start = i + 1;
75+
scheme = s.toLowerCase();
76+
currentIndex = i + 1;
7377
}
7478
break;
75-
} else if (c == '/')
79+
} else if (c == '/') {
7680
break;
81+
}
7782
}
7883
}
7984

80-
private boolean overrideWithContext(Uri context, String originalUrl) {
85+
private boolean overrideWithContext(Uri context) {
8186

8287
boolean isRelative = false;
8388

84-
// only use context if the schemes match
89+
// use context only if schemes match
8590
if (context != null && (scheme == null || scheme.equalsIgnoreCase(context.getScheme()))) {
8691

8792
// see RFC2396 5.2.3
8893
String contextPath = context.getPath();
89-
if (isNonEmpty(contextPath) && contextPath.charAt(0) == '/')
90-
scheme = null;
94+
if (isNonEmpty(contextPath) && contextPath.charAt(0) == '/') {
95+
scheme = null;
96+
}
9197

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

104-
private void computeFragment(String originalUrl) {
105-
int charpPosition = originalUrl.indexOf('#', start);
110+
private int findWithinCurrentRange(char c) {
111+
int pos = originalUrl.indexOf(c, currentIndex);
112+
return pos > end ? -1 : pos;
113+
}
114+
115+
private void trimFragment() {
116+
int charpPosition = findWithinCurrentRange('#');
106117
if (charpPosition >= 0) {
107118
end = charpPosition;
108119
}
109120
}
110121

111122
private void inheritContextQuery(Uri context, boolean isRelative) {
112123
// see RFC2396 5.2.2: query and fragment inheritance
113-
if (isRelative && start == end) {
124+
if (isRelative && currentIndex == end) {
114125
query = context.getQuery();
115126
}
116127
}
117128

118-
private boolean splitUrlAndQuery(String originalUrl) {
119-
boolean queryOnly = false;
120-
urlWithoutQuery = originalUrl;
121-
if (start < end) {
122-
int askPosition = originalUrl.indexOf('?');
123-
queryOnly = askPosition == start;
124-
if (askPosition != -1 && askPosition < end) {
129+
private boolean computeQuery() {
130+
if (currentIndex < end) {
131+
int askPosition = findWithinCurrentRange('?');
132+
if (askPosition != -1) {
125133
query = originalUrl.substring(askPosition + 1, end);
126-
if (end > askPosition)
134+
if (end > askPosition) {
127135
end = askPosition;
128-
urlWithoutQuery = originalUrl.substring(0, askPosition);
136+
}
137+
return askPosition == currentIndex;
129138
}
130139
}
131-
132-
return queryOnly;
140+
return false;
133141
}
134142

135143
private boolean currentPositionStartsWith4Slashes() {
136-
return urlWithoutQuery.regionMatches(start, "////", 0, 4);
144+
return originalUrl.regionMatches(currentIndex, "////", 0, 4);
137145
}
138146

139147
private boolean currentPositionStartsWith2Slashes() {
140-
return urlWithoutQuery.regionMatches(start, "//", 0, 2);
148+
return originalUrl.regionMatches(currentIndex, "//", 0, 2);
141149
}
142150

143151
private void computeAuthority() {
144-
int authorityEndPosition = urlWithoutQuery.indexOf('/', start);
145-
if (authorityEndPosition < 0) {
146-
authorityEndPosition = urlWithoutQuery.indexOf('?', start);
147-
if (authorityEndPosition < 0)
152+
int authorityEndPosition = findWithinCurrentRange('/');
153+
if (authorityEndPosition == -1) {
154+
authorityEndPosition = findWithinCurrentRange('?');
155+
if (authorityEndPosition == -1) {
148156
authorityEndPosition = end;
157+
}
149158
}
150-
host = authority = urlWithoutQuery.substring(start, authorityEndPosition);
151-
start = authorityEndPosition;
159+
host = authority = originalUrl.substring(currentIndex, authorityEndPosition);
160+
currentIndex = authorityEndPosition;
152161
}
153162

154163
private void computeUserInfo() {
155164
int atPosition = authority.indexOf('@');
156165
if (atPosition != -1) {
157166
userInfo = authority.substring(0, atPosition);
158167
host = authority.substring(atPosition + 1);
159-
} else
168+
} else {
160169
userInfo = null;
170+
}
161171
}
162172

163173
private boolean isMaybeIPV6() {
@@ -179,14 +189,16 @@ private void computeIPV6() {
179189
if (host.length() > portPosition) {
180190
port = Integer.parseInt(host.substring(portPosition));
181191
}
182-
} else
192+
} else {
183193
throw new IllegalArgumentException("Invalid authority field: " + authority);
194+
}
184195
}
185196

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

188-
} else
199+
} else {
189200
throw new IllegalArgumentException("Invalid authority field: " + authority);
201+
}
190202
}
191203

192204
private void computeRegularHostPort() {
@@ -218,39 +230,44 @@ private void removeEmbedded2Dots() {
218230
} else if (end == 0) {
219231
break;
220232
}
221-
} else
233+
} else {
222234
i = i + 3;
235+
}
223236
}
224237
}
225238

226239
private void removeTailing2Dots() {
227240
while (path.endsWith("/..")) {
228241
end = path.lastIndexOf('/', path.length() - 4);
229-
if (end >= 0)
242+
if (end >= 0) {
230243
path = path.substring(0, end + 1);
231-
else
244+
} else {
232245
break;
246+
}
233247
}
234248
}
235249

236250
private void removeStartingDot() {
237-
if (path.startsWith("./") && path.length() > 2)
251+
if (path.startsWith("./") && path.length() > 2) {
238252
path = path.substring(2);
253+
}
239254
}
240255

241256
private void removeTrailingDot() {
242-
if (path.endsWith("/."))
257+
if (path.endsWith("/.")) {
243258
path = path.substring(0, path.length() - 1);
259+
}
244260
}
245261

246262
private void handleRelativePath() {
247263
int lastSlashPosition = path.lastIndexOf('/');
248-
String pathEnd = urlWithoutQuery.substring(start, end);
264+
String pathEnd = originalUrl.substring(currentIndex, end);
249265

250-
if (lastSlashPosition == -1)
266+
if (lastSlashPosition == -1) {
251267
path = authority != null ? "/" + pathEnd : pathEnd;
252-
else
268+
} else {
253269
path = path.substring(0, lastSlashPosition + 1) + pathEnd;
270+
}
254271
}
255272

256273
private void handlePathDots() {
@@ -265,36 +282,37 @@ private void handlePathDots() {
265282

266283
private void parseAuthority() {
267284
if (!currentPositionStartsWith4Slashes() && currentPositionStartsWith2Slashes()) {
268-
start += 2;
285+
currentIndex += 2;
269286

270287
computeAuthority();
271288
computeUserInfo();
272289

273290
if (host != null) {
274-
if (isMaybeIPV6())
291+
if (isMaybeIPV6()) {
275292
computeIPV6();
276-
else
293+
} else {
277294
computeRegularHostPort();
295+
}
278296
}
279297

280-
if (port < -1)
298+
if (port < -1) {
281299
throw new IllegalArgumentException("Invalid port number :" + port);
300+
}
282301

283302
// see RFC2396 5.2.4: ignore context path if authority is defined
284-
if (isNonEmpty(authority))
303+
if (isNonEmpty(authority)) {
285304
path = "";
305+
}
286306
}
287307
}
288308

289309
private void computeRegularPath() {
290-
if (urlWithoutQuery.charAt(start) == '/')
291-
path = urlWithoutQuery.substring(start, end);
292-
293-
else if (isNonEmpty(path))
310+
if (originalUrl.charAt(currentIndex) == '/') {
311+
path = originalUrl.substring(currentIndex, end);
312+
} else if (isNonEmpty(path)) {
294313
handleRelativePath();
295-
296-
else {
297-
String pathEnd = urlWithoutQuery.substring(start, end);
314+
} else {
315+
String pathEnd = originalUrl.substring(currentIndex, end);
298316
path = isNonEmpty(pathEnd) && pathEnd.charAt(0) != '/' ? "/" + pathEnd : pathEnd;
299317
}
300318
handlePathDots();
@@ -307,29 +325,31 @@ private void computeQueryOnlyPath() {
307325

308326
private void computePath(boolean queryOnly) {
309327
// Parse the file path if any
310-
if (start < end)
328+
if (currentIndex < end) {
311329
computeRegularPath();
312-
else if (queryOnly && path != null)
330+
} else if (queryOnly && path != null) {
313331
computeQueryOnlyPath();
314-
else if (path == null)
332+
} else if (path == null) {
315333
path = "";
334+
}
316335
}
317336

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

320339
assertNotNull(originalUrl, "orginalUri");
321-
322-
boolean isRelative = false;
323-
324-
trimRight(originalUrl);
325-
trimLeft(originalUrl);
326-
if (!isFragmentOnly(originalUrl))
327-
computeInitialScheme(originalUrl);
328-
overrideWithContext(context, originalUrl);
329-
computeFragment(originalUrl);
340+
this.originalUrl = originalUrl;
341+
this.end = originalUrl.length();
342+
343+
trimLeft();
344+
trimRight();
345+
currentIndex = start;
346+
if (!isFragmentOnly()) {
347+
computeInitialScheme();
348+
}
349+
boolean isRelative = overrideWithContext(context);
350+
trimFragment();
330351
inheritContextQuery(context, isRelative);
331-
332-
boolean queryOnly = splitUrlAndQuery(originalUrl);
352+
boolean queryOnly = computeQuery();
333353
parseAuthority();
334354
computePath(queryOnly);
335355
}

0 commit comments

Comments
 (0)