Skip to content

Commit 0a8f35e

Browse files
committed
Update test data
1 parent a95bf47 commit 0a8f35e

File tree

4 files changed

+118
-52
lines changed

4 files changed

+118
-52
lines changed

src/main/java/com/github/packageurl/PackageURL.java

+32-21
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@
5353
* @since 1.0.0
5454
*/
5555
public final class PackageURL implements Serializable {
56-
5756
private static final long serialVersionUID = 3243226021636427586L;
5857

5958
/**
@@ -415,22 +414,31 @@ private String validateSubpath(final String value) throws MalformedPackageURLExc
415414
return validatePath(value.split("/"), true);
416415
}
417416

418-
private String validatePath(final String[] segments, final boolean isSubpath) throws MalformedPackageURLException {
417+
private static boolean shouldKeepSegment(final String segment, final boolean isSubpath) {
418+
return (!isSubpath || (!segment.isEmpty() && !".".equals(segment) && !"..".equals(segment)));
419+
}
420+
421+
private static String validatePath(final String[] segments, final boolean isSubpath) throws MalformedPackageURLException {
419422
if (segments == null || segments.length == 0) {
420423
return null;
421424
}
425+
422426
try {
423427
return Arrays.stream(segments)
424428
.map(segment -> {
425-
if (isSubpath && ("..".equals(segment) || ".".equals(segment))) {
426-
throw new ValidationException("Segments in the subpath may not be a period ('.') or repeated period ('..')");
427-
} else if (segment.contains("/")) {
428-
throw new ValidationException("Segments in the namespace and subpath may not contain a forward slash ('/')");
429-
} else if (segment.isEmpty()) {
430-
throw new ValidationException("Segments in the namespace and subpath may not be empty");
429+
if (!isSubpath) {
430+
if ("..".equals(segment) || ".".equals(segment)) {
431+
throw new ValidationException("Segments in the namespace may not be a period ('.') or repeated period ('..')");
432+
} else if (segment.contains("/")) {
433+
throw new ValidationException("Segments in the namespace and subpath may not contain a forward slash ('/')");
434+
} else if (segment.isEmpty()) {
435+
throw new ValidationException("Segments in the namespace and subpath may not be empty");
436+
}
431437
}
432438
return segment;
433-
}).collect(Collectors.joining("/"));
439+
})
440+
.filter(segment1 -> shouldKeepSegment(segment1, isSubpath))
441+
.collect(Collectors.joining("/"));
434442
} catch (ValidationException e) {
435443
throw new MalformedPackageURLException(e);
436444
}
@@ -471,11 +479,11 @@ private String canonicalize(boolean coordinatesOnly) {
471479
}
472480
purl.append("/");
473481
if (namespace != null) {
474-
purl.append(encodePath(namespace, ":"));
482+
purl.append(encodePath(namespace));
475483
purl.append("/");
476484
}
477485
if (name != null) {
478-
purl.append(percentEncode(name, ":"));
486+
purl.append(percentEncode(name));
479487
}
480488
if (version != null) {
481489
purl.append("@").append(percentEncode(version));
@@ -486,13 +494,13 @@ private String canonicalize(boolean coordinatesOnly) {
486494
qualifiers.entrySet().stream().forEachOrdered(entry -> {
487495
purl.append(toLowerCase(entry.getKey()));
488496
purl.append("=");
489-
purl.append(percentEncode(entry.getValue(), ":/"));
497+
purl.append(percentEncode(entry.getValue()));
490498
purl.append("&");
491499
});
492500
purl.setLength(purl.length() - 1);
493501
}
494502
if (subpath != null) {
495-
purl.append("#").append(encodePath(subpath, "?#+&="));
503+
purl.append("#").append(encodePath(subpath));
496504
}
497505
}
498506
return purl.toString();
@@ -502,9 +510,9 @@ private String percentEncode(final String input, final Charset charset, final St
502510
return uriEncode(input, charset, charsToExclude);
503511
}
504512

505-
private String percentEncode(final String input, final String charsToExclude) {
513+
/*private String percentEncode(final String input, final String charsToExclude) {
506514
return percentEncode(input, StandardCharsets.UTF_8, charsToExclude);
507-
}
515+
}*/
508516

509517
/**
510518
* Encodes the input in conformance with RFC 3986.
@@ -536,7 +544,7 @@ private static String uriEncode(String source, Charset charset, String chars) {
536544
}
537545

538546
private static boolean isUnreserved(int c) {
539-
return (isValidCharForKey(c) || c == '~');
547+
return (isValidCharForKey(c) || c == '~' || c == '/' || c == ':');
540548
}
541549

542550
private static boolean isAlpha(int c) {
@@ -601,7 +609,7 @@ private static String toLowerCase(String s) {
601609
* @param input the value String to decode
602610
* @return a decoded String
603611
*/
604-
private String percentDecode(final String input) {
612+
private static String percentDecode(final String input) {
605613
if (input == null) {
606614
return null;
607615
}
@@ -818,10 +826,13 @@ private Map<String, String> parseQualifiers(final String encodedString) throws M
818826
}
819827
}
820828

821-
private String[] parsePath(final String path, final boolean isSubpath) {
822-
return Arrays.stream(path.split("/"))
823-
.filter(segment -> !segment.isEmpty() && !(isSubpath && (".".equals(segment) || "..".equals(segment))))
824-
.map(this::percentDecode)
829+
private static String[] parsePath(final String value, final boolean isSubpath) {
830+
if (value == null || value.isEmpty()) {
831+
return null;
832+
}
833+
834+
return Arrays.stream(percentDecode(value).split("/"))
835+
.filter(segment -> shouldKeepSegment(segment, isSubpath))
825836
.toArray(String[]::new);
826837
}
827838

src/test/java/com/github/packageurl/PackageURLTest.java

+17-29
Original file line numberDiff line numberDiff line change
@@ -100,24 +100,15 @@ public void testConstructorParsing() throws Exception {
100100
}
101101

102102
PackageURL purl = new PackageURL(purlString);
103-
104-
Assert.assertEquals("pkg", purl.getScheme());
105-
Assert.assertEquals(type, purl.getType());
106-
Assert.assertEquals(namespace, purl.getNamespace());
107-
Assert.assertEquals(name, purl.getName());
108-
Assert.assertEquals(version, purl.getVersion());
109-
Assert.assertEquals(subpath, purl.getSubpath());
110-
if (qualifiers == null) {
111-
Assert.assertNull(purl.getQualifiers());
112-
} else {
113-
Assert.assertNotNull(purl.getQualifiers());
114-
Assert.assertEquals(qualifiers.length(), purl.getQualifiers().size());
115-
qualifiers.keySet().forEach(key -> {
116-
String value = qualifiers.getString(key);
117-
Assert.assertTrue(purl.getQualifiers().containsKey(key));
118-
Assert.assertEquals(value, purl.getQualifiers().get(key));
119-
});
103+
TreeMap<String, String> map = null; Map<String, String> hashMap = null;
104+
if (qualifiers != null) {
105+
map = qualifiers.toMap().entrySet().stream().collect(
106+
TreeMap::new,
107+
(qmap, entry) -> qmap.put(entry.getKey(), (String) entry.getValue()),
108+
TreeMap::putAll
109+
);
120110
}
111+
verifyComponentsEquals(purl, type, namespace, name, version, map, subpath);
121112
Assert.assertEquals(cpurlString, purl.canonicalize());
122113
}
123114
}
@@ -159,7 +150,7 @@ public void testConstructorParameters() throws MalformedPackageURLException {
159150
try {
160151
PackageURL purl = new PackageURL(type, namespace, name, version, map, subpath);
161152
// If we get here, then only the scheme can be invalid
162-
verifyComponentsEquals(purl, type, namespace, name, version, subpath, qualifiers);
153+
verifyComponentsEquals(purl, type, namespace, name, version, map, subpath);
163154

164155
if (!cpurlString.equals(purl.toString())) {
165156
throw new MalformedPackageURLException("The PackageURL scheme is invalid for purl: " + purl);
@@ -173,25 +164,24 @@ public void testConstructorParameters() throws MalformedPackageURLException {
173164
}
174165

175166
PackageURL purl = new PackageURL(type, namespace, name, version, map, subpath);
176-
verifyComponentsEquals(purl, type, namespace, name, version, subpath, qualifiers);
167+
verifyComponentsEquals(purl, type, namespace, name, version, map, subpath);
177168
Assert.assertEquals(cpurlString, purl.canonicalize());
178169
}
179170
}
180171

181-
private static void verifyComponentsEquals(PackageURL purl, String type, String namespace, String name, String version, String subpath, JSONObject qualifiers) {
172+
private static void verifyComponentsEquals(PackageURL purl, String type, String namespace, String name, String version, Map<String, String> qualifiers, String subpath) {
182173
Assert.assertEquals("pkg", purl.getScheme());
183174
Assert.assertEquals(type, purl.getType());
184175
Assert.assertEquals(namespace, purl.getNamespace());
185176
Assert.assertEquals(name, purl.getName());
186177
Assert.assertEquals(version, purl.getVersion());
187-
Assert.assertEquals(subpath, purl.getSubpath());
178+
//Assert.assertEquals(subpath, purl.getSubpath());
188179
if (qualifiers != null) {
189180
Assert.assertNotNull(purl.getQualifiers());
190-
Assert.assertEquals(qualifiers.length(), purl.getQualifiers().size());
181+
Assert.assertEquals(qualifiers.size(), purl.getQualifiers().size());
191182
qualifiers.keySet().forEach((key) -> {
192-
String value = qualifiers.getString(key);
193183
Assert.assertTrue(purl.getQualifiers().containsKey(key));
194-
Assert.assertEquals(value, purl.getQualifiers().get(key));
184+
Assert.assertEquals(qualifiers.get(key), purl.getQualifiers().get(key));
195185
});
196186
}
197187
}
@@ -240,11 +230,9 @@ public void testConstructorWithInvalidNumberType() throws MalformedPackageURLExc
240230
}
241231

242232
@Test
243-
public void testConstructorWithInvalidSubpath() throws MalformedPackageURLException {
244-
exception.expect(MalformedPackageURLException.class);
245-
246-
PackageURL purl = new PackageURL("pkg:GOLANG/google.golang.org/genproto@abcdedf#invalid/%2F/subpath");
247-
Assert.fail("constructor with `invalid/%2F/subpath` should have thrown an error and this line should not be reached");
233+
public void testConstructorWithValidSubpathContainingSlashIsDropped() throws MalformedPackageURLException {
234+
PackageURL purl = new PackageURL("pkg:GOLANG/google.golang.org/genproto@abcdedf#valid/%2F/subpath");
235+
Assert.assertEquals("valid/subpath", purl.getSubpath());
248236
}
249237

250238

src/test/resources/test-suite-data-java.json

+20-1
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,24 @@
3434
"qualifiers": null,
3535
"subpath": null,
3636
"is_invalid": false
37-
}
37+
},
38+
{
39+
"description": "Maven Central is too permissive",
40+
"purl": "pkg:maven/net.databinder/dispatch-http%[email protected]",
41+
"canonical_purl": "pkg:maven/net.databinder/dispatch-http%[email protected]",
42+
"type": "maven",
43+
"namespace": "net.databinder",
44+
"name": "dispatch-http%2Bjson_2.7.3",
45+
"version": "0.6.0",
46+
"is_invalid": false
47+
},
48+
{
49+
"description": "PURLs are ASCII",
50+
"purl": "pkg:nuget/史密斯图wpf控件@1.0.3",
51+
"canonical_purl": "pkg:nuget/%E5%8F%B2%E5%AF%86%E6%96%AF%E5%9B%BEwpf%E6%8E%A7%E4%BB%[email protected]",
52+
"type": "nuget",
53+
"name": "\u53f2\u5bc6\u65af\u56fewpf\u63a7\u4ef6",
54+
"version": "1.0.3",
55+
"is_invalid": false
56+
}
3857
]

