github.com/google/osv-scalibr@v0.4.1/purl/purl.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package purl provides functions to code and decode package url according to the spec: https://github.com/package-url/purl-spec 16 // This package is a convenience wrapper and abstraction layer around an existing open source implementation. 17 package purl 18 19 import ( 20 "fmt" 21 "strings" 22 23 "github.com/package-url/packageurl-go" 24 ) 25 26 // These are the known purl types as defined in the spec. Some of these require 27 // special treatment during parsing. 28 // https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst 29 const ( 30 // TypeAlpm is a pkg:alpm purl. 31 TypeAlpm = "alpm" 32 // TypeApk is a pkg:apk purl. 33 TypeApk = "apk" 34 // TypeBitbucket is a pkg:bitbucket purl. 35 TypeBitbucket = "bitbucket" 36 // TypeBrew is a pkg:brew purl. 37 TypeBrew = "brew" 38 // TypeCocoapods is a pkg:cocoapods purl. 39 TypeCocoapods = "cocoapods" 40 // TypeCargo is a pkg:cargo purl. 41 TypeCargo = "cargo" 42 // TypeComposer is a pkg:composer purl. 43 TypeComposer = "composer" 44 // TypeConan is a pkg:conan purl. 45 TypeConan = "conan" 46 // TypeConda is a pkg:conda purl. 47 TypeConda = "conda" 48 // TypeCOS is the pkg:cos purl 49 TypeCOS = "cos" 50 // TypeCran is a pkg:cran purl. 51 TypeCran = "cran" 52 // TypeDebian is a pkg:deb purl. 53 TypeDebian = "deb" 54 // TypeDocker is a pkg:docker purl. 55 TypeDocker = "docker" 56 // TypeK8s is a pkg:k8s purl. 57 TypeK8s = "k8s" 58 // TypeFlatpak is a pkg:flatpak purl. 59 TypeFlatpak = "flatpak" 60 // TypeGem is a pkg:gem purl. 61 TypeGem = "gem" 62 // TypeGeneric is a pkg:generic purl. 63 TypeGeneric = "generic" 64 // TypeGithub is a pkg:github purl. 65 TypeGithub = "github" 66 // TypeGolang is a pkg:golang purl. 67 TypeGolang = "golang" 68 // TypeHackage is a pkg:hackage purl. 69 TypeHackage = "hackage" 70 // TypeHaskell is a pkg:haskell purl. 71 TypeHaskell = "haskell" 72 // TypeMacApps is a pkg:macapps purl. 73 TypeMacApps = "macapps" 74 // TypeHex is a pkg:hex purl. 75 TypeHex = "hex" 76 // TypeMaven is a pkg:maven purl. 77 TypeMaven = "maven" 78 // TypeNix is a pkg:nix purl. 79 TypeNix = "nix" 80 // TypeNPM is a pkg:npm purl. 81 TypeNPM = "npm" 82 // TypePacman is a pkg:pacman purl. 83 TypePacman = "pacman" 84 // TypeNuget is a pkg:nuget purl. 85 TypeNuget = "nuget" 86 // TypeOCI is a pkg:oci purl 87 TypeOCI = "oci" 88 // TypeOpkg is a pkg:opkg purl. 89 TypeOpkg = "opkg" 90 // TypePub is a pkg:pub purl. 91 TypePub = "pub" 92 // TypePortage is a pkg:portage purl. 93 TypePortage = "portage" 94 // TypePyPi is a pkg:pypi purl. 95 TypePyPi = "pypi" 96 // TypeRPM is a pkg:rpm purl. 97 TypeRPM = "rpm" 98 // TypeSnap is a pkg:snap purl. 99 TypeSnap = "snap" 100 // TypeSwift is pkg:swift purl 101 TypeSwift = "swift" 102 // TypeGooget is pkg:googet purl 103 TypeGooget = "googet" 104 // TypeWordpress is pkg:wordpress purl 105 TypeWordpress = "wordpress" 106 // TypeAsdf is pkg:asdf purl 107 TypeAsdf = "asdf" 108 // TypeMacports is pkg:macports purl 109 TypeMacports = "macports" 110 // TypeWinget is pkg:winget purl 111 TypeWinget = "winget" 112 // TypeNim is pkg:nim purl 113 TypeNim = "nim" 114 // TypeLua is pkg:lua purl 115 TypeLua = "lua" 116 ) 117 118 // PackageURL is the struct representation of the parts that make a package url. 119 type PackageURL struct { 120 Type string 121 Namespace string 122 Name string 123 Version string 124 Qualifiers Qualifiers 125 Subpath string 126 } 127 128 // Qualifier represents a single key=value qualifier in the package url. 129 type Qualifier packageurl.Qualifier 130 131 // Qualifiers is a slice of key=value pairs, with order preserved as it appears 132 // in the package URL. 133 type Qualifiers packageurl.Qualifiers 134 135 // QualifiersFromMap constructs a Qualifiers slice from a string map. To get a 136 // deterministic qualifier order (despite maps not providing any iteration order 137 // guarantees) the returned Qualifiers are sorted in increasing order of key. 138 func QualifiersFromMap(mm map[string]string) Qualifiers { 139 for key, value := range mm { 140 // Empty value strings are invalid qualifiers according to the purl spec 141 // so we filter them out. 142 if value == "" { 143 delete(mm, key) 144 } 145 } 146 return Qualifiers(packageurl.QualifiersFromMap(mm)) 147 } 148 149 func (p PackageURL) String() string { 150 purl := packageurl.PackageURL{ 151 Type: p.Type, 152 Namespace: p.Namespace, 153 Name: p.Name, 154 Version: p.Version, 155 Qualifiers: packageurl.Qualifiers(p.Qualifiers), 156 Subpath: p.Subpath, 157 } 158 return (&purl).String() 159 } 160 161 // FromString parses a valid package url string into a PackageURL structure. 162 func FromString(purl string) (PackageURL, error) { 163 p, err := packageurl.FromString(purl) 164 if err != nil { 165 return PackageURL{}, fmt.Errorf("failed to decode PURL string %q: %w", purl, err) 166 } 167 if !validType(p.Type) { 168 return PackageURL{}, fmt.Errorf("invalid PURL type %q", p.Type) 169 } 170 return PackageURL{ 171 Type: p.Type, 172 Namespace: p.Namespace, 173 Name: p.Name, 174 Version: p.Version, 175 Qualifiers: Qualifiers(p.Qualifiers), 176 Subpath: p.Subpath, 177 }, nil 178 } 179 180 func validType(t string) bool { 181 types := map[string]bool{ 182 TypeAlpm: true, 183 TypeApk: true, 184 TypeBitbucket: true, 185 TypeBrew: true, 186 TypeCargo: true, 187 TypeCocoapods: true, 188 TypeComposer: true, 189 TypeConan: true, 190 TypeConda: true, 191 TypeCOS: true, 192 TypeCran: true, 193 TypeDebian: true, 194 TypePacman: true, 195 TypeDocker: true, 196 TypeFlatpak: true, 197 TypeGem: true, 198 TypeGeneric: true, 199 TypeGithub: true, 200 TypeGolang: true, 201 TypeHackage: true, 202 TypeHaskell: true, 203 TypeNim: true, 204 TypeLua: true, 205 TypeHex: true, 206 TypeMacApps: true, 207 TypeMaven: true, 208 TypeNix: true, 209 TypeNPM: true, 210 TypeNuget: true, 211 TypeOCI: true, 212 TypeOpkg: true, 213 TypePub: true, 214 TypePortage: true, 215 TypePyPi: true, 216 TypeRPM: true, 217 TypeSwift: true, 218 TypeGooget: true, 219 TypeWordpress: true, 220 TypeAsdf: true, 221 TypeMacports: true, 222 TypeWinget: true, 223 } 224 225 // purl type is case-insensitive, canonical form is lower-case 226 t = strings.ToLower(t) 227 _, ok := types[t] 228 return ok 229 } 230 231 // Qualifier names. 232 const ( 233 Distro = "distro" 234 Epoch = "epoch" 235 Arch = "arch" 236 Origin = "origin" 237 Source = "source" 238 SourceVersion = "sourceversion" 239 SourceRPM = "sourcerpm" 240 BuildNumber = "buildnumber" 241 PackageDependencies = "packagedependencies" 242 Classifier = "classifier" // Maven specific qualifier 243 Type = "type" // Maven specific qualifier 244 )