mirror of
https://github.com/anchore/syft.git
synced 2025-11-17 16:33:21 +01:00
Fill in SPDX originator for all supported package types (#2822)
* add failing test + beef up doc comments Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * cover more metadata types in spdx originator processing Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
parent
9901ea8fe9
commit
5b03788300
@ -517,9 +517,7 @@ func toPackageOriginator(p pkg.Package) *spdx.Originator {
|
||||
}
|
||||
|
||||
func toPackageSupplier(p pkg.Package) *spdx.Supplier {
|
||||
// this uses the Originator function for now until
|
||||
// a better distinction can be made for supplier
|
||||
kind, supplier := helpers.Originator(p)
|
||||
kind, supplier := helpers.Supplier(p)
|
||||
if kind == "" || supplier == "" {
|
||||
return &spdx.Supplier{
|
||||
Supplier: helpers.NOASSERTION,
|
||||
|
||||
210
syft/format/internal/spdxutil/helpers/originator_supplier.go
Normal file
210
syft/format/internal/spdxutil/helpers/originator_supplier.go
Normal file
@ -0,0 +1,210 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/anchore/syft/internal"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
const (
|
||||
orgType = "Organization"
|
||||
personType = "Person"
|
||||
)
|
||||
|
||||
// Originator needs to conform to the SPDX spec here:
|
||||
// https://spdx.github.io/spdx-spec/v2.2.2/package-information/#76-package-originator-field
|
||||
//
|
||||
// Definition:
|
||||
//
|
||||
// If the package identified in the SPDX document originated from a different person or
|
||||
// organization than identified as Package Supplier (see 7.5 above), this field identifies from
|
||||
// where or whom the package originally came. In some cases, a package may be created and
|
||||
// originally distributed by a different third party than the Package Supplier of the package.
|
||||
// For example, the SPDX document identifies the package as glibc and the Package Supplier as
|
||||
// Red Hat, but the Free Software Foundation is the Package Originator.
|
||||
//
|
||||
// Use NOASSERTION if:
|
||||
//
|
||||
// - the SPDX document creator has attempted to but cannot reach a reasonable objective determination;
|
||||
// - the SPDX document creator has made no attempt to determine this field; or
|
||||
// - the SPDX document creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
//
|
||||
// Available options are: <omit>, NOASSERTION, Person: <person>, Organization: <org>
|
||||
// return values are: <type>, <value>
|
||||
func Originator(p pkg.Package) (typ string, author string) { // nolint: funlen
|
||||
if !hasMetadata(p) {
|
||||
return typ, author
|
||||
}
|
||||
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkDBEntry:
|
||||
author = metadata.Maintainer
|
||||
|
||||
case pkg.DotnetPortableExecutableEntry:
|
||||
typ = orgType
|
||||
author = metadata.CompanyName
|
||||
|
||||
case pkg.DpkgDBEntry:
|
||||
author = metadata.Maintainer
|
||||
|
||||
case pkg.JavaArchive:
|
||||
if metadata.Manifest != nil {
|
||||
author = metadata.Manifest.Main.MustGet("Specification-Vendor")
|
||||
if author == "" {
|
||||
author = metadata.Manifest.Main.MustGet("Implementation-Vendor")
|
||||
}
|
||||
}
|
||||
|
||||
case pkg.LinuxKernelModule:
|
||||
author = metadata.Author
|
||||
|
||||
case pkg.PhpComposerLockEntry:
|
||||
if len(metadata.Authors) > 0 {
|
||||
entry := metadata.Authors[0]
|
||||
author = formatPersonOrOrg(entry.Name, entry.Email)
|
||||
}
|
||||
|
||||
case pkg.PhpComposerInstalledEntry:
|
||||
if len(metadata.Authors) > 0 {
|
||||
entry := metadata.Authors[0]
|
||||
author = formatPersonOrOrg(entry.Name, entry.Email)
|
||||
}
|
||||
|
||||
case pkg.RDescription:
|
||||
// this is most likely to have a name and email
|
||||
author = metadata.Maintainer
|
||||
|
||||
if author == "" {
|
||||
author = metadata.Author
|
||||
}
|
||||
|
||||
case pkg.NpmPackage:
|
||||
author = metadata.Author
|
||||
|
||||
case pkg.PythonPackage:
|
||||
author = formatPersonOrOrg(metadata.Author, metadata.AuthorEmail)
|
||||
|
||||
case pkg.RubyGemspec:
|
||||
if len(metadata.Authors) > 0 {
|
||||
author = metadata.Authors[0]
|
||||
}
|
||||
case pkg.RpmDBEntry:
|
||||
typ = orgType
|
||||
author = metadata.Vendor
|
||||
|
||||
case pkg.RpmArchive:
|
||||
typ = orgType
|
||||
author = metadata.Vendor
|
||||
|
||||
case pkg.WordpressPluginEntry:
|
||||
// it seems that the vast majority of the time the author is an org, not a person
|
||||
typ = orgType
|
||||
author = metadata.Author
|
||||
}
|
||||
|
||||
if typ == "" && author != "" {
|
||||
typ = personType
|
||||
}
|
||||
|
||||
return typ, parseAndFormatPersonOrOrg(author)
|
||||
}
|
||||
|
||||
// Supplier needs to conform to the SPDX spec here:
|
||||
// https://spdx.github.io/spdx-spec/v2.2.2/package-information/#75-package-supplier-field
|
||||
//
|
||||
// Definition:
|
||||
//
|
||||
// Identify the actual distribution source for the package/directory identified in the SPDX document. This might
|
||||
// or might not be different from the originating distribution source for the package. The name of the Package Supplier
|
||||
// shall be an organization or recognized author and not a web site. For example, SourceForge is a host website, not a
|
||||
// supplier, the supplier for https://sourceforge.net/projects/bridge/ is “The Linux Foundation.”
|
||||
//
|
||||
// Use NOASSERTION if:
|
||||
//
|
||||
// - the SPDX document creator has attempted to but cannot reach a reasonable objective determination;
|
||||
// - the SPDX document creator has made no attempt to determine this field; or
|
||||
// - the SPDX document creator has intentionally provided no information (no meaning should be implied by doing so).
|
||||
//
|
||||
// Available options are: <omit>, NOASSERTION, Person: <person>, Organization: <org>
|
||||
// return values are: <type>, <value>
|
||||
func Supplier(p pkg.Package) (typ string, author string) {
|
||||
if !hasMetadata(p) {
|
||||
return
|
||||
}
|
||||
|
||||
if metadata, ok := p.Metadata.(pkg.AlpmDBEntry); ok {
|
||||
// most indications here are that this is the person that is simply packaging the upstream software. Most
|
||||
// of the time this is not the original author of the upstream software (which would be the originator).
|
||||
// Though it is possible for users to be both the packager and the author, this code cannot distinct this
|
||||
// case and sticks to the semantically correct interpretation of the "packager" (which says nothing about the
|
||||
// authorship of the upstream software).
|
||||
author = metadata.Packager
|
||||
}
|
||||
|
||||
if author == "" {
|
||||
// TODO: this uses the Originator function for now until a better distinction can be made for supplier
|
||||
return Originator(p)
|
||||
}
|
||||
|
||||
if typ == "" && author != "" {
|
||||
typ = personType
|
||||
}
|
||||
|
||||
return typ, parseAndFormatPersonOrOrg(author)
|
||||
}
|
||||
|
||||
var nameEmailURLPattern = regexp.MustCompile(`^(?P<name>[^<>()]*)( <(?P<email>[^@]+@\w+\.\w+)>)?( \((?P<url>.*)\))?$`)
|
||||
|
||||
func parseAndFormatPersonOrOrg(s string) string {
|
||||
name, email, _ := parseNameEmailURL(s)
|
||||
return formatPersonOrOrg(name, email)
|
||||
}
|
||||
|
||||
func parseNameEmailURL(s string) (name, email, url string) {
|
||||
fields := internal.MatchNamedCaptureGroups(nameEmailURLPattern, s)
|
||||
name = strings.TrimSpace(fields["name"])
|
||||
email = strings.TrimSpace(fields["email"])
|
||||
url = strings.TrimSpace(fields["url"])
|
||||
|
||||
if email == "" {
|
||||
if approximatesAsEmail(url) {
|
||||
email = url
|
||||
url = ""
|
||||
} else if approximatesAsEmail(name) {
|
||||
email = name
|
||||
name = ""
|
||||
}
|
||||
}
|
||||
return name, email, url
|
||||
}
|
||||
|
||||
func approximatesAsEmail(s string) bool {
|
||||
atIndex := strings.Index(s, "@")
|
||||
if atIndex == -1 {
|
||||
return false
|
||||
}
|
||||
dotIndex := strings.Index(s[atIndex:], ".")
|
||||
return dotIndex != -1
|
||||
}
|
||||
|
||||
func formatPersonOrOrg(name, email string) string {
|
||||
name = strings.TrimSpace(name)
|
||||
email = strings.TrimSpace(email)
|
||||
|
||||
blankName := name == ""
|
||||
blankEmail := email == ""
|
||||
|
||||
if !blankEmail && !blankName {
|
||||
return fmt.Sprintf("%s (%s)", name, email)
|
||||
}
|
||||
if !blankName && blankEmail {
|
||||
return name
|
||||
}
|
||||
if blankName && !blankEmail {
|
||||
return email
|
||||
}
|
||||
return ""
|
||||
}
|
||||
@ -0,0 +1,482 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/internal/packagemetadata"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_OriginatorSupplier(t *testing.T) {
|
||||
completionTester := packagemetadata.NewCompletionTester(t,
|
||||
pkg.BinarySignature{},
|
||||
pkg.CocoaPodfileLockEntry{},
|
||||
pkg.ConanV1LockEntry{},
|
||||
pkg.ConanV2LockEntry{}, // the field Username might be the username of either the package originator or the supplier (unclear currently)
|
||||
pkg.ConanfileEntry{},
|
||||
pkg.ConaninfoEntry{},
|
||||
pkg.DartPubspecLockEntry{},
|
||||
pkg.DotnetDepsEntry{},
|
||||
pkg.ELFBinaryPackageNoteJSONPayload{},
|
||||
pkg.ElixirMixLockEntry{},
|
||||
pkg.ErlangRebarLockEntry{},
|
||||
pkg.GolangBinaryBuildinfoEntry{},
|
||||
pkg.GolangModuleEntry{},
|
||||
pkg.HackageStackYamlLockEntry{},
|
||||
pkg.HackageStackYamlEntry{},
|
||||
pkg.LinuxKernel{},
|
||||
pkg.MicrosoftKbPatch{},
|
||||
pkg.NixStoreEntry{},
|
||||
pkg.NpmPackageLockEntry{},
|
||||
pkg.PhpComposerInstalledEntry{},
|
||||
pkg.PhpPeclEntry{},
|
||||
pkg.PortageEntry{},
|
||||
pkg.PythonPipfileLockEntry{},
|
||||
pkg.PythonRequirementsEntry{},
|
||||
pkg.PythonPoetryLockEntry{},
|
||||
pkg.RustBinaryAuditEntry{},
|
||||
pkg.RustCargoLockEntry{},
|
||||
pkg.SwiftPackageManagerResolvedEntry{},
|
||||
pkg.YarnLockEntry{},
|
||||
)
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
originator string
|
||||
supplier string
|
||||
}{
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
originator: "",
|
||||
supplier: "",
|
||||
},
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "empty author on existing metadata",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "",
|
||||
},
|
||||
},
|
||||
originator: "",
|
||||
supplier: "",
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
originator: "Person: auth",
|
||||
supplier: "Person: auth",
|
||||
},
|
||||
{
|
||||
name: "from alpm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.AlpmDBEntry{
|
||||
Packager: "someone",
|
||||
},
|
||||
},
|
||||
originator: "",
|
||||
supplier: "Person: someone",
|
||||
},
|
||||
{
|
||||
name: "from dotnet -- PE binary",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.DotnetPortableExecutableEntry{
|
||||
CompanyName: "Microsoft Corporation",
|
||||
},
|
||||
},
|
||||
originator: "Organization: Microsoft Corporation",
|
||||
supplier: "Organization: Microsoft Corporation",
|
||||
},
|
||||
{
|
||||
name: "from dpkg",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
originator: "Person: auth",
|
||||
supplier: "Person: auth",
|
||||
},
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RubyGemspec{
|
||||
Authors: []string{
|
||||
"auth1",
|
||||
"auth2",
|
||||
},
|
||||
},
|
||||
},
|
||||
originator: "Person: auth1",
|
||||
supplier: "Person: auth1",
|
||||
},
|
||||
{
|
||||
name: "from java -- spec > impl cendor in main manifest section",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: pkg.KeyValues{
|
||||
{
|
||||
Key: "Implementation-Vendor",
|
||||
Value: "auth-impl",
|
||||
},
|
||||
{
|
||||
Key: "Specification-Vendor",
|
||||
Value: "auth-spec",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
originator: "Person: auth-spec",
|
||||
supplier: "Person: auth-spec",
|
||||
},
|
||||
{
|
||||
name: "from java -- fallback to impl vendor in main manifest section",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Main: pkg.KeyValues{
|
||||
{
|
||||
Key: "Implementation-Vendor",
|
||||
Value: "auth-impl",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
originator: "Person: auth-impl",
|
||||
supplier: "Person: auth-impl",
|
||||
},
|
||||
{
|
||||
name: "from java -- non-main manifest sections ignored",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.JavaArchive{
|
||||
Manifest: &pkg.JavaManifest{
|
||||
Sections: []pkg.KeyValues{
|
||||
{
|
||||
{
|
||||
Key: "Implementation-Vendor",
|
||||
Value: "auth-impl",
|
||||
},
|
||||
},
|
||||
},
|
||||
Main: pkg.KeyValues{},
|
||||
},
|
||||
},
|
||||
},
|
||||
// note: empty!
|
||||
},
|
||||
{
|
||||
name: "from linux kernel module",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.LinuxKernelModule{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
originator: "Person: auth",
|
||||
supplier: "Person: auth",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
originator: "Person: auth",
|
||||
supplier: "Person: auth",
|
||||
},
|
||||
{
|
||||
name: "from npm -- name, email, and url",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
},
|
||||
},
|
||||
originator: "Person: Isaac Z. Schlueter (i@izs.me)",
|
||||
supplier: "Person: Isaac Z. Schlueter (i@izs.me)",
|
||||
},
|
||||
{
|
||||
name: "from npm -- name, email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "Isaac Z. Schlueter <i@izs.me>",
|
||||
},
|
||||
},
|
||||
originator: "Person: Isaac Z. Schlueter (i@izs.me)",
|
||||
supplier: "Person: Isaac Z. Schlueter (i@izs.me)",
|
||||
},
|
||||
{
|
||||
name: "from php composer installed file",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PhpComposerInstalledEntry{
|
||||
Authors: []pkg.PhpComposerAuthors{
|
||||
{
|
||||
Name: "auth",
|
||||
Email: "me@auth.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
originator: "Person: auth (me@auth.com)",
|
||||
supplier: "Person: auth (me@auth.com)",
|
||||
},
|
||||
{
|
||||
name: "from php composer installed file",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PhpComposerLockEntry{
|
||||
Authors: []pkg.PhpComposerAuthors{
|
||||
{
|
||||
Name: "auth",
|
||||
Email: "me@auth.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
originator: "Person: auth (me@auth.com)",
|
||||
supplier: "Person: auth (me@auth.com)",
|
||||
},
|
||||
{
|
||||
name: "from python - just name",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackage{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
originator: "Person: auth",
|
||||
supplier: "Person: auth",
|
||||
},
|
||||
{
|
||||
name: "from python - just email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackage{
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
originator: "Person: auth@auth.gov",
|
||||
supplier: "Person: auth@auth.gov",
|
||||
},
|
||||
{
|
||||
name: "from python - both name and email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackage{
|
||||
Author: "auth",
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
originator: "Person: auth (auth@auth.gov)",
|
||||
supplier: "Person: auth (auth@auth.gov)",
|
||||
},
|
||||
{
|
||||
name: "from r -- maintainer > author",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RDescription{
|
||||
Author: "author",
|
||||
Maintainer: "maintainer",
|
||||
},
|
||||
},
|
||||
originator: "Person: maintainer",
|
||||
supplier: "Person: maintainer",
|
||||
},
|
||||
{
|
||||
name: "from r -- fallback to author",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RDescription{
|
||||
Author: "author",
|
||||
},
|
||||
},
|
||||
originator: "Person: author",
|
||||
supplier: "Person: author",
|
||||
},
|
||||
{
|
||||
name: "from rpm archive",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RpmArchive{
|
||||
Vendor: "auth",
|
||||
},
|
||||
},
|
||||
originator: "Organization: auth",
|
||||
supplier: "Organization: auth",
|
||||
},
|
||||
{
|
||||
name: "from rpm DB",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Vendor: "auth",
|
||||
},
|
||||
},
|
||||
originator: "Organization: auth",
|
||||
supplier: "Organization: auth",
|
||||
},
|
||||
{
|
||||
name: "from wordpress plugin",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.WordpressPluginEntry{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
originator: "Organization: auth",
|
||||
supplier: "Organization: auth",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
completionTester.Tested(t, test.input.Metadata)
|
||||
|
||||
typ, value := Originator(test.input)
|
||||
if typ != "" {
|
||||
value = typ + ": " + value
|
||||
}
|
||||
assert.Equal(t, test.originator, value)
|
||||
|
||||
typ, value = Supplier(test.input)
|
||||
if typ != "" {
|
||||
value = typ + ": " + value
|
||||
}
|
||||
assert.Equal(t, test.supplier, value)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseNameEmailUrl(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
wantName string
|
||||
wantEmail string
|
||||
wantUrl string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
input: "",
|
||||
},
|
||||
{
|
||||
name: "npm-like: name only",
|
||||
input: "Isaac Z. Schlueter",
|
||||
wantName: "Isaac Z. Schlueter",
|
||||
},
|
||||
{
|
||||
name: "npm-like: name and email",
|
||||
input: "Ray Nos <bogus2@gmail.com>",
|
||||
wantName: "Ray Nos",
|
||||
wantEmail: "bogus2@gmail.com",
|
||||
},
|
||||
{
|
||||
name: "npm-like: name and url",
|
||||
input: "Ray Nos (http://example.com)",
|
||||
wantName: "Ray Nos",
|
||||
wantUrl: "http://example.com",
|
||||
},
|
||||
{
|
||||
name: "npm-like: name, email, and url",
|
||||
input: "Isaac Z. Schlueter <i@izs.me> (http://blog.izs.me)",
|
||||
wantName: "Isaac Z. Schlueter",
|
||||
wantEmail: "i@izs.me",
|
||||
wantUrl: "http://blog.izs.me",
|
||||
},
|
||||
{
|
||||
name: "mixed input: email only",
|
||||
input: "i@izs.me",
|
||||
wantEmail: "i@izs.me",
|
||||
},
|
||||
{
|
||||
name: "mixed input: email in url",
|
||||
input: "my name (i@izs.me)",
|
||||
wantName: "my name",
|
||||
wantEmail: "i@izs.me",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotName, gotEmail, gotUrl := parseNameEmailURL(tt.input)
|
||||
assert.Equal(t, tt.wantName, gotName)
|
||||
assert.Equal(t, tt.wantEmail, gotEmail)
|
||||
assert.Equal(t, tt.wantUrl, gotUrl)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_formatPersonOrOrg(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
email string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "name only",
|
||||
input: "Isaac Z. Schlueter",
|
||||
want: "Isaac Z. Schlueter",
|
||||
},
|
||||
{
|
||||
name: "email only",
|
||||
email: "i@something.com",
|
||||
want: "i@something.com",
|
||||
},
|
||||
{
|
||||
name: "name and email",
|
||||
input: "Isaac Z. Schlueter",
|
||||
email: "i@something.com",
|
||||
want: "Isaac Z. Schlueter (i@something.com)",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, formatPersonOrOrg(tt.input, tt.email))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_approximatesAsEmail(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
input: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "no at",
|
||||
input: "something.com",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "no dot",
|
||||
input: "something@com",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "dot before at",
|
||||
input: "something.com@nothing",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
input: "something@nothing.com",
|
||||
want: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, approximatesAsEmail(tt.input))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,119 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
func Test_Originator(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input pkg.Package
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "no metadata",
|
||||
input: pkg.Package{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "from gem",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RubyGemspec{
|
||||
Authors: []string{
|
||||
"auth1",
|
||||
"auth2",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: "Person: auth1",
|
||||
},
|
||||
{
|
||||
name: "from npm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
expected: "Person: auth",
|
||||
},
|
||||
{
|
||||
name: "from apk",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.ApkDBEntry{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
expected: "Person: auth",
|
||||
},
|
||||
{
|
||||
name: "from python - just name",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackage{
|
||||
Author: "auth",
|
||||
},
|
||||
},
|
||||
expected: "Person: auth",
|
||||
},
|
||||
{
|
||||
name: "from python - just email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackage{
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
expected: "Person: auth@auth.gov",
|
||||
},
|
||||
{
|
||||
name: "from python - both name and email",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.PythonPackage{
|
||||
Author: "auth",
|
||||
AuthorEmail: "auth@auth.gov",
|
||||
},
|
||||
},
|
||||
expected: "Person: auth (auth@auth.gov)",
|
||||
},
|
||||
{
|
||||
name: "from rpm",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.RpmDBEntry{
|
||||
Vendor: "auth",
|
||||
},
|
||||
},
|
||||
expected: "Organization: auth",
|
||||
},
|
||||
{
|
||||
name: "from dpkg",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.DpkgDBEntry{
|
||||
Maintainer: "auth",
|
||||
},
|
||||
},
|
||||
expected: "Person: auth",
|
||||
},
|
||||
{
|
||||
// note: since this is an optional field, no value is preferred over NONE or NOASSERTION
|
||||
name: "empty",
|
||||
input: pkg.Package{
|
||||
Metadata: pkg.NpmPackage{
|
||||
Author: "",
|
||||
},
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
typ, value := Originator(test.input)
|
||||
if typ != "" {
|
||||
value = typ + ": " + value
|
||||
}
|
||||
assert.Equal(t, test.expected, value)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
)
|
||||
|
||||
// Originator needs to conform to the SPDX spec here:
|
||||
// https://spdx.github.io/spdx-spec/package-information/#76-package-originator-field
|
||||
// Available options are: <omit>, NOASSERTION, Person: <person>, Organization: <org>
|
||||
// return values are: <type>, <value>
|
||||
func Originator(p pkg.Package) (string, string) {
|
||||
typ := ""
|
||||
author := ""
|
||||
if hasMetadata(p) {
|
||||
switch metadata := p.Metadata.(type) {
|
||||
case pkg.ApkDBEntry:
|
||||
author = metadata.Maintainer
|
||||
case pkg.NpmPackage:
|
||||
author = metadata.Author
|
||||
case pkg.PythonPackage:
|
||||
author = metadata.Author
|
||||
if author == "" {
|
||||
author = metadata.AuthorEmail
|
||||
} else if metadata.AuthorEmail != "" {
|
||||
author = fmt.Sprintf("%s (%s)", author, metadata.AuthorEmail)
|
||||
}
|
||||
case pkg.RubyGemspec:
|
||||
if len(metadata.Authors) > 0 {
|
||||
author = metadata.Authors[0]
|
||||
}
|
||||
case pkg.RpmDBEntry:
|
||||
typ = "Organization"
|
||||
author = metadata.Vendor
|
||||
case pkg.DpkgDBEntry:
|
||||
author = metadata.Maintainer
|
||||
}
|
||||
if typ == "" && author != "" {
|
||||
typ = "Person"
|
||||
}
|
||||
}
|
||||
return typ, author
|
||||
}
|
||||
@ -3,9 +3,11 @@ package pkg
|
||||
type RDescription struct {
|
||||
/*
|
||||
Fields chosen by:
|
||||
docker run --rm -it rocker/r-ver bash
|
||||
$ install2.r ggplot2 # has a lot of dependencies
|
||||
$ find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep -v '^\s' | cut -d ':' -f 1 | sort | uniq -c | sort -nr
|
||||
docker run --rm -it rocker/r-ver bash
|
||||
$ install2.r ggplot2 # has a lot of dependencies
|
||||
$ find /usr/local/lib/R -name DESCRIPTION | xargs cat | grep -v '^\s' | cut -d ':' -f 1 | sort | uniq -c | sort -nr
|
||||
|
||||
For more information on the DESCRIPTION file see https://r-pkgs.org/description.html
|
||||
*/
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user