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  }