8989 TypeRPM = "rpm"
9090 // TypeSwift is pkg:swift purl
9191 TypeSwift = "swift"
92+ // TypeHuggingface is pkg:huggingface purl.
93+ TypeHuggingface = "huggingface"
94+ // TypeMLflow is pkg:mlflow purl.
95+ TypeMLFlow = "mlflow"
9296)
9397
9498// Qualifier represents a single key=value qualifier in the package url
@@ -283,7 +287,7 @@ func FromString(purl string) (PackageURL, error) {
283287 remainder = nextSplit [1 ]
284288
285289 index = strings .LastIndex (remainder , "/" )
286- name := typeAdjustName (purlType , remainder [index + 1 :])
290+ name := typeAdjustName (purlType , remainder [index + 1 :], qualifiers )
287291 version := ""
288292
289293 atIndex := strings .Index (name , "@" )
@@ -292,7 +296,7 @@ func FromString(purl string) (PackageURL, error) {
292296 if err != nil {
293297 return PackageURL {}, fmt .Errorf ("failed to unescape purl version: %s" , err )
294298 }
295- version = v
299+ version = typeAdjustVersion ( purlType , v )
296300
297301 unecapeName , err := url .PathUnescape (name [:atIndex ])
298302 if err != nil {
@@ -342,24 +346,56 @@ func FromString(purl string) (PackageURL, error) {
342346// See https://github.com/package-url/purl-spec#known-purl-types
343347func typeAdjustNamespace (purlType , ns string ) string {
344348 switch purlType {
345- case TypeBitbucket , TypeDebian , TypeGithub , TypeGolang , TypeNPM , TypeRPM :
349+ case TypeBitbucket , TypeDebian , TypeGithub , TypeGolang , TypeNPM , TypeRPM , TypeComposer :
346350 return strings .ToLower (ns )
347351 }
348352 return ns
349353}
350354
351355// Make any purl type-specific adjustments to the parsed name.
352356// See https://github.com/package-url/purl-spec#known-purl-types
353- func typeAdjustName (purlType , name string ) string {
357+ func typeAdjustName (purlType , name string , qualifiers Qualifiers ) string {
358+ quals := qualifiers .Map ()
354359 switch purlType {
355- case TypeBitbucket , TypeDebian , TypeGithub , TypeGolang , TypeNPM :
360+ case TypeBitbucket , TypeDebian , TypeGithub , TypeGolang , TypeNPM , TypeComposer :
356361 return strings .ToLower (name )
357362 case TypePyPi :
358363 return strings .ToLower (strings .ReplaceAll (name , "_" , "-" ))
364+ case TypeMLFlow :
365+ return adjustMlflowName (name , quals )
359366 }
360367 return name
361368}
362369
370+ // Make any purl type-specific adjustments to the parsed version.
371+ // See https://github.com/package-url/purl-spec#known-purl-types
372+ func typeAdjustVersion (purlType , version string ) string {
373+ switch purlType {
374+ case TypeHuggingface :
375+ return strings .ToLower (version )
376+ }
377+ return version
378+ }
379+
380+ // https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#mlflow
381+ func adjustMlflowName (name string , qualifiers map [string ]string ) string {
382+ if repo , ok := qualifiers ["repository_url" ]; ok {
383+ if strings .Contains (repo , "azureml" ) {
384+ // Azure ML is case-sensitive and must be kept as-is
385+ return name
386+ } else if strings .Contains (repo , "databricks" ) {
387+ // Databricks is case-insensitive and must be lowercased
388+ return strings .ToLower (name )
389+ } else {
390+ // Unknown repository type, keep as-is
391+ return name
392+ }
393+ } else {
394+ // No repository qualifier given, keep as-is
395+ return name
396+ }
397+ }
398+
363399// validQualifierKey validates a qualifierKey against our QualifierKeyPattern.
364400func validQualifierKey (key string ) bool {
365401 return QualifierKeyPattern .MatchString (key )
0 commit comments