// 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 }