mirror of
https://github.com/anchore/syft.git
synced 2026-07-05 02:28:25 +02:00
Signed-off-by: Rez Moss <hi@rezmoss.com> Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com> Co-authored-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
This commit is contained in:
parent
5eefd73ac7
commit
fea4a50124
@ -3,7 +3,7 @@ package internal
|
|||||||
const (
|
const (
|
||||||
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
// JSONSchemaVersion is the current schema version output by the JSON encoder
|
||||||
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
// This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment.
|
||||||
JSONSchemaVersion = "16.1.4"
|
JSONSchemaVersion = "16.1.5"
|
||||||
|
|
||||||
// Changelog
|
// Changelog
|
||||||
// 16.1.0 - reformulated the python pdm fields (added "URL" and removed the unused "path" field).
|
// 16.1.0 - reformulated the python pdm fields (added "URL" and removed the unused "path" field).
|
||||||
@ -11,5 +11,6 @@ const (
|
|||||||
// 16.1.2 - placeholder for 16.1.2 changelog
|
// 16.1.2 - placeholder for 16.1.2 changelog
|
||||||
// 16.1.3 - add GGUFFileParts to GGUFFileHeader metadata
|
// 16.1.3 - add GGUFFileParts to GGUFFileHeader metadata
|
||||||
// 16.1.4 - add BunLockEntry metadata type for bun.lock support
|
// 16.1.4 - add BunLockEntry metadata type for bun.lock support
|
||||||
|
// 16.1.5 - add DenoLockEntry and DenoRemoteLockEntry metadata types for deno.lock support
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|||||||
@ -20,6 +20,8 @@ func AllTypes() []any {
|
|||||||
pkg.CondaMetaPackage{},
|
pkg.CondaMetaPackage{},
|
||||||
pkg.DartPubspec{},
|
pkg.DartPubspec{},
|
||||||
pkg.DartPubspecLockEntry{},
|
pkg.DartPubspecLockEntry{},
|
||||||
|
pkg.DenoLockEntry{},
|
||||||
|
pkg.DenoRemoteLockEntry{},
|
||||||
pkg.DotnetDepsEntry{},
|
pkg.DotnetDepsEntry{},
|
||||||
pkg.DotnetPackagesLockEntry{},
|
pkg.DotnetPackagesLockEntry{},
|
||||||
pkg.DotnetPortableExecutableEntry{},
|
pkg.DotnetPortableExecutableEntry{},
|
||||||
|
|||||||
@ -73,6 +73,8 @@ var jsonTypes = makeJSONTypes(
|
|||||||
jsonNames(pkg.ConaninfoEntry{}, "c-conan-info-entry"),
|
jsonNames(pkg.ConaninfoEntry{}, "c-conan-info-entry"),
|
||||||
jsonNames(pkg.DartPubspecLockEntry{}, "dart-pubspec-lock-entry", "DartPubMetadata"),
|
jsonNames(pkg.DartPubspecLockEntry{}, "dart-pubspec-lock-entry", "DartPubMetadata"),
|
||||||
jsonNames(pkg.DartPubspec{}, "dart-pubspec"),
|
jsonNames(pkg.DartPubspec{}, "dart-pubspec"),
|
||||||
|
jsonNames(pkg.DenoLockEntry{}, "deno-lock-entry"),
|
||||||
|
jsonNames(pkg.DenoRemoteLockEntry{}, "deno-remote-lock-entry"),
|
||||||
jsonNames(pkg.DotnetDepsEntry{}, "dotnet-deps-entry", "DotnetDepsMetadata"),
|
jsonNames(pkg.DotnetDepsEntry{}, "dotnet-deps-entry", "DotnetDepsMetadata"),
|
||||||
jsonNames(pkg.DotnetPortableExecutableEntry{}, "dotnet-portable-executable-entry"),
|
jsonNames(pkg.DotnetPortableExecutableEntry{}, "dotnet-portable-executable-entry"),
|
||||||
jsonNames(pkg.DpkgArchiveEntry{}, "dpkg-archive-entry"),
|
jsonNames(pkg.DpkgArchiveEntry{}, "dpkg-archive-entry"),
|
||||||
|
|||||||
@ -105,7 +105,7 @@ func DefaultPackageTaskFactories() Factories {
|
|||||||
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
func(cfg CatalogingFactoryConfig) pkg.Cataloger {
|
||||||
return javascript.NewLockCataloger(cfg.PackagesConfig.JavaScript)
|
return javascript.NewLockCataloger(cfg.PackagesConfig.JavaScript)
|
||||||
},
|
},
|
||||||
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, JavaScript, Node, NPM,
|
pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, JavaScript, Node, NPM, "deno",
|
||||||
),
|
),
|
||||||
newSimplePackageTaskFactory(php.NewComposerLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "php", "composer"),
|
newSimplePackageTaskFactory(php.NewComposerLockCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, "php", "composer"),
|
||||||
newSimplePackageTaskFactory(php.NewPearCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, pkgcataloging.ImageTag, "php", "pear"),
|
newSimplePackageTaskFactory(php.NewPearCataloger, pkgcataloging.DeclaredTag, pkgcataloging.DirectoryTag, pkgcataloging.LanguageTag, pkgcataloging.ImageTag, "php", "pear"),
|
||||||
|
|||||||
4351
schema/json/schema-16.1.5.json
Normal file
4351
schema/json/schema-16.1.5.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
"$id": "anchore.io/schema/syft/json/16.1.4/document",
|
"$id": "anchore.io/schema/syft/json/16.1.5/document",
|
||||||
"$ref": "#/$defs/Document",
|
"$ref": "#/$defs/Document",
|
||||||
"$defs": {
|
"$defs": {
|
||||||
"AlpmDbEntry": {
|
"AlpmDbEntry": {
|
||||||
@ -748,6 +748,45 @@
|
|||||||
],
|
],
|
||||||
"description": "DartPubspecLockEntry is a struct that represents a single entry found in the \"packages\" section in a Dart pubspec.lock file."
|
"description": "DartPubspecLockEntry is a struct that represents a single entry found in the \"packages\" section in a Dart pubspec.lock file."
|
||||||
},
|
},
|
||||||
|
"DenoLockEntry": {
|
||||||
|
"properties": {
|
||||||
|
"integrity": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Integrity is the crpto hash of the package content for verification"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
"description": "Dependencies is the list of package specifiers that this package depends on"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"integrity",
|
||||||
|
"dependencies"
|
||||||
|
],
|
||||||
|
"description": "DenoLockEntry is a struct that rep a single entry found in the \"packages\" section of a Deno deno.lock file"
|
||||||
|
},
|
||||||
|
"DenoRemoteLockEntry": {
|
||||||
|
"properties": {
|
||||||
|
"url": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "URL is the remote URL from which the module fetcef"
|
||||||
|
},
|
||||||
|
"integrity": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Integrity is the crpto hash of the package content for verification"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
"required": [
|
||||||
|
"url",
|
||||||
|
"integrity"
|
||||||
|
],
|
||||||
|
"description": "DenoRemoteLockEntry is a struct that rep a single entry found in the \"remote\" section of a Deno deno.lock file"
|
||||||
|
},
|
||||||
"Descriptor": {
|
"Descriptor": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"name": {
|
||||||
@ -2658,6 +2697,12 @@
|
|||||||
{
|
{
|
||||||
"$ref": "#/$defs/DartPubspecLockEntry"
|
"$ref": "#/$defs/DartPubspecLockEntry"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/$defs/DenoLockEntry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"$ref": "#/$defs/DenoRemoteLockEntry"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"$ref": "#/$defs/DotnetDepsEntry"
|
"$ref": "#/$defs/DotnetDepsEntry"
|
||||||
},
|
},
|
||||||
|
|||||||
@ -57,6 +57,8 @@ func Test_OriginatorSupplier(t *testing.T) {
|
|||||||
pkg.YarnLockEntry{},
|
pkg.YarnLockEntry{},
|
||||||
pkg.TerraformLockProviderEntry{},
|
pkg.TerraformLockProviderEntry{},
|
||||||
pkg.GGUFFileHeader{},
|
pkg.GGUFFileHeader{},
|
||||||
|
pkg.DenoLockEntry{},
|
||||||
|
pkg.DenoRemoteLockEntry{},
|
||||||
)
|
)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
|||||||
@ -22,6 +22,7 @@ catalogers:
|
|||||||
config: javascript.CatalogerConfig # AUTO-GENERATED
|
config: javascript.CatalogerConfig # AUTO-GENERATED
|
||||||
selectors: # AUTO-GENERATED
|
selectors: # AUTO-GENERATED
|
||||||
- declared
|
- declared
|
||||||
|
- deno
|
||||||
- directory
|
- directory
|
||||||
- javascript
|
- javascript
|
||||||
- language
|
- language
|
||||||
@ -29,6 +30,44 @@ catalogers:
|
|||||||
- npm
|
- npm
|
||||||
- package
|
- package
|
||||||
parsers: # AUTO-GENERATED structure
|
parsers: # AUTO-GENERATED structure
|
||||||
|
- function: parseDenoLock
|
||||||
|
detector: # AUTO-GENERATED
|
||||||
|
method: glob # AUTO-GENERATED
|
||||||
|
criteria: # AUTO-GENERATED
|
||||||
|
- '**/deno.lock'
|
||||||
|
metadata_types: # AUTO-GENERATED
|
||||||
|
- pkg.DenoLockEntry
|
||||||
|
- pkg.DenoRemoteLockEntry
|
||||||
|
- pkg.NpmPackageLockEntry
|
||||||
|
package_types: # AUTO-GENERATED
|
||||||
|
- npm
|
||||||
|
purl_types: # AUTO-GENERATED
|
||||||
|
- npm
|
||||||
|
json_schema_types: # AUTO-GENERATED
|
||||||
|
- DenoLockEntry
|
||||||
|
- DenoRemoteLockEntry
|
||||||
|
- JavascriptNpmPackageLockEntry
|
||||||
|
capabilities: # MANUAL - preserved across regeneration
|
||||||
|
- name: license
|
||||||
|
default: false
|
||||||
|
- name: dependency.depth
|
||||||
|
default:
|
||||||
|
- direct
|
||||||
|
- indirect
|
||||||
|
- name: dependency.edges
|
||||||
|
default: ""
|
||||||
|
- name: dependency.kinds
|
||||||
|
default:
|
||||||
|
- runtime
|
||||||
|
- name: package_manager.files.listing
|
||||||
|
default: false
|
||||||
|
- name: package_manager.files.digests
|
||||||
|
default: false
|
||||||
|
- name: package_manager.package_integrity_hash
|
||||||
|
default: true
|
||||||
|
evidence:
|
||||||
|
- DenoLockEntry.Integrity
|
||||||
|
- DenoRemoteLockEntry.Integrity
|
||||||
- function: parseBunLock
|
- function: parseBunLock
|
||||||
detector: # AUTO-GENERATED
|
detector: # AUTO-GENERATED
|
||||||
method: glob # AUTO-GENERATED
|
method: glob # AUTO-GENERATED
|
||||||
|
|||||||
@ -20,9 +20,11 @@ func NewLockCataloger(cfg CatalogerConfig) pkg.Cataloger {
|
|||||||
packageLockAdapter := newGenericPackageLockAdapter(cfg)
|
packageLockAdapter := newGenericPackageLockAdapter(cfg)
|
||||||
pnpmLockAdapter := newGenericPnpmLockAdapter(cfg)
|
pnpmLockAdapter := newGenericPnpmLockAdapter(cfg)
|
||||||
bunLockAdapter := newGenericBunLockAdapter(cfg)
|
bunLockAdapter := newGenericBunLockAdapter(cfg)
|
||||||
|
denoLockAdapter := newGenericDenoLockAdapter(cfg)
|
||||||
return generic.NewCataloger("javascript-lock-cataloger").
|
return generic.NewCataloger("javascript-lock-cataloger").
|
||||||
WithParserByGlobs(packageLockAdapter.parsePackageLock, "**/package-lock.json").
|
WithParserByGlobs(packageLockAdapter.parsePackageLock, "**/package-lock.json").
|
||||||
WithParserByGlobs(yarnLockAdapter.parseYarnLock, "**/yarn.lock").
|
WithParserByGlobs(yarnLockAdapter.parseYarnLock, "**/yarn.lock").
|
||||||
WithParserByGlobs(pnpmLockAdapter.parsePnpmLock, "**/pnpm-lock.yaml").
|
WithParserByGlobs(pnpmLockAdapter.parsePnpmLock, "**/pnpm-lock.yaml").
|
||||||
WithParserByGlobs(bunLockAdapter.parseBunLock, "**/bun.lock")
|
WithParserByGlobs(bunLockAdapter.parseBunLock, "**/bun.lock").
|
||||||
|
WithParserByGlobs(denoLockAdapter.parseDenoLock, "**/deno.lock")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -173,6 +173,7 @@ func Test_LockCataloger_Globs(t *testing.T) {
|
|||||||
name: "obtain package files",
|
name: "obtain package files",
|
||||||
fixture: "testdata/glob-paths",
|
fixture: "testdata/glob-paths",
|
||||||
expected: []string{
|
expected: []string{
|
||||||
|
"src/deno.lock",
|
||||||
"src/package-lock.json",
|
"src/package-lock.json",
|
||||||
"src/pnpm-lock.yaml",
|
"src/pnpm-lock.yaml",
|
||||||
"src/yarn.lock",
|
"src/yarn.lock",
|
||||||
|
|||||||
296
syft/pkg/cataloger/javascript/parse_deno_lock.go
Normal file
296
syft/pkg/cataloger/javascript/parse_deno_lock.go
Normal file
@ -0,0 +1,296 @@
|
|||||||
|
package javascript
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/anchore/packageurl-go"
|
||||||
|
"github.com/anchore/syft/internal/unknown"
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/generic"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
|
||||||
|
)
|
||||||
|
|
||||||
|
type denoLock struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Jsr map[string]denoJsrPackage `json:"jsr"`
|
||||||
|
Npm map[string]denoNpmPackage `json:"npm"`
|
||||||
|
Remote map[string]string `json:"remote"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type denoJsrPackage struct {
|
||||||
|
Integrity string `json:"integrity"`
|
||||||
|
Dependencies []string `json:"dependencies"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type denoNpmPackage struct {
|
||||||
|
Integrity string `json:"integrity"`
|
||||||
|
Dependencies []string `json:"dependencies"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type genericDenoLockAdapter struct {
|
||||||
|
cfg CatalogerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGenericDenoLockAdapter(cfg CatalogerConfig) genericDenoLockAdapter {
|
||||||
|
return genericDenoLockAdapter{
|
||||||
|
cfg: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a genericDenoLockAdapter) parseDenoLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
|
||||||
|
var pkgs []pkg.Package
|
||||||
|
dec := json.NewDecoder(reader)
|
||||||
|
|
||||||
|
var lock denoLock
|
||||||
|
for {
|
||||||
|
if err := dec.Decode(&lock); errors.Is(err, io.EOF) {
|
||||||
|
break
|
||||||
|
} else if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to parse deno.lock file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for nameVersion, pkgMeta := range lock.Jsr {
|
||||||
|
name, version := parseDenoJsrNameVersion(nameVersion)
|
||||||
|
if name == "" || version == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkgs = append(pkgs, newDenoJsrPackage(reader.Location, name, version, pkgMeta))
|
||||||
|
}
|
||||||
|
|
||||||
|
for nameVersion, pkgMeta := range lock.Npm {
|
||||||
|
name, version := parseDenoNpmNameVersion(nameVersion)
|
||||||
|
if name == "" || version == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkgs = append(pkgs, newDenoNpmPackage(reader.Location, name, version, pkgMeta))
|
||||||
|
}
|
||||||
|
|
||||||
|
for rawURL, integrity := range lock.Remote {
|
||||||
|
name, version := parseDenoRemoteURL(rawURL)
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pkgs = append(pkgs, newDenoRemotePackage(reader.Location, name, version, rawURL, integrity))
|
||||||
|
}
|
||||||
|
|
||||||
|
pkg.Sort(pkgs)
|
||||||
|
|
||||||
|
return pkgs, dependency.Resolve(denoLockDependencySpecifier, pkgs), unknown.IfEmptyf(pkgs, "unable to determine packages")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDenoJsrNameVersion(nameVersion string) (name, version string) {
|
||||||
|
idx := strings.LastIndex(nameVersion, "@")
|
||||||
|
if idx <= 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
return nameVersion[:idx], nameVersion[idx+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDenoNpmNameVersion(nameVersion string) (name, version string) {
|
||||||
|
if strings.HasPrefix(nameVersion, "@") {
|
||||||
|
rest := nameVersion[1:]
|
||||||
|
idx := strings.LastIndex(rest, "@")
|
||||||
|
if idx <= 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
return nameVersion[:idx+1], rest[idx+1:]
|
||||||
|
}
|
||||||
|
idx := strings.LastIndex(nameVersion, "@")
|
||||||
|
if idx <= 0 {
|
||||||
|
return "", ""
|
||||||
|
}
|
||||||
|
return nameVersion[:idx], nameVersion[idx+1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDenoJsrPackage(location file.Location, name, version string, meta denoJsrPackage) pkg.Package {
|
||||||
|
p := pkg.Package{
|
||||||
|
Name: name,
|
||||||
|
Version: version,
|
||||||
|
Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||||
|
PURL: denoJsrPackageURL(name, version),
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.DenoLockEntry{
|
||||||
|
Integrity: meta.Integrity,
|
||||||
|
Dependencies: meta.Dependencies,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p.SetID()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDenoNpmPackage(location file.Location, name, version string, meta denoNpmPackage) pkg.Package {
|
||||||
|
p := pkg.Package{
|
||||||
|
Name: name,
|
||||||
|
Version: version,
|
||||||
|
Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||||
|
PURL: denoNpmPackageURL(name, version),
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.NpmPackageLockEntry{
|
||||||
|
Integrity: meta.Integrity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p.SetID()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDenoRemotePackage(location file.Location, name, version, rawURL, integrity string) pkg.Package {
|
||||||
|
p := pkg.Package{
|
||||||
|
Name: name,
|
||||||
|
Version: version,
|
||||||
|
Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
|
||||||
|
PURL: denoRemotePackageURL(name, version, rawURL),
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.DenoRemoteLockEntry{
|
||||||
|
URL: rawURL,
|
||||||
|
Integrity: integrity,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
p.SetID()
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDenoRemoteURL(rawURL string) (name, version string) {
|
||||||
|
rawURL = strings.TrimPrefix(rawURL, "https://")
|
||||||
|
rawURL = strings.TrimPrefix(rawURL, "http://")
|
||||||
|
|
||||||
|
atIdx := strings.Index(rawURL, "@")
|
||||||
|
if atIdx == -1 {
|
||||||
|
slashIdx := strings.Index(rawURL, "/")
|
||||||
|
if slashIdx == -1 {
|
||||||
|
return rawURL, ""
|
||||||
|
}
|
||||||
|
return rawURL[:slashIdx], ""
|
||||||
|
}
|
||||||
|
|
||||||
|
name = rawURL[:atIdx]
|
||||||
|
|
||||||
|
rest := rawURL[atIdx+1:]
|
||||||
|
slashIdx := strings.Index(rest, "/")
|
||||||
|
if slashIdx == -1 {
|
||||||
|
version = rest
|
||||||
|
} else {
|
||||||
|
version = rest[:slashIdx]
|
||||||
|
}
|
||||||
|
|
||||||
|
return name, version
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractRepositoryBase(rawURL string) string {
|
||||||
|
u, err := url.Parse(rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s://%s", u.Scheme, u.Host)
|
||||||
|
}
|
||||||
|
|
||||||
|
func denoRemotePackageURL(name, version, rawURL string) string {
|
||||||
|
repositoryURL := extractRepositoryBase(rawURL)
|
||||||
|
var qualifiers packageurl.Qualifiers
|
||||||
|
if repositoryURL != "" {
|
||||||
|
qualifiers = packageurl.Qualifiers{{Key: "repository_url", Value: repositoryURL}}
|
||||||
|
}
|
||||||
|
|
||||||
|
return packageurl.NewPackageURL(
|
||||||
|
packageurl.TypeNPM,
|
||||||
|
"",
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
qualifiers,
|
||||||
|
"",
|
||||||
|
).ToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func denoJsrPackageURL(name, version string) string {
|
||||||
|
var namespace string
|
||||||
|
fields := strings.SplitN(name, "/", 2)
|
||||||
|
if len(fields) > 1 {
|
||||||
|
namespace = fields[0]
|
||||||
|
name = fields[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return packageurl.NewPackageURL(
|
||||||
|
packageurl.TypeNPM,
|
||||||
|
namespace,
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
packageurl.Qualifiers{{Key: "repository_url", Value: "https://jsr.io"}},
|
||||||
|
"",
|
||||||
|
).ToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func denoNpmPackageURL(name, version string) string {
|
||||||
|
var namespace string
|
||||||
|
fields := strings.SplitN(name, "/", 2)
|
||||||
|
if len(fields) > 1 {
|
||||||
|
namespace = fields[0]
|
||||||
|
name = fields[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return packageurl.NewPackageURL(
|
||||||
|
packageurl.TypeNPM,
|
||||||
|
namespace,
|
||||||
|
name,
|
||||||
|
version,
|
||||||
|
nil,
|
||||||
|
"",
|
||||||
|
).ToString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func denoLockDependencySpecifier(p pkg.Package) dependency.Specification {
|
||||||
|
meta, ok := p.Metadata.(pkg.DenoLockEntry)
|
||||||
|
if !ok {
|
||||||
|
return dependency.Specification{}
|
||||||
|
}
|
||||||
|
|
||||||
|
provides := []string{p.Name}
|
||||||
|
var requires []string
|
||||||
|
|
||||||
|
for _, dep := range meta.Dependencies {
|
||||||
|
name := parseDenoDependencyName(dep)
|
||||||
|
if name != "" {
|
||||||
|
requires = append(requires, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dependency.Specification{
|
||||||
|
ProvidesRequires: dependency.ProvidesRequires{
|
||||||
|
Provides: provides,
|
||||||
|
Requires: requires,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDenoDependencyName(dep string) string {
|
||||||
|
if strings.HasPrefix(dep, "jsr:") {
|
||||||
|
dep = strings.TrimPrefix(dep, "jsr:")
|
||||||
|
} else if strings.HasPrefix(dep, "npm:") {
|
||||||
|
dep = strings.TrimPrefix(dep, "npm:")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(dep, "@") {
|
||||||
|
rest := dep[1:]
|
||||||
|
atIdx := strings.Index(rest, "@")
|
||||||
|
if atIdx > 0 {
|
||||||
|
return dep[:atIdx+1]
|
||||||
|
}
|
||||||
|
return dep
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := strings.Index(dep, "@")
|
||||||
|
if idx > 0 {
|
||||||
|
return dep[:idx]
|
||||||
|
}
|
||||||
|
return dep
|
||||||
|
}
|
||||||
83
syft/pkg/cataloger/javascript/parse_deno_lock_test.go
Normal file
83
syft/pkg/cataloger/javascript/parse_deno_lock_test.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package javascript
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/anchore/syft/syft/artifact"
|
||||||
|
"github.com/anchore/syft/syft/file"
|
||||||
|
"github.com/anchore/syft/syft/pkg"
|
||||||
|
"github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseDenoLock(t *testing.T) {
|
||||||
|
fixture := "test-fixtures/deno/deno.lock"
|
||||||
|
|
||||||
|
expectedPkgs := []pkg.Package{
|
||||||
|
{
|
||||||
|
Name: "@std/bytes",
|
||||||
|
Version: "1.0.2",
|
||||||
|
PURL: "pkg:npm/%40std/bytes@1.0.2?repository_url=https%3A%2F%2Fjsr.io",
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.DenoLockEntry{
|
||||||
|
Integrity: "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "@std/encoding",
|
||||||
|
Version: "1.0.5",
|
||||||
|
PURL: "pkg:npm/%40std/encoding@1.0.5?repository_url=https%3A%2F%2Fjsr.io",
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.DenoLockEntry{
|
||||||
|
Integrity: "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04",
|
||||||
|
Dependencies: []string{"jsr:@std/bytes@^1.0.0"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "chalk",
|
||||||
|
Version: "5.3.0",
|
||||||
|
PURL: "pkg:npm/chalk@5.3.0",
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.NpmPackageLockEntry{
|
||||||
|
Integrity: "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "deno.land/std",
|
||||||
|
Version: "0.140.0",
|
||||||
|
PURL: "pkg:npm/deno.land%2Fstd@0.140.0?repository_url=https%3A%2F%2Fdeno.land",
|
||||||
|
Language: pkg.JavaScript,
|
||||||
|
Type: pkg.NpmPkg,
|
||||||
|
Metadata: pkg.DenoRemoteLockEntry{
|
||||||
|
URL: "https://deno.land/std@0.140.0/path/mod.ts",
|
||||||
|
Integrity: "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range expectedPkgs {
|
||||||
|
expectedPkgs[i].Locations.Add(file.NewLocation(fixture))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @std/encoding depends => @std/bytes
|
||||||
|
expectedRelationships := []artifact.Relationship{
|
||||||
|
{
|
||||||
|
From: expectedPkgs[0], // @std/bytes main
|
||||||
|
To: expectedPkgs[1], // @std/encoding dep
|
||||||
|
Type: artifact.DependencyOfRelationship,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
adapter := newGenericDenoLockAdapter(DefaultCatalogerConfig())
|
||||||
|
pkgtest.TestFileParser(t, fixture, adapter.parseDenoLock, expectedPkgs, expectedRelationships)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_corruptDenoLock(t *testing.T) {
|
||||||
|
adapter := newGenericDenoLockAdapter(DefaultCatalogerConfig())
|
||||||
|
pkgtest.NewCatalogTester().
|
||||||
|
FromFile(t, "test-fixtures/deno/corrupt/deno.lock").
|
||||||
|
WithError().
|
||||||
|
TestParser(t, adapter.parseDenoLock)
|
||||||
|
}
|
||||||
4
syft/pkg/cataloger/javascript/test-fixtures/deno/corrupt/deno.lock
generated
Normal file
4
syft/pkg/cataloger/javascript/test-fixtures/deno/corrupt/deno.lock
generated
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"version": "4",
|
||||||
|
this is not valid json
|
||||||
|
}
|
||||||
27
syft/pkg/cataloger/javascript/test-fixtures/deno/deno.lock
generated
Normal file
27
syft/pkg/cataloger/javascript/test-fixtures/deno/deno.lock
generated
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"version": "4",
|
||||||
|
"specifiers": {
|
||||||
|
"jsr:@std/bytes@^1.0.0": "1.0.2",
|
||||||
|
"jsr:@std/encoding@^1.0.0": "1.0.5",
|
||||||
|
"npm:chalk@5": "5.3.0"
|
||||||
|
},
|
||||||
|
"jsr": {
|
||||||
|
"@std/bytes@1.0.2": {
|
||||||
|
"integrity": "fbdee322bbd8c599a6af186a1603b3355e59a5fb1baa139f8f4c3c9a1b3e3d57"
|
||||||
|
},
|
||||||
|
"@std/encoding@1.0.5": {
|
||||||
|
"integrity": "ecf363d4fc25bd85bd915ff6733a7e79b67e0e7806334af15f4645c569fefc04",
|
||||||
|
"dependencies": [
|
||||||
|
"jsr:@std/bytes@^1.0.0"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"npm": {
|
||||||
|
"chalk@5.3.0": {
|
||||||
|
"integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"remote": {
|
||||||
|
"https://deno.land/std@0.140.0/path/mod.ts": "d3e68d0abb393fb0bf94a6d07c46ec31dc755b544b13144dee931d8d5f06a52d"
|
||||||
|
}
|
||||||
|
}
|
||||||
3
syft/pkg/cataloger/javascript/testdata/glob-paths/src/deno.lock
generated
vendored
Normal file
3
syft/pkg/cataloger/javascript/testdata/glob-paths/src/deno.lock
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"version": "4"
|
||||||
|
}
|
||||||
19
syft/pkg/deno.go
Normal file
19
syft/pkg/deno.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package pkg
|
||||||
|
|
||||||
|
// DenoLockEntry is a struct that rep a single entry found in the "packages" section of a Deno deno.lock file
|
||||||
|
type DenoLockEntry struct {
|
||||||
|
// Integrity is the crpto hash of the package content for verification
|
||||||
|
Integrity string `mapstructure:"integrity" json:"integrity"`
|
||||||
|
|
||||||
|
// Dependencies is the list of package specifiers that this package depends on
|
||||||
|
Dependencies []string `mapstructure:"dependencies" json:"dependencies"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DenoRemoteLockEntry is a struct that rep a single entry found in the "remote" section of a Deno deno.lock file
|
||||||
|
type DenoRemoteLockEntry struct {
|
||||||
|
// URL is the remote URL from which the module fetcef
|
||||||
|
URL string `mapstructure:"url" json:"url"`
|
||||||
|
|
||||||
|
// Integrity is the crpto hash of the package content for verification
|
||||||
|
Integrity string `mapstructure:"integrity" json:"integrity"`
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user