diff options
author | Paul Duncan <pabs@pablotron.org> | 2022-02-02 04:08:55 -0500 |
---|---|---|
committer | Paul Duncan <pabs@pablotron.org> | 2022-02-02 04:08:55 -0500 |
commit | b6496c2d20904e665116133a9e9bf9ae6e3b45b8 (patch) | |
tree | 72ca645269e545e494e1d6d91145134584abc673 | |
parent | 1b9eb2eddf322560c37efa2ef2eb63d5ab661be8 (diff) | |
download | cvez-b6496c2d20904e665116133a9e9bf9ae6e3b45b8.tar.bz2 cvez-b6496c2d20904e665116133a9e9bf9ae6e3b45b8.zip |
add internal/cpe
-rw-r--r-- | internal/cpe/cpe.go | 94 | ||||
-rw-r--r-- | internal/cpe/cpe_test.go | 145 | ||||
-rw-r--r-- | internal/cpe/tokentype_string.go | 25 |
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]] +} |