Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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 library path.context; | 5 library path.context; |
| 6 | 6 |
| 7 import 'characters.dart' as chars; | 7 import 'characters.dart' as chars; |
| 8 import 'internal_style.dart'; | 8 import 'internal_style.dart'; |
| 9 import 'style.dart'; | 9 import 'style.dart'; |
| 10 import 'parsed_path.dart'; | 10 import 'parsed_path.dart'; |
| (...skipping 393 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 404 /// a context with a relative path for [current]. | 404 /// a context with a relative path for [current]. |
| 405 /// | 405 /// |
| 406 /// var context = new Context(r'some/relative/path'); | 406 /// var context = new Context(r'some/relative/path'); |
| 407 /// context.relative(r'/absolute/path'); // -> '/absolute/path' | 407 /// context.relative(r'/absolute/path'); // -> '/absolute/path' |
| 408 /// | 408 /// |
| 409 /// If [root] is relative, it may be impossible to determine a path from | 409 /// If [root] is relative, it may be impossible to determine a path from |
| 410 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is | 410 /// [from] to [path]. For example, if [root] and [path] are "." and [from] is |
| 411 /// "/", no path can be determined. In this case, a [PathException] will be | 411 /// "/", no path can be determined. In this case, a [PathException] will be |
| 412 /// thrown. | 412 /// thrown. |
| 413 String relative(String path, {String from}) { | 413 String relative(String path, {String from}) { |
| 414 // Avoid calling [current] since it is slow and calling join() when | 414 from = from == null ? current : absolute(from); |
| 415 // [from] is absolute does nothing. | |
| 416 if (from == null) { | |
| 417 from = current; | |
| 418 } else if (this.isRelative(from) || this.isRootRelative(from)) { | |
| 419 from = this.join(current, from); | |
| 420 } | |
| 421 | 415 |
| 422 // We can't determine the path from a relative path to an absolute path. | 416 // We can't determine the path from a relative path to an absolute path. |
| 423 if (this.isRelative(from) && this.isAbsolute(path)) { | 417 if (this.isRelative(from) && this.isAbsolute(path)) { |
| 424 return this.normalize(path); | 418 return this.normalize(path); |
| 425 } | 419 } |
| 426 | 420 |
| 427 // If the given path is relative, resolve it relative to the context's | 421 // If the given path is relative, resolve it relative to the context's |
| 428 // current directory. | 422 // current directory. |
| 429 if (this.isRelative(path) || this.isRootRelative(path)) { | 423 if (this.isRelative(path) || this.isRootRelative(path)) { |
| 430 path = this.absolute(path); | 424 path = this.absolute(path); |
| (...skipping 65 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 496 return pathParsed.toString(); | 490 return pathParsed.toString(); |
| 497 } | 491 } |
| 498 | 492 |
| 499 /// Returns `true` if [child] is a path beneath `parent`, and `false` | 493 /// Returns `true` if [child] is a path beneath `parent`, and `false` |
| 500 /// otherwise. | 494 /// otherwise. |
| 501 /// | 495 /// |
| 502 /// path.isWithin('/root/path', '/root/path/a'); // -> true | 496 /// path.isWithin('/root/path', '/root/path/a'); // -> true |
| 503 /// path.isWithin('/root/path', '/root/other'); // -> false | 497 /// path.isWithin('/root/path', '/root/other'); // -> false |
| 504 /// path.isWithin('/root/path', '/root/path'); // -> false | 498 /// path.isWithin('/root/path', '/root/path'); // -> false |
| 505 bool isWithin(String parent, String child) { | 499 bool isWithin(String parent, String child) { |
| 500 // Make both paths the same level of relative. We're only able to do the | |
| 501 // quick comparison if both paths are in the same format, and making a path | |
| 502 // absolute is faster than making it relative. | |
| 503 var parentIsAbsolute = isAbsolute(parent); | |
| 504 var childIsAbsolute = isAbsolute(child); | |
| 505 if (parentIsAbsolute && !childIsAbsolute) { | |
| 506 child = absolute(child); | |
| 507 if (style.isRootRelative(parent)) parent = absolute(parent); | |
| 508 } else if (childIsAbsolute && !parentIsAbsolute) { | |
| 509 parent = absolute(parent); | |
| 510 if (style.isRootRelative(child)) child = absolute(child); | |
| 511 } else if (childIsAbsolute && parentIsAbsolute) { | |
| 512 var childIsRootRelative = style.isRootRelative(child); | |
| 513 var parentIsRootRelative = style.isRootRelative(parent); | |
| 514 | |
| 515 if (childIsRootRelative && !parentIsRootRelative) { | |
| 516 child = absolute(child); | |
| 517 } else if (parentIsRootRelative && !childIsRootRelative) { | |
| 518 parent = absolute(parent); | |
| 519 } | |
| 520 } | |
| 521 | |
| 522 var fastResult = _isWithinFast(parent, child); | |
| 523 if (fastResult != null) return fastResult; | |
| 524 | |
| 506 var relative; | 525 var relative; |
| 507 try { | 526 try { |
| 508 relative = this.relative(child, from: parent); | 527 relative = this.relative(child, from: parent); |
| 509 } on PathException catch (_) { | 528 } on PathException catch (_) { |
| 510 // If no relative path from [parent] to [child] is found, [child] | 529 // If no relative path from [parent] to [child] is found, [child] |
| 511 // definitely isn't a child of [parent]. | 530 // definitely isn't a child of [parent]. |
| 512 return false; | 531 return false; |
| 513 } | 532 } |
| 514 | 533 |
| 515 var parts = this.split(relative); | 534 var parts = this.split(relative); |
| 516 return this.isRelative(relative) && | 535 return this.isRelative(relative) && |
| 517 parts.first != '..' && | 536 parts.first != '..' && |
| 518 parts.first != '.'; | 537 parts.first != '.'; |
| 519 } | 538 } |
| 520 | 539 |
| 540 /// An optimized implementation of [isWithin] that doesn't handle a few | |
| 541 /// complex cases. | |
| 542 bool _isWithinFast(String parent, String child) { | |
|
Bob Nystrom
2015/11/24 17:22:13
Possibly dumb question. How much mileage would we
nweiz
2015/12/01 00:15:15
That gives a false positive for isWithin("/foo/bar
| |
| 543 // Normally we just bail when we see "." path components, but we can handle | |
| 544 // a single dot easily enough. | |
| 545 if (parent == '.') parent = ''; | |
| 546 | |
| 547 var parentRootLength = style.rootLength(parent); | |
| 548 var childRootLength = style.rootLength(child); | |
| 549 | |
| 550 // If the roots aren't the same length, we know both paths are absolute or | |
| 551 // both are root-relative, and thus that the roots are meaningfully | |
| 552 // different. | |
| 553 // | |
| 554 // isWithin("C:/bar", "//foo/bar/baz") //=> false | |
| 555 // isWithin("http://example.com/", "http://google.com/bar") //=> false | |
| 556 if (parentRootLength != childRootLength) return false; | |
| 557 | |
| 558 var parentCodeUnits = parent.codeUnits; | |
| 559 var childCodeUnits = child.codeUnits; | |
| 560 | |
| 561 // Make sure that the roots are textually the same as well. | |
| 562 // | |
| 563 // isWithin("C:/bar", "D:/bar/baz") //=> false | |
| 564 // isWithin("http://example.com/", "http://example.org/bar") //=> false | |
| 565 for (var i = 0; i < parentRootLength; i++) { | |
| 566 var parentCodeUnit = parentCodeUnits[i]; | |
| 567 var childCodeUnit = childCodeUnits[i]; | |
| 568 if (parentCodeUnit == childCodeUnit) continue; | |
| 569 | |
| 570 // If both code units are separators, that's fine too. | |
| 571 // | |
| 572 // isWithin("C:/", r"C:\foo") //=> true | |
| 573 if (!style.isSeparator(parentCodeUnit) || | |
| 574 !style.isSeparator(childCodeUnit)) { | |
| 575 return false; | |
| 576 } | |
| 577 } | |
| 578 | |
| 579 // Start by considering the last code unit as a separator, since | |
| 580 // semantically we're starting at a new path component even if we're | |
| 581 // comparing relative paths. | |
| 582 var lastCodeUnit = chars.SLASH; | |
| 583 | |
| 584 // Iterate through both paths as long as they're semantically identical. | |
| 585 var parentIndex = parentRootLength; | |
| 586 var childIndex = childRootLength; | |
| 587 while (parentIndex < parent.length && childIndex < child.length) { | |
| 588 var parentCodeUnit = parentCodeUnits[parentIndex]; | |
| 589 var childCodeUnit = childCodeUnits[childIndex]; | |
| 590 if (parentCodeUnit == childCodeUnit) { | |
| 591 lastCodeUnit = parentCodeUnit; | |
| 592 parentIndex++; | |
| 593 childIndex++; | |
| 594 continue; | |
| 595 } | |
| 596 | |
| 597 // Different separators are considered identical. | |
| 598 var parentIsSeparator = style.isSeparator(parentCodeUnit); | |
| 599 var childIsSeparator = style.isSeparator(childCodeUnit); | |
| 600 if (parentIsSeparator && childIsSeparator) { | |
| 601 lastCodeUnit = parentCodeUnit; | |
| 602 parentIndex++; | |
| 603 childIndex++; | |
| 604 continue; | |
| 605 } | |
| 606 | |
| 607 // Ignore multiple separators in a row. | |
| 608 if (parentIsSeparator && style.isSeparator(lastCodeUnit)) { | |
| 609 parentIndex++; | |
| 610 continue; | |
| 611 } else if (childIsSeparator && style.isSeparator(lastCodeUnit)) { | |
| 612 childIndex++; | |
| 613 continue; | |
| 614 } | |
| 615 | |
| 616 // If a dot comes after a separator or another dot, it may be a | |
| 617 // directory traversal operator. Otherwise, it's just a normal | |
| 618 // non-matching character. | |
| 619 // | |
| 620 // isWithin("foo/./bar", "foo/bar/baz") //=> true | |
| 621 // isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false | |
| 622 // | |
| 623 // We could stay on the fast path for "/./", but that adds a lot of | |
| 624 // complexity and isn't likely to come up much in practice. | |
| 625 if ((parentCodeUnit == chars.PERIOD || childCodeUnit == chars.PERIOD) && | |
| 626 (style.isSeparator(lastCodeUnit) || lastCodeUnit == chars.PERIOD)) { | |
| 627 return null; | |
| 628 } | |
| 629 | |
| 630 // If we're here, we've hit two non-matching, non-significant characters. | |
| 631 // As long as the remainders of the two paths don't have any unresolved | |
| 632 // ".." components, we can be confident that [child] is not within | |
| 633 // [parent]. | |
| 634 if (_checkRemainder(childCodeUnits, childIndex) < 1) return null; | |
| 635 if (_checkRemainder(parentCodeUnits, parentIndex) < 1) return null; | |
| 636 return false; | |
| 637 } | |
| 638 | |
| 639 // If the child is shorter than the parent, it's probably not within the | |
| 640 // parent. The only exception is if the parent has some weird ".." stuff | |
| 641 // going on, in which case we do the slow check. | |
| 642 // | |
| 643 // isWithin("foo/bar/baz", "foo/bar") //=> false | |
| 644 // isWithin("foo/bar/baz/../..", "foo/bar") //=> true | |
| 645 if (childIndex == child.length) { | |
| 646 var result = _checkRemainder(parentCodeUnits, parentIndex); | |
| 647 return result < 0 ? null : false; | |
| 648 } | |
| 649 | |
| 650 // We've reached the end of the parent path, which means it's time to make a | |
| 651 // decision. Before we do, though, we'll check the rest of the child to see | |
| 652 // what that tells us. | |
| 653 var result = _checkRemainder(childCodeUnits, childIndex); | |
| 654 | |
| 655 // If there are no more components in the child, then it's the same as | |
| 656 // the parent, not within it. | |
| 657 // | |
| 658 // isWithin("foo/bar", "foo/bar") //=> false | |
| 659 // isWithin("foo/bar", "foo/bar//") //=> false | |
| 660 if (result == 0) return false; | |
| 661 | |
| 662 // If there are unresolved ".." components in the child, no decision we make | |
| 663 // will be valid. We'll abort and do the slow check instead. | |
| 664 // | |
| 665 // isWithin("foo/bar", "foo/bar/..") //=> false | |
| 666 // isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false | |
| 667 // isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true | |
| 668 if (result < 0) return null; | |
| 669 | |
| 670 // The child is within the parent if and only if we're on a separator | |
| 671 // boundary. | |
| 672 // | |
| 673 // isWithin("foo/bar", "foo/bar/baz") //=> true | |
| 674 // isWithin("foo/bar/", "foo/bar/baz") //=> true | |
| 675 // isWithin("foo/bar", "foo/barbaz") //=> false | |
| 676 return style.isSeparator(childCodeUnits[childIndex]) || | |
| 677 style.isSeparator(lastCodeUnit); | |
| 678 } | |
| 679 | |
| 680 // Returns the information about the path represented by [codeUnits] after | |
| 681 // [index]. | |
| 682 // | |
| 683 // Specifically, this returns: | |
| 684 // | |
| 685 // * A negative number if the path contains ".." components that at any point | |
| 686 // cause the directory to go above the root. | |
| 687 // | |
| 688 // * Zero if the path has no components, or if it contains ".." that at any | |
| 689 // point cause the directory to go back to the root (but not above it). | |
| 690 // | |
| 691 // * A positive number otherwise. | |
| 692 // | |
| 693 // This ignores leading separators. | |
| 694 // | |
| 695 // checkRemainder("foo") //=> + | |
| 696 // checkRemainder("foo/bar/../baz") //=> + | |
| 697 // checkRemainder("//foo/bar/baz") //=> + | |
| 698 // checkRemainder("/") //=> 0 | |
| 699 // checkRemainder("foo/../baz") //=> 0 | |
| 700 // checkRemainder("foo/../..") //=> - | |
| 701 // checkRemainder("foo/../../foo/bar/baz") //=> - | |
| 702 int _checkRemainder(List<int> codeUnits, int index) { | |
|
Bob Nystrom
2015/11/24 17:22:13
How about _pathDirection()? Something other than "
nweiz
2015/12/01 00:15:15
Done.
| |
| 703 var depth = 0; | |
| 704 | |
| 705 // We initially consider ourselves to be after an invisible root separator. | |
| 706 var afterSeparator = true; | |
| 707 for (var i = index; i < codeUnits.length; i++) { | |
| 708 var codeUnit = codeUnits[i]; | |
| 709 | |
| 710 if (style.isSeparator(codeUnit)) { | |
| 711 // Ignore doubled separators (and initial separators). | |
| 712 if (!afterSeparator) depth++; | |
| 713 afterSeparator = true; | |
| 714 continue; | |
| 715 } | |
| 716 | |
| 717 // Ignore non-meaningful characters. | |
| 718 if (codeUnit != chars.PERIOD || !afterSeparator) { | |
| 719 afterSeparator = false; | |
| 720 continue; | |
| 721 } | |
| 722 | |
| 723 // Move forward so we're positioned after "/.". | |
| 724 i++; | |
| 725 | |
| 726 if (i == codeUnits.length) return depth; | |
| 727 codeUnit = codeUnits[i]; | |
| 728 | |
| 729 // Ignore "/./", and don't increment the depth for the trailing slash. | |
| 730 if (style.isSeparator(codeUnit)) continue; | |
| 731 | |
| 732 // "/.foo" isn't anything meaningful. | |
| 733 if (codeUnit != chars.PERIOD) { | |
| 734 afterSeparator = false; | |
| 735 continue; | |
| 736 } | |
| 737 | |
| 738 // Move forward again so we're positioned after "/..". | |
| 739 i++; | |
| 740 | |
| 741 if (i == codeUnits.length) return depth - 1; | |
| 742 codeUnit = codeUnits[i]; | |
| 743 | |
| 744 // "/../" decreases depth. | |
| 745 if (style.isSeparator(codeUnit)) { | |
| 746 depth--; | |
| 747 if (depth == 0) return 0; | |
| 748 if (depth < 0) return depth; | |
| 749 continue; | |
| 750 } | |
| 751 | |
| 752 // "/..foo" isn't anything meaningful. | |
| 753 afterSeparator = false; | |
| 754 } | |
|
Bob Nystrom
2015/11/24 17:22:13
Instead of a for loop with a sort of state machine
nweiz
2015/12/01 00:15:15
Done. I also realized that there were some bugs in
| |
| 755 | |
| 756 // If the path didn't have a trailing separator, add another unit of depth | |
| 757 // to account for the current component. We have to do this because depth is counted | |
|
Bob Nystrom
2015/11/24 17:22:13
Long line.
nweiz
2015/12/01 00:15:15
Acknowledged.
| |
| 758 // on each trailing separator. | |
| 759 return afterSeparator ? depth : depth + 1; | |
| 760 } | |
| 761 | |
| 521 /// Removes a trailing extension from the last part of [path]. | 762 /// Removes a trailing extension from the last part of [path]. |
| 522 /// | 763 /// |
| 523 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' | 764 /// context.withoutExtension('path/to/foo.dart'); // -> 'path/to/foo' |
| 524 String withoutExtension(String path) { | 765 String withoutExtension(String path) { |
| 525 var parsed = _parse(path); | 766 var parsed = _parse(path); |
| 526 | 767 |
| 527 for (var i = parsed.parts.length - 1; i >= 0; i--) { | 768 for (var i = parsed.parts.length - 1; i >= 0; i--) { |
| 528 if (!parsed.parts[i].isEmpty) { | 769 if (!parsed.parts[i].isEmpty) { |
| 529 parsed.parts[i] = parsed.basenameWithoutExtension; | 770 parsed.parts[i] = parsed.basenameWithoutExtension; |
| 530 break; | 771 break; |
| (...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 643 var message = new StringBuffer(); | 884 var message = new StringBuffer(); |
| 644 message.write("$method("); | 885 message.write("$method("); |
| 645 message.write(args | 886 message.write(args |
| 646 .take(numArgs) | 887 .take(numArgs) |
| 647 .map((arg) => arg == null ? "null" : '"$arg"') | 888 .map((arg) => arg == null ? "null" : '"$arg"') |
| 648 .join(", ")); | 889 .join(", ")); |
| 649 message.write("): part ${i - 1} was null, but part $i was not."); | 890 message.write("): part ${i - 1} was null, but part $i was not."); |
| 650 throw new ArgumentError(message.toString()); | 891 throw new ArgumentError(message.toString()); |
| 651 } | 892 } |
| 652 } | 893 } |
| OLD | NEW |