github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/javascript/parse_package_lock.go (about) 1 package javascript 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "io" 8 "strings" 9 10 "github.com/anchore/syft/internal/log" 11 "github.com/anchore/syft/syft/artifact" 12 "github.com/anchore/syft/syft/file" 13 "github.com/anchore/syft/syft/pkg" 14 "github.com/anchore/syft/syft/pkg/cataloger/generic" 15 ) 16 17 // integrity check 18 var _ generic.Parser = parsePackageLock 19 20 // packageLock represents a JavaScript package.lock json file 21 type packageLock struct { 22 Name string `json:"name"` 23 Version string `json:"version"` 24 LockfileVersion int `json:"lockfileVersion"` 25 Dependencies map[string]*packageLockDependency `json:"dependencies"` 26 Packages map[string]*packageLockPackage `json:"packages"` 27 Requires bool `json:"requires"` 28 } 29 30 type packageLockPackage struct { 31 Name string `json:"name"` 32 Version string `json:"version"` 33 Integrity string `json:"integrity"` 34 Resolved string `json:"resolved"` 35 Dependencies map[string]string `json:"dependencies"` 36 DevDependencies map[string]string `json:"devDependencies"` 37 License packageLockLicense `json:"license"` 38 Dev bool `json:"dev"` 39 Requires map[string]string `json:"requires"` 40 } 41 42 type packageLockDependency struct { 43 name string 44 Version string `json:"version"` 45 Requires map[string]string `json:"requires"` 46 Integrity string `json:"integrity"` 47 Resolved string `json:"resolved"` 48 Dev bool `json:"dev"` 49 Dependencies map[string]*packageLockDependency `json:"dependencies"` 50 } 51 52 // packageLockLicense 53 type packageLockLicense []string 54 55 func parsePackageLockFile(reader file.LocationReadCloser) (packageLock, error) { 56 // in the case we find package-lock.json files in the node_modules directories, skip those 57 // as the whole purpose of the lock file is for the specific dependencies of the root project 58 if pathContainsNodeModulesDirectory(reader.AccessPath()) { 59 return packageLock{}, nil 60 } 61 dec := json.NewDecoder(reader) 62 63 var lock packageLock 64 for { 65 if err := dec.Decode(&lock); errors.Is(err, io.EOF) { 66 break 67 } else if err != nil { 68 return packageLock{}, fmt.Errorf("failed to parse package-lock.json file: %w", err) 69 } 70 } 71 return lock, nil 72 } 73 74 // parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages. 75 func parsePackageLock(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 76 lock, err := parsePackageLockFile(reader) 77 if err != nil { 78 return nil, nil, err 79 } 80 pkgs, rels := finalizePackageLockWithoutPackageJSON(resolver, &lock, reader.Location) 81 return pkgs, rels, nil 82 } 83 84 func finalizePackageLockWithoutPackageJSON(resolver file.Resolver, pkglock *packageLock, indexLocation file.Location) ([]pkg.Package, []artifact.Relationship) { 85 var root pkg.Package 86 if pkglock.Name == "" { 87 name := rootNameFromPath(indexLocation) 88 p := packageLockPackage{Name: name, Version: "0.0.0"} 89 root = newPackageLockV2Package(resolver, indexLocation, name, p) 90 } else { 91 p := packageLockPackage{Name: pkglock.Name, Version: pkglock.Version} 92 root = newPackageLockV2Package(resolver, indexLocation, pkglock.Name, p) 93 } 94 95 if pkglock.LockfileVersion == 1 { 96 return finalizePackageLockWithoutPackageJSONV1(resolver, pkglock, indexLocation, root) 97 } 98 99 if pkglock.LockfileVersion == 3 || pkglock.LockfileVersion == 2 { 100 return finalizePackageLockV2(resolver, pkglock, indexLocation, root) 101 } 102 103 return nil, nil 104 } 105 106 func finalizePackageLockWithPackageJSON(resolver file.Resolver, pkgjson *packageJSON, pkglock *packageLock, indexLocation file.Location) ([]pkg.Package, []artifact.Relationship) { 107 if pkgjson == nil { 108 return finalizePackageLockWithoutPackageJSON(resolver, pkglock, indexLocation) 109 } 110 111 if !pkgjson.hasNameAndVersionValues() { 112 log.Debugf("encountered package.json file without a name and/or version field, ignoring (path=%q)", indexLocation.AccessPath()) 113 return nil, nil 114 } 115 116 p := packageLockPackage{Name: pkgjson.Name, Version: pkgjson.Version} 117 root := newPackageLockV2Package(resolver, indexLocation, pkglock.Name, p) 118 119 if pkglock.LockfileVersion == 1 { 120 return finalizePackageLockWithPackageJSONV1(resolver, pkgjson, pkglock, indexLocation, root) 121 } 122 123 if pkglock.LockfileVersion == 3 || pkglock.LockfileVersion == 2 { 124 return finalizePackageLockV2(resolver, pkglock, indexLocation, root) 125 } 126 127 return nil, nil 128 } 129 130 func finalizePackageLockWithoutPackageJSONV1(resolver file.Resolver, pkglock *packageLock, indexLocation file.Location, root pkg.Package) ([]pkg.Package, []artifact.Relationship) { 131 if pkglock.LockfileVersion != 1 { 132 return nil, nil 133 } 134 pkgs := []pkg.Package{} 135 136 pkgs = append(pkgs, root) 137 depnameMap := map[string]pkg.Package{} 138 139 // create packages 140 for name, lockDep := range pkglock.Dependencies { 141 lockDep.name = name 142 pkg := newPackageLockV1Package(resolver, indexLocation, name, *lockDep) 143 pkgs = append(pkgs, pkg) 144 depnameMap[name] = pkg 145 } 146 pkg.Sort(pkgs) 147 return pkgs, nil 148 } 149 150 func finalizePackageLockWithPackageJSONV1(resolver file.Resolver, pkgjson *packageJSON, pkglock *packageLock, indexLocation file.Location, root pkg.Package) ([]pkg.Package, []artifact.Relationship) { 151 if pkglock.LockfileVersion != 1 { 152 return nil, nil 153 } 154 pkgs := []pkg.Package{} 155 relationships := []artifact.Relationship{} 156 157 pkgs = append(pkgs, root) 158 depnameMap := map[string]pkg.Package{} 159 160 // create packages 161 for name, lockDep := range pkglock.Dependencies { 162 lockDep.name = name 163 pkg := newPackageLockV1Package(resolver, indexLocation, name, *lockDep) 164 pkgs = append(pkgs, pkg) 165 depnameMap[name] = pkg 166 } 167 168 // create relationships 169 for name, lockDep := range pkglock.Dependencies { 170 lockDep.name = name 171 for name, sub := range lockDep.Dependencies { 172 sub.name = name 173 if subPkg, ok := depnameMap[name]; ok { 174 rel := artifact.Relationship{ 175 From: subPkg, 176 To: depnameMap[name], 177 Type: artifact.DependencyOfRelationship, 178 } 179 relationships = append(relationships, rel) 180 } 181 } 182 for name := range lockDep.Requires { 183 if subPkg, ok := depnameMap[name]; ok { 184 rel := artifact.Relationship{ 185 From: subPkg, 186 To: depnameMap[name], 187 Type: artifact.DependencyOfRelationship, 188 } 189 relationships = append(relationships, rel) 190 } 191 } 192 } 193 194 for name := range pkgjson.Dependencies { 195 rel := artifact.Relationship{ 196 From: depnameMap[name], 197 To: root, 198 Type: artifact.DependencyOfRelationship, 199 } 200 relationships = append(relationships, rel) 201 } 202 203 for name := range pkgjson.DevDependencies { 204 rel := artifact.Relationship{ 205 From: depnameMap[name], 206 To: root, 207 Type: artifact.DependencyOfRelationship, 208 } 209 relationships = append(relationships, rel) 210 } 211 212 pkg.Sort(pkgs) 213 pkg.SortRelationships(relationships) 214 return pkgs, relationships 215 } 216 217 func finalizePackageLockV2(resolver file.Resolver, pkglock *packageLock, indexLocation file.Location, root pkg.Package) ([]pkg.Package, []artifact.Relationship) { 218 if pkglock.LockfileVersion != 3 && pkglock.LockfileVersion != 2 { 219 return nil, nil 220 } 221 222 pkgs := []pkg.Package{} 223 relationships := []artifact.Relationship{} 224 depnameMap := map[string]pkg.Package{} 225 root.Licenses = pkg.NewLicenseSet(pkg.NewLicensesFromLocation(indexLocation, pkglock.Packages[""].License...)...) 226 227 // create packages 228 for name, lockDep := range pkglock.Packages { 229 // root pkg always equals "" in lock v2/v3 230 if name == "" { 231 continue 232 } 233 234 n := getNameFromPath(name) 235 pkg := newPackageLockV2Package(resolver, indexLocation, n, *lockDep) 236 237 pkgs = append(pkgs, pkg) 238 // need to store both names 239 depnameMap[name] = pkg 240 depnameMap[n] = pkg 241 } 242 243 // create relationships 244 for name, lockDep := range pkglock.Packages { 245 // root pkg always equals "" in lock v2/v3 246 if name == "" { 247 continue 248 } 249 250 if dep, ok := depnameMap[name]; ok { 251 for childName := range lockDep.Dependencies { 252 if childDep, ok := depnameMap[childName]; ok { 253 rel := artifact.Relationship{ 254 From: childDep, 255 To: dep, 256 Type: artifact.DependencyOfRelationship, 257 } 258 relationships = append(relationships, rel) 259 } 260 } 261 rootRel := artifact.Relationship{ 262 From: dep, 263 To: root, 264 Type: artifact.DependencyOfRelationship, 265 } 266 relationships = append(relationships, rootRel) 267 } 268 } 269 270 pkgs = append(pkgs, root) 271 pkg.Sort(pkgs) 272 pkg.SortRelationships(relationships) 273 return pkgs, relationships 274 } 275 276 func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) { 277 // The license field could be either a string or an array. 278 279 // 1. An array 280 var arr []string 281 if err := json.Unmarshal(data, &arr); err == nil { 282 *licenses = arr 283 return nil 284 } 285 286 // 2. A string 287 var str string 288 if err = json.Unmarshal(data, &str); err == nil { 289 *licenses = make([]string, 1) 290 (*licenses)[0] = str 291 return nil 292 } 293 294 // debug the content we did not expect 295 if len(data) > 0 { 296 log.WithFields("license", string(data)).Debug("Unable to parse the following `license` value in package-lock.json") 297 } 298 299 // 3. Unexpected 300 // In case we are unable to parse the license field, 301 // i.e if we have not covered the full specification, 302 // we do not want to throw an error, instead assign nil. 303 return nil 304 } 305 306 func getNameFromPath(path string) string { 307 parts := strings.Split(path, "node_modules/") 308 return parts[len(parts)-1] 309 }