diff --git a/agent/pkg/oas/tree.go b/agent/pkg/oas/tree.go index 70eb27b5c..5faa2a373 100644 --- a/agent/pkg/oas/tree.go +++ b/agent/pkg/oas/tree.go @@ -2,13 +2,12 @@ package oas import ( "encoding/json" + "github.com/chanced/openapi" + "github.com/up9inc/mizu/shared/logger" "net/url" "regexp" "strconv" "strings" - - "github.com/chanced/openapi" - "github.com/up9inc/mizu/shared/logger" ) type NodePath = []string @@ -79,7 +78,7 @@ func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj) (node * logger.Log.Warningf("Failed to add example to a parameter: %s", err) } - if len(*exmp) > 1 && node.pathParam.Schema.Pattern == nil { // is it enough to decide on 2 samples? + if len(*exmp) >= 3 && node.pathParam.Schema.Pattern == nil { // is it enough to decide on 2 samples? node.pathParam.Schema.Pattern = getPatternFromExamples(exmp) } } @@ -96,6 +95,7 @@ func (n *Node) getOrSet(path NodePath, existingPathObj *openapi.PathObj) (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 { @@ -108,6 +108,7 @@ func getPatternFromExamples(exmp *openapi.Examples) *openapi.Regexp { 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 @@ -118,6 +119,27 @@ func getPatternFromExamples(exmp *openapi.Examples) *openapi.Regexp { re := new(openapi.Regexp) re.Regexp = regexp.MustCompile(`\d+`) return re + } else { + prefix := longestCommonXfixStr(strs, true) + suffix := longestCommonXfixStr(strs, false) + + pat := "" + separators := "-._/:|*,+" + 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(`\d+`) + return re + } } return nil } @@ -159,19 +181,20 @@ func (n *Node) searchInParams(paramObj *openapi.ParameterObj, chunk string, chun continue } - if chunkIsGibberish { - return subnode - } else if paramObj != nil { + if paramObj != nil { // TODO: mergeParam(subnode.pathParam, paramObj) return subnode - } else if subnode.pathParam.Schema.Pattern != nil { + } 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 { return nil } + } else if chunkIsGibberish { + return subnode } + } return nil } diff --git a/agent/pkg/oas/utils.go b/agent/pkg/oas/utils.go index 3c47852ee..59fb0849b 100644 --- a/agent/pkg/oas/utils.go +++ b/agent/pkg/oas/utils.go @@ -290,6 +290,53 @@ func longestCommonXfix(strs [][]string, pre bool) []string { // https://github.c return xfix } +func longestCommonXfixStr(strs []string, pre bool) string { // https://github.com/jpillora/longestcommon + //short-circuit empty list + if len(strs) == 0 { + return "" + } + xfix := strs[0] + //short-circuit single-element list + if len(strs) == 1 { + return xfix + } + //compare first to rest + for _, str := range strs[1:] { + xfixl := len(xfix) + strl := len(str) + //short-circuit empty strings + if xfixl == 0 || strl == 0 { + return "" + } + //maximum possible length + maxl := xfixl + if strl < maxl { + maxl = strl + } + //compare letters + if pre { + //prefix, iterate left to right + for i := 0; i < maxl; i++ { + if xfix[i] != str[i] { + xfix = xfix[:i] + break + } + } + } else { + //suffix, iternate right to left + for i := 0; i < maxl; i++ { + xi := xfixl - i - 1 + si := strl - i - 1 + if xfix[xi] != str[si] { + xfix = xfix[xi+1:] + break + } + } + } + } + return xfix +} + func getSimilarPrefix(strs []string) string { chunked := make([][]string, 0) for _, item := range strs {