mirror of
https://github.com/kubeshark/kubeshark.git
synced 2025-06-21 13:58:49 +00:00
* Make `logger` a separate module such that don't depend on `shared` module as a whole for logging * Update `Dockerfile`
314 lines
7.2 KiB
Go
314 lines
7.2 KiB
Go
package oas
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/url"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/chanced/openapi"
|
|
"github.com/up9inc/mizu/logger"
|
|
)
|
|
|
|
type NodePath = []string
|
|
|
|
type Node struct {
|
|
constant *string
|
|
pathParam *openapi.ParameterObj
|
|
pathObj *openapi.PathObj
|
|
parent *Node
|
|
children []*Node
|
|
}
|
|
|
|
func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj, sampleId string) (node *Node) {
|
|
if existingPathObj == nil {
|
|
panic("Invalid function call")
|
|
}
|
|
|
|
pathChunk := path[0]
|
|
potentialMatrix := strings.SplitN(pathChunk, ";", 2)
|
|
if len(potentialMatrix) > 1 {
|
|
pathChunk = potentialMatrix[0]
|
|
logger.Log.Warningf("URI matrix params are not supported: %s", potentialMatrix[1])
|
|
}
|
|
|
|
chunkIsParam := strings.HasPrefix(pathChunk, "{") && strings.HasSuffix(pathChunk, "}")
|
|
pathChunk, err := url.PathUnescape(pathChunk)
|
|
if err != nil {
|
|
logger.Log.Warningf("URI segment is not correctly encoded: %s", pathChunk)
|
|
// any side effects on continuing?
|
|
}
|
|
|
|
chunkIsGibberish := IsGibberish(pathChunk) && !IsVersionString(pathChunk)
|
|
|
|
var paramObj *openapi.ParameterObj
|
|
if chunkIsParam && existingPathObj != nil && existingPathObj.Parameters != nil {
|
|
_, paramObj = findParamByName(existingPathObj.Parameters, openapi.InPath, pathChunk[1:len(pathChunk)-1])
|
|
}
|
|
|
|
if paramObj == nil {
|
|
node = n.searchInConstants(pathChunk)
|
|
}
|
|
|
|
if node == nil && pathChunk != "" {
|
|
node = n.searchInParams(paramObj, pathChunk, chunkIsGibberish)
|
|
}
|
|
|
|
// still no node found, should create it
|
|
if node == nil {
|
|
node = new(Node)
|
|
node.parent = n
|
|
n.children = append(n.children, node)
|
|
|
|
if paramObj != nil {
|
|
node.pathParam = paramObj
|
|
} else if chunkIsGibberish {
|
|
newParam := n.createParam()
|
|
node.pathParam = newParam
|
|
} else {
|
|
node.constant = &pathChunk
|
|
}
|
|
}
|
|
|
|
if node.pathParam != nil {
|
|
setSampleID(&node.pathParam.Extensions, sampleId)
|
|
}
|
|
|
|
// add example if it's a gibberish chunk
|
|
if node.pathParam != nil && !chunkIsParam {
|
|
exmp := &node.pathParam.Examples
|
|
err := fillParamExample(&exmp, pathChunk)
|
|
if err != nil {
|
|
logger.Log.Warningf("Failed to add example to a parameter: %s", err)
|
|
}
|
|
|
|
if len(*exmp) >= 3 && node.pathParam.Schema.Pattern == nil { // is it enough to decide on 2 samples?
|
|
node.pathParam.Schema.Pattern = getPatternFromExamples(exmp)
|
|
}
|
|
}
|
|
|
|
// TODO: eat up trailing slash, in a smart way: node.pathObj!=nil && path[1]==""
|
|
if len(path) > 1 {
|
|
return node.getOrSet(path[1:], existingPathObj, sampleId)
|
|
} else if node.pathObj == nil {
|
|
node.pathObj = existingPathObj
|
|
}
|
|
|
|
return node
|
|
}
|
|
|
|
func getPatternFromExamples(exmp *openapi.Examples) *openapi.Regexp {
|
|
allInts := true
|
|
strs := make([]string, 0)
|
|
for _, example := range *exmp {
|
|
exampleObj, err := example.ResolveExample(exampleResolver)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
var value string
|
|
err = json.Unmarshal(exampleObj.Value, &value)
|
|
if err != nil {
|
|
logger.Log.Warningf("Failed decoding parameter example into string: %s", err)
|
|
continue
|
|
}
|
|
strs = append(strs, value)
|
|
|
|
if _, err := strconv.Atoi(value); err != nil {
|
|
allInts = false
|
|
}
|
|
}
|
|
|
|
if allInts {
|
|
re := new(openapi.Regexp)
|
|
re.Regexp = regexp.MustCompile(`\d+`)
|
|
return re
|
|
} else {
|
|
prefix := longestCommonXfixStr(strs, true)
|
|
suffix := longestCommonXfixStr(strs, false)
|
|
|
|
pat := ""
|
|
separators := "-._/:|*,+" // TODO: we could also cut prefix till the last separator
|
|
if len(prefix) > 0 && strings.Contains(separators, string(prefix[len(prefix)-1])) {
|
|
pat = "^" + regexp.QuoteMeta(prefix)
|
|
}
|
|
|
|
pat += ".+"
|
|
|
|
if len(suffix) > 0 && strings.Contains(separators, string(suffix[0])) {
|
|
pat += regexp.QuoteMeta(suffix) + "$"
|
|
}
|
|
|
|
if pat != ".+" {
|
|
re := new(openapi.Regexp)
|
|
re.Regexp = regexp.MustCompile(pat)
|
|
return re
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *Node) createParam() *openapi.ParameterObj {
|
|
name := "param"
|
|
|
|
if n.constant != nil { // the node is already a param
|
|
// REST assumption, not always correct
|
|
if strings.HasSuffix(*n.constant, "es") && len(*n.constant) > 4 {
|
|
name = *n.constant
|
|
name = name[:len(name)-2] + "Id"
|
|
} else if strings.HasSuffix(*n.constant, "s") && len(*n.constant) > 3 {
|
|
name = *n.constant
|
|
name = name[:len(name)-1] + "Id"
|
|
} else {
|
|
name = *n.constant + "Id"
|
|
}
|
|
|
|
name = cleanStr(name, isAlNumRune)
|
|
if !isAlphaRune(rune(name[0])) {
|
|
name = "_" + name
|
|
}
|
|
}
|
|
|
|
newParam := createSimpleParam(name, "path", "string")
|
|
x := n.countParentParams()
|
|
if x > 0 {
|
|
newParam.Name = newParam.Name + strconv.Itoa(x)
|
|
}
|
|
|
|
return newParam
|
|
}
|
|
|
|
func (n *Node) searchInParams(paramObj *openapi.ParameterObj, chunk string, chunkIsGibberish bool) *Node {
|
|
// look among params
|
|
for _, subnode := range n.children {
|
|
if subnode.constant != nil {
|
|
continue
|
|
}
|
|
|
|
if paramObj != nil {
|
|
// TODO: mergeParam(subnode.pathParam, paramObj)
|
|
return subnode
|
|
} else if subnode.pathParam.Schema.Pattern != nil { // it has defined param pattern, have to respect it
|
|
// TODO: and not in exceptions
|
|
if subnode.pathParam.Schema.Pattern.Match([]byte(chunk)) {
|
|
return subnode
|
|
} else if chunkIsGibberish {
|
|
// TODO: what to do if gibberish chunk does not match the pattern and not in exceptions?
|
|
return nil
|
|
} else {
|
|
return nil
|
|
}
|
|
} else if chunkIsGibberish {
|
|
return subnode
|
|
}
|
|
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *Node) searchInConstants(pathChunk string) *Node {
|
|
// look among constants
|
|
for _, subnode := range n.children {
|
|
if subnode.constant == nil {
|
|
continue
|
|
}
|
|
|
|
if *subnode.constant == pathChunk {
|
|
return subnode
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (n *Node) compact() {
|
|
// TODO
|
|
}
|
|
|
|
func (n *Node) listPaths() *openapi.Paths {
|
|
paths := &openapi.Paths{Items: map[openapi.PathValue]*openapi.PathObj{}}
|
|
|
|
var strChunk string
|
|
if n.constant != nil {
|
|
strChunk = *n.constant
|
|
} else if n.pathParam != nil {
|
|
strChunk = "{" + n.pathParam.Name + "}"
|
|
} // else -> this is the root node
|
|
|
|
// add self
|
|
if n.pathObj != nil {
|
|
fillPathParams(n, n.pathObj)
|
|
paths.Items[openapi.PathValue(strChunk)] = n.pathObj
|
|
}
|
|
|
|
// recurse into children
|
|
for _, child := range n.children {
|
|
subPaths := child.listPaths()
|
|
for path, pathObj := range subPaths.Items {
|
|
var concat string
|
|
if n.parent == nil {
|
|
concat = string(path)
|
|
} else {
|
|
concat = strChunk + "/" + string(path)
|
|
}
|
|
paths.Items[openapi.PathValue(concat)] = pathObj
|
|
}
|
|
}
|
|
|
|
return paths
|
|
}
|
|
|
|
func fillPathParams(n *Node, pathObj *openapi.PathObj) {
|
|
// collect all path parameters from parent hierarchy
|
|
node := n
|
|
for {
|
|
if node.pathParam != nil {
|
|
initParams(&pathObj.Parameters)
|
|
|
|
idx, paramObj := findParamByName(pathObj.Parameters, openapi.InPath, node.pathParam.Name)
|
|
if paramObj == nil {
|
|
appended := append(*pathObj.Parameters, node.pathParam)
|
|
pathObj.Parameters = &appended
|
|
} else {
|
|
(*pathObj.Parameters)[idx] = paramObj
|
|
}
|
|
}
|
|
|
|
node = node.parent
|
|
if node == nil {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
type PathAndOp struct {
|
|
path string
|
|
op *openapi.Operation
|
|
}
|
|
|
|
func (n *Node) listOps() []PathAndOp {
|
|
res := make([]PathAndOp, 0)
|
|
for path, pathObj := range n.listPaths().Items {
|
|
for _, op := range getOps(pathObj) {
|
|
res = append(res, PathAndOp{path: string(path), op: op})
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
func (n *Node) countParentParams() int {
|
|
res := 0
|
|
node := n
|
|
for {
|
|
if node.pathParam != nil {
|
|
res++
|
|
}
|
|
|
|
if node.parent == nil {
|
|
break
|
|
}
|
|
node = node.parent
|
|
}
|
|
return res
|
|
}
|