github.com/lineaje-labs/syft@v0.98.1-0.20231227153149-9e393f60ff1b/syft/pkg/cataloger/javascript/parse_yarn_lock.go (about)

     1  package javascript
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"regexp"
     7  
     8  	"github.com/scylladb/go-set/strset"
     9  
    10  	"github.com/anchore/syft/syft/artifact"
    11  	"github.com/anchore/syft/syft/file"
    12  	"github.com/anchore/syft/syft/pkg"
    13  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    14  )
    15  
    16  // integrity check
    17  var _ generic.Parser = parseYarnLock
    18  
    19  var (
    20  	// packageNameExp matches the name of the dependency in yarn.lock
    21  	// including scope/namespace prefix if found.
    22  	// For example: "aws-sdk@2.706.0" returns "aws-sdk"
    23  	//              "@babel/code-frame@^7.0.0" returns "@babel/code-frame"
    24  	packageNameExp = regexp.MustCompile(`^"?((?:@\w[\w-_.]*\/)?\w[\w-_.]*)@`)
    25  
    26  	// versionExp matches the "version" line of a yarn.lock entry and captures the version value.
    27  	// For example: version "4.10.1" (...and the value "4.10.1" is captured)
    28  	versionExp = regexp.MustCompile(`^\W+version(?:\W+"|:\W+)([\w-_.]+)"?`)
    29  
    30  	// packageURLExp matches the name and version of the dependency in yarn.lock
    31  	// from the resolved URL, including scope/namespace prefix if any.
    32  	// For example:
    33  	//		`resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"`
    34  	//			would return "async" and "3.2.3"
    35  	//
    36  	//		`resolved "https://registry.yarnpkg.com/@4lolo/resize-observer-polyfill/-/resize-observer-polyfill-1.5.2.tgz#58868fc7224506236b5550d0c68357f0a874b84b"`
    37  	//			would return "@4lolo/resize-observer-polyfill" and "1.5.2"
    38  	packageURLExp = regexp.MustCompile(`^\s+resolved\s+"https://registry\.(?:yarnpkg\.com|npmjs\.org)/(.+?)/-/(?:.+?)-(\d+\..+?)\.tgz`)
    39  )
    40  
    41  const (
    42  	noPackage = ""
    43  	noVersion = ""
    44  )
    45  
    46  func parseYarnLock(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    47  	// in the case we find yarn.lock files in the node_modules directories, skip those
    48  	// as the whole purpose of the lock file is for the specific dependencies of the project
    49  	if pathContainsNodeModulesDirectory(reader.Path()) {
    50  		return nil, nil, nil
    51  	}
    52  
    53  	var pkgs []pkg.Package
    54  	scanner := bufio.NewScanner(reader)
    55  	parsedPackages := strset.New()
    56  	currentPackage := noPackage
    57  	currentVersion := noVersion
    58  
    59  	for scanner.Scan() {
    60  		line := scanner.Text()
    61  
    62  		if packageName := findPackageName(line); packageName != noPackage {
    63  			// When we find a new package, check if we have unsaved identifiers
    64  			if currentPackage != noPackage && currentVersion != noVersion && !parsedPackages.Has(currentPackage+"@"+currentVersion) {
    65  				pkgs = append(pkgs, newYarnLockPackage(resolver, reader.Location, currentPackage, currentVersion))
    66  				parsedPackages.Add(currentPackage + "@" + currentVersion)
    67  			}
    68  
    69  			currentPackage = packageName
    70  		} else if version := findPackageVersion(line); version != noVersion {
    71  			currentVersion = version
    72  		} else if packageName, version := findPackageAndVersion(line); packageName != noPackage && version != noVersion && !parsedPackages.Has(packageName+"@"+version) {
    73  			pkgs = append(pkgs, newYarnLockPackage(resolver, reader.Location, packageName, version))
    74  			parsedPackages.Add(packageName + "@" + version)
    75  
    76  			// Cleanup to indicate no unsaved identifiers
    77  			currentPackage = noPackage
    78  			currentVersion = noVersion
    79  		}
    80  	}
    81  
    82  	// check if we have valid unsaved data after end-of-file has reached
    83  	if currentPackage != noPackage && currentVersion != noVersion && !parsedPackages.Has(currentPackage+"@"+currentVersion) {
    84  		pkgs = append(pkgs, newYarnLockPackage(resolver, reader.Location, currentPackage, currentVersion))
    85  		parsedPackages.Add(currentPackage + "@" + currentVersion)
    86  	}
    87  
    88  	if err := scanner.Err(); err != nil {
    89  		return nil, nil, fmt.Errorf("failed to parse yarn.lock file: %w", err)
    90  	}
    91  
    92  	pkg.Sort(pkgs)
    93  
    94  	return pkgs, nil, nil
    95  }
    96  
    97  func findPackageName(line string) string {
    98  	if matches := packageNameExp.FindStringSubmatch(line); len(matches) >= 2 {
    99  		return matches[1]
   100  	}
   101  
   102  	return noPackage
   103  }
   104  
   105  func findPackageVersion(line string) string {
   106  	if matches := versionExp.FindStringSubmatch(line); len(matches) >= 2 {
   107  		return matches[1]
   108  	}
   109  
   110  	return noVersion
   111  }
   112  
   113  func findPackageAndVersion(line string) (string, string) {
   114  	if matches := packageURLExp.FindStringSubmatch(line); len(matches) >= 2 {
   115  		return matches[1], matches[2]
   116  	}
   117  
   118  	return noPackage, noVersion
   119  }