syft/syft/pkg/cataloger/swift/parse_package_resolved.go
anchore-actions-token-generator[bot] f11377fe30
chore(deps): update tools to latest versions (#3775)
---------
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Signed-off-by: Christopher Phillips <32073428+spiffcs@users.noreply.github.com>
Co-authored-by: spiffcs <32073428+spiffcs@users.noreply.github.com>
2025-04-03 17:35:26 +00:00

148 lines
3.8 KiB
Go

package swift
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"github.com/anchore/syft/internal/log"
"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"
)
var _ generic.Parser = parsePackageResolved
// swift package manager has two versions (1 and 2) of the resolved files, the types below describes the serialization strategies for each version
// with its suffix indicating which version its specific to.
type packageResolvedV1 struct {
PackageObject packageObjectV1 `json:"object"`
Version int `json:"version"`
}
type packageObjectV1 struct {
Pins []packagePinsV1
}
type packagePinsV1 struct {
Name string `json:"package"`
RepositoryURL string `json:"repositoryURL"`
State packageState `json:"state"`
}
type packageResolvedV2 struct {
Pins []packagePinsV2
}
type packagePinsV2 struct {
Identity string `json:"identity"`
Kind string `json:"kind"`
Location string `json:"location"`
State packageState `json:"state"`
}
type packagePin struct {
Identity string
Location string
Revision string
Version string
}
type packageState struct {
Revision string `json:"revision"`
Version string `json:"version"`
}
// parsePackageResolved is a parser for the contents of a Package.resolved file, which is generated by Xcode after it's resolved Swift Package Manger packages.
func parsePackageResolved(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
dec := json.NewDecoder(reader)
var packageResolvedData map[string]interface{}
for {
if err := dec.Decode(&packageResolvedData); errors.Is(err, io.EOF) {
break
} else if err != nil {
return nil, nil, fmt.Errorf("failed to parse Package.resolved file: %w", err)
}
}
if packageResolvedData["version"] == nil {
log.Trace("no version found in Package.resolved file, skipping")
return nil, nil, fmt.Errorf("no version found in Package.resolved file")
}
version, ok := packageResolvedData["version"].(float64)
if !ok {
return nil, nil, fmt.Errorf("failed to parse Package.resolved file: version is not a number")
}
var pins, err = pinsForVersion(packageResolvedData, version)
if err != nil {
return nil, nil, err
}
var pkgs []pkg.Package
for _, pkgPin := range pins {
pkgs = append(
pkgs,
newSwiftPackageManagerPackage(
pkgPin.Identity,
pkgPin.Version,
pkgPin.Location,
pkgPin.Revision,
reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
),
)
}
return pkgs, nil, unknown.IfEmptyf(pkgs, "unable to determine packages")
}
func pinsForVersion(data map[string]interface{}, version float64) ([]packagePin, error) {
var genericPins []packagePin
switch version {
case 1:
t := packageResolvedV1{}
jsonString, err := json.Marshal(data)
if err != nil {
return nil, err
}
parseErr := json.Unmarshal(jsonString, &t)
if parseErr != nil {
return nil, parseErr
}
for _, pin := range t.PackageObject.Pins {
genericPins = append(genericPins, packagePin{
pin.Name,
pin.RepositoryURL,
pin.State.Revision,
pin.State.Version,
})
}
case 2, 3:
t := packageResolvedV2{}
jsonString, err := json.Marshal(data)
if err != nil {
return nil, err
}
parseErr := json.Unmarshal(jsonString, &t)
if parseErr != nil {
return nil, parseErr
}
for _, pin := range t.Pins {
genericPins = append(genericPins, packagePin{
pin.Identity,
pin.Location,
pin.State.Revision,
pin.State.Version,
})
}
default:
return nil, fmt.Errorf("unknown swift package manager version, %f", version)
}
return genericPins, nil
}