mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-15 17:51:20 +00:00
367156f60b
* 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>
244 lines
6.5 KiB
Go
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:]
|
|
}
|