aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/cpe/cpe.go89
-rw-r--r--internal/cpe/cpe_test.go144
-rw-r--r--internal/cpe/part.go41
-rw-r--r--internal/cpe/part_string.go41
-rw-r--r--internal/cpe/part_test.go108
-rw-r--r--internal/cpe/token.go99
-rw-r--r--internal/cpe/token_test.go149
7 files changed, 438 insertions, 233 deletions
diff --git a/internal/cpe/cpe.go b/internal/cpe/cpe.go
index 73930e8..331f315 100644
--- a/internal/cpe/cpe.go
+++ b/internal/cpe/cpe.go
@@ -3,92 +3,3 @@
// 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
index e842c75..0f121b6 100644
--- a/internal/cpe/cpe_test.go
+++ b/internal/cpe/cpe_test.go
@@ -1,145 +1 @@
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/part.go b/internal/cpe/part.go
new file mode 100644
index 0000000..ef91f7c
--- /dev/null
+++ b/internal/cpe/part.go
@@ -0,0 +1,41 @@
+package cpe
+
+//go:generate stringer -linecomment -type=Part
+
+import (
+ "fmt"
+)
+
+// CPE part
+type Part byte
+
+const (
+ ApplicationPart Part = 'a' // a
+ OperatingSystemPart Part = 'o' // o
+ HardwarePart Part = 'h' // h
+ AnyPart Part = '*' // *
+ NAPart Part = '-' // -
+)
+
+// create new part from token
+func newPart(t token) (Part, error) {
+ switch t.Type {
+ case anyToken:
+ return AnyPart, nil
+ case naToken:
+ return NAPart, nil
+ case valToken:
+ switch t.Val {
+ case "a":
+ return ApplicationPart, nil
+ case "o":
+ return OperatingSystemPart, nil
+ case "h":
+ return HardwarePart, nil
+ default:
+ return 0, fmt.Errorf("unknown part: \"%s\"", t.Val)
+ }
+ default:
+ return 0, fmt.Errorf("unknown token type: 0x%02x", byte(t.Type))
+ }
+}
diff --git a/internal/cpe/part_string.go b/internal/cpe/part_string.go
new file mode 100644
index 0000000..98b9fd3
--- /dev/null
+++ b/internal/cpe/part_string.go
@@ -0,0 +1,41 @@
+// Code generated by "stringer -linecomment -type=Part"; 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[ApplicationPart-97]
+ _ = x[OperatingSystemPart-111]
+ _ = x[HardwarePart-104]
+ _ = x[AnyPart-42]
+ _ = x[NAPart-45]
+}
+
+const (
+ _Part_name_0 = "*"
+ _Part_name_1 = "-"
+ _Part_name_2 = "a"
+ _Part_name_3 = "h"
+ _Part_name_4 = "o"
+)
+
+func (i Part) String() string {
+ switch {
+ case i == 42:
+ return _Part_name_0
+ case i == 45:
+ return _Part_name_1
+ case i == 97:
+ return _Part_name_2
+ case i == 104:
+ return _Part_name_3
+ case i == 111:
+ return _Part_name_4
+ default:
+ return "Part(" + strconv.FormatInt(int64(i), 10) + ")"
+ }
+}
diff --git a/internal/cpe/part_test.go b/internal/cpe/part_test.go
new file mode 100644
index 0000000..269d16e
--- /dev/null
+++ b/internal/cpe/part_test.go
@@ -0,0 +1,108 @@
+package cpe
+
+import (
+ "testing"
+)
+
+func TestNewPart(t *testing.T) {
+ passTests := []struct {
+ name string
+ val token
+ exp Part
+ } {{
+ name: "any",
+ val: token { Type: anyToken },
+ exp: AnyPart,
+ }, {
+ name: "na",
+ val: token { Type: naToken },
+ exp: NAPart,
+ }, {
+ name: "a",
+ val: token { Type: valToken, Val: "a" },
+ exp: ApplicationPart,
+ }, {
+ name: "h",
+ val: token { Type: valToken, Val: "h" },
+ exp: HardwarePart,
+ }, {
+ name: "o",
+ val: token { Type: valToken, Val: "o" },
+ exp: OperatingSystemPart,
+ }}
+
+ for _, test := range(passTests) {
+ t.Run(test.name, func(t *testing.T) {
+ if got, err := newPart(test.val); err != nil {
+ t.Error(err)
+ } else if got != test.exp {
+ t.Errorf("got %s, exp %s", got, test.exp)
+ }
+ })
+ }
+
+ failTests := []struct {
+ name string
+ val token
+ exp string
+ } {{
+ name: "invalid token",
+ val: token { Type: valToken, Val: "foo" },
+ exp: "unknown part: \"foo\"",
+ }, {
+ name: "empty token",
+ val: token { Type: valToken },
+ exp: "unknown part: \"\"",
+ }, {
+ name: "unknown token type",
+ val: token { Type: tokenType(255) },
+ exp: "unknown token type: 0xff",
+ }}
+
+ for _, test := range(failTests) {
+ t.Run(test.name, func(t *testing.T) {
+ // tokenize, check for error
+ got, err := newPart(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)
+ }
+ })
+ }
+}
+
+func TestPartString(t *testing.T) {
+ tests := []struct {
+ val Part
+ exp string
+ } {{
+ val: AnyPart,
+ exp: "*",
+ }, {
+ val: NAPart,
+ exp: "-",
+ }, {
+ val: ApplicationPart,
+ exp: "a",
+ }, {
+ val: HardwarePart,
+ exp: "h",
+ }, {
+ val: OperatingSystemPart,
+ exp: "o",
+ }, {
+ val: Part(255),
+ exp: "Part(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)
+ }
+ })
+ }
+}
diff --git a/internal/cpe/token.go b/internal/cpe/token.go
new file mode 100644
index 0000000..d88e773
--- /dev/null
+++ b/internal/cpe/token.go
@@ -0,0 +1,99 @@
+package cpe
+
+//go:generate stringer -linecomment -type=tokenType
+
+import (
+ "errors"
+ "fmt"
+)
+
+// 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 }
+ }
+}
+
+// unterminated escape error
+var unterminatedEsc = errors.New("unterminated escape at end of buffer")
+
+// Parse buffer into list 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)
+ }
+ }
+ }
+
+ // check for unterminated escape
+ if esc {
+ return r, unterminatedEsc
+ }
+
+ 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/token_test.go b/internal/cpe/token_test.go
new file mode 100644
index 0000000..595df2c
--- /dev/null
+++ b/internal/cpe/token_test.go
@@ -0,0 +1,149 @@
+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",
+ }, {
+ id: "unterminated escape",
+ val: "\\",
+ exp: "unterminated escape at end of buffer",
+ }}
+
+ 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)
+ }
+ })
+ }
+}