@@ -94,7 +94,7 @@ 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- this .type = validateType (type );
97+ this .type = toLowerCase ( validateType (type ) );
9898 this .namespace = validateNamespace (namespace );
9999 this .name = validateName (name );
100100 this .version = validateVersion (version );
@@ -246,16 +246,19 @@ private String validateType(final String value) throws MalformedPackageURLExcept
246246 if (value == null || value .isEmpty ()) {
247247 throw new MalformedPackageURLException ("The PackageURL type cannot be null or empty" );
248248 }
249- if (value .charAt (0 ) >= '0' && value .charAt (0 ) <= '9' ) {
249+
250+ if (isDigit (value .charAt (0 ))) {
250251 throw new MalformedPackageURLException ("The PackageURL type cannot start with a number" );
251252 }
252- final String retVal = value .toLowerCase ();
253- if (retVal .chars ().anyMatch (c -> !(c == '.' || c == '+' || c == '-'
254- || (c >= 'a' && c <= 'z' )
255- || (c >= '0' && c <= '9' )))) {
253+
254+ if (!value .chars ().allMatch (c -> (c == '.' || c == '+' || c == '-'
255+ || isUpperCase (c )
256+ || isLowerCase (c )
257+ || isDigit (c )))) {
256258 throw new MalformedPackageURLException ("The PackageURL type contains invalid characters" );
257259 }
258- return retVal ;
260+
261+ return value ;
259262 }
260263
261264 private String validateNamespace (final String value ) throws MalformedPackageURLException {
@@ -270,15 +273,14 @@ private String validateNamespace(final String[] values) throws MalformedPackageU
270273 return null ;
271274 }
272275 final String tempNamespace = validatePath (values , false );
273-
274276 String retVal ;
275277 switch (type ) {
276278 case StandardTypes .BITBUCKET :
277279 case StandardTypes .DEBIAN :
278280 case StandardTypes .GITHUB :
279281 case StandardTypes .GOLANG :
280282 case StandardTypes .RPM :
281- retVal = tempNamespace . toLowerCase () ;
283+ retVal = tempNamespace != null ? toLowerCase (tempNamespace ) : null ;
282284 break ;
283285 default :
284286 retVal = tempNamespace ;
@@ -297,10 +299,10 @@ private String validateName(final String value) throws MalformedPackageURLExcept
297299 case StandardTypes .DEBIAN :
298300 case StandardTypes .GITHUB :
299301 case StandardTypes .GOLANG :
300- temp = value . toLowerCase ();
302+ temp = toLowerCase (value );
301303 break ;
302304 case StandardTypes .PYPI :
303- temp = value . replaceAll ( "_" , "-" ). toLowerCase ( );
305+ temp = toLowerCase ( value ). replace ( '_' , '-' );
304306 break ;
305307 default :
306308 temp = value ;
@@ -330,16 +332,15 @@ private Map<String, String> validateQualifiers(final Map<String, String> values)
330332 return values ;
331333 }
332334
333- private String validateKey (final String value ) throws MalformedPackageURLException {
335+ private void validateKey (final String value ) throws MalformedPackageURLException {
334336 if (value == null || value .isEmpty ()) {
335337 throw new MalformedPackageURLException ("Qualifier key is invalid: " + value );
336338 }
337- final String retValue = value . toLowerCase ();
338- if ((value .charAt (0 ) >= '0' && value . charAt ( 0 ) <= '9' )
339- || !value .chars ().allMatch (c -> ( c >= 'a' && c <= 'z' ) || (c >= '0' && c <= '9' ) || c == '.' || c == '-' || c == '_' )) {
339+
340+ if (isDigit (value .charAt (0 ))
341+ || !value .chars ().allMatch (c -> isLowerCase ( c ) || (isDigit ( c ) ) || c == '.' || c == '-' || c == '_' )) {
340342 throw new MalformedPackageURLException ("Qualifier key is invalid: " + value );
341343 }
342- return retValue ;
343344 }
344345
345346 private String validatePath (final String value , final boolean isSubpath ) throws MalformedPackageURLException {
@@ -418,7 +419,7 @@ private String canonicalize(boolean coordinatesOnly) {
418419 if (qualifiers != null && qualifiers .size () > 0 ) {
419420 purl .append ("?" );
420421 qualifiers .entrySet ().stream ().forEachOrdered ((entry ) -> {
421- purl .append (entry .getKey (). toLowerCase ( ));
422+ purl .append (toLowerCase ( entry .getKey ()));
422423 purl .append ("=" );
423424 purl .append (percentEncode (entry .getValue ()));
424425 purl .append ("&" );
@@ -466,13 +467,56 @@ private static boolean isUnreserved(int c) {
466467 }
467468
468469 private static boolean isAlpha (int c ) {
469- return (( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ));
470+ return (isLowerCase ( c ) || isUpperCase ( c ));
470471 }
471472
472473 private static boolean isDigit (int c ) {
473474 return (c >= '0' && c <= '9' );
474475 }
475476
477+ private static boolean isUpperCase (int c ) {
478+ return (c >= 'A' && c <= 'Z' );
479+ }
480+
481+ private static int indexOfFirstUpperCaseChar (String s ) {
482+ int length = s .length ();
483+
484+ for (int i = 0 ; i < length ; i ++) {
485+ if (isUpperCase (s .charAt (i ))) {
486+ return i ;
487+ }
488+ }
489+
490+ return -1 ;
491+ }
492+
493+ private static boolean isLowerCase (int c ) {
494+ return (c >= 'a' && c <= 'z' );
495+ }
496+
497+ private static int toLowerCase (int c ) {
498+ return (c ^ 0x20 );
499+ }
500+
501+ private static String toLowerCase (String s ) {
502+ int pos = indexOfFirstUpperCaseChar (s );
503+
504+ if (pos == -1 ) {
505+ return s ;
506+ }
507+
508+ char [] chars = s .toCharArray ();
509+ int length = chars .length ;
510+
511+ for (int i = pos ; i < length ; i ++) {
512+ if (isUpperCase (chars [i ])) {
513+ chars [i ] = (char ) toLowerCase (chars [i ]);
514+ }
515+ }
516+
517+ return new String (chars );
518+ }
519+
476520 /**
477521 * Optionally decodes a String, if it's encoded. If String is not encoded,
478522 * method will return the original input value.
@@ -571,7 +615,8 @@ private void parse(final String purl) throws MalformedPackageURLException {
571615 if (index <= start ) {
572616 throw new MalformedPackageURLException ("Invalid purl: does not contain both a type and name" );
573617 }
574- this .type = validateType (remainder .substring (start , index ).toLowerCase ());
618+ this .type = toLowerCase (validateType (remainder .substring (start , index )));
619+
575620 start = index + 1 ;
576621
577622 // version is optional - check for existence
@@ -619,8 +664,9 @@ private Map<String, String> parseQualifiers(final String encodedString) throws M
619664 (map , value ) -> {
620665 final String [] entry = value .split ("=" , 2 );
621666 if (entry .length == 2 && !entry [1 ].isEmpty ()) {
622- if (map .put (entry [0 ].toLowerCase (), percentDecode (entry [1 ])) != null ) {
623- throw new ValidationException ("Duplicate package qualifier encountere - more then one value was specified for " + entry [0 ].toLowerCase ());
667+ String key = toLowerCase (entry [0 ]);
668+ if (map .put (key , percentDecode (entry [1 ])) != null ) {
669+ throw new ValidationException ("Duplicate package qualifier encountered. More then one value was specified for " + key );
624670 }
625671 }
626672 },
0 commit comments