github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/pkg/cataloger/golang/parse_go_mod.go (about)

     1  package golang
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"io"
     7  	"sort"
     8  	"strings"
     9  
    10  	"github.com/nextlinux/gosbom/gosbom/artifact"
    11  	"github.com/nextlinux/gosbom/gosbom/file"
    12  	"github.com/nextlinux/gosbom/gosbom/pkg"
    13  	"github.com/nextlinux/gosbom/gosbom/pkg/cataloger/generic"
    14  	"github.com/nextlinux/gosbom/internal/log"
    15  	"golang.org/x/mod/modfile"
    16  )
    17  
    18  type goModCataloger struct {
    19  	licenses goLicenses
    20  }
    21  
    22  // parseGoModFile takes a go.mod and lists all packages discovered.
    23  //
    24  //nolint:funlen
    25  func (c *goModCataloger) parseGoModFile(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    26  	packages := make(map[string]pkg.Package)
    27  
    28  	contents, err := io.ReadAll(reader)
    29  	if err != nil {
    30  		return nil, nil, fmt.Errorf("failed to read go module: %w", err)
    31  	}
    32  
    33  	f, err := modfile.Parse(reader.RealPath, contents, nil)
    34  	if err != nil {
    35  		return nil, nil, fmt.Errorf("failed to parse go module: %w", err)
    36  	}
    37  
    38  	digests, err := parseGoSumFile(resolver, reader)
    39  	if err != nil {
    40  		log.Debugf("unable to get go.sum: %v", err)
    41  	}
    42  
    43  	for _, m := range f.Require {
    44  		licenses, err := c.licenses.getLicenses(resolver, m.Mod.Path, m.Mod.Version)
    45  		if err != nil {
    46  			log.Tracef("error getting licenses for package: %s %v", m.Mod.Path, err)
    47  		}
    48  
    49  		packages[m.Mod.Path] = pkg.Package{
    50  			Name:         m.Mod.Path,
    51  			Version:      m.Mod.Version,
    52  			Licenses:     pkg.NewLicenseSet(licenses...),
    53  			Locations:    file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    54  			PURL:         packageURL(m.Mod.Path, m.Mod.Version),
    55  			Language:     pkg.Go,
    56  			Type:         pkg.GoModulePkg,
    57  			MetadataType: pkg.GolangModMetadataType,
    58  			Metadata: pkg.GolangModMetadata{
    59  				H1Digest: digests[fmt.Sprintf("%s %s", m.Mod.Path, m.Mod.Version)],
    60  			},
    61  		}
    62  	}
    63  
    64  	// remove any old packages and replace with new ones...
    65  	for _, m := range f.Replace {
    66  		licenses, err := c.licenses.getLicenses(resolver, m.New.Path, m.New.Version)
    67  		if err != nil {
    68  			log.Tracef("error getting licenses for package: %s %v", m.New.Path, err)
    69  		}
    70  
    71  		packages[m.New.Path] = pkg.Package{
    72  			Name:         m.New.Path,
    73  			Version:      m.New.Version,
    74  			Licenses:     pkg.NewLicenseSet(licenses...),
    75  			Locations:    file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    76  			PURL:         packageURL(m.New.Path, m.New.Version),
    77  			Language:     pkg.Go,
    78  			Type:         pkg.GoModulePkg,
    79  			MetadataType: pkg.GolangModMetadataType,
    80  			Metadata: pkg.GolangModMetadata{
    81  				H1Digest: digests[fmt.Sprintf("%s %s", m.New.Path, m.New.Version)],
    82  			},
    83  		}
    84  	}
    85  
    86  	// remove any packages from the exclude fields
    87  	for _, m := range f.Exclude {
    88  		delete(packages, m.Mod.Path)
    89  	}
    90  
    91  	pkgsSlice := make([]pkg.Package, len(packages))
    92  	idx := 0
    93  	for _, p := range packages {
    94  		p.SetID()
    95  		pkgsSlice[idx] = p
    96  		idx++
    97  	}
    98  
    99  	sort.SliceStable(pkgsSlice, func(i, j int) bool {
   100  		return pkgsSlice[i].Name < pkgsSlice[j].Name
   101  	})
   102  
   103  	return pkgsSlice, nil, nil
   104  }
   105  
   106  func parseGoSumFile(resolver file.Resolver, reader file.LocationReadCloser) (map[string]string, error) {
   107  	out := map[string]string{}
   108  
   109  	if resolver == nil {
   110  		return out, fmt.Errorf("no resolver provided")
   111  	}
   112  
   113  	goSumPath := strings.TrimSuffix(reader.Location.RealPath, ".mod") + ".sum"
   114  	goSumLocation := resolver.RelativeFileByPath(reader.Location, goSumPath)
   115  	if goSumLocation == nil {
   116  		return nil, fmt.Errorf("unable to resolve: %s", goSumPath)
   117  	}
   118  	contents, err := resolver.FileContentsByLocation(*goSumLocation)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	// go.sum has the format like:
   124  	// github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
   125  	// github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
   126  	// github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
   127  	scanner := bufio.NewScanner(contents)
   128  	// optionally, resize scanner's capacity for lines over 64K, see next example
   129  	for scanner.Scan() {
   130  		line := scanner.Text()
   131  		parts := strings.Split(line, " ")
   132  		if len(parts) < 3 {
   133  			continue
   134  		}
   135  		nameVersion := fmt.Sprintf("%s %s", parts[0], parts[1])
   136  		hash := parts[2]
   137  		out[nameVersion] = hash
   138  	}
   139  
   140  	return out, nil
   141  }