1
0
Fork 0
mirror of https://github.com/gocsaf/csaf.git synced 2025-12-22 11:55:40 +01:00

Merge pull request #307 from csaf-poc/issue299

Extract more than one string from expr: category fields.
This commit is contained in:
JanHoefelmeyer 2023-01-27 09:24:06 +01:00 committed by GitHub
commit ddf0747327
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 83 additions and 10 deletions

View file

@ -451,23 +451,32 @@ func (w *worker) extractCategories(label string, advisory any) error {
w.categories[label] = cats w.categories[label] = cats
} }
var result string
matcher := util.StringMatcher(&result)
const exprPrefix = "expr:" const exprPrefix = "expr:"
var dynamic []string
matcher := util.StringTreeMatcher(&dynamic)
for _, cat := range categories { for _, cat := range categories {
if strings.HasPrefix(cat, exprPrefix) { if strings.HasPrefix(cat, exprPrefix) {
expr := cat[len(exprPrefix):] expr := cat[len(exprPrefix):]
if err := w.expr.Extract(expr, matcher, true, advisory); err != nil { // Compile first to check that the expression is okay.
return err if _, err := w.expr.Compile(expr); err != nil {
fmt.Printf("Compiling category expression %q failed: %v\n",
expr, err)
continue
} }
cats[result] = true // Ignore errors here as they result from not matching.
w.expr.Extract(expr, matcher, true, advisory)
} else { // Normal } else { // Normal
cats[cat] = true cats[cat] = true
} }
} }
// Add dynamic categories.
for _, cat := range dynamic {
cats[cat] = true
}
return nil return nil
} }

View file

@ -194,10 +194,17 @@ func (c *controller) upload(r *http.Request) (any, error) {
// Check if we have to search for dynamic categories. // Check if we have to search for dynamic categories.
var dynamicCategories []string var dynamicCategories []string
if catExprs := c.cfg.DynamicCategories(); len(catExprs) > 0 { if catExprs := c.cfg.DynamicCategories(); len(catExprs) > 0 {
var err error matcher := util.StringTreeMatcher(&dynamicCategories)
if dynamicCategories, err = pe.Strings(catExprs, true, content); err != nil {
// XXX: Should we die here? for _, expr := range catExprs {
log.Printf("eval of dynamic catecory expressions failed: %v\n", err) // Compile first to check that the expression is okay.
if _, err := pe.Compile(expr); err != nil {
log.Printf("Compiling category expression %q failed: %v\n",
expr, err)
continue
}
// Ignore errors here as they result from not matching.
pe.Extract(expr, matcher, true, content)
} }
} }

View file

@ -106,6 +106,18 @@ The following example file documents all available configuration options:
# If a list item starts with `expr:` # If a list item starts with `expr:`
# the rest of the string is used as a JsonPath expression # the rest of the string is used as a JsonPath expression
# to extract a string from the incoming advisories. # to extract a string from the incoming advisories.
# If the result of the expression is a string this string
# is used. If the result is an array each element of
# this array is tested if it is a string or an array.
# If this test fails the expression fails. If the
# test succeeds the rules are applied recursively to
# collect all strings in the result.
# Suggested expressions are:
# - vendor, product family and product names: "expr:$.product_tree..branches[?(@.category==\"vendor\" || @.category==\"product_family\" || @.category==\"product_name\")].name"
# - CVEs: "expr:$.vulnerabilities[*].cve"
# - CWEs: "expr:$.vulnerabilities[*].cwe.id"
# The used implementation to evaluate JSONPath expressions does
# not support the use of single-quotes. Double quotes have to be quoted.
# Strings not starting with `expr:` are taken verbatim. # Strings not starting with `expr:` are taken verbatim.
# By default no category documents are created. # By default no category documents are created.
# This example provides an overview over the syntax, # This example provides an overview over the syntax,

View file

@ -42,6 +42,20 @@ func NewPathEval() *PathEval {
} }
} }
// Compile compiles an expression and stores it in the
// internal cache on success.
func (pe *PathEval) Compile(expr string) (gval.Evaluable, error) {
if eval := pe.exprs[expr]; eval != nil {
return eval, nil
}
eval, err := pe.builder.NewEvaluable(expr)
if err != nil {
return nil, err
}
pe.exprs[expr] = eval
return eval, nil
}
// Eval evalutes expression expr on document doc. // Eval evalutes expression expr on document doc.
// Returns the result of the expression. // Returns the result of the expression.
func (pe *PathEval) Eval(expr string, doc any) (any, error) { func (pe *PathEval) Eval(expr string, doc any) (any, error) {
@ -101,6 +115,37 @@ func StringMatcher(dst *string) func(any) error {
} }
} }
// StringTreeMatcher returns a matcher which adds strings
// to a slice and recursively strings from arrays of strings.
func StringTreeMatcher(strings *[]string) func(any) error {
// Only add unique strings.
unique := func(s string) {
for _, t := range *strings {
if s == t {
return
}
}
*strings = append(*strings, s)
}
var recurse func(any) error
recurse = func(x any) error {
switch y := x.(type) {
case string:
unique(y)
case []any:
for _, z := range y {
if err := recurse(z); err != nil {
return err
}
}
default:
return fmt.Errorf("unsupported type: %T", x)
}
return nil
}
return recurse
}
// TimeMatcher stores a time with a given format. // TimeMatcher stores a time with a given format.
func TimeMatcher(dst *time.Time, format string) func(any) error { func TimeMatcher(dst *time.Time, format string) func(any) error {
return func(x any) error { return func(x any) error {