@@ -593,6 +593,100 @@ def build_alpm_download_url(purl_str):
593
593
return url
594
594
595
595
596
+ def normalize_version (version : str ) -> str :
597
+ """
598
+ Remove the epoch (if any) from a Debian version.
599
+ E.g., "1:2.4.47-2" becomes "2.4.47-2"
600
+ """
601
+ if ":" in version :
602
+ _ , v = version .split (":" , 1 )
603
+ return v
604
+ return version
605
+
606
+
607
+ @download_router .route ("pkg:deb/.*" )
608
+ def build_deb_download_url (purl_str : str ) -> str :
609
+ """
610
+ Construct a download URL for a Debian or Ubuntu package PURL.
611
+ Supports optional 'repository_url' in qualifiers.
612
+ """
613
+ p = PackageURL .from_string (purl_str )
614
+
615
+ name = p .name
616
+ version = p .version
617
+ namespace = p .namespace
618
+ qualifiers = p .qualifiers or {}
619
+ arch = qualifiers .get ("arch" )
620
+ repository_url = qualifiers .get ("repository_url" )
621
+
622
+ if not name or not version :
623
+ raise ValueError ("Both name and version must be present in deb purl" )
624
+
625
+ if not arch :
626
+ arch = "source"
627
+
628
+ if repository_url :
629
+ base_url = repository_url .rstrip ("/" )
630
+ else :
631
+ if namespace == "debian" :
632
+ base_url = "https://deb.debian.org/debian"
633
+ elif namespace == "ubuntu" :
634
+ base_url = "http://archive.ubuntu.com/ubuntu"
635
+ else :
636
+ raise NotImplementedError (f"Unsupported distro namespace: { namespace } " )
637
+
638
+ norm_version = normalize_version (version )
639
+
640
+ if arch == "source" :
641
+ filename = f"{ name } _{ norm_version } .dsc"
642
+ else :
643
+ filename = f"{ name } _{ norm_version } _{ arch } .deb"
644
+
645
+ pool_path = f"/pool/main/{ name [0 ].lower ()} /{ name } "
646
+
647
+ return f"{ base_url } { pool_path } /{ filename } "
648
+
649
+
650
+ @download_router .route ("pkg:qpkg/.*" )
651
+ def build_qpkg_download_url (purl : str ) -> str :
652
+ purl = PackageURL .from_string (purl )
653
+ repo_url = purl .qualifiers .get ("repo_url" )
654
+
655
+ if not repo_url :
656
+ raise ValueError ("repository_url qualifier is required for qpkg purl resolution" )
657
+
658
+ if not purl .namespace or not purl .name or not purl .version :
659
+ raise ValueError ("namespace, name, and version must be present in qpkg purl" )
660
+
661
+ path = f"{ purl .namespace } /{ purl .name } /{ purl .version } .qpkg"
662
+ return f"{ repo_url .rstrip ('/' )} /{ path } "
663
+
664
+
665
+ @download_router .route ("pkg:apk/.*" )
666
+ def build_apk_download_url (purl ):
667
+ """
668
+ Return a download URL for a fully qualified Alpine Linux package PURL.
669
+
670
+ Example:
671
+ pkg:apk/[email protected] ?arch=x86&alpine_version=v3.11&repo=main
672
+ """
673
+ purl = PackageURL .from_string (purl )
674
+ name = purl .name
675
+ version = purl .version
676
+ arch = purl .qualifiers .get ("arch" )
677
+ repo = purl .qualifiers .get ("repo" )
678
+ alpine_version = purl .qualifiers .get ("alpine_version" )
679
+
680
+ if not name or not version or not arch or not repo or not alpine_version :
681
+ raise ValueError (
682
+ "All qualifiers (arch, repo, alpine_version) and name/version must be present in apk purl"
683
+ )
684
+
685
+ return (
686
+ f"https://dl-cdn.alpinelinux.org/alpine/{ alpine_version } /{ repo } /{ arch } /{ name } -{ version } .apk"
687
+ )
688
+
689
+
596
690
def get_repo_download_url (purl ):
597
691
"""
598
692
Return ``download_url`` if present in ``purl`` qualifiers or
0 commit comments