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

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

Issue 2086613003: Add fast-mode Uri class. (Closed) Base URL: https://github.com/dart-lang/sdk.git@master
Patch Set: Created 4 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
« no previous file with comments | « pkg/analyzer/lib/src/util/fast_uri.dart ('k') | no next file » | 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:**
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
66 66
67 // The fragment content, or null if there is no fragment. 67 // The fragment content, or null if there is no fragment.
68 final String _fragment; 68 final String _fragment;
69 69
70 /** 70 /**
71 * Cache the computed return value of [pathSegements]. 71 * Cache the computed return value of [pathSegements].
72 */ 72 */
73 List<String> _pathSegments; 73 List<String> _pathSegments;
74 74
75 /** 75 /**
76 * Cache of the full normalized text representation of the URI.
77 */
78 String _text;
79
80 /**
76 * Cache the computed return value of [queryParameters]. 81 * Cache the computed return value of [queryParameters].
77 */ 82 */
78 Map<String, String> _queryParameters; 83 Map<String, String> _queryParameters;
79 Map<String, List<String>> _queryParameterLists; 84 Map<String, List<String>> _queryParameterLists;
80 85
81 /// Internal non-verifying constructor. Only call with validated arguments. 86 /// Internal non-verifying constructor. Only call with validated arguments.
82 Uri._internal(this.scheme, 87 Uri._internal(this.scheme,
83 this._userInfo, 88 this._userInfo,
84 this._host, 89 this._host,
85 this._port, 90 this._port,
(...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after
151 * use an empty map for `queryParameters`. 156 * use an empty map for `queryParameters`.
152 * 157 *
153 * If both `query` and `queryParameters` are omitted or `null`, 158 * If both `query` and `queryParameters` are omitted or `null`,
154 * the URI has no query part. 159 * the URI has no query part.
155 * 160 *
156 * The fragment component is set through [fragment]. 161 * The fragment component is set through [fragment].
157 * It should be a valid URI fragment, but invalid characters other than 162 * It should be a valid URI fragment, but invalid characters other than
158 * general delimiters, are escaped if necessary. 163 * general delimiters, are escaped if necessary.
159 * If `fragment` is omitted or `null`, the URI has no fragment part. 164 * If `fragment` is omitted or `null`, the URI has no fragment part.
160 */ 165 */
161 factory Uri({String scheme : "", 166 factory Uri({String scheme,
162 String userInfo : "", 167 String userInfo,
163 String host, 168 String host,
164 int port, 169 int port,
165 String path, 170 String path,
166 Iterable<String> pathSegments, 171 Iterable<String> pathSegments,
167 String query, 172 String query,
168 Map<String, dynamic/*String|Iterable<String>*/> queryParameters, 173 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
169 String fragment}) { 174 String fragment}) {
170 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme)); 175 scheme = _makeScheme(scheme, 0, _stringOrNullLength(scheme));
171 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo)); 176 userInfo = _makeUserInfo(userInfo, 0, _stringOrNullLength(userInfo));
172 host = _makeHost(host, 0, _stringOrNullLength(host), false); 177 host = _makeHost(host, 0, _stringOrNullLength(host), false);
(...skipping 214 matching lines...) Expand 10 before | Expand all | Expand 10 after
387 // segment = *pchar 392 // segment = *pchar
388 // segment-nz = 1*pchar 393 // segment-nz = 1*pchar
389 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) 394 // segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
390 // ; non-zero-length segment without any colon ":" 395 // ; non-zero-length segment without any colon ":"
391 // 396 //
392 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" 397 // pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
393 // 398 //
394 // query = *( pchar / "/" / "?" ) 399 // query = *( pchar / "/" / "?" )
395 // 400 //
396 // fragment = *( pchar / "/" / "?" ) 401 // fragment = *( pchar / "/" / "?" )
397 const int EOI = -1; 402 end ??= uri.length;
398 403
399 String scheme = ""; 404 // Special case data:URIs. Ignore case when testing.
400 String userinfo = ""; 405 if (end >= start + 5 &&
401 String host = null; 406 uri.codeUnitAt(start + 4) == _COLON &&
402 int port = null; 407 uri.codeUnitAt(start) | 0x20 == 0x64 /*d*/ &&
403 String path = null; 408 uri.codeUnitAt(start + 1) | 0x20 == 0x61 /*a*/ &&
404 String query = null; 409 uri.codeUnitAt(start + 2) | 0x20 == 0x74 /*t*/ &&
405 String fragment = null; 410 uri.codeUnitAt(start + 3) | 0x20 == 0x61 /*a*/) {
406 if (end == null) end = uri.length; 411 // Data-URI.
407 412 if (uri.startsWith("data", start)) {
408 int index = start; 413 // The case is right.
409 int pathStart = start; 414 if (start > 0 || end < uri.length) uri = uri.substring(start, end);
410 // End of input-marker. 415 return UriData._parse(uri, 5, null).uri;
411 int char = EOI;
412
413 void parseAuth() {
414 if (index == end) {
415 char = EOI;
416 return;
417 } 416 }
418 int authStart = index; 417 return Uriata._parse(uri.substring(start + 5, end), 0, null).uri;
floitsch 2016/06/28 00:28:03 UriData I'm guessing you didn't run the tests?
Lasse Reichstein Nielsen 2016/06/28 13:00:51 I did. Turns out we never tested a data URI starti
419 int lastColon = -1;
420 int lastAt = -1;
421 char = uri.codeUnitAt(index);
422 while (index < end) {
423 char = uri.codeUnitAt(index);
424 if (char == _SLASH || char == _QUESTION || char == _NUMBER_SIGN) {
425 break;
426 }
427 if (char == _AT_SIGN) {
428 lastAt = index;
429 lastColon = -1;
430 } else if (char == _COLON) {
431 lastColon = index;
432 } else if (char == _LEFT_BRACKET) {
433 lastColon = -1;
434 int endBracket = uri.indexOf(']', index + 1);
435 if (endBracket == -1) {
436 index = end;
437 char = EOI;
438 break;
439 } else {
440 index = endBracket;
441 }
442 }
443 index++;
444 char = EOI;
445 }
446 int hostStart = authStart;
447 int hostEnd = index;
448 if (lastAt >= 0) {
449 userinfo = _makeUserInfo(uri, authStart, lastAt);
450 hostStart = lastAt + 1;
451 }
452 if (lastColon >= 0) {
453 int portNumber;
454 if (lastColon + 1 < index) {
455 portNumber = 0;
456 for (int i = lastColon + 1; i < index; i++) {
457 int digit = uri.codeUnitAt(i);
458 if (_ZERO > digit || _NINE < digit) {
459 _fail(uri, i, "Invalid port number");
460 }
461 portNumber = portNumber * 10 + (digit - _ZERO);
462 }
463 }
464 port = _makePort(portNumber, scheme);
465 hostEnd = lastColon;
466 }
467 host = _makeHost(uri, hostStart, hostEnd, true);
468 if (index < end) {
469 char = uri.codeUnitAt(index);
470 }
471 } 418 }
472 419
473 // When reaching path parsing, the current character is known to not 420 var indices = _scanUri(uri, start, end);
floitsch 2016/06/28 00:28:03 Provide example of what the indices could look lik
Lasse Reichstein Nielsen 2016/06/28 13:00:52 Done.
474 // be part of the path.
475 const int NOT_IN_PATH = 0;
476 // When reaching path parsing, the current character is part
477 // of the a non-empty path.
478 const int IN_PATH = 1;
479 // When reaching authority parsing, authority is possible.
480 // This is only true at start or right after scheme.
481 const int ALLOW_AUTH = 2;
482 421
483 // Current state. 422 int schemeEnd = indices[_schemeEndIndex]; // >0 if has scheme
floitsch 2016/06/28 00:28:04 Finish with ".".
Lasse Reichstein Nielsen 2016/06/28 13:00:52 Just removed, it's documented in the _scanUri docs
484 // Initialized to the default value that is used when exiting the 423 int hostStart = indices[_hostStartIndex] + 1; // >0 if has authority.
485 // scheme loop by reaching the end of input. 424 int portStart = indices[_portStartIndex];
486 // All other breaks set their own state. 425 int pathStart = indices[_pathStartIndex];
487 int state = NOT_IN_PATH; 426 int queryStart = indices[_queryStartIndex];
488 int i = index; // Temporary alias for index to avoid bug 19550 in dart2js. 427 int fragmentStart = indices[_fragmentStartIndex];
489 while (i < end) { 428
490 char = uri.codeUnitAt(i); 429 // We may discover scheme while handling special cases.
491 if (char == _QUESTION || char == _NUMBER_SIGN) { 430 String scheme;
492 state = NOT_IN_PATH; 431
493 break; 432 // Derive some indices that weren't set.
433 // If fragment but no query, set query to start at fragment.
434 if (fragmentStart < queryStart) queryStart = fragmentStart;
floitsch 2016/06/28 00:28:03 It's not clear why this wouldn't be done by the _s
Lasse Reichstein Nielsen 2016/06/28 13:00:52 It absolutely could. More to the point, the _scanU
435 // If scheme but no authority, the pathStart isn't set.
436 if (schemeEnd >= start && hostStart <= start) pathStart = schemeEnd + 1;
437 // If scheme or authority but pathStart isn't set.
438 if (pathStart == start && (schemeEnd >= start || hostStart > start)) {
439 pathStart = queryStart;
440 }
441 // If authority and no port.
442 // (including when user-info contains : and portStart >= 0).
443 if (portStart < hostStart) portStart = pathStart;
444
445 bool isSimple = indices[_notSimpleIndex] < start;
446 if (isSimple) {
447 // Check/do normalizations that weren't detected by the scanner.
448 // This includes removal of empty port or userInfo,
449 // or scheme specific port and path normalizations.
450 if (hostStart > schemeEnd + 3) {
451 // Always be non-simple if URI contains user-info.
452 isSimple = false;
494 } 453 }
floitsch 2016/06/28 00:28:03 Is it necessary to enter the 'else' below? if not
Lasse Reichstein Nielsen 2016/06/28 13:00:52 Done.
495 if (char == _SLASH) { 454 if (portStart > start && portStart + 1 == pathStart) {
496 state = (i == start) ? ALLOW_AUTH : IN_PATH; 455 // If the port is empty, it should be omitted.
497 break; 456 // Path case, don't bother correcting it.
498 } 457 isSimple = false;
499 if (char == _COLON) { 458 } else if (hostStart == schemeEnd + 4) {
500 if (i == start) _fail(uri, start, "Invalid empty scheme"); 459 // If the userInfo is empty, it should be omitted.
501 scheme = _makeScheme(uri, start, i); 460 // (4 is length of "://@").
502 i++; 461 // Pathological case, don't bother correcting it.
503 if (scheme == "data") { 462 isSimple = false;
504 // This generates a URI that is (potentially) not path normalized. 463 } else {
505 // Applying part normalization to a non-hierarchial URI isn't 464 // There are a few scheme-based normalizations that
506 // meaningful. 465 // the scanner couldn't check.
507 return UriData._parse(uri, i, null).uri; 466 // That means that the input is very close to simple, so just do
508 } 467 // the normalizations.
509 pathStart = i; 468 if (schemeEnd == start + 4) {
510 if (i == end) { 469 // Do scheme based normalizations for file, http.
511 char = EOI; 470 if (uri.startsWith("file", start)) {
512 state = NOT_IN_PATH; 471 scheme = "file";
513 } else { 472 if (hostStart <= 0) {
514 char = uri.codeUnitAt(i); 473 // File URIs should have an authority.
515 if (char == _QUESTION || char == _NUMBER_SIGN) { 474 // Paths after an authority should be absolute.
516 state = NOT_IN_PATH; 475 String insert = "//";
517 } else if (char == _SLASH) { 476 int delta = 2;
518 state = ALLOW_AUTH; 477 if (!uri.startsWith("/", pathStart)) {
519 } else { 478 insert = "///";
520 state = IN_PATH; 479 delta = 3;
480 }
481 uri = "${uri.substring(start, schemeEnd + 1)}$insert"
482 "${uri.substring(pathStart, end)}";
483 schemeEnd -= start;
484 pathStart += 2 - start;
485 hostStart = schemeEnd + 3;
486 portStart = pathStart;
487 queryStart += delta;
488 fragmentStart += delta;
489 start = 0;
490 end = uri.length;
491 } else if (pathStart == queryStart) {
492 uri = "${uri.substring(start, pathStart)}/"
493 "${uri.substring(pathStart, end)}";
494 queryStart += 1;
495 fragmentStart += 1;
496 }
497 } else if (uri.startsWith("http", start)) {
498 scheme = "http";
499 // Http URIs should not have an explicit port of 80
floitsch 2016/06/28 00:28:03 Finish with ".".
Lasse Reichstein Nielsen 2016/06/28 13:00:51 Done.
500 if (portStart > start && portStart + 3 == pathStart &&
501 uri.startsWith("80", portStart + 1)) {
502 uri = uri.substring(start, portStart) +
503 uri.substring(pathStart, end);
504 schemeEnd -= start;
505 hostStart -= start;
506 portStart -= start;
507 pathStart -= 3 + start;
508 queryStart -= 3 + start;
509 fragmentStart -= 3 + start;
510 start = 0;
511 end = uri.length;
512 }
521 } 513 }
522 } 514 } else if (schemeEnd == start + 5 && uri.startsWith("https", start)) {
523 break; 515 scheme = "https";
524 } 516 // Https URIs should not have an explicit port of 443
525 i++; 517 if (portStart > start && portStart + 4 == pathStart &&
526 char = EOI; 518 uri.startsWith("443", portStart + 1)) {
527 } 519 uri = uri.substring(start, portStart) +
528 index = i; // Remove alias when bug is fixed. 520 uri.substring(pathStart, end);
529 521 schemeEnd -= start;
530 if (state == ALLOW_AUTH) { 522 hostStart -= start;
531 assert(char == _SLASH); 523 portStart -= start;
532 // Have seen one slash either at start or right after scheme. 524 pathStart -= 4 + start;
533 // If two slashes, it's an authority, otherwise it's just the path. 525 queryStart -= 4 + start;
534 index++; 526 fragmentStart -= 4 + start;
535 if (index == end) { 527 start = 0;
536 char = EOI; 528 end = uri.length;
537 state = NOT_IN_PATH; 529 }
538 } else {
539 char = uri.codeUnitAt(index);
540 if (char == _SLASH) {
541 index++;
542 parseAuth();
543 pathStart = index;
544 }
545 if (char == _QUESTION || char == _NUMBER_SIGN || char == EOI) {
546 state = NOT_IN_PATH;
547 } else {
548 state = IN_PATH;
549 } 530 }
550 } 531 }
551 } 532 }
552 533
553 assert(state == IN_PATH || state == NOT_IN_PATH); 534 if (isSimple) {
554 if (state == IN_PATH) { 535 if (start > 0 || end < uri.length) {
555 // Characters from pathStart to index (inclusive) are known 536 uri = uri.substring(start, end);
556 // to be part of the path. 537 if (schemeEnd >= 0) schemeEnd -= start;
557 while (++index < end) { 538 if (hostStart > 0) {
558 char = uri.codeUnitAt(index); 539 hostStart -= start;
559 if (char == _QUESTION || char == _NUMBER_SIGN) { 540 portStart -= start;
560 break;
561 } 541 }
562 char = EOI; 542 pathStart -= start;
543 queryStart -= start;
544 fragmentStart -= start;
563 } 545 }
564 state = NOT_IN_PATH; 546 return new _SimpleUri(uri, schemeEnd, hostStart, portStart, pathStart,
547 queryStart, fragmentStart, scheme);
548
565 } 549 }
566 550
567 assert(state == NOT_IN_PATH); 551 if (scheme == null) {
568 bool hasAuthority = (host != null); 552 scheme = "";
569 path = _makePath(uri, pathStart, index, null, scheme, hasAuthority); 553 if (schemeEnd > start) {
570 554 scheme = _makeScheme(uri, start, schemeEnd);
571 if (char == _QUESTION) { 555 } else if (schemeEnd == start) {
572 int numberSignIndex = -1; 556 _fail(uri, start, "Invalid empty scheme");
573 for (int i = index + 1; i < end; i++) {
574 if (uri.codeUnitAt(i) == _NUMBER_SIGN) {
575 numberSignIndex = i;
576 break;
577 }
578 } 557 }
579 if (numberSignIndex < 0) { 558 }
580 query = _makeQuery(uri, index + 1, end, null); 559 String userInfo = "";
581 } else { 560 String host;
582 query = _makeQuery(uri, index + 1, numberSignIndex, null); 561 int port;
583 fragment = _makeFragment(uri, numberSignIndex + 1, end); 562 if (hostStart > start) {
563 int userInfoStart = schemeEnd + 3;
564 if (userInfoStart < hostStart) {
565 userInfo = _makeUserInfo(uri, userInfoStart, hostStart - 1);
584 } 566 }
585 } else if (char == _NUMBER_SIGN) { 567 host = _makeHost(uri, hostStart, portStart, false);
586 fragment = _makeFragment(uri, index + 1, end); 568 if (portStart + 1 < pathStart) {
569 // Should throw because invalid.
570 port = int.parse(uri.substring(portStart + 1, pathStart), onError: (_) {
571 throw new FormatException("Invalid port", uri, portStart + 1);
572 });
573 port = _makePort(port, scheme);
574 }
575 }
576 String path = _makePath(uri, pathStart, queryStart, null,
577 scheme, host != null);
578 String query;
579 if (queryStart < fragmentStart) {
580 query = _makeQuery(uri, queryStart + 1, fragmentStart, null);
581 }
582 String fragment;
583 if (fragmentStart < end) {
584 fragment = _makeFragment(uri, fragmentStart + 1, end);
587 } 585 }
588 return new Uri._internal(scheme, 586 return new Uri._internal(scheme,
589 userinfo, 587 userInfo,
590 host, 588 host,
591 port, 589 port,
592 path, 590 path,
593 query, 591 query,
594 fragment); 592 fragment);
595 } 593 }
596 594
597 // Report a parse failure. 595 // Report a parse failure.
598 static void _fail(String uri, int index, String message) { 596 static void _fail(String uri, int index, String message) {
599 throw new FormatException(message, uri, index); 597 throw new FormatException(message, uri, index);
(...skipping 726 matching lines...) Expand 10 before | Expand all | Expand 10 after
1326 final int codeUnit = scheme.codeUnitAt(i); 1324 final int codeUnit = scheme.codeUnitAt(i);
1327 if (!_isSchemeCharacter(codeUnit)) { 1325 if (!_isSchemeCharacter(codeUnit)) {
1328 _fail(scheme, i, "Illegal scheme character"); 1326 _fail(scheme, i, "Illegal scheme character");
1329 } 1327 }
1330 if (_UPPER_CASE_A <= codeUnit && codeUnit <= _UPPER_CASE_Z) { 1328 if (_UPPER_CASE_A <= codeUnit && codeUnit <= _UPPER_CASE_Z) {
1331 containsUpperCase = true; 1329 containsUpperCase = true;
1332 } 1330 }
1333 } 1331 }
1334 scheme = scheme.substring(start, end); 1332 scheme = scheme.substring(start, end);
1335 if (containsUpperCase) scheme = scheme.toLowerCase(); 1333 if (containsUpperCase) scheme = scheme.toLowerCase();
1334 return _canonicalizeScheme(scheme);
1335 }
1336
1337 // Canonicalize a few often-used scheme strings.
1338 //
1339 // This improves memory usage and makes comparison faster.
1340 static String _canonicalizeScheme(String scheme) {
1341 if (scheme == "http") return "http";
1342 if (scheme == "file") return "file";
1343 if (scheme == "https") return "https";
1344 if (scheme == "package") return "package";
1336 return scheme; 1345 return scheme;
1337 } 1346 }
1338 1347
1339 static String _makeUserInfo(String userInfo, int start, int end) { 1348 static String _makeUserInfo(String userInfo, int start, int end) {
1340 if (userInfo == null) return ""; 1349 if (userInfo == null) return "";
1341 return _normalize(userInfo, start, end, _userinfoTable); 1350 return _normalize(userInfo, start, end, _userinfoTable);
1342 } 1351 }
1343 1352
1344 static String _makePath(String path, int start, int end, 1353 static String _makePath(String path, int start, int end,
1345 Iterable<String> pathSegments, 1354 Iterable<String> pathSegments,
(...skipping 579 matching lines...) Expand 10 before | Expand all | Expand 10 after
1925 * Uri.parse("file:C:xxx/yyy"); // Throws as a path segment 1934 * Uri.parse("file:C:xxx/yyy"); // Throws as a path segment
1926 * // cannot contain colon on Windows. 1935 * // cannot contain colon on Windows.
1927 * Uri.parse("file://server/share/file"); // \\server\share\file 1936 * Uri.parse("file://server/share/file"); // \\server\share\file
1928 * 1937 *
1929 * If the URI is not a file URI calling this throws 1938 * If the URI is not a file URI calling this throws
1930 * [UnsupportedError]. 1939 * [UnsupportedError].
1931 * 1940 *
1932 * If the URI cannot be converted to a file path calling this throws 1941 * If the URI cannot be converted to a file path calling this throws
1933 * [UnsupportedError]. 1942 * [UnsupportedError].
1934 */ 1943 */
1944 // TODO(lrn): Deprecate and move functionality to File class or similar.
1945 // The core libraries should not worry about the platform.
1935 String toFilePath({bool windows}) { 1946 String toFilePath({bool windows}) {
1936 if (scheme != "" && scheme != "file") { 1947 if (scheme != "" && scheme != "file") {
1937 throw new UnsupportedError( 1948 throw new UnsupportedError(
1938 "Cannot extract a file path from a $scheme URI"); 1949 "Cannot extract a file path from a $scheme URI");
1939 } 1950 }
1940 if (query != "") { 1951 if (query != "") {
1941 throw new UnsupportedError( 1952 throw new UnsupportedError(
1942 "Cannot extract a file path from a URI with a query component"); 1953 "Cannot extract a file path from a URI with a query component");
1943 } 1954 }
1944 if (fragment != "") { 1955 if (fragment != "") {
1945 throw new UnsupportedError( 1956 throw new UnsupportedError(
1946 "Cannot extract a file path from a URI with a fragment component"); 1957 "Cannot extract a file path from a URI with a fragment component");
1947 } 1958 }
1948 if (windows == null) windows = _isWindows; 1959 if (windows == null) windows = _isWindows;
1949 return windows ? _toWindowsFilePath() : _toFilePath(); 1960 return windows ? _toWindowsFilePath(this) : _toFilePath();
1950 } 1961 }
1951 1962
1952 String _toFilePath() { 1963 String _toFilePath() {
1953 if (host != "") { 1964 if (hasAuthority && host != "") {
1954 throw new UnsupportedError( 1965 throw new UnsupportedError(
1955 "Cannot extract a non-Windows file path from a file URI " 1966 "Cannot extract a non-Windows file path from a file URI "
1956 "with an authority"); 1967 "with an authority");
1957 } 1968 }
1969 // Use path segments to have any escapes unescaped.
1970 var pathSegments = this.pathSegments;
1958 _checkNonWindowsPathReservedCharacters(pathSegments, false); 1971 _checkNonWindowsPathReservedCharacters(pathSegments, false);
1959 var result = new StringBuffer(); 1972 var result = new StringBuffer();
1960 if (_isPathAbsolute) result.write("/"); 1973 if (hasAbsolutePath) result.write("/");
1961 result.writeAll(pathSegments, "/"); 1974 result.writeAll(pathSegments, "/");
1962 return result.toString(); 1975 return result.toString();
1963 } 1976 }
1964 1977
1965 String _toWindowsFilePath() { 1978 static String _toWindowsFilePath(Uri uri) {
1966 bool hasDriveLetter = false; 1979 bool hasDriveLetter = false;
1967 var segments = pathSegments; 1980 var segments = uri.pathSegments;
1968 if (segments.length > 0 && 1981 if (segments.length > 0 &&
1969 segments[0].length == 2 && 1982 segments[0].length == 2 &&
1970 segments[0].codeUnitAt(1) == _COLON) { 1983 segments[0].codeUnitAt(1) == _COLON) {
1971 _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false); 1984 _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false);
1972 _checkWindowsPathReservedCharacters(segments, false, 1); 1985 _checkWindowsPathReservedCharacters(segments, false, 1);
1973 hasDriveLetter = true; 1986 hasDriveLetter = true;
1974 } else { 1987 } else {
1975 _checkWindowsPathReservedCharacters(segments, false); 1988 _checkWindowsPathReservedCharacters(segments, false, 0);
1976 } 1989 }
1977 var result = new StringBuffer(); 1990 var result = new StringBuffer();
1978 if (_isPathAbsolute && !hasDriveLetter) result.write("\\"); 1991 if (uri.hasAbsolutePath && !hasDriveLetter) result.write(r"\");
1979 if (host != "") { 1992 if (uri.hasAuthority) {
1980 result.write("\\"); 1993 var host = uri.host;
1981 result.write(host); 1994 if (host.isNotEmpty) {
1982 result.write("\\"); 1995 result.write(r"\");
1996 result.write(host);
1997 result.write(r"\");
1998 }
1983 } 1999 }
1984 result.writeAll(segments, "\\"); 2000 result.writeAll(segments, r"\");
1985 if (hasDriveLetter && segments.length == 1) result.write("\\"); 2001 if (hasDriveLetter && segments.length == 1) result.write(r"\");
1986 return result.toString(); 2002 return result.toString();
1987 } 2003 }
1988 2004
1989 bool get _isPathAbsolute { 2005 bool get _isPathAbsolute {
1990 if (path == null || path.isEmpty) return false; 2006 return _path != null && _path.startsWith('/');
1991 return path.startsWith('/');
1992 } 2007 }
1993 2008
1994 void _writeAuthority(StringSink ss) { 2009 void _writeAuthority(StringSink ss) {
1995 if (_userInfo.isNotEmpty) { 2010 if (_userInfo.isNotEmpty) {
1996 ss.write(_userInfo); 2011 ss.write(_userInfo);
1997 ss.write("@"); 2012 ss.write("@");
1998 } 2013 }
1999 if (_host != null) ss.write(_host); 2014 if (_host != null) ss.write(_host);
2000 if (_port != null) { 2015 if (_port != null) {
2001 ss.write(":"); 2016 ss.write(":");
2002 ss.write(_port); 2017 ss.write(_port);
2003 } 2018 }
2004 } 2019 }
2005 2020
2006 /** 2021 /**
2007 * Access the structure of a `data:` URI. 2022 * Access the structure of a `data:` URI.
2008 * 2023 *
2009 * Returns a [UriData] object for `data:` URIs and `null` for all other 2024 * Returns a [UriData] object for `data:` URIs and `null` for all other
2010 * URIs. 2025 * URIs.
2011 * The [UriData] object can be used to access the media type and data 2026 * The [UriData] object can be used to access the media type and data
2012 * of a `data:` URI. 2027 * of a `data:` URI.
2013 */ 2028 */
2014 UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null; 2029 UriData get data => (scheme == "data") ? new UriData.fromUri(this) : null;
2015 2030
2016 String toString() { 2031 String toString() {
2032 if (_text != null) return _text;
2017 StringBuffer sb = new StringBuffer(); 2033 StringBuffer sb = new StringBuffer();
2018 _addIfNonEmpty(sb, scheme, scheme, ':'); 2034 if (scheme.isNotEmpty) sb..write(scheme)..write(":");
2019 if (hasAuthority || path.startsWith("//") || (scheme == "file")) { 2035 if (hasAuthority || path.startsWith("//") || (scheme == "file")) {
2020 // File URIS always have the authority, even if it is empty. 2036 // File URIS always have the authority, even if it is empty.
2021 // The empty URI means "localhost". 2037 // The empty URI means "localhost".
2022 sb.write("//"); 2038 sb.write("//");
2023 _writeAuthority(sb); 2039 _writeAuthority(sb);
2024 } 2040 }
2025 sb.write(path); 2041 sb.write(path);
2026 if (_query != null) { sb..write("?")..write(_query); } 2042 if (_query != null) sb..write("?")..write(_query);
2027 if (_fragment != null) { sb..write("#")..write(_fragment); } 2043 if (_fragment != null) sb..write("#")..write(_fragment);
2028 return sb.toString(); 2044 _text = sb.toString();
2045 return _text;
2029 } 2046 }
2030 2047
2031 bool operator==(other) { 2048 bool operator==(other) {
2032 if (other is! Uri) return false; 2049 if (other is! Uri) return false;
2033 Uri uri = other; 2050 Uri uri = other;
2034 return scheme == uri.scheme && 2051 return scheme == uri.scheme &&
2035 hasAuthority == uri.hasAuthority && 2052 hasAuthority == uri.hasAuthority &&
2036 userInfo == uri.userInfo && 2053 userInfo == uri.userInfo &&
2037 host == uri.host && 2054 host == uri.host &&
2038 port == uri.port && 2055 port == uri.port &&
2039 path == uri.path && 2056 path == uri.path &&
2040 hasQuery == uri.hasQuery && 2057 hasQuery == uri.hasQuery &&
2041 query == uri.query && 2058 query == uri.query &&
2042 hasFragment == uri.hasFragment && 2059 hasFragment == uri.hasFragment &&
2043 fragment == uri.fragment; 2060 fragment == uri.fragment;
2044 } 2061 }
2045 2062
2046 int get hashCode { 2063 int get hashCode {
2047 int combine(part, current) { 2064 return (_text ?? toString()).hashCode;
2048 // The sum is truncated to 30 bits to make sure it fits into a Smi.
2049 return (current * 31 + part.hashCode) & 0x3FFFFFFF;
2050 }
2051 return combine(scheme, combine(userInfo, combine(host, combine(port,
2052 combine(path, combine(query, combine(fragment, 1)))))));
2053 }
2054
2055 static void _addIfNonEmpty(StringBuffer sb, String test,
2056 String first, String second) {
2057 if ("" != test) {
2058 sb.write(first);
2059 sb.write(second);
2060 }
2061 } 2065 }
2062 2066
2063 /** 2067 /**
2064 * Encode the string [component] using percent-encoding to make it 2068 * Encode the string [component] using percent-encoding to make it
2065 * safe for literal use as a URI component. 2069 * safe for literal use as a URI component.
2066 * 2070 *
2067 * All characters except uppercase and lowercase letters, digits and 2071 * All characters except uppercase and lowercase letters, digits and
2068 * the characters `-_.!~*'()` are percent-encoded. This is the 2072 * the characters `-_.!~*'()` are percent-encoded. This is the
2069 * set of characters specified in RFC 2396 and the which is 2073 * set of characters specified in RFC 2396 and the which is
2070 * specified for the encodeUriComponent in ECMA-262 version 5.1. 2074 * specified for the encodeUriComponent in ECMA-262 version 5.1.
(...skipping 1281 matching lines...) Expand 10 before | Expand all | Expand 10 after
3352 // All non-escape RFC-2396 uric characters. 3356 // All non-escape RFC-2396 uric characters.
3353 // 3357 //
3354 // uric = reserved | unreserved | escaped 3358 // uric = reserved | unreserved | escaped
3355 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | "," 3359 // reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
3356 // unreserved = alphanum | mark 3360 // unreserved = alphanum | mark
3357 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")" 3361 // mark = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
3358 // 3362 //
3359 // This is the same characters as in a URI query (which is URI pchar plus '?') 3363 // This is the same characters as in a URI query (which is URI pchar plus '?')
3360 static const _uricTable = Uri._queryCharTable; 3364 static const _uricTable = Uri._queryCharTable;
3361 } 3365 }
3366
3367 // --------------------------------------------------------------------
3368 // Constants used to read the scanner result.
3369
3370 const int _nopIndex = 0;
3371 const int _schemeEndIndex = 1;
3372 const int _hostStartIndex = 2;
3373 const int _portStartIndex = 3;
3374 const int _pathStartIndex = 4;
3375 const int _queryStartIndex = 5;
3376 const int _fragmentStartIndex = 6;
3377 const int _notSimpleIndex = 7;
3378
3379 // Initial state for scanner.
3380 const int _start = 00;
3381
3382 final _scannerTables = _createTables();
3383
3384 // ----------------------------------------------------------------------
3385 // Code to create the URI scanner table.
3386
3387 const int _schemeOrPath = 01;
3388 const int _authOrPath = 02;
3389 const int _authOrPathSlash = 03;
3390 const int _uinfoOrHost0 = 04;
3391 const int _uinfoOrHost = 05;
3392 const int _uinfoOrPort0 = 06;
3393 const int _uinfoOrPort = 07;
3394 const int _ipv6Host = 08;
3395 const int _relPathSeg = 09;
3396 const int _pathSeg = 10;
3397 const int _path = 11;
3398 const int _query = 12;
3399 const int _fragment = 13;
3400 const int _schemeOrPathDot = 14;
3401 const int _schemeOrPathDot2 = 15;
3402 const int _relPathSegDot = 16;
3403 const int _relPathSegDot2 = 17;
3404 const int _pathSegDot = 18;
3405 const int _pathSegDot2 = 19;
3406
3407 const int _scheme0 = 20;
3408 const int _scheme = 21;
3409
3410 const int _stateCount = 22;
3411 const int _nonSimpleEndStates = 14;
floitsch 2016/06/28 00:28:04 This needs a comment and should be separated from
Lasse Reichstein Nielsen 2016/06/28 13:00:52 Commented and moved. Most of the states have been
3412
3413 const int _nop = _nopIndex << 5;
3414 const int _schemeEnd = _schemeEndIndex << 5;
3415 const int _hostStart = _hostStartIndex << 5;
3416 const int _portStart = _portStartIndex << 5;
3417 const int _pathStart = _pathStartIndex << 5;
3418 const int _queryStart = _queryStartIndex << 5;
3419 const int _fragmentStart = _fragmentStartIndex << 5;
3420 const int _notSimple = _notSimpleIndex << 5;
3421
3422
3423 void _setChars(Uint8List target, String chars, int value) {
floitsch 2016/06/28 00:28:03 Needs dartdocs.
Lasse Reichstein Nielsen 2016/06/28 13:00:51 Done.
3424 for (int i = 0; i < chars.length; i++) {
3425 var char = chars.codeUnitAt(i);
3426 target[char ^ 0x60] = value;
floitsch 2016/06/28 00:28:04 Needs explanation what the ^ 0x60 does.
Lasse Reichstein Nielsen 2016/06/28 13:00:52 Now described on the _scannerTables declaration.
3427 }
3428 }
3429
3430 void _setRange(Uint8List target, String range, int value) {
floitsch 2016/06/28 00:28:03 Needs dartdocs.
Lasse Reichstein Nielsen 2016/06/28 13:00:51 Done.
3431 for (int i = range.codeUnitAt(0), n = range.codeUnitAt(1); i <= n; i++) {
3432 target[i ^ 0x60] = value;
3433 }
3434 }
3435
3436 List<Uint8List> _createTables() {
floitsch 2016/06/28 00:28:04 Needs documentation.
Lasse Reichstein Nielsen 2016/06/28 13:00:52 Done.
3437 // TODO(lrn): Use a precomputed table.
3438 var tables = new List<Uint8List>.generate(_stateCount,
3439 (_) => new Uint8List(96));
3440
3441 const unreserved =
3442 "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-._~" ;
3443 const subDelims = r"!$&'()*+,;=";
3444 const pchar = "$unreserved$subDelims";
floitsch 2016/06/28 00:28:03 what does the "p" stand for?
Lasse Reichstein Nielsen 2016/06/28 13:00:52 This refers to the "pchar" character group in RFC
3445
3446 build(index, defaultValue) =>
3447 tables[index]..fillRange(0, 96, defaultValue);
3448
3449 var b;
3450 // Validate as path, if it is a scheme, we handle it later.
3451 b = build(_start, _schemeOrPath | _notSimple);
3452 _setChars(b, pchar, _schemeOrPath);
3453 _setChars(b, ".", _schemeOrPathDot);
3454 _setChars(b, "%", _schemeOrPath | _notSimple);
3455 _setChars(b, ":", _authOrPath | _schemeEnd); // Handle later.
3456 _setChars(b, "/", _authOrPathSlash);
3457 _setChars(b, "?", _query | _queryStart);
3458 _setChars(b, "#", _fragment | _fragmentStart);
3459
3460 b = build(_schemeOrPathDot, _schemeOrPath | _notSimple);
3461 _setChars(b, pchar, _schemeOrPath);
3462 _setChars(b, ".", _schemeOrPathDot2);
3463 _setChars(b, "%", _schemeOrPath | _notSimple);
3464 _setChars(b, ':', _authOrPath | _schemeEnd);
3465 _setChars(b, "/", _pathSeg | _notSimple);
3466 _setChars(b, "?", _query | _queryStart);
3467 _setChars(b, "#", _fragment | _fragmentStart);
3468
3469 b = build(_schemeOrPathDot2, _schemeOrPath | _notSimple);
3470 _setChars(b, pchar, _schemeOrPath);
3471 _setChars(b, "%", _schemeOrPath | _notSimple);
3472 _setChars(b, ':', _authOrPath | _schemeEnd);
3473 _setChars(b, "/", _relPathSeg);
3474 _setChars(b, "?", _query | _queryStart);
3475 _setChars(b, "#", _fragment | _fragmentStart);
3476
3477 b = build(_schemeOrPath, _schemeOrPath | _notSimple);
3478 _setChars(b, pchar, _schemeOrPath);
3479 _setChars(b, ':', _authOrPath | _schemeEnd);
3480 _setChars(b, "/", _pathSeg);
3481 _setChars(b, "?", _query | _queryStart);
3482 _setChars(b, "#", _fragment | _fragmentStart);
3483
3484 b = build(_authOrPath, _path | _notSimple);
3485 _setChars(b, pchar, _path);
3486 _setChars(b, "/", _authOrPathSlash);
3487 _setChars(b, ".", _pathSegDot);
3488 _setChars(b, "?", _query | _queryStart);
3489 _setChars(b, "#", _fragment | _fragmentStart);
3490
3491 b = build(_authOrPathSlash, _path | _notSimple);
3492 _setChars(b, pchar, _path);
3493 _setChars(b, "/", _uinfoOrHost0 | _hostStart);
3494 _setChars(b, ".", _pathSegDot);
3495 _setChars(b, "?", _query | _queryStart);
3496 _setChars(b, "#", _fragment | _fragmentStart);
3497
3498 b = build(_uinfoOrHost0, _uinfoOrHost | _notSimple);
3499 _setChars(b, pchar, _uinfoOrHost);
3500 _setRange(b, "AZ", _uinfoOrHost | _notSimple);
3501 _setChars(b, ":", _uinfoOrPort0 | _portStart);
3502 _setChars(b, "@", _uinfoOrHost0 | _hostStart);
3503 _setChars(b, "[", _ipv6Host | _notSimple);
3504 _setChars(b, "/", _pathSeg | _pathStart);
3505 _setChars(b, "?", _query | _queryStart);
3506 _setChars(b, "#", _fragment | _fragmentStart);
3507
3508 b = build(_uinfoOrHost, _uinfoOrHost | _notSimple);
3509 _setChars(b, pchar, _uinfoOrHost);
3510 _setRange(b, "AZ", _uinfoOrHost | _notSimple);
3511 _setChars(b, ":", _uinfoOrPort0 | _portStart);
3512 _setChars(b, "@", _uinfoOrHost0 | _hostStart);
3513 _setChars(b, "/", _pathSeg | _pathStart);
3514 _setChars(b, "?", _query | _queryStart);
3515 _setChars(b, "#", _fragment | _fragmentStart);
3516
3517 b = build(_uinfoOrPort0, _uinfoOrPort | _notSimple);
3518 _setChars(b, "0", _uinfoOrPort | _notSimple);
3519 _setRange(b, "19", _uinfoOrPort);
3520 _setChars(b, "@", _uinfoOrHost0 | _hostStart);
3521 _setChars(b, "/", _pathSeg | _pathStart);
3522 _setChars(b, "?", _query | _queryStart);
3523 _setChars(b, "#", _fragment | _fragmentStart);
3524
3525 b = build(_uinfoOrPort, _uinfoOrPort | _notSimple);
3526 _setRange(b, "09", _uinfoOrPort);
3527 _setChars(b, "@", _uinfoOrHost0 | _hostStart);
3528 _setChars(b, "/", _pathSeg | _pathStart);
3529 _setChars(b, "?", _query | _queryStart);
3530 _setChars(b, "#", _fragment | _fragmentStart);
3531
3532 b = build(_ipv6Host, _ipv6Host);
3533 _setChars(b, "]", _uinfoOrHost);
3534
3535 b = build(_relPathSeg, _path | _notSimple);
3536 _setChars(b, pchar, _path);
3537 _setChars(b, ".", _relPathSegDot);
3538 _setChars(b, "/", _pathSeg | _notSimple);
3539 _setChars(b, "?", _query | _queryStart);
3540 _setChars(b, "#", _fragment | _fragmentStart);
3541
3542 b = build(_relPathSegDot, _path | _notSimple);
3543 _setChars(b, pchar, _path);
3544 _setChars(b, ".", _relPathSegDot2);
3545 _setChars(b, "/", _pathSeg | _notSimple);
3546 _setChars(b, "?", _query | _queryStart);
3547 _setChars(b, "#", _fragment | _fragmentStart);
3548
3549 b = build(_relPathSegDot2, _path | _notSimple);
3550 _setChars(b, pchar, _path);
3551 _setChars(b, ".", _path);
3552 _setChars(b, "/", _relPathSeg);
3553 _setChars(b, "?", _query | _queryStart);
3554 _setChars(b, "#", _fragment | _fragmentStart);
3555
3556 b = build(_pathSeg, _path | _notSimple);
3557 _setChars(b, pchar, _path);
3558 _setChars(b, ".", _pathSegDot);
3559 _setChars(b, "/", _pathSeg | _notSimple);
3560 _setChars(b, "?", _query | _queryStart);
3561 _setChars(b, "#", _fragment | _fragmentStart);
3562
3563 b = build(_pathSegDot, _path | _notSimple);
3564 _setChars(b, pchar, _path);
3565 _setChars(b, ".", _pathSegDot2);
3566 _setChars(b, "/", _pathSeg | _notSimple);
3567 _setChars(b, "?", _query | _queryStart);
3568 _setChars(b, "#", _fragment | _fragmentStart);
3569
3570 b = build(_pathSegDot2, _path | _notSimple);
3571 _setChars(b, pchar, _path);
3572 _setChars(b, ".", _path);
3573 _setChars(b, "/", _pathSeg | _notSimple);
3574 _setChars(b, "?", _query | _queryStart);
3575 _setChars(b, "#", _fragment | _fragmentStart);
3576
3577 b = build(_path, _path | _notSimple);
3578 _setChars(b, pchar, _path);
3579 _setChars(b, "/", _pathSeg);
3580 _setChars(b, "?", _query | _queryStart);
3581 _setChars(b, "#", _fragment | _fragmentStart);
3582
3583 b = build(_query, _query | _notSimple);
3584 _setChars(b, pchar, _query);
3585 _setChars(b, "?", _query);
3586 _setChars(b, "#", _fragment | _fragmentStart);
3587
3588 b = build(_fragment, _fragment | _notSimple);
3589 _setChars(b, pchar, _fragment);
3590 _setChars(b, "?", _fragment);
3591
3592 // A separate two-state validator for scheme names.
3593 // Only accepts lower-case letters.
3594 b = build(_scheme0, _scheme | _notSimple);
3595 _setRange(b, "az", _scheme);
3596
3597 b = build(_scheme, _scheme | _notSimple);
3598 _setRange(b, "az", _scheme);
3599 _setRange(b, "09", _scheme);
3600 _setChars(b, "+-.", _scheme);
3601
3602 return tables;
3603 }
3604
3605 // --------------------------------------------------------------------
3606 // Code that uses the URI scanner table.
3607
3608 int _scan(String uri, int start, int end, int state, List<int> indices) {
3609 var tables = _scannerTables;
3610 assert(end <= uri.length);
3611 for (int i = start; i < end; i++) {
3612 var table = tables[state];
3613 // xor 0x60 to move range 0x20-0x7f into 0x00-0x5f
3614 int char = uri.codeUnitAt(i) ^ 0x60;
3615 // use 0x1f (nee 0x7f) to represent all unhandled characters.
floitsch 2016/06/28 00:28:03 Use
Lasse Reichstein Nielsen 2016/06/28 13:00:52 Done.
3616 if (char > 0x5f) char = 0x1f; // TODO: check if negating test is better.
floitsch 2016/06/28 00:28:03 TODO(ldap)
Lasse Reichstein Nielsen 2016/06/28 13:00:52 Or check it. It isn't faster.
3617 int transition = table[char];
3618 state = transition & 0x1f;
3619 indices[transition >> 5] = i;
3620 }
3621 return state;
3622 }
3623
3624 List<int> _scanUri(String uri, int start, int end) {
3625 var indices = new List<int>.filled(8, start - 1);
3626 indices[_portStartIndex] = start; // equals effective pathStart if no auth.
floitsch 2016/06/28 00:28:04 "Equals"
Lasse Reichstein Nielsen 2016/06/28 13:00:51 Done.
3627 indices[_pathStartIndex] = start;
3628 indices[_queryStartIndex] = end;
3629 indices[_fragmentStartIndex] = end;
3630 var state = _scan(uri, start, end, _start, indices);
3631 // Some states that should be non-simple, but the URI ended early.
3632 if (state >= _nonSimpleEndStates) {
3633 indices[_notSimpleIndex] = end;
3634 }
3635 int schemeEnd = indices[_schemeEndIndex];
3636 if (schemeEnd >= start) {
3637 // Rescan the scheme part now that we know it's not a path.
3638 state = _scan(uri, start, schemeEnd, _scheme0, indices);
3639 if (state == _scheme0) {
3640 // Empty scheme.
3641 indices[_notSimpleIndex] = schemeEnd;
3642 }
3643 }
3644 return indices;
3645 }
3646
3647 bool _isUpperCase(int char) {
floitsch 2016/06/28 00:28:04 unused?
Lasse Reichstein Nielsen 2016/06/28 13:00:52 Indeed. Gone.
3648 return 0x41 <= char && char <= 0x5b;
3649 }
3650
3651 class _SimpleUri implements Uri {
3652 final String _uri;
3653 final int _schemeEnd;
3654 final int _hostStart;
3655 final int _portStart;
3656 final int _pathStart;
3657 final int _queryStart;
3658 final int _fragmentStart;
3659 /// The scheme is often used to distinguish URIs.
3660 /// To make comparisons more efficient, we cache the value, and
3661 /// canonicalize a few known types.
3662 String _schemeCache;
3663
3664 _SimpleUri(
3665 this._uri,
3666 this._schemeEnd,
3667 this._hostStart,
3668 this._portStart,
3669 this._pathStart,
3670 this._queryStart,
3671 this._fragmentStart,
3672 this._schemeCache);
3673
3674 bool get hasScheme => _schemeEnd > 0;
3675 bool get hasAuthority => _hostStart > 0;
3676 bool get hasUserInfo => _hostStart > _schemeEnd + 4;
3677 bool get hasPort => _hostStart > 0 && _portStart + 1 < _pathStart;
3678 bool get hasQuery => _queryStart < _fragmentStart;
3679 bool get hasFragment => _fragmentStart < _uri.length;
3680
3681 bool get _isFile => _schemeEnd == 4 && _uri.startsWith("file");
3682 bool get _isHttp => _schemeEnd == 4 && _uri.startsWith("http");
3683 bool get _isHttps => _schemeEnd == 5 && _uri.startsWith("https");
3684 bool get _isPackage => _schemeEnd == 7 && _uri.startsWith("package");
3685 bool _isScheme(String scheme) =>
3686 _schemeEnd == scheme.length && _uri.startsWith(scheme);
3687
3688 bool get hasAbsolutePath => _uri.startsWith("/", _pathStart);
3689 bool get hasEmptyPath => _pathStart == _queryStart;
3690
3691 bool get isAbsolute => hasScheme && !hasFragment;
3692
3693 String get scheme {
3694 if (_schemeEnd <= 0) return "";
3695 if (_schemeCache != null) return _schemeCache;
3696 if (_isHttp) {
3697 _schemeCache = "http";
3698 } else if (_isHttps) {
3699 _schemeCache = "https";
3700 } else if (_isFile) {
3701 _schemeCache = "file";
3702 } else if (_isPackage) {
3703 _schemeCache = "package";
3704 } else {
3705 _schemeCache = _uri.substring(0, _schemeEnd);
3706 }
3707 return _schemeCache;
3708 }
3709 String get authority => _hostStart > 0 ?
3710 _uri.substring(_schemeEnd + 3, _pathStart) : "";
3711 String get userInfo => (_hostStart > _schemeEnd + 3) ?
3712 _uri.substring(_schemeEnd + 3, _hostStart - 1) : "";
3713 String get host =>
3714 _hostStart > 0 ? _uri.substring(_hostStart, _portStart) : "";
3715 int get port {
3716 if (hasPort) return int.parse(_uri.substring(_portStart + 1, _pathStart));
3717 if (_isHttp) return 80;
3718 if (_isHttps) return 443;
3719 return 0;
3720 }
3721 String get path =>_uri.substring(_pathStart, _queryStart);
3722 String get query => (_queryStart < _fragmentStart) ?
3723 _uri.substring(_queryStart + 1, _fragmentStart) : "";
3724 String get fragment => (_fragmentStart < _uri.length) ?
3725 _uri.substring(_fragmentStart + 1) : "";
3726
3727 String get origin {
3728 // Check original behavior - W3C spec is wonky!
3729 bool isHttp = _isHttp;
3730 if (_schemeEnd < 0 || _hostStart == _portStart) {
3731 throw new StateError("Cannot use origin without a scheme: $this");
3732 }
3733 if (!isHttp && !_isHttps) {
3734 throw new StateError(
3735 "Origin is only applicable schemes http and https: $this");
3736 }
3737 if (_hostStart == _schemeEnd + 3) {
3738 return _uri.substring(0, _pathStart);
3739 }
3740 // Need to drop anon-empty userInfo.
3741 return _uri.substring(0, _schemeEnd + 3) +
3742 _uri.substring(_hostStart, _pathStart);
3743 }
3744
3745 List<String> get pathSegments {
3746 int start = _pathStart;
3747 int end = _queryStart;
3748 if (_uri.startsWith("/", start)) start++;
3749 if (start == end) return const <String>[];
3750 List<String> parts = [];
3751 for (int i = start; i < end; i++) {
3752 var char = _uri.codeUnitAt(i);
3753 if (char == Uri._SLASH) {
3754 parts.add(_uri.substring(start, i));
3755 start = i + 1;
3756 }
3757 }
3758 parts.add(_uri.substring(start, end));
3759 return new List<String>.unmodifiable(parts);
3760 }
3761
3762 Map<String, String> get queryParameters {
3763 if (!hasQuery) return const <String, String>{};
3764 return new UnmodifiableMapView<String, String>(
3765 Uri.splitQueryString(query));
3766 }
3767
3768 Map<String, List<String>> get queryParametersAll {
3769 if (!hasQuery) return const <String, List<String>>{};
3770 Map queryParameterLists = Uri._splitQueryStringAll(query);
3771 for (var key in queryParameterLists.keys) {
3772 queryParameterLists[key] =
3773 new List<String>.unmodifiable(queryParameterLists[key]);
3774 }
3775 return new Map<String, List<String>>.unmodifiable(queryParameterLists);
3776 }
3777
3778 bool _isPort(String port) {
3779 int portDigitStart = _portStart + 1;
3780 return portDigitStart + port.length == _pathStart &&
3781 _uri.startsWith(port, portDigitStart);
3782 }
3783
3784 Uri normalizePath() => this;
3785
3786 Uri removeFragment() {
3787 if (!hasFragment) return this;
3788 return new _SimpleUri(
3789 _uri.substring(0, _fragmentStart),
3790 _schemeEnd, _hostStart, _portStart,
3791 _pathStart, _queryStart, _fragmentStart, _schemeCache);
3792 }
3793
3794 Uri replace({String scheme,
3795 String userInfo,
3796 String host,
3797 int port,
3798 String path,
3799 Iterable<String> pathSegments,
3800 String query,
3801 Map<String, dynamic/*String|Iterable<String>*/> queryParameters,
3802 String fragment}) {
3803 bool schemeChanged = false;
3804 if (scheme != null) {
3805 scheme = Uri._makeScheme(scheme, 0, scheme.length);
3806 schemeChanged = !_isScheme(scheme);
3807 } else {
3808 scheme = this.scheme;
3809 }
3810 bool isFile = (scheme == "file");
3811 if (userInfo != null) {
3812 userInfo = Uri._makeUserInfo(userInfo, 0, userInfo.length);
3813 } else if (_hostStart > 0) {
3814 userInfo = _uri.substring(_schemeEnd + 3, _hostStart);
3815 } else {
3816 userInfo = "";
3817 }
3818 if (port != null) {
3819 port = Uri._makePort(port, scheme);
3820 } else {
3821 port = this.hasPort ? this.port : null;
3822 if (schemeChanged) {
3823 // The default port might have changed.
3824 port = Uri._makePort(port, scheme);
3825 }
3826 }
3827 if (host != null) {
3828 host = Uri._makeHost(host, 0, host.length, false);
3829 } else if (_hostStart > 0) {
3830 host = _uri.substring(_hostStart, _portStart);
3831 } else if (userInfo.isNotEmpty || port != null || isFile) {
3832 host = "";
3833 }
3834
3835 bool hasAuthority = host != null;
3836 if (path != null || pathSegments != null) {
3837 path = Uri._makePath(path, 0, _stringOrNullLength(path), pathSegments,
3838 scheme, hasAuthority);
3839 } else {
3840 path = _uri.substring(_pathStart, _queryStart);
3841 if ((isFile || (hasAuthority && !path.isEmpty)) &&
3842 !path.startsWith('/')) {
3843 path = "/" + path;
3844 }
3845 }
3846
3847 if (query != null || queryParameters != null) {
3848 query =
3849 Uri._makeQuery(query, 0, _stringOrNullLength(query), queryParameters);
3850 } else if (_queryStart < _fragmentStart) {
3851 query = _uri.substring(_queryStart, _fragmentStart);
3852 }
3853
3854 if (fragment != null) {
3855 fragment = Uri._makeFragment(fragment, 0, fragment.length);
3856 } else if (_fragmentStart < _uri.length) {
3857 fragment = _uri.substring(_fragmentStart);
3858 }
3859
3860 return new Uri._internal(
3861 scheme, userInfo, host, port, path, query, fragment);
3862 }
3863
3864 Uri resolve(String reference) {
3865 return resolveUri(Uri.parse(reference));
3866 }
3867
3868 Uri resolveUri(Uri reference) {
3869 if (reference is _SimpleUri) {
3870 return _simpleMerge(this, reference);
3871 }
3872 return _toNonSimple().resolveUri(reference);
3873 }
3874
3875 // Merge two simple URIs. This should always result in a prefix of
3876 // one concatentated with a suffix of the other, which is again simple.
3877 // In a few cases, there might be a need for extra normalization, when
3878 // resolving on top of a known scheme.
3879 Uri _simpleMerge(_SimpleUri base, _SimpleUri ref) {
3880 if (ref.hasScheme) return ref;
3881 if (ref.hasAuthority) {
3882 if (!base.hasScheme) return ref;
3883 bool isSimple = true;
3884 if (base._isFile) {
3885 isSimple = !ref.hasEmptyPath;
3886 } else if (base._isHttp) {
3887 isSimple = !ref._isPort("80");
3888 } else if (base._isHttps) {
3889 isSimple = !ref._isPort("443");
3890 }
3891 if (isSimple) {
3892 var delta = base._schemeEnd + 1;
3893 var newUri = base._uri.substring(0, base._schemeEnd + 1) +
3894 ref._uri.substring(ref._schemeEnd + 1);
3895 return new _SimpleUri(newUri,
3896 base._schemeEnd,
3897 ref._hostStart + delta,
3898 ref._portStart + delta,
3899 ref._pathStart + delta,
3900 ref._queryStart + delta,
3901 ref._fragmentStart + delta,
3902 base._schemeCache);
3903 } else {
3904 // Slowcase.
3905 return _toNonSimple().resolveUri(ref);
3906 }
3907 }
3908 if (ref.hasEmptyPath) {
3909 if (ref.hasQuery) {
3910 int delta = base._queryStart - ref._queryStart;
3911 var newUri = base._uri.substring(0, base._queryStart) +
3912 ref._uri.substring(ref._queryStart);
3913 return new _SimpleUri(newUri,
3914 base._schemeEnd,
3915 base._hostStart,
3916 base._portStart,
3917 base._pathStart,
3918 ref._queryStart + delta,
3919 ref._fragmentStart + delta,
3920 base._schemeCache);
3921 }
3922 if (ref.hasFragment) {
3923 int delta = base._fragmentStart - ref._fragmentStart;
3924 var newUri = base._uri.substring(0, base._fragmentStart) +
3925 ref._uri.substring(ref._fragmentStart);
3926 return new _SimpleUri(newUri,
3927 base._schemeEnd,
3928 base._hostStart,
3929 base._portStart,
3930 base._pathStart,
3931 base._queryStart,
3932 ref._fragmentStart + delta,
3933 base._schemeCache);
3934 }
3935 return base.removeFragment();
3936 }
3937 if (ref.hasAbsolutePath) {
3938 var delta = base._pathStart - ref._pathStart;
3939 var newUri = base._uri.substring(0, base._pathStart) +
3940 ref._uri.substring(ref._pathStart);
3941 return new _SimpleUri(newUri,
3942 base._schemeEnd,
3943 base._hostStart,
3944 base._portStart,
3945 base._pathStart,
3946 ref._queryStart + delta,
3947 ref._fragmentStart + delta,
3948 base._schemeCache);
3949 }
3950 if (base.hasEmptyPath && base.hasAuthority) {
3951 // ref has relative non-empty path.
3952 var delta = base._pathStart - ref._pathStart + 1;
3953 var newUri = "${base._uri.substring(0, base._pathStart)}/"
3954 "${ref._uri.substring(ref._pathStart)}";
3955 return new _SimpleUri(newUri,
3956 base._schemeEnd,
3957 base._hostStart,
3958 base._portStart,
3959 base._pathStart,
3960 ref._queryStart + delta,
3961 ref._fragmentStart + delta,
3962 base._schemeCache);
3963 }
3964 // Merge paths.
3965 if (base._uri.startsWith("../", base._pathStart)) {
3966 // Complex rare case, go slow.
3967 return _toNonSimple().resolveUri(ref);
3968 }
3969 String baseUri = base._uri;
3970 String refUri = ref._uri;
3971 int baseStart = base._pathStart;
3972 int baseEnd = base._queryStart;
3973 int refStart = ref._pathStart;
3974 int refEnd = ref._queryStart;
3975 int backCount = 1;
3976 while (refStart + 3 <= refEnd && refUri.startsWith("../", refStart)) {
3977 refStart += 3;
3978 backCount += 1;
3979 }
3980 if (refStart + 2 == refEnd && refUri.startsWith("..", refStart)) {
3981 refStart += 2;
3982 backCount += 1;
3983 }
3984
3985 const slash = 0x2f;
3986 while (baseEnd > baseStart) {
3987 baseEnd--;
3988 int char = baseUri.codeUnitAt(baseEnd);
3989 if (char == slash) {
3990 backCount--;
3991 if (backCount == 0) break;
3992 }
3993 }
3994 var delta;
3995 var newUri;
3996 if (baseEnd != baseStart || base._uri.startsWith("/", baseStart) ||
3997 base.hasScheme || base.hasAuthority) {
3998 delta = baseEnd - refStart + 1;
3999 newUri = "${base._uri.substring(0, baseEnd)}/"
4000 "${ref._uri.substring(refStart)}";
4001 } else {
4002 // We exactly removed all of a relative path.
4003 // Example: foo:bar/baz resolve vs. ../../qux -> foo:qux
4004 delta = baseEnd - refStart;
4005 newUri = "${base._uri.substring(0, baseStart)}"
4006 "${ref._uri.substring(refStart)}";
4007 }
4008
4009 return new _SimpleUri(newUri,
4010 base._schemeEnd,
4011 base._hostStart,
4012 base._portStart,
4013 base._pathStart,
4014 ref._queryStart + delta,
4015 ref._fragmentStart + delta,
4016 base._schemeCache);
4017 }
4018
4019 // TODO: Deprecate Remove Kill Crush Destroy!
floitsch 2016/06/28 00:28:03 TODO(ldap). Also, make this more informative.
Lasse Reichstein Nielsen 2016/06/28 13:00:52 Done. Whoops. :)
4020 // Reason: requires knowing whether we are on Windows.
4021 // That's a PLATFORM SPECIFIC THING.
4022 // This should be File.pathFromUri, since File is platform specific already.
4023 String toFilePath({bool windows}) {
4024 if (_schemeEnd >= 0 && !_isFile) {
4025 throw new UnsupportedError(
4026 "Cannot extract a file path from a $scheme URI");
4027 }
4028 if (_queryStart < _uri.length) {
4029 if (_queryStart < _fragmentStart) {
4030 throw new UnsupportedError(
4031 "Cannot extract a file path from a URI with a query component");
4032 }
4033 throw new UnsupportedError(
4034 "Cannot extract a file path from a URI with a fragment component");
4035 }
4036 if (windows == null) windows = Uri._isWindows;
4037 return windows ? Uri._toWindowsFilePath(this) : _toFilePath();
4038 }
4039
4040 String _toFilePath() {
4041 if (_hostStart < _portStart) {
4042 // Has authority and non-empty host.
4043 throw new UnsupportedError(
4044 "Cannot extract a non-Windows file path from a file URI "
4045 "with an authority");
4046 }
4047 return this.path;
4048 }
4049
4050 UriData get data {
4051 assert(scheme != "data");
4052 return null;
4053 }
4054
4055 int get hashCode => _uri.hashCode;
4056
4057 bool operator==(Object other) {
4058 if (other is! Uri) return false;
4059 return _uri == other.toString();
4060 }
4061
4062 Uri _toNonSimple() {
4063 return new Uri._internal(
4064 this.scheme,
4065 this.userInfo,
4066 this.hasAuthority ? this.host: null,
4067 this.hasPort ? this.port : null,
4068 this.path,
4069 this.hasQuery ? this.query : null,
4070 this.hasFragment ? this.fragment : null
4071 );
4072 }
4073
4074 String toString() => _uri;
4075 }
OLDNEW
« no previous file with comments | « pkg/analyzer/lib/src/util/fast_uri.dart ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698