github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/javascript/parse_package_lock.go (about) 1 package javascript 2 3 import ( 4 "context" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "strings" 10 11 "github.com/anchore/syft/internal/log" 12 "github.com/anchore/syft/internal/unknown" 13 "github.com/anchore/syft/syft/artifact" 14 "github.com/anchore/syft/syft/file" 15 "github.com/anchore/syft/syft/pkg" 16 "github.com/anchore/syft/syft/pkg/cataloger/generic" 17 "github.com/anchore/syft/syft/pkg/cataloger/internal/dependency" 18 ) 19 20 // packageLock represents a JavaScript package.lock json file 21 type packageLock struct { 22 Requires bool `json:"requires"` 23 LockfileVersion int `json:"lockfileVersion"` 24 Dependencies map[string]lockDependency 25 Packages map[string]lockPackage 26 } 27 28 // lockDependency represents a single package dependency listed in the package.lock json file 29 type lockDependency struct { 30 Version string `json:"version"` 31 Resolved string `json:"resolved"` 32 Integrity string `json:"integrity"` 33 Dev bool `json:"dev"` 34 } 35 36 type lockPackage struct { 37 Name string `json:"name"` // only present in the root package entry (named "") 38 Version string `json:"version"` 39 Resolved string `json:"resolved"` 40 Integrity string `json:"integrity"` 41 License packageLockLicense `json:"license"` 42 Dev bool `json:"dev"` 43 Dependencies map[string]string `json:"dependencies"` 44 } 45 46 // packageLockLicense 47 type packageLockLicense []string 48 49 type genericPackageLockAdapter struct { 50 cfg CatalogerConfig 51 } 52 53 func newGenericPackageLockAdapter(cfg CatalogerConfig) genericPackageLockAdapter { 54 return genericPackageLockAdapter{ 55 cfg: cfg, 56 } 57 } 58 59 // parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages. 60 func (a genericPackageLockAdapter) parsePackageLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 61 // in the case we find package-lock.json files in the node_modules directories, skip those 62 // as the whole purpose of the lock file is for the specific dependencies of the root project 63 if pathContainsNodeModulesDirectory(reader.Path()) { 64 return nil, nil, nil 65 } 66 67 var pkgs []pkg.Package 68 dec := json.NewDecoder(reader) 69 70 var lock packageLock 71 for { 72 if err := dec.Decode(&lock); errors.Is(err, io.EOF) { 73 break 74 } else if err != nil { 75 return nil, nil, fmt.Errorf("failed to parse package-lock.json file: %w", err) 76 } 77 } 78 79 if lock.LockfileVersion == 1 { 80 for name, pkgMeta := range lock.Dependencies { 81 // skip packages that are only present as a dev dependency 82 if !a.cfg.IncludeDevDependencies && pkgMeta.Dev { 83 continue 84 } 85 86 pkgs = append(pkgs, newPackageLockV1Package(ctx, a.cfg, resolver, reader.Location, name, pkgMeta)) 87 } 88 } 89 90 if lock.LockfileVersion == 2 || lock.LockfileVersion == 3 { 91 for name, pkgMeta := range lock.Packages { 92 if name == "" { 93 if pkgMeta.Name == "" { 94 continue 95 } 96 name = pkgMeta.Name 97 } 98 99 // skip packages that are only present as a dev dependency 100 if !a.cfg.IncludeDevDependencies && pkgMeta.Dev { 101 continue 102 } 103 104 // handles alias names 105 if pkgMeta.Name != "" { 106 name = pkgMeta.Name 107 } 108 109 newPkg := newPackageLockV2Package(ctx, a.cfg, resolver, reader.Location, getNameFromPath(name), pkgMeta) 110 pkgs = append(pkgs, newPkg) 111 } 112 } 113 114 pkg.Sort(pkgs) 115 116 return pkgs, dependency.Resolve(packageLockDependencySpecifier, pkgs), unknown.IfEmptyf(pkgs, "unable to determine packages") 117 } 118 119 func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) { 120 // The license field could be either a string or an array. 121 122 // 1. An array 123 var arr []string 124 if err := json.Unmarshal(data, &arr); err == nil { 125 *licenses = arr 126 return nil 127 } 128 129 // 2. A string 130 var str string 131 if err = json.Unmarshal(data, &str); err == nil { 132 *licenses = make([]string, 1) 133 (*licenses)[0] = str 134 return nil 135 } 136 137 // debug the content we did not expect 138 if len(data) > 0 { 139 log.WithFields("license", string(data)).Debug("Unable to parse the following `license` value in package-lock.json") 140 } 141 142 // 3. Unexpected 143 // In case we are unable to parse the license field, 144 // i.e if we have not covered the full specification, 145 // we do not want to throw an error, instead assign nil. 146 return nil 147 } 148 149 func getNameFromPath(path string) string { 150 parts := strings.Split(path, "node_modules/") 151 return parts[len(parts)-1] 152 }