diff --git a/src/main/java/org/apache/maven/plugin/resources/remote/ModelInheritanceAssembler.java b/src/main/java/org/apache/maven/plugin/resources/remote/ModelInheritanceAssembler.java index 1da5c2e..d48bb0d 100644 --- a/src/main/java/org/apache/maven/plugin/resources/remote/ModelInheritanceAssembler.java +++ b/src/main/java/org/apache/maven/plugin/resources/remote/ModelInheritanceAssembler.java @@ -18,13 +18,14 @@ */ package org.apache.maven.plugin.resources.remote; +import java.io.File; +import java.nio.file.InvalidPathException; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.LinkedHashMap; -import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Properties; -import java.util.StringTokenizer; import java.util.TreeMap; import org.apache.maven.model.Build; @@ -568,51 +569,34 @@ protected String appendPath(String parentPath, String childPath, String pathAdju uncleanPath = uncleanPath.substring(protocolIdx + 3); } - if (uncleanPath.startsWith("/")) { - cleanedPath += "/"; - } - return cleanedPath + resolvePath(uncleanPath); } - // TODO Move this to plexus-utils' PathTool. - private static String resolvePath(String uncleanPath) { - LinkedList pathElements = new LinkedList<>(); - - StringTokenizer tokenizer = new StringTokenizer(uncleanPath, "/"); - - while (tokenizer.hasMoreTokens()) { - String token = tokenizer.nextToken(); - - switch (token) { - case "": - // Empty path entry ("...//.."), remove. - break; - case "..": - if (pathElements.isEmpty()) { - // FIXME: somehow report to the user - // that there are too many '..' elements. - // For now, ignore the extra '..'. - } else { - pathElements.removeLast(); - } - break; - default: - pathElements.addLast(token); - break; - } - } + /** + * Normalizes the path part of an SCM URL using {@link java.nio.file.Path#normalize()}. + * + */ + private String resolvePath(String uncleanPath) { + boolean trailingSeparator = uncleanPath.endsWith("/"); - StringBuilder cleanedPath = new StringBuilder(); + String resolved; + try { + resolved = Paths.get(uncleanPath).normalize().toString().replace(File.separatorChar, '/'); + } catch (InvalidPathException e) { + resolved = uncleanPath; + } - while (!pathElements.isEmpty()) { - cleanedPath.append(pathElements.removeFirst()); - if (!pathElements.isEmpty()) { - cleanedPath.append('/'); - } + if (trailingSeparator && !resolved.endsWith("/")) { + resolved += "/"; } - return cleanedPath.toString(); + return resolved; } private static void mergeExtensionLists(Build childBuild, Build parentBuild) { diff --git a/src/test/java/org/apache/maven/plugin/resources/remote/ModelInheritanceAssemblerTest.java b/src/test/java/org/apache/maven/plugin/resources/remote/ModelInheritanceAssemblerTest.java new file mode 100644 index 0000000..cd023cc --- /dev/null +++ b/src/test/java/org/apache/maven/plugin/resources/remote/ModelInheritanceAssemblerTest.java @@ -0,0 +1,59 @@ +/* + * 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.plugin.resources.remote; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Unit tests for the SCM path normalization in {@link ModelInheritanceAssembler}. + */ +public class ModelInheritanceAssemblerTest { + + private final ModelInheritanceAssembler assembler = new ModelInheritanceAssembler(); + + @Test + public void appendPathPreservesTrailingSlash() { + assertEquals( + "http://svn.example.com/repo/", + assembler.appendPath("http://svn.example.com/repo/", null, null, false)); + } + + @Test + public void appendPathAppendsChild() { + assertEquals( + "http://svn.example.com/repo/child", + assembler.appendPath("http://svn.example.com/repo", "child", null, true)); + } + + @Test + public void appendPathCollapsesDotSegment() { + assertEquals( + "http://svn.example.com/repo/child", + assembler.appendPath("http://svn.example.com/repo/./child", null, null, false)); + } + + @Test + public void appendPathResolvesParentDirectory() { + assertEquals( + "http://svn.example.com/repos", + assembler.appendPath("http://svn.example.com/repos/project/..", null, null, false)); + } +}