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)

Reply via email to