From ab471921fbfabd15e03eb4571784c15c1e48ae81 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 12 Aug 2026 19:12:55 +0200 Subject: [PATCH 1/2] [MNG-8129] Handle InvalidPathException for broken relativePath on Windows Some POMs on Maven Central have set to a groupId:artifactId coordinate (e.g. artemis-project-2.33.0.pom uses org.apache:apache) instead of a proper filesystem path. On Windows, colons are reserved for drive letters, so Path.resolve() throws InvalidPathException before any file-existence check can run. Catch InvalidPathException in both BuildPathSource.resolve() (new API) and FileModelSource.getRelatedSource() (compat) and return null, letting the caller fall through to repository-based parent resolution. This matches Maven 3.x behavior where the file-existence check after Path.resolve() made the invalid path harmless on all platforms. Closes #12738 Co-Authored-By: Claude Opus 4.6 --- .../apache/maven/api/services/Sources.java | 8 +++++++- .../maven/api/services/SourcesTest.java | 20 +++++++++++++++++++ .../maven/model/building/FileModelSource.java | 8 +++++++- .../model/building/FileModelSourceTest.java | 16 +++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/Sources.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/Sources.java index 73d8978424cc..fce9e43e9a88 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/Sources.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/Sources.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.Objects; @@ -238,7 +239,12 @@ public Source resolve(@Nonnull String relative) { @Nullable public ModelSource resolve(@Nonnull ModelLocator locator, @Nonnull String relative) { String norm = relative.replace('\\', File.separatorChar).replace('/', File.separatorChar); - Path path = getPath().getParent().resolve(norm); + Path path; + try { + path = getPath().getParent().resolve(norm); + } catch (InvalidPathException e) { + return null; + } Path relatedPom = locator.locateExistingPom(path); if (relatedPom != null) { return new BuildPathSource(relatedPom); diff --git a/api/maven-api-core/src/test/java/org/apache/maven/api/services/SourcesTest.java b/api/maven-api-core/src/test/java/org/apache/maven/api/services/SourcesTest.java index 58aec58f1e39..2e1cc5e171c3 100644 --- a/api/maven-api-core/src/test/java/org/apache/maven/api/services/SourcesTest.java +++ b/api/maven-api-core/src/test/java/org/apache/maven/api/services/SourcesTest.java @@ -146,4 +146,24 @@ void testNullHandling() { assertThrows(NullPointerException.class, () -> Sources.buildSource(null)); assertThrows(NullPointerException.class, () -> Sources.resolvedSource(null, "modelId")); } + + /** + * Tests that BuildPathSource.resolve() gracefully handles relative paths + * that are not valid filesystem paths (e.g. containing ':' which is illegal + * on Windows) by returning null instead of throwing InvalidPathException. + * This reproduces MNG-8129. + */ + @Test + void testBuildPathSourceResolveWithInvalidPath() throws IOException { + Path pomFile = tempDir.resolve("pom.xml"); + Files.writeString(pomFile, ""); + + Sources.BuildPathSource source = (Sources.BuildPathSource) Sources.buildSource(pomFile); + ModelSource.ModelLocator locator = mock(ModelSource.ModelLocator.class); + when(locator.locateExistingPom(any(Path.class))).thenReturn(null); + + // Must not throw InvalidPathException on any platform (MNG-8129) + ModelSource result = source.resolve(locator, "org.apache:apache"); + assertNull(result); + } } diff --git a/compat/maven-model-builder/src/main/java/org/apache/maven/model/building/FileModelSource.java b/compat/maven-model-builder/src/main/java/org/apache/maven/model/building/FileModelSource.java index f1a4e150495f..ef0944e08110 100644 --- a/compat/maven-model-builder/src/main/java/org/apache/maven/model/building/FileModelSource.java +++ b/compat/maven-model-builder/src/main/java/org/apache/maven/model/building/FileModelSource.java @@ -21,6 +21,7 @@ import java.io.File; import java.net.URI; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import org.apache.maven.building.FileSource; @@ -61,7 +62,12 @@ public File getPomFile() { public ModelSource2 getRelatedSource(String relPath) { relPath = relPath.replace('\\', File.separatorChar).replace('/', File.separatorChar); - Path relatedPom = getPath().getParent().resolve(relPath); + Path relatedPom; + try { + relatedPom = getPath().getParent().resolve(relPath); + } catch (InvalidPathException e) { + return null; + } if (Files.isDirectory(relatedPom)) { // TODO figure out how to reuse ModelLocator.locatePom(File) here diff --git a/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileModelSourceTest.java b/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileModelSourceTest.java index 9f5d43427fe3..e13fc6a92ee8 100644 --- a/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileModelSourceTest.java +++ b/compat/maven-model-builder/src/test/java/org/apache/maven/model/building/FileModelSourceTest.java @@ -65,6 +65,22 @@ void testWindowsPaths() throws Exception { "Expected " + upperCaseFileSource + " to equal " + lowerCaseFileSource); } + /** + * Tests that getRelatedSource() gracefully handles relative paths that are + * not valid filesystem paths (e.g. containing ':' which is illegal on Windows) + * by returning null instead of throwing InvalidPathException. + * This reproduces MNG-8129. + */ + @Test + void testGetRelatedSourceWithInvalidRelativePath() throws Exception { + File tempFile = createTempFile("pomTest"); + FileModelSource source = new FileModelSource(tempFile); + + // Must not throw InvalidPathException on any platform (MNG-8129) + ModelSource2 result = source.getRelatedSource("org.apache:apache"); + org.junit.jupiter.api.Assertions.assertNull(result); + } + private File createTempFile(String name) throws IOException { File tempFile = File.createTempFile(name, ".xml"); tempFile.deleteOnExit(); From 0f2e90972a941da8ad5bacfc7629ddc4fabf95a5 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 12 Aug 2026 22:01:50 +0200 Subject: [PATCH 2/2] [MNG-8129] Validate relativePath for illegal filesystem characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reject values containing characters that are illegal in filesystem paths (: " < > | ? *) during model validation. This catches nonsensical values like "org.apache:apache" early, with a clear error message, complementing the InvalidPathException catch in the resolution layer. The check uses errOn31 severity — WARNING for compat (STRICT=3.0) and ERROR for the new API (STRICT=4.2). Co-Authored-By: Claude Opus 4.6 --- .../validation/DefaultModelValidator.java | 19 +++++++++++ .../validation/DefaultModelValidatorTest.java | 10 ++++++ .../validation/bad-parent-relativePath.xml | 33 +++++++++++++++++++ .../impl/model/DefaultModelValidator.java | 19 +++++++++++ .../impl/model/DefaultModelValidatorTest.java | 10 ++++++ .../validation/bad-parent-relativePath.xml | 33 +++++++++++++++++++ 6 files changed, 124 insertions(+) create mode 100644 compat/maven-model-builder/src/test/resources/poms/validation/bad-parent-relativePath.xml create mode 100644 impl/maven-impl/src/test/resources/poms/validation/bad-parent-relativePath.xml diff --git a/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java b/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java index e1f2b40d9471..7d73487b4c18 100644 --- a/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java +++ b/compat/maven-model-builder/src/main/java/org/apache/maven/model/validation/DefaultModelValidator.java @@ -84,6 +84,8 @@ public class DefaultModelValidator implements ModelValidator { private static final String ILLEGAL_FS_CHARS = "\\/:\"<>|?*"; + private static final String ILLEGAL_RELATIVE_PATH_CHARS = ":\"<>|?*"; + private static final String ILLEGAL_VERSION_CHARS = ILLEGAL_FS_CHARS; private static final String ILLEGAL_REPO_ID_CHARS = ILLEGAL_FS_CHARS; @@ -156,6 +158,23 @@ public void validateRawModel(Model m, ModelBuildingRequest request, ModelProblem } } else if (request.getValidationLevel() >= ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_2_0) { Severity errOn30 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_0); + Severity errOn31 = getSeverity(request, ModelBuildingRequest.VALIDATION_LEVEL_MAVEN_3_1); + + // [MNG-8129] Validate that relativePath does not contain characters that are illegal in filesystem paths + if (parent != null + && parent.getRelativePath() != null + && !parent.getRelativePath().isEmpty()) { + validateBannedCharacters( + "parent.", + "relativePath", + problems, + errOn31, + Version.V20, + parent.getRelativePath(), + null, + parent, + ILLEGAL_RELATIVE_PATH_CHARS); + } // [MNG-6074] Maven should produce an error if no model version has been set in a POM file used to build an // effective model. diff --git a/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java b/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java index 4d72d8e01692..a8be8963d9dd 100644 --- a/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java +++ b/compat/maven-model-builder/src/test/java/org/apache/maven/model/validation/DefaultModelValidatorTest.java @@ -477,6 +477,16 @@ void testDistributionManagementStatus() throws Exception { assertTrue(result.getErrors().get(0).contains("distributionManagement.status")); } + @Test + void testBadParentRelativePath() throws Exception { + SimpleProblemCollector result = validateRaw("bad-parent-relativePath.xml"); + + assertViolations(result, 0, 0, 1); + + assertContains(result.getWarnings().get(0), "parent.relativePath"); + assertContains(result.getWarnings().get(0), "must not contain any of these characters"); + } + @Test void testIncompleteParent() throws Exception { SimpleProblemCollector result = validateRaw("incomplete-parent.xml"); diff --git a/compat/maven-model-builder/src/test/resources/poms/validation/bad-parent-relativePath.xml b/compat/maven-model-builder/src/test/resources/poms/validation/bad-parent-relativePath.xml new file mode 100644 index 000000000000..4bd9a4d469c8 --- /dev/null +++ b/compat/maven-model-builder/src/test/resources/poms/validation/bad-parent-relativePath.xml @@ -0,0 +1,33 @@ + + + + 4.0.0 + + + org.apache + apache + 1 + org.apache:apache + + + aid + gid + 0.1 + diff --git a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java index 0c8739a22ade..bb023b7c33cc 100644 --- a/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java +++ b/impl/maven-impl/src/main/java/org/apache/maven/impl/model/DefaultModelValidator.java @@ -94,6 +94,8 @@ public class DefaultModelValidator implements ModelValidator { private static final String ILLEGAL_FS_CHARS = "\\/:\"<>|?*"; + private static final String ILLEGAL_RELATIVE_PATH_CHARS = ":\"<>|?*"; + private static final String ILLEGAL_VERSION_CHARS = ILLEGAL_FS_CHARS; private static final String ILLEGAL_REPO_ID_CHARS = ILLEGAL_FS_CHARS; @@ -479,6 +481,23 @@ && equals(parent.getArtifactId(), model.getArtifactId())) { } Severity errOn30 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_0); + Severity errOn31 = getSeverity(validationLevel, ModelValidator.VALIDATION_LEVEL_MAVEN_3_1); + + // [MNG-8129] Validate that relativePath does not contain characters that are illegal in filesystem paths + if (parent != null + && parent.getRelativePath() != null + && !parent.getRelativePath().isEmpty()) { + validateBannedCharacters( + "parent.", + "relativePath", + problems, + errOn31, + Version.V20, + parent.getRelativePath(), + null, + parent, + ILLEGAL_RELATIVE_PATH_CHARS); + } boolean isModelVersion41OrMore = !Objects.equals(ModelBuilder.MODEL_VERSION_4_0_0, model.getModelVersion()); if (isModelVersion41OrMore) { diff --git a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java index 64b77dbabf37..f79500592ed2 100644 --- a/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java +++ b/impl/maven-impl/src/test/java/org/apache/maven/impl/model/DefaultModelValidatorTest.java @@ -547,6 +547,16 @@ void testDistributionManagementStatus() throws Exception { assertTrue(result.getErrors().get(0).contains("distributionManagement.status")); } + @Test + void testBadParentRelativePath() throws Exception { + SimpleProblemCollector result = validateFile("bad-parent-relativePath.xml"); + + assertViolations(result, 0, 1, 0); + + assertContains(result.getErrors().get(0), "parent.relativePath"); + assertContains(result.getErrors().get(0), "must not contain any of these characters"); + } + @Test void testIncompleteParent() throws Exception { SimpleProblemCollector result = validateRaw("incomplete-parent.xml"); diff --git a/impl/maven-impl/src/test/resources/poms/validation/bad-parent-relativePath.xml b/impl/maven-impl/src/test/resources/poms/validation/bad-parent-relativePath.xml new file mode 100644 index 000000000000..4bd9a4d469c8 --- /dev/null +++ b/impl/maven-impl/src/test/resources/poms/validation/bad-parent-relativePath.xml @@ -0,0 +1,33 @@ + + + + 4.0.0 + + + org.apache + apache + 1 + org.apache:apache + + + aid + gid + 0.1 +