Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions syft/pkg/cataloger/php/cataloger.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,17 @@ Package php provides a concrete Cataloger implementation for PHP ecosystem files
package php

import (
"github.com/anchore/syft/syft/pkg/cataloger/common"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
)

// NewPHPComposerInstalledCataloger returns a new cataloger for PHP installed.json files.
func NewPHPComposerInstalledCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/installed.json": parseInstalledJSON,
}

return common.NewGenericCataloger(nil, globParsers, "php-composer-installed-cataloger")
func NewPHPComposerInstalledCataloger() *generic.Cataloger {
return generic.NewCataloger("php-composer-installed-cataloger").
WithParserByGlobs(parseInstalledJSON, "**/installed.json")
}

// NewPHPComposerLockCataloger returns a new cataloger for PHP composer.lock files.
func NewPHPComposerLockCataloger() *common.GenericCataloger {
globParsers := map[string]common.ParserFn{
"**/composer.lock": parseComposerLock,
}

return common.NewGenericCataloger(nil, globParsers, "php-composer-lock-cataloger")
func NewPHPComposerLockCataloger() *generic.Cataloger {
return generic.NewCataloger("php-composer-lock-cataloger").
WithParserByGlobs(parseComposerLock, "**/composer.lock")
}
51 changes: 51 additions & 0 deletions syft/pkg/cataloger/php/package.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package php

import (
"strings"

"github.com/anchore/packageurl-go"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)

func newComposerLockPackage(m pkg.PhpComposerJSONMetadata, location ...source.Location) pkg.Package {
p := pkg.Package{
Name: m.Name,
Version: m.Version,
Locations: source.NewLocationSet(location...),
PURL: packageURL(m),
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Metadata: m,
}

p.SetID()
return p
}

func packageURL(m pkg.PhpComposerJSONMetadata) string {
var name, vendor string
fields := strings.Split(m.Name, "/")
switch len(fields) {
case 0:
return ""
case 1:
name = m.Name
case 2:
vendor = fields[0]
name = fields[1]
default:
vendor = fields[0]
name = strings.Join(fields[1:], "-")
}

pURL := packageurl.NewPackageURL(
packageurl.TypeComposer,
vendor,
name,
m.Version,
nil,
"")
return pURL.ToString()
}
53 changes: 53 additions & 0 deletions syft/pkg/cataloger/php/package_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package php

import (
"testing"

"github.com/sergi/go-diff/diffmatchpatch"

"github.com/anchore/syft/syft/pkg"
)

func Test_packageURL(t *testing.T) {
tests := []struct {
name string
metadata pkg.PhpComposerJSONMetadata
expected string
}{
{
name: "with extractable vendor",
metadata: pkg.PhpComposerJSONMetadata{
Name: "ven/name",
Version: "1.0.1",
},
expected: "pkg:composer/ven/name@1.0.1",
},
{
name: "name with slashes (invalid)",
metadata: pkg.PhpComposerJSONMetadata{
Name: "ven/name/component",
Version: "1.0.1",
},
expected: "pkg:composer/ven/name-component@1.0.1",
},
{
name: "unknown vendor",
metadata: pkg.PhpComposerJSONMetadata{
Name: "name",
Version: "1.0.1",
},
expected: "pkg:composer/name@1.0.1",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual := packageURL(test.metadata)
if actual != test.expected {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(test.expected, actual, true)
t.Errorf("diff: %s", dmp.DiffPrettyText(diffs))
}
})
}
}
23 changes: 9 additions & 14 deletions syft/pkg/cataloger/php/parse_composer_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,20 @@ import (

"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/source"
)

var _ generic.Parser = parseComposerLock

type composerLock struct {
Packages []pkg.PhpComposerJSONMetadata `json:"packages"`
PackageDev []pkg.PhpComposerJSONMetadata `json:"packages-dev"`
}

