Fix escaping even more.

Change-Id: Ie958d557aae0dda68c451e9fafc615221cc07bb0
This commit is contained in:
Bjoern Rabenstein 2014-05-14 19:28:53 +02:00
parent 82e55cd560
commit d3ebb29141
4 changed files with 50 additions and 27 deletions

View File

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

View File

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

View File

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

View File

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