From 6890688b8fecb74e535f214efa9e7db77129d8bb Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 30 Apr 2021 10:35:33 +0300 Subject: [PATCH] oracle: add max nesting depth to JSONPath filter --- pkg/services/oracle/filter_test.go | 1 - pkg/services/oracle/jsonpath/jsonpath.go | 40 +++++++++++++-- pkg/services/oracle/jsonpath/jsonpath_test.go | 50 ++++++++++++++++++- 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/pkg/services/oracle/filter_test.go b/pkg/services/oracle/filter_test.go index 7a7de548c..134d94b6d 100644 --- a/pkg/services/oracle/filter_test.go +++ b/pkg/services/oracle/filter_test.go @@ -31,7 +31,6 @@ func TestFilter(t *testing.T) { }{ {"[]", "$.Name"}, {`["Acme Co"]`, "$.Manufacturers[0].Name"}, - {`["Acme Co"]`, "$..Manufacturers[0].Name"}, {`[50]`, "$.Manufacturers[0].Products[0].Price"}, {`["Elbow Grease"]`, "$.Manufacturers[1].Products[0].Name"}, {`[{"Name":"Elbow Grease","Price":99.95}]`, "$.Manufacturers[1].Products[0]"}, diff --git a/pkg/services/oracle/jsonpath/jsonpath.go b/pkg/services/oracle/jsonpath/jsonpath.go index 7a1957f61..94887926e 100644 --- a/pkg/services/oracle/jsonpath/jsonpath.go +++ b/pkg/services/oracle/jsonpath/jsonpath.go @@ -13,8 +13,9 @@ type ( // pathParser combines JSONPath and a position to start parsing from. pathParser struct { - s string - i int + s string + i int + depth int } ) @@ -32,10 +33,15 @@ const ( pathNumber ) +const maxNestingDepth = 6 + // Get returns substructures of value selected by path. // The result is always non-nil unless path is invalid. func Get(path string, value interface{}) ([]interface{}, bool) { - p := pathParser{s: path} + p := pathParser{ + depth: maxNestingDepth, + s: path, + } typ, _ := p.nextToken() if typ != pathRoot { @@ -177,6 +183,11 @@ func (p *pathParser) processDot(objs []interface{}) ([]interface{}, bool) { // descend descends 1 level down. // It flattens arrays and returns map values for maps. func (p *pathParser) descend(objs []interface{}) ([]interface{}, bool) { + if p.depth <= 0 { + return nil, false + } + p.depth-- + var values []interface{} for i := range objs { switch obj := objs[i].(type) { @@ -209,7 +220,7 @@ func (p *pathParser) descendRecursive(objs []interface{}) ([]interface{}, bool) var values []interface{} for len(objs) > 0 { - newObjs, _ := p.descendByIdent(objs, val) + newObjs, _ := p.descendByIdentAux(objs, false, val) values = append(values, newObjs...) objs, _ = p.descend(objs) } @@ -219,6 +230,17 @@ func (p *pathParser) descendRecursive(objs []interface{}) ([]interface{}, bool) // descendByIdent performs map's field access by name. func (p *pathParser) descendByIdent(objs []interface{}, names ...string) ([]interface{}, bool) { + return p.descendByIdentAux(objs, true, names...) +} + +func (p *pathParser) descendByIdentAux(objs []interface{}, checkDepth bool, names ...string) ([]interface{}, bool) { + if checkDepth { + if p.depth <= 0 { + return nil, false + } + p.depth-- + } + var values []interface{} for i := range objs { obj, ok := objs[i].(map[string]interface{}) @@ -237,6 +259,11 @@ func (p *pathParser) descendByIdent(objs []interface{}, names ...string) ([]inte // descendByIndex performs array access by index. func (p *pathParser) descendByIndex(objs []interface{}, indices ...int) ([]interface{}, bool) { + if p.depth <= 0 { + return nil, false + } + p.depth-- + var values []interface{} for i := range objs { obj, ok := objs[i].([]interface{}) @@ -382,6 +409,11 @@ func (p *pathParser) processSlice(objs []interface{}, start int) ([]interface{}, // descendByRange is similar to descend but skips maps and returns sub-slices for arrays. func (p *pathParser) descendByRange(objs []interface{}, start, end int) ([]interface{}, bool) { + if p.depth <= 0 { + return nil, false + } + p.depth-- + var values []interface{} for i := range objs { arr, ok := objs[i].([]interface{}) diff --git a/pkg/services/oracle/jsonpath/jsonpath_test.go b/pkg/services/oracle/jsonpath/jsonpath_test.go index 94be5d4c0..c6cff988c 100644 --- a/pkg/services/oracle/jsonpath/jsonpath_test.go +++ b/pkg/services/oracle/jsonpath/jsonpath_test.go @@ -98,7 +98,6 @@ func TestDescendByIdent(t *testing.T) { {"$.*.name", `["small","big"]`}, {"$..store.name", `["big"]`}, {"$.store..name", `["big","ppp","sub1","sub2"]`}, - {"$..sub[*].name", `["sub1","sub2"]`}, {"$..sub.name", `[]`}, {"$..sub..name", `["sub1","sub2"]`}, } @@ -107,6 +106,28 @@ func TestDescendByIdent(t *testing.T) { tc.testUnmarshalGet(t, js) }) } + + t.Run("big depth", func(t *testing.T) { + js := `{"a":{"b":{"c":{"d":{"e":{"f":{"g":1}}}}}}}` + t.Run("single field", func(t *testing.T) { + t.Run("max", func(t *testing.T) { + p := pathTestCase{"$.a.b.c.d.e.f", `[{"g":1}]`} + p.testUnmarshalGet(t, js) + }) + + _, ok := unmarshalGet(t, js, "$.a.b.c.d.e.f.g") + require.False(t, ok) + }) + t.Run("wildcard", func(t *testing.T) { + t.Run("max", func(t *testing.T) { + p := pathTestCase{"$.*.*.*.*.*.*", `[{"g":1}]`} + p.testUnmarshalGet(t, js) + }) + + _, ok := unmarshalGet(t, js, "$.*.*.*.*.*.*.*") + require.False(t, ok) + }) + }) } func TestDescendByIndex(t *testing.T) { @@ -131,6 +152,28 @@ func TestDescendByIndex(t *testing.T) { }) } + t.Run("big depth", func(t *testing.T) { + js := `[[[[[[[1]]]]]]]` + t.Run("single index", func(t *testing.T) { + t.Run("max", func(t *testing.T) { + p := pathTestCase{"$[0][0][0][0][0][0]", "[[1]]"} + p.testUnmarshalGet(t, js) + }) + + _, ok := unmarshalGet(t, js, "$[0][0][0][0][0][0][0]") + require.False(t, ok) + }) + t.Run("slice", func(t *testing.T) { + t.Run("max", func(t *testing.T) { + p := pathTestCase{"$[0:][0:][0:][0:][0:][0:]", "[[1]]"} + p.testUnmarshalGet(t, js) + }) + + _, ok := unmarshalGet(t, js, "$[0:][0:][0:][0:][0:][0:][0:]") + require.False(t, ok) + }) + }) + t.Run("$[:][1], skip wrong types", func(t *testing.T) { js := `[[1,2],{"1":"4"},[5,6]]` p := pathTestCase{"$[:][1]", "[2,6]"} @@ -227,4 +270,9 @@ func TestCSharpCompat(t *testing.T) { tc.testUnmarshalGet(t, js) }) } + + t.Run("bad cases", func(t *testing.T) { + _, ok := unmarshalGet(t, js, `$..book[*].author"`) + require.False(t, ok) + }) }