You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by jo...@apache.org on 2026/10/01 18:53:09 UTC
(sling-org-apache-sling-api) 01/01: SLING-13373 Path.matches() should respect ../ segments
This is an automated email from the ASF dual-hosted git repository.
joerghoh pushed a commit to branch SLING-13373
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-api.git
commit bb1b3e4a7a0b74fda80088e838a0a0bc835927c0
Author: Joerg Hoh <jo...@apache.org>
AuthorDate: Thu Oct 1 20:52:52 2026 +0200
SLING-13373 Path.matches() should respect ../ segments
---
.../org/apache/sling/api/resource/path/Path.java | 18 +++++++---
.../apache/sling/api/resource/path/PathSet.java | 3 ++
.../sling/api/resource/path/PathSetTest.java | 23 +++++++++++++
.../apache/sling/api/resource/path/PathTest.java | 40 ++++++++++++++++++++++
4 files changed, 80 insertions(+), 4 deletions(-)
diff --git a/src/main/java/org/apache/sling/api/resource/path/Path.java b/src/main/java/org/apache/sling/api/resource/path/Path.java
index 77326aa..6f1c7ad 100644
--- a/src/main/java/org/apache/sling/api/resource/path/Path.java
+++ b/src/main/java/org/apache/sling/api/resource/path/Path.java
@@ -20,6 +20,7 @@ package org.apache.sling.api.resource.path;
import java.util.regex.Pattern;
+import org.apache.sling.api.resource.ResourceUtil;
import org.jetbrains.annotations.NotNull;
/**
@@ -102,8 +103,13 @@ public class Path implements Comparable<Path> {
* provided path matches the pattern. If this path object holds a pattern
* and a pattern is provided as the argument, it returns only {@code true}
* if the pattern is the same.
- * If the provided argument is not an absolute path (e.g. if it is a relative
- * path or a pattern), this method returns {@code false}.
+ * If the provided argument is a concrete path, it is normalized using
+ * {@link ResourceUtil#normalize(String)} before matching, resolving {@code .}
+ * and {@code ..} segments and collapsing consecutive slashes.
+ * If normalization fails, this method returns {@code false}.
+ * Glob pattern arguments retain their pattern semantics.
+ * If the provided argument is not an absolute path or an absolute glob
+ * pattern, this method throws {@code IllegalArgumentException}.
*
* @param otherPath Absolute path to check.
* @return {@code true} If other path is within the sub tree of this path
@@ -153,10 +159,14 @@ public class Path implements Comparable<Path> {
if (!otherPath.startsWith("/")) {
throw new IllegalArgumentException("Path must be absolute: " + otherPath);
}
+ final String normalizedOtherPath = ResourceUtil.normalize(otherPath);
+ if (normalizedOtherPath == null) {
+ return false;
+ }
if (isPattern) {
- return this.regexPattern.matcher(otherPath).matches();
+ return this.regexPattern.matcher(normalizedOtherPath).matches();
}
- return this.path.equals(otherPath) || otherPath.startsWith(this.prefix);
+ return this.path.equals(normalizedOtherPath) || normalizedOtherPath.startsWith(this.prefix);
}
/**
diff --git a/src/main/java/org/apache/sling/api/resource/path/PathSet.java b/src/main/java/org/apache/sling/api/resource/path/PathSet.java
index 3bb6324..373cb28 100644
--- a/src/main/java/org/apache/sling/api/resource/path/PathSet.java
+++ b/src/main/java/org/apache/sling/api/resource/path/PathSet.java
@@ -125,6 +125,9 @@ public class PathSet implements Iterable<Path> {
/**
* Check whether the provided path is in the sub tree of any
* of the paths in this set.
+ * Concrete path arguments are normalized before matching; paths that
+ * cannot be normalized do not match. Glob pattern arguments retain their
+ * pattern semantics.
* @param otherPath The path to match
* @return The path which matches the provided path, {@code null} otherwise.
* @see Path#matches(String)
diff --git a/src/test/java/org/apache/sling/api/resource/path/PathSetTest.java b/src/test/java/org/apache/sling/api/resource/path/PathSetTest.java
index 734eadd..cdc2b1c 100644
--- a/src/test/java/org/apache/sling/api/resource/path/PathSetTest.java
+++ b/src/test/java/org/apache/sling/api/resource/path/PathSetTest.java
@@ -113,6 +113,29 @@ public class PathSetTest {
assertEquals(new Path("/x/y"), set.matches("/x/y/g/e"));
}
+ @Test
+ public void testMatchingNormalizesTraversalSegments() {
+ for (final String entry : new String[] {"/apps", "glob:/apps/**"}) {
+ final PathSet set = PathSet.fromStrings(entry);
+
+ assertNull(set.matches("/apps/../etc/secret"));
+ assertNull(set.matches("/apps/foo/../../etc"));
+ assertNull(set.matches("/../apps/foo"));
+ assertNull(set.matches("/apps/..."));
+ assertEquals(new Path(entry), set.matches("/apps/foo/../bar"));
+ assertEquals(new Path(entry), set.matches("/libs/../apps/foo"));
+ assertEquals(new Path(entry), set.matches("/apps//foo"));
+ }
+ }
+
+ @Test
+ public void testSubsetNormalizesTraversalSegments() {
+ final PathSet set = PathSet.fromStrings("/apps/../etc/secret", "/apps/foo");
+
+ assertEqualSets(set.getSubset("/apps"), "/apps/foo");
+ assertEqualSets(set.getSubset(PathSet.fromStrings("/apps")), "/apps/foo");
+ }
+
@Test
public void testToStringSet() {
final PathSet set = PathSet.fromStrings("/a", "/x/y");
diff --git a/src/test/java/org/apache/sling/api/resource/path/PathTest.java b/src/test/java/org/apache/sling/api/resource/path/PathTest.java
index 0450693..abf7859 100644
--- a/src/test/java/org/apache/sling/api/resource/path/PathTest.java
+++ b/src/test/java/org/apache/sling/api/resource/path/PathTest.java
@@ -63,6 +63,46 @@ public class PathTest {
assertMatch(p, "/content", "/content/a", "/content/a/b");
}
+ @Test
+ public void testMatchesNormalizesTraversalSegments() {
+ final Path path = new Path("/apps");
+
+ assertNoMatch(path, "/apps/../etc/secret", "/apps/./../etc/secret", "/apps/foo/../../etc");
+ assertMatch(
+ path,
+ "/apps/foo/../bar",
+ "/libs/../apps/foo",
+ "/apps//foo",
+ "/apps/foo/.",
+ "/apps/foo/..",
+ "/apps/.hidden",
+ "/apps/foo..bar");
+ assertNoMatch(path, "/../apps/foo", "/apps/../../..", "/apps/...", "/apps/..../foo");
+ }
+
+ @Test
+ public void testPatternMatchNormalizesTraversalSegments() {
+ final Path glob = new Path("glob:/apps/**");
+
+ assertNoMatch(glob, "/apps/../etc/secret", "/apps/foo/../../etc", "/../apps/foo", "/apps/...");
+ assertMatch(glob, "/apps/foo/../bar", "/libs/../apps/foo", "/apps//foo");
+
+ final Path singleSegmentGlob = new Path("glob:/apps/*");
+ assertMatch(singleSegmentGlob, "/apps/foo/../bar", "/apps//bar");
+ assertNoMatch(singleSegmentGlob, "/apps/foo/bar", "/apps/../etc");
+ }
+
+ @Test
+ public void testRootMatchRejectsInvalidPaths() {
+ final Path root = new Path("/");
+ final Path glob = new Path("glob:/**");
+
+ assertNoMatch(root, "/../apps", "/apps/../../etc", "/apps/...");
+ assertNoMatch(glob, "/../apps", "/apps/../../etc", "/apps/...");
+ assertMatch(root, "/apps/..", "//apps//foo", "/apps/.");
+ assertMatch(glob, "/apps/..", "//apps//foo", "/apps/.");
+ }
+
@Test
public void testPatternMatchingA() {
final Path p = new Path("glob:/apps/**/*.html");