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 }