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