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;
}