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"
|
"fmt"
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"math"
|
"math"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -55,6 +56,9 @@ type point struct {
|
||||||
data []byte
|
data []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compile the regex that detects unquoted double quote sequences
|
||||||
|
var quoteReplacer = regexp.MustCompile(`([^\\])"`)
|
||||||
|
|
||||||
var escapeCodes = map[byte][]byte{
|
var escapeCodes = map[byte][]byte{
|
||||||
',': []byte(`\,`),
|
',': []byte(`\,`),
|
||||||
'"': []byte(`\"`),
|
'"': []byte(`\"`),
|
||||||
|
@ -604,7 +608,8 @@ func scanFieldValue(buf []byte, i int) (int, []byte) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if buf[i] == '"' {
|
// If we see a double quote, makes sure it is not escaped
|
||||||
|
if buf[i] == '"' && buf[i-1] != '\\' {
|
||||||
i += 1
|
i += 1
|
||||||
quoted = !quoted
|
quoted = !quoted
|
||||||
continue
|
continue
|
||||||
|
@ -651,6 +656,21 @@ func unescapeString(in string) string {
|
||||||
return in
|
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
|
// 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 {
|
func NewPoint(name string, tags Tags, fields Fields, time time.Time) Point {
|
||||||
return &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 the first char is a double-quote, then unmarshal as string
|
||||||
if valueBuf[0] == '"' {
|
if valueBuf[0] == '"' {
|
||||||
value = unescapeString(string(valueBuf[1 : len(valueBuf)-1]))
|
value = unescapeQuoteString(string(valueBuf[1 : len(valueBuf)-1]))
|
||||||
// Check for numeric characters
|
// Check for numeric characters
|
||||||
} else if (valueBuf[0] >= '0' && valueBuf[0] <= '9') || valueBuf[0] == '-' || valueBuf[0] == '.' {
|
} else if (valueBuf[0] >= '0' && valueBuf[0] <= '9') || valueBuf[0] == '-' || valueBuf[0] == '.' {
|
||||||
value, err = parseNumber(valueBuf)
|
value, err = parseNumber(valueBuf)
|
||||||
|
@ -955,7 +975,7 @@ func (p Fields) MarshalBinary() []byte {
|
||||||
b = append(b, t...)
|
b = append(b, t...)
|
||||||
case string:
|
case string:
|
||||||
b = append(b, '"')
|
b = append(b, '"')
|
||||||
b = append(b, []byte(t)...)
|
b = append(b, []byte(escapeQuoteString(t))...)
|
||||||
b = append(b, '"')
|
b = append(b, '"')
|
||||||
case nil:
|
case nil:
|
||||||
// skip
|
// skip
|
||||||
|
|
|
@ -453,7 +453,7 @@ func TestParsePointWithStringWithCommas(t *testing.T) {
|
||||||
},
|
},
|
||||||
Fields{
|
Fields{
|
||||||
"value": 1.0,
|
"value": 1.0,
|
||||||
"str": "foo,bar", // commas in string value
|
"str": `foo\,bar`, // commas in string value
|
||||||
},
|
},
|
||||||
time.Unix(1, 0)),
|
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) {
|
func TestParsePointWithStringWithEquals(t *testing.T) {
|
||||||
test(t, `cpu,host=serverA,region=us-east str="foo=bar",value=1.0, 1000000000`,
|
test(t, `cpu,host=serverA,region=us-east str="foo=bar",value=1.0, 1000000000`,
|
||||||
NewPoint(
|
NewPoint(
|
||||||
|
|
Loading…
Reference in New Issue