github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/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/nextlinux/gosbom/gosbom/artifact"
    11  	"github.com/nextlinux/gosbom/gosbom/file"
    12  	"github.com/nextlinux/gosbom/gosbom/pkg"
    13  	"github.com/nextlinux/gosbom/gosbom/pkg/cataloger/generic"
    14  	"github.com/nextlinux/gosbom/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(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    48  	// in the case we find package-lock.json files in the node_modules directories, skip those
    49  	// as the whole purpose of the lock file is for the specific dependencies of the root project
    50  	if pathContainsNodeModulesDirectory(reader.AccessPath()) {
    51  		return nil, nil, nil
    52  	}
    53  
    54  	var pkgs []pkg.Package
    55  	dec := json.NewDecoder(reader)
    56  
    57  	var lock packageLock
    58  	for {
    59  		if err := dec.Decode(&lock); errors.Is(err, io.EOF) {
    60  			break
    61  		} else if err != nil {
    62  			return nil, nil, fmt.Errorf("failed to parse package-lock.json file: %w", err)
    63  		}
    64  	}
    65  
    66  	if lock.LockfileVersion == 1 {
    67  		for name, pkgMeta := range lock.Dependencies {
    68  			pkgs = append(pkgs, newPackageLockV1Package(resolver, reader.Location, name, pkgMeta))
    69  		}
    70  	}
    71  
    72  	if lock.LockfileVersion == 2 || lock.LockfileVersion == 3 {
    73  		for name, pkgMeta := range lock.Packages {
    74  			if name == "" {
    75  				if pkgMeta.Name == "" {
    76  					continue
    77  				}
    78  				name = pkgMeta.Name
    79  			}
    80  
    81  			// handles alias names
    82  			if pkgMeta.Name != "" {
    83  				name = pkgMeta.Name
    84  			}
    85  
    86  			pkgs = append(
    87  				pkgs,
    88  				newPackageLockV2Package(resolver, reader.Location, getNameFromPath(name), pkgMeta),
    89  			)
    90  		}
    91  	}
    92  
    93  	pkg.Sort(pkgs)
    94  
    95  	return pkgs, nil, nil
    96  }
    97  
    98  func (licenses *packageLockLicense) UnmarshalJSON(data []byte) (err error) {
    99  	// The license field could be either a string or an array.
   100  
   101  	// 1. An array
   102  	var arr []string
   103  	if err := json.Unmarshal(data, &arr); err == nil {
   104  		*licenses = arr
   105  		return nil
   106  	}
   107  
   108  	// 2. A string
   109  	var str string
   110  	if err = json.Unmarshal(data, &str); err == nil {
   111  		*licenses = make([]string, 1)
   112  		(*licenses)[0] = str
   113  		return nil
   114  	}
   115  
   116  	// debug the content we did not expect
   117  	if len(data) > 0 {
   118  		log.WithFields("license", string(data)).Debug("Unable to parse the following `license` value in package-lock.json")
   119  	}
   120  
   121  	// 3. Unexpected
   122  	// In case we are unable to parse the license field,
   123  	// i.e if we have not covered the full specification,
   124  	// we do not want to throw an error, instead assign nil.
   125  	return nil
   126  }
   127  
   128  func getNameFromPath(path string) string {
   129  	parts := strings.Split(path, "node_modules/")
   130  	return parts[len(parts)-1]
   131  }