github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/python/parse_pipfile_lock.go (about)

     1  package python
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"strings"
    10  
    11  	"github.com/anchore/syft/syft/artifact"
    12  	"github.com/anchore/syft/syft/file"
    13  	"github.com/anchore/syft/syft/pkg"
    14  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    15  )
    16  
    17  type pipfileLock struct {
    18  	Meta struct {
    19  		Hash struct {
    20  			Sha256 string `json:"sha256"`
    21  		} `json:"hash"`
    22  		PipfileSpec int `json:"pipfile-spec"`
    23  		Requires    struct {
    24  			PythonVersion string `json:"python_version"`
    25  		} `json:"requires"`
    26  		Sources []struct {
    27  			Name      string `json:"name"`
    28  			URL       string `json:"url"`
    29  			VerifySsl bool   `json:"verify_ssl"`
    30  		} `json:"sources"`
    31  	} `json:"_meta"`
    32  	Default map[string]pipfileLockDependency `json:"default"`
    33  	Develop map[string]pipfileLockDependency `json:"develop"`
    34  }
    35  
    36  type pipfileLockDependency struct {
    37  	Hashes  []string `json:"hashes"`
    38  	Version string   `json:"version"`
    39  	Index   string   `json:"index"`
    40  }
    41  
    42  var _ generic.Parser = parsePipfileLock
    43  
    44  // parsePipfileLock is a parser function for Pipfile.lock contents, returning "Default" python packages discovered.
    45  func parsePipfileLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    46  	pkgs := make([]pkg.Package, 0)
    47  	dec := json.NewDecoder(reader)
    48  
    49  	for {
    50  		var lock pipfileLock
    51  		if err := dec.Decode(&lock); errors.Is(err, io.EOF) {
    52  			break
    53  		} else if err != nil {
    54  			return nil, nil, fmt.Errorf("failed to parse Pipfile.lock file: %w", err)
    55  		}
    56  		sourcesMap := map[string]string{}
    57  		for _, source := range lock.Meta.Sources {
    58  			sourcesMap[source.Name] = source.URL
    59  		}
    60  		for name, pkgMeta := range lock.Default {
    61  			var index string
    62  			if pkgMeta.Index != "" {
    63  				index = sourcesMap[pkgMeta.Index]
    64  			} else {
    65  				// https://pipenv.pypa.io/en/latest/indexes.html
    66  				index = "https://pypi.org/simple"
    67  			}
    68  			version := strings.TrimPrefix(pkgMeta.Version, "==")
    69  			pkgs = append(pkgs, newPackageForIndexWithMetadata(name, version, pkg.PythonPipfileLockEntry{Index: index, Hashes: pkgMeta.Hashes}, reader.Location))
    70  		}
    71  	}
    72  
    73  	pkg.Sort(pkgs)
    74  
    75  	return pkgs, nil, nil
    76  }