github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/pkg/cataloger/javascript/package.go (about) 1 package javascript 2 3 import ( 4 "encoding/json" 5 "io" 6 "path" 7 "strings" 8 9 "github.com/anchore/packageurl-go" 10 "github.com/anchore/syft/syft/file" 11 "github.com/anchore/syft/syft/pkg" 12 "github.com/lineaje-labs/syft/internal/log" 13 ) 14 15 func newPackageJSONPackage(u packageJSON, indexLocation file.Location) pkg.Package { 16 licenseCandidates, err := u.licensesFromJSON() 17 if err != nil { 18 log.Warnf("unable to extract licenses from javascript package.json: %+v", err) 19 } 20 21 license := pkg.NewLicensesFromLocation(indexLocation, licenseCandidates...) 22 p := pkg.Package{ 23 Name: u.Name, 24 Version: u.Version, 25 PURL: packageURL(u.Name, u.Version), 26 Locations: file.NewLocationSet(indexLocation), 27 Language: pkg.JavaScript, 28 Licenses: pkg.NewLicenseSet(license...), 29 Type: pkg.NpmPkg, 30 Metadata: pkg.NpmPackage{ 31 Name: u.Name, 32 Version: u.Version, 33 Description: u.Description, 34 Author: u.Author.AuthorString(), 35 Homepage: u.Homepage, 36 URL: u.Repository.URL, 37 Private: u.Private, 38 }, 39 } 40 41 p.SetID() 42 43 return p 44 } 45 46 func newPackageLockV1Package( 47 resolver file.Resolver, location file.Location, name string, u lockDependency, 48 ) pkg.Package { 49 version := u.Version 50 51 const aliasPrefixPackageLockV1 = "npm:" 52 53 // Handles type aliases https://github.com/npm/rfcs/blob/main/implemented/0001-package-aliases.md 54 if strings.HasPrefix(version, aliasPrefixPackageLockV1) { 55 // this is an alias. 56 // `"version": "npm:canonical-name@X.Y.Z"` 57 canonicalPackageAndVersion := version[len(aliasPrefixPackageLockV1):] 58 versionSeparator := strings.LastIndex(canonicalPackageAndVersion, "@") 59 60 name = canonicalPackageAndVersion[:versionSeparator] 61 version = canonicalPackageAndVersion[versionSeparator+1:] 62 } 63 64 return finalizeLockPkg( 65 resolver, 66 location, 67 pkg.Package{ 68 Name: name, 69 Version: version, 70 Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 71 PURL: packageURL(name, version), 72 Language: pkg.JavaScript, 73 Type: pkg.NpmPkg, 74 Metadata: pkg.NpmPackageLockEntry{Resolved: u.Resolved, Integrity: u.Integrity}, 75 }, 76 ) 77 } 78 79 func newPackageLockV2Package(resolver file.Resolver, location file.Location, name string, u lockPackage) pkg.Package { 80 return finalizeLockPkg( 81 resolver, 82 location, 83 pkg.Package{ 84 Name: name, 85 Version: u.Version, 86 Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 87 Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(location, u.License...)...), 88 PURL: packageURL(name, u.Version), 89 Language: pkg.JavaScript, 90 Type: pkg.NpmPkg, 91 Metadata: pkg.NpmPackageLockEntry{Resolved: u.Resolved, Integrity: u.Integrity}, 92 }, 93 ) 94 } 95 96 func newPnpmPackage(resolver file.Resolver, location file.Location, name, version string) pkg.Package { 97 return finalizeLockPkg( 98 resolver, 99 location, 100 pkg.Package{ 101 Name: name, 102 Version: version, 103 Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 104 PURL: packageURL(name, version), 105 Language: pkg.JavaScript, 106 Type: pkg.NpmPkg, 107 }, 108 ) 109 } 110 111 func newYarnLockPackage(resolver file.Resolver, location file.Location, name, version string) pkg.Package { 112 return finalizeLockPkg( 113 resolver, 114 location, 115 pkg.Package{ 116 Name: name, 117 Version: version, 118 Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 119 PURL: packageURL(name, version), 120 Language: pkg.JavaScript, 121 Type: pkg.NpmPkg, 122 }, 123 ) 124 } 125 126 func finalizeLockPkg(resolver file.Resolver, location file.Location, p pkg.Package) pkg.Package { 127 licenseCandidate := addLicenses(p.Name, resolver, location) 128 p.Licenses.Add(pkg.NewLicensesFromLocation(location, licenseCandidate...)...) 129 p.SetID() 130 return p 131 } 132 133 func addLicenses(name string, resolver file.Resolver, location file.Location) (allLicenses []string) { 134 if resolver == nil { 135 return allLicenses 136 } 137 138 dir := path.Dir(location.RealPath) 139 pkgPath := []string{dir, "node_modules"} 140 pkgPath = append(pkgPath, strings.Split(name, "/")...) 141 pkgPath = append(pkgPath, "package.json") 142 pkgFile := path.Join(pkgPath...) 143 locations, err := resolver.FilesByPath(pkgFile) 144 if err != nil { 145 log.Debugf("an error occurred attempting to read: %s - %+v", pkgFile, err) 146 return allLicenses 147 } 148 149 if len(locations) == 0 { 150 return allLicenses 151 } 152 153 for _, l := range locations { 154 contentReader, err := resolver.FileContentsByLocation(l) 155 if err != nil { 156 log.Debugf("error getting file content reader for %s: %v", pkgFile, err) 157 return allLicenses 158 } 159 160 contents, err := io.ReadAll(contentReader) 161 if err != nil { 162 log.Debugf("error reading file contents for %s: %v", pkgFile, err) 163 return allLicenses 164 } 165 166 var pkgJSON packageJSON 167 err = json.Unmarshal(contents, &pkgJSON) 168 if err != nil { 169 log.Debugf("error parsing %s: %v", pkgFile, err) 170 return allLicenses 171 } 172 173 licenses, err := pkgJSON.licensesFromJSON() 174 if err != nil { 175 log.Debugf("error getting licenses from %s: %v", pkgFile, err) 176 return allLicenses 177 } 178 179 allLicenses = append(allLicenses, licenses...) 180 } 181 182 return allLicenses 183 } 184 185 // packageURL returns the PURL for the specific NPM package (see https://github.com/package-url/purl-spec) 186 func packageURL(name, version string) string { 187 var namespace string 188 189 fields := strings.SplitN(name, "/", 2) 190 if len(fields) > 1 { 191 namespace = fields[0] 192 name = fields[1] 193 } 194 195 return packageurl.NewPackageURL( 196 packageurl.TypeNPM, 197 namespace, 198 name, 199 version, 200 nil, 201 "", 202 ).ToString() 203 }