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 <[email protected]>
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)