Skip to content

Commit 7f63aaa

Browse files
authored
Merge branch 'master' into fix-lowercase
2 parents 30899e7 + af34fc4 commit 7f63aaa

File tree

2 files changed

+80
-59
lines changed

2 files changed

+80
-59
lines changed

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

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,7 @@
2828
* @since 1.0.0
2929
*/
3030
public class MalformedPackageURLException extends Exception {
31-
32-
private static final long serialVersionUID = 1095476478991047663L;
31+
private static final long serialVersionUID = -3428748639194901696L;
3332

3433
/**
3534
* Constructs a {@code MalformedPackageURLException} with no detail message.
@@ -47,4 +46,24 @@ public MalformedPackageURLException(String msg) {
4746
super(msg);
4847
}
4948

49+
/**
50+
* Constructs a new {@code MalformedPackageURLException} with the specified detail message and
51+
* cause.
52+
*
53+
* @param message the detail message
54+
* @param cause the cause
55+
*/
56+
public MalformedPackageURLException(String message, Throwable cause) {
57+
super(message, cause);
58+
}
59+
60+
/**
61+
* Constructs a new {@code MalformedPackageURLException} with the specified cause and a detail
62+
* message of {@code (cause==null ? null : cause.toString())}.
63+
*
64+
* @param cause the cause
65+
*/
66+
public MalformedPackageURLException(Throwable cause) {
67+
super(cause);
68+
}
5069
}

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

