From f227a67557171e810a7b7d770219327dbb18c9b7 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 31 Aug 2026 22:38:24 +0200 Subject: [PATCH 1/3] Validate the repository key used in legacy metadata file names AbstractRepositoryMetadata.getLocalFilename rejects a repository key that is the ".." token, contains '/', '\', ':', or an ISO control character, before using it in a local file name. LegacyLocalRepositoryManager.ArtifactMetadataAdapter applies the same validation. Backport of the corresponding fix from PR #12945 (maven-4.0.x). Co-Authored-By: Claude Opus 4.6 --- .../LegacyLocalRepositoryManager.java | 28 ++++++- .../metadata/AbstractRepositoryMetadata.java | 28 ++++++- .../LegacyLocalRepositoryManagerTest.java | 61 ++++++++++++++ .../AbstractRepositoryMetadataTest.java | 81 +++++++++++++++++++ 4 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 maven-core/src/test/java/org/apache/maven/artifact/repository/LegacyLocalRepositoryManagerTest.java create mode 100644 maven-core/src/test/java/org/apache/maven/artifact/repository/metadata/AbstractRepositoryMetadataTest.java 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)); + } +} From d965408a2864e6b8c71425c63294732e0ef949a9 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 31 Aug 2026 22:38:36 +0200 Subject: [PATCH 2/3] Validate metadata version tokens and fail closed on checksum mismatches DefaultRepositoryMetadataManager.readMetadata applies validateVersioning to reject metadata carrying version tokens (latest, release, versions, snapshot versions, snapshot timestamp) that contain '..', '/', '\', ':', or ISO control characters. DefaultRepositoryMetadataManager.resolve now catches ChecksumFailedException ahead of the generic TransferFailedException handler. Under checksumPolicy=fail a checksum mismatch fails metadata resolution instead of only logging a warning. The update-check file is only touched on success, not-found, or a generic transfer failure, so a checksum failure is retried on the next build. Backport of the corresponding fixes from PR #12945 (maven-4.0.x). Co-Authored-By: Claude Opus 4.6 --- .../DefaultRepositoryMetadataManager.java | 80 ++++++++++++++++++- ...positoryMetadataManagerValidationTest.java | 63 +++++++++++++++ .../maven-metadata.xml | 31 +++++++ .../metadata-invalid-token/maven-metadata.xml | 28 +++++++ 4 files changed, 200 insertions(+), 2 deletions(-) create mode 100644 maven-compat/src/test/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManagerValidationTest.java create mode 100644 maven-compat/src/test/resources/metadata-invalid-timestamp/maven-metadata.xml create mode 100644 maven-compat/src/test/resources/metadata-invalid-token/maven-metadata.xml 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 14f73e12907d..070bfe04eacd 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); } } @@ -279,9 +291,57 @@ protected Metadata readMetadata(File mappingFile) throws RepositoryMetadataReadE throw new RepositoryMetadataReadException( "Cannot read metadata from '" + mappingFile + "': " + e.getMessage(), e); } + + validateVersioning(result); + return result; } + /** + * Version tokens adopted from repository metadata must be valid coordinate components; metadata carrying + * anything else is treated as invalid. + */ + private static void validateVersioning(Metadata metadata) throws RepositoryMetadataReadException { + if (metadata == null) { + return; + } + Versioning versioning = metadata.getVersioning(); + if (versioning == null) { + return; + } + validateVersionToken(versioning.getLatest()); + validateVersionToken(versioning.getRelease()); + for (String version : versioning.getVersions()) { + validateVersionToken(version); + } + for (SnapshotVersion snapshotVersion : versioning.getSnapshotVersions()) { + validateVersionToken(snapshotVersion.getVersion()); + } + Snapshot snapshot = versioning.getSnapshot(); + if (snapshot != null) { + validateVersionToken(snapshot.getTimestamp()); + } + } + + private static void validateVersionToken(String value) throws RepositoryMetadataReadException { + if (value == null || value.isEmpty()) { + return; + } + boolean valid = !"..".equals(value); + if (valid) { + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '/' || c == '\\' || c == ':' || Character.isISOControl(c)) { + valid = false; + break; + } + } + } + if (!valid) { + throw new RepositoryMetadataReadException("Metadata contains an invalid version token: '" + value + "'"); + } + } + /** * Ensures the last updated timestamp of the specified metadata does not refer to the future and fixes the local * metadata if necessary to allow proper merging/updating of metadata during deployment. @@ -354,7 +414,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() @@ -380,6 +440,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/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/resources/metadata-invalid-timestamp/maven-metadata.xml b/maven-compat/src/test/resources/metadata-invalid-timestamp/maven-metadata.xml new file mode 100644 index 000000000000..68cf46a41f3d --- /dev/null +++ b/maven-compat/src/test/resources/metadata-invalid-timestamp/maven-metadata.xml @@ -0,0 +1,31 @@ + + + + org.apache.maven.its + dep-invalid-timestamp + 1.0-SNAPSHOT + + + 20120809.112920:1 + 1 + + 20120809112920 + + 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 new file mode 100644 index 000000000000..8f4eb7200c7b --- /dev/null +++ b/maven-compat/src/test/resources/metadata-invalid-token/maven-metadata.xml @@ -0,0 +1,28 @@ + + + + org.apache.maven.its + dep-invalid-token + 1.0-SNAPSHOT + + 1.0:2.0 + 20120809112920 + + From a707ec44183635addcadcd1fd079051a0a3fd2e3 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Mon, 31 Aug 2026 22:38:49 +0200 Subject: [PATCH 3/3] Honour configured checksum policy on the legacy compat paths DefaultRepositoryMetadataManager.getArtifactMetadataFromDeploymentRepository and LegacyRepositorySystem.retrieve hardcoded checksumPolicy=warn for every transfer, so a repository configured with checksumPolicy=fail (or the global -C/--strict-checksums flag) was not honored on the deploy-metadata fetch or on generic retrieve() calls. Resolve the effective policy from the repository instead: the metadata fetch uses the same per-metadata policy resolution the download path already uses, and retrieve() takes the stricter of the repository's release and snapshot policies since a generic path cannot be classified as either. Backport of the corresponding fix from PR #12945 (maven-4.0.x). Co-Authored-By: Claude Opus 4.6 --- .../legacy/LegacyRepositorySystem.java | 35 +++++++- .../DefaultRepositoryMetadataManagerTest.java | 83 +++++++++++++++++++ .../legacy/LegacyRepositorySystemTest.java | 27 ++++++ 3 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 maven-compat/src/test/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManagerTest.java 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 new file mode 100644 index 000000000000..e4bf982c3274 --- /dev/null +++ b/maven-compat/src/test/java/org/apache/maven/artifact/repository/metadata/DefaultRepositoryMetadataManagerTest.java @@ -0,0 +1,83 @@ +/* + * 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 javax.inject.Inject; +import javax.inject.Named; + +import java.io.File; +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.assertThrows; + +/** + * Tests {@link DefaultRepositoryMetadataManager}. + */ +@PlexusTest +@Deprecated +class DefaultRepositoryMetadataManagerTest extends AbstractArtifactComponentTest { + + @Inject + private RepositoryMetadataManager repositoryMetadataManager; + + @Inject + @Named("default") + private ArtifactRepositoryLayout layout; + + @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)); + } +} 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)); + } }