Skip to content

Commit 39f9848

Browse files
committed
Check for invalid characters when adding an extension
* Fixes #49895 * use a pattern matcher to validate the groupId and artifactId for a fully qualified extension name (group, artifact, and version) * introduced a new collection of invalid keywords so they can be reported separately from those that are not in the catalog * added some tests that try to mimic manual tests that have been used Signed-off-by:Nathan Erwin <[email protected]>
1 parent 374d258 commit 39f9848

File tree

3 files changed

+115
-8
lines changed

3 files changed

+115
-8
lines changed

independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/commands/handlers/AddExtensionsCommandHandler.java

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import java.util.Collections;
1111
import java.util.List;
1212
import java.util.Set;
13+
import java.util.regex.Pattern;
1314

1415
import org.apache.commons.lang3.StringUtils;
1516

@@ -34,6 +35,8 @@
3435
*/
3536
public class AddExtensionsCommandHandler implements QuarkusCommandHandler {
3637

38+
private static final Pattern VALID_IDENTIFIER = Pattern.compile("[A-Za-z0-9_.-]+");
39+
3740
@Override
3841
public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws QuarkusCommandException {
3942
final Set<String> extensionsQuery = invocation.getValue(AddExtensions.EXTENSIONS, Collections.emptySet());
@@ -65,11 +68,21 @@ public QuarkusCommandOutcome execute(QuarkusCommandInvocation invocation) throws
6568
.info(MessageIcons.NOOP_ICON + " Extension " + a.getGroupId() + ":" + a.getArtifactId()
6669
+ " was already installed"));
6770
return QuarkusCommandOutcome.success().setValue(AddExtensions.OUTCOME_UPDATED, result.isSourceUpdated());
68-
} else if (!extensionInstallPlan.getUnmatchedKeywords().isEmpty()) {
69-
invocation.log()
70-
.info(ERROR_ICON + " Nothing installed because keyword(s) '"
71-
+ String.join("', '", extensionInstallPlan.getUnmatchedKeywords())
72-
+ "' were not matched in the catalog.");
71+
} else if (!extensionInstallPlan.getUnmatchedKeywords().isEmpty()
72+
|| !extensionInstallPlan.getInvalidKeywords().isEmpty()) {
73+
if (!extensionInstallPlan.getUnmatchedKeywords().isEmpty()) {
74+
invocation.log()
75+
.info(ERROR_ICON + " Nothing installed because keyword(s) '"
76+
+ String.join("', '", extensionInstallPlan.getUnmatchedKeywords())
77+
+ "' were not matched in the catalog.");
78+
}
79+
80+
if (!extensionInstallPlan.getInvalidKeywords().isEmpty()) {
81+
invocation.log()
82+
.info(FAILURE_ICON + " Nothing installed because keyword(s) '"
83+
+ String.join("', '", extensionInstallPlan.getInvalidKeywords())
84+
+ "' were invalid.");
85+
}
7386
} else {
7487
invocation.log()
7588
.info(FAILURE_ICON + " The provided keyword(s) did not match any extension from the catalog.");
@@ -107,7 +120,13 @@ public ExtensionInstallPlan planInstallation(QuarkusCommandInvocation invocation
107120
int countColons = StringUtils.countMatches(keyword, ":");
108121
if (countColons > 1) {
109122
// it's a gav
110-
builder.addIndependentExtension(ArtifactCoords.fromString(keyword));
123+
ArtifactCoords coords = ArtifactCoords.fromString(keyword);
124+
if (VALID_IDENTIFIER.matcher(coords.getGroupId()).matches()
125+
&& VALID_IDENTIFIER.matcher(coords.getArtifactId()).matches()) {
126+
builder.addIndependentExtension(coords);
127+
} else {
128+
builder.addInvalidKeyword(keyword);
129+
}
111130
continue;
112131
}
113132
List<Extension> listed = List.of();

independent-projects/tools/devtools-common/src/main/java/io/quarkus/devtools/project/extensions/ExtensionInstallPlan.java

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,25 @@ public class ExtensionInstallPlan {
1414
Collections.emptySet(),
1515
Collections.emptySet(),
1616
Collections.emptySet(),
17+
Collections.emptySet(),
1718
Collections.emptySet());
1819

1920
private final Set<ArtifactCoords> platforms;
2021
private final Set<ArtifactCoords> managedExtensions;
2122
private final Set<ArtifactCoords> independentExtensions;
2223
private final Collection<String> unmatchedKeywords;
24+
private final Collection<String> invalidKeywords;
2325

2426
private ExtensionInstallPlan(Set<ArtifactCoords> platforms,
2527
Set<ArtifactCoords> managedExtensions,
2628
Set<ArtifactCoords> independentExtensions,
27-
Collection<String> unmatchedKeywords) {
29+
Collection<String> unmatchedKeywords,
30+
Collection<String> invalidKeywords) {
2831
this.platforms = platforms;
2932
this.managedExtensions = managedExtensions;
3033
this.independentExtensions = independentExtensions;
3134
this.unmatchedKeywords = unmatchedKeywords;
35+
this.invalidKeywords = invalidKeywords;
3236
}
3337

3438
public boolean isNotEmpty() {
@@ -77,13 +81,18 @@ public Collection<String> getUnmatchedKeywords() {
7781
return unmatchedKeywords;
7882
}
7983

84+
public Collection<String> getInvalidKeywords() {
85+
return invalidKeywords;
86+
}
87+
8088
@Override
8189
public String toString() {
8290
return "InstallRequest{" +
8391
"platforms=" + platforms +
8492
", managedExtensions=" + managedExtensions +
8593
", independentExtensions=" + independentExtensions +
8694
", unmatchedKeywords=" + unmatchedKeywords +
95+
", invalidKeywords=" + invalidKeywords +
8796
'}';
8897
}
8998

@@ -97,9 +106,11 @@ public static class Builder {
97106
private final Set<ArtifactCoords> extensionsInPlatforms = new LinkedHashSet<>();
98107
private final Set<ArtifactCoords> independentExtensions = new LinkedHashSet<>();
99108
private final Collection<String> unmatchedKeywords = new ArrayList<>();
109+
private final Collection<String> invalidKeywords = new ArrayList<>();
100110

101111
public ExtensionInstallPlan build() {
102-
return new ExtensionInstallPlan(platforms, extensionsInPlatforms, independentExtensions, unmatchedKeywords);
112+
return new ExtensionInstallPlan(platforms, extensionsInPlatforms, independentExtensions, unmatchedKeywords,
113+
invalidKeywords);
103114
}
104115

105116
public Builder addIndependentExtension(ArtifactCoords artifactCoords) {
@@ -122,6 +133,11 @@ public Builder addUnmatchedKeyword(String unmatchedKeyword) {
122133
return this;
123134
}
124135

136+
public Builder addInvalidKeyword(String invalidKeyword) {
137+
this.invalidKeywords.add(invalidKeyword);
138+
return this;
139+
}
140+
125141
public boolean hasExtensionInPlatform() {
126142
return !this.extensionsInPlatforms.isEmpty();
127143
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package io.quarkus.devtools.project.create;
2+
3+
import static io.quarkus.devtools.project.create.MultiplePlatformBomsTestBase.enableRegistryClient;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import java.nio.file.Path;
8+
import java.util.List;
9+
10+
import org.junit.jupiter.api.BeforeAll;
11+
import org.junit.jupiter.api.Test;
12+
13+
import io.quarkus.devtools.commands.data.QuarkusCommandOutcome;
14+
import io.quarkus.devtools.testing.registry.client.TestRegistryClientBuilder;
15+
import io.quarkus.registry.catalog.PlatformStreamCoords;
16+
17+
public class MavenProjectAddExtensionTest extends MultiplePlatformBomsTestBase {
18+
19+
private static final String PLATFORM_KEY = "io.test.platform";
20+
21+
@BeforeAll
22+
public static void setup() throws Exception {
23+
TestRegistryClientBuilder.newInstance()
24+
.baseDir(configDir())
25+
.newRegistry("registry.test.io")
26+
.newPlatform(PLATFORM_KEY)
27+
.newStream("1.0")
28+
.newRelease("1.1.1")
29+
.quarkusVersion("1.1.1")
30+
// default bom including quarkus-core + essential metadata
31+
.addCoreMember().release()
32+
// foo platform member
33+
.newMember("acme-a-bom").addExtension("ext-a").release()
34+
.stream().platform()
35+
.newArchivedStream("0.5")
36+
.newArchivedRelease("0.5.1")
37+
.quarkusVersion("0.5.1")
38+
// default bom including quarkus-core + essential metadata
39+
.addCoreMember().release()
40+
// foo platform member
41+
.newMember("acme-a-bom").addExtension("ext-a").release()
42+
.registry()
43+
.clientBuilder()
44+
.build();
45+
46+
enableRegistryClient();
47+
}
48+
49+
@Override
50+
protected String getMainPlatformKey() {
51+
return PLATFORM_KEY;
52+
}
53+
54+
@Test
55+
public void test() throws Exception {
56+
final Path projectDir = newProjectDir("existing-project");
57+
createProject(projectDir, new PlatformStreamCoords(null, "0.5"), List.of("ext-a"));
58+
59+
// valid characters, existing dependency
60+
QuarkusCommandOutcome outcome = addExtensions(projectDir, List.of("io.quarkus:quarkus-info:3.20.1"));
61+
assertTrue(outcome.isSuccess());
62+
63+
// invalid characters (tilde in artifactId)
64+
outcome = addExtensions(projectDir,
65+
List.of("io.quarkiverse.businessscore:quarkus-business-score-health~:1.0.0.Alpha4"));
66+
assertFalse(outcome.isSuccess());
67+
68+
// valid characters, questionable dependency
69+
outcome = addExtensions(projectDir, List.of("group:artifact:version"));
70+
assertTrue(outcome.isSuccess());
71+
}
72+
}

0 commit comments

Comments
 (0)