@@ -667,6 +667,233 @@ def build_bitbucket_purl(url):
667667 )
668668
669669
670+ def build_route_regex (domain_patterns , path_suffix = "/.*" ):
671+ """
672+ Build a route regex from a list of domains
673+ """
674+ domain_pattern = "|" .join (domain_patterns )
675+ return rf"https?://({ domain_pattern } ){ path_suffix } "
676+
677+
678+ SUB_GITLAB_DOMAINS = [r"git\.codelinaro\.org" , r"gitlab\.(?!com\b)[^/]+" ]
679+ SUB_GITLAB_ROUTE_REGEX = build_route_regex (SUB_GITLAB_DOMAINS )
680+
681+
682+ @purl_router .route (SUB_GITLAB_ROUTE_REGEX )
683+ def build_gitlab_sub_purl (url ):
684+ """
685+ Return a PackageURL object from a GitLab Sub domains commit URL
686+ For example:
687+ https://git.codelinaro.org/linaro/qcom/project/-/commit/a40a9732c840e5a324fba78b0ff7980b497c3831
688+ """
689+
690+ gitlab_sub_commit_pattern = (
691+ r"^https?://"
692+ r"(?P<domain>[^/]+)/"
693+ r"(?P<namespace>.+?)/(?P<name>(?!-/)[^/]+)/(?:-/)?commit/(?P<version>[0-9a-fA-F]{7,64})/?$"
694+ )
695+
696+ commit_match = re .search (gitlab_sub_commit_pattern , url )
697+ if commit_match :
698+ domain = commit_match .group ("domain" )
699+ raw_namespace = commit_match .group ("namespace" ).strip ("/" )
700+ namespace = f"{ domain } /{ raw_namespace } "
701+
702+ return PackageURL (
703+ type = "generic" ,
704+ namespace = namespace ,
705+ name = commit_match .group ("name" ),
706+ version = commit_match .group ("version" ),
707+ )
708+
709+ return None
710+
711+
712+ GITEA_DOMAINS = ["codeberg\.org" , "gitea\.com" ]
713+ GITEA_ROUTE_REGEX = build_route_regex (GITEA_DOMAINS )
714+
715+
716+ @purl_router .route (GITEA_ROUTE_REGEX )
717+ def build_gitea_purl (url ):
718+ """
719+ Return a PackageURL object from a gitea/forgejo url
720+ For example:
721+ https://codeberg.org/alpinelinux/aports/commit/a40a9732c840e5a324fba78b0ff7980b497c3831
722+ https://gitea.com/htc47/entur/commit/271b852cfb761a1fe257aa0f0a12ff38bd8bfd1c
723+ """
724+
725+ gitea_commit_pattern = (
726+ r"^https?://"
727+ r"(?P<domain>[^/]+)/"
728+ r"(?P<namespace>[^/]+)/(?P<name>[^/]+)/commit/(?P<version>[0-9a-fA-F]{7,64})/?$"
729+ )
730+
731+ commit_match = re .search (gitea_commit_pattern , url )
732+ if commit_match :
733+ domain = commit_match .group ("domain" )
734+ namespace = f"{ domain } /{ commit_match .group ('namespace' )} "
735+
736+ return PackageURL (
737+ type = "generic" ,
738+ namespace = namespace ,
739+ name = commit_match .group ("name" ),
740+ version = commit_match .group ("version" ),
741+ )
742+
743+ return None
744+
745+
746+ CGIT_DOMAINS = [r"git\.kernel\.org" , r"gitweb\.gentoo\.org" , "cgit\.git\.savannah\.gnu\.org" ]
747+ CGIT_ROUTE_REGEX = build_route_regex (CGIT_DOMAINS )
748+
749+
750+ @purl_router .route (CGIT_ROUTE_REGEX )
751+ def build_cgit_purl (url ):
752+ """
753+ Return a PackageURL object from a cgit url
754+ For example:
755+ https://git.kernel.org/pub/scm/bluetooth/bluez.git/commit/?id=74770b1fd2be612f9c2cf807db81fcdcc35e6560
756+ https://cgit.git.savannah.gnu.org/cgit/uddf.git/commit/?id=98c41e131dc952aee43d4ec392b80ca4c426be8d
757+ https://gitweb.gentoo.org/dev/darkside.git/commit/?id=8d4b0836f3b6ab7075212926d9aad0b50246d825
758+ """
759+
760+ cgit_project_pattern = (
761+ r"^https?://"
762+ r"(?P<domain>[^/]+)/"
763+ r"(?P<namespace>.+)/"
764+ r"(?P<name>[^/]+?)"
765+ r"(?:\.git)?"
766+ r"/commit/\?id="
767+ r"(?P<version>[0-9a-fA-F]{7,64})/?$"
768+ )
769+
770+ commit_match = re .search (cgit_project_pattern , url )
771+ if commit_match :
772+ domain = commit_match .group ("domain" )
773+ namespace = f"{ domain } /{ commit_match .group ('namespace' )} "
774+ return PackageURL (
775+ type = "generic" ,
776+ namespace = namespace ,
777+ name = commit_match .group ("name" ),
778+ version = commit_match .group ("version" ),
779+ qualifiers = {},
780+ subpath = "" ,
781+ )
782+
783+
784+ GITILES_DOMAINS = [
785+ r"android\.googlesource\.com" ,
786+ r"aomedia\.googlesource\.com" ,
787+ r"chromium\.googlesource\.com" ,
788+ ]
789+ GITILES_ROUTE_REGEX = build_route_regex (GITILES_DOMAINS )
790+
791+
792+ @purl_router .route (GITILES_ROUTE_REGEX )
793+ def build_gitiles_purl (url ):
794+ """
795+ Return a PackageURL object from Gitiles url
796+ For example:
797+ https://android.googlesource.com/platform/packages/apps/Settings/+/2968ccc911956fa5813a9a6a5e5c8970e383a60f
798+ https://aomedia.googlesource.com/libavifinfo/+/43716e9c34d3389b4882fbd1a81c04543ed04fe3
799+ """
800+
801+ gitiles_project_pattern = (
802+ r"^https?://"
803+ r"(?P<domain>[^/]+)/"
804+ r"(?:(?P<namespace>.+)/)?"
805+ r"(?P<name>[^/]+?)"
806+ r"/\+/"
807+ r"(?P<version>[0-9a-fA-F]{7,64})/?$"
808+ )
809+
810+ match = re .search (gitiles_project_pattern , url )
811+ if match :
812+ raw_namespace = match .group ("namespace" )
813+ domain = match .group ("domain" )
814+ namespace = f"{ domain } /{ raw_namespace } " if raw_namespace else domain
815+ return PackageURL (
816+ type = "generic" ,
817+ namespace = namespace ,
818+ name = match .group ("name" ),
819+ version = match .group ("version" ),
820+ qualifiers = {},
821+ subpath = "" ,
822+ )
823+
824+
825+ ALLURA_DOMAINS = [r"sourceforge\.net" , r"forge-allura\.apache\.org" ]
826+ ALLURA_ROUTE_REGEX = build_route_regex (ALLURA_DOMAINS , "/p/.*" )
827+
828+
829+ @purl_router .route (ALLURA_ROUTE_REGEX )
830+ def build_allura_purl (url ):
831+ """
832+ Return a PackageURL object from an Apache Allura url (e.g., SourceForge).
833+ For example:
834+ https://sourceforge.net/p/djvu/djvulibre-git/ci/e15d51510048927f172f1bf1f27ede65907d940d
835+ https://sourceforge.net/p/infrarecorder/code/ci/9361b6f267e7b1c1576c48f6dac6dec18d8a93e0/
836+ https://forge-allura.apache.org/p/allura/git/ci/674e070e5ca7db7c75cf61d8efd2a3e3e49bd946/
837+ """
838+
839+ allura_pattern = (
840+ r"^https?://"
841+ r"(?P<domain>[^/]+)"
842+ r"(?P<namespace>.+)/"
843+ r"(?P<name>[^/]+?)"
844+ r"/ci/"
845+ r"(?P<version>[0-9a-fA-F]{7,64})/?$"
846+ )
847+
848+ commit_match = re .search (allura_pattern , url )
849+ if commit_match :
850+ domain = commit_match .group ("domain" )
851+ namespace = f"{ domain } /{ commit_match .group ('namespace' )} "
852+ return PackageURL (
853+ type = "generic" ,
854+ namespace = namespace ,
855+ name = commit_match .group ("name" ),
856+ version = commit_match .group ("version" ),
857+ qualifiers = {},
858+ subpath = "" ,
859+ )
860+
861+
862+ GITWEB_DOMAINS = [r"gcc\.gnu\.org/git" , r"git\.postgresql\.org/gitweb" ]
863+ GITWEB_ROUTE_REGEX = build_route_regex (GITWEB_DOMAINS )
864+
865+
866+ @purl_router .route (GITWEB_ROUTE_REGEX )
867+ def build_gitweb_purl (url ):
868+ """
869+ Return a PackageURL object from a Gitweb url.
870+ For example:
871+ https://gcc.gnu.org/git/?p=gcc.git;a=commit;h=82cc94e5fb69d1c45a386f83798251de5bff9339
872+ https://git.postgresql.org/gitweb/?p=hamn.git;a=commit;h=a796b71a5b3fe7f751f1086a08cb114b9877dea2
873+ """
874+
875+ gitweb_pattern = (
876+ r"^https?://"
877+ r"(?P<namespace>[^?]+?)"
878+ r"/?(?=\?)"
879+ r"(?=.*[?;&]p=(?P<name>[^;&]+?)(?:\.git)?(?:[;&]|$))"
880+ r"(?=.*[?;&]h=(?P<version>[0-9a-fA-F]{7,64}))"
881+ )
882+
883+ commit_match = re .search (gitweb_pattern , url )
884+ if commit_match :
885+ namespace = commit_match .group ("namespace" )
886+ name = commit_match .group ("name" )
887+ return PackageURL (
888+ type = "generic" ,
889+ namespace = namespace ,
890+ name = name ,
891+ version = commit_match .group ("version" ),
892+ qualifiers = {},
893+ subpath = "" ,
894+ )
895+
896+
670897@purl_router .route ("https?://gitlab\\ .com/(?!.*/archive/).*" )
671898def build_gitlab_purl (url ):
672899 """
0 commit comments