github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/pkg/cataloger/dotnet/parse_dotnet_portable_executable.go (about) 1 package dotnet 2 3 import ( 4 "fmt" 5 "io" 6 "regexp" 7 "strings" 8 9 "github.com/saferwall/pe" 10 11 "github.com/anchore/packageurl-go" 12 "github.com/anchore/syft/syft/artifact" 13 "github.com/anchore/syft/syft/file" 14 "github.com/anchore/syft/syft/pkg" 15 "github.com/anchore/syft/syft/pkg/cataloger/generic" 16 "github.com/lineaje-labs/syft/internal/log" 17 ) 18 19 var _ generic.Parser = parseDotnetPortableExecutable 20 21 func parseDotnetPortableExecutable( 22 _ file.Resolver, _ *generic.Environment, f file.LocationReadCloser, 23 ) ([]pkg.Package, []artifact.Relationship, error) { 24 by, err := io.ReadAll(f) 25 if err != nil { 26 return nil, nil, fmt.Errorf("unable to read file: %w", err) 27 } 28 29 peFile, err := pe.NewBytes(by, &pe.Options{}) 30 if err != nil { 31 // TODO: known-unknown 32 log.Tracef("unable to create PE instance for file '%s': %v", f.RealPath, err) 33 return nil, nil, nil 34 } 35 36 err = peFile.Parse() 37 if err != nil { 38 // TODO: known-unknown 39 log.Tracef("unable to parse PE file '%s': %v", f.RealPath, err) 40 return nil, nil, nil 41 } 42 43 versionResources, err := peFile.ParseVersionResources() 44 if err != nil { 45 // TODO: known-unknown 46 log.Tracef("unable to parse version resources in PE file: %s: %v", f.RealPath, err) 47 return nil, nil, nil 48 } 49 50 dotNetPkg, err := buildDotNetPackage(versionResources, f) 51 if err != nil { 52 // TODO: known-unknown 53 log.Tracef("unable to build dotnet package: %v", err) 54 return nil, nil, nil 55 } 56 57 return []pkg.Package{dotNetPkg}, nil, nil 58 } 59 60 func buildDotNetPackage(versionResources map[string]string, f file.LocationReadCloser) (dnpkg pkg.Package, err error) { 61 name := findName(versionResources) 62 if name == "" { 63 return dnpkg, fmt.Errorf("unable to find FileDescription, or ProductName in PE file: %s", f.RealPath) 64 } 65 66 version := findVersion(versionResources) 67 if strings.TrimSpace(version) == "" { 68 return dnpkg, fmt.Errorf("unable to find FileVersion in PE file: %s", f.RealPath) 69 } 70 71 purl := packageurl.NewPackageURL( 72 packageurl.TypeNuget, // See explanation in syft/pkg/cataloger/dotnet/package.go as to why this was chosen. 73 "", 74 name, 75 version, 76 nil, 77 "", 78 ).ToString() 79 80 metadata := pkg.DotnetPortableExecutableEntry{ 81 AssemblyVersion: versionResources["Assembly Version"], 82 LegalCopyright: versionResources["LegalCopyright"], 83 Comments: versionResources["Comments"], 84 InternalName: versionResources["InternalName"], 85 CompanyName: versionResources["CompanyName"], 86 ProductName: versionResources["ProductName"], 87 ProductVersion: versionResources["ProductVersion"], 88 } 89 90 dnpkg = pkg.Package{ 91 Name: name, 92 Version: version, 93 Locations: file.NewLocationSet(f.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 94 Type: pkg.DotnetPkg, 95 Language: pkg.Dotnet, 96 PURL: purl, 97 Metadata: metadata, 98 } 99 100 dnpkg.SetID() 101 102 return dnpkg, nil 103 } 104 105 func findVersion(versionResources map[string]string) string { 106 for _, key := range []string{"FileVersion"} { 107 if version, ok := versionResources[key]; ok { 108 if strings.TrimSpace(version) == "" { 109 continue 110 } 111 fields := strings.Fields(version) 112 if len(fields) > 0 { 113 return fields[0] 114 } 115 } 116 } 117 return "" 118 } 119 120 func findName(versionResources map[string]string) string { 121 for _, key := range []string{"FileDescription", "ProductName"} { 122 if name, ok := versionResources[key]; ok { 123 if strings.TrimSpace(name) == "" { 124 continue 125 } 126 trimmed := strings.TrimSpace(name) 127 return regexp.MustCompile(`[^a-zA-Z0-9.]+`).ReplaceAllString(trimmed, "") 128 } 129 } 130 return "" 131 }