diff --git a/maven-compat/src/main/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManager.java b/maven-compat/src/main/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManager.java
index 0169406bea76..a22500bdf5e4 100644
--- a/maven-compat/src/main/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManager.java
+++ b/maven-compat/src/main/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManager.java
@@ -35,6 +35,7 @@
import org.apache.maven.artifact.repository.RepositoryRequest;
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Writer;
import org.apache.maven.repository.internal.metadata.ValidatingMetadataXpp3Reader;
+import org.apache.maven.repository.legacy.ChecksumFailedException;
import org.apache.maven.repository.legacy.UpdateCheckManager;
import org.apache.maven.repository.legacy.WagonManager;
import org.apache.maven.wagon.ResourceDoesNotExistException;
@@ -114,6 +115,17 @@ public void resolve(RepositoryMetadata metadata, RepositoryRequest request)
getLogger().info(metadata.getKey() + ": checking for updates from " + repository.getId());
try {
wagonManager.getArtifactMetadata(metadata, repository, file, policy.getChecksumPolicy());
+ updateCheckManager.touch(metadata, repository, file);
+ } catch (ChecksumFailedException e) {
+ // ChecksumFailedException is only thrown by the wagon manager under
+ // CHECKSUM_POLICY_FAIL: honor the strict policy by failing metadata resolution
+ // instead of downgrading the integrity failure to a warning. The update
+ // tracking file is deliberately not touched, so the next build retries
+ // immediately instead of trusting stale metadata for a full update interval.
+ throw new RepositoryMetadataResolutionException(
+ metadata + " failed checksum verification against repository: " + repository.getId()
+ + " due to an error: " + e.getMessage(),
+ e);
} catch (ResourceDoesNotExistException e) {
getLogger().debug(metadata + " could not be found on repository: " + repository.getId());
@@ -129,12 +141,12 @@ public void resolve(RepositoryMetadata metadata, RepositoryRequest request)
file.delete(); // if this fails, forget about it
}
}
+ updateCheckManager.touch(metadata, repository, file);
} catch (TransferFailedException e) {
getLogger()
.warn(metadata + " could not be retrieved from repository: " + repository.getId()
+ " due to an error: " + e.getMessage());
getLogger().debug("Exception", e);
- } finally {
updateCheckManager.touch(metadata, repository, file);
}
}
@@ -281,6 +293,9 @@ protected Metadata readMetadata(File mappingFile) throws RepositoryMetadataReadE
throw new RepositoryMetadataReadException(
"Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e);
}
+
+ validateVersioning(result);
+
return result;
}
@@ -401,7 +416,7 @@ private File getArtifactMetadataFromDeploymentRepository(
try {
wagonManager.getArtifactMetadataFromDeploymentRepository(
- metadata, remoteRepository, file, ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN);
+ metadata, remoteRepository, file, getChecksumPolicy(metadata, remoteRepository));
} catch (ResourceDoesNotExistException e) {
getLogger()
.info(metadata + " could not be found on repository: " + remoteRepository.getId()
@@ -427,6 +442,22 @@ private File getArtifactMetadataFromDeploymentRepository(
return file;
}
+ /**
+ * Determines the effective checksum policy for a transfer from the given repository. The
+ * operator-configured policy (e.g. {@code fail} via {@code -C}/{@code --strict-checksums} or a
+ * per-repository {@code checksumPolicy}) must govern every remote transfer, so it must not be
+ * hardcoded at the call sites.
+ */
+ private String getChecksumPolicy(ArtifactMetadata metadata, ArtifactRepository repository) {
+ if (metadata instanceof RepositoryMetadata) {
+ ArtifactRepositoryPolicy policy = ((RepositoryMetadata) metadata).getPolicy(repository);
+ if (policy != null && policy.getChecksumPolicy() != null) {
+ return policy.getChecksumPolicy();
+ }
+ }
+ return ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN;
+ }
+
public void deploy(
ArtifactMetadata metadata, ArtifactRepository localRepository, ArtifactRepository deploymentRepository)
throws RepositoryMetadataDeploymentException {
diff --git a/maven-compat/src/main/java/org/apache/maven/repository/legacy/LegacyRepositorySystem.java b/maven-compat/src/main/java/org/apache/maven/repository/legacy/LegacyRepositorySystem.java
index 677c0ac8cfc3..9c2fad1ce0f6 100644
--- a/maven-compat/src/main/java/org/apache/maven/repository/legacy/LegacyRepositorySystem.java
+++ b/maven-compat/src/main/java/org/apache/maven/repository/legacy/LegacyRepositorySystem.java
@@ -662,7 +662,7 @@ public void retrieve(
destination,
remotePath,
TransferListenerAdapter.newAdapter(transferListener),
- ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN,
+ getChecksumPolicy(repository),
true);
} catch (org.apache.maven.wagon.TransferFailedException e) {
throw new ArtifactTransferFailedException(getMessage(e, "Error transferring artifact."), e);
@@ -671,6 +671,39 @@ public void retrieve(
}
}
+ /**
+ * Determines the effective checksum policy for a generic retrieval from the given repository.
+ * The operator-configured policy (e.g. {@code fail} via {@code -C}/{@code --strict-checksums})
+ * must govern every remote transfer instead of a hardcoded lenient default. A generic remote
+ * path cannot be classified as release or snapshot, so the stricter of the two configured
+ * policies applies.
+ */
+ private static String getChecksumPolicy(ArtifactRepository repository) {
+ String releases =
+ (repository.getReleases() != null) ? repository.getReleases().getChecksumPolicy() : null;
+ String snapshots =
+ (repository.getSnapshots() != null) ? repository.getSnapshots().getChecksumPolicy() : null;
+ String policy;
+ if (releases == null) {
+ policy = snapshots;
+ } else if (snapshots == null || checksumRank(releases) >= checksumRank(snapshots)) {
+ policy = releases;
+ } else {
+ policy = snapshots;
+ }
+ return (policy != null) ? policy : ArtifactRepositoryPolicy.CHECKSUM_POLICY_WARN;
+ }
+
+ private static int checksumRank(String policy) {
+ if (ArtifactRepositoryPolicy.CHECKSUM_POLICY_FAIL.equals(policy)) {
+ return 2;
+ } else if (ArtifactRepositoryPolicy.CHECKSUM_POLICY_IGNORE.equals(policy)) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+
public void publish(
ArtifactRepository repository, File source, String remotePath, ArtifactTransferListener transferListener)
throws ArtifactTransferFailedException {
diff --git a/maven-compat/src/test/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManagerTest.java b/maven-compat/src/test/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManagerTest.java
index b56dc2be6546..c2e6615dccad 100644
--- a/maven-compat/src/test/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManagerTest.java
+++ b/maven-compat/src/test/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManagerTest.java
@@ -18,23 +18,74 @@
*/
package org.apache.maven.artifact.repository.metadata;
+import javax.inject.Inject;
+import javax.inject.Named;
+
import java.io.File;
import java.net.URL;
+import java.util.Collections;
+import org.apache.maven.artifact.AbstractArtifactComponentTest;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
+import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
+import org.codehaus.plexus.testing.PlexusTest;
+import org.codehaus.plexus.util.FileUtils;
import org.junit.jupiter.api.Test;
+import static org.codehaus.plexus.testing.PlexusExtension.getBasedir;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
- * Tests that {@link DefaultRepositoryMetadataManager} rejects repository metadata carrying version tokens that
- * are not valid coordinate components, on the legacy read path used when metadata is loaded for merging.
+ * Tests {@link DefaultRepositoryMetadataManager}.
*/
-public class DefaultRepositoryMetadataManagerTest {
+@PlexusTest
+@Deprecated
+class DefaultRepositoryMetadataManagerTest extends AbstractArtifactComponentTest {
+
+ @Inject
+ private RepositoryMetadataManager repositoryMetadataManager;
+
+ @Inject
+ @Named("default")
+ private ArtifactRepositoryLayout layout;
private final DefaultRepositoryMetadataManager manager = new DefaultRepositoryMetadataManager();
+ @Override
+ protected String component() {
+ return "repositoryMetadataManager";
+ }
+
+ @Test
+ void testResolveHonorsConfiguredFailChecksumPolicy() throws Exception {
+ RepositoryMetadata metadata = new GroupRepositoryMetadata("checksum-policy-test-group");
+
+ ArtifactRepositoryPolicy failPolicy = new ArtifactRepositoryPolicy(
+ true, ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS, ArtifactRepositoryPolicy.CHECKSUM_POLICY_FAIL);
+
+ File remoteBase = new File(getBasedir(), "target/test-repositories/" + component() + "/remote-repository");
+ FileUtils.deleteDirectory(remoteBase);
+
+ ArtifactRepository remoteRepo = artifactRepositoryFactory.createArtifactRepository(
+ "test", "file://" + remoteBase.getPath(), layout, failPolicy, failPolicy);
+
+ String remotePath = remoteRepo.pathOfRemoteRepositoryMetadata(metadata);
+ File remoteFile = new File(remoteBase, remotePath);
+ remoteFile.getParentFile().mkdirs();
+ FileUtils.fileWrite(remoteFile.getAbsolutePath(), "");
+ FileUtils.fileWrite(remoteFile.getAbsolutePath() + ".sha1", "0000000000000000000000000000000000000000");
+
+ ArtifactRepository localRepo = localRepository();
+ FileUtils.deleteDirectory(new File(localRepo.getBasedir()));
+
+ assertThrows(
+ RepositoryMetadataResolutionException.class,
+ () -> repositoryMetadataManager.resolve(metadata, Collections.singletonList(remoteRepo), localRepo));
+ }
+
@Test
void testMetadataWithInvalidVersionTokenIsRejected() {
File metadataFile = testFile("metadata-invalid-token/maven-metadata.xml");
diff --git a/maven-compat/src/test/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManagerValidationTest.java b/maven-compat/src/test/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManagerValidationTest.java
new file mode 100644
index 000000000000..2167606b810b
--- /dev/null
+++ b/maven-compat/src/test/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManagerValidationTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.apache.maven.artifact.repository.metadata;
+
+import java.io.File;
+import java.net.URL;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Tests that {@link DefaultRepositoryMetadataManager} rejects repository metadata carrying version tokens that
+ * are not valid coordinate components, on the legacy read path used when metadata is loaded for merging.
+ */
+class DefaultRepositoryMetadataManagerValidationTest {
+
+ private final DefaultRepositoryMetadataManager manager = new DefaultRepositoryMetadataManager();
+
+ @Test
+ void testMetadataWithInvalidVersionTokenIsRejected() {
+ File metadataFile = testFile("metadata-invalid-token/maven-metadata.xml");
+
+ RepositoryMetadataReadException exception =
+ assertThrows(RepositoryMetadataReadException.class, () -> manager.readMetadata(metadataFile));
+
+ assertTrue(exception.getMessage().contains("invalid version token"), exception.getMessage());
+ }
+
+ @Test
+ void testMetadataWithInvalidSnapshotTimestampIsRejected() {
+ File metadataFile = testFile("metadata-invalid-timestamp/maven-metadata.xml");
+
+ RepositoryMetadataReadException exception =
+ assertThrows(RepositoryMetadataReadException.class, () -> manager.readMetadata(metadataFile));
+
+ assertTrue(exception.getMessage().contains("invalid version token"), exception.getMessage());
+ }
+
+ private static File testFile(String resource) {
+ URL url = Thread.currentThread().getContextClassLoader().getResource(resource);
+ assertNotNull(url, "test resource not found: " + resource);
+ return new File(url.getFile());
+ }
+}
diff --git a/maven-compat/src/test/java/org/apache/maven/repository/legacy/LegacyRepositorySystemTest.java b/maven-compat/src/test/java/org/apache/maven/repository/legacy/LegacyRepositorySystemTest.java
index 9e723531c96f..1ed87341fe35 100644
--- a/maven-compat/src/test/java/org/apache/maven/repository/legacy/LegacyRepositorySystemTest.java
+++ b/maven-compat/src/test/java/org/apache/maven/repository/legacy/LegacyRepositorySystemTest.java
@@ -21,17 +21,22 @@
import javax.inject.Inject;
import java.io.File;
+import java.nio.file.Files;
import java.util.Arrays;
import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.Authentication;
+import org.apache.maven.repository.ArtifactTransferFailedException;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.settings.Server;
import org.codehaus.plexus.testing.PlexusTest;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
/**
* Tests {@link LegacyRepositorySystem}.
@@ -66,4 +71,26 @@ public void testAuthenticationHandling() throws Exception {
assertEquals("jason", authentication.getUsername());
assertEquals("abc123", authentication.getPassword());
}
+
+ @Test
+ void testRetrieveHonorsConfiguredFailChecksumPolicy(@TempDir File tempDir) throws Exception {
+ File remoteBase = new File(tempDir, "remote");
+ remoteBase.mkdirs();
+ File remoteFile = new File(remoteBase, "sample.txt");
+ Files.write(remoteFile.toPath(), "content".getBytes());
+ Files.write(
+ new File(remoteBase, "sample.txt.sha1").toPath(),
+ "0000000000000000000000000000000000000000".getBytes());
+
+ ArtifactRepositoryPolicy failPolicy = new ArtifactRepositoryPolicy(
+ true, ArtifactRepositoryPolicy.UPDATE_POLICY_ALWAYS, ArtifactRepositoryPolicy.CHECKSUM_POLICY_FAIL);
+ ArtifactRepository repository = repositorySystem.createArtifactRepository(
+ "test", "file://" + remoteBase.getAbsolutePath(), null, failPolicy, failPolicy);
+
+ File destination = new File(tempDir, "sample.txt");
+
+ assertThrows(
+ ArtifactTransferFailedException.class,
+ () -> repositorySystem.retrieve(repository, destination, "sample.txt", null));
+ }
}
diff --git a/maven-compat/src/test/resources/metadata-invalid-timestamp/maven-metadata.xml b/maven-compat/src/test/resources/metadata-invalid-timestamp/maven-metadata.xml
index fdd7cc27fdfb..68cf46a41f3d 100644
--- a/maven-compat/src/test/resources/metadata-invalid-timestamp/maven-metadata.xml
+++ b/maven-compat/src/test/resources/metadata-invalid-timestamp/maven-metadata.xml
@@ -1,30 +1,26 @@
-
+ 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.
+-->
+
org.apache.maven.its
dep-invalid-timestamp
- 1.0-SNAPSHOT
+ 1.0-SNAPSHOT
20120809.112920:1
diff --git a/maven-compat/src/test/resources/metadata-invalid-token/maven-metadata.xml b/maven-compat/src/test/resources/metadata-invalid-token/maven-metadata.xml
index 92e1b6b813d3..8f4eb7200c7b 100644
--- a/maven-compat/src/test/resources/metadata-invalid-token/maven-metadata.xml
+++ b/maven-compat/src/test/resources/metadata-invalid-token/maven-metadata.xml
@@ -1,30 +1,26 @@
-
+ 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.
+-->
+
org.apache.maven.its
dep-invalid-token
- 1.0-SNAPSHOT
+ 1.0-SNAPSHOT
1.0:2.0
20120809112920
diff --git a/maven-core/src/main/java/org/apache/maven/artifact/repository/LegacyLocalRepositoryManager.java b/maven-core/src/main/java/org/apache/maven/artifact/repository/LegacyLocalRepositoryManager.java
index 5ff846e7ff4f..85ef3b14f6f1 100644
--- a/maven-core/src/main/java/org/apache/maven/artifact/repository/LegacyLocalRepositoryManager.java
+++ b/maven-core/src/main/java/org/apache/maven/artifact/repository/LegacyLocalRepositoryManager.java
@@ -211,7 +211,7 @@ public String getRemoteFilename() {
}
public String getLocalFilename(ArtifactRepository repository) {
- return insertRepositoryKey(getRemoteFilename(), repository.getKey());
+ return insertRepositoryKey(getRemoteFilename(), validateRepositoryKey(repository.getKey()));
}
private String insertRepositoryKey(String filename, String repositoryKey) {
@@ -225,6 +225,32 @@ private String insertRepositoryKey(String filename, String repositoryKey) {
return result;
}
+ /**
+ * The repository key (its id) is used verbatim as part of a local file name, so it must lie within
+ * the usual coordinate character set.
+ */
+ private static String validateRepositoryKey(String key) {
+ if (key == null || key.isEmpty()) {
+ return key;
+ }
+ if (isInvalidPathToken(key)) {
+ throw new IllegalArgumentException("Invalid repository key '" + key + "'");
+ }
+ return key;
+ }
+
+ private static boolean isInvalidPathToken(String value) {
+ if ("..".equals(value) || value.indexOf('/') >= 0 || value.indexOf('\\') >= 0 || value.indexOf(':') >= 0) {
+ return true;
+ }
+ for (int i = 0; i < value.length(); i++) {
+ if (Character.isISOControl(value.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public void merge(org.apache.maven.repository.legacy.metadata.ArtifactMetadata metadata) {
// not used
}
diff --git a/maven-core/src/main/java/org/apache/maven/artifact/repository/metadata/AbstractRepositoryMetadata.java b/maven-core/src/main/java/org/apache/maven/artifact/repository/metadata/AbstractRepositoryMetadata.java
index 2b97665f5d19..c1635804627b 100644
--- a/maven-core/src/main/java/org/apache/maven/artifact/repository/metadata/AbstractRepositoryMetadata.java
+++ b/maven-core/src/main/java/org/apache/maven/artifact/repository/metadata/AbstractRepositoryMetadata.java
@@ -50,7 +50,33 @@ public String getRemoteFilename() {
}
public String getLocalFilename(ArtifactRepository repository) {
- return "maven-metadata-" + repository.getKey() + ".xml";
+ return "maven-metadata-" + validateRepositoryKey(repository.getKey()) + ".xml";
+ }
+
+ /**
+ * The repository key (its id) is used verbatim as part of a local file name, so it must lie within
+ * the usual coordinate character set.
+ */
+ private static String validateRepositoryKey(String key) {
+ if (key == null || key.isEmpty()) {
+ return key;
+ }
+ if (isInvalidPathToken(key)) {
+ throw new IllegalArgumentException("Invalid repository key '" + key + "'");
+ }
+ return key;
+ }
+
+ private static boolean isInvalidPathToken(String value) {
+ if ("..".equals(value) || value.indexOf('/') >= 0 || value.indexOf('\\') >= 0 || value.indexOf(':') >= 0) {
+ return true;
+ }
+ for (int i = 0; i < value.length(); i++) {
+ if (Character.isISOControl(value.charAt(i))) {
+ return true;
+ }
+ }
+ return false;
}
public void storeInLocalRepository(ArtifactRepository localRepository, ArtifactRepository remoteRepository)
diff --git a/maven-core/src/test/java/org/apache/maven/artifact/repository/LegacyLocalRepositoryManagerTest.java b/maven-core/src/test/java/org/apache/maven/artifact/repository/LegacyLocalRepositoryManagerTest.java
new file mode 100644
index 000000000000..a677ea4c224a
--- /dev/null
+++ b/maven-core/src/test/java/org/apache/maven/artifact/repository/LegacyLocalRepositoryManagerTest.java
@@ -0,0 +1,61 @@
+/*
+ * 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.apache.maven.artifact.repository;
+
+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;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class LegacyLocalRepositoryManagerTest {
+
+ private static LegacyLocalRepositoryManager.ArtifactMetadataAdapter newAdapter() {
+ Metadata metadata =
+ new DefaultMetadata("g", "a", "1.0", "maven-metadata.xml", Metadata.Nature.RELEASE_OR_SNAPSHOT);
+ return new LegacyLocalRepositoryManager.ArtifactMetadataAdapter(metadata);
+ }
+
+ private static ArtifactRepository repositoryWithId(String id) {
+ ArtifactRepository repo = mock(ArtifactRepository.class);
+ when(repo.getKey()).thenReturn(id);
+ return repo;
+ }
+
+ @Test
+ void getLocalFilenameKeepsWellFormedRepositoryKeyUnchanged() {
+ String filename = newAdapter().getLocalFilename(repositoryWithId("central"));
+
+ assertEquals("maven-metadata-central.xml", filename);
+ }
+
+ @Test
+ void getLocalFilenameRejectsRepositoryKeyContainingPathSeparator() {
+ assertThrows(
+ IllegalArgumentException.class, () -> newAdapter().getLocalFilename(repositoryWithId("repo/evil")));
+ }
+
+ @Test
+ void getLocalFilenameRejectsRepositoryKeyThatIsAParentDirectoryReference() {
+ assertThrows(IllegalArgumentException.class, () -> newAdapter().getLocalFilename(repositoryWithId("..")));
+ }
+}
diff --git a/maven-core/src/test/java/org/apache/maven/artifact/repository/metadata/AbstractRepositoryMetadataTest.java b/maven-core/src/test/java/org/apache/maven/artifact/repository/metadata/AbstractRepositoryMetadataTest.java
new file mode 100644
index 000000000000..a8d2aeef06bf
--- /dev/null
+++ b/maven-core/src/test/java/org/apache/maven/artifact/repository/metadata/AbstractRepositoryMetadataTest.java
@@ -0,0 +1,81 @@
+/*
+ * 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.apache.maven.artifact.repository.metadata;
+
+import org.apache.maven.artifact.Artifact;
+import org.apache.maven.artifact.repository.ArtifactRepository;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Ensures that a repository key which may originate from a downloaded POM's {@code } section
+ * cannot select a local metadata file name outside the intended one.
+ */
+class AbstractRepositoryMetadataTest {
+
+ private static ArtifactRepository repository(String id) {
+ ArtifactRepository repo = mock(ArtifactRepository.class);
+ when(repo.getKey()).thenReturn(id);
+ return repo;
+ }
+
+ private static RepositoryMetadata createMetadata() {
+ Artifact artifact = mock(Artifact.class);
+ when(artifact.getGroupId()).thenReturn("org.test");
+ when(artifact.getArtifactId()).thenReturn("test-artifact");
+ when(artifact.getVersion()).thenReturn("1.0");
+ return new ArtifactRepositoryMetadata(artifact);
+ }
+
+ @Test
+ void repositoryKeyWithColonIsRejected() {
+ RepositoryMetadata metadata = createMetadata();
+ ArtifactRepository repo = repository("central:1.0");
+
+ assertThrows(IllegalArgumentException.class, () -> metadata.getLocalFilename(repo));
+ }
+
+ @Test
+ void repositoryKeyWithDotDotSegmentIsRejected() {
+ RepositoryMetadata metadata = createMetadata();
+ ArtifactRepository repo = repository("x/../../../../../../settings");
+
+ assertThrows(IllegalArgumentException.class, () -> metadata.getLocalFilename(repo));
+ }
+
+ @Test
+ void repositoryKeyWithBackslashIsRejected() {
+ RepositoryMetadata metadata = createMetadata();
+ ArtifactRepository repo = repository("x\\..\\..\\settings");
+
+ assertThrows(IllegalArgumentException.class, () -> metadata.getLocalFilename(repo));
+ }
+
+ @Test
+ void wellFormedRepositoryKeyProducesExpectedFilename() {
+ RepositoryMetadata metadata = createMetadata();
+ ArtifactRepository repo = repository("central");
+
+ assertEquals("maven-metadata-central.xml", metadata.getLocalFilename(repo));
+ }
+}