github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/pkg/cataloger/swift/parse_package_resolved.go (about)

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