Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(327)

Side by Side Diff: sdk/lib/core/uri.dart

Issue 335373003: New Uri.parse and validation. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: More test. Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of dart.core; 5 part of dart.core;
6 6
7 /** 7 /**
8 * A parsed URI, such as a URL. 8 * A parsed URI, such as a URL.
9 * 9 *
10 * **See also:** 10 * **See also:**
(...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after
109 * Cache the computed return value of [pathSegements]. 109 * Cache the computed return value of [pathSegements].
110 */ 110 */
111 List<String> _pathSegments; 111 List<String> _pathSegments;
112 112
113 /** 113 /**
114 * Cache the computed return value of [queryParameters]. 114 * Cache the computed return value of [queryParameters].
115 */ 115 */
116 Map<String, String> _queryParameters; 116 Map<String, String> _queryParameters;
117 117
118 /** 118 /**
119 * Creates a new URI object by parsing a URI string. 119 * Creates a new `Uri` object by parsing a URI string.
120 *
121 * If the string is not valid as a URI or URI reference,
122 * invalid characters will be percent escaped where possible.
123 * The resulting `Uri` will represent a valid URI or URI reference.
120 */ 124 */
121 static Uri parse(String uri) { 125 static Uri parse(String uri) {
122 // This parsing will not validate percent-encoding, IPv6, etc. When done 126 // This parsing will not validate percent-encoding, IPv6, etc. When done
123 // it will call `new Uri(...)` which will perform these validations. 127 // it will call `new Uri(...)` which will perform these validations.
124 // This is purely splitting up the URI string into components. 128 // This is purely splitting up the URI string into components.
125 // 129 //
126 // Important parts of the RFC 3986 used here: 130 // Important parts of the RFC 3986 used here:
127 // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ] 131 // URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
128 // 132 //
129 // hier-part = "//" authority path-abempty 133 // hier-part = "//" authority path-abempty
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
168 // ; non-zero-length segment without any colon ":" 172 // ; non-zero-length segment without any colon ":"
169 // 173 //
170 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 174 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
171 // 175 //
172 // query = *( pchar / "/" / "?" ) 176 // query = *( pchar / "/" / "?" )
173 // 177 //
174 // fragment = *( pchar / "/" / "?" ) 178 // fragment = *( pchar / "/" / "?" )
175 bool isRegName(int ch) { 179 bool isRegName(int ch) {
176 return ch < 128 && ((_regNameTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 180 return ch < 128 && ((_regNameTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
177 } 181 }
178 182 const int EOI = -1;
179 int ipV6Address(int index) { 183
180 // IPv6. Skip to ']'. 184 String scheme = "";
181 index = uri.indexOf(']', index); 185 String path;
182 if (index == -1) { 186 String userinfo = "";
183 throw new FormatException("Bad end of IPv6 host"); 187 String host = "";
184 } 188 int port = 0;
185 return index + 1; 189 String query = "";
186 } 190 String fragment = "";
187 191
188 int length = uri.length;
189 int index = 0; 192 int index = 0;
190 193 int pathStart = 0;
191 int schemeEndIndex = 0; 194 // End of input-marker.
192 195 int char = EOI;
193 if (length == 0) { 196
194 return new Uri(); 197 void parseAuth() {
195 } 198 if (index == uri.length) {
196 199 char = EOI;
197 if (uri.codeUnitAt(0) != _SLASH) { 200 return;
198 // Can be scheme. 201 }
199 while (index < length) { 202 int authStart = index;
200 // Look for ':'. If found, continue from the post of ':'. If not (end 203 int lastColon = -1;
201 // reached or invalid scheme char found) back up one char, and continue 204 int lastAt = -1;
202 // to path. 205 char = uri.codeUnitAt(index);
203 // Note that scheme-chars is contained in path-chars. 206 while (index < uri.length) {
204 int codeUnit = uri.codeUnitAt(index++); 207 char = uri.codeUnitAt(index);
205 if (!_isSchemeCharacter(codeUnit)) { 208 if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) {
206 if (codeUnit == _COLON) {
207 schemeEndIndex = index;
208 } else {
209 // Back up one char, since we met an invalid scheme char.
210 index--;
211 }
212 break; 209 break;
213 } 210 }
214 } 211 if (char == _AT_SIGN) {
215 } 212 lastAt = index;
216 213 lastColon = -1;
217 int userInfoEndIndex = -1; 214 } else if (char == _COLON) {
218 int portIndex = -1; 215 lastColon = index;
219 int authorityEndIndex = schemeEndIndex; 216 } else if (char == _LEFT_BRACKET) {
220 // If we see '//', there must be an authority. 217 lastColon = -1;
221 if (authorityEndIndex == index && 218 int endBracket = uri.indexOf(']', index + 1);
222 authorityEndIndex + 1 < length && 219 if (endBracket == -1) {
223 uri.codeUnitAt(authorityEndIndex) == _SLASH && 220 index = uri.length;
224 uri.codeUnitAt(authorityEndIndex + 1) == _SLASH) { 221 char = EOI;
225 // Skip '//'.
226 authorityEndIndex += 2;
227 // It can both be host and userInfo.
228 while (authorityEndIndex < length) {
229 int codeUnit = uri.codeUnitAt(authorityEndIndex++);
230 if (!isRegName(codeUnit)) {
231 if (codeUnit == _LEFT_BRACKET) {
232 authorityEndIndex = ipV6Address(authorityEndIndex);
233 } else if (portIndex == -1 && codeUnit == _COLON) {
234 // First time ':'.
235 portIndex = authorityEndIndex;
236 } else if (codeUnit == _AT_SIGN || codeUnit == _COLON) {
237 // Second time ':' or first '@'. Must be userInfo.
238 userInfoEndIndex = uri.indexOf('@', authorityEndIndex - 1);
239 // Not found. Must be path then.
240 if (userInfoEndIndex == -1) {
241 authorityEndIndex = index;
242 break;
243 }
244 portIndex = -1;
245 authorityEndIndex = userInfoEndIndex + 1;
246 // Now it can only be host:port.
247 while (authorityEndIndex < length) {
248 int codeUnit = uri.codeUnitAt(authorityEndIndex++);
249 if (!isRegName(codeUnit)) {
250 if (codeUnit == _LEFT_BRACKET) {
251 authorityEndIndex = ipV6Address(authorityEndIndex);
252 } else if (codeUnit == _COLON) {
253 if (portIndex != -1) {
254 throw new FormatException("Double port in host");
255 }
256 portIndex = authorityEndIndex;
257 } else {
258 authorityEndIndex--;
259 break;
260 }
261 }
262 }
263 break; 222 break;
264 } else { 223 } else {
265 authorityEndIndex--; 224 index = endBracket;
266 break;
267 } 225 }
268 } 226 }
269 } 227 index++;
228 char = EOI;
229 }
230 int hostStart = authStart;
231 int hostEnd = index;
232 if (lastAt >= 0) {
233 userinfo = _makeUserInfo(uri, authStart, lastAt);
234 hostStart = lastAt + 1;
235 }
236 if (lastColon >= 0) {
237 if (lastColon + 1 == index) {
238 _fail(uri, index, "Invalid port number");
239 }
240 int portNumber = 0;
241 for (int i = lastColon + 1; i < index; i++) {
242 int digit = uri.codeUnitAt(i);
243 if (_ZERO > digit || _NINE < digit) {
244 _fail(uri, i, "Invalid port number");
245 }
246 portNumber = portNumber * 10 + (digit - _ZERO);
247 }
248 port = _makePort(portNumber, scheme);
249 hostEnd = lastColon;
250 }
251 host = _makeHost(uri, hostStart, hostEnd, true);
252 if (index < uri.length) {
253 char = uri.codeUnitAt(index);
254 }
255 }
256
257 // When reaching path parsing, the current character is known to not
258 // be part of the path.
259 const int NOT_IN_PATH = 0;
260 // When reaching path parsing, the current character is part
261 // of the a non-empty path.
262 const int IN_PATH = 1;
263 // When reaching authority parsing, authority is possible.
264 // This is only true at start or right after scheme.
265 const int ALLOW_AUTH = 2;
266
267 // Current state.
268 // Initialized to the default value that is used when exiting the
269 // scheme loop by reaching the end of input.
270 // All other breaks set their own state.
271 int state = NOT_IN_PATH;
272 while (index < uri.length) {
273 char = uri.codeUnitAt(index);
274 if (char == _QUESTION || char == _NUMBER_SIGN) {
275 state = NOT_IN_PATH;
276 break;
277 }
278 if (char == _SLASH) {
279 state = (index == 0) ? ALLOW_AUTH : IN_PATH;
280 break;
281 }
282 if (char == _COLON) {
283 if (index == 0) _fail(uri, 0, "Invalid empty scheme");
284 scheme = _makeScheme(uri, index);
285 index++;
286 pathStart = index;
287 if (index == uri.length) {
288 char = EOI;
289 state = NOT_IN_PATH;
290 } else {
291 char = uri.codeUnitAt(index);
292 if (char == _QUESTION || char == _NUMBER_SIGN) {
293 state = NOT_IN_PATH;
294 } else if (char == _SLASH) {
295 state = ALLOW_AUTH;
296 } else {
297 state = IN_PATH;
298 }
299 }
300 break;
301 }
302 index++;
303 char = EOI;
304 }
305
306 if (state == ALLOW_AUTH) {
307 assert(char == _SLASH);
308 // Have seen one slash either at start or right after scheme.
309 // If two slashes, it's an authority, otherwise it's just the path.
310 index++;
311 if (index == uri.length) {
312 char = EOI;
313 state = NOT_IN_PATH;
314 } else {
315 char = uri.codeUnitAt(index);
316 if (char == _SLASH) {
317 index++;
318 parseAuth();
319 pathStart = index;
320 }
321 if (char == _QUESTION || char == _NUMBER_SIGN || char == EOI) {
322 state = NOT_IN_PATH;
323 } else {
324 state = IN_PATH;
325 }
326 }
327 }
328
329 assert(state == IN_PATH || state == NOT_IN_PATH);
330 if (state == IN_PATH) {
331 // Characters from pathStart to index (inclusive) are known
332 // to be part of the path.
333 while (++index < uri.length) {
334 char = uri.codeUnitAt(index);
335 if (char == _QUESTION || char == _NUMBER_SIGN) {
336 break;
337 }
338 char = EOI;
339 }
340 state = NOT_IN_PATH;
341 }
342
343 assert(state == NOT_IN_PATH);
344 bool ensureLeadingSlash = (host != "" || scheme == "file");
345 path = _makePath(uri, pathStart, index, null, ensureLeadingSlash);
346
347 if (char == _QUESTION) {
348 int numberSignIndex = uri.indexOf('#', index + 1);
349 if (numberSignIndex < 0) {
350 query = _makeQuery(uri, index + 1, uri.length, null);
351 } else {
352 query = _makeQuery(uri, index + 1, numberSignIndex, null);
353 fragment = _makeFragment(uri, numberSignIndex + 1, uri.length);
354 }
355 } else if (char == _NUMBER_SIGN) {
356 fragment = _makeFragment(uri, index + 1, uri.length);
357 }
358 return new Uri._internal(scheme,
359 userinfo,
360 host,
361 port,
362 path,
363 query,
364 fragment);
365 }
366
367 // Report a parse failure.
368 static void _fail(String uri, int index, String message) {
369 // TODO(lrn): Consider adding this to FormatException.
370 if (index == uri.length) {
371 message += " at end of input.";
270 } else { 372 } else {
271 authorityEndIndex = schemeEndIndex; 373 message += " at position $index.\n";
272 } 374 // Pick a slice of uri containing index and, if
273 375 // necessary, truncate the ends to ensure the entire
274 // At path now. 376 // slice fits on one line.
275 int pathEndIndex = authorityEndIndex; 377 int min = 0;
276 while (pathEndIndex < length) { 378 int max = uri.length;
277 int codeUnit = uri.codeUnitAt(pathEndIndex++); 379 String pre = "";
278 if (codeUnit == _QUESTION || codeUnit == _NUMBER_SIGN) { 380 String post = "";
279 pathEndIndex--; 381 if (uri.length > 78) {
280 break; 382 min = index - 10;
281 } 383 if (min < 0) min = 0;
282 } 384 int max = min + 72;
283 385 if (max > uri.length) {
284 // Maybe query. 386 max = uri.length;
285 int queryEndIndex = pathEndIndex; 387 min = max - 72;
286 if (queryEndIndex < length && uri.codeUnitAt(queryEndIndex) == _QUESTION) { 388 }
287 while (queryEndIndex < length) { 389 if (min != 0) pre = "...";
288 int codeUnit = uri.codeUnitAt(queryEndIndex++); 390 if (max != uri.length) post = "...";
289 if (codeUnit == _NUMBER_SIGN) { 391 }
290 queryEndIndex--; 392 // Combine message, slice and a caret pointing to the error index.
291 break; 393 message = "$message$pre${uri.substring(min, max)}$post\n"
292 } 394 "${' ' * (pre.length + index - min)}^";
293 } 395 }
294 } 396 throw new FormatException(message);
295
296 var scheme = null;
297 if (schemeEndIndex > 0) {
298 scheme = uri.substring(0, schemeEndIndex - 1);
299 }
300
301 var host = "";
302 var userInfo = "";
303 var port = 0;
304 if (schemeEndIndex != authorityEndIndex) {
305 int startIndex = schemeEndIndex + 2;
306 if (userInfoEndIndex > 0) {
307 userInfo = uri.substring(startIndex, userInfoEndIndex);
308 startIndex = userInfoEndIndex + 1;
309 }
310 if (portIndex > 0) {
311 var portStr = uri.substring(portIndex, authorityEndIndex);
312 try {
313 port = int.parse(portStr);
314 } catch (_) {
315 throw new FormatException("Invalid port: '$portStr'");
316 }
317 host = uri.substring(startIndex, portIndex - 1);
318 } else {
319 host = uri.substring(startIndex, authorityEndIndex);
320 }
321 }
322
323 var path = uri.substring(authorityEndIndex, pathEndIndex);
324 var query = "";
325 if (pathEndIndex < queryEndIndex) {
326 query = uri.substring(pathEndIndex + 1, queryEndIndex);
327 }
328 var fragment = "";
329 // If queryEndIndex is not at end (length), there is a fragment.
330 if (queryEndIndex < length) {
331 fragment = uri.substring(queryEndIndex + 1, length);
332 }
333
334 return new Uri(scheme: scheme,
335 userInfo: userInfo,
336 host: host,
337 port: port,
338 path: path,
339 query: query,
340 fragment: fragment);
341 } 397 }
342 398
399 /// Internal non-verifying constructor. Only call with validated arguments.
400 Uri._internal(this.scheme,
401 this.userInfo,
402 this._host,
403 this._port,
404 this._path,
405 this.query,
406 this.fragment);
407
343 /** 408 /**
344 * Creates a new URI from its components. 409 * Creates a new URI from its components.
345 * 410 *
346 * Each component is set through a named argument. Any number of 411 * Each component is set through a named argument. Any number of
347 * components can be provided. The default value for the components 412 * components can be provided. The default value for the components
348 * not provided is the empry string, except for [port] which has a 413 * not provided is the empry string, except for [port] which has a
349 * default value of 0. The [path] and [query] components can be set 414 * default value of 0. The [path] and [query] components can be set
350 * using two different named arguments. 415 * using two different named arguments.
351 * 416 *
352 * The scheme component is set through [scheme]. The scheme is 417 * The scheme component is set through [scheme]. The scheme is
353 * normalized to all lowercase letters. 418 * normalized to all lowercase letters.
354 * 419 *
355 * The user info part of the authority component is set through 420 * The user info part of the authority component is set through
356 * [userInfo]. 421 * [userInfo].
357 * 422 *
358 * The host part of the authority component is set through 423 * The host part of the authority component is set through
359 * [host]. The host can either be a hostname, an IPv4 address or an 424 * [host]. The host can either be a hostname, an IPv4 address or an
360 * IPv6 address, contained in '[' and ']'. If the host contains a 425 * IPv6 address, contained in '[' and ']'. If the host contains a
361 * ':' character, the '[' and ']' are added if not already provided. 426 * ':' character, the '[' and ']' are added if not already provided.
427 * The host is normalized to all lowercase letters.
362 * 428 *
363 * The port part of the authority component is set through 429 * The port part of the authority component is set through
364 * [port]. The port is normalized for scheme http and https where 430 * [port]. The port is normalized for scheme http and https where
365 * port 80 and port 443 respectively is set. 431 * port 80 and port 443 respectively is set.
366 * 432 *
367 * The path component is set through either [path] or 433 * The path component is set through either [path] or
368 * [pathSegments]. When [path] is used, the provided string is 434 * [pathSegments]. When [path] is used, the provided string is
369 * expected to be fully percent-encoded, and is used in its literal 435 * expected to be fully percent-encoded, and is used in its literal
370 * form. When [pathSegments] is used, each of the provided segments 436 * form. When [pathSegments] is used, each of the provided segments
371 * is percent-encoded and joined using the forward slash 437 * is percent-encoded and joined using the forward slash
372 * separator. The percent-encoding of the path segments encodes all 438 * separator. The percent-encoding of the path segments encodes all
373 * characters except for the unreserved characters and the following 439 * characters except for the unreserved characters and the following
374 * list of characters: `!$&'()*+,;=:@`. If the other components 440 * list of characters: `!$&'()*+,;=:@`. If the other components
375 * calls for an absolute path a leading slash `/` is prepended if 441 * calls for an absolute path a leading slash `/` is prepended if
376 * not already there. 442 * not already there.
377 * 443 *
378 * The query component is set through either [query] or 444 * The query component is set through either [query] or
379 * [queryParameters]. When [query] is used the provided string is 445 * [queryParameters]. When [query] is used the provided string is
380 * expected to be fully percent-encoded and is used in its literal 446 * expected to be fully percent-encoded and is used in its literal
381 * form. When [queryParameters] is used the query is built from the 447 * form. When [queryParameters] is used the query is built from the
382 * provided map. Each key and value in the map is percent-encoded 448 * provided map. Each key and value in the map is percent-encoded
383 * and joined using equal and ampersand characters. The 449 * and joined using equal and ampersand characters. The
384 * percent-encoding of the keys and values encodes all characters 450 * percent-encoding of the keys and values encodes all characters
385 * except for the unreserved characters. 451 * except for the unreserved characters.
386 * 452 *
387 * The fragment component is set through [fragment]. 453 * The fragment component is set through [fragment].
388 */ 454 */
389 Uri({String scheme, 455 factory Uri({String scheme,
390 this.userInfo: "", 456 String userInfo: "",
391 String host: "", 457 String host: "",
392 port: 0, 458 port: 0,
393 String path, 459 String path,
394 Iterable<String> pathSegments, 460 Iterable<String> pathSegments,
395 String query, 461 String query,
396 Map<String, String> queryParameters, 462 Map<String, String> queryParameters,
397 fragment: ""}) : 463 fragment: ""}) {
398 scheme = _makeScheme(scheme), 464 scheme = _makeScheme(scheme, _stringOrNullLength(scheme));
399 _host = _makeHost(host), 465 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
400 query = _makeQuery(query, queryParameters), 466 host = _makeHost(host, 0, _stringOrNullLength(host), false);
401 fragment = _makeFragment(fragment) { 467 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
402 // Perform scheme specific normalization. 468 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
403 if (scheme == "http" && port == 80) { 469 port = _makePort(port, scheme);
404 _port = 0; 470 bool ensureLeadingSlash = (host != "" || scheme == "file");
405 } else if (scheme == "https" && port == 443) { 471 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
406 _port = 0; 472 ensureLeadingSlash);
407 } else { 473
408 _port = port; 474 return new Uri._internal(scheme, userInfo, host, port,
409 } 475 path, query, fragment);
410 // Fill the path.
411 _path = _makePath(path, pathSegments);
412 } 476 }
413 477
414 /** 478 /**
415 * Creates a new `http` URI from authority, path and query. 479 * Creates a new `http` URI from authority, path and query.
416 * 480 *
417 * Examples: 481 * Examples:
418 * 482 *
419 * ``` 483 * ```
420 * // http://example.org/path?q=dart. 484 * // http://example.org/path?q=dart.
421 * new Uri.http("google.com", "/search", { "q" : "dart" }); 485 * new Uri.http("google.com", "/search", { "q" : "dart" });
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
482 var hostEnd = hostStart; 546 var hostEnd = hostStart;
483 if (hostStart < authority.length && 547 if (hostStart < authority.length &&
484 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) { 548 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
485 // IPv6 host. 549 // IPv6 host.
486 for (; hostEnd < authority.length; hostEnd++) { 550 for (; hostEnd < authority.length; hostEnd++) {
487 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break; 551 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
488 } 552 }
489 if (hostEnd == authority.length) { 553 if (hostEnd == authority.length) {
490 throw new FormatException("Invalid IPv6 host entry."); 554 throw new FormatException("Invalid IPv6 host entry.");
491 } 555 }
492 parseIPv6Address(authority.substring(hostStart + 1, hostEnd)); 556 parseIPv6Address(authority, hostStart + 1, hostEnd);
493 hostEnd++; // Skip the closing bracket. 557 hostEnd++; // Skip the closing bracket.
494 if (hostEnd != authority.length && 558 if (hostEnd != authority.length &&
495 authority.codeUnitAt(hostEnd) != _COLON) { 559 authority.codeUnitAt(hostEnd) != _COLON) {
496 throw new FormatException("Invalid end of authority"); 560 throw new FormatException("Invalid end of authority");
497 } 561 }
498 } 562 }
499 // Split host and port. 563 // Split host and port.
500 bool hasPort = false; 564 bool hasPort = false;
501 for (; hostEnd < authority.length; hostEnd++) { 565 for (; hostEnd < authority.length; hostEnd++) {
502 if (authority.codeUnitAt(hostEnd) == _COLON) { 566 if (authority.codeUnitAt(hostEnd) == _COLON) {
(...skipping 255 matching lines...) Expand 10 before | Expand all | Expand 10 after
758 * The returned map is unmodifiable and will throw [UnsupportedError] on any 822 * The returned map is unmodifiable and will throw [UnsupportedError] on any
759 * calls that would mutate it. 823 * calls that would mutate it.
760 */ 824 */
761 Map<String, String> get queryParameters { 825 Map<String, String> get queryParameters {
762 if (_queryParameters == null) { 826 if (_queryParameters == null) {
763 _queryParameters = new UnmodifiableMapView(splitQueryString(query)); 827 _queryParameters = new UnmodifiableMapView(splitQueryString(query));
764 } 828 }
765 return _queryParameters; 829 return _queryParameters;
766 } 830 }
767 831
768 static String _makeHost(String host) { 832 static int _makePort(int port, String scheme) {
769 if (host == null || host.isEmpty) return host; 833 // Perform scheme specific normalization.
770 if (host.codeUnitAt(0) == _LEFT_BRACKET) { 834 if (port == 80 && scheme == "http") {
771 if (host.codeUnitAt(host.length - 1) != _RIGHT_BRACKET) { 835 return 0;
772 throw new FormatException('Missing end `]` to match `[` in host');
773 }
774 parseIPv6Address(host.substring(1, host.length - 1));
775 return host;
776 } 836 }
777 for (int i = 0; i < host.length; i++) { 837 if (port == 443 && scheme == "https") {
778 if (host.codeUnitAt(i) == _COLON) { 838 return 0;
779 parseIPv6Address(host);
780 return '[$host]';
781 }
782 } 839 }
783 return host; 840 return port;
784 } 841 }
785 842
786 static String _makeScheme(String scheme) { 843 /**
787 bool isSchemeLowerCharacter(int ch) { 844 * Check and normalize a most name.
788 return ch < 128 && 845 *
789 ((_schemeLowerTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 846 * If the host name starts and ends with '[' and ']', it is considered an
847 * IPv6 address. If [strictIPv6] is false, the address is also considered
848 * an IPv6 address if it contains any ':' character.
849 *
850 * If it is not an IPv6 address, it is case- and escape-normalized.
851 * This escapes all characters not valid in a reg-name,
852 * and converts all non-escape upper-case letters to lower-case.
853 */
854 static String _makeHost(String host, int start, int end, bool strictIPv6) {
855 // TODO(lrn): Should we normalize IPv6 addresses according to RFC 5952?
856
857 if (host == null) return null;
858 if (start == end) return "";
859 // Host is an IPv6 address if it starts with '[' or contains a colon.
860 if (host.codeUnitAt(start) == _LEFT_BRACKET) {
861 if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) {
862 _fail(host, start, 'Missing end `]` to match `[` in host');
863 }
864 parseIPv6Address(host, start + 1, end - 1);
865 // RFC 5952 requires hex digits to be lower case.
866 return host.substring(start, end).toLowerCase();
790 } 867 }
791 868 if (!strictIPv6) {
792 if (scheme == null) return ""; 869 // TODO(lrn): skip if too short to be a valid IPv6 address?
793 bool allLowercase = true; 870 for (int i = start; i < end; i++) {
794 int length = scheme.length; 871 if (host.codeUnitAt(i) == _COLON) {
795 for (int i = 0; i < length; i++) { 872 parseIPv6Address(host, start, end);
796 int codeUnit = scheme.codeUnitAt(i); 873 return '[$host]';
797 if (i == 0 && !_isAlphabeticCharacter(codeUnit)) {
798 // First code unit must be an alphabetic character.
799 throw new ArgumentError('Illegal scheme: $scheme');
800 }
801 if (!isSchemeLowerCharacter(codeUnit)) {
802 if (_isSchemeCharacter(codeUnit)) {
803 allLowercase = false;
804 } else {
805 throw new ArgumentError('Illegal scheme: $scheme');
806 } 874 }
807 } 875 }
808 } 876 }
809 877 return _normalizeRegName(host, start, end);
810 return allLowercase ? scheme : scheme.toLowerCase();
811 } 878 }
812 879
813 String _makePath(String path, Iterable<String> pathSegments) { 880 static bool _isRegNameChar(int char) {
881 return char < 127 && (_regNameTable[char >> 4] & (1 << (char & 0xf))) != 0;
882 }
883
884 /**
885 * Validates and does case- and percent-encoding normalization.
886 *
887 * The [host] must be an RFC3986 "reg-name". It is converted
888 * to lower case, and percent escapes are converted to either
889 * lower case unreserved characters or upper case escapes.
890 */
891 static String _normalizeRegName(String host, int start, int end) {
892 StringBuffer buffer;
893 int sectionStart = start;
894 int index = start;
895 // Whether all characters between sectionStart and index are normalized,
896 bool isNormalized = true;
897
898 while (index < end) {
899 int char = host.codeUnitAt(index);
900 if (char == _PERCENT) {
901 // The _regNameTable contains "%", so we check that first.
902 String replacement = _normalizeEscape(host, index, true);
903 if (replacement == null && isNormalized) {
904 index += 3;
905 continue;
906 }
907 if (buffer == null) buffer = new StringBuffer();
908 String slice = host.substring(sectionStart, index);
909 if (!isNormalized) slice = slice.toLowerCase();
910 buffer.write(slice);
911 int sourceLength = 3;
912 if (replacement == null) {
913 replacement = host.substring(index, index + 3);
914 } else if (replacement == "%") {
915 replacement = "%25";
916 sourceLength = 1;
917 }
918 buffer.write(replacement);
919 index += sourceLength;
920 sectionStart = index;
921 isNormalized = true;
922 } else if (_isRegNameChar(char)) {
923 if (isNormalized && _UPPER_CASE_A <= char && _UPPER_CASE_Z >= char) {
924 // Put initial slice in buffer and continue in non-normalized mode
925 if (buffer == null) buffer = new StringBuffer();
926 if (sectionStart < index) {
927 buffer.write(host.substring(sectionStart, index));
928 sectionStart = index;
929 }
930 isNormalized = false;
931 }
932 index++;
933 } else if (_isGeneralDelimiter(char)) {
934 _fail(host, index, "Invalid character");
935 } else {
936 int sourceLength = 1;
937 if ((char & 0xFC00) == 0xD800 && (index + 1) < end) {
938 int tail = host.codeUnitAt(index + 1);
939 if ((tail & 0xFC00) == 0xDC00) {
940 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
941 sourceLength = 2;
942 }
943 }
944 if (buffer == null) buffer = new StringBuffer();
945 String slice = host.substring(sectionStart, index);
946 if (!isNormalized) slice = slice.toLowerCase();
947 buffer.write(slice);
948 buffer.write(_escapeChar(char));
949 index += sourceLength;
950 sectionStart = index;
951 }
952 }
953 if (buffer == null) return host.substring(start, end);
954 if (sectionStart < end) {
955 String slice = host.substring(sectionStart, end);
956 if (!isNormalized) slice = slice.toLowerCase();
957 buffer.write(slice);
958 }
959 return buffer.toString();
960 }
961
962 /**
963 * Validates scheme characters and does case-normalization.
964 *
965 * Schemes are converted to lower case. They cannot contain escapes.
966 */
967 static String _makeScheme(String scheme, int end) {
968 if (end == 0) return "";
969 int char = scheme.codeUnitAt(0);
970 if (!_isAlphabeticCharacter(char)) {
971 _fail(scheme, 0, "Scheme not starting with alphabetic character");
972 }
973 bool allLowercase = char >= _LOWER_CASE_A;
974 for (int i = 0; i < end; i++) {
975 int codeUnit = scheme.codeUnitAt(i);
976 if (!_isSchemeCharacter(codeUnit)) {
977 _fail(scheme, i, "Illegal scheme character");
978 }
979 if (_LOWER_CASE_A <= char && _LOWER_CASE_Z >= char) {
980 allLowercase = false;
981 }
982 }
983 scheme = scheme.substring(0, end);
984 if (!allLowercase) scheme = scheme.toLowerCase();
985 return scheme;
986 }
987
988 static String _makeUserInfo(String userInfo, int start, int end) {
989 if (userInfo == null) return "null";
990 return _normalize(userInfo, start, end, _userinfoTable);
991 }
992
993 static String _makePath(String path, int start, int end,
994 Iterable<String> pathSegments,
995 bool ensureLeadingSlash) {
814 if (path == null && pathSegments == null) return ""; 996 if (path == null && pathSegments == null) return "";
815 if (path != null && pathSegments != null) { 997 if (path != null && pathSegments != null) {
816 throw new ArgumentError('Both path and pathSegments specified'); 998 throw new ArgumentError('Both path and pathSegments specified');
817 } 999 }
818 var result; 1000 var result;
819 if (path != null) { 1001 if (path != null) {
820 result = _normalize(path); 1002 result = _normalize(path, start, end, _pathCharOrSlashTable);
821 } else { 1003 } else {
822 result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/"); 1004 result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/");
823 } 1005 }
824 if ((hasAuthority || (scheme == "file")) && 1006 if (ensureLeadingSlash && result.isNotEmpty && !result.startsWith("/")) {
825 result.isNotEmpty && !result.startsWith("/")) {
826 return "/$result"; 1007 return "/$result";
827 } 1008 }
828 return result; 1009 return result;
829 } 1010 }
830 1011
831 static String _makeQuery(String query, Map<String, String> queryParameters) { 1012 static String _makeQuery(String query, int start, int end,
1013 Map<String, String> queryParameters) {
832 if (query == null && queryParameters == null) return ""; 1014 if (query == null && queryParameters == null) return "";
833 if (query != null && queryParameters != null) { 1015 if (query != null && queryParameters != null) {
834 throw new ArgumentError('Both query and queryParameters specified'); 1016 throw new ArgumentError('Both query and queryParameters specified');
835 } 1017 }
836 if (query != null) return _normalize(query); 1018 if (query != null) return _normalize(query, start, end, _queryCharTable);
837 1019
838 var result = new StringBuffer(); 1020 var result = new StringBuffer();
839 var first = true; 1021 var first = true;
840 queryParameters.forEach((key, value) { 1022 queryParameters.forEach((key, value) {
841 if (!first) { 1023 if (!first) {
842 result.write("&"); 1024 result.write("&");
843 } 1025 }
844 first = false; 1026 first = false;
845 result.write(Uri.encodeQueryComponent(key)); 1027 result.write(Uri.encodeQueryComponent(key));
846 if (value != null && !value.isEmpty) { 1028 if (value != null && !value.isEmpty) {
847 result.write("="); 1029 result.write("=");
848 result.write(Uri.encodeQueryComponent(value)); 1030 result.write(Uri.encodeQueryComponent(value));
849 } 1031 }
850 }); 1032 });
851 return result.toString(); 1033 return result.toString();
852 } 1034 }
853 1035
854 static String _makeFragment(String fragment) { 1036 static String _makeFragment(String fragment, int start, int end) {
855 if (fragment == null) return ""; 1037 if (fragment == null) return "";
856 return _normalize(fragment); 1038 return _normalize(fragment, start, end, _queryCharTable);
857 } 1039 }
858 1040
859 static String _normalize(String component) { 1041 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
860 int index = component.indexOf('%');
861 if (index < 0) return component;
862 1042
863 bool isNormalizedHexDigit(int digit) { 1043 static bool _isHexDigit(int char) {
864 return (_ZERO <= digit && digit <= _NINE) || 1044 if (_NINE >= char) return _ZERO <= char;
865 (_UPPER_CASE_A <= digit && digit <= _UPPER_CASE_F); 1045 char |= 0x20;
1046 return _LOWER_CASE_A <= char && _LOWER_CASE_F >= char;
1047 }
1048
1049 static int _hexValue(int char) {
1050 assert(_isHexDigit(char));
1051 if (_NINE >= char) return char - _ZERO;
1052 char |= 0x20;
1053 return char - (_LOWER_CASE_A - 10);
1054 }
1055
1056 /**
1057 * Performs RFC 3986 Percent-Encoding Normalization.
1058 *
1059 * Returns a replacement string that should be replace the original escape.
1060 * Returns null if no replacement is necessary because the escape is
1061 * not for an unreserved character and is already non-lower-case.
1062 *
1063 * Returns "%" if the escape is invalid (not two valid hex digits following
1064 * the percent sign). The calling code should replace the percent
1065 * sign with "%25", but leave the following two characters unmodified.
1066 *
1067 * If [lowerCase] is true, a single character returned is always lower case,
1068 */
1069 static String _normalizeEscape(String source, int index, bool lowerCase) {
1070 assert(source.codeUnitAt(index) == _PERCENT);
1071 if (index + 2 >= source.length) {
1072 return "%"; // Marks the escape as invalid.
866 } 1073 }
1074 int firstDigit = source.codeUnitAt(index + 1);
1075 int secondDigit = source.codeUnitAt(index + 2);
1076 if (!_isHexDigit(firstDigit) || !_isHexDigit(secondDigit)) {
1077 return "%"; // Marks the escape as invalid.
1078 }
1079 int value = _hexValue(firstDigit) * 16 + _hexValue(secondDigit);
1080 if (_isUnreservedChar(value)) {
1081 if (lowerCase && _UPPER_CASE_A <= value && _UPPER_CASE_Z >= value) {
1082 value |= 0x20;
1083 }
1084 return new String.fromCharCode(value);
1085 }
1086 if (firstDigit >= _LOWER_CASE_A || secondDigit >= _LOWER_CASE_A) {
1087 // Either digit is lower case.
1088 return source.substring(index, index + 3).toUpperCase();
1089 }
1090 // Escape is retained, and is already non-lower case, so return null to
1091 // represent "no replacement necessary".
1092 return null;
1093 }
867 1094
868 bool isLowerCaseHexDigit(int digit) { 1095 static bool _isUnreservedChar(int ch) {
869 return _LOWER_CASE_A <= digit && digit <= _LOWER_CASE_F; 1096 return ch < 127 &&
870 } 1097 ((_unreservedTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
1098 }
871 1099
872 bool isUnreserved(int ch) { 1100 static String _escapeChar(char) {
873 return ch < 128 && 1101 assert(char <= 0x10ffff); // It's a valid unicode code point.
874 ((_unreservedTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 1102 const hexDigits = "0123456789ABCDEF";
875 } 1103 List codeUnits;
876 1104 if (char < 0x80) {
877 int normalizeHexDigit(int index) { 1105 // ASCII, a single percent encoded sequence.
878 var codeUnit = component.codeUnitAt(index); 1106 codeUnits = new List(3);
879 if (isLowerCaseHexDigit(codeUnit)) { 1107 codeUnits[0] = _PERCENT;
880 return codeUnit - 0x20; 1108 codeUnits[1] = hexDigits.codeUnitAt(char >> 4);
881 } else if (!isNormalizedHexDigit(codeUnit)) { 1109 codeUnits[2] = hexDigits.codeUnitAt(char & 0xf);
882 throw new ArgumentError("Invalid URI component: $component"); 1110 } else {
883 } else { 1111 // Do UTF-8 encoding of character, then percent encode bytes.
884 return codeUnit; 1112 int flag = 0xc0; // The high-bit markers on the first byte of UTF-8.
1113 int encodedBytes = 2;
1114 if (char > 0x7ff) {
1115 flag = 0xe0;
1116 encodedBytes = 3;
1117 if (char > 0xffff) {
1118 encodedBytes = 4;
1119 flag = 0xf0;
1120 }
1121 }
1122 codeUnits = new List(3 * encodedBytes);
1123 int index = 0;
1124 while (--encodedBytes >= 0) {
1125 int byte = ((char >> (6 * encodedBytes)) & 0x3f) | flag;
1126 codeUnits[index] = _PERCENT;
1127 codeUnits[index + 1] = hexDigits.codeUnitAt(byte >> 4);
1128 codeUnits[index + 2] = hexDigits.codeUnitAt(byte & 0xf);
1129 index += 3;
1130 flag = 0x80; // Following bytes have only high bit set.
885 } 1131 }
886 } 1132 }
1133 return new String.fromCharCodes(codeUnits);
1134 }
887 1135
888 int decodeHexDigitPair(int index) { 1136 /**
889 int byte = 0; 1137 * Runs through component checking that each character is valid and
890 for (int i = 0; i < 2; i++) { 1138 * normalize percent escapes.
891 var codeUnit = component.codeUnitAt(index + i); 1139 *
892 if (_ZERO <= codeUnit && codeUnit <= _NINE) { 1140 * Uses [charTable] to check if a non-`%` character is allowed.
893 byte = byte * 16 + codeUnit - _ZERO; 1141 * Each `%` character must be followed by two hex digits.
1142 * If the hex-digits are lower case letters, they are converted to
1143 * upper case.
1144 */
1145 static String _normalize(String component, int start, int end,
1146 List<int> charTable) {
1147 StringBuffer buffer;
1148 int sectionStart = start;
1149 int index = start;
1150 // Loop while characters are valid and escapes correct and upper-case.
1151 while (index < end) {
1152 int char = component.codeUnitAt(index);
1153 if (char < 127 && (charTable[char >> 4] & (1 << (char & 0x0f))) != 0) {
1154 index++;
1155 } else {
1156 String replacement;
1157 int sourceLength;
1158 if (char == _PERCENT) {
1159 replacement = _normalizeEscape(component, index, false);
1160 // Returns null if we should keep the existing escape.
1161 if (replacement == null) {
1162 index += 3;
1163 continue;
1164 }
1165 // Returns "%" if we should escape the existing percent.
1166 if ("%" == replacement) {
1167 replacement = "%25";
1168 sourceLength = 1;
1169 } else {
1170 sourceLength = 3;
1171 }
1172 } else if (_isGeneralDelimiter(char)) {
1173 _fail(component, index, "Invalid character");
894 } else { 1174 } else {
895 // Check ranges A-F (0x41-0x46) and a-f (0x61-0x66). 1175 sourceLength = 1;
896 codeUnit |= 0x20; 1176 if ((char & 0xFC00) == 0xD800) {
897 if (_LOWER_CASE_A <= codeUnit && 1177 // Possible lead surrogate.
898 codeUnit <= _LOWER_CASE_F) { 1178 if (index + 1 < end) {
899 byte = byte * 16 + codeUnit - _LOWER_CASE_A + 10; 1179 int tail = component.codeUnitAt(index + 1);
900 } else { 1180 if ((tail & 0xFC00) == 0xDC00) {
901 throw new ArgumentError( 1181 // Tail surrogat.
902 "Invalid percent-encoding in URI component: $component"); 1182 sourceLength = 2;
1183 char = 0x10000 | ((char & 0x3ff) << 10) | (tail & 0x3ff);
1184 }
1185 }
903 } 1186 }
1187 replacement = _escapeChar(char);
904 } 1188 }
905 } 1189 if (buffer == null) buffer = new StringBuffer();
906 return byte; 1190 buffer.write(component.substring(sectionStart, index));
907 } 1191 buffer.write(replacement);
908 1192 index += sourceLength;
909 // Start building the normalized component string. 1193 sectionStart = index;
910 StringBuffer result;
911 int length = component.length;
912 int prevIndex = 0;
913
914 // Copy a part of the component string to the result.
915 void fillResult() {
916 if (result == null) {
917 assert(prevIndex == 0);
918 result = new StringBuffer(component.substring(prevIndex, index));
919 } else {
920 result.write(component.substring(prevIndex, index));
921 } 1194 }
922 } 1195 }
923 1196 if (buffer == null) {
924 while (index < length) { 1197 // Makes no copy if start == 0 and end == component.length.
925 // Normalize percent-encoding to uppercase and don't encode 1198 return component.substring(start, end);
926 // unreserved characters.
927 assert(component.codeUnitAt(index) == _PERCENT);
928 if (length < index + 2) {
929 throw new ArgumentError(
930 "Invalid percent-encoding in URI component: $component");
931 }
932
933 var codeUnit1 = component.codeUnitAt(index + 1);
934 var codeUnit2 = component.codeUnitAt(index + 2);
935 var decodedCodeUnit = decodeHexDigitPair(index + 1);
936 if (isNormalizedHexDigit(codeUnit1) &&
937 isNormalizedHexDigit(codeUnit2) &&
938 !isUnreserved(decodedCodeUnit)) {
939 index += 3;
940 } else {
941 fillResult();
942 if (isUnreserved(decodedCodeUnit)) {
943 result.writeCharCode(decodedCodeUnit);
944 } else {
945 result.write("%");
946 result.writeCharCode(normalizeHexDigit(index + 1));
947 result.writeCharCode(normalizeHexDigit(index + 2));
948 }
949 index += 3;
950 prevIndex = index;
951 }
952 int next = component.indexOf('%', index);
953 if (next >= index) {
954 index = next;
955 } else {
956 index = length;
957 }
958 } 1199 }
959 if (result == null) return component; 1200 if (sectionStart < end) {
960 1201 buffer.write(component.substring(sectionStart, end));
961 if (result != null && prevIndex != index) fillResult(); 1202 }
962 assert(index == length); 1203 return buffer.toString();
963
964 return result.toString();
965 } 1204 }
966 1205
967 static bool _isSchemeCharacter(int ch) { 1206 static bool _isSchemeCharacter(int ch) {
968 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 1207 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
969 } 1208 }
970 1209
1210 static bool _isGeneralDelimiter(int ch) {
1211 return ch <= 64 &&
1212 ((_genDelimitersTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
1213 }
971 1214
972 /** 1215 /**
973 * Returns whether the URI is absolute. 1216 * Returns whether the URI is absolute.
974 */ 1217 */
975 bool get isAbsolute => scheme != "" && fragment == ""; 1218 bool get isAbsolute => scheme != "" && fragment == "";
976 1219
977 String _merge(String base, String reference) { 1220 String _merge(String base, String reference) {
978 if (base == "") return "/$reference"; 1221 if (base == "") return "/$reference";
979 return "${base.substring(0, base.lastIndexOf("/") + 1)}$reference"; 1222 return "${base.substring(0, base.lastIndexOf("/") + 1)}$reference";
980 } 1223 }
(...skipping 477 matching lines...) Expand 10 before | Expand all | Expand 10 after
1458 .toList(); 1701 .toList();
1459 } 1702 }
1460 1703
1461 /** 1704 /**
1462 * Parse the [host] as an IP version 6 (IPv6) address, returning the address 1705 * Parse the [host] as an IP version 6 (IPv6) address, returning the address
1463 * as a list of 16 bytes in network byte order (big endian). 1706 * as a list of 16 bytes in network byte order (big endian).
1464 * 1707 *
1465 * Throws a [FormatException] if [host] is not a valid IPv6 address 1708 * Throws a [FormatException] if [host] is not a valid IPv6 address
1466 * representation. 1709 * representation.
1467 * 1710 *
1711 * Acts on the substring from [start] to [end]. If [end] is omitted, it
1712 * defaults ot the end of the string.
1713 *
1468 * Some examples of IPv6 addresses: 1714 * Some examples of IPv6 addresses:
1469 * * ::1 1715 * * ::1
1470 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210 1716 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
1471 * * 3ffe:2a00:100:7031::1 1717 * * 3ffe:2a00:100:7031::1
1472 * * ::FFFF:129.144.52.38 1718 * * ::FFFF:129.144.52.38
1473 * * 2010:836B:4179::836B:4179 1719 * * 2010:836B:4179::836B:4179
1474 */ 1720 */
1475 static List<int> parseIPv6Address(String host) { 1721 static List<int> parseIPv6Address(String host, [int start = 0, int end]) {
1722 if (end == null) end = host.length;
1476 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated 1723 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated
1477 // by `:`'s, with the following exceptions: 1724 // by `:`'s, with the following exceptions:
1478 // 1725 //
1479 // - One (and only one) wildcard (`::`) may be present, representing a fill 1726 // - One (and only one) wildcard (`::`) may be present, representing a fill
1480 // of 0's. The IPv6 `::` is thus 16 bytes of `0`. 1727 // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
1481 // - The last two parts may be replaced by an IPv4 address. 1728 // - The last two parts may be replaced by an IPv4 address.
1482 void error(String msg) { 1729 void error(String msg) {
1483 throw new FormatException('Illegal IPv6 address, $msg'); 1730 throw new FormatException('Illegal IPv6 address, $msg');
1484 } 1731 }
1485 int parseHex(int start, int end) { 1732 int parseHex(int start, int end) {
1486 if (end - start > 4) { 1733 if (end - start > 4) {
1487 error('an IPv6 part can only contain a maximum of 4 hex digits'); 1734 error('an IPv6 part can only contain a maximum of 4 hex digits');
1488 } 1735 }
1489 int value = int.parse(host.substring(start, end), radix: 16); 1736 int value = int.parse(host.substring(start, end), radix: 16);
1490 if (value < 0 || value > (1 << 16) - 1) { 1737 if (value < 0 || value > (1 << 16) - 1) {
1491 error('each part must be in the range of `0x0..0xFFFF`'); 1738 error('each part must be in the range of `0x0..0xFFFF`');
1492 } 1739 }
1493 return value; 1740 return value;
1494 } 1741 }
1495 if (host.length < 2) error('address is too short'); 1742 if (host.length < 2) error('address is too short');
1496 List<int> parts = []; 1743 List<int> parts = [];
1497 bool wildcardSeen = false; 1744 bool wildcardSeen = false;
1498 int partStart = 0; 1745 int partStart = start;
1499 // Parse all parts, except a potential last one. 1746 // Parse all parts, except a potential last one.
1500 for (int i = 0; i < host.length; i++) { 1747 for (int i = start; i < end; i++) {
1501 if (host.codeUnitAt(i) == _COLON) { 1748 if (host.codeUnitAt(i) == _COLON) {
1502 if (i == 0) { 1749 if (i == start) {
1503 // If we see a `:` in the beginning, expect wildcard. 1750 // If we see a `:` in the beginning, expect wildcard.
1504 i++; 1751 i++;
1505 if (host.codeUnitAt(i) != _COLON) { 1752 if (host.codeUnitAt(i) != _COLON) {
1506 error('invalid start colon.'); 1753 error('invalid start colon.');
1507 } 1754 }
1508 partStart = i; 1755 partStart = i;
1509 } 1756 }
1510 if (i == partStart) { 1757 if (i == partStart) {
1511 // Wildcard. We only allow one. 1758 // Wildcard. We only allow one.
1512 if (wildcardSeen) { 1759 if (wildcardSeen) {
1513 error('only one wildcard `::` is allowed'); 1760 error('only one wildcard `::` is allowed');
1514 } 1761 }
1515 wildcardSeen = true; 1762 wildcardSeen = true;
1516 parts.add(-1); 1763 parts.add(-1);
1517 } else { 1764 } else {
1518 // Found a single colon. Parse [partStart..i] as a hex entry. 1765 // Found a single colon. Parse [partStart..i] as a hex entry.
1519 parts.add(parseHex(partStart, i)); 1766 parts.add(parseHex(partStart, i));
1520 } 1767 }
1521 partStart = i + 1; 1768 partStart = i + 1;
1522 } 1769 }
1523 } 1770 }
1524 if (parts.length == 0) error('too few parts'); 1771 if (parts.length == 0) error('too few parts');
1525 bool atEnd = partStart == host.length; 1772 bool atEnd = (partStart == end);
1526 bool isLastWildcard = parts.last == -1; 1773 bool isLastWildcard = (parts.last == -1);
1527 if (atEnd && !isLastWildcard) { 1774 if (atEnd && !isLastWildcard) {
1528 error('expected a part after last `:`'); 1775 error('expected a part after last `:`');
1529 } 1776 }
1530 if (!atEnd) { 1777 if (!atEnd) {
1531 try { 1778 try {
1532 parts.add(parseHex(partStart, host.length)); 1779 parts.add(parseHex(partStart, end));
1533 } catch (e) { 1780 } catch (e) {
1534 // Failed to parse the last chunk as hex. Try IPv4. 1781 // Failed to parse the last chunk as hex. Try IPv4.
1535 try { 1782 try {
1536 List<int> last = parseIPv4Address(host.substring(partStart)); 1783 List<int> last = parseIPv4Address(host.substring(partStart, end));
1537 parts.add(last[0] << 8 | last[1]); 1784 parts.add(last[0] << 8 | last[1]);
1538 parts.add(last[2] << 8 | last[3]); 1785 parts.add(last[2] << 8 | last[3]);
1539 } catch (e) { 1786 } catch (e) {
1540 error('invalid end of IPv6 address.'); 1787 error('invalid end of IPv6 address.');
1541 } 1788 }
1542 } 1789 }
1543 } 1790 }
1544 if (wildcardSeen) { 1791 if (wildcardSeen) {
1545 if (parts.length > 7) { 1792 if (parts.length > 7) {
1546 error('an address with a wildcard must have less than 7 parts'); 1793 error('an address with a wildcard must have less than 7 parts');
1547 } 1794 }
1548 } else if (parts.length != 8) { 1795 } else if (parts.length != 8) {
1549 error('an address without a wildcard must contain exactly 8 parts'); 1796 error('an address without a wildcard must contain exactly 8 parts');
1550 } 1797 }
1551 // TODO(ajohnsen): Consider using Uint8List. 1798 // TODO(ajohnsen): Consider using Uint8List.
1552 return parts 1799 List bytes = new List<int>(16);
1553 .expand((value) { 1800 for (int i = 0, index = 0; i < parts.length; i++) {
1554 if (value == -1) { 1801 int value = parts[i];
1555 return new List.filled((9 - parts.length) * 2, 0); 1802 if (value == -1) {
1556 } else { 1803 int wildCardLength = 9 - parts.length;
1557 return [(value >> 8) & 0xFF, value & 0xFF]; 1804 for (int j = 0; j < wildCardLength; j++) {
1558 } 1805 bytes[index] = 0;
1559 }) 1806 bytes[index + 1] = 0;
1560 .toList(); 1807 index += 2;
1808 }
1809 } else {
1810 bytes[index] = value >> 8;
1811 bytes[index + 1] = value & 0xff;
1812 index += 2;
1813 }
1814 }
1815 return bytes;
1561 } 1816 }
1562 1817
1563 // Frequently used character codes. 1818 // Frequently used character codes.
1564 static const int _SPACE = 0x20; 1819 static const int _SPACE = 0x20;
1565 static const int _DOUBLE_QUOTE = 0x22; 1820 static const int _DOUBLE_QUOTE = 0x22;
1566 static const int _NUMBER_SIGN = 0x23; 1821 static const int _NUMBER_SIGN = 0x23;
1567 static const int _PERCENT = 0x25; 1822 static const int _PERCENT = 0x25;
1568 static const int _ASTERISK = 0x2A; 1823 static const int _ASTERISK = 0x2A;
1569 static const int _PLUS = 0x2B; 1824 static const int _PLUS = 0x2B;
1570 static const int _SLASH = 0x2F; 1825 static const int _SLASH = 0x2F;
(...skipping 242 matching lines...) Expand 10 before | Expand all | Expand 10 after
1813 0x2bff, // 0x30 - 0x3f 1111111111010100 2068 0x2bff, // 0x30 - 0x3f 1111111111010100
1814 // ABCDEFGHIJKLMNO 2069 // ABCDEFGHIJKLMNO
1815 0xfffe, // 0x40 - 0x4f 0111111111111111 2070 0xfffe, // 0x40 - 0x4f 0111111111111111
1816 // PQRSTUVWXYZ _ 2071 // PQRSTUVWXYZ _
1817 0x87ff, // 0x50 - 0x5f 1111111111100001 2072 0x87ff, // 0x50 - 0x5f 1111111111100001
1818 // abcdefghijklmno 2073 // abcdefghijklmno
1819 0xfffe, // 0x60 - 0x6f 0111111111111111 2074 0xfffe, // 0x60 - 0x6f 0111111111111111
1820 // pqrstuvwxyz ~ 2075 // pqrstuvwxyz ~
1821 0x47ff]; // 0x70 - 0x7f 1111111111100010 2076 0x47ff]; // 0x70 - 0x7f 1111111111100010
1822 2077
2078 // General delimiter characters, RFC 3986 section 2.2.
2079 // gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
2080 //
2081 static const _genDelimitersTable = const [
2082 // LSB MSB
2083 // | |
2084 0x0000, // 0x00 - 0x0f 0000000000000000
2085 0x0000, // 0x10 - 0x1f 0000000000000000
2086 // # /
2087 0x8008, // 0x20 - 0x2f 0001000000000001
2088 // : [ ]?
2089 0xe400, // 0x30 - 0x3f 0000000000101011
2090 // @
2091 0x0001, // 0x40 - 0x4f 1000000000000000
2092 //
2093 0x0000, // 0x50 - 0x5f 0000000000000000
2094 //
2095 0x0000, // 0x60 - 0x6f 0000000000000000
2096 //
2097 0x0000]; // 0x70 - 0x7f 0000000000000000
2098
2099 // Characters allowed in the userinfo as of RFC 3986.
2100 // RFC 3986 Apendix A
2101 // userinfo = *( unreserved / pct-encoded / sub-delims / ':')
2102 static const _userinfoTable = const [
2103 // LSB MSB
2104 // | |
2105 0x0000, // 0x00 - 0x0f 0000000000000000
2106 0x0000, // 0x10 - 0x1f 0000000000000000
2107 // ! $ &'()*+,-.
2108 0x7fd2, // 0x20 - 0x2f 0100101111111110
2109 // 0123456789:; =
2110 0x2fff, // 0x30 - 0x3f 1111111111110100
2111 // ABCDEFGHIJKLMNO
2112 0xfffe, // 0x40 - 0x4f 0111111111111111
2113 // PQRSTUVWXYZ _
2114 0x87ff, // 0x50 - 0x5f 1111111111100001
2115 // abcdefghijklmno
2116 0xfffe, // 0x60 - 0x6f 0111111111111111
2117 // pqrstuvwxyz ~
2118 0x47ff]; // 0x70 - 0x7f 1111111111100010
2119
1823 // Characters allowed in the reg-name as of RFC 3986. 2120 // Characters allowed in the reg-name as of RFC 3986.
1824 // RFC 3986 Apendix A 2121 // RFC 3986 Apendix A
1825 // reg-name = *( unreserved / pct-encoded / sub-delims ) 2122 // reg-name = *( unreserved / pct-encoded / sub-delims )
1826 static const _regNameTable = const [ 2123 static const _regNameTable = const [
1827 // LSB MSB 2124 // LSB MSB
1828 // | | 2125 // | |
1829 0x0000, // 0x00 - 0x0f 0000000000000000 2126 0x0000, // 0x00 - 0x0f 0000000000000000
1830 0x0000, // 0x10 - 0x1f 0000000000000000 2127 0x0000, // 0x10 - 0x1f 0000000000000000
1831 // ! $%&'()*+,-. 2128 // ! $%&'()*+,-.
1832 0x7ff2, // 0x20 - 0x2f 0100111111111110 2129 0x7ff2, // 0x20 - 0x2f 0100111111111110
(...skipping 22 matching lines...) Expand all
1855 0x2fff, // 0x30 - 0x3f 1111111111110100 2152 0x2fff, // 0x30 - 0x3f 1111111111110100
1856 // @ABCDEFGHIJKLMNO 2153 // @ABCDEFGHIJKLMNO
1857 0xffff, // 0x40 - 0x4f 1111111111111111 2154 0xffff, // 0x40 - 0x4f 1111111111111111
1858 // PQRSTUVWXYZ _ 2155 // PQRSTUVWXYZ _
1859 0x87ff, // 0x50 - 0x5f 1111111111100001 2156 0x87ff, // 0x50 - 0x5f 1111111111100001
1860 // abcdefghijklmno 2157 // abcdefghijklmno
1861 0xfffe, // 0x60 - 0x6f 0111111111111111 2158 0xfffe, // 0x60 - 0x6f 0111111111111111
1862 // pqrstuvwxyz ~ 2159 // pqrstuvwxyz ~
1863 0x47ff]; // 0x70 - 0x7f 1111111111100010 2160 0x47ff]; // 0x70 - 0x7f 1111111111100010
1864 2161
2162 // Characters allowed in the path as of RFC 3986.
2163 // RFC 3986 section 3.3 *and* slash.
2164 static const _pathCharOrSlashTable = const [
2165 // LSB MSB
2166 // | |
2167 0x0000, // 0x00 - 0x0f 0000000000000000
2168 0x0000, // 0x10 - 0x1f 0000000000000000
2169 // ! $ &'()*+,-./
2170 0xffd2, // 0x20 - 0x2f 0100101111111111
2171 // 0123456789:; =
2172 0x2fff, // 0x30 - 0x3f 1111111111110100
2173 // @ABCDEFGHIJKLMNO
2174 0xffff, // 0x40 - 0x4f 1111111111111111
2175
2176 // PQRSTUVWXYZ _
2177 0x87ff, // 0x50 - 0x5f 1111111111100001
2178 // abcdefghijklmno
2179 0xfffe, // 0x60 - 0x6f 0111111111111111
2180 // pqrstuvwxyz ~
2181 0x47ff]; // 0x70 - 0x7f 1111111111100010
2182
1865 // Characters allowed in the query as of RFC 3986. 2183 // Characters allowed in the query as of RFC 3986.
1866 // RFC 3986 section 3.4. 2184 // RFC 3986 section 3.4.
1867 // query = *( pchar / "/" / "?" ) 2185 // query = *( pchar / "/" / "?" )
1868 static const _queryCharTable = const [ 2186 static const _queryCharTable = const [
1869 // LSB MSB 2187 // LSB MSB
1870 // | | 2188 // | |
1871 0x0000, // 0x00 - 0x0f 0000000000000000 2189 0x0000, // 0x00 - 0x0f 0000000000000000
1872 0x0000, // 0x10 - 0x1f 0000000000000000 2190 0x0000, // 0x10 - 0x1f 0000000000000000
1873 // ! $ &'()*+,-./ 2191 // ! $ &'()*+,-./
1874 0xffd2, // 0x20 - 0x2f 0100101111111111 2192 0xffd2, // 0x20 - 0x2f 0100101111111111
1875 // 0123456789:; = ? 2193 // 0123456789:; = ?
1876 0xafff, // 0x30 - 0x3f 1111111111110101 2194 0xafff, // 0x30 - 0x3f 1111111111110101
1877 // @ABCDEFGHIJKLMNO 2195 // @ABCDEFGHIJKLMNO
1878 0xffff, // 0x40 - 0x4f 1111111111111111 2196 0xffff, // 0x40 - 0x4f 1111111111111111
1879 // PQRSTUVWXYZ _ 2197 // PQRSTUVWXYZ _
1880 0x87ff, // 0x50 - 0x5f 1111111111100001 2198 0x87ff, // 0x50 - 0x5f 1111111111100001
1881 // abcdefghijklmno 2199 // abcdefghijklmno
1882 0xfffe, // 0x60 - 0x6f 0111111111111111 2200 0xfffe, // 0x60 - 0x6f 0111111111111111
1883 // pqrstuvwxyz ~ 2201 // pqrstuvwxyz ~
1884 0x47ff]; // 0x70 - 0x7f 1111111111100010 2202 0x47ff]; // 0x70 - 0x7f 1111111111100010
1885 } 2203 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698