diff --git a/staging/src/k8s.io/client-go/util/jsonpath/jsonpath.go b/staging/src/k8s.io/client-go/util/jsonpath/jsonpath.go index 6633ca0d651..80410607024 100644 --- a/staging/src/k8s.io/client-go/util/jsonpath/jsonpath.go +++ b/staging/src/k8s.io/client-go/util/jsonpath/jsonpath.go @@ -255,10 +255,9 @@ func (j *JSONPath) evalArray(input []reflect.Value, node *ArrayNode) ([]reflect. params[1].Value = value.Len() } - if params[1].Value < 0 { + if params[1].Value < 0 || (params[1].Value == 0 && params[1].Derived) { params[1].Value += value.Len() } - sliceLength := value.Len() if params[1].Value != params[0].Value { // if you're requesting zero elements, allow it through. if params[0].Value >= sliceLength || params[0].Value < 0 { @@ -267,6 +266,11 @@ func (j *JSONPath) evalArray(input []reflect.Value, node *ArrayNode) ([]reflect. if params[1].Value > sliceLength || params[1].Value < 0 { return input, fmt.Errorf("array index out of bounds: index %d, length %d", params[1].Value-1, sliceLength) } + if params[0].Value > params[1].Value { + return input, fmt.Errorf("starting index %d is greater than ending index %d", params[0].Value, params[1].Value) + } + } else { + return result, nil } if !params[2].Known { diff --git a/staging/src/k8s.io/client-go/util/jsonpath/jsonpath_test.go b/staging/src/k8s.io/client-go/util/jsonpath/jsonpath_test.go index 6c7277d10a2..fd7253f95fb 100644 --- a/staging/src/k8s.io/client-go/util/jsonpath/jsonpath_test.go +++ b/staging/src/k8s.io/client-go/util/jsonpath/jsonpath_test.go @@ -369,3 +369,153 @@ func TestFilterPartialMatchesSometimesMissingAnnotations(t *testing.T) { t, ) } + +func TestNegativeIndex(t *testing.T) { + var input = []byte( + `{ + "apiVersion": "v1", + "kind": "Pod", + "spec": { + "containers": [ + { + "image": "radial/busyboxplus:curl", + "name": "fake0" + }, + { + "image": "radial/busyboxplus:curl", + "name": "fake1" + }, + { + "image": "radial/busyboxplus:curl", + "name": "fake2" + }, + { + "image": "radial/busyboxplus:curl", + "name": "fake3" + }]}}`) + + var data interface{} + err := json.Unmarshal(input, &data) + if err != nil { + t.Fatal(err) + } + + testJSONPath( + []jsonpathTest{ + { + "test containers[0], it equals containers[0]", + `{.spec.containers[0].name}`, + data, + "fake0", + false, + }, + { + "test containers[0:0], it equals the empty set", + `{.spec.containers[0:0].name}`, + data, + "", + false, + }, + { + "test containers[0:-1], it equals containers[0:3]", + `{.spec.containers[0:-1].name}`, + data, + "fake0 fake1 fake2", + false, + }, + { + "test containers[-1:0], expect error", + `{.spec.containers[-1:0].name}`, + data, + "", + true, + }, + { + "test containers[-1], it equals containers[3]", + `{.spec.containers[-1].name}`, + data, + "fake3", + false, + }, + { + "test containers[-1:], it equals containers[3:]", + `{.spec.containers[-1:].name}`, + data, + "fake3", + false, + }, + { + "test containers[-2], it equals containers[2]", + `{.spec.containers[-2].name}`, + data, + "fake2", + false, + }, + { + "test containers[-2:], it equals containers[2:]", + `{.spec.containers[-2:].name}`, + data, + "fake2 fake3", + false, + }, + { + "test containers[-3], it equals containers[1]", + `{.spec.containers[-3].name}`, + data, + "fake1", + false, + }, + { + "test containers[-4], it equals containers[0]", + `{.spec.containers[-4].name}`, + data, + "fake0", + false, + }, + { + "test containers[-4:], it equals containers[0:]", + `{.spec.containers[-4:].name}`, + data, + "fake0 fake1 fake2 fake3", + false, + }, + { + "test containers[-5], expect a error cause it out of bounds", + `{.spec.containers[-5].name}`, + data, + "", + true, // expect error + }, + { + "test containers[5:5], expect empty set", + `{.spec.containers[5:5].name}`, + data, + "", + false, + }, + { + "test containers[-5:-5], expect empty set", + `{.spec.containers[-5:-5].name}`, + data, + "", + false, + }, + { + "test containers[3:1], expect a error cause start index is greater than end index", + `{.spec.containers[3:1].name}`, + data, + "", + true, + }, + { + "test containers[-1:-2], it equals containers[3:2], expect a error cause start index is greater than end index", + `{.spec.containers[-1:-2].name}`, + data, + "", + true, + }, + }, + false, + t, + ) +} diff --git a/staging/src/k8s.io/client-go/util/jsonpath/node.go b/staging/src/k8s.io/client-go/util/jsonpath/node.go index 2f612b188fb..83abe8b0377 100644 --- a/staging/src/k8s.io/client-go/util/jsonpath/node.go +++ b/staging/src/k8s.io/client-go/util/jsonpath/node.go @@ -130,8 +130,9 @@ func (f *IdentifierNode) String() string { // ParamsEntry holds param information for ArrayNode type ParamsEntry struct { - Value int - Known bool // whether the value is known when parse it + Value int + Known bool // whether the value is known when parse it + Derived bool } // ArrayNode holds start, end, step information for array index selection diff --git a/staging/src/k8s.io/client-go/util/jsonpath/parser.go b/staging/src/k8s.io/client-go/util/jsonpath/parser.go index 99b45849c98..93d58295ff9 100644 --- a/staging/src/k8s.io/client-go/util/jsonpath/parser.go +++ b/staging/src/k8s.io/client-go/util/jsonpath/parser.go @@ -325,6 +325,7 @@ Loop: if i == 1 { params[i].Known = true params[i].Value = params[0].Value + 1 + params[i].Derived = true } else { params[i].Known = false params[i].Value = 0 diff --git a/staging/src/k8s.io/client-go/util/jsonpath/parser_test.go b/staging/src/k8s.io/client-go/util/jsonpath/parser_test.go index 4f71a60ac7b..473815f877d 100644 --- a/staging/src/k8s.io/client-go/util/jsonpath/parser_test.go +++ b/staging/src/k8s.io/client-go/util/jsonpath/parser_test.go @@ -35,10 +35,10 @@ var parserTests = []parserTest{ []Node{newText("hello "), newList(), newField("jsonpath")}, false}, {"quote", `{"{"}`, []Node{newList(), newText("{")}, false}, {"array", `{[1:3]}`, []Node{newList(), - newArray([3]ParamsEntry{{1, true}, {3, true}, {0, false}})}, false}, + newArray([3]ParamsEntry{{1, true, false}, {3, true, false}, {0, false, false}})}, false}, {"allarray", `{.book[*].author}`, []Node{newList(), newField("book"), - newArray([3]ParamsEntry{{0, false}, {0, false}, {0, false}}), newField("author")}, false}, + newArray([3]ParamsEntry{{0, false, false}, {0, false, false}, {0, false, false}}), newField("author")}, false}, {"wildcard", `{.bicycle.*}`, []Node{newList(), newField("bicycle"), newWildcard()}, false}, {"filter", `{[?(@.price<3)]}`, @@ -52,7 +52,7 @@ var parserTests = []parserTest{ }, false}, {"union", `{['bicycle.price', 3, 'book.price']}`, []Node{newList(), newUnion([]*ListNode{}), newList(), newField("bicycle"), newField("price"), - newList(), newArray([3]ParamsEntry{{3, true}, {4, true}, {0, false}}), + newList(), newArray([3]ParamsEntry{{3, true, false}, {4, true, true}, {0, false, false}}), newList(), newField("book"), newField("price"), }, false}, {"range", `{range .items}{.name},{end}`, []Node{ @@ -77,6 +77,12 @@ var parserTests = []parserTest{ []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}, + {"negative index slice, equals a[len-5] to a[len-1]", `{[-5:]}`, []Node{newList(), + newArray([3]ParamsEntry{{-5, true, false}, {0, false, false}, {0, false, false}})}, false}, + {"negative index slice, equals a[len-1]", `{[-1]}`, []Node{newList(), + newArray([3]ParamsEntry{{-1, true, false}, {0, true, true}, {0, false, false}})}, false}, + {"negative index slice, equals a[1] to a[len-1]", `{[1:-1]}`, []Node{newList(), + newArray([3]ParamsEntry{{1, true, false}, {-1, true, false}, {0, false, false}})}, false}, } func collectNode(nodes []Node, cur Node) []Node {