github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/ocaml/parse_opam.go (about) 1 package ocaml 2 3 import ( 4 "context" 5 "encoding/json" 6 "io" 7 "path" 8 "regexp" 9 "strings" 10 11 "github.com/anchore/syft/internal/log" 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 ) 17 18 //nolint:funlen 19 func parseOpamPackage(ctx context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 20 var pkgs []pkg.Package 21 22 opamVersionRe := regexp.MustCompile(`(?m)opam-version:\s*"[0-9]+\.[0-9]+"`) 23 versionRe := regexp.MustCompile(`(?m)^version:\s*"(?P<version>[^"]*)"`) 24 licenseRe := regexp.MustCompile(`(?m)^license:\s*(?P<license>(?:"[^"]*")|(?:\[[^\]]*\]))`) 25 homepageRe := regexp.MustCompile(`(?m)homepage:\s*"(?P<url>[^"]+)"`) 26 urlRe := regexp.MustCompile(`(?m)url\s*{(?P<url>[^}]+)}`) 27 28 data, err := io.ReadAll(reader) 29 if err != nil { 30 log.WithFields("error", err).Trace("unable to read opam package") 31 return nil, nil, nil 32 } 33 34 if opamVersionRe.FindSubmatch(data) == nil { 35 log.WithFields("warning", err).Trace("opam version not found") 36 return nil, nil, nil 37 } 38 39 // If name is inferred from file name/path 40 var name, version string 41 var licenses []string 42 loc := reader.AccessPath 43 dir, file := path.Split(loc) 44 45 if file == "opam" { 46 // folder name is the package name and version 47 s := strings.SplitN(path.Base(dir), ".", 2) 48 name = s[0] 49 50 if len(s) > 1 { 51 version = s[1] 52 } 53 } else { 54 // filename is the package name and version is in the content 55 name = strings.Replace(file, ".opam", "", 1) 56 57 m := versionRe.FindSubmatch(data) 58 59 if m != nil { 60 version = string(m[1]) 61 } 62 } 63 64 entry := pkg.OpamPackage{ 65 Name: name, 66 Version: version, 67 } 68 69 licenseMatch := licenseRe.FindSubmatch(data) 70 if licenseMatch != nil { 71 licenses = parseLicenses(string(licenseMatch[1])) 72 73 entry.Licenses = licenses 74 } 75 76 urlMatch := urlRe.FindSubmatch(data) 77 if urlMatch != nil { 78 url, checksums := parseURL(urlMatch[1]) 79 80 if url != "" { 81 entry.URL = url 82 } 83 84 if checksums != nil { 85 entry.Checksums = checksums 86 } 87 } 88 89 homepageMatch := homepageRe.FindSubmatch(data) 90 if homepageMatch != nil { 91 entry.Homepage = string(homepageMatch[1]) 92 } 93 94 pkgs = append( 95 pkgs, 96 newOpamPackage( 97 ctx, 98 entry, 99 reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), 100 ), 101 ) 102 103 return pkgs, nil, nil 104 } 105 106 func parseLicenses(licensesStr string) []string { 107 licenses := []string{} 108 109 if licensesStr[:1] == `"` { 110 content := licensesStr[1 : len(licensesStr)-1] 111 licenses = append(licenses, content) 112 } else { 113 var d []string 114 err := json.Unmarshal([]byte(licensesStr), &d) 115 116 if err == nil { 117 licenses = append(licenses, d...) 118 } 119 } 120 121 return licenses 122 } 123 124 func parseURL(data []byte) (string, []string) { 125 urlRe := regexp.MustCompile(`(?m)src:\s*"(?P<url>.*)"`) 126 checksumsRe := regexp.MustCompile(`(?m)checksum:\s*("[^"]*"|\[\s*((?:"[^"]*"\s*)+)\])`) 127 128 urlMatch := urlRe.FindSubmatch(data) 129 if urlMatch == nil { 130 return "", nil 131 } 132 133 url := urlMatch[1] 134 135 var checksum []string 136 checksumMatch := checksumsRe.FindSubmatch(data) 137 if checksumMatch != nil { 138 var fields []string 139 if checksumMatch[2] != nil { 140 fields = strings.Fields(string(checksumMatch[2])) 141 } else { 142 fields = strings.Fields(string(checksumMatch[1])) 143 } 144 145 for _, f := range fields { 146 checksum = append(checksum, strings.ReplaceAll(f, `"`, "")) 147 } 148 } 149 150 return string(url), checksum 151 }