Fix string field value escaping
Commas and quotes could get escaped and parsed incorrectly if they were both present in a string value. Fixes #3013pull/3088/head
parent
cb9a40df64
commit
2854108941
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
"hash/fnv"
|
||||
"math"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -55,6 +56,9 @@ type point struct {
|
|||
data []byte
|
||||
}
|
||||
|
||||
// Compile the regex that detects unquoted double quote sequences
|
||||
var quoteReplacer = regexp.MustCompile(`([^\\])"`)
|
||||
|
||||
var escapeCodes = map[byte][]byte{
|
||||
',': []byte(`\,`),
|
||||
'"': []byte(`\"`),
|
||||
|
@ -604,7 +608,8 @@ func scanFieldValue(buf []byte, i int) (int, []byte) {
|
|||
break
|
||||
}
|
||||
|
||||
if buf[i] == '"' {
|
||||
// If we see a double quote, makes sure it is not escaped
|
||||
if buf[i] == '"' && buf[i-1] != '\\' {
|
||||
i += 1
|
||||
quoted = !quoted
|
||||
continue
|
||||
|
@ -651,6 +656,21 @@ func unescapeString(in string) string {
|
|||
return in
|
||||
}
|
||||
|
||||
// escapeQuoteString returns a copy of in with any double quotes that
|
||||
// have not been escaped with escaped quotes
|
||||
func escapeQuoteString(in string) string {
|
||||
if strings.IndexAny(in, `"`) == -1 {
|
||||
return in
|
||||
}
|
||||
return quoteReplacer.ReplaceAllString(in, `$1\"`)
|
||||
}
|
||||
|
||||
// unescapeQuoteString returns a copy of in with any escaped double-quotes
|
||||
// with unescaped double quotes
|
||||
func unescapeQuoteString(in string) string {
|
||||
return strings.Replace(in, `\"`, `"`, -1)
|
||||
}
|
||||
|
||||
// NewPoint returns a new point with the given measurement name, tags, fiels and timestamp
|
||||
func NewPoint(name string, tags Tags, fields Fields, time time.Time) Point {
|
||||
return &point{
|
||||
|
@ -895,7 +915,7 @@ func newFieldsFromBinary(buf []byte) Fields {
|
|||
|
||||
// If the first char is a double-quote, then unmarshal as string
|
||||
if valueBuf[0] == '"' {
|
||||
value = unescapeString(string(valueBuf[1 : len(valueBuf)-1]))
|
||||
value = unescapeQuoteString(string(valueBuf[1 : len(valueBuf)-1]))
|
||||
// Check for numeric characters
|
||||
} else if (valueBuf[0] >= '0' && valueBuf[0] <= '9') || valueBuf[0] == '-' || valueBuf[0] == '.' {
|
||||
value, err = parseNumber(valueBuf)
|
||||
|
@ -955,7 +975,7 @@ func (p Fields) MarshalBinary() []byte {
|
|||
b = append(b, t...)
|
||||
case string:
|
||||
b = append(b, '"')
|
||||
b = append(b, []byte(t)...)
|
||||
b = append(b, []byte(escapeQuoteString(t))...)
|
||||
b = append(b, '"')
|
||||
case nil:
|
||||
// skip
|
||||
|
|
|
@ -453,7 +453,7 @@ func TestParsePointWithStringWithCommas(t *testing.T) {
|
|||
},
|
||||
Fields{
|
||||
"value": 1.0,
|
||||
"str": "foo,bar", // commas in string value
|
||||
"str": `foo\,bar`, // commas in string value
|
||||
},
|
||||
time.Unix(1, 0)),
|
||||
)
|
||||
|
@ -475,6 +475,37 @@ func TestParsePointWithStringWithCommas(t *testing.T) {
|
|||
|
||||
}
|
||||
|
||||
func TestParsePointEscapedStringsAndCommas(t *testing.T) {
|
||||
// non-escaped comma and quotes
|
||||
test(t, `cpu,host=serverA,region=us-east value="{Hello\"{,}\" World}" 1000000000`,
|
||||
NewPoint(
|
||||
"cpu",
|
||||
Tags{
|
||||
"host": "serverA",
|
||||
"region": "us-east",
|
||||
},
|
||||
Fields{
|
||||
"value": `{Hello"{,}" World}`,
|
||||
},
|
||||
time.Unix(1, 0)),
|
||||
)
|
||||
|
||||
// escaped comma and quotes
|
||||
test(t, `cpu,host=serverA,region=us-east value="{Hello\"{\,}\" World}" 1000000000`,
|
||||
NewPoint(
|
||||
"cpu",
|
||||
Tags{
|
||||
"host": "serverA",
|
||||
"region": "us-east",
|
||||
},
|
||||
Fields{
|
||||
"value": `{Hello"{\,}" World}`,
|
||||
},
|
||||
time.Unix(1, 0)),
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
func TestParsePointWithStringWithEquals(t *testing.T) {
|
||||
test(t, `cpu,host=serverA,region=us-east str="foo=bar",value=1.0, 1000000000`,
|
||||
NewPoint(
|
||||
|
|
Loading…
Reference in New Issue