From 7730d14b54d5795dd4aede9d1c558a497188a23c Mon Sep 17 00:00:00 2001 From: mikcl Date: Thu, 19 Jan 2023 08:22:40 +0000 Subject: [PATCH] npm: package-lock license decoding to accept string or array Signed-off-by: mikcl --- syft/pkg/cataloger/javascript/package.go | 4 +- .../javascript/parse_package_lock.go | 44 ++++++++++++++++--- .../javascript/parse_package_lock_test.go | 41 +++++++++++++++++ .../pkg-lock/array-license-package-lock.json | 41 +++++++++++++++++ 4 files changed, 123 insertions(+), 7 deletions(-) create mode 100644 syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/array-license-package-lock.json diff --git a/syft/pkg/cataloger/javascript/package.go b/syft/pkg/cataloger/javascript/package.go index 665a08f82e1..34729e4215b 100644 --- a/syft/pkg/cataloger/javascript/package.go +++ b/syft/pkg/cataloger/javascript/package.go @@ -78,8 +78,8 @@ func newPackageLockV1Package(resolver source.FileResolver, location source.Locat func newPackageLockV2Package(resolver source.FileResolver, location source.Location, name string, u lockPackage) pkg.Package { var licenses []string - if u.License != "" { - licenses = append(licenses, u.License) + if u.License != nil { + licenses = u.License } return finalizeLockPkg( diff --git a/syft/pkg/cataloger/javascript/parse_package_lock.go b/syft/pkg/cataloger/javascript/parse_package_lock.go index 09453cd9edd..bfc8ff82320 100644 --- a/syft/pkg/cataloger/javascript/parse_package_lock.go +++ b/syft/pkg/cataloger/javascript/parse_package_lock.go @@ -7,6 +7,7 @@ import ( "io" "strings" + "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" @@ -24,6 +25,39 @@ type packageLock struct { Packages map[string]lockPackage } +// packageLockLicense +type packageLockLicense []string + +func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) { + // The license field could be either a string or an array. + + // 1. An array + var arr []string + if err := json.Unmarshal(data, &arr); err == nil { + *licenses = arr + return nil + } + + // 2. A string + var str string + if err = json.Unmarshal(data, &str); err == nil { + *licenses = make([]string, 1) + (*licenses)[0] = str + return nil + } + + // debug the content we did not expect + if len(data) > 0 { + log.WithFields("license", string(data)).Debug("Unable to parse the following `license` value in package-lock.json") + } + + // 3. Unexpected + // In case we are unable to parse the license field, + // i.e if we have not covered the full specification, + // we do not want to throw an error, instead assign nil. + return nil +} + // lockDependency represents a single package dependency listed in the package.lock json file type lockDependency struct { Version string `json:"version"` @@ -32,11 +66,11 @@ type lockDependency struct { } type lockPackage struct { - Name string `json:"name"` // only present in the root package entry (named "") - Version string `json:"version"` - Resolved string `json:"resolved"` - Integrity string `json:"integrity"` - License string `json:"license"` + Name string `json:"name"` // only present in the root package entry (named "") + Version string `json:"version"` + Resolved string `json:"resolved"` + Integrity string `json:"integrity"` + License packageLockLicense `json:"license"` } // parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages. diff --git a/syft/pkg/cataloger/javascript/parse_package_lock_test.go b/syft/pkg/cataloger/javascript/parse_package_lock_test.go index 29277f0e06d..75601d43712 100644 --- a/syft/pkg/cataloger/javascript/parse_package_lock_test.go +++ b/syft/pkg/cataloger/javascript/parse_package_lock_test.go @@ -297,3 +297,44 @@ func TestParsePackageLockAlias(t *testing.T) { pkgtest.TestFileParser(t, packageLock, parsePackageLock, expected, expectedRelationships) } } + +func TestParsePackageLockLicenseWithArray(t *testing.T) { + fixture := "test-fixtures/pkg-lock/array-license-package-lock.json" + var expectedRelationships []artifact.Relationship + expectedPkgs := []pkg.Package{ + { + Name: "tmp", + Version: "1.0.0", + Licenses: []string{"ISC"}, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + PURL: "pkg:npm/tmp@1.0.0", + MetadataType: "NpmPackageLockJsonMetadata", + Metadata: pkg.NpmPackageLockJSONMetadata{}, + }, + { + Name: "pause-stream", + Version: "0.0.11", + Licenses: []string{"MIT", "Apache2"}, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + PURL: "pkg:npm/pause-stream@0.0.11", + MetadataType: "NpmPackageLockJsonMetadata", + Metadata: pkg.NpmPackageLockJSONMetadata{}, + }, + { + Name: "through", + Version: "2.3.8", + Licenses: []string{"MIT"}, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + PURL: "pkg:npm/through@2.3.8", + MetadataType: "NpmPackageLockJsonMetadata", + Metadata: pkg.NpmPackageLockJSONMetadata{}, + }, + } + for i := range expectedPkgs { + expectedPkgs[i].Locations.Add(source.NewLocation(fixture)) + } + pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships) +} diff --git a/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/array-license-package-lock.json b/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/array-license-package-lock.json new file mode 100644 index 00000000000..e5f92084e74 --- /dev/null +++ b/syft/pkg/cataloger/javascript/test-fixtures/pkg-lock/array-license-package-lock.json @@ -0,0 +1,41 @@ +{ + "name": "tmp", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "tmp", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "pause-stream": "0.0.11" + } + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "license": [ + "MIT", + "Apache2" + ], + "dependencies": { + "through": "~2.3" + } + }, + "node_modules/through": { + "version": "2.3.8", + "license": "MIT" + } + }, + "dependencies": { + "pause-stream": { + "version": "0.0.11", + "requires": { + "through": "~2.3" + } + }, + "through": { + "version": "2.3.8" + } + } + } \ No newline at end of file