github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/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/syft/artifact" 11 "github.com/anchore/syft/syft/file" 12 "github.com/anchore/syft/syft/pkg" 13 "github.com/anchore/syft/syft/pkg/cataloger/generic" 14 "github.com/lineaje-labs/syft/internal/log" 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 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 } 34 35 type lockPackage struct { 36 Name string `json:"name"` // only present in the root package entry (named "") 37 Version string `json:"version"` 38 Resolved string `json:"resolved"` 39 Integrity string `json:"integrity"` 40 License packageLockLicense `json:"license"` 41 } 42 43 // packageLockLicense 44 type packageLockLicense []string 45 46 // parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages. 47 func parsePackageLock( 48 resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser, 49 ) ([]pkg.Package, []artifact.Relationship, error) { 50 // in the case we find package-lock.json files in the node_modules directories, skip those 51 // as the whole purpose of the lock file is for the specific dependencies of the root project 52 if pathContainsNodeModulesDirectory(reader.Path()) { 53 return nil, nil, nil 54 } 55 56 var pkgs []pkg.Package 57 dec := json.NewDecoder(reader) 58 59 var lock packageLock 60 for { 61 if err := dec.Decode(&lock); errors.Is(err, io.EOF) { 62 break 63 } else if err != nil { 64 return nil, nil, fmt.Errorf("failed to parse package-lock.json file: %w", err) 65 } 66 } 67 68 if lock.LockfileVersion == 1 { 69 for name, pkgMeta := range lock.Dependencies { 70 pkgs = append(pkgs, newPackageLockV1Package(resolver, reader.Location, name, pkgMeta)) 71 } 72 } 73 74 if lock.LockfileVersion == 2 || lock.LockfileVersion == 3 { 75 for name, pkgMeta := range lock.Packages { 76 if name == "" { 77 if pkgMeta.Name == "" { 78 continue 79 } 80 name = pkgMeta.Name 81 } 82 83 // handles alias names 84 if pkgMeta.Name != "" { 85 name = pkgMeta.Name 86 } 87 88 pkgs = append( 89 pkgs, 90 newPackageLockV2Package(resolver, reader.Location, getNameFromPath(name), pkgMeta), 91 ) 92 } 93 } 94 95 pkg.Sort(pkgs) 96 97 return pkgs, nil, nil 98 } 99 100 func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) { 101 // The license field could be either a string or an array. 102 103 // 1. An array 104 var arr []string 105 if err := json.Unmarshal(data, &arr); err == nil { 106 *licenses = arr 107 return nil 108 } 109 110 // 2. A string 111 var str string 112 if err = json.Unmarshal(data, &str); err == nil { 113 *licenses = make([]string, 1) 114 (*licenses)[0] = str 115 return nil 116 } 117 118 // debug the content we did not expect 119 if len(data) > 0 { 120 log.WithFields("license", string(data)).Debug("Unable to parse the following `license` value in package-lock.json") 121 } 122 123 // 3. Unexpected 124 // In case we are unable to parse the license field, 125 // i.e if we have not covered the full specification, 126 // we do not want to throw an error, instead assign nil. 127 return nil 128 } 129 130 func getNameFromPath(path string) string { 131 parts := strings.Split(path, "node_modules/") 132 return parts[len(parts)-1] 133 }