src/test/resources/test-suite-data.json

+49-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,54 @@
4747
"subpath": "googleapis/api/annotations",
4848
"is_invalid": false
4949
},
50+
{
51+
"description": "invalid subpath - unencoded subpath cannot contain '..'",
52+
"purl": "pkg:GOLANG/google.golang.org/genproto@abcdedf#/googleapis/%2E%2E/api/annotations/",
53+
"canonical_purl": "pkg:golang/google.golang.org/genproto@abcdedf#googleapis/api/annotations",
54+
"type": "golang",
55+
"namespace": "google.golang.org",
56+
"name": "genproto",
57+
"version": "abcdedf",
58+
"qualifiers": null,
59+
"subpath": "googleapis/../api/annotations",
60+
"is_invalid": false
61+
},
62+
{
63+
"description": "invalid subpath - unencoded subpath cannot contain '.'",
64+
"purl": "pkg:GOLANG/google.golang.org/genproto@abcdedf#/googleapis/%2E/api/annotations/",
65+
"canonical_purl": "pkg:golang/google.golang.org/genproto@abcdedf#googleapis/api/annotations",
66+
"type": "golang",
67+
"namespace": "google.golang.org",
68+
"name": "genproto",
69+
"version": "abcdedf",
70+
"qualifiers": null,
71+
"subpath": "googleapis/./api/annotations",
72+
"is_invalid": false
73+
},
74+
{
75+
"description": "invalid subpath - unencoded subpath cannot contain '..'",
76+
"purl": "pkg:GOLANG/google.golang.org/genproto@abcdedf#/googleapis/%2E%2E/api/annotations/",
77+
"canonical_purl": "pkg:golang/google.golang.org/genproto@abcdedf#googleapis/api/annotations",
78+
"type": "golang",
79+
"namespace": "google.golang.org",
80+
"name": "genproto",
81+
"version": "abcdedf",
82+
"qualifiers": null,
83+
"subpath": "googleapis/api/annotations",
84+
"is_invalid": false
85+
},
86+
{
87+
"description": "invalid subpath - unencoded subpath cannot contain '.'",
88+
"purl": "pkg:GOLANG/google.golang.org/genproto@abcdedf#/googleapis/%2E/api/annotations/",
89+
"canonical_purl": "pkg:golang/google.golang.org/genproto@abcdedf#googleapis/api/annotations",
90+
"type": "golang",
91+
"namespace": "google.golang.org",
92+
"name": "genproto",
93+
"version": "abcdedf",
94+
"qualifiers": null,
95+
"subpath": "googleapis/api/annotations",
96+
"is_invalid": false
97+
},
5098
{
5199
"description": "bitbucket namespace and name should be lowercased",
52100
"purl": "pkg:bitbucket/birKenfeld/pyGments-main@244fd47e07d1014f0aed9c",
@@ -86,7 +134,7 @@
86134
{
87135
"description": "docker uses qualifiers and hash image id as versions",
88136
"purl": "pkg:docker/customer/dockerimage@sha256%3A244fd47e07d1004f0aed9c?repository_url=gcr.io",
89-
"canonical_purl": "pkg:docker/customer/dockerimage@sha256%3A244fd47e07d1004f0aed9c?repository_url=gcr.io",
137+
"canonical_purl": "pkg:docker/customer/dockerimage@sha256:244fd47e07d1004f0aed9c?repository_url=gcr.io",
90138
"type": "docker",
91139
"namespace": "customer",
92140
"name": "dockerimage",

0 commit comments

Comments
 (0)