Skip to content

Commit

Permalink
Fix inserting string values with backslashes
Browse files Browse the repository at this point in the history
Fixes #3682
  • Loading branch information
jwilder committed Aug 19, 2015
1 parent 5b11d25 commit b38e81e
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 33 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ Please see the *Features* section below for full details.
- [#3697](https://github.com/influxdb/influxdb/issues/3697): Correctly merge non-chunked results for same series. Fix issue #3242.
- [#3708](https://github.com/influxdb/influxdb/issues/3708): Fix double escaping measurement name during cluster replication
- [#3704](https://github.com/influxdb/influxdb/issues/3704): cluster replication issue for measurement name containing backslash

>>>>>>> Fix measurement name being double-escaped during replication
- [#3681](https://github.com/influxdb/influxdb/issues/3681): Quoted measurement names fail
- [#3681](https://github.com/influxdb/influxdb/issues/3682): Fix inserting string value with backslashes

## v0.9.2 [2015-07-24]

Expand Down
2 changes: 1 addition & 1 deletion tsdb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Field keys are always strings and follow the same syntactical rules as described
* _float_ - Numeric values tha are not followed by a trailing i. (e.g. 1, 1.0, -3.14, 6.0+e5, 10).
* _boolean_ - A value indicating true or false. Valid boolean strings are (t, T, true, TRUE, f, F, false, and FALSE).
* _string_ - A text value. All string values _must_ be surrounded in double-quotes `"`. If the string contains
a double-quote, it must be escaped with a backslash, e.g. `\"`.
a double-quote or backslashes, it must be escaped with a backslash, e.g. `\"`, `\\`.


```
Expand Down
121 changes: 91 additions & 30 deletions tsdb/points.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,14 @@ func ParsePointsWithPrecision(buf []byte, defaultTime time.Time, precision strin
continue
}

pt, err := parsePoint(block, defaultTime, precision)
// strip the newline if one is present
if block[len(block)-1] == '\n' {
block = block[:len(block)-1]
}

pt, err := parsePoint(block[start:len(block)], defaultTime, precision)
if err != nil {
return nil, fmt.Errorf("unable to parse '%s': %v", string(block), err)
return nil, fmt.Errorf("unable to parse '%s': %v", string(block[start:len(block)]), err)
}
points = append(points, pt)

Expand Down Expand Up @@ -400,6 +405,15 @@ func less(buf []byte, indices []int, i, j int) bool {
return bytes.Compare(a, b) < 0
}

func isFieldEscapeChar(b byte) bool {
for c := range escapeCodes {
if c == b {
return true
}
}
return false
}

// scanFields scans buf, starting at i for the fields section of a point. It returns
// the ending position and the byte slice of the fields within buf
func scanFields(buf []byte, i int) (int, []byte, error) {
Expand All @@ -419,10 +433,18 @@ func scanFields(buf []byte, i int) (int, []byte, error) {
break
}

// escaped character
if buf[i] == '\\' {
i += 2
continue
// escaped characters?
if buf[i] == '\\' && i+1 < len(buf) {

// Is this an escape char within a string field? Only " and \ are allowed.
if quoted && (buf[i+1] == '"' || buf[i+1] == '\\') {
i += 2
continue
// Non-string field escaped chars
} else if !quoted && isFieldEscapeChar(buf[i+1]) {
i += 2
continue
}
}

// If the value is quoted, scan until we get to the end quote
Expand Down Expand Up @@ -724,11 +746,6 @@ func scanLine(buf []byte, i int) (int, []byte) {
continue
}

if buf[i] == '\\' {
i += 2
continue
}

if buf[i] == '\n' && !quoted {
break
}
Expand Down Expand Up @@ -818,15 +835,16 @@ func scanFieldValue(buf []byte, i int) (int, []byte) {
break
}

// If we see a double quote, makes sure it is not escaped
if buf[i] == '"' && buf[i-1] != '\\' {
i += 1
quoted = !quoted
// Only escape char for a field value is a double-quote
if buf[i] == '\\' && i+1 < len(buf) && buf[i+1] == '"' {
i += 2
continue
}

if buf[i] == '\\' {
i += 2
// Quoted value? (e.g. string)
if buf[i] == '"' {
i += 1
quoted = !quoted
continue
}

Expand Down Expand Up @@ -894,19 +912,62 @@ 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
// escapeStringField returns a copy of in with any double quotes or
// backslashes with escaped values
func escapeStringField(in string) string {
var out []byte
i := 0
for {
if i >= len(in) {
break
}
// escape double-quotes
if in[i] == '\\' {
out = append(out, '\\')
out = append(out, '\\')
i += 1
continue
}
// escape double-quotes
if in[i] == '"' {
out = append(out, '\\')
out = append(out, '"')
i += 1
continue
}
out = append(out, in[i])
i += 1

}
return quoteReplacer.ReplaceAllString(in, `$1\"`)
return string(out)
}

// 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)
// unescapeStringField returns a copy of in with any escaped double-quotes
// or backslashes unescaped
func unescapeStringField(in string) string {
var out []byte
i := 0
for {
if i >= len(in) {
break
}
// unescape backslashes
if in[i] == '\\' && i+1 < len(in) && in[i+1] == '\\' {
out = append(out, '\\')
i += 2
continue
}
// unescape double-quotes
if in[i] == '\\' && i+1 < len(in) && in[i+1] == '"' {
out = append(out, '"')
i += 2
continue
}
out = append(out, in[i])
i += 1

}
return string(out)
}

// NewPoint returns a new point with the given measurement name, tags, fields and timestamp
Expand Down Expand Up @@ -1161,7 +1222,7 @@ func newFieldsFromBinary(buf []byte) Fields {

// If the first char is a double-quote, then unmarshal as string
if valueBuf[0] == '"' {
value = unescapeQuoteString(string(valueBuf[1 : len(valueBuf)-1]))
value = unescapeStringField(string(valueBuf[1 : len(valueBuf)-1]))
// Check for numeric characters and special NaN or Inf
} else if (valueBuf[0] >= '0' && valueBuf[0] <= '9') || valueBuf[0] == '-' || valueBuf[0] == '+' || valueBuf[0] == '.' ||
valueBuf[0] == 'N' || valueBuf[0] == 'n' || // NaN
Expand Down Expand Up @@ -1228,14 +1289,14 @@ func (p Fields) MarshalBinary() []byte {
b = append(b, t...)
case string:
b = append(b, '"')
b = append(b, []byte(escapeQuoteString(t))...)
b = append(b, []byte(escapeStringField(t))...)
b = append(b, '"')
case nil:
// skip
default:
// Can't determine the type, so convert to string
b = append(b, '"')
b = append(b, []byte(escapeQuoteString(fmt.Sprintf("%v", v)))...)
b = append(b, []byte(escapeStringField(fmt.Sprintf("%v", v)))...)
b = append(b, '"')

}
Expand Down
42 changes: 42 additions & 0 deletions tsdb/points_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,48 @@ func TestParsePointWithStringWithEquals(t *testing.T) {
)
}

func TestParsePointWithStringWithBackslash(t *testing.T) {
test(t, `cpu value="test\\\"" 1000000000`,
tsdb.NewPoint(
"cpu",
tsdb.Tags{},
tsdb.Fields{
"value": `test\"`,
},
time.Unix(1, 0)),
)

test(t, `cpu value="test\\" 1000000000`,
tsdb.NewPoint(
"cpu",
tsdb.Tags{},
tsdb.Fields{
"value": `test\`,
},
time.Unix(1, 0)),
)

test(t, `cpu value="test\\\"" 1000000000`,
tsdb.NewPoint(
"cpu",
tsdb.Tags{},
tsdb.Fields{
"value": `test\"`,
},
time.Unix(1, 0)),
)

test(t, `cpu value="test\"" 1000000000`,
tsdb.NewPoint(
"cpu",
tsdb.Tags{},
tsdb.Fields{
"value": `test"`,
},
time.Unix(1, 0)),
)
}

func TestParsePointWithBoolField(t *testing.T) {
test(t, `cpu,host=serverA,region=us-east true=true,t=t,T=T,TRUE=TRUE,True=True,false=false,f=f,F=F,FALSE=FALSE,False=False 1000000000`,
tsdb.NewPoint(
Expand Down

0 comments on commit b38e81e

Please sign in to comment.