github.com/nextlinux/gosbom@v0.81.1-0.20230627115839-1ff50c281391/gosbom/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/nextlinux/gosbom/gosbom/file"
    10  	"github.com/nextlinux/gosbom/gosbom/pkg"
    11  	"github.com/nextlinux/gosbom/internal/log"
    12  
    13  	"github.com/anchore/packageurl-go"
    14  )
    15  
    16  func newPackageJSONPackage(u packageJSON, indexLocation file.Location) pkg.Package {
    17  	licenseCandidates, err := u.licensesFromJSON()
    18  	if err != nil {
    19  		log.Warnf("unable to extract licenses from javascript package.json: %+v", err)
    20  	}
    21  
    22  	license := pkg.NewLicensesFromLocation(indexLocation, licenseCandidates...)
    23  	p := pkg.Package{
    24  		Name:         u.Name,
    25  		Version:      u.Version,
    26  		PURL:         packageURL(u.Name, u.Version),
    27  		Locations:    file.NewLocationSet(indexLocation),
    28  		Language:     pkg.JavaScript,
    29  		Licenses:     pkg.NewLicenseSet(license...),
    30  		Type:         pkg.NpmPkg,
    31  		MetadataType: pkg.NpmPackageJSONMetadataType,
    32  		Metadata: pkg.NpmPackageJSONMetadata{
    33  			Name:        u.Name,
    34  			Version:     u.Version,
    35  			Description: u.Description,
    36  			Author:      u.Author.AuthorString(),
    37  			Homepage:    u.Homepage,
    38  			URL:         u.Repository.URL,
    39  			Private:     u.Private,
    40  		},
    41  	}
    42  
    43  	p.SetID()
    44  
    45  	return p
    46  }
    47  
    48  func newPackageLockV1Package(resolver file.Resolver, location file.Location, name string, u lockDependency) 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  			MetadataType: pkg.NpmPackageLockJSONMetadataType,
    75  			Metadata:     pkg.NpmPackageLockJSONMetadata{Resolved: u.Resolved, Integrity: u.Integrity},
    76  		},
    77  	)
    78  }
    79  
    80  func newPackageLockV2Package(resolver file.Resolver, location file.Location, name string, u lockPackage) pkg.Package {
    81  	return finalizeLockPkg(
    82  		resolver,
    83  		location,
    84  		pkg.Package{
    85  			Name:         name,
    86  			Version:      u.Version,
    87  			Locations:    file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    88  			Licenses:     pkg.NewLicenseSet(pkg.NewLicensesFromLocation(location, u.License...)...),
    89  			PURL:         packageURL(name, u.Version),
    90  			Language:     pkg.JavaScript,
    91  			Type:         pkg.NpmPkg,
    92  			MetadataType: pkg.NpmPackageLockJSONMetadataType,
    93  			Metadata:     pkg.NpmPackageLockJSONMetadata{Resolved: u.Resolved, Integrity: u.Integrity},
    94  		},
    95  	)
    96  }
    97  
    98  func newPnpmPackage(resolver file.Resolver, location file.Location, name, version string) pkg.Package {
    99  	return finalizeLockPkg(
   100  		resolver,
   101  		location,
   102  		pkg.Package{
   103  			Name:      name,
   104  			Version:   version,
   105  			Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
   106  			PURL:      packageURL(name, version),
   107  			Language:  pkg.JavaScript,
   108  			Type:      pkg.NpmPkg,
   109  		},
   110  	)
   111  }
   112  
   113  func newYarnLockPackage(resolver file.Resolver, location file.Location, name, version string) pkg.Package {
   114  	return finalizeLockPkg(
   115  		resolver,
   116  		location,
   117  		pkg.Package{
   118  			Name:      name,
   119  			Version:   version,
   120  			Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
   121  			PURL:      packageURL(name, version),
   122  			Language:  pkg.JavaScript,
   123  			Type:      pkg.NpmPkg,
   124  		},
   125  	)
   126  }
   127  
   128  func finalizeLockPkg(resolver file.Resolver, location file.Location, p pkg.Package) pkg.Package {
   129  	licenseCandidate := addLicenses(p.Name, resolver, location)
   130  	p.Licenses.Add(pkg.NewLicensesFromLocation(location, licenseCandidate...)...)
   131  	p.SetID()
   132  	return p
   133  }
   134  
   135  func addLicenses(name string, resolver file.Resolver, location file.Location) (allLicenses []string) {
   136  	if resolver == nil {
   137  		return allLicenses
   138  	}
   139  
   140  	dir := path.Dir(location.RealPath)
   141  	pkgPath := []string{dir, "node_modules"}
   142  	pkgPath = append(pkgPath, strings.Split(name, "/")...)
   143  	pkgPath = append(pkgPath, "package.json")
   144  	pkgFile := path.Join(pkgPath...)
   145  	locations, err := resolver.FilesByPath(pkgFile)
   146  	if err != nil {
   147  		log.Debugf("an error occurred attempting to read: %s - %+v", pkgFile, err)
   148  		return allLicenses
   149  	}
   150  
   151  	if len(locations) == 0 {
   152  		return allLicenses
   153  	}
   154  
   155  	for _, l := range locations {
   156  		contentReader, err := resolver.FileContentsByLocation(l)
   157  		if err != nil {
   158  			log.Debugf("error getting file content reader for %s: %v", pkgFile, err)
   159  			return allLicenses
   160  		}
   161  
   162  		contents, err := io.ReadAll(contentReader)
   163  		if err != nil {
   164  			log.Debugf("error reading file contents for %s: %v", pkgFile, err)
   165  			return allLicenses
   166  		}
   167  
   168  		var pkgJSON packageJSON
   169  		err = json.Unmarshal(contents, &pkgJSON)
   170  		if err != nil {
   171  			log.Debugf("error parsing %s: %v", pkgFile, err)
   172  			return allLicenses
   173  		}
   174  
   175  		licenses, err := pkgJSON.licensesFromJSON()
   176  		if err != nil {
   177  			log.Debugf("error getting licenses from %s: %v", pkgFile, err)
   178  			return allLicenses
   179  		}
   180  
   181  		allLicenses = append(allLicenses, licenses...)
   182  	}
   183  
   184  	return allLicenses
   185  }
   186  
   187  // packageURL returns the PURL for the specific NPM package (see https://github.com/package-url/purl-spec)
   188  func packageURL(name, version string) string {
   189  	var namespace string
   190  
   191  	fields := strings.SplitN(name, "/", 2)
   192  	if len(fields) > 1 {
   193  		namespace = fields[0]
   194  		name = fields[1]
   195  	}
   196  
   197  	return packageurl.NewPackageURL(
   198  		packageurl.TypeNPM,
   199  		namespace,
   200  		name,
   201  		version,
   202  		nil,
   203  		"",
   204  	).ToString()
   205  }