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 /internal | |
| parent | 1b9eb2eddf322560c37efa2ef2eb63d5ab661be8 (diff) | |
| download | cvez-b6496c2d20904e665116133a9e9bf9ae6e3b45b8.tar.xz cvez-b6496c2d20904e665116133a9e9bf9ae6e3b45b8.zip | |
add internal/cpe
Diffstat (limited to 'internal')
| -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]] +} | 
