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 }