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  }