From d3ebb29141b10a3f1cba8001c0143d77c6db4dbf Mon Sep 17 00:00:00 2001 From: Bjoern Rabenstein Date: Wed, 14 May 2014 19:28:53 +0200 Subject: [PATCH] Fix escaping even more. Change-Id: Ie958d557aae0dda68c451e9fafc615221cc07bb0 --- text/create.go | 20 +++++++++++--------- text/create_test.go | 8 ++++---- text/parse.go | 41 +++++++++++++++++++++++++++++++---------- text/parse_test.go | 8 ++++---- 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/text/create.go b/text/create.go index 7f2376a..072619f 100644 --- a/text/create.go +++ b/text/create.go @@ -54,7 +54,8 @@ func MetricFamilyToText(out io.Writer, in *dto.MetricFamily) (int, error) { if in.Help != nil { n, err := fmt.Fprintf( out, "# HELP %s %s\n", - name, strings.Replace(*in.Help, "\n", `\n`, -1)) + name, escapeString(*in.Help, false), + ) written += n if err != nil { return written, err @@ -217,7 +218,7 @@ func labelPairsToText( for _, lp := range in { n, err := fmt.Fprintf( out, `%c%s="%s"`, - separator, lp.GetName(), escapeLabelValue(lp.GetValue()), + separator, lp.GetName(), escapeString(lp.GetValue(), true), ) written += n if err != nil { @@ -229,7 +230,7 @@ func labelPairsToText( n, err := fmt.Fprintf( out, `%c%s="%s"`, separator, additionalLabelName, - escapeLabelValue(additionalLabelValue), + escapeString(additionalLabelValue, true), ) written += n if err != nil { @@ -244,16 +245,17 @@ func labelPairsToText( return written, nil } -// escapeLabelValue replaces '\' by '\\', '"' by '\"', and new line character by '\n'. -func escapeLabelValue(v string) string { +// escapeString replaces '\' by '\\', new line character by '\n', and - if +// includeDoubleQuote is true - '"' by '\"'. +func escapeString(v string, includeDoubleQuote bool) string { result := bytes.NewBuffer(make([]byte, 0, len(v))) for _, c := range v { - switch c { - case '\\': + switch { + case c == '\\': result.WriteString(`\\`) - case '"': + case includeDoubleQuote && c == '"': result.WriteString(`\"`) - case '\n': + case c == '\n': result.WriteString(`\n`) default: result.WriteRune(c) diff --git a/text/create_test.go b/text/create_test.go index 6c6fc3f..c5f3367 100644 --- a/text/create_test.go +++ b/text/create_test.go @@ -33,7 +33,7 @@ func testCreate(t test.Tester) { { in: &dto.MetricFamily{ Name: proto.String("name"), - Help: proto.String("two-line\n doc string"), + Help: proto.String("two-line\n doc str\\ing"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ &dto.Metric{ @@ -69,7 +69,7 @@ func testCreate(t test.Tester) { }, }, }, - out: `# HELP name two-line\n doc string + out: `# HELP name two-line\n doc str\\ing # TYPE name counter name{labelname="val1",basename="basevalue"} NaN name{labelname="val2",basename="basevalue"} 0.23 1234567890 @@ -79,7 +79,7 @@ name{labelname="val2",basename="basevalue"} 0.23 1234567890 { in: &dto.MetricFamily{ Name: proto.String("gauge_name"), - Help: proto.String("gauge\ndoc\nstring"), + Help: proto.String("gauge\ndoc\nstr\"ing"), Type: dto.MetricType_GAUGE.Enum(), Metric: []*dto.Metric{ &dto.Metric{ @@ -114,7 +114,7 @@ name{labelname="val2",basename="basevalue"} 0.23 1234567890 }, }, }, - out: `# HELP gauge_name gauge\ndoc\nstring + out: `# HELP gauge_name gauge\ndoc\nstr"ing # TYPE gauge_name gauge gauge_name{name_1="val with\nnew line",name_2="val with \\backslash and \"quotes\""} +Inf gauge_name{name_1="Björn",name_2="佖佥"} 3.14e+42 diff --git a/text/parse.go b/text/parse.go index 009e39c..3e7fb30 100644 --- a/text/parse.go +++ b/text/parse.go @@ -401,7 +401,7 @@ func (p *Parser) startTimestamp() stateFn { return nil } p.currentMetric.TimestampMs = proto.Int64(timestamp) - if p.readTokenUntilNewline(); p.err != nil { + if p.readTokenUntilNewline(false); p.err != nil { return nil // Unexpected end of input. } if p.currentToken.Len() > 0 { @@ -419,12 +419,10 @@ func (p *Parser) readingHelp() stateFn { return nil } // Rest of line is the docstring. - if p.readTokenUntilNewline(); p.err != nil { + if p.readTokenUntilNewline(true); p.err != nil { return nil // Unexpected end of input. } - p.currentMF.Help = proto.String( - strings.Replace(p.currentToken.String(), `\n`, "\n", -1), - ) + p.currentMF.Help = proto.String(p.currentToken.String()) return p.startOfLine } @@ -436,7 +434,7 @@ func (p *Parser) readingType() stateFn { return nil } // Rest of line is the type. - if p.readTokenUntilNewline(); p.err != nil { + if p.readTokenUntilNewline(false); p.err != nil { return nil // Unexpected end of input. } metricType, ok := dto.MetricType_value[strings.ToUpper(p.currentToken.String())] @@ -490,11 +488,34 @@ func (p *Parser) readTokenUntilWhitespace() { // readTokenUntilNewline copies bytes from p.buf into p.currentToken. The first // byte considered is the byte already read (now in p.currentByte). The first // newline byte encountered is still copied into p.currentByte, but not into -// p.currentToken. -func (p *Parser) readTokenUntilNewline() { +// p.currentToken. If recognizeEscapeSequence is true, two escape sequences are +// recognized: '\\' tranlates into '\', and '\n' into a line-feed character. All +// other escape sequences are invalid and cause an error. +func (p *Parser) readTokenUntilNewline(recognizeEscapeSequence bool) { p.currentToken.Reset() - for p.err == nil && p.currentByte != '\n' { - p.currentToken.WriteByte(p.currentByte) + escaped := false + for p.err == nil { + if recognizeEscapeSequence && escaped { + switch p.currentByte { + case '\\': + p.currentToken.WriteByte(p.currentByte) + case 'n': + p.currentToken.WriteByte('\n') + default: + p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte)) + return + } + escaped = false + } else { + switch p.currentByte { + case '\n': + return + case '\\': + escaped = true + default: + p.currentToken.WriteByte(p.currentByte) + } + } p.currentByte, p.err = p.buf.ReadByte() } } diff --git a/text/parse_test.go b/text/parse_test.go index 9e176cf..8c36be1 100644 --- a/text/parse_test.go +++ b/text/parse_test.go @@ -91,9 +91,9 @@ no_labels{} 3 # TYPE name counter name{labelname="val1",basename="basevalue"} NaN name {labelname="val2",basename="base\"v\\al\nue"} 0.23 1234567890 -# HELP name two-line\n doc string +# HELP name two-line\n doc str\\ing - # HELP name2 doc string 2 + # HELP name2 doc str"ing 2 # TYPE name2 gauge name2{labelname="val2" ,basename = "basevalue2" } +Inf 54321 name2{ labelname = "val1" }-Inf @@ -101,7 +101,7 @@ name2{ labelname = "val1" }-Inf out: []*dto.MetricFamily{ &dto.MetricFamily{ Name: proto.String("name"), - Help: proto.String("two-line\n doc string"), + Help: proto.String("two-line\n doc str\\ing"), Type: dto.MetricType_COUNTER.Enum(), Metric: []*dto.Metric{ &dto.Metric{ @@ -139,7 +139,7 @@ name2{ labelname = "val1" }-Inf }, &dto.MetricFamily{ Name: proto.String("name2"), - Help: proto.String("doc string 2"), + Help: proto.String("doc str\"ing 2"), Type: dto.MetricType_GAUGE.Enum(), Metric: []*dto.Metric{ &dto.Metric{