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

     1  package javascript
     2  
     3  import (
     4  	"encoding/json"
     5  	"io"
     6  	"path"
     7  	"strings"
     8  
     9  	"github.com/anchore/packageurl-go"
    10  	"github.com/anchore/syft/syft/file"
    11  	"github.com/anchore/syft/syft/pkg"
    12  	"github.com/lineaje-labs/syft/internal/log"
    13  )
    14  
    15  func newPackageJSONPackage(u packageJSON, indexLocation file.Location) pkg.Package {
    16  	licenseCandidates, err := u.licensesFromJSON()
    17  	if err != nil {
    18  		log.Warnf("unable to extract licenses from javascript package.json: %+v", err)
    19  	}
    20  
    21  	license := pkg.NewLicensesFromLocation(indexLocation, licenseCandidates...)
    22  	p := pkg.Package{
    23  		Name:      u.Name,
    24  		Version:   u.Version,
    25  		PURL:      packageURL(u.Name, u.Version),
    26  		Locations: file.NewLocationSet(indexLocation),
    27  		Language:  pkg.JavaScript,
    28  		Licenses:  pkg.NewLicenseSet(license...),
    29  		Type:      pkg.NpmPkg,
    30  		Metadata: pkg.NpmPackage{
    31  			Name:        u.Name,
    32  			Version:     u.Version,
    33  			Description: u.Description,
    34  			Author:      u.Author.AuthorString(),
    35  			Homepage:    u.Homepage,
    36  			URL:         u.Repository.URL,
    37  			Private:     u.Private,
    38  		},
    39  	}
    40  
    41  	p.SetID()
    42  
    43  	return p
    44  }
    45  
    46  func newPackageLockV1Package(
    47  	resolver file.Resolver, location file.Location, name string, u lockDependency,
    48  ) pkg.Package {
    49  	version := u.Version
    50  
    51  	const aliasPrefixPackageLockV1 = "npm:"
    52  
    53  	// Handles type aliases https://github.com/npm/rfcs/blob/main/implemented/0001-package-aliases.md
    54  	if strings.HasPrefix(version, aliasPrefixPackageLockV1) {
    55  		// this is an alias.
    56  		// `"version": "npm:canonical-name@X.Y.Z"`
    57  		canonicalPackageAndVersion := version[len(aliasPrefixPackageLockV1):]
    58  		versionSeparator := strings.LastIndex(canonicalPackageAndVersion, "@")
    59  
    60  		name = canonicalPackageAndVersion[:versionSeparator]
    61  		version = canonicalPackageAndVersion[versionSeparator+1:]
    62  	}
    63  
    64  	return finalizeLockPkg(
    65  		resolver,
    66  		location,
    67  		pkg.Package{
    68  			Name:      name,
    69  			Version:   version,
    70  			Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    71  			PURL:      packageURL(name, version),
    72  			Language:  pkg.JavaScript,
    73  			Type:      pkg.NpmPkg,
    74  			Metadata:  pkg.NpmPackageLockEntry{Resolved: u.Resolved, Integrity: u.Integrity},
    75  		},
    76  	)
    77  }
    78  
    79  func newPackageLockV2Package(resolver file.Resolver, location file.Location, name string, u lockPackage) pkg.Package {
    80  	return finalizeLockPkg(
    81  		resolver,
    82  		location,
    83  		pkg.Package{
    84  			Name:      name,
    85  			Version:   u.Version,
    86  			Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    87  			Licenses:  pkg.NewLicenseSet(pkg.NewLicensesFromLocation(location, u.License...)...),
    88  			PURL:      packageURL(name, u.Version),
    89  			Language:  pkg.JavaScript,
    90  			Type:      pkg.NpmPkg,
    91  			Metadata:  pkg.NpmPackageLockEntry{Resolved: u.Resolved, Integrity: u.Integrity},
    92  		},
    93  	)
    94  }
    95  
    96  func newPnpmPackage(resolver file.Resolver, location file.Location, name, version string) pkg.Package {
    97  	return finalizeLockPkg(
    98  		resolver,
    99  		location,
   100  		pkg.Package{
   101  			Name:      name,
   102  			Version:   version,
   103  			Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
   104  			PURL:      packageURL(name, version),
   105  			Language:  pkg.JavaScript,
   106  			Type:      pkg.NpmPkg,
   107  		},
   108  	)
   109  }
   110  
   111  func newYarnLockPackage(resolver file.Resolver, location file.Location, name, version string) pkg.Package {
   112  	return finalizeLockPkg(
   113  		resolver,
   114  		location,
   115  		pkg.Package{
   116  			Name:      name,
   117  			Version:   version,
   118  			Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
   119  			PURL:      packageURL(name, version),
   120  			Language:  pkg.JavaScript,
   121  			Type:      pkg.NpmPkg,
   122  		},
   123  	)
   124  }
   125  
   126  func finalizeLockPkg(resolver file.Resolver, location file.Location, p pkg.Package) pkg.Package {
   127  	licenseCandidate := addLicenses(p.Name, resolver, location)
   128  	p.Licenses.Add(pkg.NewLicensesFromLocation(location, licenseCandidate...)...)
   129  	p.SetID()
   130  	return p
   131  }
   132  
   133  func addLicenses(name string, resolver file.Resolver, location file.Location) (allLicenses []string) {
   134  	if resolver == nil {
   135  		return allLicenses
   136  	}
   137  
   138  	dir := path.Dir(location.RealPath)
   139  	pkgPath := []string{dir, "node_modules"}
   140  	pkgPath = append(pkgPath, strings.Split(name, "/")...)
   141  	pkgPath = append(pkgPath, "package.json")
   142  	pkgFile := path.Join(pkgPath...)
   143  	locations, err := resolver.FilesByPath(pkgFile)
   144  	if err != nil {
   145  		log.Debugf("an error occurred attempting to read: %s - %+v", pkgFile, err)
   146  		return allLicenses
   147  	}
   148  
   149  	if len(locations) == 0 {
   150  		return allLicenses
   151  	}
   152  
   153  	for _, l := range locations {
   154  		contentReader, err := resolver.FileContentsByLocation(l)
   155  		if err != nil {
   156  			log.Debugf("error getting file content reader for %s: %v", pkgFile, err)
   157  			return allLicenses
   158  		}
   159  
   160  		contents, err := io.ReadAll(contentReader)
   161  		if err != nil {
   162  			log.Debugf("error reading file contents for %s: %v", pkgFile, err)
   163  			return allLicenses
   164  		}
   165  
   166  		var pkgJSON packageJSON
   167  		err = json.Unmarshal(contents, &pkgJSON)
   168  		if err != nil {
   169  			log.Debugf("error parsing %s: %v", pkgFile, err)
   170  			return allLicenses
   171  		}
   172  
   173  		licenses, err := pkgJSON.licensesFromJSON()
   174  		if err != nil {
   175  			log.Debugf("error getting licenses from %s: %v", pkgFile, err)
   176  			return allLicenses
   177  		}
   178  
   179  		allLicenses = append(allLicenses, licenses...)
   180  	}
   181  
   182  	return allLicenses
   183  }
   184  
   185  // packageURL returns the PURL for the specific NPM package (see https://github.com/package-url/purl-spec)
   186  func packageURL(name, version string) string {
   187  	var namespace string
   188  
   189  	fields := strings.SplitN(name, "/", 2)
   190  	if len(fields) > 1 {
   191  		namespace = fields[0]
   192  		name = fields[1]
   193  	}
   194  
   195  	return packageurl.NewPackageURL(
   196  		packageurl.TypeNPM,
   197  		namespace,
   198  		name,
   199  		version,
   200  		nil,
   201  		"",
   202  	).ToString()
   203  }