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