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 |