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 { if in.Help != nil {
n, err := fmt.Fprintf( n, err := fmt.Fprintf(
out, "# HELP %s %s\n", out, "# HELP %s %s\n",
name, strings.Replace(*in.Help, "\n", `\n`, -1)) name, escapeString(*in.Help, false),
)
written += n written += n
if err != nil { if err != nil {
return written, err return written, err
@ -217,7 +218,7 @@ func labelPairsToText(
for _, lp := range in { for _, lp := range in {
n, err := fmt.Fprintf( n, err := fmt.Fprintf(
out, `%c%s="%s"`, out, `%c%s="%s"`,
separator, lp.GetName(), escapeLabelValue(lp.GetValue()), separator, lp.GetName(), escapeString(lp.GetValue(), true),
) )
written += n written += n
if err != nil { if err != nil {
@ -229,7 +230,7 @@ func labelPairsToText(
n, err := fmt.Fprintf( n, err := fmt.Fprintf(
out, `%c%s="%s"`, out, `%c%s="%s"`,
separator, additionalLabelName, separator, additionalLabelName,
escapeLabelValue(additionalLabelValue), escapeString(additionalLabelValue, true),
) )
written += n written += n
if err != nil { if err != nil {
@ -244,16 +245,17 @@ func labelPairsToText(
return written, nil return written, nil
} }
// escapeLabelValue replaces '\' by '\\', '"' by '\"', and new line character by '\n'. // escapeString replaces '\' by '\\', new line character by '\n', and - if
func escapeLabelValue(v string) string { // includeDoubleQuote is true - '"' by '\"'.
func escapeString(v string, includeDoubleQuote bool) string {
result := bytes.NewBuffer(make([]byte, 0, len(v))) result := bytes.NewBuffer(make([]byte, 0, len(v)))
for _, c := range v { for _, c := range v {
switch c { switch {
case '\\': case c == '\\':
result.WriteString(`\\`) result.WriteString(`\\`)
case '"': case includeDoubleQuote && c == '"':
result.WriteString(`\"`) result.WriteString(`\"`)
case '\n': case c == '\n':
result.WriteString(`\n`) result.WriteString(`\n`)
default: default:
result.WriteRune(c) result.WriteRune(c)

View File

@ -33,7 +33,7 @@ func testCreate(t test.Tester) {
{ {
in: &dto.MetricFamily{ in: &dto.MetricFamily{
Name: proto.String("name"), 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(), Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{ Metric: []*dto.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 # TYPE name counter
name{labelname="val1",basename="basevalue"} NaN name{labelname="val1",basename="basevalue"} NaN
name{labelname="val2",basename="basevalue"} 0.23 1234567890 name{labelname="val2",basename="basevalue"} 0.23 1234567890
@ -79,7 +79,7 @@ name{labelname="val2",basename="basevalue"} 0.23 1234567890
{ {
in: &dto.MetricFamily{ in: &dto.MetricFamily{
Name: proto.String("gauge_name"), Name: proto.String("gauge_name"),
Help: proto.String("gauge\ndoc\nstring"), Help: proto.String("gauge\ndoc\nstr\"ing"),
Type: dto.MetricType_GAUGE.Enum(), Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{ Metric: []*dto.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 # TYPE gauge_name gauge
gauge_name{name_1="val with\nnew line",name_2="val with \\backslash and \"quotes\""} +Inf 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 gauge_name{name_1="Björn",name_2="佖佥"} 3.14e+42

View File

@ -401,7 +401,7 @@ func (p *Parser) startTimestamp() stateFn {
return nil return nil
} }
p.currentMetric.TimestampMs = proto.Int64(timestamp) 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. return nil // Unexpected end of input.
} }
if p.currentToken.Len() > 0 { if p.currentToken.Len() > 0 {
@ -419,12 +419,10 @@ func (p *Parser) readingHelp() stateFn {
return nil return nil
} }
// Rest of line is the docstring. // 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. return nil // Unexpected end of input.
} }
p.currentMF.Help = proto.String( p.currentMF.Help = proto.String(p.currentToken.String())
strings.Replace(p.currentToken.String(), `\n`, "\n", -1),
)
return p.startOfLine return p.startOfLine
} }
@ -436,7 +434,7 @@ func (p *Parser) readingType() stateFn {
return nil return nil
} }
// Rest of line is the type. // 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. return nil // Unexpected end of input.
} }
metricType, ok := dto.MetricType_value[strings.ToUpper(p.currentToken.String())] 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 // readTokenUntilNewline copies bytes from p.buf into p.currentToken. The first
// byte considered is the byte already read (now in p.currentByte). 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 // newline byte encountered is still copied into p.currentByte, but not into
// p.currentToken. // p.currentToken. If recognizeEscapeSequence is true, two escape sequences are
func (p *Parser) readTokenUntilNewline() { // 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() p.currentToken.Reset()
for p.err == nil && p.currentByte != '\n' { escaped := false
p.currentToken.WriteByte(p.currentByte) 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() p.currentByte, p.err = p.buf.ReadByte()
} }
} }

View File

@ -91,9 +91,9 @@ no_labels{} 3
# TYPE name counter # TYPE name counter
name{labelname="val1",basename="basevalue"} NaN name{labelname="val1",basename="basevalue"} NaN
name {labelname="val2",basename="base\"v\\al\nue"} 0.23 1234567890 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 # TYPE name2 gauge
name2{labelname="val2" ,basename = "basevalue2" } +Inf 54321 name2{labelname="val2" ,basename = "basevalue2" } +Inf 54321
name2{ labelname = "val1" }-Inf name2{ labelname = "val1" }-Inf
@ -101,7 +101,7 @@ name2{ labelname = "val1" }-Inf
out: []*dto.MetricFamily{ out: []*dto.MetricFamily{
&dto.MetricFamily{ &dto.MetricFamily{
Name: proto.String("name"), 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(), Type: dto.MetricType_COUNTER.Enum(),
Metric: []*dto.Metric{ Metric: []*dto.Metric{
&dto.Metric{ &dto.Metric{
@ -139,7 +139,7 @@ name2{ labelname = "val1" }-Inf
}, },
&dto.MetricFamily{ &dto.MetricFamily{
Name: proto.String("name2"), Name: proto.String("name2"),
Help: proto.String("doc string 2"), Help: proto.String("doc str\"ing 2"),
Type: dto.MetricType_GAUGE.Enum(), Type: dto.MetricType_GAUGE.Enum(),
Metric: []*dto.Metric{ Metric: []*dto.Metric{
&dto.Metric{ &dto.Metric{