2222package com .github .packageurl ;
2323
2424import java .io .Serializable ;
25+ import java .net .MalformedURLException ;
2526import java .net .URI ;
2627import java .net .URISyntaxException ;
28+ import java .net .URL ;
2729import java .nio .charset .Charset ;
2830import java .nio .charset .StandardCharsets ;
2931import java .util .Arrays ;
@@ -99,7 +101,7 @@ public PackageURL(final String type, final String namespace, final String name,
99101 this .type = validateType (type );
100102 this .namespace = validateNamespace (namespace );
101103 this .name = validateName (name );
102- this .version = validateVersion (version );
104+ this .version = validateVersion (type , version );
103105 this .qualifiers = validateQualifiers (qualifiers );
104106 this .subpath = validatePath (subpath , true );
105107 verifyTypeConstraints (this .type , this .namespace , this .name );
@@ -271,13 +273,25 @@ private String validateNamespace(final String[] values) throws MalformedPackageU
271273
272274 String retVal ;
273275 switch (type ) {
276+ case StandardTypes .APK :
274277 case StandardTypes .BITBUCKET :
275- case StandardTypes .DEBIAN :
278+ case StandardTypes .COMPOSER :
279+ case StandardTypes .DEB :
276280 case StandardTypes .GITHUB :
277281 case StandardTypes .GOLANG :
282+ case StandardTypes .HEX :
283+ case StandardTypes .LUAROCKS :
284+ case StandardTypes .QPKG :
278285 case StandardTypes .RPM :
279286 retVal = tempNamespace .toLowerCase ();
280287 break ;
288+ case StandardTypes .MLFLOW :
289+ case StandardTypes .OCI :
290+ if (tempNamespace != null ) {
291+ throw new MalformedPackageURLException ("The PackageURL specified contains a namespace which is not allowed for type: " + type );
292+ }
293+ retVal = null ;
294+ break ;
281295 default :
282296 retVal = tempNamespace ;
283297 break ;
@@ -291,14 +305,22 @@ private String validateName(final String value) throws MalformedPackageURLExcept
291305 }
292306 String temp ;
293307 switch (type ) {
308+ case StandardTypes .APK :
294309 case StandardTypes .BITBUCKET :
295- case StandardTypes .DEBIAN :
310+ case StandardTypes .BITNAMI :
311+ case StandardTypes .COMPOSER :
312+ case StandardTypes .DEB :
296313 case StandardTypes .GITHUB :
297- case StandardTypes .GOLANG :
314+ case StandardTypes .HEX :
315+ case StandardTypes .LUAROCKS :
316+ case StandardTypes .OCI :
298317 temp = value .toLowerCase ();
299318 break ;
319+ case StandardTypes .PUB :
320+ temp = value .toLowerCase ().replaceAll ("[^a-z0-9_]" , "_" );
321+ break ;
300322 case StandardTypes .PYPI :
301- temp = value .replaceAll ( "_" , "-" ). toLowerCase ( );
323+ temp = value .toLowerCase (). replace ( '_' , '-' );
302324 break ;
303325 default :
304326 temp = value ;
@@ -307,17 +329,26 @@ private String validateName(final String value) throws MalformedPackageURLExcept
307329 return temp ;
308330 }
309331
310- private String validateVersion (final String value ) {
332+ private String validateVersion (final String type , final String value ) {
311333 if (value == null ) {
312334 return null ;
313335 }
314- return value ;
336+
337+ switch (type ) {
338+ case StandardTypes .HUGGINGFACE :
339+ case StandardTypes .LUAROCKS :
340+ case StandardTypes .OCI :
341+ return value .toLowerCase ();
342+ default :
343+ return value ;
344+ }
315345 }
316346
317347 private Map <String , String > validateQualifiers (final Map <String , String > values ) throws MalformedPackageURLException {
318348 if (values == null ) {
319349 return null ;
320350 }
351+
321352 for (Map .Entry <String , String > entry : values .entrySet ()) {
322353 validateKey (entry .getKey ());
323354 final String value = entry .getValue ();
@@ -460,7 +491,7 @@ private static String uriEncode(String source, Charset charset) {
460491 }
461492
462493 private static boolean isUnreserved (int c ) {
463- return (isAlpha (c ) || isDigit (c ) || '-' == c || '.' == c || '_' == c || '~' == c );
494+ return (isAlpha (c ) || isDigit (c ) || '-' == c || '.' == c || '_' == c || '~' == c || ':' == c || '/' == c );
464495 }
465496
466497 private static boolean isAlpha (int c ) {
@@ -575,7 +606,7 @@ private void parse(final String purl) throws MalformedPackageURLException {
575606 // version is optional - check for existence
576607 index = remainder .lastIndexOf ("@" );
577608 if (index >= start ) {
578- this .version = validateVersion (percentDecode (remainder .substring (index + 1 )));
609+ this .version = validateVersion (type , percentDecode (remainder .substring (index + 1 )));
579610 remainder .setLength (index );
580611 }
581612
@@ -602,10 +633,57 @@ private void parse(final String purl) throws MalformedPackageURLException {
602633 * @throws MalformedPackageURLException if constraints are not met
603634 */
604635 private void verifyTypeConstraints (String type , String namespace , String name ) throws MalformedPackageURLException {
605- if (StandardTypes .MAVEN .equals (type )) {
606- if (namespace == null || namespace .isEmpty () || name == null || name .isEmpty ()) {
607- throw new MalformedPackageURLException ("The PackageURL specified is invalid. Maven requires both a namespace and name." );
608- }
636+ switch (type ) {
637+ case StandardTypes .CONAN :
638+ if ((namespace != null || qualifiers != null ) && (namespace == null || (qualifiers == null || !qualifiers .containsKey ("channel" )))) {
639+ throw new MalformedPackageURLException ("The PackageURL specified is invalid. Conan requires a namespace to have a 'channel' qualifier" );
640+ }
641+ break ;
642+ case StandardTypes .CPAN :
643+ if (name == null || name .indexOf ('-' ) != -1 ) {
644+ throw new MalformedPackageURLException ("The PackageURL specified is invalid. CPAN requires a name" );
645+ }
646+ if (namespace != null && (name .contains ("::" ) || name .indexOf ('-' ) != -1 )) {
647+ throw new MalformedPackageURLException ("The PackageURL specified is invalid. CPAN name may not contain '::' or '-'" );
648+ }
649+ break ;
650+ case StandardTypes .CRAN :
651+ if (version == null ) {
652+ throw new MalformedPackageURLException ("The PackageURL specified is invalid. CRAN requires a version" );
653+ }
654+ break ;
655+ case StandardTypes .HACKAGE :
656+ if (name == null || version == null ) {
657+ throw new MalformedPackageURLException ("The PackageURL specified is invalid. Hackage requires a name and version" );
658+ }
659+ break ;
660+ case StandardTypes .MAVEN :
661+ if (namespace == null || name == null ) {
662+ throw new MalformedPackageURLException ("The PackageURL specified is invalid. Maven requires both a namespace and name" );
663+ }
664+ break ;
665+ case StandardTypes .MLFLOW :
666+ if (qualifiers != null ) {
667+ String repositoryUrl = qualifiers .get ("repository_url" );
668+ if (repositoryUrl != null ) {
669+ String host = null ;
670+ try {
671+ URL url = new URL (repositoryUrl );
672+ host = url .getHost ();
673+ if (host .matches (".*[.]?azuredatabricks.net$" )) {
674+ this .name = name .toLowerCase ();
675+ }
676+ } catch (MalformedURLException e ) {
677+ throw new MalformedPackageURLException ("The PackageURL specified is invalid. MLFlow repository_url is not a valid URL for host " + host );
678+ }
679+ }
680+ }
681+ break ;
682+ case StandardTypes .SWIFT :
683+ if (namespace == null || name == null || version == null ) {
684+ throw new MalformedPackageURLException ("The PackageURL specified is invalid. Swift requires a namespace, name, and version" );
685+ }
686+ break ;
609687 }
610688 }
611689
@@ -727,24 +805,42 @@ public int hashCode() {
727805 *
728806 * @since 1.0.0
729807 */
730- public static class StandardTypes {
808+ public static final class StandardTypes {
809+ public static final String ALPM = "alpm" ;
810+ public static final String APK = "apk" ;
731811 public static final String BITBUCKET = "bitbucket" ;
812+ public static final String BITNAMI = "bitnami" ;
813+ public static final String COCOAPODS = "cocoapods" ;
732814 public static final String CARGO = "cargo" ;
733815 public static final String COMPOSER = "composer" ;
734- public static final String DEBIAN = "deb" ;
816+ public static final String CONAN = "conan" ;
817+ public static final String CONDA = "conda" ;
818+ public static final String CPAN = "cpan" ;
819+ public static final String CRAN = "cran" ;
820+ public static final String DEB = "deb" ;
735821 public static final String DOCKER = "docker" ;
736822 public static final String GEM = "gem" ;
737823 public static final String GENERIC = "generic" ;
738824 public static final String GITHUB = "github" ;
739825 public static final String GOLANG = "golang" ;
826+ public static final String HACKAGE = "hackage" ;
740827 public static final String HEX = "hex" ;
828+ public static final String HUGGINGFACE = "huggingface" ;
829+ public static final String LUAROCKS = "luarocks" ;
741830 public static final String MAVEN = "maven" ;
831+ public static final String MLFLOW = "mlflow" ;
742832 public static final String NPM = "npm" ;
743833 public static final String NUGET = "nuget" ;
834+ public static final String QPKG = "qpkg" ;
835+ public static final String OCI = "oci" ;
836+ public static final String PUB = "pub" ;
744837 public static final String PYPI = "pypi" ;
745838 public static final String RPM = "rpm" ;
746- public static final String NIXPKGS = "nixpkgs" ;
747- public static final String HACKAGE = "hackage" ;
839+ public static final String SWID = "swid" ;
840+ public static final String SWIFT = "swift" ;
841+ // FIXME: Remove this since it should be named DEB
842+ public static final String DEBIAN = "deb" ;
843+ // FIXME: Remove this since it should be named NIX and it is not a standard type
844+ public static final String NIXPKGS = "nix" ;
748845 }
749-
750846}
0 commit comments