@@ -145,13 +145,7 @@ public PackageURL(final String purl) throws MalformedPackageURLException {
145145 } else {
146146 this .subpath = null ;
147147 }
148- // qualifiers are optional - check for existence
149- final String rawQuery = uri .getRawQuery ();
150- if (rawQuery != null && !rawQuery .isEmpty ()) {
151- this .qualifiers = parseQualifiers (rawQuery );
152- } else {
153- this .qualifiers = null ;
154- }
148+
155149 // this is the rest of the purl that needs to be parsed
156150 String remainder = uri .getRawPath ();
157151 // trim trailing '/'
@@ -169,6 +163,14 @@ public PackageURL(final String purl) throws MalformedPackageURLException {
169163 }
170164 this .type = StringUtil .toLowerCase (validateType (remainder .substring (start , index )));
171165
166+ // qualifiers are optional - check for existence
167+ final String rawQuery = uri .getRawQuery ();
168+ if (rawQuery != null && !rawQuery .isEmpty ()) {
169+ this .qualifiers = parseQualifiers (this .type , rawQuery );
170+ } else {
171+ this .qualifiers = null ;
172+ }
173+
172174 start = index + 1 ;
173175
174176 // version is optional - check for existence
@@ -183,10 +185,10 @@ public PackageURL(final String purl) throws MalformedPackageURLException {
183185 // The 'remainder' should now consist of an optional namespace and the name
184186 index = remainder .lastIndexOf ('/' );
185187 if (index <= start ) {
186- this .name = validateName (this .type , StringUtil .percentDecode (remainder .substring (start )));
188+ this .name = validateName (this .type , StringUtil .percentDecode (remainder .substring (start )), this . qualifiers );
187189 this .namespace = null ;
188190 } else {
189- this .name = validateName (this .type , StringUtil .percentDecode (remainder .substring (index + 1 )));
191+ this .name = validateName (this .type , StringUtil .percentDecode (remainder .substring (index + 1 )), this . qualifiers );
190192 remainder = remainder .substring (0 , index );
191193 this .namespace = validateNamespace (this .type , parsePath (remainder .substring (start ), false ));
192194 }
@@ -231,9 +233,9 @@ public PackageURL(
231233 throws MalformedPackageURLException {
232234 this .type = StringUtil .toLowerCase (validateType (requireNonNull (type , "type" )));
233235 this .namespace = validateNamespace (this .type , namespace );
234- this .name = validateName (this .type , requireNonNull (name , "name" ));
236+ this .qualifiers = parseQualifiers (this .type , qualifiers );
237+ this .name = validateName (this .type , requireNonNull (name , "name" ), this .qualifiers );
235238 this .version = validateVersion (this .type , version );
236- this .qualifiers = parseQualifiers (qualifiers );
237239 this .subpath = validateSubpath (subpath );
238240 verifyTypeConstraints (this .type , this .namespace , this .name );
239241 }
@@ -394,7 +396,7 @@ private static void validateChars(String value, IntPredicate predicate, String c
394396 return retVal ;
395397 }
396398
397- private static String validateName (final String type , final String value ) throws MalformedPackageURLException {
399+ private static String validateName (final String type , final String value , final Map < String , String > qualifiers ) throws MalformedPackageURLException {
398400 if (value .isEmpty ()) {
399401 throw new MalformedPackageURLException ("The PackageURL name specified is invalid" );
400402 }
@@ -412,6 +414,9 @@ private static String validateName(final String type, final String value) throws
412414 case StandardTypes .OCI :
413415 temp = StringUtil .toLowerCase (value );
414416 break ;
417+ case StandardTypes .MLFLOW :
418+ temp = validateMlflowName (value , qualifiers );
419+ break ;
415420 case StandardTypes .PUB :
416421 temp = StringUtil .toLowerCase (value ).replaceAll ("[^a-z0-9_]" , "_" );
417422 break ;
@@ -425,6 +430,19 @@ private static String validateName(final String type, final String value) throws
425430 return temp ;
426431 }
427432
433+ /*
434+ MLflow names are case-sensitive for Azure ML and must be kept as-is,
435+ for Databricks it is case insensitive and must be lowercased.
436+ */
437+ private static String validateMlflowName (final String name , final Map <String ,String > qualifiers ){
438+
439+ String value = qualifiers .get ("repository_url" );
440+ if (value != null && value .toLowerCase ().contains ("databricks" )) {
441+ return StringUtil .toLowerCase (name );
442+ }
443+ return name ;
444+ }
445+
428446 private static @ Nullable String validateVersion (final String type , final @ Nullable String value ) {
429447 if (value == null ) {
430448 return null ;
@@ -440,7 +458,7 @@ private static String validateName(final String type, final String value) throws
440458 }
441459 }
442460
443- private static @ Nullable Map <String , String > validateQualifiers (final @ Nullable Map <String , String > values )
461+ private static @ Nullable Map <String , String > validateQualifiers (final String type , final @ Nullable Map <String , String > values )
444462 throws MalformedPackageURLException {
445463 if (values == null || values .isEmpty ()) {
446464 return null ;
@@ -451,6 +469,22 @@ private static String validateName(final String type, final String value) throws
451469 validateKey (key );
452470 validateValue (key , entry .getValue ());
453471 }
472+
473+ switch (type ) {
474+ case StandardTypes .BAZEL :
475+ String defaultRegistry = "https://bcr.bazel.build" ;
476+ String repoURL = values .get ("repository_url" );
477+ String normalized = repoURL .toLowerCase ();
478+ if (normalized .endsWith ("/" )) {
479+ normalized = normalized .substring (0 , normalized .length () - 1 );
480+ }
481+
482+ if (normalized .equals (defaultRegistry )){
483+ values .remove ("repository_url" );
484+ }
485+ break ;
486+ }
487+
454488 return values ;
455489 }
456490
@@ -577,7 +611,7 @@ private static void verifyTypeConstraints(String type, @Nullable String namespac
577611 }
578612 }
579613
580- private static @ Nullable Map <String , String > parseQualifiers (final @ Nullable Map <String , String > qualifiers )
614+ private static @ Nullable Map <String , String > parseQualifiers (final String type , final @ Nullable Map <String , String > qualifiers )
581615 throws MalformedPackageURLException {
582616 if (qualifiers == null || qualifiers .isEmpty ()) {
583617 return null ;
@@ -590,14 +624,14 @@ private static void verifyTypeConstraints(String type, @Nullable String namespac
590624 TreeMap ::new ,
591625 (map , value ) -> map .put (StringUtil .toLowerCase (value .getKey ()), value .getValue ()),
592626 TreeMap ::putAll );
593- return validateQualifiers (results );
627+ return validateQualifiers (type , results );
594628 } catch (ValidationException ex ) {
595629 throw new MalformedPackageURLException (ex .getMessage ());
596630 }
597631 }
598632
599633 @ SuppressWarnings ("StringSplitter" ) // reason: surprising behavior is okay in this case
600- private static @ Nullable Map <String , String > parseQualifiers (final String encodedString )
634+ private static @ Nullable Map <String , String > parseQualifiers (final String type , final String encodedString )
601635 throws MalformedPackageURLException {
602636 try {
603637 final TreeMap <String , String > results = Arrays .stream (encodedString .split ("&" ))
@@ -615,7 +649,7 @@ private static void verifyTypeConstraints(String type, @Nullable String namespac
615649 }
616650 },
617651 TreeMap ::putAll );
618- return validateQualifiers (results );
652+ return validateQualifiers (type , results );
619653 } catch (ValidationException e ) {
620654 throw new MalformedPackageURLException (e );
621655 }
@@ -730,6 +764,12 @@ public static final class StandardTypes {
730764 * @since 2.0.0
731765 */
732766 public static final String APK = "apk" ;
767+ /**
768+ * Bazel-based packages.
769+ *
770+ * @since 2.0.0
771+ */
772+ public static final String BAZEL = "bazel" ;
733773 /**
734774 * Bitbucket-based packages.
735775 */
0 commit comments