1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00
kyverno/pkg/utils/jsonpointer/pointer.go
Ved Ratan 367156f60b
[Chore] Bump to Go 1.20 (#6683)
* changed go version 1.19->1.20

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

* updated go version in actions

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

* bumped golangci-lint

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

* fix

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

* fix conflicts

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

* fix

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

* fixed some linter issues

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

* fixed some linter issues

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

* possible fix

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

* fix

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

* small fix

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

* fix

Signed-off-by: Ved Ratan <vedratan8@gmail.com>

---------

Signed-off-by: Ved Ratan <vedratan8@gmail.com>
Signed-off-by: Ved Ratan <82467006+VedRatan@users.noreply.github.com>
2023-04-03 11:40:47 +00:00

244 lines
6.5 KiB
Go

package jsonpointer
import (
"fmt"
"strconv"
"strings"
"unicode"
"unicode/utf8"
"k8s.io/utils/strings/slices"
)
// Pointer is a JSON pointer that can be retrieved as either as a RFC6901 string or as a JMESPath formatted string.
type Pointer []string
// unquoted identifiers must only contain these characters.
var unquotedFirstCharRangeTable = &unicode.RangeTable{
R16: []unicode.Range16{
{Lo: '(', Hi: '(', Stride: 1}, // Special non-standard. Used by policy documents for matching attributes.
{Lo: 'A', Hi: 'Z', Stride: 1},
{Lo: '_', Hi: '_', Stride: 1},
{Lo: 'a', Hi: 'z', Stride: 1},
},
}
// unquoted identifiers can contain any combination of these runes.
var unquotedStringRangeTable = &unicode.RangeTable{
R16: []unicode.Range16{
{Lo: ')', Hi: ')', Stride: 1}, // Special non-standard. Used by policy documents for matching attributes.
{Lo: '0', Hi: '9', Stride: 1},
{Lo: 'A', Hi: 'Z', Stride: 1},
{Lo: '_', Hi: '_', Stride: 1},
{Lo: 'a', Hi: 'z', Stride: 1},
},
}
// a quoted identifier can contain any of these characters as is.
var unescapedCharRangeTable = &unicode.RangeTable{
R16: []unicode.Range16{
{Lo: 0x20, Hi: 0x21, Stride: 1},
{Lo: 0x23, Hi: 0x5B, Stride: 1},
{Lo: 0x5D, Hi: 0x1EFF, Stride: 1},
},
R32: []unicode.Range32{
{Lo: 0x1000, Hi: 0x10FFFF, Stride: 1},
},
LatinOffset: 0x1EFF - unicode.MaxLatin1,
}
// some special characters must be escaped to be possible to use inside a quoted identifier.
var escapeCharMap = map[rune]string{
'"': `\"`, // quotation mark
'\\': `\\`, // reverse solidus
'/': `\/`, // solidus
'\b': `\b`, // backspace
'\f': `\f`, // form feed
'\n': `\n`, // line feed
'\r': `\r`, // carriage return
'\t': `\t`, // tab
}
const initialCapacity = 10 // pointers should start with a non-zero capacity to lower the amount of re-allocations done by append.
// New will return an empty Pointer.
func New() Pointer {
return make([]string, 0, initialCapacity)
}
// Parse will parse the string as a JSON pointer according to RFC 6901.
func Parse(s string) Pointer {
pointer := New()
replacer := strings.NewReplacer("~1", "/", "~0", "~", `\\`, `\`, `\"`, `"`)
for _, component := range strings.FieldsFunc(s, func(r rune) bool {
return r == '/'
}) {
pointer = append(pointer, replacer.Replace(component))
}
return pointer
}
// ParsePath will parse the raw path and return it in the form of a Pointer.
func ParsePath(rawPath string) Pointer {
// Start with a slice with a non-zero capacity to avoid reallocation for most paths.
pointer := New()
// Use a string builder and a flush function to append path components to the slice.
sb := strings.Builder{}
flush := func() {
s := sb.String()
if s != "" {
pointer = append(pointer, s)
}
sb.Reset()
}
var pos, width int
var escaped, quoted bool
for i := 0; i <= len(rawPath); i += width {
var r rune
r, width = utf8.DecodeRuneInString(rawPath[i:])
if r == utf8.RuneError && width == 1 {
break
}
switch {
case escaped: // previous character was a backslash.
sb.WriteRune(r)
escaped = !escaped
case r == '\\': // escape character
escaped = !escaped
case r == '"': // quoted strings
if quoted {
s, _ := strconv.Unquote(rawPath[pos : i+width])
sb.WriteString(s)
}
quoted = !quoted
case r == '/' && !quoted:
flush()
case r == utf8.RuneError: // end of string
flush()
return pointer
default:
sb.WriteRune(r)
}
pos = i + width
}
// This is unreachable but we must return something.
return pointer
}
// JMESPath will return the Pointer in the form of a JMESPath string.
func (p Pointer) JMESPath() string {
sb := strings.Builder{}
for _, component := range p {
// Components that are valid unsigned integers are treated as indices.
if _, err := strconv.ParseUint(component, 10, 64); err == nil {
sb.WriteRune('[')
sb.WriteString(component)
sb.WriteRune(']')
continue
}
// Write a dot before we write anything, as long as buffer is not empty.
if sb.Len() > 0 {
sb.WriteRune('.')
}
// If the component starts with a character that is valid as an initial character for an identifier
// and the remaining characters are also valid for an unquoted identifier then we can append it to
// the JMESPath as is.
if ch, _ := utf8.DecodeRuneInString(component); unicode.Is(unquotedFirstCharRangeTable, ch) &&
strings.IndexFunc(component, func(r rune) bool {
return !unicode.Is(unquotedStringRangeTable, r)
}) == -1 {
sb.WriteString(component)
continue
}
// The component contains characters that are not allowed for unquoted identifiers, so we need to take some extra
// steps to ensure that it's a valid, quoted identifier.
sb.WriteRune('"')
for _, r := range component {
// Any character in the range table of allowed runes can be written as is.
if unicode.Is(unescapedCharRangeTable, r) {
sb.WriteRune(r)
continue
}
// Convert special characters to their escaped sequence.
if escaped, ok := escapeCharMap[r]; ok {
sb.WriteString(escaped)
continue
}
// All other characters must be written as unicode escape sequences ay 16 bits a piece.
if i := utf8.RuneLen(r); i <= 2 {
// Rune is 1 or 2 bytes.
_, _ = fmt.Fprintf(&sb, "\\u%04x", r&0xffff)
} else {
_, _ = fmt.Fprintf(&sb, "\\u%04x", r&0xffff)
_, _ = fmt.Fprintf(&sb, "\\u%04x", r>>16)
}
}
sb.WriteRune('"')
}
// Return the JMESPath.
return sb.String()
}
// String will return the pointer as a string (RFC6901).
func (p Pointer) String() string {
sb := strings.Builder{}
replacer := strings.NewReplacer("~", "~0", "/", "~1", `\`, `\\`, `"`, `\"`)
for _, component := range p {
if sb.Len() > 0 {
sb.WriteRune('/')
}
_, _ = replacer.WriteString(&sb, component)
}
// Return the pointer.
return sb.String()
}
// Append will return a Pointer with the strings appended.
func (p Pointer) Append(s ...string) Pointer {
return append(p, s...)
}
// Prepend will return a Pointer prefixed with the specified strings.
func (p Pointer) Prepend(s ...string) Pointer {
return append(s, p...)
}
// AppendPath will parse the string as a JSON pointer and return a new pointer.
func (p Pointer) AppendPath(s string) Pointer {
return append(p, ParsePath(s)...)
}
// SkipN will return a new Pointer where the first N element are stripped.
func (p Pointer) SkipN(n int) Pointer {
if n > len(p)-1 {
return []string{}
}
return p[n:]
}
// SkipPast will return a new Pointer where every element upto and including the specified string has been stripped off.
func (p Pointer) SkipPast(s string) Pointer {
return p[slices.Index(p, s)+1:]
}