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