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

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

Issue 352093003: Try to retain original structure of URI. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fix _userInfo ending up null in some cases. Created 6 years, 5 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
« no previous file with comments | « no previous file | tests/co19/co19-co19.status » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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:**
11 * 11 *
12 * * [URIs][uris] in the [library tour][libtour] 12 * * [URIs][uris] in the [library tour][libtour]
13 * * [RFC-3986](http://tools.ietf.org/html/rfc3986) 13 * * [RFC-3986](http://tools.ietf.org/html/rfc3986)
14 * 14 *
15 * [uris]: http://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html#c h03-uri 15 * [uris]: http://www.dartlang.org/docs/dart-up-and-running/contents/ch03.html#c h03-uri
16 * [libtour]: http://www.dartlang.org/docs/dart-up-and-running/contents/ch03.htm l 16 * [libtour]: http://www.dartlang.org/docs/dart-up-and-running/contents/ch03.htm l
17 */ 17 */
18 class Uri { 18 class Uri {
19 // The host name of the URI.
20 // Set to `null` if there is no authority in a URI.
19 final String _host; 21 final String _host;
22 // The port. Set to null if there is no port. Normalized to null if
23 // the port is the default port for the scheme.
24 // Set to the value of the default port if an empty port was supplied.
20 int _port; 25 int _port;
26 // The path. Always non-null.
21 String _path; 27 String _path;
22 28
23 /** 29 /**
24 * Returns the scheme component. 30 * Returns the scheme component.
25 * 31 *
26 * Returns the empty string if there is no scheme component. 32 * Returns the empty string if there is no scheme component.
27 */ 33 */
34 // We represent the missing scheme as an empty string.
35 // A valid scheme cannot be empty.
28 final String scheme; 36 final String scheme;
29 37
30 /** 38 /**
31 * Returns the authority component. 39 * Returns the authority component.
32 * 40 *
33 * The authority is formatted from the [userInfo], [host] and [port] 41 * The authority is formatted from the [userInfo], [host] and [port]
34 * parts. 42 * parts.
35 * 43 *
36 * Returns the empty string if there is no authority component. 44 * Returns the empty string if there is no authority component.
37 */ 45 */
38 String get authority { 46 String get authority {
39 if (!hasAuthority) return ""; 47 if (!hasAuthority) return "";
40 var sb = new StringBuffer(); 48 var sb = new StringBuffer();
41 _writeAuthority(sb); 49 _writeAuthority(sb);
42 return sb.toString(); 50 return sb.toString();
43 } 51 }
44 52
45 /** 53 /**
54 * The user-info part of the authority.
55 *
56 * Does not distinguish between an empty user-info and an absent one.
57 * The value is always non-null.
58 */
59 final String _userInfo;
60
61 /**
46 * Returns the user info part of the authority component. 62 * Returns the user info part of the authority component.
47 * 63 *
48 * Returns the empty string if there is no user info in the 64 * Returns the empty string if there is no user info in the
49 * authority component. 65 * authority component.
50 */ 66 */
51 final String userInfo; 67 String get userInfo => _userInfo;
52 68
53 /** 69 /**
54 * Returns the host part of the authority component. 70 * Returns the host part of the authority component.
55 * 71 *
56 * Returns the empty string if there is no authority component and 72 * Returns the empty string if there is no authority component and
57 * hence no host. 73 * hence no host.
58 * 74 *
59 * If the host is an IP version 6 address, the surrounding `[` and `]` is 75 * If the host is an IP version 6 address, the surrounding `[` and `]` is
60 * removed. 76 * removed.
61 */ 77 */
62 String get host { 78 String get host {
63 if (_host != null && _host.startsWith('[')) { 79 if (_host == null) return "";
80 if (_host.startsWith('[')) {
64 return _host.substring(1, _host.length - 1); 81 return _host.substring(1, _host.length - 1);
65 } 82 }
66 return _host; 83 return _host;
67 } 84 }
68 85
69 /** 86 /**
70 * Returns the port part of the authority component. 87 * Returns the port part of the authority component.
71 * 88 *
72 * Returns 0 if there is no port in the authority component. 89 * Returns the defualt port if there is no port number in the authority
90 * component. That's 80 for http, 443 for https, and 0 for everything else.
73 */ 91 */
74 int get port { 92 int get port {
75 if (_port == 0) { 93 if (_port == null) return _defaultPort(scheme);
76 if (scheme == "http") return 80;
77 if (scheme == "https") return 443;
78 }
79 return _port; 94 return _port;
80 } 95 }
81 96
97 // The default port for the scheme of this Uri..
98 static int _defaultPort(String scheme) {
99 if (scheme == "http") return 80;
100 if (scheme == "https") return 443;
101 return 0;
102 }
103
82 /** 104 /**
83 * Returns the path component. 105 * Returns the path component.
84 * 106 *
85 * The returned path is encoded. To get direct access to the decoded 107 * The returned path is encoded. To get direct access to the decoded
86 * path use [pathSegments]. 108 * path use [pathSegments].
87 * 109 *
88 * Returns the empty string if there is no path component. 110 * Returns the empty string if there is no path component.
89 */ 111 */
90 String get path => _path; 112 String get path => _path;
91 113
114 // The query content, or null if there is no query.
115 final String _query;
116
92 /** 117 /**
93 * Returns the query component. The returned query is encoded. To get 118 * Returns the query component. The returned query is encoded. To get
94 * direct access to the decoded query use [queryParameters]. 119 * direct access to the decoded query use [queryParameters].
95 * 120 *
96 * Returns the empty string if there is no query component. 121 * Returns the empty string if there is no query component.
97 */ 122 */
98 final String query; 123 String get query => (_query == null) ? "" : _query;
124
125 // The fragment content, or null if there is no fragment.
126 final String _fragment;
99 127
100 /** 128 /**
101 * Returns the fragment identifier component. 129 * Returns the fragment identifier component.
102 * 130 *
103 * Returns the empty string if there is no fragment identifier 131 * Returns the empty string if there is no fragment identifier
104 * component. 132 * component.
105 */ 133 */
106 final String fragment; 134 String get fragment => (_fragment == null) ? "" : _fragment;
107 135
108 /** 136 /**
109 * Cache the computed return value of [pathSegements]. 137 * Cache the computed return value of [pathSegements].
110 */ 138 */
111 List<String> _pathSegments; 139 List<String> _pathSegments;
112 140
113 /** 141 /**
114 * Cache the computed return value of [queryParameters]. 142 * Cache the computed return value of [queryParameters].
115 */ 143 */
116 Map<String, String> _queryParameters; 144 Map<String, String> _queryParameters;
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after
175 // 203 //
176 // query = *( pchar / "/" / "?" ) 204 // query = *( pchar / "/" / "?" )
177 // 205 //
178 // fragment = *( pchar / "/" / "?" ) 206 // fragment = *( pchar / "/" / "?" )
179 bool isRegName(int ch) { 207 bool isRegName(int ch) {
180 return ch < 128 && ((_regNameTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 208 return ch < 128 && ((_regNameTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
181 } 209 }
182 const int EOI = -1; 210 const int EOI = -1;
183 211
184 String scheme = ""; 212 String scheme = "";
185 String path;
186 String userinfo = ""; 213 String userinfo = "";
187 String host = ""; 214 String host = null;
188 int port = 0; 215 int port = null;
189 String query = ""; 216 String path = null;
190 String fragment = ""; 217 String query = null;
218 String fragment = null;
191 219
192 int index = 0; 220 int index = 0;
193 int pathStart = 0; 221 int pathStart = 0;
194 // End of input-marker. 222 // End of input-marker.
195 int char = EOI; 223 int char = EOI;
196 224
197 void parseAuth() { 225 void parseAuth() {
198 if (index == uri.length) { 226 if (index == uri.length) {
199 char = EOI; 227 char = EOI;
200 return; 228 return;
(...skipping 26 matching lines...) Expand all
227 index++; 255 index++;
228 char = EOI; 256 char = EOI;
229 } 257 }
230 int hostStart = authStart; 258 int hostStart = authStart;
231 int hostEnd = index; 259 int hostEnd = index;
232 if (lastAt >= 0) { 260 if (lastAt >= 0) {
233 userinfo = _makeUserInfo(uri, authStart, lastAt); 261 userinfo = _makeUserInfo(uri, authStart, lastAt);
234 hostStart = lastAt + 1; 262 hostStart = lastAt + 1;
235 } 263 }
236 if (lastColon >= 0) { 264 if (lastColon >= 0) {
237 int portNumber = 0; 265 int portNumber;
238 for (int i = lastColon + 1; i < index; i++) { 266 if (lastColon + 1 < index) {
239 int digit = uri.codeUnitAt(i); 267 portNumber = 0;
240 if (_ZERO > digit || _NINE < digit) { 268 for (int i = lastColon + 1; i < index; i++) {
241 _fail(uri, i, "Invalid port number"); 269 int digit = uri.codeUnitAt(i);
270 if (_ZERO > digit || _NINE < digit) {
271 _fail(uri, i, "Invalid port number");
272 }
273 portNumber = portNumber * 10 + (digit - _ZERO);
242 } 274 }
243 portNumber = portNumber * 10 + (digit - _ZERO);
244 } 275 }
245 port = _makePort(portNumber, scheme); 276 port = _makePort(portNumber, scheme);
246 hostEnd = lastColon; 277 hostEnd = lastColon;
247 } 278 }
248 host = _makeHost(uri, hostStart, hostEnd, true); 279 host = _makeHost(uri, hostStart, hostEnd, true);
249 if (index < uri.length) { 280 if (index < uri.length) {
250 char = uri.codeUnitAt(index); 281 char = uri.codeUnitAt(index);
251 } 282 }
252 } 283 }
253 284
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
333 char = uri.codeUnitAt(index); 364 char = uri.codeUnitAt(index);
334 if (char == _QUESTION || char == _NUMBER_SIGN) { 365 if (char == _QUESTION || char == _NUMBER_SIGN) {
335 break; 366 break;
336 } 367 }
337 char = EOI; 368 char = EOI;
338 } 369 }
339 state = NOT_IN_PATH; 370 state = NOT_IN_PATH;
340 } 371 }
341 372
342 assert(state == NOT_IN_PATH); 373 assert(state == NOT_IN_PATH);
343 bool ensureLeadingSlash = (host != "" || scheme == "file"); 374 bool ensureLeadingSlash = (host != null || scheme == "file");
344 path = _makePath(uri, pathStart, index, null, ensureLeadingSlash); 375 path = _makePath(uri, pathStart, index, null, ensureLeadingSlash);
345 376
346 if (char == _QUESTION) { 377 if (char == _QUESTION) {
347 int numberSignIndex = uri.indexOf('#', index + 1); 378 int numberSignIndex = uri.indexOf('#', index + 1);
348 if (numberSignIndex < 0) { 379 if (numberSignIndex < 0) {
349 query = _makeQuery(uri, index + 1, uri.length, null); 380 query = _makeQuery(uri, index + 1, uri.length, null);
350 } else { 381 } else {
351 query = _makeQuery(uri, index + 1, numberSignIndex, null); 382 query = _makeQuery(uri, index + 1, numberSignIndex, null);
352 fragment = _makeFragment(uri, numberSignIndex + 1, uri.length); 383 fragment = _makeFragment(uri, numberSignIndex + 1, uri.length);
353 } 384 }
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
390 } 421 }
391 // Combine message, slice and a caret pointing to the error index. 422 // Combine message, slice and a caret pointing to the error index.
392 message = "$message$pre${uri.substring(min, max)}$post\n" 423 message = "$message$pre${uri.substring(min, max)}$post\n"
393 "${' ' * (pre.length + index - min)}^"; 424 "${' ' * (pre.length + index - min)}^";
394 } 425 }
395 throw new FormatException(message); 426 throw new FormatException(message);
396 } 427 }
397 428
398 /// Internal non-verifying constructor. Only call with validated arguments. 429 /// Internal non-verifying constructor. Only call with validated arguments.
399 Uri._internal(this.scheme, 430 Uri._internal(this.scheme,
400 this.userInfo, 431 this._userInfo,
401 this._host, 432 this._host,
402 this._port, 433 this._port,
403 this._path, 434 this._path,
404 this.query, 435 this._query,
405 this.fragment); 436 this._fragment);
406 437
407 /** 438 /**
408 * Creates a new URI from its components. 439 * Creates a new URI from its components.
409 * 440 *
410 * Each component is set through a named argument. Any number of 441 * Each component is set through a named argument. Any number of
411 * components can be provided. The default value for the components 442 * components can be provided. The [path] and [query] components can be set
412 * not provided is the empry string, except for [port] which has a 443 * using either of two different named arguments.
413 * default value of 0. The [path] and [query] components can be set
414 * using two different named arguments.
415 * 444 *
416 * The scheme component is set through [scheme]. The scheme is 445 * The scheme component is set through [scheme]. The scheme is
417 * normalized to all lowercase letters. 446 * normalized to all lowercase letters. If the scheme is omitted or empty,
447 * the URI will not have a scheme part.
418 * 448 *
419 * The user info part of the authority component is set through 449 * The user info part of the authority component is set through
420 * [userInfo]. 450 * [userInfo]. It defaults to the empty string, which will be omitted
451 * from the string representation of the URI.
421 * 452 *
422 * The host part of the authority component is set through 453 * The host part of the authority component is set through
423 * [host]. The host can either be a hostname, an IPv4 address or an 454 * [host]. The host can either be a hostname, an IPv4 address or an
424 * IPv6 address, contained in '[' and ']'. If the host contains a 455 * IPv6 address, contained in '[' and ']'. If the host contains a
425 * ':' character, the '[' and ']' are added if not already provided. 456 * ':' character, the '[' and ']' are added if not already provided.
426 * The host is normalized to all lowercase letters. 457 * The host is normalized to all lowercase letters.
427 * 458 *
428 * The port part of the authority component is set through 459 * The port part of the authority component is set through
429 * [port]. The port is normalized for scheme http and https where 460 * [port].
430 * port 80 and port 443 respectively is set. 461 * If [port] is omitted or `null`, it implies the default port for
462 * the URI's scheme, and is equivalent to passing that port explicitly.
463 * The recognized schemes, and their default ports, are "http" (80) and
464 * "https" (443). All other schemes are considered as having zero as the
465 * default port.
466 *
467 * If any of `userInfo`, `host` or `port` are provided,
468 * the URI will have an autority according to [hasAuthority].
431 * 469 *
432 * The path component is set through either [path] or 470 * The path component is set through either [path] or
433 * [pathSegments]. When [path] is used, the provided string is 471 * [pathSegments]. When [path] is used, it should be a valid URI path,
434 * expected to be fully percent-encoded, and is used in its literal 472 * but invalid characters, except the general delimiters ':/@[]?#',
435 * form. When [pathSegments] is used, each of the provided segments 473 * will be escaped if necessary.
436 * is percent-encoded and joined using the forward slash 474 * When [pathSegments] is used, each of the provided segments
475 * is first percent-encoded and then joined using the forward slash
437 * separator. The percent-encoding of the path segments encodes all 476 * separator. The percent-encoding of the path segments encodes all
438 * characters except for the unreserved characters and the following 477 * characters except for the unreserved characters and the following
439 * list of characters: `!$&'()*+,;=:@`. If the other components 478 * list of characters: `!$&'()*+,;=:@`. If the other components
440 * calls for an absolute path a leading slash `/` is prepended if 479 * calls for an absolute path a leading slash `/` is prepended if
441 * not already there. 480 * not already there.
442 * 481 *
443 * The query component is set through either [query] or 482 * The query component is set through either [query] or
444 * [queryParameters]. When [query] is used the provided string is 483 * [queryParameters]. When [query] is used the provided string should
445 * expected to be fully percent-encoded and is used in its literal 484 * be a valid URI query, but invalid characters other than general delimiters,
446 * form. When [queryParameters] is used the query is built from the 485 * will be escaped if necessary.
486 * When [queryParameters] is used the query is built from the
447 * provided map. Each key and value in the map is percent-encoded 487 * provided map. Each key and value in the map is percent-encoded
448 * and joined using equal and ampersand characters. The 488 * and joined using equal and ampersand characters. The
449 * percent-encoding of the keys and values encodes all characters 489 * percent-encoding of the keys and values encodes all characters
450 * except for the unreserved characters. 490 * except for the unreserved characters.
491 * If both `query` and `queryParameters` are omitted or `null`, the
492 * URI will have no query part.
451 * 493 *
452 * The fragment component is set through [fragment]. 494 * The fragment component is set through [fragment].
495 * It should be a valid URI fragment, but invalid characters other than
496 * general delimiters, will be escaped if necessary.
497 * If `fragment` is omitted or `null`, the URI will have no fragment part.
453 */ 498 */
454 factory Uri({String scheme, 499 factory Uri({String scheme : "",
455 String userInfo: "", 500 String userInfo : "",
456 String host: "", 501 String host,
457 port: 0, 502 int port,
458 String path, 503 String path,
459 Iterable<String> pathSegments, 504 Iterable<String> pathSegments,
460 String query, 505 String query,
461 Map<String, String> queryParameters, 506 Map<String, String> queryParameters,
462 fragment: ""}) { 507 fragment}) {
463 scheme = _makeScheme(scheme, _stringOrNullLength(scheme)); 508 scheme = _makeScheme(scheme, _stringOrNullLength(scheme));
464 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo)); 509 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
465 host = _makeHost(host, 0, _stringOrNullLength(host), false); 510 host = _makeHost(host, 0, _stringOrNullLength(host), false);
466 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters); 511 query = _makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
467 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment)); 512 fragment = _makeFragment(fragment, 0, _stringOrNullLength(fragment));
468 port = _makePort(port, scheme); 513 port = _makePort(port, scheme);
469 bool ensureLeadingSlash = (host != "" || scheme == "file"); 514 bool isFile = (scheme == "file");
515 if (host == null &&
516 (userInfo.isNotEmpty || port != null || isFile)) {
517 host = "";
518 }
519 bool ensureLeadingSlash = (host != null || isFile);
470 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments, 520 path = _makePath(path, 0, _stringOrNullLength(path), pathSegments,
471 ensureLeadingSlash); 521 ensureLeadingSlash);
472 522
473 return new Uri._internal(scheme, userInfo, host, port, 523 return new Uri._internal(scheme, userInfo, host, port,
474 path, query, fragment); 524 path, query, fragment);
475 } 525 }
476 526
477 /** 527 /**
478 * Creates a new `http` URI from authority, path and query. 528 * Creates a new `http` URI from authority, path and query.
479 * 529 *
480 * Examples: 530 * Examples:
481 * 531 *
482 * ``` 532 * ```
483 * // http://example.org/path?q=dart. 533 * // http://example.org/path?q=dart.
484 * new Uri.http("google.com", "/search", { "q" : "dart" }); 534 * new Uri.http("google.com", "/search", { "q" : "dart" });
485 * 535 *
486 * // http://user:pass@localhost:8080 536 * // http://user:pass@localhost:8080
487 * new Uri.http("user:pass@localhost:8080", ""); 537 * new Uri.http("user:pass@localhost:8080", "");
488 * 538 *
489 * // http://example.org/a%20b 539 * // http://example.org/a%20b
490 * new Uri.http("example.org", "a b"); 540 * new Uri.http("example.org", "a b");
491 * 541 *
492 * // http://example.org/a%252F 542 * // http://example.org/a%252F
493 * new Uri.http("example.org", "/a%2F"); 543 * new Uri.http("example.org", "/a%2F");
494 * ``` 544 * ```
495 * 545 *
496 * The `scheme` is always set to `http`. 546 * The `scheme` is always set to `http`.
497 * 547 *
498 * The `userInfo`, `host` and `port` components are set from the 548 * The `userInfo`, `host` and `port` components are set from the
499 * [authority] argument. 549 * [authority] argument. If `authority` is `null` or empty,
550 * the created `Uri` will have no authority, and will not be directly usable
551 * as an HTTP URL, which must have a non-empty host.
500 * 552 *
501 * The `path` component is set from the [unencodedPath] 553 * The `path` component is set from the [unencodedPath]
502 * argument. The path passed must not be encoded as this constructor 554 * argument. The path passed must not be encoded as this constructor
503 * encodes the path. 555 * encodes the path.
504 * 556 *
505 * The `query` component is set from the optional [queryParameters] 557 * The `query` component is set from the optional [queryParameters]
506 * argument. 558 * argument.
507 */ 559 */
508 factory Uri.http(String authority, 560 factory Uri.http(String authority,
509 String unencodedPath, 561 String unencodedPath,
(...skipping 11 matching lines...) Expand all
521 String unencodedPath, 573 String unencodedPath,
522 [Map<String, String> queryParameters]) { 574 [Map<String, String> queryParameters]) {
523 return _makeHttpUri("https", authority, unencodedPath, queryParameters); 575 return _makeHttpUri("https", authority, unencodedPath, queryParameters);
524 } 576 }
525 577
526 static Uri _makeHttpUri(String scheme, 578 static Uri _makeHttpUri(String scheme,
527 String authority, 579 String authority,
528 String unencodedPath, 580 String unencodedPath,
529 Map<String, String> queryParameters) { 581 Map<String, String> queryParameters) {
530 var userInfo = ""; 582 var userInfo = "";
531 var host = ""; 583 var host = null;
532 var port = 0; 584 var port = null;
533 585
534 var hostStart = 0; 586 if (authority != null && authority.isNotEmpty) {
535 // Split off the user info. 587 var hostStart = 0;
536 bool hasUserInfo = false; 588 // Split off the user info.
537 for (int i = 0; i < authority.length; i++) { 589 bool hasUserInfo = false;
538 if (authority.codeUnitAt(i) == _AT_SIGN) { 590 for (int i = 0; i < authority.length; i++) {
539 hasUserInfo = true; 591 if (authority.codeUnitAt(i) == _AT_SIGN) {
540 userInfo = authority.substring(0, i); 592 hasUserInfo = true;
541 hostStart = i + 1; 593 userInfo = authority.substring(0, i);
542 break; 594 hostStart = i + 1;
595 break;
596 }
543 } 597 }
598 var hostEnd = hostStart;
599 if (hostStart < authority.length &&
600 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
601 // IPv6 host.
602 for (; hostEnd < authority.length; hostEnd++) {
603 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
604 }
605 if (hostEnd == authority.length) {
606 throw new FormatException("Invalid IPv6 host entry.");
607 }
608 parseIPv6Address(authority, hostStart + 1, hostEnd);
609 hostEnd++; // Skip the closing bracket.
610 if (hostEnd != authority.length &&
611 authority.codeUnitAt(hostEnd) != _COLON) {
612 throw new FormatException("Invalid end of authority");
613 }
614 }
615 // Split host and port.
616 bool hasPort = false;
617 for (; hostEnd < authority.length; hostEnd++) {
618 if (authority.codeUnitAt(hostEnd) == _COLON) {
619 var portString = authority.substring(hostEnd + 1);
620 // We allow the empty port - falling back to initial value.
621 if (portString.isNotEmpty) port = int.parse(portString);
622 break;
623 }
624 }
625 host = authority.substring(hostStart, hostEnd);
544 } 626 }
545 var hostEnd = hostStart;
546 if (hostStart < authority.length &&
547 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
548 // IPv6 host.
549 for (; hostEnd < authority.length; hostEnd++) {
550 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
551 }
552 if (hostEnd == authority.length) {
553 throw new FormatException("Invalid IPv6 host entry.");
554 }
555 parseIPv6Address(authority, hostStart + 1, hostEnd);
556 hostEnd++; // Skip the closing bracket.
557 if (hostEnd != authority.length &&
558 authority.codeUnitAt(hostEnd) != _COLON) {
559 throw new FormatException("Invalid end of authority");
560 }
561 }
562 // Split host and port.
563 bool hasPort = false;
564 for (; hostEnd < authority.length; hostEnd++) {
565 if (authority.codeUnitAt(hostEnd) == _COLON) {
566 var portString = authority.substring(hostEnd + 1);
567 // We allow the empty port - falling back to initial value.
568 if (portString.isNotEmpty) port = int.parse(portString);
569 break;
570 }
571 }
572 host = authority.substring(hostStart, hostEnd);
573
574 return new Uri(scheme: scheme, 627 return new Uri(scheme: scheme,
575 userInfo: userInfo, 628 userInfo: userInfo,
576 host: host, 629 host: host,
577 port: port, 630 port: port,
578 pathSegments: unencodedPath.split("/"), 631 pathSegments: unencodedPath.split("/"),
579 queryParameters: queryParameters); 632 queryParameters: queryParameters);
580 } 633 }
581 634
582 /** 635 /**
583 * Creates a new file URI from an absolute or relative file path. 636 * Creates a new file URI from an absolute or relative file path.
(...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after
714 throw new ArgumentError("Illegal drive letter " + 767 throw new ArgumentError("Illegal drive letter " +
715 new String.fromCharCode(charCode)); 768 new String.fromCharCode(charCode));
716 } else { 769 } else {
717 throw new UnsupportedError("Illegal drive letter " + 770 throw new UnsupportedError("Illegal drive letter " +
718 new String.fromCharCode(charCode)); 771 new String.fromCharCode(charCode));
719 } 772 }
720 } 773 }
721 774
722 static _makeFileUri(String path) { 775 static _makeFileUri(String path) {
723 String sep = "/"; 776 String sep = "/";
724 if (path.length > 0 && path[0] == sep) { 777 if (path.startsWith(sep)) {
725 // Absolute file:// URI. 778 // Absolute file:// URI.
726 return new Uri(scheme: "file", pathSegments: path.split(sep)); 779 return new Uri(scheme: "file", pathSegments: path.split(sep));
727 } else { 780 } else {
728 // Relative URI. 781 // Relative URI.
729 return new Uri(pathSegments: path.split(sep)); 782 return new Uri(pathSegments: path.split(sep));
730 } 783 }
731 } 784 }
732 785
733 static _makeWindowsFileUrl(String path) { 786 static _makeWindowsFileUrl(String path) {
734 if (path.startsWith("\\\\?\\")) { 787 if (path.startsWith("\\\\?\\")) {
(...skipping 88 matching lines...) Expand 10 before | Expand all | Expand 10 after
823 */ 876 */
824 Map<String, String> get queryParameters { 877 Map<String, String> get queryParameters {
825 if (_queryParameters == null) { 878 if (_queryParameters == null) {
826 _queryParameters = new UnmodifiableMapView(splitQueryString(query)); 879 _queryParameters = new UnmodifiableMapView(splitQueryString(query));
827 } 880 }
828 return _queryParameters; 881 return _queryParameters;
829 } 882 }
830 883
831 static int _makePort(int port, String scheme) { 884 static int _makePort(int port, String scheme) {
832 // Perform scheme specific normalization. 885 // Perform scheme specific normalization.
833 if (port == 80 && scheme == "http") { 886 if (port != null && port == _defaultPort(scheme)) return null;
834 return 0;
835 }
836 if (port == 443 && scheme == "https") {
837 return 0;
838 }
839 return port; 887 return port;
840 } 888 }
841 889
842 /** 890 /**
843 * Check and normalize a most name. 891 * Check and normalize a most name.
844 * 892 *
845 * If the host name starts and ends with '[' and ']', it is considered an 893 * If the host name starts and ends with '[' and ']', it is considered an
846 * IPv6 address. If [strictIPv6] is false, the address is also considered 894 * IPv6 address. If [strictIPv6] is false, the address is also considered
847 * an IPv6 address if it contains any ':' character. 895 * an IPv6 address if it contains any ':' character.
848 * 896 *
849 * If it is not an IPv6 address, it is case- and escape-normalized. 897 * If it is not an IPv6 address, it is case- and escape-normalized.
850 * This escapes all characters not valid in a reg-name, 898 * This escapes all characters not valid in a reg-name,
851 * and converts all non-escape upper-case letters to lower-case. 899 * and converts all non-escape upper-case letters to lower-case.
852 */ 900 */
853 static String _makeHost(String host, int start, int end, bool strictIPv6) { 901 static String _makeHost(String host, int start, int end, bool strictIPv6) {
854 // TODO(lrn): Should we normalize IPv6 addresses according to RFC 5952? 902 // TODO(lrn): Should we normalize IPv6 addresses according to RFC 5952?
855
856 if (host == null) return null; 903 if (host == null) return null;
857 if (start == end) return ""; 904 if (start == end) return "";
858 // Host is an IPv6 address if it starts with '[' or contains a colon. 905 // Host is an IPv6 address if it starts with '[' or contains a colon.
859 if (host.codeUnitAt(start) == _LEFT_BRACKET) { 906 if (host.codeUnitAt(start) == _LEFT_BRACKET) {
860 if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) { 907 if (host.codeUnitAt(end - 1) != _RIGHT_BRACKET) {
861 _fail(host, start, 'Missing end `]` to match `[` in host'); 908 _fail(host, start, 'Missing end `]` to match `[` in host');
862 } 909 }
863 parseIPv6Address(host, start + 1, end - 1); 910 parseIPv6Address(host, start + 1, end - 1);
864 // RFC 5952 requires hex digits to be lower case. 911 // RFC 5952 requires hex digits to be lower case.
865 return host.substring(start, end).toLowerCase(); 912 return host.substring(start, end).toLowerCase();
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after
978 if (_LOWER_CASE_A <= char && _LOWER_CASE_Z >= char) { 1025 if (_LOWER_CASE_A <= char && _LOWER_CASE_Z >= char) {
979 allLowercase = false; 1026 allLowercase = false;
980 } 1027 }
981 } 1028 }
982 scheme = scheme.substring(0, end); 1029 scheme = scheme.substring(0, end);
983 if (!allLowercase) scheme = scheme.toLowerCase(); 1030 if (!allLowercase) scheme = scheme.toLowerCase();
984 return scheme; 1031 return scheme;
985 } 1032 }
986 1033
987 static String _makeUserInfo(String userInfo, int start, int end) { 1034 static String _makeUserInfo(String userInfo, int start, int end) {
988 if (userInfo == null) return "null"; 1035 if (userInfo == null) return "";
989 return _normalize(userInfo, start, end, _userinfoTable); 1036 return _normalize(userInfo, start, end, _userinfoTable);
990 } 1037 }
991 1038
992 static String _makePath(String path, int start, int end, 1039 static String _makePath(String path, int start, int end,
993 Iterable<String> pathSegments, 1040 Iterable<String> pathSegments,
994 bool ensureLeadingSlash) { 1041 bool ensureLeadingSlash) {
995 if (path == null && pathSegments == null) return ""; 1042 if (path == null && pathSegments == null) return "";
996 if (path != null && pathSegments != null) { 1043 if (path != null && pathSegments != null) {
997 throw new ArgumentError('Both path and pathSegments specified'); 1044 throw new ArgumentError('Both path and pathSegments specified');
998 } 1045 }
999 var result; 1046 var result;
1000 if (path != null) { 1047 if (path != null) {
1001 result = _normalize(path, start, end, _pathCharOrSlashTable); 1048 result = _normalize(path, start, end, _pathCharOrSlashTable);
1002 } else { 1049 } else {
1003 result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/"); 1050 result = pathSegments.map((s) => _uriEncode(_pathCharTable, s)).join("/");
1004 } 1051 }
1005 if (ensureLeadingSlash && result.isNotEmpty && !result.startsWith("/")) { 1052 if (ensureLeadingSlash && result.isNotEmpty && !result.startsWith("/")) {
1006 return "/$result"; 1053 return "/$result";
1007 } 1054 }
1008 return result; 1055 return result;
1009 } 1056 }
1010 1057
1011 static String _makeQuery(String query, int start, int end, 1058 static String _makeQuery(String query, int start, int end,
1012 Map<String, String> queryParameters) { 1059 Map<String, String> queryParameters) {
1013 if (query == null && queryParameters == null) return ""; 1060 if (query == null && queryParameters == null) return null;
1014 if (query != null && queryParameters != null) { 1061 if (query != null && queryParameters != null) {
1015 throw new ArgumentError('Both query and queryParameters specified'); 1062 throw new ArgumentError('Both query and queryParameters specified');
1016 } 1063 }
1017 if (query != null) return _normalize(query, start, end, _queryCharTable); 1064 if (query != null) return _normalize(query, start, end, _queryCharTable);
1018 1065
1019 var result = new StringBuffer(); 1066 var result = new StringBuffer();
1020 var first = true; 1067 var first = true;
1021 queryParameters.forEach((key, value) { 1068 queryParameters.forEach((key, value) {
1022 if (!first) { 1069 if (!first) {
1023 result.write("&"); 1070 result.write("&");
1024 } 1071 }
1025 first = false; 1072 first = false;
1026 result.write(Uri.encodeQueryComponent(key)); 1073 result.write(Uri.encodeQueryComponent(key));
1027 if (value != null && !value.isEmpty) { 1074 if (value != null && !value.isEmpty) {
1028 result.write("="); 1075 result.write("=");
1029 result.write(Uri.encodeQueryComponent(value)); 1076 result.write(Uri.encodeQueryComponent(value));
1030 } 1077 }
1031 }); 1078 });
1032 return result.toString(); 1079 return result.toString();
1033 } 1080 }
1034 1081
1035 static String _makeFragment(String fragment, int start, int end) { 1082 static String _makeFragment(String fragment, int start, int end) {
1036 if (fragment == null) return ""; 1083 if (fragment == null) return null;
1037 return _normalize(fragment, start, end, _queryCharTable); 1084 return _normalize(fragment, start, end, _queryCharTable);
1038 } 1085 }
1039 1086
1040 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length; 1087 static int _stringOrNullLength(String s) => (s == null) ? 0 : s.length;
1041 1088
1042 static bool _isHexDigit(int char) { 1089 static bool _isHexDigit(int char) {
1043 if (_NINE >= char) return _ZERO <= char; 1090 if (_NINE >= char) return _ZERO <= char;
1044 char |= 0x20; 1091 char |= 0x20;
1045 return _LOWER_CASE_A <= char && _LOWER_CASE_F >= char; 1092 return _LOWER_CASE_A <= char && _LOWER_CASE_F >= char;
1046 } 1093 }
(...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after
1266 * 1313 *
1267 * Returns the resolved URI. 1314 * Returns the resolved URI.
1268 * 1315 *
1269 * The algorithm for resolving a reference is described in 1316 * The algorithm for resolving a reference is described in
1270 * [RFC-3986 Section 5] 1317 * [RFC-3986 Section 5]
1271 * (http://tools.ietf.org/html/rfc3986#section-5 "RFC-1123"). 1318 * (http://tools.ietf.org/html/rfc3986#section-5 "RFC-1123").
1272 */ 1319 */
1273 Uri resolveUri(Uri reference) { 1320 Uri resolveUri(Uri reference) {
1274 // From RFC 3986. 1321 // From RFC 3986.
1275 String targetScheme; 1322 String targetScheme;
1276 String targetUserInfo; 1323 String targetUserInfo = "";
1277 String targetHost; 1324 String targetHost;
1278 int targetPort; 1325 int targetPort;
1279 String targetPath; 1326 String targetPath;
1280 String targetQuery; 1327 String targetQuery;
1281 if (reference.scheme != "") { 1328 if (reference.scheme.isNotEmpty) {
1282 targetScheme = reference.scheme; 1329 targetScheme = reference.scheme;
1283 targetUserInfo = reference.userInfo;
1284 targetHost = reference.host;
1285 targetPort = reference.port;
1286 targetPath = _removeDotSegments(reference.path);
1287 targetQuery = reference.query;
1288 } else {
1289 if (reference.hasAuthority) { 1330 if (reference.hasAuthority) {
1290 targetUserInfo = reference.userInfo; 1331 targetUserInfo = reference.userInfo;
1291 targetHost = reference.host; 1332 targetHost = reference.host;
1292 targetPort = reference.port; 1333 targetPort = reference.hasPort ? reference.port : null;
1334 }
1335 targetPath = _removeDotSegments(reference.path);
1336 if (reference.hasQuery) {
1337 targetQuery = reference.query;
1338 }
1339 } else {
1340 targetScheme = this.scheme;
1341 if (reference.hasAuthority) {
1342 targetUserInfo = reference.userInfo;
1343 targetHost = reference.host;
1344 targetPort = _makePort(reference.hasPort ? reference.port : null,
1345 targetScheme);
1293 targetPath = _removeDotSegments(reference.path); 1346 targetPath = _removeDotSegments(reference.path);
1294 targetQuery = reference.query; 1347 if (reference.hasQuery) targetQuery = reference.query;
1295 } else { 1348 } else {
1296 if (reference.path == "") { 1349 if (reference.path == "") {
1297 targetPath = this.path; 1350 targetPath = this._path;
1298 if (reference.query != "") { 1351 if (reference.hasQuery) {
1299 targetQuery = reference.query; 1352 targetQuery = reference.query;
1300 } else { 1353 } else {
1301 targetQuery = this.query; 1354 targetQuery = this._query;
1302 } 1355 }
1303 } else { 1356 } else {
1304 if (reference.path.startsWith("/")) { 1357 if (reference.path.startsWith("/")) {
1305 targetPath = _removeDotSegments(reference.path); 1358 targetPath = _removeDotSegments(reference.path);
1306 } else { 1359 } else {
1307 targetPath = _removeDotSegments(_merge(this.path, reference.path)); 1360 targetPath = _removeDotSegments(_merge(this._path, reference.path));
1308 } 1361 }
1309 targetQuery = reference.query; 1362 if (reference.hasQuery) targetQuery = reference.query;
1310 } 1363 }
1311 targetUserInfo = this.userInfo; 1364 targetUserInfo = this._userInfo;
1312 targetHost = this.host; 1365 targetHost = this._host;
1313 targetPort = this.port; 1366 targetPort = this._port;
1314 } 1367 }
1315 targetScheme = this.scheme;
1316 } 1368 }
1317 return new Uri(scheme: targetScheme, 1369 String fragment = reference.hasFragment ? reference.fragment : null;
1318 userInfo: targetUserInfo, 1370 return new Uri._internal(targetScheme,
1319 host: targetHost, 1371 targetUserInfo,
1320 port: targetPort, 1372 targetHost,
1321 path: targetPath, 1373 targetPort,
1322 query: targetQuery, 1374 targetPath,
1323 fragment: reference.fragment); 1375 targetQuery,
1376 fragment);
1324 } 1377 }
1325 1378
1326 /** 1379 /**
1327 * Returns whether the URI has an [authority] component. 1380 * Returns whether the URI has an [authority] component.
1328 */ 1381 */
1329 bool get hasAuthority => host != ""; 1382 bool get hasAuthority => _host != null;
1383
1384 /**
1385 * Returns whether the URI has an explicit port.
1386 *
1387 * If the port number is the default port number
1388 * (zero for unrecognized schemes, with http (80) and https (443) being
1389 * recognized),
1390 * then the port is made implicit and omitted from the URI.
1391 */
1392 bool get hasPort => _port != null;
1393
1394 /**
1395 * Returns whether the URI has a query part.
1396 */
1397 bool get hasQuery => _query != null;
1398
1399 /**
1400 * Returns whether the URI has a fragment part.
1401 */
1402 bool get hasFragment => _fragment != null;
1330 1403
1331 /** 1404 /**
1332 * Returns the origin of the URI in the form scheme://host:port for the 1405 * Returns the origin of the URI in the form scheme://host:port for the
1333 * schemes http and https. 1406 * schemes http and https.
1334 * 1407 *
1335 * It is an error if the scheme is not "http" or "https". 1408 * It is an error if the scheme is not "http" or "https".
1336 * 1409 *
1337 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin 1410 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin
1338 */ 1411 */
1339 String get origin { 1412 String get origin {
1340 if (scheme == "" || _host == null || _host == "") { 1413 if (scheme == "" || _host == null || _host == "") {
1341 throw new StateError("Cannot use origin without a scheme: $this"); 1414 throw new StateError("Cannot use origin without a scheme: $this");
1342 } 1415 }
1343 if (scheme != "http" && scheme != "https") { 1416 if (scheme != "http" && scheme != "https") {
1344 throw new StateError( 1417 throw new StateError(
1345 "Origin is only applicable schemes http and https: $this"); 1418 "Origin is only applicable schemes http and https: $this");
1346 } 1419 }
1347 if (_port == 0) return "$scheme://$_host"; 1420 if (_port == null) return "$scheme://$_host";
1348 return "$scheme://$_host:$_port"; 1421 return "$scheme://$_host:$_port";
1349 } 1422 }
1350 1423
1351 /** 1424 /**
1352 * Returns the file path from a file URI. 1425 * Returns the file path from a file URI.
1353 * 1426 *
1354 * The returned path has either Windows or non-Windows 1427 * The returned path has either Windows or non-Windows
1355 * semantics. 1428 * semantics.
1356 * 1429 *
1357 * For non-Windows semantics the slash ("/") is used to separate 1430 * For non-Windows semantics the slash ("/") is used to separate
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after
1464 if (hasDriveLetter && segments.length == 1) result.write("\\"); 1537 if (hasDriveLetter && segments.length == 1) result.write("\\");
1465 return result.toString(); 1538 return result.toString();
1466 } 1539 }
1467 1540
1468 bool get _isPathAbsolute { 1541 bool get _isPathAbsolute {
1469 if (path == null || path.isEmpty) return false; 1542 if (path == null || path.isEmpty) return false;
1470 return path.startsWith('/'); 1543 return path.startsWith('/');
1471 } 1544 }
1472 1545
1473 void _writeAuthority(StringSink ss) { 1546 void _writeAuthority(StringSink ss) {
1474 _addIfNonEmpty(ss, userInfo, userInfo, "@"); 1547 if (_userInfo.isNotEmpty) {
1475 ss.write(_host == null ? "null" : _host); 1548 ss.write(_userInfo);
1476 if (_port != 0) { 1549 ss.write("@");
1550 }
1551 if (_host != null) ss.write(_host);
1552 if (_port != null) {
1477 ss.write(":"); 1553 ss.write(":");
1478 ss.write(_port.toString()); 1554 ss.write(_port);
1479 } 1555 }
1480 } 1556 }
1481 1557
1482 String toString() { 1558 String toString() {
1483 StringBuffer sb = new StringBuffer(); 1559 StringBuffer sb = new StringBuffer();
1484 _addIfNonEmpty(sb, scheme, scheme, ':'); 1560 _addIfNonEmpty(sb, scheme, scheme, ':');
1485 if (hasAuthority || path.startsWith("//") || (scheme == "file")) { 1561 if (hasAuthority || path.startsWith("//") || (scheme == "file")) {
1486 // File URIS always have the authority, even if it is empty. 1562 // File URIS always have the authority, even if it is empty.
1487 // The empty URI means "localhost". 1563 // The empty URI means "localhost".
1488 sb.write("//"); 1564 sb.write("//");
1489 _writeAuthority(sb); 1565 _writeAuthority(sb);
1490 } 1566 }
1491 sb.write(path); 1567 sb.write(path);
1492 _addIfNonEmpty(sb, query, "?", query); 1568 if (_query != null) { sb..write("?")..write(_query); }
1493 _addIfNonEmpty(sb, fragment, "#", fragment); 1569 if (_fragment != null) { sb..write("#")..write(_fragment); }
1494 return sb.toString(); 1570 return sb.toString();
1495 } 1571 }
1496 1572
1497 bool operator==(other) { 1573 bool operator==(other) {
1498 if (other is! Uri) return false; 1574 if (other is! Uri) return false;
1499 Uri uri = other; 1575 Uri uri = other;
1500 return scheme == uri.scheme && 1576 return scheme == uri.scheme &&
1577 hasAuthority == uri.hasAuthority &&
1501 userInfo == uri.userInfo && 1578 userInfo == uri.userInfo &&
1502 host == uri.host && 1579 host == uri.host &&
1503 port == uri.port && 1580 port == uri.port &&
1504 path == uri.path && 1581 path == uri.path &&
1582 hasQuery == uri.hasQuery &&
1505 query == uri.query && 1583 query == uri.query &&
1584 hasFragment == uri.hasFragment &&
1506 fragment == uri.fragment; 1585 fragment == uri.fragment;
1507 } 1586 }
1508 1587
1509 int get hashCode { 1588 int get hashCode {
1510 int combine(part, current) { 1589 int combine(part, current) {
1511 // The sum is truncated to 30 bits to make sure it fits into a Smi. 1590 // The sum is truncated to 30 bits to make sure it fits into a Smi.
1512 return (current * 31 + part.hashCode) & 0x3FFFFFFF; 1591 return (current * 31 + part.hashCode) & 0x3FFFFFFF;
1513 } 1592 }
1514 return combine(scheme, combine(userInfo, combine(host, combine(port, 1593 return combine(scheme, combine(userInfo, combine(host, combine(port,
1515 combine(path, combine(query, combine(fragment, 1))))))); 1594 combine(path, combine(query, combine(fragment, 1)))))));
(...skipping 679 matching lines...) Expand 10 before | Expand all | Expand 10 after
2195 0xafff, // 0x30 - 0x3f 1111111111110101 2274 0xafff, // 0x30 - 0x3f 1111111111110101
2196 // @ABCDEFGHIJKLMNO 2275 // @ABCDEFGHIJKLMNO
2197 0xffff, // 0x40 - 0x4f 1111111111111111 2276 0xffff, // 0x40 - 0x4f 1111111111111111
2198 // PQRSTUVWXYZ _ 2277 // PQRSTUVWXYZ _
2199 0x87ff, // 0x50 - 0x5f 1111111111100001 2278 0x87ff, // 0x50 - 0x5f 1111111111100001
2200 // abcdefghijklmno 2279 // abcdefghijklmno
2201 0xfffe, // 0x60 - 0x6f 0111111111111111 2280 0xfffe, // 0x60 - 0x6f 0111111111111111
2202 // pqrstuvwxyz ~ 2281 // pqrstuvwxyz ~
2203 0x47ff]; // 0x70 - 0x7f 1111111111100010 2282 0x47ff]; // 0x70 - 0x7f 1111111111100010
2204 } 2283 }
OLDNEW
« no previous file with comments | « no previous file | tests/co19/co19-co19.status » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698