23
23
24
24
import static java .util .Objects .requireNonNull ;
25
25
26
+ import com .github .packageurl .type .PackageTypeFactory ;
26
27
import java .io .Serializable ;
27
28
import java .net .URI ;
28
29
import java .net .URISyntaxException ;
@@ -77,34 +78,34 @@ public final class PackageURL implements Serializable {
77
78
private final String type ;
78
79
79
80
/**
80
- * The name prefix such as a Maven groupid , a Docker image owner, a GitHub user or organization.
81
+ * The name prefix such as a Maven groupId , a Docker image owner, a GitHub user or organization.
81
82
* Optional and type-specific.
82
83
*/
83
- private final @ Nullable String namespace ;
84
+ private @ Nullable String namespace ;
84
85
85
86
/**
86
87
* The name of the package.
87
88
* Required.
88
89
*/
89
- private final String name ;
90
+ private String name ;
90
91
91
92
/**
92
93
* The version of the package.
93
94
* Optional.
94
95
*/
95
- private final @ Nullable String version ;
96
+ private @ Nullable String version ;
96
97
97
98
/**
98
99
* Extra qualifying data for a package such as an OS, architecture, a distro, etc.
99
100
* Optional and type-specific.
100
101
*/
101
- private final @ Nullable Map <String , String > qualifiers ;
102
+ private @ Nullable Map <String , String > qualifiers ;
102
103
103
104
/**
104
105
* Extra subpath within a package, relative to the package root.
105
106
* Optional.
106
107
*/
107
- private final @ Nullable String subpath ;
108
+ private @ Nullable String subpath ;
108
109
109
110
/**
110
111
* Constructs a new PackageURL object by parsing the specified string.
@@ -194,7 +195,6 @@ public PackageURL(final String purl) throws MalformedPackageURLException {
194
195
remainder = remainder .substring (0 , index );
195
196
this .namespace = validateNamespace (this .type , parsePath (remainder .substring (start ), false ));
196
197
}
197
- verifyTypeConstraints (this .type , this .namespace , this .name );
198
198
} catch (URISyntaxException e ) {
199
199
throw new MalformedPackageURLException ("Invalid purl: " + e .getMessage (), e );
200
200
}
@@ -264,7 +264,6 @@ public PackageURL(
264
264
this .version = validateVersion (this .type , version );
265
265
this .qualifiers = parseQualifiers (qualifiers );
266
266
this .subpath = validateSubpath (subpath );
267
- verifyTypeConstraints (this .type , this .namespace , this .name );
268
267
}
269
268
270
269
/**
@@ -360,11 +359,11 @@ private static String validateType(final String value) throws MalformedPackageUR
360
359
return value ;
361
360
}
362
361
363
- private static boolean isValidCharForType (int c ) {
362
+ public static boolean isValidCharForType (int c ) {
364
363
return (isAlphaNumeric (c ) || c == '.' || c == '+' || c == '-' );
365
364
}
366
365
367
- private static boolean isValidCharForKey (int c ) {
366
+ public static boolean isValidCharForKey (int c ) {
368
367
return (isAlphaNumeric (c ) || c == '.' || c == '_' || c == '-' );
369
368
}
370
369
@@ -538,6 +537,12 @@ private static void validateValue(final String key, final @Nullable String value
538
537
}
539
538
}
540
539
540
+ public PackageURL normalize () throws MalformedPackageURLException {
541
+ PackageTypeFactory .getInstance ().validateComponents (type , namespace , name , version , qualifiers , subpath );
542
+ return PackageTypeFactory .getInstance ()
543
+ .normalizeComponents (type , namespace , name , version , qualifiers , subpath );
544
+ }
545
+
541
546
/**
542
547
* Returns the canonicalized representation of the purl.
543
548
*
@@ -565,6 +570,17 @@ public String canonicalize() {
565
570
* @since 1.3.2
566
571
*/
567
572
private String canonicalize (boolean coordinatesOnly ) {
573
+ try {
574
+ PackageURL packageURL = normalize ();
575
+ namespace = packageURL .getNamespace ();
576
+ name = packageURL .getName ();
577
+ version = packageURL .getVersion ();
578
+ qualifiers = packageURL .getQualifiers ();
579
+ subpath = packageURL .getSubpath ();
580
+ } catch (MalformedPackageURLException e ) {
581
+ throw new ValidationException ("Normalization failed" , e );
582
+ }
583
+
568
584
final StringBuilder purl = new StringBuilder ();
569
585
purl .append (SCHEME_PART ).append (type ).append ('/' );
570
586
if (namespace != null ) {
@@ -577,7 +593,7 @@ private String canonicalize(boolean coordinatesOnly) {
577
593
}
578
594
579
595
if (!coordinatesOnly ) {
580
- if (qualifiers != null ) {
596
+ if (! qualifiers . isEmpty () ) {
581
597
purl .append ('?' );
582
598
Set <Map .Entry <String , String >> entries = qualifiers .entrySet ();
583
599
boolean separator = false ;
@@ -606,18 +622,22 @@ private static boolean shouldEncode(int c) {
606
622
return !isUnreserved (c );
607
623
}
608
624
609
- private static boolean isAlpha (int c ) {
625
+ public static boolean isAlpha (int c ) {
610
626
return (isLowerCase (c ) || isUpperCase (c ));
611
627
}
612
628
613
629
private static boolean isDigit (int c ) {
614
630
return (c >= '0' && c <= '9' );
615
631
}
616
632
617
- private static boolean isAlphaNumeric (int c ) {
633
+ public static boolean isAlphaNumeric (int c ) {
618
634
return (isDigit (c ) || isAlpha (c ));
619
635
}
620
636
637
+ public static boolean isWhitespace (int c ) {
638
+ return (c == ' ' || c == '\t' || c == '\r' || c == '\n' );
639
+ }
640
+
621
641
private static boolean isUpperCase (int c ) {
622
642
return (c >= 'A' && c <= 'Z' );
623
643
}
@@ -642,7 +662,7 @@ private static int toLowerCase(int c) {
642
662
return isUpperCase (c ) ? (c ^ 0x20 ) : c ;
643
663
}
644
664
645
- static String toLowerCase (String s ) {
665
+ public static String toLowerCase (String s ) {
646
666
int pos = indexOfFirstUpperCaseChar (s );
647
667
648
668
if (pos == -1 ) {
@@ -770,22 +790,6 @@ static String percentEncode(final String source) {
770
790
return changed ? new String (buffer .array (), 0 , buffer .position (), StandardCharsets .UTF_8 ) : source ;
771
791
}
772
792
773
- /**
774
- * Some purl types may have specific constraints. This method attempts to verify them.
775
- * @param type the purl type
776
- * @param namespace the purl namespace
777
- * @throws MalformedPackageURLException if constraints are not met
778
- */
779
- private static void verifyTypeConstraints (String type , @ Nullable String namespace , @ Nullable String name )
780
- throws MalformedPackageURLException {
781
- if (StandardTypes .MAVEN .equals (type )) {
782
- if (isEmpty (namespace ) || isEmpty (name )) {
783
- throw new MalformedPackageURLException (
784
- "The PackageURL specified is invalid. Maven requires both a namespace and name." );
785
- }
786
- }
787
- }
788
-
789
793
private static @ Nullable Map <String , String > parseQualifiers (final @ Nullable Map <String , String > qualifiers )
790
794
throws MalformedPackageURLException {
791
795
if (qualifiers == null || qualifiers .isEmpty ()) {
@@ -1107,15 +1111,15 @@ public static final class StandardTypes {
1107
1111
* @deprecated use {@link #DEB} instead
1108
1112
*/
1109
1113
@ Deprecated
1110
- public static final String DEBIAN = "deb" ;
1114
+ public static final String DEBIAN = DEB ;
1111
1115
/**
1112
1116
* Nixos packages.
1113
1117
*
1114
1118
* @since 1.1.0
1115
1119
* @deprecated use {@link #NIX} instead
1116
1120
*/
1117
1121
@ Deprecated
1118
- public static final String NIXPKGS = "nix" ;
1122
+ public static final String NIXPKGS = NIX ;
1119
1123
1120
1124
private StandardTypes () {}
1121
1125
}
0 commit comments