Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions toolkit/types/cpe/bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package cpe

import (
"testing"
)

const (
// Test string where all value strings can be copied directly.
cpeFS = `cpe:2.3:a:foo\\bar:big\$money_2010:*:*:*:*:special:ipod_touch:80gb:*`
// Test string that needs additional escaping for the unbound form.
cpeEscapeFS = `cpe:2.3:a:hp:insight_diagnostics:7.4.0.1570:-:*:*:online:win2003:x64:*`
)

func BenchmarkUnbindFS(b *testing.B) {
inner := func(in string) func(*testing.B) {
return func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
out, err := UnbindFS(in)
if err != nil {
b.Error(err)
}
_ = out
}
}
}
b.Run("Simple", inner(cpeFS))
b.Run("Escape", inner(cpeEscapeFS))
}

func BenchmarkUnmarshalFS(b *testing.B) {
inner := func(in string) func(*testing.B) {
return func(b *testing.B) {
b.ReportAllocs()
for b.Loop() {
var out WFN
if err := out.UnmarshalFS(in); err != nil {
b.Error(err)
}
}
}
}
b.Run("Simple", inner(cpeFS))
b.Run("Escape", inner(cpeEscapeFS))
}
2 changes: 1 addition & 1 deletion toolkit/types/cpe/bind.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "strings"
func (w WFN) BindFS() string {
b := strings.Builder{}
b.WriteString(`cpe:2.3`)
for i := 0; i < NumAttr; i++ {
for i := range NumAttr {
b.WriteByte(':')
w.Attr[i].bind(&b)
}
Expand Down
1 change: 1 addition & 0 deletions toolkit/types/cpe/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ package cpe
//go:generate go tool stringer -type ValueKind
//go:generate go tool stringer -type Relation -linecomment
//go:generate go tool cpedict
//go:generate go tool mkragel unbind_fs.rl
9 changes: 4 additions & 5 deletions toolkit/types/cpe/marshaling.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,13 @@ func (w *WFN) UnmarshalText(b []byte) (err error) {
// Scan implements [sql.Scanner].
//
// Passing an empty string does not error and leaves the WFN in its current state.
func (w *WFN) Scan(src interface{}) (err error) {
func (w *WFN) Scan(src any) (err error) {
var s string
switch src.(type) {
switch src := src.(type) {
case []byte:
s = string(src.([]byte))
s = strings.ToValidUTF8(s, "�")
s = strings.ToValidUTF8(string(src), "�")
case string:
s = src.(string)
s = src
default:
return fmt.Errorf("cpe: unable to Scan from type %T", src)
}
Expand Down
74 changes: 38 additions & 36 deletions toolkit/types/cpe/unbind.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cpe
import (
"fmt"
"strings"
"unicode"
)

const (
Expand All @@ -17,7 +18,8 @@ func Unbind(s string) (WFN, error) {
case strings.HasPrefix(s, cpe22Prefix):
return UnbindURI(s)
case strings.HasPrefix(s, cpe23Prefix):
return UnbindFS(s)
var wfn WFN
return wfn, wfn.UnmarshalFS(s)
default:
}
return WFN{}, fmt.Errorf("cpe: string does not appear to be a bound WFN: %q", s)
Expand Down Expand Up @@ -153,21 +155,41 @@ var valueURI = strings.NewReplacer(
)

// UnbindFS attempts to unbind a string as CPE 2.3 formatted string into a WFN.
//
// Deprecated: Use [WFN.UnmarshalFS].
func UnbindFS(s string) (WFN, error) {
r := WFN{}
wfn := WFN{}
if !strings.HasPrefix(s, cpe23Prefix) {
return r, fmt.Errorf("cpe: malformed CPE formatted string: bad prefix")
}
fs := splitFS(s)
if l := len(fs); l != 13 {
return r, fmt.Errorf("cpe: malformed CPE formatted string: bad components: %d != 13", l)
return wfn, fmt.Errorf("cpe: malformed CPE formatted string: bad prefix")
}
fs = fs[2:13] // Skip the first two segments, "cpe" and "2.3".
s = s[len(cpe23Prefix):]
var b strings.Builder
for i, c := range fs {
r.Attr[i].unbindFS(&b, c)
a := 0
prev, esc := 0, false
for i, r := range s {
switch {
case r >= unicode.MaxASCII:
return wfn, fmt.Errorf("cpe: malformed CPE formatted string: invalid character %q @ %d", r, i)
case r == '\\':
esc = true
continue
case r == ':':
if esc {
break
}
wfn.Attr[a].unbindFS(&b, s[prev:i])
a++
if a == NumAttr {
return wfn, fmt.Errorf("cpe: malformed CPE formatted string: bad components: >13")
}
prev = i + 1
default:
}
esc = false
}
return r, r.Valid()
wfn.Attr[a].unbindFS(&b, s[prev:])

return wfn, wfn.Valid()
}

// UnbindFS undoes the FS binding and assigns it to v.
Expand All @@ -185,34 +207,14 @@ func (v *Value) unbindFS(b *strings.Builder, s string) {
}
}

// SplitFS splits a string in to unquoted-colon separated segments.
func splitFS(s string) []string {
var fs []string
prev, esc := 0, false
for i, r := range s {
switch r {
case '\\':
esc = true
continue
case ':':
if esc {
break
}
fs = append(fs, s[prev:i])
prev = i + 1
default:
}
esc = false
}
fs = append(fs, s[prev:])
return fs
}

// UnbindFSValue does what it says on the tin.
//
// Caller provides scratch space for the return construction via the passed
// strings.Builder.
func unbindFSValue(b *strings.Builder, s string) string {
if !strings.ContainsFunc(s, reserved) {
return s
}
b.Reset()
esc := false
for _, r := range s {
Expand All @@ -221,14 +223,14 @@ func unbindFSValue(b *strings.Builder, s string) string {
switch {
case r == '\\':
esc = true
b.WriteRune('\\')
b.WriteByte('\\')
continue
case r == '*' || r == '?':
fallthrough
case esc || !reserved(r):
b.WriteRune(r)
default:
b.WriteRune('\\')
b.WriteByte('\\')
b.WriteRune(r)
}
esc = false
Expand Down
Loading
Loading