diff --git a/pkg/logql/log/logfmt/jsonstring.go b/pkg/logql/log/logfmt/jsonstring.go index cd2d76bbe95b..d248280948d9 100644 --- a/pkg/logql/log/logfmt/jsonstring.go +++ b/pkg/logql/log/logfmt/jsonstring.go @@ -36,7 +36,7 @@ func unquoteBytes(s []byte) (t []byte, ok bool) { r := 0 for r < len(s) { c := s[r] - if c == '\\' || c == '"' || c < ' ' { + if c == '\\' || c == '"' { break } if c < utf8.RuneSelf { @@ -118,8 +118,8 @@ func unquoteBytes(s []byte) (t []byte, ok bool) { w += utf8.EncodeRune(b[w:], rr) } - // Quote, control characters are invalid. - case c == '"', c < ' ': + // Unescaped quote is invalid. + case c == '"': return // ASCII diff --git a/pkg/logql/log/parser_test.go b/pkg/logql/log/parser_test.go index 74fc215e5b44..1e2f11d3fa83 100644 --- a/pkg/logql/log/parser_test.go +++ b/pkg/logql/log/parser_test.go @@ -574,6 +574,50 @@ func Test_logfmtParser_Parse(t *testing.T) { {Name: "foobar", Value: "foo bar"}, }, }, + { + "escaped control chars in logfmt", + []byte(`foobar="foo\nbar\tbaz"`), + labels.Labels{ + {Name: "a", Value: "b"}, + }, + labels.Labels{ + {Name: "a", Value: "b"}, + {Name: "foobar", Value: "foo\nbar\tbaz"}, + }, + }, + { + "literal control chars in logfmt", + []byte("foobar=\"foo\nbar\tbaz\""), + labels.Labels{ + {Name: "a", Value: "b"}, + }, + labels.Labels{ + {Name: "a", Value: "b"}, + {Name: "foobar", Value: "foo\nbar\tbaz"}, + }, + }, + { + "escaped slash logfmt", + []byte(`foobar="foo ba\\r baz"`), + labels.Labels{ + {Name: "a", Value: "b"}, + }, + labels.Labels{ + {Name: "a", Value: "b"}, + {Name: "foobar", Value: `foo ba\r baz`}, + }, + }, + { + "literal newline and escaped slash logfmt", + []byte("foobar=\"foo bar\nb\\\\az\""), + labels.Labels{ + {Name: "a", Value: "b"}, + }, + labels.Labels{ + {Name: "a", Value: "b"}, + {Name: "foobar", Value: "foo bar\nb\\az"}, + }, + }, { "double property logfmt", []byte(`foobar="foo bar" latency=10ms`),