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,14 @@ private static void validateValue(final String key, final @Nullable String value
538
537
}
539
538
}
540
539
540
+ public PackageURL normalize () throws MalformedPackageURLException {
541
+ System .out .println ("Normalizing PackageURL " + type + " " + namespace + " " + name + " " + version + " "
542
+ + qualifiers + " " + subpath );
543
+ PackageTypeFactory .getInstance ().validateComponents (type , namespace , name , version , qualifiers , subpath );
544
+ return PackageTypeFactory .getInstance ()
545
+ .normalizeComponents (type , namespace , name , version , qualifiers , subpath );
546
+ }
547
+
541
548
/**
542
549
* Returns the canonicalized representation of the purl.
543
550
*
@@ -565,6 +572,17 @@ public String canonicalize() {
565
572
* @since 1.3.2
566
573
*/
567
574
private String canonicalize (boolean coordinatesOnly ) {
575
+ try {
576
+ PackageURL packageURL = normalize ();
577
+ namespace = packageURL .getNamespace ();
578
+ name = packageURL .getName ();
579
+ version = packageURL .getVersion ();
580
+ qualifiers = packageURL .getQualifiers ();
581
+ subpath = packageURL .getSubpath ();
582
+ } catch (MalformedPackageURLException e ) {
583
+ throw new ValidationException ("Normalization failed" , e );
584
+ }
585
+
568
586
final StringBuilder purl = new StringBuilder ();
569
587
purl .append (SCHEME_PART ).append (type ).append ('/' );
570
588
if (namespace != null ) {
@@ -577,7 +595,7 @@ private String canonicalize(boolean coordinatesOnly) {
577
595
}
578
596
579
597
if (!coordinatesOnly ) {
580
- if (qualifiers != null ) {
598
+ if (! qualifiers . isEmpty () ) {
581
599
purl .append ('?' );
582
600
Set <Map .Entry <String , String >> entries = qualifiers .entrySet ();
583
601
boolean separator = false ;
@@ -606,18 +624,22 @@ private static boolean shouldEncode(int c) {
606
624
return !isUnreserved (c );
607
625
}
608
626
609
- private static boolean isAlpha (int c ) {
627
+ public static boolean isAlpha (int c ) {
610
628
return (isLowerCase (c ) || isUpperCase (c ));
611
629
}
612
630
613
631
private static boolean isDigit (int c ) {
614
632
return (c >= '0' && c <= '9' );
615
633
}
616
634
617
- private static boolean isAlphaNumeric (int c ) {
635
+ public static boolean isAlphaNumeric (int c ) {
618
636
return (isDigit (c ) || isAlpha (c ));
619
637
}
620
638
639
+ public static boolean isWhitespace (int c ) {
640
+ return (c == ' ' || c == '\t' || c == '\r' || c == '\n' );
641
+ }
642
+
621
643
private static boolean isUpperCase (int c ) {
622
644
return (c >= 'A' && c <= 'Z' );
623
645
}
@@ -642,7 +664,7 @@ private static int toLowerCase(int c) {
642
664
return isUpperCase (c ) ? (c ^ 0x20 ) : c ;
643
665
}
644
666
645
- static String toLowerCase (String s ) {
667
+ public static String toLowerCase (String s ) {
646
668
int pos = indexOfFirstUpperCaseChar (s );
647
669
648
670
if (pos == -1 ) {
@@ -770,22 +792,6 @@ static String percentEncode(final String source) {
770
792
return changed ? new String (buffer .array (), 0 , buffer .position (), StandardCharsets .UTF_8 ) : source ;
771
793
}
772
794
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
795
private static @ Nullable Map <String , String > parseQualifiers (final @ Nullable Map <String , String > qualifiers )
790
796
throws MalformedPackageURLException {
791
797
if (qualifiers == null || qualifiers .isEmpty ()) {
@@ -1107,15 +1113,15 @@ public static final class StandardTypes {
1107
1113
* @deprecated use {@link #DEB} instead
1108
1114
*/
1109
1115
@ Deprecated
1110
- public static final String DEBIAN = "deb" ;
1116
+ public static final String DEBIAN = DEB ;
1111
1117
/**
1112
1118
* Nixos packages.
1113
1119
*
1114
1120
* @since 1.1.0
1115
1121
* @deprecated use {@link #NIX} instead
1116
1122
*/
1117
1123
@ Deprecated
1118
- public static final String NIXPKGS = "nix" ;
1124
+ public static final String NIXPKGS = NIX ;
1119
1125
1120
1126
private StandardTypes () {}
1121
1127
}
0 commit comments