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