mirror of
https://github.com/gocsaf/csaf.git
synced 2025-12-22 05:40:11 +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:
parent
b457dc872f
commit
9073a8a282
3 changed files with 258 additions and 96 deletions
61
csaf/util.go
61
csaf/util.go
|
|
@ -36,3 +36,64 @@ func ExtractProviderURL(r io.Reader, all bool) ([]string, error) {
|
|||
}
|
||||
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
182
csaf/util_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,8 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/csaf-poc/csaf_distribution/v3/csaf"
|
||||
"github.com/csaf-poc/csaf_distribution/v3/util"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
@ -35,106 +34,26 @@ func main() {
|
|||
|
||||
// run prints PURLs belonging to the given Product IDs.
|
||||
func run(files []string, ids string) error {
|
||||
|
||||
uf := newURLFinder(strings.Split(ids, ","))
|
||||
|
||||
for _, file := range files {
|
||||
adv, err := csaf.LoadAdvisory(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("loading %q failed: %w", file, err)
|
||||
}
|
||||
uf.findURLs(adv)
|
||||
uf.dumpURLs()
|
||||
uf.clear()
|
||||
|
||||
for _, id := range strings.Split(ids, ",") {
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue