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 }