github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/swift/parse_package_resolved.go (about)

     1  package swift
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/anchore/syft/internal/log"
    11  	"github.com/anchore/syft/syft/artifact"
    12  	"github.com/anchore/syft/syft/file"
    13  	"github.com/anchore/syft/syft/pkg"
    14  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    15  )
    16  
    17  var _ generic.Parser = parsePackageResolved
    18  
    19  // swift package manager has two versions (1 and 2) of the resolved files, the types below describes the serialization strategies for each version
    20  // with its suffix indicating which version its specific to.
    21  
    22  type packageResolvedV1 struct {
    23  	PackageObject packageObjectV1 `json:"object"`
    24  	Version       int             `json:"version"`
    25  }
    26  
    27  type packageObjectV1 struct {
    28  	Pins []packagePinsV1
    29  }
    30  
    31  type packagePinsV1 struct {
    32  	Name          string       `json:"package"`
    33  	RepositoryURL string       `json:"repositoryURL"`
    34  	State         packageState `json:"state"`
    35  }
    36  
    37  type packageResolvedV2 struct {
    38  	Pins []packagePinsV2
    39  }
    40  
    41  type packagePinsV2 struct {
    42  	Identity string       `json:"identity"`
    43  	Kind     string       `json:"kind"`
    44  	Location string       `json:"location"`
    45  	State    packageState `json:"state"`
    46  }
    47  
    48  type packagePin struct {
    49  	Identity string
    50  	Location string
    51  	Revision string
    52  	Version  string
    53  }
    54  
    55  type packageState struct {
    56  	Revision string `json:"revision"`
    57  	Version  string `json:"version"`
    58  }
    59  
    60  // 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.
    61  func parsePackageResolved(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    62  	dec := json.NewDecoder(reader)
    63  	var packageResolvedData map[string]interface{}
    64  	for {
    65  		if err := dec.Decode(&packageResolvedData); errors.Is(err, io.EOF) {
    66  			break
    67  		} else if err != nil {
    68  			return nil, nil, fmt.Errorf("failed to parse Package.resolved file: %w", err)
    69  		}
    70  	}
    71  
    72  	if packageResolvedData["version"] == nil {
    73  		log.Trace("no version found in Package.resolved file, skipping")
    74  		return nil, nil, nil
    75  	}
    76  
    77  	version, ok := packageResolvedData["version"].(float64)
    78  	if !ok {
    79  		return nil, nil, fmt.Errorf("failed to parse Package.resolved file: version is not a number")
    80  	}
    81  
    82  	var pins, err = pinsForVersion(packageResolvedData, version)
    83  	if err != nil {
    84  		return nil, nil, err
    85  	}
    86  
    87  	var pkgs []pkg.Package
    88  	for _, pkgPin := range pins {
    89  		pkgs = append(
    90  			pkgs,
    91  			newSwiftPackageManagerPackage(
    92  				pkgPin.Identity,
    93  				pkgPin.Version,
    94  				pkgPin.Location,
    95  				pkgPin.Revision,
    96  				reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
    97  			),
    98  		)
    99  	}
   100  	return pkgs, nil, nil
   101  }
   102  
   103  func pinsForVersion(data map[string]interface{}, version float64) ([]packagePin, error) {
   104  	var genericPins []packagePin
   105  	switch version {
   106  	case 1:
   107  		t := packageResolvedV1{}
   108  		jsonString, err := json.Marshal(data)
   109  		if err != nil {
   110  			return nil, err
   111  		}
   112  		parseErr := json.Unmarshal(jsonString, &t)
   113  		if parseErr != nil {
   114  			return nil, parseErr
   115  		}
   116  		for _, pin := range t.PackageObject.Pins {
   117  			genericPins = append(genericPins, packagePin{
   118  				pin.Name,
   119  				pin.RepositoryURL,
   120  				pin.State.Revision,
   121  				pin.State.Version,
   122  			})
   123  		}
   124  	case 2:
   125  		t := packageResolvedV2{}
   126  		jsonString, err := json.Marshal(data)
   127  		if err != nil {
   128  			return nil, err
   129  		}
   130  		parseErr := json.Unmarshal(jsonString, &t)
   131  		if parseErr != nil {
   132  			return nil, parseErr
   133  		}
   134  		for _, pin := range t.Pins {
   135  			genericPins = append(genericPins, packagePin{
   136  				pin.Identity,
   137  				pin.Location,
   138  				pin.State.Revision,
   139  				pin.State.Version,
   140  			})
   141  		}
   142  	default:
   143  		return nil, fmt.Errorf("unknown swift package manager version, %f", version)
   144  	}
   145  	return genericPins, nil
   146  }