OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 } | |
OLD | NEW |