1
0
Fork 0
mirror of https://github.com/gocsaf/csaf.git synced 2025-12-22 18:15:42 +01:00

feat: Add function to find product identification helpers inspecting the tree (#505)

* feat: Add function to find product identification helpers inspecting the tree

Signed-off-by: juan131 <jariza@vmware.com>

* fix: simplify unit tests

Signed-off-by: juan131 <jariza@vmware.com>

* fix: also iterate over relationships

Signed-off-by: juan131 <jariza@vmware.com>

* fix: adapt example to use new library function

Signed-off-by: juan131 <jariza@vmware.com>

* Separate collecting and visiting of the product id helpers.

---------

Signed-off-by: juan131 <jariza@vmware.com>
Co-authored-by: Sascha L. Teichmann <sascha.teichmann@intevation.de>
This commit is contained in:
Juan Ariza Toledano 2023-12-01 15:31:25 +01:00 committed by GitHub
parent b457dc872f
commit 9073a8a282
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 258 additions and 96 deletions

View file

@ -36,3 +36,64 @@ func ExtractProviderURL(r io.Reader, all bool) ([]string, error) {
} }
return urls, nil return urls, nil
} }
// CollectProductIdentificationHelpers returns a slice of all ProductIdentificationHelper
// for a given ProductID.
func (pt *ProductTree) CollectProductIdentificationHelpers(id ProductID) []*ProductIdentificationHelper {
var helpers []*ProductIdentificationHelper
pt.FindProductIdentificationHelpers(
id, func(helper *ProductIdentificationHelper) {
helpers = append(helpers, helper)
})
return helpers
}
// FindProductIdentificationHelpers calls visit on all ProductIdentificationHelper
// for a given ProductID by iterating over all full product names and branches
// recursively available in the ProductTree.
func (pt *ProductTree) FindProductIdentificationHelpers(
id ProductID,
visit func(*ProductIdentificationHelper),
) {
// Iterate over all full product names
if fpns := pt.FullProductNames; fpns != nil {
for _, fpn := range *fpns {
if fpn != nil &&
fpn.ProductID != nil && *fpn.ProductID == id &&
fpn.ProductIdentificationHelper != nil {
visit(fpn.ProductIdentificationHelper)
}
}
}
// Iterate over branches recursively
var recBranch func(b *Branch)
recBranch = func(b *Branch) {
if b == nil {
return
}
if fpn := b.Product; fpn != nil &&
fpn.ProductID != nil && *fpn.ProductID == id &&
fpn.ProductIdentificationHelper != nil {
visit(fpn.ProductIdentificationHelper)
}
for _, c := range b.Branches {
recBranch(c)
}
}
for _, b := range pt.Branches {
recBranch(b)
}
// Iterate over relationships
if rels := pt.RelationShips; rels != nil {
for _, rel := range *rels {
if rel != nil {
if fpn := rel.FullProductName; fpn != nil && fpn.ProductID != nil &&
*fpn.ProductID == id && fpn.ProductIdentificationHelper != nil {
visit(fpn.ProductIdentificationHelper)
}
}
}
}
}

182
csaf/util_test.go Normal file
View file

@ -0,0 +1,182 @@
// This file is Free Software under the MIT License
// without warranty, see README.md and LICENSES/MIT.txt for details.
//
// SPDX-License-Identifier: MIT
//
// SPDX-FileCopyrightText: 2022 German Federal Office for Information Security (BSI) <https://www.bsi.bund.de>
// Software-Engineering: 2022 Intevation GmbH <https://intevation.de>
package csaf
import (
"reflect"
"testing"
)
func TestProductTree_FindProductIdentificationHelpers(t *testing.T) {
type fields struct {
Branches Branches
FullProductNames *FullProductNames
RelationShips *Relationships
}
type args struct {
id ProductID
}
tests := []struct {
name string
fields fields
args args
want []*ProductIdentificationHelper
}{
{
name: "empty product tree",
args: args{
id: "CSAFPID-0001",
},
want: nil,
},
{
name: "product tree with matching full product names",
fields: fields{
FullProductNames: &FullProductNames{{
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
ProductIdentificationHelper: &ProductIdentificationHelper{
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
},
}},
},
args: args{
id: "CSAFPID-0001",
},
want: []*ProductIdentificationHelper{{
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
}},
},
{
name: "product tree with no matching full product names",
fields: fields{
FullProductNames: &FullProductNames{{
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
ProductIdentificationHelper: &ProductIdentificationHelper{
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
},
}},
},
args: args{
id: "CSAFPID-0002",
},
want: nil,
},
{
name: "product tree with matching branches",
fields: fields{
Branches: Branches{{
Name: &[]string{"beta"}[0],
Product: &FullProductName{
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
ProductIdentificationHelper: &ProductIdentificationHelper{
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
},
},
Branches: Branches{{
Name: &[]string{"beta-2"}[0],
Product: &FullProductName{
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
ProductIdentificationHelper: &ProductIdentificationHelper{
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta-2:*:*:*:*:*:*"}[0],
},
},
}},
}},
},
args: args{
id: "CSAFPID-0001",
},
want: []*ProductIdentificationHelper{{
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
}, {
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta-2:*:*:*:*:*:*"}[0],
}},
},
{
name: "product tree with no matching branches",
fields: fields{
Branches: Branches{{
Name: &[]string{"beta"}[0],
Product: &FullProductName{
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
ProductIdentificationHelper: &ProductIdentificationHelper{
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
},
},
Branches: Branches{{
Name: &[]string{"beta-2"}[0],
Product: &FullProductName{
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
ProductIdentificationHelper: &ProductIdentificationHelper{
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta-2:*:*:*:*:*:*"}[0],
},
},
}},
}},
},
args: args{
id: "CSAFPID-0002",
},
want: nil,
},
{
name: "product tree with matching relationships",
fields: fields{
RelationShips: &Relationships{{
FullProductName: &FullProductName{
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
ProductIdentificationHelper: &ProductIdentificationHelper{
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
},
},
}},
},
args: args{
id: "CSAFPID-0001",
},
want: []*ProductIdentificationHelper{{
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
}},
},
{
name: "product tree with no matching relationships",
fields: fields{
RelationShips: &Relationships{{
FullProductName: &FullProductName{
ProductID: &[]ProductID{"CSAFPID-0001"}[0],
ProductIdentificationHelper: &ProductIdentificationHelper{
CPE: &[]CPE{"cpe:2.3:a:microsoft:internet_explorer:1.0.0:beta:*:*:*:*:*:*"}[0],
},
},
}},
},
args: args{
id: "CSAFPID-0002",
},
want: nil,
},
}
t.Parallel()
for _, testToRun := range tests {
test := testToRun
t.Run(test.name, func(tt *testing.T) {
tt.Parallel()
pt := &ProductTree{
Branches: test.fields.Branches,
FullProductNames: test.fields.FullProductNames,
RelationShips: test.fields.RelationShips,
}
if got := pt.CollectProductIdentificationHelpers(test.args.id); !reflect.DeepEqual(got, test.want) {
tt.Errorf("ProductTree.FindProductIdentificationHelpers() = %v, want %v",
got, test.want)
}
})
}
}

