github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/javascript/parse_package_lock.go (about)

     1  package javascript
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"strings"
    10  
    11  	"github.com/anchore/syft/internal/log"
    12  	"github.com/anchore/syft/internal/unknown"
    13  	"github.com/anchore/syft/syft/artifact"
    14  	"github.com/anchore/syft/syft/file"
    15  	"github.com/anchore/syft/syft/pkg"
    16  	"github.com/anchore/syft/syft/pkg/cataloger/generic"
    17  	"github.com/anchore/syft/syft/pkg/cataloger/internal/dependency"
    18  )
    19  
    20  // packageLock represents a JavaScript package.lock json file
    21  type packageLock struct {
    22  	Requires        bool `json:"requires"`
    23  	LockfileVersion int  `json:"lockfileVersion"`
    24  	Dependencies    map[string]lockDependency
    25  	Packages        map[string]lockPackage
    26  }
    27  
    28  // lockDependency represents a single package dependency listed in the package.lock json file
    29  type lockDependency struct {
    30  	Version   string `json:"version"`
    31  	Resolved  string `json:"resolved"`
    32  	Integrity string `json:"integrity"`
    33  	Dev       bool   `json:"dev"`
    34  }
    35  
    36  type lockPackage struct {
    37  	Name         string             `json:"name"` // only present in the root package entry (named "")
    38  	Version      string             `json:"version"`
    39  	Resolved     string             `json:"resolved"`
    40  	Integrity    string             `json:"integrity"`
    41  	License      packageLockLicense `json:"license"`
    42  	Dev          bool               `json:"dev"`
    43  	Dependencies map[string]string  `json:"dependencies"`
    44  }
    45  
    46  // packageLockLicense
    47  type packageLockLicense []string
    48  
    49  type genericPackageLockAdapter struct {
    50  	cfg CatalogerConfig
    51  }
    52  
    53  func newGenericPackageLockAdapter(cfg CatalogerConfig) genericPackageLockAdapter {
    54  	return genericPackageLockAdapter{
    55  		cfg: cfg,
    56  	}
    57  }
    58  
    59  // parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages.
    60  func (a genericPackageLockAdapter) parsePackageLock(ctx context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    61  	// in the case we find package-lock.json files in the node_modules directories, skip those
    62  	// as the whole purpose of the lock file is for the specific dependencies of the root project
    63  	if pathContainsNodeModulesDirectory(reader.Path()) {
    64  		return nil, nil, nil
    65  	}
    66  
    67  	var pkgs []pkg.Package
    68  	dec := json.NewDecoder(reader)
    69  
    70  	var lock packageLock
    71  	for {
    72  		if err := dec.Decode(&lock); errors.Is(err, io.EOF) {
    73  			break
    74  		} else if err != nil {
    75  			return nil, nil, fmt.Errorf("failed to parse package-lock.json file: %w", err)
    76  		}
    77  	}
    78  
    79  	if lock.LockfileVersion == 1 {
    80  		for name, pkgMeta := range lock.Dependencies {
    81  			// skip packages that are only present as a dev dependency
    82  			if !a.cfg.IncludeDevDependencies && pkgMeta.Dev {
    83  				continue
    84  			}
    85  
    86  			pkgs = append(pkgs, newPackageLockV1Package(ctx, a.cfg, resolver, reader.Location, name, pkgMeta))
    87  		}
    88  	}
    89  
    90  	if lock.LockfileVersion == 2 || lock.LockfileVersion == 3 {
    91  		for name, pkgMeta := range lock.Packages {
    92  			if name == "" {
    93  				if pkgMeta.Name == "" {
    94  					continue
    95  				}
    96  				name = pkgMeta.Name
    97  			}
    98  
    99  			// skip packages that are only present as a dev dependency
   100  			if !a.cfg.IncludeDevDependencies && pkgMeta.Dev {
   101  				continue
   102  			}
   103  
   104  			// handles alias names
   105  			if pkgMeta.Name != "" {
   106  				name = pkgMeta.Name
   107  			}
   108  
   109  			newPkg := newPackageLockV2Package(ctx, a.cfg, resolver, reader.Location, getNameFromPath(name), pkgMeta)
   110  			pkgs = append(pkgs, newPkg)
   111  		}
   112  	}
   113  
   114  	pkg.Sort(pkgs)
   115  
   116  	return pkgs, dependency.Resolve(packageLockDependencySpecifier, pkgs), unknown.IfEmptyf(pkgs, "unable to determine packages")
   117  }
   118  
   119  func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
   120  	// The license field could be either a string or an array.
   121  
   122  	// 1. An array
   123  	var arr []string
   124  	if err := json.Unmarshal(data, &arr); err == nil {
   125  		*licenses = arr
   126  		return nil
   127  	}
   128  
   129  	// 2. A string
   130  	var str string
   131  	if err = json.Unmarshal(data, &str); err == nil {
   132  		*licenses = make([]string, 1)
   133  		(*licenses)[0] = str
   134  		return nil
   135  	}
   136  
   137  	// debug the content we did not expect
   138  	if len(data) > 0 {
   139  		log.WithFields("license", string(data)).Debug("Unable to parse the following `license` value in package-lock.json")
   140  	}
   141  
   142  	// 3. Unexpected
   143  	// In case we are unable to parse the license field,
   144  	// i.e if we have not covered the full specification,
   145  	// we do not want to throw an error, instead assign nil.
   146  	return nil
   147  }
   148  
   149  func getNameFromPath(path string) string {
   150  	parts := strings.Split(path, "node_modules/")
   151  	return parts[len(parts)-1]
   152  }