diff --git a/pkgs/path/CHANGELOG.md b/pkgs/path/CHANGELOG.md index 9ad90d26..504f0797 100644 --- a/pkgs/path/CHANGELOG.md +++ b/pkgs/path/CHANGELOG.md @@ -1,5 +1,7 @@ ## 1.9.2-wip +- `isWithin` treats a parent that ends in `/.` as that directory, so + `isWithin('/dir/.', '/dir/.file')` is true. - Make `Url` style better at recognizing schemes and authorities. Only consider a path as being a schemed URL if it starts with a valid scheme. diff --git a/pkgs/path/lib/src/context.dart b/pkgs/path/lib/src/context.dart index a036b61c..5dcda4ca 100644 --- a/pkgs/path/lib/src/context.dart +++ b/pkgs/path/lib/src/context.dart @@ -731,6 +731,28 @@ final class Context { while (parentIndex < parent.length && childIndex < child.length) { var parentCodeUnit = parent.codeUnitAt(parentIndex); var childCodeUnit = child.codeUnitAt(childIndex); + + // A lone "." component means "this directory". Skip it before comparing + // characters so a parent like "/dir/." is the same directory as "/dir" + // and still contains children such as "/dir/.file". Matching the "." + // against the child's next character would treat ".file" as a different + // component and reject a path that is actually inside the parent. + if (parentCodeUnit == chars.period && style.isSeparator(lastCodeUnit)) { + final nextParentIndex = parentIndex + 1; + final dotIsComponent = nextParentIndex == parent.length || + style.isSeparator(parent.codeUnitAt(nextParentIndex)); + if (dotIsComponent) { + parentIndex = nextParentIndex; + if (parentIndex < parent.length && + style.isSeparator(parent.codeUnitAt(parentIndex))) { + lastParentSeparator = parentIndex; + lastCodeUnit = parent.codeUnitAt(parentIndex); + parentIndex++; + } + continue; + } + } + if (style.codeUnitsEqual(parentCodeUnit, childCodeUnit)) { if (style.isSeparator(parentCodeUnit)) { lastParentSeparator = parentIndex; diff --git a/pkgs/path/test/posix_test.dart b/pkgs/path/test/posix_test.dart index 6cd52709..514530b3 100644 --- a/pkgs/path/test/posix_test.dart +++ b/pkgs/path/test/posix_test.dart @@ -578,6 +578,15 @@ void main() { expect(context.isWithin('foo/bar', 'foo/bar///'), isFalse); expect(context.isWithin('foo/.bar', 'foo/.bar/baz'), isTrue); expect(context.isWithin('foo/./bar', 'foo/.bar/baz'), isFalse); + // A trailing "." is the current directory, so it must not be glued onto + // a child whose next component merely starts with ".". + expect(context.isWithin('/dir/.', '/dir/.file'), isTrue); + expect(context.isWithin('/dir/.', '/dir/file'), isTrue); + expect(context.isWithin('/dir/.', '/dir'), isFalse); + expect(context.isWithin('/dir/.', '/dir/./.file'), isTrue); + expect(context.isWithin('foo/.', 'foo/.file'), isTrue); + expect(context.equals('/dir/.', '/dir'), isTrue); + expect(context.equals('/dir/.', '/dir/.'), isTrue); expect(context.isWithin('foo/..bar', 'foo/..bar/baz'), isTrue); expect(context.isWithin('foo/bar', 'foo/bar/baz/..'), isFalse); expect(context.isWithin('foo/bar', 'foo/bar/baz/../qux'), isTrue);