View file

@ -9,9 +9,8 @@ import (
"os" "os"
"strings" "strings"
"golang.org/x/exp/slices"
"github.com/csaf-poc/csaf_distribution/v3/csaf" "github.com/csaf-poc/csaf_distribution/v3/csaf"
"github.com/csaf-poc/csaf_distribution/v3/util"
) )
func main() { func main() {
@ -35,106 +34,26 @@ func main() {
// run prints PURLs belonging to the given Product IDs. // run prints PURLs belonging to the given Product IDs.
func run(files []string, ids string) error { func run(files []string, ids string) error {
uf := newURLFinder(strings.Split(ids, ","))
for _, file := range files { for _, file := range files {
adv, err := csaf.LoadAdvisory(file) adv, err := csaf.LoadAdvisory(file)
if err != nil { if err != nil {
return fmt.Errorf("loading %q failed: %w", file, err) return fmt.Errorf("loading %q failed: %w", file, err)
} }
uf.findURLs(adv)
uf.dumpURLs() for _, id := range strings.Split(ids, ",") {
uf.clear() already := util.Set[csaf.PURL]{}
i := 0
adv.ProductTree.FindProductIdentificationHelpers(
csaf.ProductID(id),
func(h *csaf.ProductIdentificationHelper) {
if h.PURL != nil && !already.Contains(*h.PURL) {
already.Add(*h.PURL)
i++
fmt.Printf("%d. %s\n", i, *h.PURL)
}
})
}
} }
return nil return nil
} }
// urlFinder helps to find the URLs of a set of product ids in advisories.
type urlFinder struct {
ids []csaf.ProductID
urls [][]csaf.PURL
}
// newURLFinder creates a new urlFinder for given ids.
func newURLFinder(ids []string) *urlFinder {
uf := &urlFinder{
ids: make([]csaf.ProductID, len(ids)),
urls: make([][]csaf.PURL, len(ids)),
}
for i := range uf.ids {
uf.ids[i] = csaf.ProductID(ids[i])
}
return uf
}
// clear resets the url finder after a run on an advisory.
func (uf *urlFinder) clear() {
for i := range uf.urls {
uf.urls[i] = uf.urls[i][:0]
}
}
// dumpURLs dumps the found URLs to stdout.
func (uf *urlFinder) dumpURLs() {
for i, urls := range uf.urls {
if len(urls) == 0 {
continue
}
fmt.Printf("Found URLs for %s:\n", uf.ids[i])
for j, url := range urls {
fmt.Printf("%d. %s\n", j+1, url)
}
}
}
// findURLs find the URLs in an advisory.
func (uf *urlFinder) findURLs(adv *csaf.Advisory) {
tree := adv.ProductTree
if tree == nil {
return
}
// If we have found it and we have a valid URL add unique.
add := func(idx int, h *csaf.ProductIdentificationHelper) {
if idx != -1 && h != nil && h.PURL != nil &&
!slices.Contains(uf.urls[idx], *h.PURL) {
uf.urls[idx] = append(uf.urls[idx], *h.PURL)
}
}
// First iterate over full product names.
if names := tree.FullProductNames; names != nil {
for _, name := range *names {
if name != nil && name.ProductID != nil {
add(slices.Index(uf.ids, *name.ProductID), name.ProductIdentificationHelper)
}
}
}
// Second traverse the branches recursively.
var recBranch func(*csaf.Branch)
recBranch = func(b *csaf.Branch) {
if p := b.Product; p != nil && p.ProductID != nil {
add(slices.Index(uf.ids, *p.ProductID), p.ProductIdentificationHelper)
}
for _, c := range b.Branches {
recBranch(c)
}
}
for _, b := range tree.Branches {
recBranch(b)
}
// Third iterate over relationships.
if tree.RelationShips != nil {
for _, rel := range *tree.RelationShips {
if rel != nil {
if fpn := rel.FullProductName; fpn != nil && fpn.ProductID != nil {
add(slices.Index(uf.ids, *fpn.ProductID), fpn.ProductIdentificationHelper)
}
}
}
}
}