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

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