This is an automated email from the ASF dual-hosted git repository. maartenc pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/ant-ivy.git
The following commit(s) were added to refs/heads/master by this push: new 75b55433 IVY-1660: the ivy:retrieve task failed when the retrieve pattern contained some text in parentheses before the first token 75b55433 is described below commit 75b554334ebd549f935ddd6610795a6fb51e5dd7 Author: Maarten Coene <4728619+maart...@users.noreply.github.com> AuthorDate: Fri Aug 1 22:41:32 2025 +0200 IVY-1660: the ivy:retrieve task failed when the retrieve pattern contained some text in parentheses before the first token --- asciidoc/release-notes.adoc | 1 + src/java/org/apache/ivy/core/IvyPatternHelper.java | 59 +++++++++++++++++----- .../org/apache/ivy/util/IvyPatternHelperTest.java | 15 +++--- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/asciidoc/release-notes.adoc b/asciidoc/release-notes.adoc index e821260c..d6c1a6ab 100644 --- a/asciidoc/release-notes.adoc +++ b/asciidoc/release-notes.adoc @@ -55,6 +55,7 @@ Note, if you have resolved dependencies with version of Ivy prior to 2.6.0, you - NEW: added a new `nearest` conflict manager, which handles conflicts in the same way that Maven does. (IVY-813) (Thanks to Eric Milles) - IMPROVEMENT: use Apache Commons Compress for pack200 handling to avoid issues on Java 14 and later. If pack200 is needed, make sure to add Apache Commons Compress to your classpath. (IVY-1652) - FIX: improved Maven dependencyManagement matching for dependencies with a non-default type or classifier (IVY-1654) (Thanks to Mark Kittisopikul) +- FIX: the `ivy:retrieve` task failed when the retrieve pattern contained some text in parentheses before the first token, for instance: `/jobs/lib (JDK 17)/[artifact].[ext]` (IVY-1660) == Committers and Contributors diff --git a/src/java/org/apache/ivy/core/IvyPatternHelper.java b/src/java/org/apache/ivy/core/IvyPatternHelper.java index 3614ac78..c9a17148 100644 --- a/src/java/org/apache/ivy/core/IvyPatternHelper.java +++ b/src/java/org/apache/ivy/core/IvyPatternHelper.java @@ -18,6 +18,7 @@ package org.apache.ivy.core; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -166,7 +167,7 @@ public final class IvyPatternHelper { tokens.put(ORIGINAL_ARTIFACTNAME_KEY, new OriginalArtifactNameValue(origin)); } - return substituteTokens(pattern, tokens, false); + return substituteTokens(pattern, tokens, false, true); } // CheckStyle:ParameterNumber ON @@ -225,10 +226,10 @@ public final class IvyPatternHelper { public static String substituteTokens(String pattern, Map<String, String> tokens) { Map<String, Object> tokensCopy = new HashMap<>(); tokensCopy.putAll(tokens); - return substituteTokens(pattern, tokensCopy, true); + return substituteTokens(pattern, tokensCopy, true, true); } - private static String substituteTokens(String pattern, Map<String, Object> tokens, boolean external) { + private static String substituteTokens(String pattern, Map<String, Object> tokens, boolean external, boolean checkPathTraversal) { Map<String, Object> tokensCopy = external ? tokens : new HashMap<>(tokens); if (tokensCopy.containsKey(ORGANISATION_KEY) && !tokensCopy.containsKey(ORGANISATION_KEY2)) { tokensCopy.put(ORGANISATION_KEY2, tokensCopy.get(ORGANISATION_KEY)); @@ -330,7 +331,10 @@ public final class IvyPatternHelper { } String afterTokenSubstitution = buffer.toString(); - checkAgainstPathTraversal(pattern, afterTokenSubstitution); + if (checkPathTraversal) { + checkAgainstPathTraversal(pattern, afterTokenSubstitution); + } + return afterTokenSubstitution; } @@ -493,18 +497,49 @@ public final class IvyPatternHelper { } public static String getTokenRoot(String pattern) { - int index = pattern.indexOf('['); + String token = getFirstToken(pattern); + if (token == null) { + // no token found, return the whole pattern + return pattern; + } + + int index = pattern.indexOf('[' + token + ']'); if (index == -1) { + // should not happen, but just in case return pattern; - } else { - // it could be that pattern is something like "lib/([optional]/)[module]" - // we don't want the '(' in the result - int optionalIndex = pattern.indexOf('('); - if (optionalIndex >= 0) { - index = Math.min(index, optionalIndex); + } + + // to tackle optional token parts, we follow this strategy: + // 1. substitute the token with a dummy value (e.g. "xxx") + // 2. substitute the token with an empty value + // 3. compare the two results and find the first character that is different + // -> this character is the first character that is not part of the root + String sub1 = substituteTokens(pattern, Collections.singletonMap(token, "xxx"), false, false); + String sub2 = substituteTokens(pattern, new HashMap<>(), true, false); + + // due to the optional part, the second substitution could result in a shorter string + index = Math.min(index, sub2.length()); + + // now we compare the two strings character by character until we find a difference + for (int i = 0; i < index; i++) { + if (sub1.charAt(i) != sub2.charAt(i)) { + // we found the first character that is different, so we can return the root + index = i; + break; + } + } + + // now let's find the last path separator before that index + // this tackles cases like "lib/config-[conf]/[module]" where we want to return "lib/" as root + for (int i = index - 1; i >= 0; i--) { + char c = sub1.charAt(i); + if (c == '/' || c == '\\') { + index = i + 1; // we want to include the separator in the result + break; } - return pattern.substring(0, index); } + + return sub1.substring(0, index); } public static String getFirstToken(String pattern) { diff --git a/test/java/org/apache/ivy/util/IvyPatternHelperTest.java b/test/java/org/apache/ivy/util/IvyPatternHelperTest.java index cee720d0..218136a6 100644 --- a/test/java/org/apache/ivy/util/IvyPatternHelperTest.java +++ b/test/java/org/apache/ivy/util/IvyPatternHelperTest.java @@ -72,14 +72,13 @@ public class IvyPatternHelperTest { @Test public void testTokenRoot() { - String pattern = "lib/[type]/[artifact].[ext]"; - assertEquals("lib/", IvyPatternHelper.getTokenRoot(pattern)); - } - - @Test - public void testTokenRootWithOptionalFirstToken() { - String pattern = "lib/([type]/)[artifact].[ext]"; - assertEquals("lib/", IvyPatternHelper.getTokenRoot(pattern)); + assertEquals("lib/", IvyPatternHelper.getTokenRoot("lib/[type]/[artifact].[ext]")); + assertEquals("lib/", IvyPatternHelper.getTokenRoot("lib/([type]/)[artifact].[ext]")); + assertEquals("lib/", IvyPatternHelper.getTokenRoot("lib(/[type])/[artifact].[ext]")); + assertEquals("lib/", IvyPatternHelper.getTokenRoot("lib/(type-[type]/)[artifact].[ext]")); + assertEquals("lib/", IvyPatternHelper.getTokenRoot("lib(/type-[type])/[artifact].[ext]")); + assertEquals("lib/", IvyPatternHelper.getTokenRoot("lib/([type]/)")); + assertEquals("lib/lib (JDK 17)/", IvyPatternHelper.getTokenRoot("lib/lib (JDK 17)/[artifact].[ext]")); //IVY-1660 } @Test(expected = IllegalArgumentException.class)