Skip to content

Commit 4d262f0

Browse files
authored
Merge pull request #330 from bookingcom/parser/various-space-comma-bug-fixes
parser: return errors if there are syntax bugs in metric names wrapped by a function
2 parents 8fd5c0c + 81bf8cc commit 4d262f0

File tree

7 files changed

+224
-91
lines changed

7 files changed

+224
-91
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ jobs:
3333
uses: golangci/golangci-lint-action@v2
3434
with:
3535
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
36-
version: v1.30
36+
version: v1.40.1
3737

3838
# Optional: working directory, useful for monorepos
3939
# working-directory: somedir

.golangci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ linters:
149149
disable:
150150
- maligned
151151
- prealloc
152+
- makezero # Disable due to panic bug in makezero ([email protected])
153+
- errorlint # Disable temporarily, more could be found in: https://github.com/bookingcom/carbonapi/issues/333
152154
disable-all: false
153155
presets:
154156
- bugs

AUTHORS.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,5 +44,6 @@ Slawomir Skowron
4444
Stefan Boronea
4545
Vladimir Smirnov
4646
Vsevolod Polyakov
47+
Xiaofan Hu
4748
Zachary Dykstra
4849
Zhang Cheng

pkg/parser/interface.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ var (
3535
ErrMissingQuote = ParseError("missing quote")
3636
// ErrUnexpectedCharacter is a parse error returned when an expression contains an unexpected character.
3737
ErrUnexpectedCharacter = ParseError("unexpected character")
38+
// ErrMissingBracket is a parse error returned when an expression is missing an opening or closing bracket.
39+
ErrMissingBracket = ParseError("missing opening or closing bracket []")
40+
// ErrMissingBrace is a parse error returned when an expression is missing an opening or closing brace.
41+
ErrMissingBrace = ParseError("missing opening or closing brace {}")
42+
// ErrCommaInBrackets is a parse error returned when an expression has comma within brackets.
43+
ErrCommaInBrackets = ParseError("comma within brackets")
44+
// ErrSpacesInMetricName is a parse error returned when an expression has space in metric name.
45+
ErrSpacesInMetricName = ParseError("space in metric name")
46+
// ErrSpacesInBraces is a parse error returned when an expression has space in braces.
47+
ErrSpacesInBraces = ParseError("space in braces")
48+
// ErrSpacesInBrackets is a parse error returned when an expression has space in brackets.
49+
ErrSpacesInBrackets = ParseError("space in brackets")
50+
// ErrBraceInBrackets is a parse error returned when an expression has brace within brackets.
51+
ErrBraceInBrackets = ParseError("brace within brackets")
52+
// ErrNestedBrackets is a parse error returned when an expression has nested brackets.
53+
ErrNestedBrackets = ParseError("nested brackets")
3854
// ErrBadType is an eval error returned when a argument has wrong type.
3955
ErrBadType = ParseError("bad type")
4056
// ErrMissingArgument is an eval error returned when a argument is missing.

pkg/parser/internal.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package parser
22

33
import (
44
"fmt"
5-
65
"runtime/debug"
76
)
87

pkg/parser/parser.go

Lines changed: 86 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -354,8 +354,13 @@ func parseExprWithoutPipe(e string) (Expr, string, error) {
354354
val, tail, err := parseString(e)
355355
return &expr{valStr: val, etype: EtString}, tail, err
356356
}
357+
357358
var name string
358-
name, e = parseName(e)
359+
var err error
360+
name, e, err = parseName(e)
361+
if err != nil {
362+
return nil, e, err
363+
}
359364

360365
if strings.ToLower(name) == "false" || strings.ToLower(name) == "true" {
361366
return &expr{valStr: name, etype: EtString, target: name}, e, nil
@@ -420,7 +425,6 @@ func IsNameChar(r byte) bool {
420425
r == '.' || r == '_' ||
421426
r == '-' || r == '*' ||
422427
r == '?' || r == ':' ||
423-
r == '[' || r == ']' ||
424428
r == '^' || r == '$' ||
425429
r == '<' || r == '>' ||
426430
r == '&' || r == '#'
@@ -557,48 +561,118 @@ func parseConst(s string) (float64, string, error) {
557561
// RangeTables is an array of *unicode.RangeTable
558562
var RangeTables []*unicode.RangeTable
559563

560-
func parseName(s string) (string, string) {
561-
564+
// parseName parses the next symbol from s and returns
565+
// * the parsed symbol (function or metric name),
566+
// * the rest of the string from s
567+
// * syntax error
568+
func parseName(s string) (string, string, error) {
562569
var (
563-
braces, i, w int
564-
r rune
570+
braces, brackets int
571+
i, w int
572+
r rune
565573
)
566574

567575
FOR:
568-
for braces, i = 0, 0; i < len(s); i += w {
569-
576+
for i = 0; i < len(s); i += w {
570577
w = 1
571578
if IsNameChar(s[i]) {
572579
continue
573580
}
574581

582+
// Graphite render spec: https://graphite.readthedocs.io/en/latest/render_api.html#graphing-metrics
575583
switch s[i] {
576584
case '{':
585+
// No way escape { in metric names, thus using it
586+
// in the range brackets should be an error.
587+
if brackets > 0 {
588+
return s, "", ErrBraceInBrackets
589+
}
590+
577591
braces++
578592
case '}':
579-
if braces == 0 {
580-
break FOR
593+
// No way escape } in metric names, thus using it
594+
// in the range brackets should be an error.
595+
if brackets > 0 {
596+
return s, "", ErrBraceInBrackets
597+
} else if braces == 0 {
598+
return s, "", ErrMissingBrace
581599
}
600+
582601
braces--
602+
case '[':
603+
// Nested brackets support isn't really necessary as
604+
// left bracket [ alone can't be in metric name. And
605+
// go-carbon doesn't support it at the moment.
606+
//
607+
// Before this change, no errors are returned to the
608+
// user and no metrics are returned. It's arguably
609+
// worse than just return an error.
610+
if brackets > 0 {
611+
return s, "", ErrNestedBrackets
612+
}
613+
614+
brackets++
615+
case ']':
616+
// No way to escape braces {} and brackets [] in
617+
// graphite query, thus missing open [ means it's a query bug.
618+
if brackets == 0 {
619+
return s, "", ErrMissingBracket
620+
}
621+
622+
brackets--
583623
case ',':
624+
// No way to escape a comma in graphite query, thus
625+
// metric name is not allowed to have comma within it,
626+
// thus it isn't allowed to query it within [].
627+
if brackets > 0 {
628+
return s, "", ErrCommaInBrackets
629+
}
630+
584631
if braces == 0 {
585632
break FOR
586633
}
634+
case ' ', '\t', '\n':
635+
// Spaces is not allowed in metric name, so it isn't
636+
// really needed for us to support it in value list
637+
// {} and range list [] queries.
638+
//
639+
// Although it is nice to allow spaces in value list query,
640+
// support in storage layer like go-carbon is also required.
641+
//
642+
// At the same time, if not using any graphite function,
643+
// the current parser also doesn't support spaces in
644+
// value list syntax {} and would return an 400 error.
645+
if braces > 0 {
646+
return s, "", ErrSpacesInBraces
647+
}
648+
if brackets > 0 {
649+
return s, "", ErrSpacesInBrackets
650+
}
651+
652+
break FOR
587653
default:
588654
r, w = utf8.DecodeRuneInString(s[i:])
589655
if unicode.In(r, RangeTables...) {
590656
continue
591657
}
592658
break FOR
593659
}
660+
}
594661

662+
// No way to escape braces {} and brackets [] in graphite query, thus
663+
// missing closed }/] means it's a query bug.
664+
if braces > 0 {
665+
return s, "", ErrMissingBrace
666+
}
667+
if brackets > 0 {
668+
return s, "", ErrMissingBracket
595669
}
596670

597671
if i == len(s) {
598-
return s, ""
672+
return s, "", nil
599673
}
600674

601-
return s[:i], s[i:]
675+
return s[:i], s[i:], nil
602676
}
603677

604678
func parseString(s string) (string, string, error) {

0 commit comments

Comments
 (0)