Skip to content

Commit

Permalink
feat: Add support for npm lockfile version 3
Browse files Browse the repository at this point in the history
This PR adds support for npm lockfile version 3, which drops the
"dependencies" key and uses "packages" instead. I've refactored the
lockfile parser to make the distinction between the versions explicit
rather than the implicit behaviour before. It _might_ be worth splitting
into separate files at some point, but the logic is so minimal that I
haven't done it.

Some open questions;

- Does the code look vaguely correct? I don't know Go well at all
- I can't find good documentation around the presence of the "license"
  key under the "packages" entries. It seems to be present in the v2
  fixture, but I couldn't recreate that locally.
- Are there other places that I need to add / update tests?

Fixes #1203
Signed-off-by: Rob Cresswell <[email protected]>
  • Loading branch information
robcresswell committed Sep 15, 2022
1 parent 0a1cd25 commit 35fd439
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 16 deletions.
45 changes: 29 additions & 16 deletions syft/pkg/cataloger/javascript/parse_package_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,38 +47,51 @@ func parsePackageLock(path string, reader io.Reader) ([]*pkg.Package, []artifact
var packages []*pkg.Package
dec := json.NewDecoder(reader)

var lock PackageLock
for {
var lock PackageLock
if err := dec.Decode(&lock); err == io.EOF {
break
} else if err != nil {
return nil, nil, fmt.Errorf("failed to parse package-lock.json file: %w", err)
}
licenseMap := make(map[string]string)
for _, pkgMeta := range lock.Packages {
var sb strings.Builder
sb.WriteString(pkgMeta.Resolved)
sb.WriteString(pkgMeta.Integrity)
licenseMap[sb.String()] = pkgMeta.License
}
}

if lock.LockfileVersion == 1 {
for name, pkgMeta := range lock.Dependencies {
var sb strings.Builder
sb.WriteString(pkgMeta.Resolved)
sb.WriteString(pkgMeta.Integrity)
var licenses []string
if license, exists := licenseMap[sb.String()]; exists {
licenses = append(licenses, license)
}
packages = append(packages, &pkg.Package{
Name: name,
Version: pkgMeta.Version,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
Licenses: licenses,
})
}
}

if lock.LockfileVersion == 2 || lock.LockfileVersion == 3 {
for name, pkgMeta := range lock.Packages {
if name == "" {
continue
}

newPackage := &pkg.Package{
Name: getNameFromPath(name),
Version: pkgMeta.Version,
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
}

if pkgMeta.License != "" {
newPackage.Licenses = []string{pkgMeta.License}
}

packages = append(packages, newPackage)
}
}

return packages, nil, nil
}

func getNameFromPath(path string) string {
parts := strings.Split(path, "node_modules/")
return parts[len(parts)-1]
}
40 changes: 40 additions & 0 deletions syft/pkg/cataloger/javascript/parse_package_lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,43 @@ func TestParsePackageLockV2(t *testing.T) {

assertPkgsEqual(t, actual, expected)
}

func TestParsePackageLockV3(t *testing.T) {
expected := map[string]pkg.Package{
"@types/prop-types": {
Name: "@types/prop-types",
Version: "15.7.5",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"@types/react": {
Name: "@types/prop-types",
Version: "18.0.20",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"@types/scheduler": {
Name: "@types/scheduler",
Version: "0.16.2",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
"csstype": {
Name: "csstype",
Version: "3.1.1",
Language: pkg.JavaScript,
Type: pkg.NpmPkg,
},
}
fixture, err := os.Open("test-fixtures/pkg-lock/package-lock-3.json")
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}

actual, _, err := parsePackageLock(fixture.Name(), fixture)
if err != nil {
t.Fatalf("failed to parse package-lock.json: %+v", err)
}

assertPkgsEqual(t, actual, expected)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"name": "lock-v3-fixture",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "lock-v3-fixture",
"version": "1.0.0",
"dependencies": {
"@types/react": "^18.0.9"
}
},
"node_modules/@types/prop-types": {
"version": "15.7.5",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
"integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w=="
},
"node_modules/@types/react": {
"version": "18.0.20",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.0.20.tgz",
"integrity": "sha512-MWul1teSPxujEHVwZl4a5HxQ9vVNsjTchVA+xRqv/VYGCuKGAU6UhfrTdF5aBefwD1BHUD8i/zq+O/vyCm/FrA==",
"dependencies": {
"@types/prop-types": "*",
"@types/scheduler": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
"integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"node_modules/csstype": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.1.tgz",
"integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw=="
}
}
}

0 comments on commit 35fd439

Please sign in to comment.