// parseComposerLock is a parser function for Composer.lock contents, returning "Default" php packages discovered.
func parseComposerLock(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
packages := make([]*pkg.Package, 0)
func parseComposerLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
pkgs := make([]pkg.Package, 0)
dec := json.NewDecoder(reader)

for {
Expand All @@ -26,19 +30,10 @@ func parseComposerLock(_ string, reader io.Reader) ([]*pkg.Package, []artifact.R
} else if err != nil {
return nil, nil, fmt.Errorf("failed to parse composer.lock file: %w", err)
}
for _, pkgMeta := range lock.Packages {
version := pkgMeta.Version
name := pkgMeta.Name
packages = append(packages, &pkg.Package{
Name: name,
Version: version,
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Metadata: pkgMeta,
})
for _, m := range lock.Packages {
pkgs = append(pkgs, newComposerLockPackage(m, reader.Location))
}
}

return packages, nil, nil
return pkgs, nil, nil
}
30 changes: 12 additions & 18 deletions syft/pkg/cataloger/php/parse_composer_lock_test.go
Original file line number Diff line number Diff line change
@@ -1,19 +1,24 @@
package php

import (
"os"
"testing"

"github.com/go-test/deep"

"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
"github.com/anchore/syft/syft/source"
)

func TestParseComposerFileLock(t *testing.T) {
expected := []*pkg.Package{
var expectedRelationships []artifact.Relationship
fixture := "test-fixtures/composer.lock"
locations := source.NewLocationSet(source.NewLocation(fixture))
expectedPkgs := []pkg.Package{
{
Name: "adoy/fastcgi-client",
Version: "1.0.2",
PURL: "pkg:composer/adoy/fastcgi-client@1.0.2",
Locations: locations,
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Expand Down Expand Up @@ -52,6 +57,8 @@ func TestParseComposerFileLock(t *testing.T) {
{
Name: "alcaeus/mongo-php-adapter",
Version: "1.1.11",
Locations: locations,
PURL: "pkg:composer/alcaeus/mongo-php-adapter@1.1.11",
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Expand Down Expand Up @@ -106,18 +113,5 @@ func TestParseComposerFileLock(t *testing.T) {
},
},
}
fixture, err := os.Open("test-fixtures/composer.lock")
if err != nil {
t.Fatalf("failed to open fixture: %+v", err)
}

// TODO: no relationships are under test yet
actual, _, err := parseComposerLock(fixture.Name(), fixture)
if err != nil {
t.Fatalf("failed to parse requirements: %+v", err)
}

for _, d := range deep.Equal(expected, actual) {
t.Errorf("diff: %+v", d)
}
pkgtest.TestFileParser(t, fixture, parseComposerLock, expectedPkgs, expectedRelationships)
}
29 changes: 10 additions & 19 deletions syft/pkg/cataloger/php/parse_installed_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import (

"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/pkg/cataloger/common"
"github.com/anchore/syft/syft/pkg/cataloger/generic"
"github.com/anchore/syft/syft/source"
)

var _ generic.Parser = parseComposerLock

// Note: composer version 2 introduced a new structure for the installed.json file, so we support both
type installedJSONComposerV2 struct {
Packages []pkg.PhpComposerJSONMetadata `json:"packages"`
Expand All @@ -36,34 +39,22 @@ func (w *installedJSONComposerV2) UnmarshalJSON(data []byte) error {
return nil
}

// integrity check
var _ common.ParserFn = parseComposerLock

// parseComposerLock is a parser function for Composer.lock contents, returning "Default" php packages discovered.
func parseInstalledJSON(_ string, reader io.Reader) ([]*pkg.Package, []artifact.Relationship, error) {
packages := make([]*pkg.Package, 0)
// parseInstalledJSON is a parser function for Composer.lock contents, returning "Default" php packages discovered.
func parseInstalledJSON(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
var pkgs []pkg.Package
dec := json.NewDecoder(reader)

for {
var lock installedJSONComposerV2
if err := dec.Decode(&lock); err == io.EOF {
break
} else if err != nil {
return nil, nil, fmt.Errorf("failed to parse composer.lock file: %w", err)
return nil, nil, fmt.Errorf("failed to parse installed.json file: %w", err)
}
for _, pkgMeta := range lock.Packages {
version := pkgMeta.Version
name := pkgMeta.Name
packages = append(packages, &pkg.Package{
Name: name,
Version: version,
Language: pkg.PHP,
Type: pkg.PhpComposerPkg,
MetadataType: pkg.PhpComposerJSONMetadataType,
Metadata: pkgMeta,
})
pkgs = append(pkgs, newComposerLockPackage(pkgMeta, reader.Location))
}
}

return packages, nil, nil
return pkgs, nil, nil
}
Loading