github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/javascript/parse_yarn_lock.go (about)

     1  package javascript
     2  
     3  import (
     4  	"bufio"
     5  	"strings"
     6  
     7  	"github.com/anchore/syft/syft/artifact"
     8  	"github.com/anchore/syft/syft/file"
     9  	"github.com/anchore/syft/syft/pkg"
    10  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    11  	"github.com/anchore/syft/syft/pkg/cataloger/javascript/key"
    12  	yarnparse "github.com/anchore/syft/syft/pkg/cataloger/javascript/parser/yarn"
    13  )
    14  
    15  // integrity check
    16  var _ generic.Parser = parseYarnLock
    17  
    18  type yarnLockPackage struct {
    19  	Name         string
    20  	Version      string
    21  	Integrity    string
    22  	Resolved     string
    23  	Dependencies map[string]string
    24  }
    25  
    26  func parseYarnLock(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    27  	yarnMap := parseYarnLockFile(resolver, reader)
    28  	pkgs, _ := finalizeYarnLockWithoutPackageJSON(resolver, yarnMap, reader.Location)
    29  	return pkgs, nil, nil
    30  }
    31  
    32  func newYarnLockPackage(resolver file.Resolver, location file.Location, p *yarnLockPackage) pkg.Package {
    33  	if p == nil {
    34  		return pkg.Package{}
    35  	}
    36  
    37  	return finalizeLockPkg(
    38  		resolver,
    39  		location,
    40  		pkg.Package{
    41  			Name:         p.Name,
    42  			Version:      p.Version,
    43  			Locations:    file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    44  			PURL:         packageURL(p.Name, p.Version),
    45  			MetadataType: pkg.NpmPackageLockJSONMetadataType,
    46  			Language:     pkg.JavaScript,
    47  			Type:         pkg.NpmPkg,
    48  			Metadata: pkg.NpmPackageLockJSONMetadata{
    49  				Resolved:  p.Resolved,
    50  				Integrity: p.Integrity,
    51  			},
    52  		},
    53  	)
    54  }
    55  
    56  // parseYarnLockFile takes a yarn.lock file and returns a map of packages
    57  func parseYarnLockFile(_ file.Resolver, file file.LocationReadCloser) map[string]*yarnLockPackage {
    58  	/*
    59  		name@version[, name@version]:
    60  			version "xxx"
    61  			resolved "xxx"
    62  			integrity "xxx"
    63  			dependencies:
    64  				name "xxx"
    65  				name "xxx"
    66  	*/
    67  	lineNumber := 1
    68  	lockMap := map[string]*yarnLockPackage{}
    69  
    70  	scanner := bufio.NewScanner(file.ReadCloser)
    71  	scanner.Split(yarnparse.ScanBlocks)
    72  	for scanner.Scan() {
    73  		block := scanner.Bytes()
    74  		pkg, refVersions, newLine, err := parseYarnPkgBlock(block, lineNumber)
    75  		lineNumber = newLine + 2
    76  		if err != nil {
    77  			return nil
    78  		} else if pkg.Name == "" {
    79  			continue
    80  		}
    81  
    82  		for _, refVersion := range refVersions {
    83  			lockMap[refVersion] = &pkg
    84  		}
    85  	}
    86  
    87  	if err := scanner.Err(); err != nil {
    88  		return nil
    89  	}
    90  
    91  	return lockMap
    92  }
    93  
    94  // rootNameFromPath returns a "fake" root name of a package based on it
    95  // directory name. This is used when there is no package.json file
    96  // to create a root package.
    97  func rootNameFromPath(location file.Location) string {
    98  	splits := strings.Split(location.RealPath, "/")
    99  	if len(splits) < 2 {
   100  		return ""
   101  	}
   102  	return splits[len(splits)-2]
   103  }
   104  
   105  // finalizeYarnLockWithPackageJSON takes a yarn.lock file and a package.json file and returns a map of packages
   106  // nolint:funlen
   107  func finalizeYarnLockWithPackageJSON(resolver file.Resolver, pkgjson *packageJSON, yarnlock map[string]*yarnLockPackage, indexLocation file.Location) ([]pkg.Package, []artifact.Relationship) {
   108  	if pkgjson == nil {
   109  		return nil, nil
   110  	}
   111  
   112  	var pkgs []pkg.Package
   113  	var relationships []artifact.Relationship
   114  	var root pkg.Package
   115  	seenPkgMap := make(map[string]bool)
   116  
   117  	p := yarnLockPackage{
   118  		Name:    pkgjson.Name,
   119  		Version: pkgjson.Version,
   120  	}
   121  	root = newYarnLockPackage(resolver, indexLocation, &p)
   122  
   123  	for name, version := range pkgjson.Dependencies {
   124  		depPkg := yarnlock[key.NpmPackageKey(name, version)]
   125  		dep := newYarnLockPackage(resolver, indexLocation, depPkg)
   126  		rel := artifact.Relationship{
   127  			From: dep,
   128  			To:   root,
   129  			Type: artifact.DependencyOfRelationship,
   130  		}
   131  		relationships = append(relationships, rel)
   132  	}
   133  	for name, version := range pkgjson.DevDependencies {
   134  		depPkg := yarnlock[key.NpmPackageKey(name, version)]
   135  		dep := newYarnLockPackage(resolver, indexLocation, depPkg)
   136  		rel := artifact.Relationship{
   137  			From: dep,
   138  			To:   root,
   139  			Type: artifact.DependencyOfRelationship,
   140  		}
   141  		relationships = append(relationships, rel)
   142  	}
   143  	pkgs = append(pkgs, root)
   144  
   145  	// create packages
   146  	for _, lockPkg := range yarnlock {
   147  		if seenPkgMap[key.NpmPackageKey(lockPkg.Name, lockPkg.Version)] {
   148  			continue
   149  		}
   150  
   151  		pkg := newYarnLockPackage(resolver, indexLocation, lockPkg)
   152  		pkgs = append(pkgs, pkg)
   153  		seenPkgMap[key.NpmPackageKey(lockPkg.Name, lockPkg.Version)] = true
   154  	}
   155  
   156  	// create relationships
   157  	for _, lockPkg := range yarnlock {
   158  		pkg := newYarnLockPackage(resolver, indexLocation, lockPkg)
   159  
   160  		for name, version := range lockPkg.Dependencies {
   161  			dep := yarnlock[key.NpmPackageKey(name, version)]
   162  			depPkg := newYarnLockPackage(
   163  				resolver,
   164  				indexLocation,
   165  				dep,
   166  			)
   167  
   168  			rel := artifact.Relationship{
   169  				From: depPkg,
   170  				To:   pkg,
   171  				Type: artifact.DependencyOfRelationship,
   172  			}
   173  			relationships = append(relationships, rel)
   174  		}
   175  	}
   176  
   177  	pkg.Sort(pkgs)
   178  	pkg.SortRelationships(relationships)
   179  	return pkgs, relationships
   180  }
   181  
   182  // finalizeYarnLockWithoutPackageJSON takes a yarn.lock file and returns a map of packages
   183  func finalizeYarnLockWithoutPackageJSON(resolver file.Resolver, yarnlock map[string]*yarnLockPackage, indexLocation file.Location) ([]pkg.Package, []artifact.Relationship) {
   184  	var pkgs []pkg.Package
   185  	var relationships []artifact.Relationship
   186  	seenPkgMap := make(map[string]bool)
   187  
   188  	name := rootNameFromPath(indexLocation)
   189  	if name != "" {
   190  		p := yarnLockPackage{
   191  			Name:    name,
   192  			Version: "0.0.0",
   193  		}
   194  		root := newYarnLockPackage(resolver, indexLocation, &p)
   195  		pkgs = append(pkgs, root)
   196  	}
   197  
   198  	// create packages
   199  	for _, lockPkg := range yarnlock {
   200  		if seenPkgMap[key.NpmPackageKey(lockPkg.Name, lockPkg.Version)] {
   201  			continue
   202  		}
   203  
   204  		pkg := newYarnLockPackage(resolver, indexLocation, lockPkg)
   205  		pkgs = append(pkgs, pkg)
   206  		seenPkgMap[key.NpmPackageKey(lockPkg.Name, lockPkg.Version)] = true
   207  	}
   208  
   209  	// create relationships
   210  	for _, lockPkg := range yarnlock {
   211  		pkg := newYarnLockPackage(resolver, indexLocation, lockPkg)
   212  
   213  		for name, version := range lockPkg.Dependencies {
   214  			dep := yarnlock[key.NpmPackageKey(name, version)]
   215  			depPkg := newYarnLockPackage(
   216  				resolver,
   217  				indexLocation,
   218  				dep,
   219  			)
   220  
   221  			rel := artifact.Relationship{
   222  				From: depPkg,
   223  				To:   pkg,
   224  				Type: artifact.DependencyOfRelationship,
   225  			}
   226  			relationships = append(relationships, rel)
   227  		}
   228  	}
   229  
   230  	pkg.Sort(pkgs)
   231  	pkg.SortRelationships(relationships)
   232  	return pkgs, relationships
   233  }
   234  
   235  /*
   236  	parseYarnPkgBlock parses a yarn package block like this and return a yarnLockPackage struct
   237  	and refVersions which are "tslib@^2.1.0" and "tslib@^2.3.0" in this example
   238  
   239  "tslib@^2.1.0", "tslib@^2.3.0":
   240  
   241  	"integrity" "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
   242  	"resolved" "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz"
   243  	"version" "2.4.1"
   244  */
   245  func parseYarnPkgBlock(block []byte, lineNum int) (pkg yarnLockPackage, refVersions []string, newLine int, err error) {
   246  	pkgRef, lineNumber, err := yarnparse.ParseBlock(block, lineNum)
   247  	for _, pattern := range pkgRef.Patterns {
   248  		nv := strings.Split(pattern, ":")
   249  		if len(nv) != 2 {
   250  			continue
   251  		}
   252  		refVersions = append(refVersions, key.NpmPackageKey(nv[0], nv[1]))
   253  	}
   254  
   255  	return yarnLockPackage{
   256  		Name:         pkgRef.Name,
   257  		Version:      pkgRef.Version,
   258  		Integrity:    pkgRef.Integrity,
   259  		Resolved:     pkgRef.Resolved,
   260  		Dependencies: pkgRef.Dependencies,
   261  	}, refVersions, lineNumber, err
   262  }