Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 18 additions & 7 deletions expfmt/text_parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ func (p *TextParser) startOfLine() stateFn {
return p.startComment
case '\n':
return p.startOfLine // Empty line, start the next one.
case '\r':
return p.startOfLine // Empty line, start the next one.
}
return p.readingMetricName
}
Expand All @@ -162,21 +164,21 @@ func (p *TextParser) startComment() stateFn {
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentByte == '\n' {
if p.currentByte == '\n' || p.currentByte == '\r' {
return p.startOfLine
}
if p.readTokenUntilWhitespace(); p.err != nil {
return nil // Unexpected end of input.
}
// If we have hit the end of line already, there is nothing left
// to do. This is not considered a syntax error.
if p.currentByte == '\n' {
if p.currentByte == '\n' || p.currentByte == '\r' {
return p.startOfLine
}
keyword := p.currentToken.String()
if keyword != "HELP" && keyword != "TYPE" {
// Generic comment, ignore by fast forwarding to end of line.
for p.currentByte != '\n' {
for p.currentByte != '\n' || p.currentByte == '\r' {
if p.currentByte, p.err = p.buf.ReadByte(); p.err != nil {
return nil // Unexpected end of input.
}
Expand All @@ -190,7 +192,7 @@ func (p *TextParser) startComment() stateFn {
if p.readTokenAsMetricName(); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentByte == '\n' {
if p.currentByte == '\n' || p.currentByte == '\r' {
// At the end of the line already.
// Again, this is not considered a syntax error.
return p.startOfLine
Expand All @@ -203,7 +205,7 @@ func (p *TextParser) startComment() stateFn {
if p.skipBlankTab(); p.err != nil {
return nil // Unexpected end of input.
}
if p.currentByte == '\n' {
if p.currentByte == '\n' || p.currentByte == '\r' {
// At the end of the line already.
// Again, this is not considered a syntax error.
return p.startOfLine
Expand Down Expand Up @@ -446,7 +448,7 @@ func (p *TextParser) readingValue() stateFn {
default:
p.err = fmt.Errorf("unexpected type for metric name %q", p.currentMF.GetName())
}
if p.currentByte == '\n' {
if p.currentByte == '\n' || p.currentByte == '\r' {
return p.startOfLine
}
return p.startTimestamp
Expand Down Expand Up @@ -546,7 +548,7 @@ func (p *TextParser) skipBlankTabIfCurrentBlankTab() {
// into p.currentToken.
func (p *TextParser) readTokenUntilWhitespace() {
p.currentToken.Reset()
for p.err == nil && !isBlankOrTab(p.currentByte) && p.currentByte != '\n' {
for p.err == nil && !isBlankOrTab(p.currentByte) && p.currentByte != '\n' && p.currentByte != '\r' {
p.currentToken.WriteByte(p.currentByte)
p.currentByte, p.err = p.buf.ReadByte()
}
Expand All @@ -568,6 +570,8 @@ func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) {
p.currentToken.WriteByte(p.currentByte)
case 'n':
p.currentToken.WriteByte('\n')
case 'r':
p.currentToken.WriteByte('\r')
default:
p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
return
Expand All @@ -577,6 +581,8 @@ func (p *TextParser) readTokenUntilNewline(recognizeEscapeSequence bool) {
switch p.currentByte {
case '\n':
return
case '\r':
return
case '\\':
escaped = true
default:
Expand Down Expand Up @@ -641,6 +647,8 @@ func (p *TextParser) readTokenAsLabelValue() {
p.currentToken.WriteByte(p.currentByte)
case 'n':
p.currentToken.WriteByte('\n')
case 'r':
p.currentToken.WriteByte('\r')
default:
p.parseError(fmt.Sprintf("invalid escape sequence '\\%c'", p.currentByte))
return
Expand All @@ -654,6 +662,9 @@ func (p *TextParser) readTokenAsLabelValue() {
case '\n':
p.parseError(fmt.Sprintf("label value %q contains unescaped new-line", p.currentToken.String()))
return
case '\r':
p.parseError(fmt.Sprintf("label value %q contains unescaped carriage-return", p.currentToken.String()))
return
case '\\':
escaped = true
default:
Expand Down
45 changes: 45 additions & 0 deletions expfmt/text_parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,51 @@ request_duration_microseconds_count 2693
},
},
},
// 5: Minimal, with carriage returns.
{
in: "\r\n" +
"minimal_metric 1.234\r\n" +
"another_metric -3e3 103948\r\n" +
"# Even that:\r\n" +
"no_labels{} 3\r\n" +
"# HELP line for non-existing metric will be ignored.\r\n",
out: []*dto.MetricFamily{
&dto.MetricFamily{
Name: proto.String("minimal_metric"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(1.234),
},
},
},
},
&dto.MetricFamily{
Name: proto.String("another_metric"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(-3e3),
},
TimestampMs: proto.Int64(103948),
},
},
},
&dto.MetricFamily{
Name: proto.String("no_labels"),
Type: dto.MetricType_UNTYPED.Enum(),
Metric: []*dto.Metric{
&dto.Metric{
Untyped: &dto.Untyped{
Value: proto.Float64(3),
},
},
},
},
},
},
}

for i, scenario := range scenarios {
Expand Down