aboutsummaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
Diffstat (limited to 'internal')
-rw-r--r--internal/cpe/cpe.go94
-rw-r--r--internal/cpe/cpe_test.go145
-rw-r--r--internal/cpe/tokentype_string.go25
3 files changed, 264 insertions, 0 deletions
diff --git a/internal/cpe/cpe.go b/internal/cpe/cpe.go
new file mode 100644
index 0000000..73930e8
--- /dev/null
+++ b/internal/cpe/cpe.go
@@ -0,0 +1,94 @@
+// CPE 2.3 formatted string parser.
+//
+// Source: NISTIR 7605, figure 6-3:
+// https://nvlpubs.nist.gov/nistpubs/Legacy/IR/nistir7695.pdf
+package cpe
+
+import (
+ "fmt"
+)
+
+//go:generate stringer -linecomment -type=tokenType
+
+// token type
+type tokenType byte
+
+const (
+ anyToken tokenType = iota // any
+ naToken // na
+ valToken // val
+)
+
+// token
+type token struct {
+ Type tokenType // token type
+ Val string // token value
+}
+
+// parse buffer into token.
+func newToken(val []byte) token {
+ if len(val) > 0 {
+ switch val[0] {
+ case '*':
+ return token { Type: anyToken }
+ case '-':
+ return token { Type: naToken }
+ default:
+ return token { Type: valToken, Val: string(val) }
+ }
+ } else {
+ // empty value
+ return token { Type: valToken, Val: "" }
+ }
+}
+
+// Parse buffer into slice of tokens.
+func tokenize(buf []byte) ([]token, error) {
+ // build result
+ var r []token
+
+ // current token and escape state
+ var curr []byte
+ esc := false
+
+ // build result
+ for _, b := range(buf) {
+ if esc {
+ switch b {
+ // valid escaped characters
+ case '\\', '*', '-', '!', '"', '#', '$', '%', '&', '\'', '(', ')',
+ '+', ',', '/', ':', ';', '<', '=', '>', '@', '[', ']', '^', '`',
+ '{', '|', '}', '~':
+ curr = append(curr, b)
+ esc = false
+ default:
+ return r, fmt.Errorf("invalid escape byte: 0x%02x", b)
+ }
+ } else {
+ switch b {
+ case '\\':
+ esc = true
+ case ':':
+ // push token, clear buffer
+ r = append(r, newToken(curr))
+ curr = nil
+ case 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
+ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x',
+ 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '-', '.', '_', '*', '?':
+ curr = append(curr, b)
+ default:
+ return r, fmt.Errorf("invalid byte: 0x%02x", b)
+ }
+ }
+ }
+
+ if len(curr) > 0 {
+ // push token, clear buffer
+ r = append(r, newToken(curr))
+ curr = nil
+ }
+
+ // return success
+ return r, nil
+}
diff --git a/internal/cpe/cpe_test.go b/internal/cpe/cpe_test.go
new file mode 100644
index 0000000..e842c75
--- /dev/null
+++ b/internal/cpe/cpe_test.go
@@ -0,0 +1,145 @@
+package cpe
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestTokenTypeString(t *testing.T) {
+ tests := []struct {
+ val tokenType
+ exp string
+ } {
+ { anyToken, "any" },
+ { naToken, "na" },
+ { valToken, "val" },
+ { tokenType(255), "tokenType(255)" },
+ }
+
+ for _, test := range(tests) {
+ t.Run(test.exp, func(t *testing.T) {
+ got := test.val.String()
+ if got != test.exp {
+ t.Errorf("got \"%s\", exp \"%s\"", got, test.exp)
+ }
+ })
+ }
+}
+
+func TestNewToken(t *testing.T) {
+ passTests := []struct {
+ name string
+ val string
+ exp token
+ } {
+ { "any", "*", token { Type: anyToken } },
+ { "na", "-", token { Type: naToken } },
+ { "empty", "", token { Type: valToken } },
+ { "foo", "foo", token { Type: valToken, Val: "foo" } },
+ }
+
+ for _, test := range(passTests) {
+ t.Run(test.name, func(t *testing.T) {
+ got := newToken([]byte(test.val))
+ if got.Type != test.exp.Type {
+ t.Errorf("token: got %s, exp %s", got.Type, test.exp.Type)
+ } else if got.Type == valToken && got.Val != test.exp.Val {
+ t.Errorf("value: got \"%s\", exp \"%s\"", got.Val, test.exp.Val)
+ }
+ })
+ }
+}
+
+func TestTokenize(t *testing.T) {
+ passTests := []struct {
+ val string
+ exp []token
+ } {{
+ val: "foo",
+ exp: []token { token { Type: valToken, Val: "foo" } },
+ }, {
+ val: "foo:bar",
+ exp: []token {
+ token { Type: valToken, Val: "foo" },
+ token { Type: valToken, Val: "bar" },
+ },
+ }, {
+ val: "*",
+ exp: []token { token { Type: anyToken } },
+ }, {
+ val: "-",
+ exp: []token { token { Type: naToken } },
+ }, {
+ val: "*:bar",
+ exp: []token {
+ token { Type: anyToken },
+ token { Type: valToken, Val: "bar" },
+ },
+ }, {
+ val: "foo:*",
+ exp: []token {
+ token { Type: valToken, Val: "foo" },
+ token { Type: anyToken },
+ },
+ }, {
+ val: "-:bar",
+ exp: []token {
+ token { Type: naToken },
+ token { Type: valToken, Val: "bar" },
+ },
+ }, {
+ val: "foo:-",
+ exp: []token {
+ token { Type: valToken, Val: "foo" },
+ token { Type: naToken },
+ },
+ }, {
+ val: "foo\\*:-",
+ exp: []token {
+ token { Type: valToken, Val: "foo*" },
+ token { Type: naToken },
+ },
+ }}
+
+ for _, test := range(passTests) {
+ t.Run(test.val, func(t *testing.T) {
+ // tokenize, check for error
+ got, err := tokenize([]byte(test.val))
+ if err != nil {
+ t.Error(err)
+ return
+ }
+
+ if !reflect.DeepEqual(got, test.exp) {
+ t.Errorf("token: got %v, exp %v", got, test.exp)
+ return
+ }
+ })
+ }
+
+ failTests := []struct {
+ id string
+ val string
+ exp string
+ } {{
+ id: "invalid escape",
+ val: "foo\\.",
+ exp: "invalid escape byte: 0x2e",
+ }, {
+ id: "invalid byte",
+ val: "\n",
+ exp: "invalid byte: 0x0a",
+ }}
+
+ for _, test := range(failTests) {
+ t.Run(test.id, func(t *testing.T) {
+ // tokenize, check for error
+ got, err := tokenize([]byte(test.val))
+ if err == nil {
+ t.Errorf("got %v, exp error", got)
+ } else if err.Error() != test.exp {
+ t.Errorf("got \"%s\", exp \"%s\"", err.Error(), test.exp)
+ }
+ })
+ }
+}
diff --git a/internal/cpe/tokentype_string.go b/internal/cpe/tokentype_string.go
new file mode 100644
index 0000000..7b53758
--- /dev/null
+++ b/internal/cpe/tokentype_string.go
@@ -0,0 +1,25 @@
+// Code generated by "stringer -linecomment -type=tokenType"; DO NOT EDIT.
+
+package cpe
+
+import "strconv"
+
+func _() {
+ // An "invalid array index" compiler error signifies that the constant values have changed.
+ // Re-run the stringer command to generate them again.
+ var x [1]struct{}
+ _ = x[anyToken-0]
+ _ = x[naToken-1]
+ _ = x[valToken-2]
+}
+
+const _tokenType_name = "anynaval"
+
+var _tokenType_index = [...]uint8{0, 3, 5, 8}
+
+func (i tokenType) String() string {
+ if i >= tokenType(len(_tokenType_index)-1) {
+ return "tokenType(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+ return _tokenType_name[_tokenType_index[i]:_tokenType_index[i+1]]
+}