This is an automated email from the ASF dual-hosted git repository. slachiewicz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/maven-remote-resources-plugin.git
commit 205a0cd5f0eb2eeb577d5bfb429e47200f3a7862 Author: Sylwester Lachiewicz <[email protected]> AuthorDate: Wed Oct 28 09:32:22 2026 +0000 Reject a licenseMapping whose spdxId is not an SPDX ID or expression --- .../remote/AbstractProcessRemoteResourcesMojo.java | 10 +++-- .../plugin/resources/remote/SpdxLicenses.java | 46 ++++++++++++++++++---- .../plugin/resources/remote/SpdxLicensesTest.java | 10 +++++ 3 files changed, 55 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java b/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java index c6cecfb..8367976 100644 --- a/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java +++ b/src/main/java/org/apache/maven/plugin/resources/remote/AbstractProcessRemoteResourcesMojo.java @@ -746,11 +746,15 @@ public abstract class AbstractProcessRemoteResourcesMojo extends AbstractMojo { protected void validate() throws MojoExecutionException { if (licenseMappings != null) { + SpdxLicenses spdx = new SpdxLicenses(); for (LicenseMapping mapping : licenseMappings) { - if (SpdxLicenses.isBlank(mapping.getSpdxId()) - || (SpdxLicenses.isBlank(mapping.getName()) && SpdxLicenses.isBlank(mapping.getUrl()))) { + if (SpdxLicenses.isBlank(mapping.getName()) && SpdxLicenses.isBlank(mapping.getUrl())) { + throw new MojoExecutionException("Each licenseMapping needs a name, a url or both: " + mapping); + } + if (spdx.canonicalMappingId(mapping.getSpdxId()) == null) { throw new MojoExecutionException( - "Each licenseMapping needs an spdxId and a name, a url or both: " + mapping); + "The spdxId of a licenseMapping must be an SPDX license ID, such as MIT, or an SPDX " + + "license expression, such as Apache-2.0 OR MIT: " + mapping); } } } diff --git a/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java b/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java index 6e34ce3..a6e980a 100644 --- a/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java +++ b/src/main/java/org/apache/maven/plugin/resources/remote/SpdxLicenses.java @@ -61,6 +61,8 @@ public class SpdxLicenses { private static final Pattern LICENSE_REF = Pattern.compile("LicenseRef-[A-Za-z0-9.-]+"); + private static final Pattern ID_SHAPE = Pattern.compile("[A-Za-z0-9][A-Za-z0-9.+-]*"); + private static final Pattern ID_URL = Pattern.compile("(?:opensource\\.org/licenses?|spdx\\.org/licenses)/([^/?]+)"); @@ -173,7 +175,7 @@ public class SpdxLicenses { public String id(String name, String url) { for (LicenseMapping mapping : mappings) { if (matches(mapping.getName(), name, true) && matches(mapping.getUrl(), url, false)) { - return mapping.getSpdxId().trim(); + return canonicalMappingId(mapping.getSpdxId()); } } String byName = isBlank(name) ? null : idOfName(name.trim()); @@ -187,7 +189,7 @@ public class SpdxLicenses { private String idOfName(String name) { String id = knownId(name); if (id == null) { - id = parseExpression(name); + id = parseExpression(name, false, true); } if (id == null) { id = names.get(normaliseName(name)); @@ -206,29 +208,54 @@ public class SpdxLicenses { return id; } + /** + * Returns the SPDX ID or expression a {@link LicenseMapping} declares, with known IDs in their SPDX spelling, + * or {@code null} unless it is a well-formed expression. IDs the built-in table does not know are accepted + * as declared, since a mapping exists to name them. + */ + String canonicalMappingId(String spdxId) { + if (isBlank(spdxId)) { + return null; + } + String id = knownId(spdxId.trim(), true); + return id != null ? id : parseExpression(spdxId, true, false); + } + /** Returns the ID in its SPDX spelling, a <code>LicenseRef-</code> ID as declared, or {@code null}. */ private String knownId(String token) { + return knownId(token, false); + } + + private String knownId(String token, boolean anyIdShape) { String id = ids.get(token.toLowerCase(Locale.ROOT)); if (id == null && LICENSE_REF.matcher(token).matches()) { id = token; } + if (id == null && anyIdShape && ID_SHAPE.matcher(token).matches() && !isOperator(token)) { + id = token; + } return id; } + private static boolean isOperator(String token) { + return token.equalsIgnoreCase("AND") || token.equalsIgnoreCase("OR") || token.equalsIgnoreCase("WITH"); + } + /** * Returns the expression with each ID in its SPDX spelling, or {@code null} unless it is a well-formed * SPDX license expression that combines known IDs with <code>AND</code>, <code>OR</code> or * <code>WITH</code>. */ - private String parseExpression(String declared) { + private String parseExpression(String declared, boolean anyIdShape, boolean needsOperator) { List<String> tokens = new ArrayList<>(); Matcher m = EXPRESSION_TOKEN.matcher(declared); while (m.find()) { tokens.add(m.group()); } - ExpressionParser parser = new ExpressionParser(tokens); + ExpressionParser parser = new ExpressionParser(tokens, anyIdShape); String result = parser.expression(); - return result != null && parser.next == tokens.size() && parser.operators > 0 ? result : null; + boolean complete = result != null && parser.next == tokens.size(); + return complete && (parser.operators > 0 || !needsOperator) ? result : null; } /** Recursive descent over <code>term ((AND | OR) term)*</code>, where a term is an ID, optionally @@ -240,8 +267,11 @@ public class SpdxLicenses { private int operators; - ExpressionParser(List<String> tokens) { + private final boolean anyIdShape; + + ExpressionParser(List<String> tokens, boolean anyIdShape) { this.tokens = tokens; + this.anyIdShape = anyIdShape; } String expression() { @@ -280,14 +310,14 @@ public class SpdxLicenses { } return "(" + inner + ")"; } - String id = knownId(token); + String id = knownId(token, anyIdShape); if (id == null) { return null; } if (next < tokens.size() && tokens.get(next).equalsIgnoreCase("WITH")) { next++; operators++; - String exception = next < tokens.size() ? knownId(tokens.get(next++)) : null; + String exception = next < tokens.size() ? knownId(tokens.get(next++), anyIdShape) : null; return exception == null ? null : id + " WITH " + exception; } return id; diff --git a/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java b/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java index 97c2453..f8296d6 100644 --- a/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java +++ b/src/test/java/org/apache/maven/plugin/resources/remote/SpdxLicensesTest.java @@ -155,6 +155,16 @@ class SpdxLicensesTest { assertEquals("MIT", mapped.id("MIT", null)); } + @Test + void mappingIds() { + assertEquals("Zlib", spdx.canonicalMappingId("Zlib")); + assertEquals("Apache-2.0 OR MIT", spdx.canonicalMappingId(" apache-2.0 or mit ")); + assertEquals("LicenseRef-Widget", spdx.canonicalMappingId("LicenseRef-Widget")); + assertNull(spdx.canonicalMappingId("Apache License 2.0")); + assertNull(spdx.canonicalMappingId("OR")); + assertNull(spdx.canonicalMappingId("")); + } + private static LicenseMapping mapping(String spdxId, String name, String url) { LicenseMapping mapping = new LicenseMapping(); mapping.setSpdxId(spdxId);
