This is an automated email from the ASF dual-hosted git repository.
cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-resolver.git
The following commit(s) were added to refs/heads/master by this push:
new ffcf15375 Validate path components (#1959)
ffcf15375 is described below
commit ffcf153754a8149e2a61eefaa3d36000cfe5707e
Author: Tamas Cservenak <[email protected]>
AuthorDate: Tue Jul 14 22:03:34 2026 +0200
Validate path components (#1959)
But make it reusable as well. When 2.0.21 released, Maven codebase of
`DefaultVersionResolver` and `DefaultVersionRangeResolver` should be updated
too, and use this util method.
Supersedes (and based on) #1958
---
.../internal/impl/DefaultLocalPathComposer.java | 4 ++
.../impl/DefaultLocalPathComposerTest.java | 82 ++++++++++++++++++++++
.../java/org/eclipse/aether/util/PathUtils.java | 49 +++++++++++++
3 files changed, 135 insertions(+)
diff --git
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathComposer.java
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathComposer.java
index e85a4c56b..32f89815c 100644
---
a/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathComposer.java
+++
b/maven-resolver-impl/src/main/java/org/eclipse/aether/internal/impl/DefaultLocalPathComposer.java
@@ -25,6 +25,8 @@ import org.eclipse.aether.artifact.Artifact;
import org.eclipse.aether.metadata.Metadata;
import static java.util.Objects.requireNonNull;
+import static org.eclipse.aether.util.PathUtils.validateArtifactComponents;
+import static org.eclipse.aether.util.PathUtils.validateMetadataComponents;
/**
* Default implementation of {@link LocalPathComposer}.
@@ -37,6 +39,7 @@ public final class DefaultLocalPathComposer implements
LocalPathComposer {
@Override
public String getPathForArtifact(Artifact artifact, boolean local) {
requireNonNull(artifact);
+ validateArtifactComponents(artifact);
StringBuilder path = new StringBuilder(128);
@@ -68,6 +71,7 @@ public final class DefaultLocalPathComposer implements
LocalPathComposer {
public String getPathForMetadata(Metadata metadata, String repositoryKey) {
requireNonNull(metadata);
requireNonNull(repositoryKey);
+ validateMetadataComponents(metadata);
StringBuilder path = new StringBuilder(128);
diff --git
a/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultLocalPathComposerTest.java
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultLocalPathComposerTest.java
new file mode 100644
index 000000000..51e442ef9
--- /dev/null
+++
b/maven-resolver-impl/src/test/java/org/eclipse/aether/internal/impl/DefaultLocalPathComposerTest.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.eclipse.aether.internal.impl;
+
+import org.eclipse.aether.artifact.DefaultArtifact;
+import org.eclipse.aether.metadata.DefaultMetadata;
+import org.eclipse.aether.metadata.Metadata;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+class DefaultLocalPathComposerTest {
+
+ private final DefaultLocalPathComposer composer = new
DefaultLocalPathComposer();
+
+ @Test
+ void testGetPathForArtifact() {
+ String path = composer.getPathForArtifact(new DefaultArtifact("g.id",
"a-id", "jar", "1.0"), true);
+ assertEquals("g/id/a-id/1.0/a-id-1.0.jar", path);
+ }
+
+ @Test
+ void testGetPathForMetadata() {
+ Metadata metadata = new DefaultMetadata("g.id", "a-id", "1.0",
"maven-metadata.xml", Metadata.Nature.RELEASE);
+ String path = composer.getPathForMetadata(metadata, "central");
+ assertEquals("g/id/a-id/1.0/maven-metadata-central.xml", path);
+ }
+
+ @Test
+ void testGetPathForMetadataRejectsTraversalInVersion() {
+ Metadata metadata = new DefaultMetadata(
+ "g.id", "a-id", "../../../../tmp/PWNED-SNAPSHOT",
"maven-metadata.xml", Metadata.Nature.RELEASE);
+ assertThrows(IllegalArgumentException.class, () ->
composer.getPathForMetadata(metadata, "central"));
+ }
+
+ @Test
+ void testGetPathForArtifactRejectsTraversalInVersion() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> composer.getPathForArtifact(new DefaultArtifact("g.id",
"a-id", "jar", "../../escape"), true));
+ }
+
+ @Test
+ void testGetPathForMetadataRejectsSlashInVersion() {
+ Metadata metadata =
+ new DefaultMetadata("g.id", "a-id", "1.0/../../etc",
"maven-metadata.xml", Metadata.Nature.RELEASE);
+ assertThrows(IllegalArgumentException.class, () ->
composer.getPathForMetadata(metadata, "central"));
+ }
+
+ @Test
+ void testGetPathForArtifactRejectsBackslashInVersion() {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> composer.getPathForArtifact(new DefaultArtifact("g.id",
"a-id", "jar", "1.0\\..\\escape"), true));
+ }
+
+ @Test
+ void testGetPathForMetadataAcceptsNormalVersions() {
+ // Verify that normal version strings with dots and dashes are still
accepted
+ Metadata metadata =
+ new DefaultMetadata("g.id", "a-id", "1.0.0-SNAPSHOT",
"maven-metadata.xml", Metadata.Nature.SNAPSHOT);
+ String path = composer.getPathForMetadata(metadata, "central");
+ assertEquals("g/id/a-id/1.0.0-SNAPSHOT/maven-metadata-central.xml",
path);
+ }
+}
diff --git
a/maven-resolver-util/src/main/java/org/eclipse/aether/util/PathUtils.java
b/maven-resolver-util/src/main/java/org/eclipse/aether/util/PathUtils.java
index a1480cd2c..b853ae111 100644
--- a/maven-resolver-util/src/main/java/org/eclipse/aether/util/PathUtils.java
+++ b/maven-resolver-util/src/main/java/org/eclipse/aether/util/PathUtils.java
@@ -22,6 +22,9 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import org.eclipse.aether.artifact.Artifact;
+import org.eclipse.aether.metadata.Metadata;
+
import static java.util.Objects.requireNonNull;
/**
@@ -72,4 +75,50 @@ public final class PathUtils {
}
return result.toString();
}
+
+ /**
+ * Validates that a coordinate component does not contain path traversal
sequences
+ * or path separator characters that could cause the composed path to
escape
+ * the local repository directory.
+ *
+ * @since 2.0.21
+ */
+ public static void validatePathComponent(String value, String label) {
+ if (value != null && !value.isEmpty()) {
+ // Important: "equals .." and not "contains ..", as if escape
attempted, it will contain path separators
+ // OTOH: version "1.." is valid version string!
+ if (value.equals("..") || value.contains("/") ||
value.contains("\\")) {
+ throw new IllegalArgumentException(
+ "Invalid " + label + ": must not contain '..', '/' or
'\\': " + value);
+ }
+ }
+ }
+
+ /**
+ * Validates all coordinate components of an {@link Artifact}.
+ *
+ * @see #validatePathComponent(String, String)
+ * @since 2.0.21
+ */
+ public static void validateArtifactComponents(Artifact artifact) {
+ validatePathComponent(artifact.getGroupId(), "groupId");
+ validatePathComponent(artifact.getArtifactId(), "artifactId");
+ validatePathComponent(artifact.getVersion(), "version");
+ validatePathComponent(artifact.getBaseVersion(), "baseVersion");
+ validatePathComponent(artifact.getClassifier(), "classifier");
+ validatePathComponent(artifact.getExtension(), "extension");
+ }
+
+ /**
+ * Validates all coordinate components of a {@link Metadata}.
+ *
+ * @see #validatePathComponent(String, String)
+ * @since 2.0.21
+ */
+ public static void validateMetadataComponents(Metadata metadata) {
+ validatePathComponent(metadata.getGroupId(), "groupId");
+ validatePathComponent(metadata.getArtifactId(), "artifactId");
+ validatePathComponent(metadata.getVersion(), "version");
+ // note: type may contain string like ".meta/prefixes.txt"!
+ }
}