_PathRelation _isWithinOrEqualsFast()

in lib/src/context.dart [616:809]


  _PathRelation _isWithinOrEqualsFast(String parent, String child) {
    // Normally we just bail when we see "." path components, but we can handle
    // a single dot easily enough.
    if (parent == '.') parent = '';

    final parentRootLength = style.rootLength(parent);
    final childRootLength = style.rootLength(child);

    // If the roots aren't the same length, we know both paths are absolute or
    // both are root-relative, and thus that the roots are meaningfully
    // different.
    //
    //     isWithin("C:/bar", "//foo/bar/baz") //=> false
    //     isWithin("http://example.com/", "http://google.com/bar") //=> false
    if (parentRootLength != childRootLength) return _PathRelation.different;

    // Make sure that the roots are textually the same as well.
    //
    //     isWithin("C:/bar", "D:/bar/baz") //=> false
    //     isWithin("http://example.com/", "http://example.org/bar") //=> false
    for (var i = 0; i < parentRootLength; i++) {
      final parentCodeUnit = parent.codeUnitAt(i);
      final childCodeUnit = child.codeUnitAt(i);
      if (!style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) {
        return _PathRelation.different;
      }
    }

    // Start by considering the last code unit as a separator, since
    // semantically we're starting at a new path component even if we're
    // comparing relative paths.
    var lastCodeUnit = chars.slash;

    /// The index of the last separator in [parent].
    int? lastParentSeparator;

    // Iterate through both paths as long as they're semantically identical.
    var parentIndex = parentRootLength;
    var childIndex = childRootLength;
    while (parentIndex < parent.length && childIndex < child.length) {
      var parentCodeUnit = parent.codeUnitAt(parentIndex);
      var childCodeUnit = child.codeUnitAt(childIndex);
      if (style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) {
        if (style.isSeparator(parentCodeUnit)) {
          lastParentSeparator = parentIndex;
        }

        lastCodeUnit = parentCodeUnit;
        parentIndex++;
        childIndex++;
        continue;
      }

      // Ignore multiple separators in a row.
      if (style.isSeparator(parentCodeUnit) &&
          style.isSeparator(lastCodeUnit)) {
        lastParentSeparator = parentIndex;
        parentIndex++;
        continue;
      } else if (style.isSeparator(childCodeUnit) &&
          style.isSeparator(lastCodeUnit)) {
        childIndex++;
        continue;
      }

      // If a dot comes after a separator, it may be a directory traversal
      // operator. To check that, we need to know if it's followed by either
      // "/" or "./". Otherwise, it's just a normal non-matching character.
      //
      //     isWithin("foo/./bar", "foo/bar/baz") //=> true
      //     isWithin("foo/bar/../baz", "foo/bar/.foo") //=> false
      if (parentCodeUnit == chars.period && style.isSeparator(lastCodeUnit)) {
        parentIndex++;

        // We've hit "/." at the end of the parent path, which we can ignore,
        // since the paths were equivalent up to this point.
        if (parentIndex == parent.length) break;
        parentCodeUnit = parent.codeUnitAt(parentIndex);

        // We've hit "/./", which we can ignore.
        if (style.isSeparator(parentCodeUnit)) {
          lastParentSeparator = parentIndex;
          parentIndex++;
          continue;
        }

        // We've hit "/..", which may be a directory traversal operator that
        // we can't handle on the fast track.
        if (parentCodeUnit == chars.period) {
          parentIndex++;
          if (parentIndex == parent.length ||
              style.isSeparator(parent.codeUnitAt(parentIndex))) {
            return _PathRelation.inconclusive;
          }
        }

        // If this isn't a directory traversal, fall through so we hit the
        // normal handling for mismatched paths.
      }

      // This is the same logic as above, but for the child path instead of the
      // parent.
      if (childCodeUnit == chars.period && style.isSeparator(lastCodeUnit)) {
        childIndex++;
        if (childIndex == child.length) break;
        childCodeUnit = child.codeUnitAt(childIndex);

        if (style.isSeparator(childCodeUnit)) {
          childIndex++;
          continue;
        }

        if (childCodeUnit == chars.period) {
          childIndex++;
          if (childIndex == child.length ||
              style.isSeparator(child.codeUnitAt(childIndex))) {
            return _PathRelation.inconclusive;
          }
        }
      }

      // If we're here, we've hit two non-matching, non-significant characters.
      // As long as the remainders of the two paths don't have any unresolved
      // ".." components, we can be confident that [child] is not within
      // [parent].
      final childDirection = _pathDirection(child, childIndex);
      if (childDirection != _PathDirection.belowRoot) {
        return _PathRelation.inconclusive;
      }

      final parentDirection = _pathDirection(parent, parentIndex);
      if (parentDirection != _PathDirection.belowRoot) {
        return _PathRelation.inconclusive;
      }

      return _PathRelation.different;
    }

    // If the child is shorter than the parent, it's probably not within the
    // parent. The only exception is if the parent has some weird ".." stuff
    // going on, in which case we do the slow check.
    //
    //     isWithin("foo/bar/baz", "foo/bar") //=> false
    //     isWithin("foo/bar/baz/../..", "foo/bar") //=> true
    if (childIndex == child.length) {
      if (parentIndex == parent.length ||
          style.isSeparator(parent.codeUnitAt(parentIndex))) {
        lastParentSeparator = parentIndex;
      } else {
        lastParentSeparator ??= math.max(0, parentRootLength - 1);
      }

      final direction = _pathDirection(parent, lastParentSeparator);
      if (direction == _PathDirection.atRoot) return _PathRelation.equal;
      return direction == _PathDirection.aboveRoot
          ? _PathRelation.inconclusive
          : _PathRelation.different;
    }

    // We've reached the end of the parent path, which means it's time to make a
    // decision. Before we do, though, we'll check the rest of the child to see
    // what that tells us.
    final direction = _pathDirection(child, childIndex);

    // If there are no more components in the child, then it's the same as
    // the parent.
    //
    //     isWithin("foo/bar", "foo/bar") //=> false
    //     isWithin("foo/bar", "foo/bar//") //=> false
    //     equals("foo/bar", "foo/bar") //=> true
    //     equals("foo/bar", "foo/bar//") //=> true
    if (direction == _PathDirection.atRoot) return _PathRelation.equal;

    // If there are unresolved ".." components in the child, no decision we make
    // will be valid. We'll abort and do the slow check instead.
    //
    //     isWithin("foo/bar", "foo/bar/..") //=> false
    //     isWithin("foo/bar", "foo/bar/baz/bang/../../..") //=> false
    //     isWithin("foo/bar", "foo/bar/baz/bang/../../../bar/baz") //=> true
    if (direction == _PathDirection.aboveRoot) {
      return _PathRelation.inconclusive;
    }

    // The child is within the parent if and only if we're on a separator
    // boundary.
    //
    //     isWithin("foo/bar", "foo/bar/baz") //=> true
    //     isWithin("foo/bar/", "foo/bar/baz") //=> true
    //     isWithin("foo/bar", "foo/barbaz") //=> false
    return (style.isSeparator(child.codeUnitAt(childIndex)) ||
            style.isSeparator(lastCodeUnit))
        ? _PathRelation.within
        : _PathRelation.different;
  }