Skip to content

Commit

Permalink
Fix string field value escaping
Browse files Browse the repository at this point in the history
Commas and quotes could get escaped and parsed incorrectly if they
were both present in a string value.

Fixes #3013
  • Loading branch information
jwilder committed Jun 22, 2015
1 parent cb9a40d commit 2854108
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 4 deletions.
26 changes: 23 additions & 3 deletions tsdb/points.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"hash/fnv"
"math"
"regexp"
"sort"
"strconv"
"strings"
Expand Down Expand Up @@ -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(`\"`),
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
33 changes: 32 additions & 1 deletion tsdb/points_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
)
Expand All @@ -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(
Expand Down

0 comments on commit 2854108

Please sign in to comment.