Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update for latest test suite #174

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 114 additions & 18 deletions src/main/java/com/github/packageurl/PackageURL.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
package com.github.packageurl;

import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
Expand Down Expand Up @@ -99,7 +101,7 @@ public PackageURL(final String type, final String namespace, final String name,
this.type = validateType(type);
this.namespace = validateNamespace(namespace);
this.name = validateName(name);
this.version = validateVersion(version);
this.version = validateVersion(type, version);
this.qualifiers = validateQualifiers(qualifiers);
this.subpath = validatePath(subpath, true);
verifyTypeConstraints(this.type, this.namespace, this.name);
Expand Down Expand Up @@ -271,13 +273,25 @@ private String validateNamespace(final String[] values) throws MalformedPackageU

String retVal;
switch (type) {
case StandardTypes.APK:
case StandardTypes.BITBUCKET:
case StandardTypes.DEBIAN:
case StandardTypes.COMPOSER:
case StandardTypes.DEB:
case StandardTypes.GITHUB:
case StandardTypes.GOLANG:
case StandardTypes.HEX:
case StandardTypes.LUAROCKS:
case StandardTypes.QPKG:
case StandardTypes.RPM:
retVal = tempNamespace.toLowerCase();
break;
case StandardTypes.MLFLOW:
case StandardTypes.OCI:
if (tempNamespace != null) {
throw new MalformedPackageURLException("The PackageURL specified contains a namespace which is not allowed for type: " + type);
}
retVal = null;
break;
default:
retVal = tempNamespace;
break;
Expand All @@ -291,14 +305,22 @@ private String validateName(final String value) throws MalformedPackageURLExcept
}
String temp;
switch (type) {
case StandardTypes.APK:
case StandardTypes.BITBUCKET:
case StandardTypes.DEBIAN:
case StandardTypes.BITNAMI:
case StandardTypes.COMPOSER:
case StandardTypes.DEB:
case StandardTypes.GITHUB:
case StandardTypes.GOLANG:
case StandardTypes.HEX:
case StandardTypes.LUAROCKS:
case StandardTypes.OCI:
temp = value.toLowerCase();
break;
case StandardTypes.PUB:
temp = value.toLowerCase().replaceAll("[^a-z0-9_]", "_");
break;
case StandardTypes.PYPI:
temp = value.replaceAll("_", "-").toLowerCase();
temp = value.toLowerCase().replace('_', '-');
break;
default:
temp = value;
Expand All @@ -307,17 +329,26 @@ private String validateName(final String value) throws MalformedPackageURLExcept
return temp;
}

private String validateVersion(final String value) {
private String validateVersion(final String type, final String value) {
if (value == null) {
return null;
}
return value;

switch (type) {
case StandardTypes.HUGGINGFACE:
case StandardTypes.LUAROCKS:
case StandardTypes.OCI:
return value.toLowerCase();
default:
return value;
}
}

private Map<String, String> validateQualifiers(final Map<String, String> values) throws MalformedPackageURLException {
if (values == null) {
return null;
}

for (Map.Entry<String, String> entry : values.entrySet()) {
validateKey(entry.getKey());
final String value = entry.getValue();
Expand Down Expand Up @@ -460,7 +491,7 @@ private static String uriEncode(String source, Charset charset) {
}

private static boolean isUnreserved(int c) {
return (isAlpha(c) || isDigit(c) || '-' == c || '.' == c || '_' == c || '~' == c);
return (isAlpha(c) || isDigit(c) || '-' == c || '.' == c || '_' == c || '~' == c || ':' == c || '/' == c);
}

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

Expand All @@ -602,10 +633,57 @@ private void parse(final String purl) throws MalformedPackageURLException {
* @throws MalformedPackageURLException if constraints are not met
*/
private void verifyTypeConstraints(String type, String namespace, String name) throws MalformedPackageURLException {
if (StandardTypes.MAVEN.equals(type)) {
if (namespace == null || namespace.isEmpty() || name == null || name.isEmpty()) {
throw new MalformedPackageURLException("The PackageURL specified is invalid. Maven requires both a namespace and name.");
}
switch (type) {
case StandardTypes.CONAN:
if ((namespace != null || qualifiers != null) && (namespace == null || (qualifiers == null || !qualifiers.containsKey("channel")))) {
throw new MalformedPackageURLException("The PackageURL specified is invalid. Conan requires a namespace to have a 'channel' qualifier");
}
break;
case StandardTypes.CPAN:
if (name == null || name.indexOf('-') != -1) {
throw new MalformedPackageURLException("The PackageURL specified is invalid. CPAN requires a name");
}
if (namespace != null && (name.contains("::") || name.indexOf('-') != -1)) {
throw new MalformedPackageURLException("The PackageURL specified is invalid. CPAN name may not contain '::' or '-'");
}
break;
case StandardTypes.CRAN:
if (version == null) {
throw new MalformedPackageURLException("The PackageURL specified is invalid. CRAN requires a version");
}
break;
case StandardTypes.HACKAGE:
if (name == null || version == null) {
throw new MalformedPackageURLException("The PackageURL specified is invalid. Hackage requires a name and version");
}
break;
case StandardTypes.MAVEN:
if (namespace == null || name == null) {
throw new MalformedPackageURLException("The PackageURL specified is invalid. Maven requires both a namespace and name");
}
break;
case StandardTypes.MLFLOW:
if (qualifiers != null) {
String repositoryUrl = qualifiers.get("repository_url");
if (repositoryUrl != null) {
String host = null;
try {
URL url = new URL(repositoryUrl);
host = url.getHost();
if (host.matches(".*[.]?azuredatabricks.net$")) {
this.name = name.toLowerCase();
}
} catch (MalformedURLException e) {
throw new MalformedPackageURLException("The PackageURL specified is invalid. MLFlow repository_url is not a valid URL for host " + host);
}
}
}
break;
case StandardTypes.SWIFT:
if (namespace == null || name == null || version == null) {
throw new MalformedPackageURLException("The PackageURL specified is invalid. Swift requires a namespace, name, and version");
}
break;
}
}

Expand Down Expand Up @@ -727,24 +805,42 @@ public int hashCode() {
*
* @since 1.0.0
*/
public static class StandardTypes {
public static final class StandardTypes {
public static final String ALPM = "alpm";
public static final String APK = "apk";
public static final String BITBUCKET = "bitbucket";
public static final String BITNAMI = "bitnami";
public static final String COCOAPODS = "cocoapods";
public static final String CARGO = "cargo";
public static final String COMPOSER = "composer";
public static final String DEBIAN = "deb";
public static final String CONAN = "conan";
public static final String CONDA = "conda";
public static final String CPAN = "cpan";
public static final String CRAN = "cran";
public static final String DEB = "deb";
public static final String DOCKER = "docker";
public static final String GEM = "gem";
public static final String GENERIC = "generic";
public static final String GITHUB = "github";
public static final String GOLANG = "golang";
public static final String HACKAGE = "hackage";
public static final String HEX = "hex";
public static final String HUGGINGFACE = "huggingface";
public static final String LUAROCKS = "luarocks";
public static final String MAVEN = "maven";
public static final String MLFLOW = "mlflow";
public static final String NPM = "npm";
public static final String NUGET = "nuget";
public static final String QPKG = "qpkg";
public static final String OCI = "oci";
public static final String PUB = "pub";
public static final String PYPI = "pypi";
public static final String RPM = "rpm";
public static final String NIXPKGS = "nixpkgs";
public static final String HACKAGE = "hackage";
public static final String SWID = "swid";
public static final String SWIFT = "swift";
// FIXME: Remove this since it should be named DEB
public static final String DEBIAN = "deb";
// FIXME: Remove this since it should be named NIX and it is not a standard type
public static final String NIXPKGS = "nix";
}

}
92 changes: 58 additions & 34 deletions src/test/java/com/github/packageurl/PackageURLTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,13 @@ public void testConstructorParameters() throws MalformedPackageURLException {
if (invalid) {
try {
PackageURL purl = new PackageURL(type, namespace, name, version, map, subpath);
// If we get here, then only the scheme can be invalid
verifyComponentsEquals(purl, type, namespace, name, version, subpath, qualifiers);

if (!cpurlString.equals(purl.toString())) {
throw new MalformedPackageURLException("The PackageURL scheme is invalid for purl: " + purl);
}

Assert.fail("Invalid package url components should have caused an exception: " + purl.toString());
} catch (MalformedPackageURLException e) {
Assert.assertNotNull(e.getMessage());
Expand All @@ -147,23 +154,26 @@ public void testConstructorParameters() throws MalformedPackageURLException {
}

PackageURL purl = new PackageURL(type, namespace, name, version, map, subpath);

verifyComponentsEquals(purl, type, namespace, name, version, subpath, qualifiers);
Assert.assertEquals(cpurlString, purl.canonicalize());
Assert.assertEquals("pkg", purl.getScheme());
Assert.assertEquals(type, purl.getType());
Assert.assertEquals(namespace, purl.getNamespace());
Assert.assertEquals(name, purl.getName());
Assert.assertEquals(version, purl.getVersion());
Assert.assertEquals(subpath, purl.getSubpath());
if (qualifiers != null) {
Assert.assertNotNull(purl.getQualifiers());
Assert.assertEquals(qualifiers.length(), purl.getQualifiers().size());
qualifiers.keySet().forEach((key) -> {
String value = qualifiers.getString(key);
Assert.assertTrue(purl.getQualifiers().containsKey(key));
Assert.assertEquals(value, purl.getQualifiers().get(key));
});
}
}
}

private static void verifyComponentsEquals(PackageURL purl, String type, String namespace, String name, String version, String subpath, JSONObject qualifiers) {
Assert.assertEquals("pkg", purl.getScheme());
Assert.assertEquals(type, purl.getType());
Assert.assertEquals(namespace, purl.getNamespace());
Assert.assertEquals(name, purl.getName());
Assert.assertEquals(version, purl.getVersion());
Assert.assertEquals(subpath, purl.getSubpath());
if (qualifiers != null) {
Assert.assertNotNull(purl.getQualifiers());
Assert.assertEquals(qualifiers.length(), purl.getQualifiers().size());
qualifiers.keySet().forEach((key) -> {
String value = qualifiers.getString(key);
Assert.assertTrue(purl.getQualifiers().containsKey(key));
Assert.assertEquals(value, purl.getQualifiers().get(key));
});
}
}

Expand Down Expand Up @@ -268,24 +278,38 @@ public void testConstructorWithDuplicateQualifiers() throws MalformedPackageURLE

@Test
public void testStandardTypes() {
exception = ExpectedException.none();
Assert.assertEquals(PackageURL.StandardTypes.BITBUCKET, "bitbucket");
Assert.assertEquals(PackageURL.StandardTypes.CARGO, "cargo");
Assert.assertEquals(PackageURL.StandardTypes.COMPOSER, "composer");
Assert.assertEquals(PackageURL.StandardTypes.DEBIAN, "deb");
Assert.assertEquals(PackageURL.StandardTypes.DOCKER, "docker");
Assert.assertEquals(PackageURL.StandardTypes.GEM, "gem");
Assert.assertEquals(PackageURL.StandardTypes.GENERIC, "generic");
Assert.assertEquals(PackageURL.StandardTypes.GITHUB, "github");
Assert.assertEquals(PackageURL.StandardTypes.GOLANG, "golang");
Assert.assertEquals(PackageURL.StandardTypes.HEX, "hex");
Assert.assertEquals(PackageURL.StandardTypes.MAVEN, "maven");
Assert.assertEquals(PackageURL.StandardTypes.NPM, "npm");
Assert.assertEquals(PackageURL.StandardTypes.NUGET, "nuget");
Assert.assertEquals(PackageURL.StandardTypes.PYPI, "pypi");
Assert.assertEquals(PackageURL.StandardTypes.RPM, "rpm");
Assert.assertEquals(PackageURL.StandardTypes.NIXPKGS, "nixpkgs");
Assert.assertEquals(PackageURL.StandardTypes.HACKAGE, "hackage");
Assert.assertEquals("alpm", PackageURL.StandardTypes.ALPM);
Assert.assertEquals("apk", PackageURL.StandardTypes.APK);
Assert.assertEquals("bitbucket", PackageURL.StandardTypes.BITBUCKET);
Assert.assertEquals("bitnami", PackageURL.StandardTypes.BITNAMI);
Assert.assertEquals("cocoapods", PackageURL.StandardTypes.COCOAPODS);
Assert.assertEquals("cargo", PackageURL.StandardTypes.CARGO);
Assert.assertEquals("composer", PackageURL.StandardTypes.COMPOSER);
Assert.assertEquals("conan", PackageURL.StandardTypes.CONAN);
Assert.assertEquals("conda", PackageURL.StandardTypes.CONDA);
Assert.assertEquals("cpan", PackageURL.StandardTypes.CPAN);
Assert.assertEquals("cran", PackageURL.StandardTypes.CRAN);
Assert.assertEquals("deb", PackageURL.StandardTypes.DEB);
Assert.assertEquals("docker", PackageURL.StandardTypes.DOCKER);
Assert.assertEquals("gem", PackageURL.StandardTypes.GEM);
Assert.assertEquals("generic", PackageURL.StandardTypes.GENERIC);
Assert.assertEquals("github", PackageURL.StandardTypes.GITHUB);
Assert.assertEquals("golang", PackageURL.StandardTypes.GOLANG);
Assert.assertEquals("hackage", PackageURL.StandardTypes.HACKAGE);
Assert.assertEquals("hex", PackageURL.StandardTypes.HEX);
Assert.assertEquals("huggingface", PackageURL.StandardTypes.HUGGINGFACE);
Assert.assertEquals("luarocks", PackageURL.StandardTypes.LUAROCKS);
Assert.assertEquals("maven", PackageURL.StandardTypes.MAVEN);
Assert.assertEquals("mlflow", PackageURL.StandardTypes.MLFLOW);
Assert.assertEquals("npm", PackageURL.StandardTypes.NPM);
Assert.assertEquals("nuget", PackageURL.StandardTypes.NUGET);
Assert.assertEquals("qpkg", PackageURL.StandardTypes.QPKG);
Assert.assertEquals("oci", PackageURL.StandardTypes.OCI);
Assert.assertEquals("pub", PackageURL.StandardTypes.PUB);
Assert.assertEquals("pypi", PackageURL.StandardTypes.PYPI);
Assert.assertEquals("rpm", PackageURL.StandardTypes.RPM);
Assert.assertEquals("swid", PackageURL.StandardTypes.SWID);
Assert.assertEquals("swift", PackageURL.StandardTypes.SWIFT);
}

@Test
Expand Down
Loading