Fix string field value escaping

Commas and quotes could get escaped and parsed incorrectly if they
were both present in a string value.

Fixes #3013
pull/3088/head
Jason Wilder 2015-06-22 12:22:03 -06:00
parent cb9a40df64
commit 2854108941
2 changed files with 55 additions and 4 deletions

View File

@ -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

View File

@ -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(