Files
syft/syft/pkg/cataloger/php/parse_pecl_pear.go
T
Will Murphy e38851143e chore: centralize temp files and prefer streaming IO (#4668)
* chore: centralize temp files and prefer streaming IO

Catalogers that create temp files ad-hoc can easily forget cleanup,
leaking files on disk. Similarly, io.ReadAll is convenient but risks
OOM on large or malicious inputs.

Introduce internal/tmpdir to manage all cataloger temp storage under
a single root directory with automatic cleanup. Prefer streaming
parsers (bufio.Scanner, json/yaml.NewDecoder, io.LimitReader) over
buffering entire inputs into memory. Add ruleguard rules to enforce
both practices going forward.

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

* chore: go back to old release parsing

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

* simplify to limit reader in version check

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

* chore: regex change postponed

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

* simplify supplement release to limitreader

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>

---------

Signed-off-by: Will Murphy <willmurphyscode@users.noreply.github.com>
2026-03-18 10:53:51 -04:00

113 lines
2.8 KiB
Go

package php
import (
"context"
"fmt"
"io"
"github.com/elliotchance/phpserialize"
"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"
)
type peclPearData struct {
Name string
Channel string
Version string
License []string
}
func (p *peclPearData) ToPear() pkg.PhpPearEntry {
return pkg.PhpPearEntry{
Name: p.Name,
Channel: p.Channel,
Version: p.Version,
License: p.License,
}
}
func (p *peclPearData) ToPecl() pkg.PhpPeclEntry { //nolint:staticcheck
return pkg.PhpPeclEntry(p.ToPear()) //nolint:staticcheck
}
func parsePecl(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
m, err := parsePeclPearSerialized(reader)
if err != nil {
return nil, nil, err
}
if m == nil {
return nil, nil, unknown.New(reader.Location, fmt.Errorf("no pecl package found"))
}
return []pkg.Package{newPeclPackage(ctx, *m, reader.Location)}, nil, nil
}
func parsePear(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
m, err := parsePeclPearSerialized(reader)
if err != nil {
return nil, nil, err
}
if m == nil {
return nil, nil, unknown.New(reader.Location, fmt.Errorf("no pear package found"))
}
return []pkg.Package{newPearPackage(ctx, *m, reader.Location)}, nil, nil
}
// parsePeclPearSerialized is a parser function for Pear metadata contents, returning "Default" php packages discovered.
func parsePeclPearSerialized(reader file.LocationReadCloser) (*peclPearData, error) {
data, err := io.ReadAll(reader) //nolint:gocritic // phpserialize requires []byte
if err != nil {
return nil, fmt.Errorf("failed to read file: %w", err)
}
metadata, err := phpserialize.UnmarshalAssociativeArray(
data,
)
if err != nil {
return nil, fmt.Errorf("failed to parse pear metadata file: %w", err)
}
name, ok := metadata["name"].(string)
if !ok {
return nil, fmt.Errorf("failed to parse pear package name: %w", err)
}
channel, ok := metadata["channel"].(string)
if !ok {
// this could be the v5 format
channel = ""
}
version := readStruct(metadata, "version", "release")
license := readStruct(metadata, "license", "_content")
return &peclPearData{
Name: name,
Channel: channel,
Version: version,
License: []string{
license,
},
}, nil
}
func readStruct(metadata any, fields ...string) string {
if len(fields) > 0 {
value, ok := metadata.(map[any]any)
if !ok {
return ""
}
return readStruct(value[fields[0]], fields[1:]...)
}
value, ok := metadata.(string)
if !ok {
return ""
}
return value
}