github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/pkg/cataloger/r/package.go (about) 1 package r 2 3 import ( 4 "strings" 5 6 "github.com/anchore/packageurl-go" 7 "github.com/anchore/syft/syft/file" 8 "github.com/anchore/syft/syft/pkg" 9 ) 10 11 func newPackage(pd parseData, locations ...file.Location) pkg.Package { 12 locationSet := file.NewLocationSet() 13 for _, loc := range locations { 14 locationSet.Add(loc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) 15 } 16 17 licenses := parseLicenseData(pd.License) 18 19 result := pkg.Package{ 20 Name: pd.Package, 21 Version: pd.Version, 22 Locations: locationSet, 23 Licenses: pkg.NewLicenseSet(licenses...), 24 Language: pkg.R, 25 Type: pkg.Rpkg, 26 PURL: packageURL(pd), 27 Metadata: pd.RDescription, 28 } 29 30 result.SetID() 31 return result 32 } 33 34 func packageURL(m parseData) string { 35 return packageurl.NewPackageURL("cran", "", m.Package, m.Version, nil, "").ToString() 36 } 37 38 // https://r-pkgs.org/description.html#the-license-field 39 // four forms: 40 // 1. "GPL (>= 2)" 41 // 2. "GPL-2" 42 // 3. "MIT + file LICENSE" 43 // 4. "pointer to the full text of the license; file LICENSE" 44 // Multiple licences can be specified separated by ‘|’ 45 // (surrounded by spaces) in which case the user can choose any of the above cases. 46 // https://cran.rstudio.com/doc/manuals/r-devel/R-exts.html#Licensing 47 func parseLicenseData(license string, locations ...file.Location) []pkg.License { 48 licenses := make([]pkg.License, 0) 49 50 // check if multiple licenses are separated by | 51 splitField := strings.Split(license, "|") 52 for _, l := range splitField { 53 // check case 1 for surrounding parens 54 l = strings.TrimSpace(l) 55 if strings.Contains(l, "(") && strings.Contains(l, ")") { 56 licenseVersion := strings.SplitN(l, " ", 2) 57 if len(licenseVersion) == 2 { 58 l = strings.Join([]string{licenseVersion[0], parseVersion(licenseVersion[1])}, "") 59 licenses = append(licenses, pkg.NewLicenseFromLocations(l, locations...)) 60 continue 61 } 62 } 63 64 // case 3 65 if strings.Contains(l, "+") && strings.Contains(l, "LICENSE") { 66 splitField := strings.Split(l, " ") 67 if len(splitField) > 0 { 68 licenses = append(licenses, pkg.NewLicenseFromLocations(splitField[0], locations...)) 69 continue 70 } 71 } 72 73 // TODO: case 4 if we are able to read the location data and find the adjacent file? 74 if l == "file LICENSE" { 75 continue 76 } 77 78 // no specific case found for the above so assume case 2 79 // check if the common name in case 2 is valid SDPX otherwise value will be populated 80 licenses = append(licenses, pkg.NewLicenseFromLocations(l, locations...)) 81 continue 82 } 83 return licenses 84 } 85 86 // attempt to make best guess at SPDX license ID from version operator in case 2 87 /* 88 ‘<’, ‘<=’, ‘>’, ‘>=’, ‘==’, or ‘!=’ 89 cant be (>= 2.0) OR (>= 2.0, < 3) 90 since there is no way in SPDX licenses to express < some other version 91 we attempt to check the constraint to see if this should be + or not 92 */ 93 func parseVersion(version string) string { 94 version = strings.ReplaceAll(version, "(", "") 95 version = strings.ReplaceAll(version, ")", "") 96 97 // multiple constraints 98 if strings.Contains(version, ",") { 99 multipleConstraints := strings.Split(version, ",") 100 // SPDX does not support considering multiple constraints 101 // so we will just take the first one and attempt to form the best SPDX ID we can 102 for _, v := range multipleConstraints { 103 constraintVersion := strings.SplitN(v, " ", 2) 104 if len(constraintVersion) == 2 { 105 // switch on the operator and return the version with + or without 106 switch constraintVersion[0] { 107 case ">", ">=": 108 return constraintVersion[1] + "+" 109 default: 110 return constraintVersion[1] 111 } 112 } 113 } 114 } 115 // single constraint 116 singleContraint := strings.Split(version, " ") 117 if len(singleContraint) == 2 { 118 switch singleContraint[0] { 119 case ">", ">=": 120 return singleContraint[1] + "+" 121 default: 122 return singleContraint[1] 123 } 124 } 125 126 // could not parse version constraint so return "" 127 return "" 128 }