Skip to content

Commit 832a736

Browse files
committed
Update for latest test suite
1 parent ee6dda9 commit 832a736

File tree

3 files changed

+541
-97
lines changed

3 files changed

+541
-97
lines changed

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

Lines changed: 114 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
package com.github.packageurl;
2323

2424
import java.io.Serializable;
25+
import java.net.MalformedURLException;
2526
import java.net.URI;
2627
import java.net.URISyntaxException;
28+
import java.net.URL;
2729
import java.nio.charset.Charset;
2830
import java.nio.charset.StandardCharsets;
2931
import java.util.Arrays;
@@ -99,7 +101,7 @@ public PackageURL(final String type, final String namespace, final String name,
99101
this.type = validateType(type);
100102
this.namespace = validateNamespace(namespace);
101103
this.name = validateName(name);
102-
this.version = validateVersion(version);
104+
this.version = validateVersion(type, version);
103105
this.qualifiers = validateQualifiers(qualifiers);
104106
this.subpath = validatePath(subpath, true);
105107
verifyTypeConstraints(this.type, this.namespace, this.name);
@@ -271,13 +273,25 @@ private String validateNamespace(final String[] values) throws MalformedPackageU
271273

272274
String retVal;
273275
switch (type) {
276+
case StandardTypes.APK:
274277
case StandardTypes.BITBUCKET:
275-
case StandardTypes.DEBIAN:
278+
case StandardTypes.COMPOSER:
279+
case StandardTypes.DEB:
276280
case StandardTypes.GITHUB:
277281
case StandardTypes.GOLANG:
282+
case StandardTypes.HEX:
283+
case StandardTypes.LUAROCKS:
284+
case StandardTypes.QPKG:
278285
case StandardTypes.RPM:
279286
retVal = tempNamespace.toLowerCase();
280287
break;
288+
case StandardTypes.MLFLOW:
289+
case StandardTypes.OCI:
290+
if (tempNamespace != null) {
291+
throw new MalformedPackageURLException("The PackageURL specified contains a namespace which is not allowed for type: " + type);
292+
}
293+
retVal = null;
294+
break;
281295
default:
282296
retVal = tempNamespace;
283297
break;
@@ -291,14 +305,22 @@ private String validateName(final String value) throws MalformedPackageURLExcept
291305
}
292306
String temp;
293307
switch (type) {
308+
case StandardTypes.APK:
294309
case StandardTypes.BITBUCKET:
295-
case StandardTypes.DEBIAN:
310+
case StandardTypes.BITNAMI:
311+
case StandardTypes.COMPOSER:
312+
case StandardTypes.DEB:
296313
case StandardTypes.GITHUB:
297-
case StandardTypes.GOLANG:
314+
case StandardTypes.HEX:
315+
case StandardTypes.LUAROCKS:
316+
case StandardTypes.OCI:
298317
temp = value.toLowerCase();
299318
break;
319+
case StandardTypes.PUB:
320+
temp = value.toLowerCase().replaceAll("[^a-z0-9_]", "_");
321+
break;
300322
case StandardTypes.PYPI:
301-
temp = value.replaceAll("_", "-").toLowerCase();
323+
temp = value.toLowerCase().replace('_', '-');
302324
break;
303325
default:
304326
temp = value;
@@ -307,17 +329,26 @@ private String validateName(final String value) throws MalformedPackageURLExcept
307329
return temp;
308330
}
309331

310-
private String validateVersion(final String value) {
332+
private String validateVersion(final String type, final String value) {
311333
if (value == null) {
312334
return null;
313335
}
314-
return value;
336+
337+
switch (type) {
338+
case StandardTypes.HUGGINGFACE:
339+
case StandardTypes.LUAROCKS:
340+
case StandardTypes.OCI:
341+
return value.toLowerCase();
342+
default:
343+
return value;
344+
}
315345
}
316346

317347
private Map<String, String> validateQualifiers(final Map<String, String> values) throws MalformedPackageURLException {
318348
if (values == null) {
319349
return null;
320350
}
351+
321352
for (Map.Entry<String, String> entry : values.entrySet()) {
322353
validateKey(entry.getKey());
323354
final String value = entry.getValue();
@@ -460,7 +491,7 @@ private static String uriEncode(String source, Charset charset) {
460491
}
461492

462493
private static boolean isUnreserved(int c) {
463-
return (isAlpha(c) || isDigit(c) || '-' == c || '.' == c || '_' == c || '~' == c);
494+
return (isAlpha(c) || isDigit(c) || '-' == c || '.' == c || '_' == c || '~' == c || ':' == c || '/' == c);
464495
}
465496

466497
private static boolean isAlpha(int c) {
@@ -575,7 +606,7 @@ private void parse(final String purl) throws MalformedPackageURLException {
575606
// version is optional - check for existence
576607
index = remainder.lastIndexOf("@");
577608
if (index >= start) {
578-
this.version = validateVersion(percentDecode(remainder.substring(index + 1)));
609+
this.version = validateVersion(type, percentDecode(remainder.substring(index + 1)));
579610
remainder.setLength(index);
580611
}
581612

@@ -602,10 +633,57 @@ private void parse(final String purl) throws MalformedPackageURLException {
602633
* @throws MalformedPackageURLException if constraints are not met
603634
*/
604635
private void verifyTypeConstraints(String type, String namespace, String name) throws MalformedPackageURLException {
605-
if (StandardTypes.MAVEN.equals(type)) {
606-
if (namespace == null || namespace.isEmpty() || name == null || name.isEmpty()) {
607-
throw new MalformedPackageURLException("The PackageURL specified is invalid. Maven requires both a namespace and name.");
608-
}
636+
switch (type) {
637+
case StandardTypes.CONAN:
638+
if ((namespace != null || qualifiers != null) && (namespace == null || (qualifiers == null || !qualifiers.containsKey("channel")))) {
639+
throw new MalformedPackageURLException("The PackageURL specified is invalid. Conan requires a namespace to have a 'channel' qualifier");
640+
}
641+
break;
642+
case StandardTypes.CPAN:
643+
if (name == null || name.indexOf('-') != -1) {
644+
throw new MalformedPackageURLException("The PackageURL specified is invalid. CPAN requires a name");
645+
}
646+
if (namespace != null && (name.contains("::") || name.indexOf('-') != -1)) {
647+
throw new MalformedPackageURLException("The PackageURL specified is invalid. CPAN name may not contain '::' or '-'");
648+
}
649+
break;
650+
case StandardTypes.CRAN:
651+
if (version == null) {
652+
throw new MalformedPackageURLException("The PackageURL specified is invalid. CRAN requires a version");
653+
}
654+
break;
655+
case StandardTypes.HACKAGE:
656+
if (name == null || version == null) {
657+
throw new MalformedPackageURLException("The PackageURL specified is invalid. Hackage requires a name and version");
658+
}
659+
break;
660+
case StandardTypes.MAVEN:
661+
if (namespace == null || name == null) {
662+
throw new MalformedPackageURLException("The PackageURL specified is invalid. Maven requires both a namespace and name");
663+
}
664+
break;
665+
case StandardTypes.MLFLOW:
666+
if (qualifiers != null) {
667+
String repositoryUrl = qualifiers.get("repository_url");
668+
if (repositoryUrl != null) {
669+
String host = null;
670+
try {
671+
URL url = new URL(repositoryUrl);
672+
host = url.getHost();
673+
if (host.matches(".*[.]?azuredatabricks.net$")) {
674+
this.name = name.toLowerCase();
675+
}
676+
} catch (MalformedURLException e) {
677+
throw new MalformedPackageURLException("The PackageURL specified is invalid. MLFlow repository_url is not a valid URL for host " + host);
678+
}
679+
}
680+
}
681+
break;
682+
case StandardTypes.SWIFT:
683+
if (namespace == null || name == null || version == null) {
684+
throw new MalformedPackageURLException("The PackageURL specified is invalid. Swift requires a namespace, name, and version");
685+
}
686+
break;
609687
}
610688
}
611689

@@ -727,24 +805,42 @@ public int hashCode() {
727805
*
728806
* @since 1.0.0
729807
*/
730-
public static class StandardTypes {
808+
public static final class StandardTypes {
809+
public static final String ALPM = "alpm";
810+
public static final String APK = "apk";
731811
public static final String BITBUCKET = "bitbucket";
812+
public static final String BITNAMI = "bitnami";
813+
public static final String COCOAPODS = "cocoapods";
732814
public static final String CARGO = "cargo";
733815
public static final String COMPOSER = "composer";
734-
public static final String DEBIAN = "deb";
816+
public static final String CONAN = "conan";
817+
public static final String CONDA = "conda";
818+
public static final String CPAN = "cpan";
819+
public static final String CRAN = "cran";
820+
public static final String DEB = "deb";
735821
public static final String DOCKER = "docker";
736822
public static final String GEM = "gem";
737823
public static final String GENERIC = "generic";
738824
public static final String GITHUB = "github";
739825
public static final String GOLANG = "golang";
826+
public static final String HACKAGE = "hackage";
740827
public static final String HEX = "hex";
828+
public static final String HUGGINGFACE = "huggingface";
829+
public static final String LUAROCKS = "luarocks";
741830
public static final String MAVEN = "maven";
831+
public static final String MLFLOW = "mlflow";
742832
public static final String NPM = "npm";
743833
public static final String NUGET = "nuget";
834+
public static final String QPKG = "qpkg";
835+
public static final String OCI = "oci";
836+
public static final String PUB = "pub";
744837
public static final String PYPI = "pypi";
745838
public static final String RPM = "rpm";
746-
public static final String NIXPKGS = "nixpkgs";
747-
public static final String HACKAGE = "hackage";
839+
public static final String SWID = "swid";
840+
public static final String SWIFT = "swift";
841+
// FIXME: Remove this since it should be named DEB
842+
public static final String DEBIAN = "deb";
843+
// FIXME: Remove this since it should be named NIX and it is not a standard type
844+
public static final String NIXPKGS = "nix";
748845
}
749-
750846
}

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

Lines changed: 58 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,13 @@ public void testConstructorParameters() throws MalformedPackageURLException {
139139
if (invalid) {
140140
try {
141141
PackageURL purl = new PackageURL(type, namespace, name, version, map, subpath);
142+
// If we get here, then only the scheme can be invalid
143+
verifyComponentsEquals(purl, type, namespace, name, version, subpath, qualifiers);
144+
145+
if (!cpurlString.equals(purl.toString())) {
146+
throw new MalformedPackageURLException("The PackageURL scheme is invalid for purl: " + purl);
147+
}
148+
142149
Assert.fail("Invalid package url components should have caused an exception: " + purl.toString());
143150
} catch (MalformedPackageURLException e) {
144151
Assert.assertNotNull(e.getMessage());
@@ -147,23 +154,26 @@ public void testConstructorParameters() throws MalformedPackageURLException {
147154
}
148155

149156
PackageURL purl = new PackageURL(type, namespace, name, version, map, subpath);
150-
157+
verifyComponentsEquals(purl, type, namespace, name, version, subpath, qualifiers);
151158
Assert.assertEquals(cpurlString, purl.canonicalize());
152-
Assert.assertEquals("pkg", purl.getScheme());
153-
Assert.assertEquals(type, purl.getType());
154-
Assert.assertEquals(namespace, purl.getNamespace());
155-
Assert.assertEquals(name, purl.getName());
156-
Assert.assertEquals(version, purl.getVersion());
157-
Assert.assertEquals(subpath, purl.getSubpath());
158-
if (qualifiers != null) {
159-
Assert.assertNotNull(purl.getQualifiers());
160-
Assert.assertEquals(qualifiers.length(), purl.getQualifiers().size());
161-
qualifiers.keySet().forEach((key) -> {
162-
String value = qualifiers.getString(key);
163-
Assert.assertTrue(purl.getQualifiers().containsKey(key));
164-
Assert.assertEquals(value, purl.getQualifiers().get(key));
165-
});
166-
}
159+
}
160+
}
161+
162+
private static void verifyComponentsEquals(PackageURL purl, String type, String namespace, String name, String version, String subpath, JSONObject qualifiers) {
163+
Assert.assertEquals("pkg", purl.getScheme());
164+
Assert.assertEquals(type, purl.getType());
165+
Assert.assertEquals(namespace, purl.getNamespace());
166+
Assert.assertEquals(name, purl.getName());
167+
Assert.assertEquals(version, purl.getVersion());
168+
Assert.assertEquals(subpath, purl.getSubpath());
169+
if (qualifiers != null) {
170+
Assert.assertNotNull(purl.getQualifiers());
171+
Assert.assertEquals(qualifiers.length(), purl.getQualifiers().size());
172+
qualifiers.keySet().forEach((key) -> {
173+
String value = qualifiers.getString(key);
174+
Assert.assertTrue(purl.getQualifiers().containsKey(key));
175+
Assert.assertEquals(value, purl.getQualifiers().get(key));
176+
});
167177
}
168178
}
169179

@@ -268,24 +278,38 @@ public void testConstructorWithDuplicateQualifiers() throws MalformedPackageURLE
268278

269279
@Test
270280
public void testStandardTypes() {
271-
exception = ExpectedException.none();
272-
Assert.assertEquals(PackageURL.StandardTypes.BITBUCKET, "bitbucket");
273-
Assert.assertEquals(PackageURL.StandardTypes.CARGO, "cargo");
274-
Assert.assertEquals(PackageURL.StandardTypes.COMPOSER, "composer");
275-
Assert.assertEquals(PackageURL.StandardTypes.DEBIAN, "deb");
276-
Assert.assertEquals(PackageURL.StandardTypes.DOCKER, "docker");
277-
Assert.assertEquals(PackageURL.StandardTypes.GEM, "gem");
278-
Assert.assertEquals(PackageURL.StandardTypes.GENERIC, "generic");
279-
Assert.assertEquals(PackageURL.StandardTypes.GITHUB, "github");
280-
Assert.assertEquals(PackageURL.StandardTypes.GOLANG, "golang");
281-
Assert.assertEquals(PackageURL.StandardTypes.HEX, "hex");
282-
Assert.assertEquals(PackageURL.StandardTypes.MAVEN, "maven");
283-
Assert.assertEquals(PackageURL.StandardTypes.NPM, "npm");
284-
Assert.assertEquals(PackageURL.StandardTypes.NUGET, "nuget");
285-
Assert.assertEquals(PackageURL.StandardTypes.PYPI, "pypi");
286-
Assert.assertEquals(PackageURL.StandardTypes.RPM, "rpm");
287-
Assert.assertEquals(PackageURL.StandardTypes.NIXPKGS, "nixpkgs");
288-
Assert.assertEquals(PackageURL.StandardTypes.HACKAGE, "hackage");
281+
Assert.assertEquals("alpm", PackageURL.StandardTypes.ALPM);
282+
Assert.assertEquals("apk", PackageURL.StandardTypes.APK);
283+
Assert.assertEquals("bitbucket", PackageURL.StandardTypes.BITBUCKET);
284+
Assert.assertEquals("bitnami", PackageURL.StandardTypes.BITNAMI);
285+
Assert.assertEquals("cocoapods", PackageURL.StandardTypes.COCOAPODS);
286+
Assert.assertEquals("cargo", PackageURL.StandardTypes.CARGO);
287+
Assert.assertEquals("composer", PackageURL.StandardTypes.COMPOSER);
288+
Assert.assertEquals("conan", PackageURL.StandardTypes.CONAN);
289+
Assert.assertEquals("conda", PackageURL.StandardTypes.CONDA);
290+
Assert.assertEquals("cpan", PackageURL.StandardTypes.CPAN);
291+
Assert.assertEquals("cran", PackageURL.StandardTypes.CRAN);
292+
Assert.assertEquals("deb", PackageURL.StandardTypes.DEB);
293+
Assert.assertEquals("docker", PackageURL.StandardTypes.DOCKER);
294+
Assert.assertEquals("gem", PackageURL.StandardTypes.GEM);
295+
Assert.assertEquals("generic", PackageURL.StandardTypes.GENERIC);
296+
Assert.assertEquals("github", PackageURL.StandardTypes.GITHUB);
297+
Assert.assertEquals("golang", PackageURL.StandardTypes.GOLANG);
298+
Assert.assertEquals("hackage", PackageURL.StandardTypes.HACKAGE);
299+
Assert.assertEquals("hex", PackageURL.StandardTypes.HEX);
300+
Assert.assertEquals("huggingface", PackageURL.StandardTypes.HUGGINGFACE);
301+
Assert.assertEquals("luarocks", PackageURL.StandardTypes.LUAROCKS);
302+
Assert.assertEquals("maven", PackageURL.StandardTypes.MAVEN);
303+
Assert.assertEquals("mlflow", PackageURL.StandardTypes.MLFLOW);
304+
Assert.assertEquals("npm", PackageURL.StandardTypes.NPM);
305+
Assert.assertEquals("nuget", PackageURL.StandardTypes.NUGET);
306+
Assert.assertEquals("qpkg", PackageURL.StandardTypes.QPKG);
307+
Assert.assertEquals("oci", PackageURL.StandardTypes.OCI);
308+
Assert.assertEquals("pub", PackageURL.StandardTypes.PUB);
309+
Assert.assertEquals("pypi", PackageURL.StandardTypes.PYPI);
310+
Assert.assertEquals("rpm", PackageURL.StandardTypes.RPM);
311+
Assert.assertEquals("swid", PackageURL.StandardTypes.SWID);
312+
Assert.assertEquals("swift", PackageURL.StandardTypes.SWIFT);
289313
}
290314

291315
@Test

0 commit comments

Comments
 (0)