mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 15:50:15 -08:00
refactor: migrate from github.com/aquasecurity/jfather to github.com/go-json-experiment/json (#8591)
This commit is contained in:
2
go.mod
2
go.mod
@@ -48,7 +48,7 @@ require (
|
||||
github.com/docker/go-units v0.5.0
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/go-git/go-git/v5 v5.14.0
|
||||
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 // Replace with encoding/json/v2 when proposal is accepted. Track https://github.com/golang/go/issues/71497
|
||||
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 // Replace with encoding/json/v2 when proposal is accepted. Track https://github.com/golang/go/issues/71497
|
||||
github.com/go-openapi/runtime v0.28.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.23.0 // indirect
|
||||
github.com/go-redis/redis/v8 v8.11.5
|
||||
|
||||
4
go.sum
4
go.sum
@@ -1127,8 +1127,8 @@ github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7
|
||||
github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE=
|
||||
github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA=
|
||||
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535 h1:yE7argOs92u+sSCRgqqe6eF+cDaVhSPlioy1UkA0p/w=
|
||||
github.com/go-json-experiment/json v0.0.0-20250211171154-1ae217ad3535/go.mod h1:BWmvoE1Xia34f3l/ibJweyhrT+aROb/FQ6d+37F0e2s=
|
||||
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874 h1:F8d1AJ6M9UQCavhwmO6ZsrYLfG8zVFWfEfMS2MXPkSY=
|
||||
github.com/go-json-experiment/json v0.0.0-20250223041408-d3c622f1b874/go.mod h1:TiCD2a1pcmjd7YnhGH0f/zKNcCD06B029pHhzV23c2M=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
|
||||
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
package conan
|
||||
|
||||
import (
|
||||
"io"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/jfather"
|
||||
"github.com/aquasecurity/trivy/pkg/dependency"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
xjson "github.com/aquasecurity/trivy/pkg/x/json"
|
||||
)
|
||||
|
||||
type LockFile struct {
|
||||
@@ -27,17 +28,18 @@ type GraphLock struct {
|
||||
type Node struct {
|
||||
Ref string `json:"ref"`
|
||||
Requires []string `json:"requires"`
|
||||
StartLine int
|
||||
EndLine int
|
||||
xjson.Location
|
||||
}
|
||||
type Requires []Require
|
||||
|
||||
type Require struct {
|
||||
Dependency string
|
||||
StartLine int
|
||||
EndLine int
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
type Requires []Require
|
||||
func (r *Require) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||
return json.UnmarshalDecode(dec, &r.Dependency)
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
logger *log.Logger
|
||||
@@ -63,7 +65,7 @@ func (p *Parser) parseV1(lock LockFile) ([]ftypes.Package, []ftypes.Dependency,
|
||||
if node.Ref == "" {
|
||||
continue
|
||||
}
|
||||
pkg, err := toPackage(node.Ref, node.StartLine, node.EndLine)
|
||||
pkg, err := toPackage(node.Ref, node.Location)
|
||||
if err != nil {
|
||||
p.logger.Debug("Parse ref error", log.Err(err))
|
||||
continue
|
||||
@@ -105,7 +107,7 @@ func (p *Parser) parseV2(lock LockFile) ([]ftypes.Package, []ftypes.Dependency,
|
||||
var pkgs []ftypes.Package
|
||||
|
||||
for _, req := range lock.Requires {
|
||||
pkg, err := toPackage(req.Dependency, req.StartLine, req.EndLine)
|
||||
pkg, err := toPackage(req.Dependency, req.Location)
|
||||
if err != nil {
|
||||
p.logger.Debug("Creating package entry from requirement failed", log.Err(err))
|
||||
continue
|
||||
@@ -118,12 +120,7 @@ func (p *Parser) parseV2(lock LockFile) ([]ftypes.Package, []ftypes.Dependency,
|
||||
|
||||
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
|
||||
var lock LockFile
|
||||
|
||||
input, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("failed to read conan lock file: %w", err)
|
||||
}
|
||||
if err := jfather.Unmarshal(input, &lock); err != nil {
|
||||
if err := xjson.UnmarshalRead(r, &lock); err != nil {
|
||||
return nil, nil, xerrors.Errorf("failed to decode conan lock file: %w", err)
|
||||
}
|
||||
|
||||
@@ -152,7 +149,7 @@ func parsePackage(text string) (string, string, error) {
|
||||
return ss[0], ss[1], nil
|
||||
}
|
||||
|
||||
func toPackage(pkg string, startLine, endLine int) (ftypes.Package, error) {
|
||||
func toPackage(pkg string, location xjson.Location) (ftypes.Package, error) {
|
||||
name, version, err := parsePackage(pkg)
|
||||
if err != nil {
|
||||
return ftypes.Package{}, err
|
||||
@@ -161,33 +158,6 @@ func toPackage(pkg string, startLine, endLine int) (ftypes.Package, error) {
|
||||
ID: dependency.ID(ftypes.Conan, name, version),
|
||||
Name: name,
|
||||
Version: version,
|
||||
Locations: []ftypes.Location{
|
||||
{
|
||||
StartLine: startLine,
|
||||
EndLine: endLine,
|
||||
},
|
||||
},
|
||||
Locations: []ftypes.Location{ftypes.Location(location)},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps
|
||||
func (n *Node) UnmarshalJSONWithMetadata(node jfather.Node) error {
|
||||
if err := node.Decode(&n); err != nil {
|
||||
return err
|
||||
}
|
||||
// Decode func will overwrite line numbers if we save them first
|
||||
n.StartLine = node.Range().Start.Line
|
||||
n.EndLine = node.Range().End.Line
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Require) UnmarshalJSONWithMetadata(node jfather.Node) error {
|
||||
var dep string
|
||||
if err := node.Decode(&dep); err != nil {
|
||||
return err
|
||||
}
|
||||
r.Dependency = dep
|
||||
r.StartLine = node.Range().Start.Line
|
||||
r.EndLine = node.Range().End.Line
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package core_deps
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -9,11 +8,11 @@ import (
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/jfather"
|
||||
"github.com/aquasecurity/trivy/pkg/dependency"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
xjson "github.com/aquasecurity/trivy/pkg/x/json"
|
||||
)
|
||||
|
||||
type dotNetDependencies struct {
|
||||
@@ -24,8 +23,7 @@ type dotNetDependencies struct {
|
||||
|
||||
type dotNetLibrary struct {
|
||||
Type string `json:"type"`
|
||||
StartLine int
|
||||
EndLine int
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
type RuntimeTarget struct {
|
||||
@@ -52,12 +50,7 @@ func NewParser() *Parser {
|
||||
|
||||
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
|
||||
var depsFile dotNetDependencies
|
||||
|
||||
input, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("read error: %w", err)
|
||||
}
|
||||
if err = jfather.Unmarshal(input, &depsFile); err != nil {
|
||||
if err := xjson.UnmarshalRead(r, &depsFile); err != nil {
|
||||
return nil, nil, xerrors.Errorf("failed to decode .deps.json file: %w", err)
|
||||
}
|
||||
|
||||
@@ -90,12 +83,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
|
||||
ID: dependency.ID(ftypes.DotNetCore, split[0], split[1]),
|
||||
Name: split[0],
|
||||
Version: split[1],
|
||||
Locations: []ftypes.Location{
|
||||
{
|
||||
StartLine: lib.StartLine,
|
||||
EndLine: lib.EndLine,
|
||||
},
|
||||
},
|
||||
Locations: []ftypes.Location{ftypes.Location(lib.Location)},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -118,14 +106,3 @@ func (p *Parser) isRuntimeLibrary(targetLibs map[string]TargetLib, library strin
|
||||
// Check that `runtime`, `runtimeTarget` and `native` sections are not empty
|
||||
return !lo.IsEmpty(lib)
|
||||
}
|
||||
|
||||
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps
|
||||
func (t *dotNetLibrary) UnmarshalJSONWithMetadata(node jfather.Node) error {
|
||||
if err := node.Decode(&t); err != nil {
|
||||
return err
|
||||
}
|
||||
// Decode func will overwrite line numbers if we save them first
|
||||
t.StartLine = node.Range().Start.Line
|
||||
t.EndLine = node.Range().End.Line
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ func TestParse(t *testing.T) {
|
||||
{
|
||||
name: "sad path",
|
||||
file: "testdata/invalid.deps.json",
|
||||
wantErr: "failed to decode .deps.json file: EOF",
|
||||
wantErr: "failed to decode .deps.json file: jsontext: unexpected EOF within",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package npm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"path"
|
||||
"slices"
|
||||
@@ -12,13 +11,13 @@ import (
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/jfather"
|
||||
"github.com/aquasecurity/trivy/pkg/dependency"
|
||||
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
"github.com/aquasecurity/trivy/pkg/set"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
xjson "github.com/aquasecurity/trivy/pkg/x/json"
|
||||
)
|
||||
|
||||
const nodeModulesDir = "node_modules"
|
||||
@@ -34,8 +33,7 @@ type Dependency struct {
|
||||
Dependencies map[string]Dependency `json:"dependencies"`
|
||||
Requires map[string]string `json:"requires"`
|
||||
Resolved string `json:"resolved"`
|
||||
StartLine int
|
||||
EndLine int
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
@@ -49,8 +47,7 @@ type Package struct {
|
||||
Dev bool `json:"dev"`
|
||||
Link bool `json:"link"`
|
||||
Workspaces []string `json:"workspaces"`
|
||||
StartLine int
|
||||
EndLine int
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
@@ -65,11 +62,7 @@ func NewParser() *Parser {
|
||||
|
||||
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
|
||||
var lockFile LockFile
|
||||
input, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("read error: %w", err)
|
||||
}
|
||||
if err := jfather.Unmarshal(input, &lockFile); err != nil {
|
||||
if err := xjson.UnmarshalRead(r, &lockFile); err != nil {
|
||||
return nil, nil, xerrors.Errorf("decode error: %w", err)
|
||||
}
|
||||
|
||||
@@ -117,10 +110,6 @@ func (p *Parser) parseV2(packages map[string]Package) ([]ftypes.Package, []ftype
|
||||
}
|
||||
|
||||
pkgID := packageID(pkgName, pkg.Version)
|
||||
location := ftypes.Location{
|
||||
StartLine: pkg.StartLine,
|
||||
EndLine: pkg.EndLine,
|
||||
}
|
||||
|
||||
var ref ftypes.ExternalRef
|
||||
if pkg.Resolved != "" {
|
||||
@@ -145,7 +134,7 @@ func (p *Parser) parseV2(packages map[string]Package) ([]ftypes.Package, []ftype
|
||||
sortExternalReferences(savedPkg.ExternalReferences)
|
||||
}
|
||||
|
||||
savedPkg.Locations = append(savedPkg.Locations, location)
|
||||
savedPkg.Locations = append(savedPkg.Locations, ftypes.Location(pkg.Location))
|
||||
sort.Sort(savedPkg.Locations)
|
||||
|
||||
pkgs[pkgID] = savedPkg
|
||||
@@ -159,7 +148,7 @@ func (p *Parser) parseV2(packages map[string]Package) ([]ftypes.Package, []ftype
|
||||
Relationship: lo.Ternary(pkgIndirect, ftypes.RelationshipIndirect, ftypes.RelationshipDirect),
|
||||
Dev: pkg.Dev,
|
||||
ExternalReferences: lo.Ternary(ref.URL != "", []ftypes.ExternalRef{ref}, nil),
|
||||
Locations: []ftypes.Location{location},
|
||||
Locations: []ftypes.Location{ftypes.Location(pkg.Location)},
|
||||
}
|
||||
pkgs[pkgID] = newPkg
|
||||
|
||||
@@ -304,12 +293,7 @@ func (p *Parser) parseV1(dependencies map[string]Dependency, versions map[string
|
||||
URL: dep.Resolved,
|
||||
},
|
||||
},
|
||||
Locations: []ftypes.Location{
|
||||
{
|
||||
StartLine: dep.StartLine,
|
||||
EndLine: dep.EndLine,
|
||||
},
|
||||
},
|
||||
Locations: []ftypes.Location{ftypes.Location(dep.Location)},
|
||||
}
|
||||
pkgs = append(pkgs, pkg)
|
||||
|
||||
@@ -396,28 +380,6 @@ func joinPaths(paths ...string) string {
|
||||
return strings.Join(paths, "/")
|
||||
}
|
||||
|
||||
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps for v1
|
||||
func (t *Dependency) UnmarshalJSONWithMetadata(node jfather.Node) error {
|
||||
if err := node.Decode(&t); err != nil {
|
||||
return err
|
||||
}
|
||||
// Decode func will overwrite line numbers if we save them first
|
||||
t.StartLine = node.Range().Start.Line
|
||||
t.EndLine = node.Range().End.Line
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps for v2 or newer
|
||||
func (t *Package) UnmarshalJSONWithMetadata(node jfather.Node) error {
|
||||
if err := node.Decode(&t); err != nil {
|
||||
return err
|
||||
}
|
||||
// Decode func will overwrite line numbers if we save them first
|
||||
t.StartLine = node.Range().Start.Line
|
||||
t.EndLine = node.Range().End.Line
|
||||
return nil
|
||||
}
|
||||
|
||||
func packageID(name, version string) string {
|
||||
return dependency.ID(ftypes.Npm, name, version)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package lock
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/jfather"
|
||||
"github.com/aquasecurity/trivy/pkg/dependency"
|
||||
"github.com/aquasecurity/trivy/pkg/dependency/parser/utils"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
xjson "github.com/aquasecurity/trivy/pkg/x/json"
|
||||
)
|
||||
|
||||
type LockFile struct {
|
||||
@@ -23,9 +21,8 @@ type Dependencies map[string]Dependency
|
||||
type Dependency struct {
|
||||
Type string `json:"type"`
|
||||
Resolved string `json:"resolved"`
|
||||
StartLine int
|
||||
EndLine int
|
||||
Dependencies map[string]string `json:"dependencies,omitempty"`
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
type Parser struct{}
|
||||
@@ -36,11 +33,7 @@ func NewParser() *Parser {
|
||||
|
||||
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
|
||||
var lockFile LockFile
|
||||
input, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("failed to read packages.lock.json: %w", err)
|
||||
}
|
||||
if err := jfather.Unmarshal(input, &lockFile); err != nil {
|
||||
if err := xjson.UnmarshalRead(r, &lockFile); err != nil {
|
||||
return nil, nil, xerrors.Errorf("failed to decode packages.lock.json: %w", err)
|
||||
}
|
||||
|
||||
@@ -60,12 +53,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
|
||||
Name: packageName,
|
||||
Version: packageContent.Resolved,
|
||||
Relationship: lo.Ternary(packageContent.Type == "Direct", ftypes.RelationshipDirect, ftypes.RelationshipIndirect),
|
||||
Locations: []ftypes.Location{
|
||||
{
|
||||
StartLine: packageContent.StartLine,
|
||||
EndLine: packageContent.EndLine,
|
||||
},
|
||||
},
|
||||
Locations: []ftypes.Location{ftypes.Location(packageContent.Location)},
|
||||
}
|
||||
pkgs = append(pkgs, pkg)
|
||||
|
||||
@@ -97,17 +85,6 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
|
||||
return utils.UniquePackages(pkgs), deps, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps
|
||||
func (t *Dependency) UnmarshalJSONWithMetadata(node jfather.Node) error {
|
||||
if err := node.Decode(&t); err != nil {
|
||||
return err
|
||||
}
|
||||
// Decode func will overwrite line numbers if we save them first
|
||||
t.StartLine = node.Range().Start.Line
|
||||
t.EndLine = node.Range().End.Line
|
||||
return nil
|
||||
}
|
||||
|
||||
func packageID(name, version string) string {
|
||||
return dependency.ID(ftypes.NuGet, name, version)
|
||||
}
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
package composer
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/jfather"
|
||||
"github.com/aquasecurity/trivy/pkg/dependency"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/licensing"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
xjson "github.com/aquasecurity/trivy/pkg/x/json"
|
||||
)
|
||||
|
||||
type LockFile struct {
|
||||
@@ -24,8 +23,7 @@ type packageInfo struct {
|
||||
Version string `json:"version"`
|
||||
Require map[string]string `json:"require"`
|
||||
License any `json:"license"`
|
||||
StartLine int
|
||||
EndLine int
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
type Parser struct {
|
||||
@@ -40,11 +38,7 @@ func NewParser() *Parser {
|
||||
|
||||
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
|
||||
var lockFile LockFile
|
||||
input, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("read error: %w", err)
|
||||
}
|
||||
if err = jfather.Unmarshal(input, &lockFile); err != nil {
|
||||
if err := xjson.UnmarshalRead(r, &lockFile); err != nil {
|
||||
return nil, nil, xerrors.Errorf("decode error: %w", err)
|
||||
}
|
||||
|
||||
@@ -57,12 +51,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
|
||||
Version: lpkg.Version,
|
||||
Relationship: ftypes.RelationshipUnknown, // composer.lock file doesn't have info about direct/indirect dependencies
|
||||
Licenses: licenses(lpkg.License),
|
||||
Locations: []ftypes.Location{
|
||||
{
|
||||
StartLine: lpkg.StartLine,
|
||||
EndLine: lpkg.EndLine,
|
||||
},
|
||||
},
|
||||
Locations: []ftypes.Location{ftypes.Location(lpkg.Location)},
|
||||
}
|
||||
pkgs[pkg.Name] = pkg
|
||||
|
||||
@@ -105,17 +94,6 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
|
||||
return pkgSlice, deps, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps
|
||||
func (t *packageInfo) UnmarshalJSONWithMetadata(node jfather.Node) error {
|
||||
if err := node.Decode(&t); err != nil {
|
||||
return err
|
||||
}
|
||||
// Decode func will overwrite line numbers if we save them first
|
||||
t.StartLine = node.Range().Start.Line
|
||||
t.EndLine = node.Range().End.Line
|
||||
return nil
|
||||
}
|
||||
|
||||
// licenses returns slice of licenses from string, string with separators (`or`, `and`, etc.) or string array
|
||||
// cf. https://getcomposer.org/doc/04-schema.md#license
|
||||
func licenses(val any) []string {
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
package pipenv
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/jfather"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
xjson "github.com/aquasecurity/trivy/pkg/x/json"
|
||||
)
|
||||
|
||||
type lockFile struct {
|
||||
@@ -16,8 +15,7 @@ type lockFile struct {
|
||||
}
|
||||
type dependency struct {
|
||||
Version string `json:"version"`
|
||||
StartLine int
|
||||
EndLine int
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
type Parser struct{}
|
||||
@@ -28,11 +26,7 @@ func NewParser() *Parser {
|
||||
|
||||
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
|
||||
var lockFile lockFile
|
||||
input, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("failed to read packages.lock.json: %w", err)
|
||||
}
|
||||
if err := jfather.Unmarshal(input, &lockFile); err != nil {
|
||||
if err := xjson.UnmarshalRead(r, &lockFile); err != nil {
|
||||
return nil, nil, xerrors.Errorf("failed to decode Pipenv.lock: %w", err)
|
||||
}
|
||||
|
||||
@@ -41,24 +35,8 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
|
||||
pkgs = append(pkgs, ftypes.Package{
|
||||
Name: pkgName,
|
||||
Version: strings.TrimLeft(dep.Version, "="),
|
||||
Locations: []ftypes.Location{
|
||||
{
|
||||
StartLine: dep.StartLine,
|
||||
EndLine: dep.EndLine,
|
||||
},
|
||||
},
|
||||
Locations: []ftypes.Location{ftypes.Location(dep.Location)},
|
||||
})
|
||||
}
|
||||
return pkgs, nil, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps
|
||||
func (t *dependency) UnmarshalJSONWithMetadata(node jfather.Node) error {
|
||||
if err := node.Decode(&t); err != nil {
|
||||
return err
|
||||
}
|
||||
// Decode func will overwrite line numbers if we save them first
|
||||
t.StartLine = node.Range().Start.Line
|
||||
t.EndLine = node.Range().End.Line
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
package lockfile
|
||||
|
||||
import (
|
||||
"io"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/jfather"
|
||||
"github.com/aquasecurity/trivy/pkg/dependency"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
xjson "github.com/aquasecurity/trivy/pkg/x/json"
|
||||
)
|
||||
|
||||
// lockfile format defined at: https://stringbean.github.io/sbt-dependency-lock/file-formats/version-1.html
|
||||
@@ -24,8 +23,7 @@ type sbtLockfileDependency struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Configurations []string `json:"configurations"`
|
||||
StartLine int
|
||||
EndLine int
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
type Parser struct{}
|
||||
@@ -36,12 +34,7 @@ func NewParser() *Parser {
|
||||
|
||||
func (Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
|
||||
var lockfile sbtLockfile
|
||||
input, err := io.ReadAll(r)
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("failed to read sbt lockfile: %w", err)
|
||||
}
|
||||
if err := jfather.Unmarshal(input, &lockfile); err != nil {
|
||||
if err := xjson.UnmarshalRead(r, &lockfile); err != nil {
|
||||
return nil, nil, xerrors.Errorf("JSON decoding failed: %w", err)
|
||||
}
|
||||
|
||||
@@ -54,12 +47,7 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency,
|
||||
ID: dependency.ID(ftypes.Sbt, name, dep.Version),
|
||||
Name: name,
|
||||
Version: dep.Version,
|
||||
Locations: []ftypes.Location{
|
||||
{
|
||||
StartLine: dep.StartLine,
|
||||
EndLine: dep.EndLine,
|
||||
},
|
||||
},
|
||||
Locations: []ftypes.Location{ftypes.Location(dep.Location)},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -68,17 +56,6 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency,
|
||||
return libraries, nil, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps
|
||||
func (t *sbtLockfileDependency) UnmarshalJSONWithMetadata(node jfather.Node) error {
|
||||
if err := node.Decode(&t); err != nil {
|
||||
return err
|
||||
}
|
||||
// Decode func will overwrite line numbers if we save them first
|
||||
t.StartLine = node.Range().Start.Line
|
||||
t.EndLine = node.Range().End.Line
|
||||
return nil
|
||||
}
|
||||
|
||||
func isIncludedConfig(config string) bool {
|
||||
return config == "compile" || config == "runtime"
|
||||
}
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
package swift
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/jfather"
|
||||
"github.com/aquasecurity/trivy/pkg/dependency"
|
||||
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/log"
|
||||
xio "github.com/aquasecurity/trivy/pkg/x/io"
|
||||
xjson "github.com/aquasecurity/trivy/pkg/x/json"
|
||||
)
|
||||
|
||||
// Parser is a parser for Package.resolved files
|
||||
@@ -28,11 +27,7 @@ func NewParser() *Parser {
|
||||
|
||||
func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) {
|
||||
var lockFile LockFile
|
||||
input, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, nil, xerrors.Errorf("read error: %w", err)
|
||||
}
|
||||
if err := jfather.Unmarshal(input, &lockFile); err != nil {
|
||||
if err := xjson.UnmarshalRead(r, &lockFile); err != nil {
|
||||
return nil, nil, xerrors.Errorf("decode error: %w", err)
|
||||
}
|
||||
|
||||
@@ -58,12 +53,7 @@ func (p *Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependenc
|
||||
ID: dependency.ID(ftypes.Swift, name, version),
|
||||
Name: name,
|
||||
Version: version,
|
||||
Locations: []ftypes.Location{
|
||||
{
|
||||
StartLine: pin.StartLine,
|
||||
EndLine: pin.EndLine,
|
||||
},
|
||||
},
|
||||
Locations: []ftypes.Location{ftypes.Location(pin.Location)},
|
||||
})
|
||||
}
|
||||
sort.Sort(pkgs)
|
||||
@@ -75,7 +65,7 @@ func pkgName(pin Pin, lockVersion int) string {
|
||||
// v2 uses `Location`
|
||||
name := pin.RepositoryURL
|
||||
if lockVersion > 1 {
|
||||
name = pin.Location
|
||||
name = pin.Loc
|
||||
}
|
||||
// Swift uses `https://github.com/<author>/<package>.git format
|
||||
// `.git` suffix can be omitted (take a look happy test)
|
||||
@@ -84,14 +74,3 @@ func pkgName(pin Pin, lockVersion int) string {
|
||||
name = strings.TrimSuffix(name, ".git")
|
||||
return name
|
||||
}
|
||||
|
||||
// UnmarshalJSONWithMetadata needed to detect start and end lines of deps for v1
|
||||
func (p *Pin) UnmarshalJSONWithMetadata(node jfather.Node) error {
|
||||
if err := node.Decode(&p); err != nil {
|
||||
return err
|
||||
}
|
||||
// Decode func will overwrite line numbers if we save them first
|
||||
p.StartLine = node.Range().Start.Line
|
||||
p.EndLine = node.Range().End.Line
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package swift
|
||||
|
||||
import (
|
||||
xjson "github.com/aquasecurity/trivy/pkg/x/json"
|
||||
)
|
||||
|
||||
type LockFile struct {
|
||||
Object Object `json:"object"`
|
||||
Pins []Pin `json:"pins"`
|
||||
@@ -13,10 +17,9 @@ type Object struct {
|
||||
type Pin struct {
|
||||
Package string `json:"package"`
|
||||
RepositoryURL string `json:"repositoryURL"` // Package.revision v1
|
||||
Location string `json:"location"` // Package.revision v2
|
||||
Loc string `json:"location"` // Package.revision v2
|
||||
State State `json:"state"`
|
||||
StartLine int
|
||||
EndLine int
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
type State struct {
|
||||
|
||||
@@ -44,7 +44,7 @@ func ManifestFromJSON(path string, data []byte) (*Manifest, error) {
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, root, json.WithUnmarshalers(
|
||||
json.UnmarshalFromFunc(func(dec *jsontext.Decoder, node *ManifestNode, opts json.Options) error {
|
||||
json.UnmarshalFromFunc(func(dec *jsontext.Decoder, node *ManifestNode) error {
|
||||
startOffset := dec.InputOffset()
|
||||
if err := unmarshalManifestNode(dec, node); err != nil {
|
||||
return err
|
||||
|
||||
107
pkg/x/json/json.go
Normal file
107
pkg/x/json/json.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
|
||||
"github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
"github.com/aquasecurity/trivy/pkg/set"
|
||||
)
|
||||
|
||||
// lineReader is a custom reader that tracks line numbers.
|
||||
type lineReader struct {
|
||||
r io.Reader
|
||||
line int
|
||||
}
|
||||
|
||||
// newLineReader creates a new line reader.
|
||||
func newLineReader(r io.Reader) *lineReader {
|
||||
return &lineReader{
|
||||
r: r,
|
||||
line: 1,
|
||||
}
|
||||
}
|
||||
|
||||
func (lr *lineReader) Read(p []byte) (n int, err error) {
|
||||
n, err = lr.r.Read(p)
|
||||
if n > 0 {
|
||||
// Count the number of newlines in the read buffer
|
||||
lr.line += bytes.Count(p[:n], []byte("\n"))
|
||||
}
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (lr *lineReader) Line() int {
|
||||
return lr.line
|
||||
}
|
||||
|
||||
func Unmarshal(data []byte, v any) error {
|
||||
return UnmarshalRead(bytes.NewBuffer(data), v)
|
||||
}
|
||||
|
||||
func UnmarshalRead(r io.Reader, v any) error {
|
||||
lr := newLineReader(r)
|
||||
unmarshalers := unmarshalerWithObjectLocation(lr)
|
||||
return json.UnmarshalRead(lr, v, json.WithUnmarshalers(unmarshalers))
|
||||
}
|
||||
|
||||
// Location is wrap of types.Location.
|
||||
// This struct is required when you need to detect location of your object from json file.
|
||||
type Location types.Location
|
||||
|
||||
func (l *Location) SetLocation(location types.Location) {
|
||||
*l = Location(location)
|
||||
}
|
||||
|
||||
// ObjectLocation is required when you need to save Location for your struct.
|
||||
type ObjectLocation interface {
|
||||
SetLocation(location types.Location)
|
||||
}
|
||||
|
||||
// unmarshalerWithObjectLocation creates json.Unmarshaler for ObjectLocation to save object location into xjson.Location
|
||||
// To use UnmarshalerWithObjectLocation for primitive types, you must implement the UnmarshalerFrom interface for those objects.
|
||||
// cf. https://pkg.go.dev/github.com/go-json-experiment/json#UnmarshalerFrom
|
||||
func unmarshalerWithObjectLocation(r *lineReader) *json.Unmarshalers {
|
||||
visited := set.New[int]()
|
||||
return unmarshaler(r, visited)
|
||||
}
|
||||
|
||||
func unmarshaler(r *lineReader, visited set.Set[int]) *json.Unmarshalers {
|
||||
return json.UnmarshalFromFunc(func(dec *jsontext.Decoder, loc ObjectLocation) error {
|
||||
// Decoder.InputOffset reports the offset after the last token,
|
||||
// but we want to record the offset before the next token.
|
||||
//
|
||||
// Call Decoder.PeekKind to buffer enough to reach the next token.
|
||||
// Add the number of leading whitespace, commas, and colons
|
||||
// to locate the start of the next token.
|
||||
// cf. https://pkg.go.dev/github.com/go-json-experiment/json@v0.0.0-20250223041408-d3c622f1b874#example-WithUnmarshalers-RecordOffsets
|
||||
kind := dec.PeekKind()
|
||||
|
||||
unread := bytes.TrimLeft(dec.UnreadBuffer(), " \n\r\t,:")
|
||||
start := r.Line() - bytes.Count(unread, []byte("\n")) // The decoder buffer may have read more lines.
|
||||
|
||||
// Check visited set to avoid infinity loops
|
||||
if visited.Contains(start) {
|
||||
return json.SkipFunc
|
||||
}
|
||||
visited.Append(start)
|
||||
|
||||
// Return more detailed error for cases when UnmarshalJSONFrom is not implemented for primitive type.
|
||||
if _, ok := loc.(json.UnmarshalerFrom); !ok && kind != '[' && kind != '{' {
|
||||
return xerrors.Errorf("structures with single primitive type should implement UnmarshalJSONFrom: %T", loc)
|
||||
}
|
||||
|
||||
if err := json.UnmarshalDecode(dec, loc, json.WithUnmarshalers(unmarshaler(r, visited))); err != nil {
|
||||
return err
|
||||
}
|
||||
loc.SetLocation(types.Location{
|
||||
StartLine: start,
|
||||
EndLine: r.Line() - bytes.Count(dec.UnreadBuffer(), []byte("\n")),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
149
pkg/x/json/json_test.go
Normal file
149
pkg/x/json/json_test.go
Normal file
@@ -0,0 +1,149 @@
|
||||
package json_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
xjson "github.com/aquasecurity/trivy/pkg/x/json"
|
||||
)
|
||||
|
||||
// See npm.LockFile
|
||||
type nestedStruct struct {
|
||||
Dependencies map[string]Dependency `json:"dependencies"`
|
||||
}
|
||||
|
||||
type Dependency struct {
|
||||
Version string `json:"version"`
|
||||
Dependencies map[string]Dependency `json:"dependencies"`
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
type stringWithLocation struct {
|
||||
Requires Requires `json:"requires"`
|
||||
}
|
||||
|
||||
type Requires []Require
|
||||
|
||||
type Require struct {
|
||||
Dependency string
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
func (r *Require) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
||||
return json.UnmarshalDecode(dec, &r.Dependency)
|
||||
}
|
||||
|
||||
type stringsWithoutUnmarshalerFrom struct {
|
||||
Strings []StringWithoutUnmarshalerFrom `json:"strings"`
|
||||
}
|
||||
|
||||
type StringWithoutUnmarshalerFrom struct {
|
||||
String string
|
||||
xjson.Location
|
||||
}
|
||||
|
||||
func TestUnmarshal(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in []byte
|
||||
out any
|
||||
want any
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "nested LocationObjects",
|
||||
in: []byte(`{
|
||||
"dependencies": {
|
||||
"body-parser": {
|
||||
"version": "1.18.3",
|
||||
"dependencies": {
|
||||
"debug": {
|
||||
"version": "2.6.9"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`),
|
||||
out: nestedStruct{},
|
||||
want: nestedStruct{
|
||||
Dependencies: map[string]Dependency{
|
||||
"body-parser": {
|
||||
Version: "1.18.3",
|
||||
Location: xjson.Location{
|
||||
StartLine: 3,
|
||||
EndLine: 10,
|
||||
},
|
||||
Dependencies: map[string]Dependency{
|
||||
// UnmarshalerWithObjectLocation doesn't support Location for nested objects
|
||||
"debug": {
|
||||
Version: "2.6.9",
|
||||
Location: xjson.Location{
|
||||
StartLine: 6,
|
||||
EndLine: 8,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Location for only string",
|
||||
in: []byte(`{
|
||||
"version": "0.5",
|
||||
"requires": [
|
||||
"sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488",
|
||||
"matrix/1.3#905c3f0babc520684c84127378fefdd0%1675278900.0103245"
|
||||
]
|
||||
}`),
|
||||
out: stringWithLocation{},
|
||||
want: stringWithLocation{
|
||||
Requires: []Require{
|
||||
{
|
||||
Dependency: "sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488",
|
||||
Location: xjson.Location{
|
||||
StartLine: 4,
|
||||
EndLine: 4,
|
||||
},
|
||||
},
|
||||
{
|
||||
Dependency: "matrix/1.3#905c3f0babc520684c84127378fefdd0%1675278900.0103245",
|
||||
Location: xjson.Location{
|
||||
StartLine: 5,
|
||||
EndLine: 5,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "String object without UnmarshalerFrom implementation",
|
||||
in: []byte(`{
|
||||
"strings": [
|
||||
"sound32/1.0#83d4b7bf607b3b60a6546f8b58b5cdd7%1675278904.0791488",
|
||||
"matrix/1.3#905c3f0babc520684c84127378fefdd0%1675278900.0103245"
|
||||
]
|
||||
}`),
|
||||
out: stringsWithoutUnmarshalerFrom{},
|
||||
wantErr: "structures with single primitive type should implement UnmarshalJSONFrom",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := xjson.Unmarshal(tt.in, &tt.out)
|
||||
|
||||
if tt.wantErr != "" {
|
||||
require.ErrorContains(t, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tt.want, tt.out)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user