Lines changed: 59 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ public PackageURL(final String type, final String name) throws MalformedPackageU
9494
public PackageURL(final String type, final String namespace, final String name, final String version,
9595
final TreeMap<String, String> qualifiers, final String subpath)
9696
throws MalformedPackageURLException {
97-
9897
this.scheme = validateScheme("pkg");
9998
this.type = toLowerCase(validateType(type));
10099
this.namespace = validateNamespace(namespace);
@@ -106,9 +105,14 @@ public PackageURL(final String type, final String namespace, final String name,
106105
}
107106

108107
/**
109-
* The PackageURL scheme constant
108+
* The PackageURL scheme constant.
109+
*/
110+
public static final String SCHEME = "pkg";
111+
112+
/**
113+
* The PackageURL scheme ({@code "pkg"}) constant followed by a colon ({@code ':'}).
110114
*/
111-
private String scheme;
115+
private static final String SCHEME_PART = SCHEME + ':';
112116

113117
/**
114118
* The package "type" or package "protocol" such as maven, npm, nuget, gem, pypi, etc.
@@ -170,7 +174,7 @@ public PackageURLBuilder toBuilder() {
170174
* @since 1.0.0
171175
*/
172176
public String getScheme() {
173-
return scheme;
177+
return SCHEME;
174178
}
175179

176180
/**
@@ -233,11 +237,10 @@ public String getSubpath() {
233237
return subpath;
234238
}
235239

236-
private String validateScheme(final String value) throws MalformedPackageURLException {
237-
if ("pkg".equals(value)) {
238-
return "pkg";
239-
}
240-
throw new MalformedPackageURLException("The PackageURL scheme is invalid");
240+
private void validateScheme(final String value) throws MalformedPackageURLException {
241+
if (!SCHEME.equals(value)) {
242+
throw new MalformedPackageURLException("The PackageURL scheme '" + value + "' is invalid. It should be '" + SCHEME + "'");
243+
}
241244
}
242245

243246
private String validateType(final String value) throws MalformedPackageURLException {
@@ -364,8 +367,8 @@ private String validatePath(final String[] segments, final boolean isSubpath) th
364367
}
365368
return segment;
366369
}).collect(Collectors.joining("/"));
367-
} catch (ValidationException ex) {
368-
throw new MalformedPackageURLException(ex.getMessage());
370+
} catch (ValidationException e) {
371+
throw new MalformedPackageURLException(e);
369372
}
370373
}
371374

@@ -398,7 +401,7 @@ public String canonicalize() {
398401
*/
399402
private String canonicalize(boolean coordinatesOnly) {
400403
final StringBuilder purl = new StringBuilder();
401-
purl.append(scheme).append(":");
404+
purl.append(SCHEME_PART);
402405
if (type != null) {
403406
purl.append(type);
404407
}
@@ -563,78 +566,79 @@ public static String uriDecode(String source) {
563566
*/
564567
private void parse(final String purl) throws MalformedPackageURLException {
565568
if (purl == null || purl.trim().isEmpty()) {
566-
throw new MalformedPackageURLException("Invalid purl: Contains an empty or null value");
569+
throw new MalformedPackageURLException("Invalid purl: Is empty or null");
567570
}
568571

569572
try {
570-
final URI uri = new URI(purl);
571-
// Check to ensure that none of these parts are parsed. If so, it's an invalid purl.
572-
if (uri.getUserInfo() != null || uri.getPort() != -1) {
573-
throw new MalformedPackageURLException("Invalid purl: Contains parts not supported by the purl spec");
573+
if (!purl.startsWith(SCHEME_PART)) {
574+
throw new MalformedPackageURLException("Invalid purl: " + purl + ". It does not start with '" + SCHEME_PART + "'");
574575
}
575576

576-
this.scheme = validateScheme(uri.getScheme());
577+
final int length = purl.length();
578+
int start = SCHEME_PART.length();
577579

578-
// subpath is optional - check for existence
579-
if (uri.getRawFragment() != null && !uri.getRawFragment().isEmpty()) {
580-
this.subpath = validatePath(parsePath(uri.getRawFragment(), true), true);
580+
while (start < length && '/' == purl.charAt(start)) {
581+
start++;
581582
}
582-
// This is the purl (minus the scheme) that needs parsed.
583-
final StringBuilder remainder = new StringBuilder(uri.getRawSchemeSpecificPart());
584583

585-
// qualifiers are optional - check for existence
586-
int index = remainder.lastIndexOf("?");
587-
if (index >= 0) {
588-
this.qualifiers = parseQualifiers(remainder.substring(index + 1));
589-
remainder.setLength(index);
584+
final URI uri = new URI(String.join("/", SCHEME_PART, purl.substring(start)));
585+
586+
validateScheme(uri.getScheme());
587+
588+
// Check to ensure that none of these parts are parsed. If so, it's an invalid purl.
589+
if (uri.getRawAuthority() != null) {
590+
throw new MalformedPackageURLException("Invalid purl: A purl must NOT contain a URL Authority ");
591+
}
592+
593+
// subpath is optional - check for existence
594+
final String rawFragment = uri.getRawFragment();
595+
if (rawFragment != null && !rawFragment.isEmpty()) {
596+
this.subpath = validatePath(parsePath(rawFragment, true), true);
590597
}
598+
// qualifiers are optional - check for existence
599+
final String rawQuery = uri.getRawQuery();
600+
if (rawQuery != null && !rawQuery.isEmpty()) {
601+
this.qualifiers = parseQualifiers(rawQuery);
591602

592-
// trim leading and trailing '/'
603+
}
604+
// this is the rest of the purl that needs to be parsed
605+
String remainder = uri.getRawPath();
606+
// trim trailing '/'
593607
int end = remainder.length() - 1;
594608
while (end > 0 && '/' == remainder.charAt(end)) {
595609
end--;
596610
}
597-
if (end < remainder.length() - 1) {
598-
remainder.setLength(end + 1);
599-
}
600-
int start = 0;
601-
while (start < remainder.length() && '/' == remainder.charAt(start)) {
602-
start++;
603-
}
604-
//there is no need for the "expensive" delete operation if the start is tracked and used throughout the rest
605-
// of the parsing.
606-
//if (start > 0) {
607-
// remainder.delete(0, start);
608-
//}
609-
611+
remainder = remainder.substring(0, end + 1);
612+
// there is exactly one leading '/' at this point
613+
start = 1;
610614
// type
611-
index = remainder.indexOf("/", start);
615+
int index = remainder.indexOf('/', start);
612616
if (index <= start) {
613617
throw new MalformedPackageURLException("Invalid purl: does not contain both a type and name");
614618
}
615619
this.type = toLowerCase(validateType(remainder.substring(start, index)));
616-
//remainder.delete(0, index + 1);
620+
617621
start = index + 1;
618622

619623
// version is optional - check for existence
620-
index = remainder.lastIndexOf("@");
624+
index = remainder.lastIndexOf('@');
621625
if (index >= start) {
622626
this.version = validateVersion(percentDecode(remainder.substring(index + 1)));
623-
remainder.setLength(index);
627+
remainder = remainder.substring(0, index);
624628
}
625629

626-
// The 'remainder' should now consist of the an optional namespace, and the name
627-
index = remainder.lastIndexOf("/");
630+
// The 'remainder' should now consist of an optional namespace and the name
631+
index = remainder.lastIndexOf('/');
628632
if (index <= start) {
629633
this.name = validateName(percentDecode(remainder.substring(start)));
630634
} else {
631635
this.name = validateName(percentDecode(remainder.substring(index + 1)));
632-
remainder.setLength(index);
636+
remainder = remainder.substring(0, index);
633637
this.namespace = validateNamespace(parsePath(remainder.substring(start), false));
634638
}
635639
verifyTypeConstraints(this.type, this.namespace, this.name);
636640
} catch (URISyntaxException e) {
637-
throw new MalformedPackageURLException("Invalid purl: " + e.getMessage());
641+
throw new MalformedPackageURLException("Invalid purl: " + e.getMessage(), e);
638642
}
639643
}
640644

@@ -669,8 +673,8 @@ private Map<String, String> parseQualifiers(final String encodedString) throws M
669673
},
670674
TreeMap<String, String>::putAll);
671675
return validateQualifiers(results);
672-
} catch (ValidationException ex) {
673-
throw new MalformedPackageURLException(ex.getMessage());
676+
} catch (ValidationException e) {
677+
throw new MalformedPackageURLException(e);
674678
}
675679
}
676680

@@ -717,8 +721,7 @@ public boolean isBaseEquals(final PackageURL purl) {
717721
* @since 1.4.0
718722
*/
719723
public boolean isCoordinatesEquals(final PackageURL purl) {
720-
return Objects.equals(scheme, purl.scheme) &&
721-
Objects.equals(type, purl.type) &&
724+
return Objects.equals(type, purl.type) &&
722725
Objects.equals(namespace, purl.namespace) &&
723726
Objects.equals(name, purl.name) &&
724727
Objects.equals(version, purl.version);
@@ -753,8 +756,7 @@ public boolean equals(Object o) {
753756
if (this == o) return true;
754757
if (o == null || getClass() != o.getClass()) return false;
755758
final PackageURL other = (PackageURL) o;
756-
return Objects.equals(scheme, other.scheme) &&
757-
Objects.equals(type, other.type) &&
759+
return Objects.equals(type, other.type) &&
758760
Objects.equals(namespace, other.namespace) &&
759761
Objects.equals(name, other.name) &&
760762
Objects.equals(version, other.version) &&
@@ -764,7 +766,7 @@ public boolean equals(Object o) {
764766

765767
@Override
766768
public int hashCode() {
767-
return Objects.hash(scheme, type, namespace, name, version, qualifiers, subpath);
769+
return Objects.hash(type, namespace, name, version, qualifiers, subpath);
768770
}
769771

770772
/**

0 commit comments

Comments
 (0)