Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix JSONPath parser will not filter strings containing parentheses #43674

Merged
merged 2 commits into from
Jul 6, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
92 changes: 83 additions & 9 deletions staging/src/k8s.io/client-go/util/jsonpath/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package jsonpath

import (
"errors"
"fmt"
"regexp"
"strconv"
Expand All @@ -42,6 +43,8 @@ type Parser struct {
width int
}

var ErrSyntax = errors.New("invalid syntax")

// Parse parsed the given text and return a node Parser.
// If an error is encountered, parsing stops and an empty
// Parser is returned with the error
Expand Down Expand Up @@ -159,8 +162,8 @@ func (p *Parser) parseInsideAction(cur *ListNode) error {
p.consumeText()
case r == '[':
return p.parseArray(cur)
case r == '"':
return p.parseQuote(cur)
case r == '"' || r == '\'':
return p.parseQuote(cur, r)
case r == '.':
return p.parseField(cur)
case r == '+' || r == '-' || unicode.IsDigit(r):
Expand Down Expand Up @@ -334,13 +337,33 @@ Loop:
func (p *Parser) parseFilter(cur *ListNode) error {
p.pos += len("[?(")
p.consumeText()
begin := false
end := false
var pair rune

Loop:
for {
switch p.next() {
r := p.next()
switch r {
case eof, '\n':
return fmt.Errorf("unterminated filter")
case '"', '\'':
if begin == false {
//save the paired rune
begin = true
pair = r
continue
}
//only add when met paired rune
if p.input[p.pos-2] != '\\' && r == pair {
end = true
}
case ')':
break Loop
//in rightParser below quotes only appear zero or once
//and must be paired at the beginning and end
if begin == end {
break Loop
}
}
}
if p.next() != ']' {
Expand Down Expand Up @@ -370,19 +393,22 @@ Loop:
return p.parseInsideAction(cur)
}

// parseQuote unquotes string inside double quote
func (p *Parser) parseQuote(cur *ListNode) error {
// parseQuote unquotes string inside double or single quote
func (p *Parser) parseQuote(cur *ListNode, end rune) error {
Loop:
for {
switch p.next() {
case eof, '\n':
return fmt.Errorf("unterminated quoted string")
case '"':
break Loop
case end:
//if it's not escape break the Loop
if p.input[p.pos-2] != '\\' {
break Loop
}
}
}
value := p.consumeText()
s, err := strconv.Unquote(value)
s, err := UnquoteExtend(value)
if err != nil {
return fmt.Errorf("unquote string %s error %v", value, err)
}
Expand Down Expand Up @@ -447,3 +473,51 @@ func isAlphaNumeric(r rune) bool {
func isBool(s string) bool {
return s == "true" || s == "false"
}

//UnquoteExtend is almost same as strconv.Unquote(), but it support parse single quotes as a string
func UnquoteExtend(s string) (string, error) {
n := len(s)
if n < 2 {
return "", ErrSyntax
}
quote := s[0]
if quote != s[n-1] {
return "", ErrSyntax
}
s = s[1 : n-1]

if quote != '"' && quote != '\'' {
return "", ErrSyntax
}

// Is it trivial? Avoid allocation.
if !contains(s, '\\') && !contains(s, quote) {
return s, nil
}

var runeTmp [utf8.UTFMax]byte
buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
for len(s) > 0 {
c, multibyte, ss, err := strconv.UnquoteChar(s, quote)
if err != nil {
return "", err
}
s = ss
if c < utf8.RuneSelf || !multibyte {
buf = append(buf, byte(c))
} else {
n := utf8.EncodeRune(runeTmp[:], c)
buf = append(buf, runeTmp[:n]...)
}
}
return string(buf), nil
}

func contains(s string, c byte) bool {
for i := 0; i < len(s); i++ {
if s[i] == c {
return true
}
}
return false
}
16 changes: 16 additions & 0 deletions staging/src/k8s.io/client-go/util/jsonpath/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ var parserTests = []parserTest{
newList(), newIdentifier("end"),
}, false},
{"malformat input", `{\\\}`, []Node{}, true},
{"paired parentheses in quotes", `{[?(@.status.nodeInfo.osImage == "()")]}`,
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("()")}, false},
{"paired parentheses in double quotes and with double quotes escape", `{[?(@.status.nodeInfo.osImage == "(\"\")")]}`,
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("(\"\")")}, false},
{"unregular parentheses in double quotes", `{[?(@.test == "())(")]}`,
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("test"), newList(), newText("())(")}, false},
{"plain text in single quotes", `{[?(@.status.nodeInfo.osImage == 'Linux')]}`,
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("Linux")}, false},
{"test filter suffix", `{[?(@.status.nodeInfo.osImage == "{[()]}")]}`,
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("{[()]}")}, false},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add: double inside single, single inside double, single containing escaped single

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updated, ptal

{"double inside single", `{[?(@.status.nodeInfo.osImage == "''")]}`,
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("''")}, false},
{"single inside double", `{[?(@.status.nodeInfo.osImage == '""')]}`,
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("\"\"")}, false},
{"single containing escaped single", `{[?(@.status.nodeInfo.osImage == '\\\'')]}`,
[]Node{newList(), newFilter(newList(), newList(), "=="), newList(), newField("status"), newField("nodeInfo"), newField("osImage"), newList(), newText("\\'")}, false},
}

func collectNode(nodes []Node, cur Node) []Node {
Expand Down