This is a fork of:
https://github.com/roglew/puppy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1090 lines
27 KiB
1090 lines
27 KiB
package puppy |
|
|
|
import ( |
|
"errors" |
|
"fmt" |
|
"net/http" |
|
"net/url" |
|
"regexp" |
|
"strconv" |
|
"strings" |
|
"time" |
|
) |
|
|
|
type SearchField int |
|
type StrComparer int |
|
|
|
type strFieldGetter func(req *ProxyRequest) ([]string, error) |
|
type kvFieldGetter func(req *ProxyRequest) ([]*PairValue, error) |
|
|
|
type RequestChecker func(req *ProxyRequest) bool |
|
|
|
// Searchable fields |
|
const ( |
|
FieldAll SearchField = iota |
|
|
|
FieldRequestBody |
|
FieldResponseBody |
|
FieldAllBody |
|
FieldWSMessage |
|
|
|
FieldRequestHeaders |
|
FieldResponseHeaders |
|
FieldBothHeaders |
|
|
|
FieldMethod |
|
FieldHost |
|
FieldPath |
|
FieldURL |
|
FieldStatusCode |
|
FieldTag |
|
|
|
FieldBothParam |
|
FieldURLParam |
|
FieldPostParam |
|
FieldResponseCookie |
|
FieldRequestCookie |
|
FieldBothCookie |
|
|
|
FieldAfter |
|
FieldBefore |
|
FieldTimeRange |
|
|
|
FieldInvert |
|
|
|
FieldId |
|
) |
|
|
|
// Operators for string values |
|
const ( |
|
StrIs StrComparer = iota |
|
StrContains |
|
StrContainsRegexp |
|
|
|
StrLengthGreaterThan |
|
StrLengthLessThan |
|
StrLengthEqualTo |
|
) |
|
|
|
// A struct representing the data to be searched for a pair such as a header or url param |
|
type PairValue struct { |
|
key string |
|
value string |
|
} |
|
|
|
// A list of queries. Will match if any queries match the request |
|
type QueryPhrase [][]interface{} |
|
// A list of phrases. Will match if all the phrases match the request |
|
type MessageQuery []QueryPhrase |
|
|
|
// A list of queries in string form. Will match if any queries match the request |
|
type StrQueryPhrase [][]string |
|
// A list of phrases in string form. Will match if all the phrases match the request |
|
type StrMessageQuery []StrQueryPhrase |
|
|
|
// Return a function that returns whether a request matches the given conditions |
|
func NewRequestChecker(args ...interface{}) (RequestChecker, error) { |
|
// Generates a request checker from the given search arguments |
|
if len(args) == 0 { |
|
return nil, errors.New("search requires a search field") |
|
} |
|
|
|
field, ok := args[0].(SearchField) |
|
if !ok { |
|
return nil, fmt.Errorf("first argument must hava a type of SearchField") |
|
} |
|
|
|
switch field { |
|
|
|
// Normal string fields |
|
case FieldAll, FieldRequestBody, FieldResponseBody, FieldAllBody, FieldWSMessage, FieldMethod, FieldHost, FieldPath, FieldStatusCode, FieldTag, FieldId: |
|
getter, err := createstrFieldGetter(field) |
|
if err != nil { |
|
return nil, fmt.Errorf("error performing search: %s", err.Error()) |
|
} |
|
|
|
if len(args) != 3 { |
|
return nil, errors.New("searches through strings must have one checker and one value") |
|
} |
|
|
|
comparer, ok := args[1].(StrComparer) |
|
if !ok { |
|
return nil, errors.New("comparer must be a StrComparer") |
|
} |
|
|
|
return genStrFieldChecker(getter, comparer, args[2]) |
|
|
|
// Normal key/value fields |
|
case FieldRequestHeaders, FieldResponseHeaders, FieldBothHeaders, FieldBothParam, FieldURLParam, FieldPostParam, FieldResponseCookie, FieldRequestCookie, FieldBothCookie: |
|
getter, err := createKvPairGetter(field) |
|
if err != nil { |
|
return nil, fmt.Errorf("error performing search: %s", err.Error()) |
|
} |
|
|
|
if len(args) == 3 { |
|
// Get comparer and value out of function arguments |
|
comparer, ok := args[1].(StrComparer) |
|
if !ok { |
|
return nil, errors.New("comparer must be a StrComparer") |
|
} |
|
|
|
// Create a strFieldGetter out of our key/value getter |
|
strgetter := func(req *ProxyRequest) ([]string, error) { |
|
pairs, err := getter(req) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return pairsToStrings(pairs), nil |
|
} |
|
|
|
// return a str field checker using our new str getter |
|
return genStrFieldChecker(strgetter, comparer, args[2]) |
|
} else if len(args) == 5 { |
|
// Get comparer and value out of function arguments |
|
comparer1, ok := args[1].(StrComparer) |
|
if !ok { |
|
return nil, errors.New("first comparer must be a StrComparer") |
|
} |
|
|
|
val1, ok := args[2].(string) |
|
if !ok { |
|
return nil, errors.New("first val must be a list of bytes") |
|
} |
|
|
|
comparer2, ok := args[3].(StrComparer) |
|
if !ok { |
|
return nil, errors.New("second comparer must be a StrComparer") |
|
} |
|
|
|
val2, ok := args[4].(string) |
|
if !ok { |
|
return nil, errors.New("second val must be a list of bytes") |
|
} |
|
|
|
// Create a checker out of our getter, comparers, and vals |
|
return genKvFieldChecker(getter, comparer1, val1, comparer2, val2) |
|
} else { |
|
return nil, errors.New("invalid number of arguments for a key/value search") |
|
} |
|
|
|
// Other fields |
|
case FieldAfter: |
|
if len(args) != 2 { |
|
return nil, errors.New("searching by 'after' takes exactly on parameter") |
|
} |
|
|
|
val, ok := args[1].(time.Time) |
|
if !ok { |
|
return nil, errors.New("search argument must be a time.Time") |
|
} |
|
|
|
return func(req *ProxyRequest) bool { |
|
return req.StartDatetime.After(val) |
|
}, nil |
|
|
|
case FieldBefore: |
|
if len(args) != 2 { |
|
return nil, errors.New("searching by 'before' takes exactly one parameter") |
|
} |
|
|
|
val, ok := args[1].(time.Time) |
|
if !ok { |
|
return nil, errors.New("search argument must be a time.Time") |
|
} |
|
|
|
return func(req *ProxyRequest) bool { |
|
return req.StartDatetime.Before(val) |
|
}, nil |
|
|
|
case FieldTimeRange: |
|
if len(args) != 3 { |
|
return nil, errors.New("searching by time range takes exactly two parameters") |
|
} |
|
|
|
begin, ok := args[1].(time.Time) |
|
if !ok { |
|
return nil, errors.New("search arguments must be a time.Time") |
|
} |
|
|
|
end, ok := args[2].(time.Time) |
|
if !ok { |
|
return nil, errors.New("search arguments must be a time.Time") |
|
} |
|
|
|
return func(req *ProxyRequest) bool { |
|
return req.StartDatetime.After(begin) && req.StartDatetime.Before(end) |
|
}, nil |
|
|
|
case FieldInvert: |
|
orig, err := NewRequestChecker(args[1:]...) |
|
if err != nil { |
|
return nil, fmt.Errorf("error with query to invert: %s", err.Error()) |
|
} |
|
return func(req *ProxyRequest) bool { |
|
return !orig(req) |
|
}, nil |
|
|
|
default: |
|
return nil, errors.New("invalid field") |
|
} |
|
} |
|
|
|
func createstrFieldGetter(field SearchField) (strFieldGetter, error) { |
|
switch field { |
|
case FieldAll: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
strs := make([]string, 0) |
|
strs = append(strs, string(req.FullMessage())) |
|
|
|
if req.ServerResponse != nil { |
|
strs = append(strs, string(req.ServerResponse.FullMessage())) |
|
} |
|
|
|
for _, wsm := range req.WSMessages { |
|
strs = append(strs, string(wsm.Message)) |
|
} |
|
|
|
return strs, nil |
|
}, nil |
|
case FieldRequestBody: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
strs := make([]string, 0) |
|
strs = append(strs, string(req.BodyBytes())) |
|
return strs, nil |
|
}, nil |
|
case FieldResponseBody: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
strs := make([]string, 0) |
|
if req.ServerResponse != nil { |
|
strs = append(strs, string(req.ServerResponse.BodyBytes())) |
|
} |
|
return strs, nil |
|
}, nil |
|
case FieldAllBody: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
strs := make([]string, 0) |
|
strs = append(strs, string(req.BodyBytes())) |
|
if req.ServerResponse != nil { |
|
strs = append(strs, string(req.ServerResponse.BodyBytes())) |
|
} |
|
return strs, nil |
|
}, nil |
|
case FieldWSMessage: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
strs := make([]string, 0) |
|
|
|
for _, wsm := range req.WSMessages { |
|
strs = append(strs, string(wsm.Message)) |
|
} |
|
|
|
return strs, nil |
|
}, nil |
|
case FieldMethod: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
strs := make([]string, 0) |
|
strs = append(strs, req.Method) |
|
return strs, nil |
|
}, nil |
|
case FieldHost: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
strs := make([]string, 0) |
|
strs = append(strs, req.DestHost) |
|
strs = append(strs, req.Host) |
|
return strs, nil |
|
}, nil |
|
case FieldPath: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
strs := make([]string, 0) |
|
strs = append(strs, req.URL.Path) |
|
return strs, nil |
|
}, nil |
|
case FieldURL: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
strs := make([]string, 0) |
|
strs = append(strs, req.FullURL().String()) |
|
return strs, nil |
|
}, nil |
|
case FieldStatusCode: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
strs := make([]string, 0) |
|
if req.ServerResponse != nil { |
|
strs = append(strs, strconv.Itoa(req.ServerResponse.StatusCode)) |
|
} |
|
return strs, nil |
|
}, nil |
|
case FieldTag: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
return req.Tags(), nil |
|
}, nil |
|
case FieldId: |
|
return func(req *ProxyRequest) ([]string, error) { |
|
strs := make([]string, 1) |
|
strs[0] = req.DbId |
|
return strs, nil |
|
}, nil |
|
default: |
|
return nil, errors.New("field is not a string") |
|
} |
|
} |
|
|
|
func genStrChecker(cmp StrComparer, argval interface{}) (func(str string) bool, error) { |
|
switch cmp { |
|
case StrContains: |
|
val, ok := argval.(string) |
|
if !ok { |
|
return nil, errors.New("argument must be a string") |
|
} |
|
return func(str string) bool { |
|
if strings.Contains(str, val) { |
|
return true |
|
} |
|
return false |
|
}, nil |
|
case StrIs: |
|
val, ok := argval.(string) |
|
if !ok { |
|
return nil, errors.New("argument must be a string") |
|
} |
|
return func(str string) bool { |
|
if str == val { |
|
return true |
|
} |
|
return false |
|
}, nil |
|
case StrContainsRegexp: |
|
val, ok := argval.(string) |
|
if !ok { |
|
return nil, errors.New("argument must be a string") |
|
} |
|
regex, err := regexp.Compile(string(val)) |
|
if err != nil { |
|
return nil, fmt.Errorf("could not compile regular expression: %s", err.Error()) |
|
} |
|
return func(str string) bool { |
|
return regex.MatchString(string(str)) |
|
}, nil |
|
case StrLengthGreaterThan: |
|
val, ok := argval.(int) |
|
if !ok { |
|
return nil, errors.New("argument must be an integer") |
|
} |
|
return func(str string) bool { |
|
if len(str) > val { |
|
return true |
|
} |
|
return false |
|
}, nil |
|
case StrLengthLessThan: |
|
val, ok := argval.(int) |
|
if !ok { |
|
return nil, errors.New("argument must be an integer") |
|
} |
|
return func(str string) bool { |
|
if len(str) < val { |
|
return true |
|
} |
|
return false |
|
}, nil |
|
case StrLengthEqualTo: |
|
val, ok := argval.(int) |
|
if !ok { |
|
return nil, errors.New("argument must be an integer") |
|
} |
|
return func(str string) bool { |
|
if len(str) == val { |
|
return true |
|
} |
|
return false |
|
}, nil |
|
default: |
|
return nil, errors.New("invalid comparer") |
|
} |
|
} |
|
|
|
func genStrFieldChecker(strGetter strFieldGetter, cmp StrComparer, val interface{}) (RequestChecker, error) { |
|
getter := strGetter |
|
comparer, err := genStrChecker(cmp, val) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return func(req *ProxyRequest) bool { |
|
strs, err := getter(req) |
|
if err != nil { |
|
panic(err) |
|
} |
|
for _, str := range strs { |
|
if comparer(str) { |
|
return true |
|
} |
|
} |
|
return false |
|
}, nil |
|
} |
|
|
|
func pairValuesFromHeader(header http.Header) []*PairValue { |
|
// Returns a list of pair values from a http.Header |
|
pairs := make([]*PairValue, 0) |
|
for k, vs := range header { |
|
for _, v := range vs { |
|
pair := &PairValue{string(k), string(v)} |
|
pairs = append(pairs, pair) |
|
} |
|
} |
|
return pairs |
|
} |
|
|
|
func pairValuesFromURLQuery(values url.Values) []*PairValue { |
|
// Returns a list of pair values from a http.Header |
|
pairs := make([]*PairValue, 0) |
|
for k, vs := range values { |
|
for _, v := range vs { |
|
pair := &PairValue{string(k), string(v)} |
|
pairs = append(pairs, pair) |
|
} |
|
} |
|
return pairs |
|
} |
|
|
|
func pairValuesFromCookies(cookies []*http.Cookie) []*PairValue { |
|
pairs := make([]*PairValue, 0) |
|
for _, c := range cookies { |
|
pair := &PairValue{string(c.Name), string(c.Value)} |
|
pairs = append(pairs, pair) |
|
} |
|
return pairs |
|
} |
|
|
|
func pairsToStrings(pairs []*PairValue) []string { |
|
// Converts a list of pairs into a list of strings containing all keys and values |
|
// k1: v1, k2: v2 -> ["k1", "v1", "k2", "v2"] |
|
strs := make([]string, 0) |
|
for _, p := range pairs { |
|
strs = append(strs, p.key) |
|
strs = append(strs, p.value) |
|
} |
|
return strs |
|
} |
|
|
|
func createKvPairGetter(field SearchField) (kvFieldGetter, error) { |
|
switch field { |
|
case FieldRequestHeaders: |
|
return func(req *ProxyRequest) ([]*PairValue, error) { |
|
return pairValuesFromHeader(req.Header), nil |
|
}, nil |
|
case FieldResponseHeaders: |
|
return func(req *ProxyRequest) ([]*PairValue, error) { |
|
var pairs []*PairValue |
|
if req.ServerResponse != nil { |
|
pairs = pairValuesFromHeader(req.ServerResponse.Header) |
|
} else { |
|
pairs = make([]*PairValue, 0) |
|
} |
|
return pairs, nil |
|
}, nil |
|
case FieldBothHeaders: |
|
return func(req *ProxyRequest) ([]*PairValue, error) { |
|
pairs := pairValuesFromHeader(req.Header) |
|
if req.ServerResponse != nil { |
|
pairs = append(pairs, pairValuesFromHeader(req.ServerResponse.Header)...) |
|
} |
|
return pairs, nil |
|
}, nil |
|
case FieldBothParam: |
|
return func(req *ProxyRequest) ([]*PairValue, error) { |
|
pairs := pairValuesFromURLQuery(req.URL.Query()) |
|
params, err := req.PostParameters() |
|
if err == nil { |
|
pairs = append(pairs, pairValuesFromURLQuery(params)...) |
|
} |
|
return pairs, nil |
|
}, nil |
|
case FieldURLParam: |
|
return func(req *ProxyRequest) ([]*PairValue, error) { |
|
return pairValuesFromURLQuery(req.URL.Query()), nil |
|
}, nil |
|
case FieldPostParam: |
|
return func(req *ProxyRequest) ([]*PairValue, error) { |
|
params, err := req.PostParameters() |
|
if err != nil { |
|
return nil, err |
|
} |
|
return pairValuesFromURLQuery(params), nil |
|
}, nil |
|
case FieldResponseCookie: |
|
return func(req *ProxyRequest) ([]*PairValue, error) { |
|
pairs := make([]*PairValue, 0) |
|
if req.ServerResponse != nil { |
|
cookies := req.ServerResponse.Cookies() |
|
pairs = append(pairs, pairValuesFromCookies(cookies)...) |
|
} |
|
return pairs, nil |
|
}, nil |
|
case FieldRequestCookie: |
|
return func(req *ProxyRequest) ([]*PairValue, error) { |
|
return pairValuesFromCookies(req.Cookies()), nil |
|
}, nil |
|
case FieldBothCookie: |
|
return func(req *ProxyRequest) ([]*PairValue, error) { |
|
pairs := pairValuesFromCookies(req.Cookies()) |
|
if req.ServerResponse != nil { |
|
cookies := req.ServerResponse.Cookies() |
|
pairs = append(pairs, pairValuesFromCookies(cookies)...) |
|
} |
|
return pairs, nil |
|
}, nil |
|
default: |
|
return nil, errors.New("not implemented") |
|
} |
|
} |
|
|
|
func genKvFieldChecker(kvGetter kvFieldGetter, cmp1 StrComparer, val1 string, |
|
cmp2 StrComparer, val2 string) (RequestChecker, error) { |
|
getter := kvGetter |
|
cmpfunc1, err := genStrChecker(cmp1, val1) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
cmpfunc2, err := genStrChecker(cmp2, val2) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return func(req *ProxyRequest) bool { |
|
pairs, err := getter(req) |
|
if err != nil { |
|
return false |
|
} |
|
|
|
for _, p := range pairs { |
|
if cmpfunc1(p.key) && cmpfunc2(p.value) { |
|
return true |
|
} |
|
} |
|
return false |
|
}, nil |
|
} |
|
|
|
func checkerFromPhrase(phrase QueryPhrase) (RequestChecker, error) { |
|
checkers := make([]RequestChecker, len(phrase)) |
|
for i, args := range phrase { |
|
newChecker, err := NewRequestChecker(args...) |
|
if err != nil { |
|
return nil, fmt.Errorf("error with search %d: %s", i, err.Error()) |
|
} |
|
checkers[i] = newChecker |
|
} |
|
|
|
ret := func(req *ProxyRequest) bool { |
|
for _, checker := range checkers { |
|
if checker(req) { |
|
return true |
|
} |
|
} |
|
return false |
|
} |
|
|
|
return ret, nil |
|
} |
|
|
|
// Creates a RequestChecker from a MessageQuery |
|
func CheckerFromMessageQuery(query MessageQuery) (RequestChecker, error) { |
|
checkers := make([]RequestChecker, len(query)) |
|
for i, phrase := range query { |
|
newChecker, err := checkerFromPhrase(phrase) |
|
if err != nil { |
|
return nil, fmt.Errorf("error with phrase %d: %s", i, err.Error()) |
|
} |
|
checkers[i] = newChecker |
|
} |
|
|
|
ret := func(req *ProxyRequest) bool { |
|
for _, checker := range checkers { |
|
if !checker(req) { |
|
return false |
|
} |
|
} |
|
return true |
|
} |
|
|
|
return ret, nil |
|
} |
|
|
|
/* |
|
StringSearch conversions |
|
*/ |
|
|
|
func fieldGoToString(field SearchField) (string, error) { |
|
switch field { |
|
case FieldAll: |
|
return "all", nil |
|
case FieldRequestBody: |
|
return "reqbody", nil |
|
case FieldResponseBody: |
|
return "rspbody", nil |
|
case FieldAllBody: |
|
return "body", nil |
|
case FieldWSMessage: |
|
return "wsmessage", nil |
|
case FieldRequestHeaders: |
|
return "reqheader", nil |
|
case FieldResponseHeaders: |
|
return "rspheader", nil |
|
case FieldBothHeaders: |
|
return "header", nil |
|
case FieldMethod: |
|
return "method", nil |
|
case FieldHost: |
|
return "host", nil |
|
case FieldPath: |
|
return "path", nil |
|
case FieldURL: |
|
return "url", nil |
|
case FieldStatusCode: |
|
return "statuscode", nil |
|
case FieldBothParam: |
|
return "param", nil |
|
case FieldURLParam: |
|
return "urlparam", nil |
|
case FieldPostParam: |
|
return "postparam", nil |
|
case FieldResponseCookie: |
|
return "rspcookie", nil |
|
case FieldRequestCookie: |
|
return "reqcookie", nil |
|
case FieldBothCookie: |
|
return "cookie", nil |
|
case FieldTag: |
|
return "tag", nil |
|
case FieldAfter: |
|
return "after", nil |
|
case FieldBefore: |
|
return "before", nil |
|
case FieldTimeRange: |
|
return "timerange", nil |
|
case FieldInvert: |
|
return "invert", nil |
|
case FieldId: |
|
return "dbid", nil |
|
default: |
|
return "", errors.New("invalid field") |
|
} |
|
} |
|
|
|
func fieldStrToGo(field string) (SearchField, error) { |
|
switch strings.ToLower(field) { |
|
case "all": |
|
return FieldAll, nil |
|
case "reqbody", "reqbd", "qbd", "qdata", "qdt": |
|
return FieldRequestBody, nil |
|
case "rspbody", "rspbd", "sbd", "sdata", "sdt": |
|
return FieldResponseBody, nil |
|
case "body", "bd", "data", "dt": |
|
return FieldAllBody, nil |
|
case "wsmessage", "wsm": |
|
return FieldWSMessage, nil |
|
case "reqheader", "reqhd", "qhd": |
|
return FieldRequestHeaders, nil |
|
case "rspheader", "rsphd", "shd": |
|
return FieldResponseHeaders, nil |
|
case "header", "hd": |
|
return FieldBothHeaders, nil |
|
case "method", "verb", "vb": |
|
return FieldMethod, nil |
|
case "host", "domain", "hs", "dm": |
|
return FieldHost, nil |
|
case "path", "pt": |
|
return FieldPath, nil |
|
case "url": |
|
return FieldURL, nil |
|
case "statuscode", "sc": |
|
return FieldStatusCode, nil |
|
case "param", "pm": |
|
return FieldBothParam, nil |
|
case "urlparam", "uparam": |
|
return FieldURLParam, nil |
|
case "postparam", "pparam": |
|
return FieldPostParam, nil |
|
case "rspcookie", "rspck", "sck": |
|
return FieldResponseCookie, nil |
|
case "reqcookie", "reqck", "qck": |
|
return FieldRequestCookie, nil |
|
case "cookie", "ck": |
|
return FieldBothCookie, nil |
|
case "tag": |
|
return FieldTag, nil |
|
case "after", "af": |
|
return FieldAfter, nil |
|
case "before", "b4": |
|
return FieldBefore, nil |
|
case "timerange": |
|
return FieldTimeRange, nil |
|
case "invert", "inv": |
|
return FieldInvert, nil |
|
case "dbid": |
|
return FieldId, nil |
|
default: |
|
return 0, fmt.Errorf("invalid field: %s", field) |
|
} |
|
} |
|
|
|
// Converts a StrComparer and a value into a comparer and value that can be used in string queries |
|
func cmpValGoToStr(comparer StrComparer, val interface{}) (string, string, error) { |
|
var cmpStr string |
|
switch comparer { |
|
case StrIs: |
|
cmpStr = "is" |
|
val, ok := val.(string) |
|
if !ok { |
|
return "", "", errors.New("val must be a string") |
|
} |
|
return cmpStr, val, nil |
|
case StrContains: |
|
cmpStr = "contains" |
|
val, ok := val.(string) |
|
if !ok { |
|
return "", "", errors.New("val must be a string") |
|
} |
|
return cmpStr, val, nil |
|
case StrContainsRegexp: |
|
cmpStr = "containsregexp" |
|
val, ok := val.(string) |
|
if !ok { |
|
return "", "", errors.New("val must be a string") |
|
} |
|
return cmpStr, val, nil |
|
case StrLengthGreaterThan: |
|
cmpStr = "lengt" |
|
val, ok := val.(int) |
|
if !ok { |
|
return "", "", errors.New("val must be an int") |
|
} |
|
return cmpStr, strconv.Itoa(val), nil |
|
case StrLengthLessThan: |
|
cmpStr = "lenlt" |
|
val, ok := val.(int) |
|
if !ok { |
|
return "", "", errors.New("val must be an int") |
|
} |
|
return cmpStr, strconv.Itoa(val), nil |
|
case StrLengthEqualTo: |
|
cmpStr = "leneq" |
|
val, ok := val.(int) |
|
if !ok { |
|
return "", "", errors.New("val must be an int") |
|
} |
|
return cmpStr, strconv.Itoa(val), nil |
|
default: |
|
return "", "", errors.New("invalid comparer") |
|
} |
|
} |
|
|
|
func cmpValStrToGo(strArgs []string) (StrComparer, interface{}, error) { |
|
if len(strArgs) != 2 { |
|
return 0, "", fmt.Errorf("parsing a comparer/val requires one comparer and one value. Got %d arguments.", len(strArgs)) |
|
} |
|
|
|
switch strArgs[0] { |
|
case "is": |
|
return StrIs, strArgs[1], nil |
|
case "contains", "ct": |
|
return StrContains, strArgs[1], nil |
|
case "containsregexp", "ctr": |
|
return StrContainsRegexp, strArgs[1], nil |
|
case "lengt": |
|
i, err := strconv.Atoi(strArgs[1]) |
|
if err != nil { |
|
return 0, nil, err |
|
} |
|
return StrLengthGreaterThan, i, nil |
|
case "lenlt": |
|
i, err := strconv.Atoi(strArgs[1]) |
|
if err != nil { |
|
return 0, nil, err |
|
} |
|
return StrLengthLessThan, i, nil |
|
case "leneq": |
|
i, err := strconv.Atoi(strArgs[1]) |
|
if err != nil { |
|
return 0, nil, err |
|
} |
|
return StrLengthEqualTo, i, nil |
|
default: |
|
return 0, "", fmt.Errorf("invalid comparer: %s", strArgs[0]) |
|
} |
|
} |
|
|
|
func CheckArgsStrToGo(strArgs []string) ([]interface{}, error) { |
|
args := make([]interface{}, 0) |
|
if len(strArgs) == 0 { |
|
return nil, errors.New("missing field") |
|
} |
|
|
|
// Parse the field |
|
field, err := fieldStrToGo(strArgs[0]) |
|
if err != nil { |
|
return nil, err |
|
} |
|
args = append(args, field) |
|
|
|
remaining := strArgs[1:] |
|
// Parse the query arguments |
|
switch args[0] { |
|
// Normal string fields |
|
case FieldAll, FieldRequestBody, FieldResponseBody, FieldAllBody, FieldWSMessage, FieldMethod, FieldHost, FieldPath, FieldStatusCode, FieldTag, FieldId: |
|
if len(remaining) != 2 { |
|
return nil, errors.New("string field searches require one comparer and one value") |
|
} |
|
|
|
cmp, val, err := cmpValStrToGo(remaining) |
|
if err != nil { |
|
return nil, err |
|
} |
|
args = append(args, cmp) |
|
args = append(args, val) |
|
// Normal key/value fields |
|
case FieldRequestHeaders, FieldResponseHeaders, FieldBothHeaders, FieldBothParam, FieldURLParam, FieldPostParam, FieldResponseCookie, FieldRequestCookie, FieldBothCookie: |
|
if len(remaining) == 2 { |
|
cmp, val, err := cmpValStrToGo(remaining) |
|
if err != nil { |
|
return nil, err |
|
} |
|
args = append(args, cmp) |
|
args = append(args, val) |
|
} else if len(remaining) == 4 { |
|
cmp, val, err := cmpValStrToGo(remaining[0:2]) |
|
if err != nil { |
|
return nil, err |
|
} |
|
args = append(args, cmp) |
|
args = append(args, val) |
|
|
|
cmp, val, err = cmpValStrToGo(remaining[2:4]) |
|
if err != nil { |
|
return nil, err |
|
} |
|
args = append(args, cmp) |
|
args = append(args, val) |
|
} else { |
|
return nil, errors.New("key/value field searches require either one comparer and one value or two comparer/value pairs") |
|
} |
|
|
|
// Other fields |
|
case FieldAfter, FieldBefore: |
|
if len(remaining) != 1 { |
|
return nil, errors.New("before/after take exactly one argument") |
|
} |
|
nanoseconds, err := strconv.ParseInt(remaining[0], 10, 64) |
|
if err != nil { |
|
return nil, errors.New("error parsing time") |
|
} |
|
timeVal := time.Unix(0, nanoseconds) |
|
args = append(args, timeVal) |
|
case FieldTimeRange: |
|
if len(remaining) != 2 { |
|
return nil, errors.New("time range takes exactly two arguments") |
|
} |
|
startNanoseconds, err := strconv.ParseInt(remaining[0], 10, 64) |
|
if err != nil { |
|
return nil, errors.New("error parsing start time") |
|
} |
|
startTimeVal := time.Unix(0, startNanoseconds) |
|
args = append(args, startTimeVal) |
|
|
|
endNanoseconds, err := strconv.ParseInt(remaining[1], 10, 64) |
|
if err != nil { |
|
return nil, errors.New("error parsing end time") |
|
} |
|
endTimeVal := time.Unix(0, endNanoseconds) |
|
args = append(args, endTimeVal) |
|
case FieldInvert: |
|
remainingArgs, err := CheckArgsStrToGo(remaining) |
|
if err != nil { |
|
return nil, fmt.Errorf("error with query to invert: %s", err.Error()) |
|
} |
|
args = append(args, remainingArgs...) |
|
default: |
|
return nil, fmt.Errorf("field not yet implemented: %s", strArgs[0]) |
|
} |
|
|
|
return args, nil |
|
} |
|
|
|
func CheckArgsGoToStr(args []interface{}) ([]string, error) { |
|
if len(args) == 0 { |
|
return nil, errors.New("no arguments") |
|
} |
|
|
|
retargs := make([]string, 0) |
|
|
|
field, ok := args[0].(SearchField) |
|
if !ok { |
|
return nil, errors.New("first argument is not a field") |
|
} |
|
|
|
strField, err := fieldGoToString(field) |
|
if err != nil { |
|
return nil, err |
|
} |
|
retargs = append(retargs, strField) |
|
|
|
switch field { |
|
case FieldAll, FieldRequestBody, FieldResponseBody, FieldAllBody, FieldWSMessage, FieldMethod, FieldHost, FieldPath, FieldStatusCode, FieldTag, FieldId: |
|
if len(args) != 3 { |
|
return nil, errors.New("string fields require exactly two arguments") |
|
} |
|
|
|
comparer, ok := args[1].(StrComparer) |
|
if !ok { |
|
return nil, errors.New("comparer must be a StrComparer") |
|
} |
|
|
|
cmpStr, valStr, err := cmpValGoToStr(comparer, args[2]) |
|
if err != nil { |
|
return nil, err |
|
} |
|
retargs = append(retargs, cmpStr) |
|
retargs = append(retargs, valStr) |
|
return retargs, nil |
|
|
|
case FieldRequestHeaders, FieldResponseHeaders, FieldBothHeaders, FieldBothParam, FieldURLParam, FieldPostParam, FieldResponseCookie, FieldRequestCookie, FieldBothCookie: |
|
if len(args) == 3 { |
|
comparer, ok := args[1].(StrComparer) |
|
if !ok { |
|
return nil, errors.New("comparer must be a StrComparer") |
|
} |
|
|
|
cmpStr, valStr, err := cmpValGoToStr(comparer, args[2]) |
|
if err != nil { |
|
return nil, err |
|
} |
|
retargs = append(retargs, cmpStr) |
|
retargs = append(retargs, valStr) |
|
|
|
return retargs, nil |
|
} else if len(args) == 5 { |
|
comparer1, ok := args[1].(StrComparer) |
|
if !ok { |
|
return nil, errors.New("comparer1 must be a StrComparer") |
|
} |
|
|
|
cmpStr1, valStr1, err := cmpValGoToStr(comparer1, args[2]) |
|
if err != nil { |
|
return nil, err |
|
} |
|
retargs = append(retargs, cmpStr1) |
|
retargs = append(retargs, valStr1) |
|
|
|
comparer2, ok := args[1].(StrComparer) |
|
if !ok { |
|
return nil, errors.New("comparer2 must be a StrComparer") |
|
} |
|
|
|
cmpStr2, valStr2, err := cmpValGoToStr(comparer2, args[2]) |
|
if err != nil { |
|
return nil, err |
|
} |
|
retargs = append(retargs, cmpStr2) |
|
retargs = append(retargs, valStr2) |
|
|
|
return retargs, nil |
|
} else { |
|
return nil, errors.New("key/value queries take exactly two or four arguments") |
|
} |
|
|
|
case FieldAfter, FieldBefore: |
|
if len(args) != 2 { |
|
return nil, errors.New("before/after fields require exactly one argument") |
|
} |
|
|
|
time, ok := args[1].(time.Time) |
|
if !ok { |
|
return nil, errors.New("argument must have a type of time.Time") |
|
} |
|
nanoseconds := time.UnixNano() |
|
retargs = append(retargs, strconv.FormatInt(nanoseconds, 10)) |
|
return retargs, nil |
|
|
|
case FieldTimeRange: |
|
if len(args) != 3 { |
|
return nil, errors.New("time range fields require exactly two arguments") |
|
} |
|
|
|
time1, ok := args[1].(time.Time) |
|
if !ok { |
|
return nil, errors.New("arguments must have a type of time.Time") |
|
} |
|
nanoseconds1 := time1.UnixNano() |
|
retargs = append(retargs, strconv.FormatInt(nanoseconds1, 10)) |
|
|
|
time2, ok := args[2].(time.Time) |
|
if !ok { |
|
return nil, errors.New("arguments must have a type of time.Time") |
|
} |
|
nanoseconds2 := time2.UnixNano() |
|
retargs = append(retargs, strconv.FormatInt(nanoseconds2, 10)) |
|
return retargs, nil |
|
|
|
case FieldInvert: |
|
strs, err := CheckArgsGoToStr(args[1:]) |
|
if err != nil { |
|
return nil, err |
|
} |
|
retargs = append(retargs, strs...) |
|
return retargs, nil |
|
|
|
default: |
|
return nil, fmt.Errorf("invalid field") |
|
} |
|
} |
|
|
|
func strPhraseToGoPhrase(phrase StrQueryPhrase) (QueryPhrase, error) { |
|
goPhrase := make(QueryPhrase, len(phrase)) |
|
for i, strArgs := range phrase { |
|
var err error |
|
goPhrase[i], err = CheckArgsStrToGo(strArgs) |
|
if err != nil { |
|
return nil, fmt.Errorf("Error with argument set %d: %s", i, err.Error()) |
|
} |
|
} |
|
return goPhrase, nil |
|
} |
|
|
|
func goPhraseToStrPhrase(phrase QueryPhrase) (StrQueryPhrase, error) { |
|
strPhrase := make(StrQueryPhrase, len(phrase)) |
|
for i, goArgs := range phrase { |
|
var err error |
|
strPhrase[i], err = CheckArgsGoToStr(goArgs) |
|
if err != nil { |
|
return nil, fmt.Errorf("Error with argument set %d: %s", i, err.Error()) |
|
} |
|
} |
|
return strPhrase, nil |
|
} |
|
|
|
// Converts a StrMessageQuery into a MessageQuery |
|
func StrQueryToMsgQuery(query StrMessageQuery) (MessageQuery, error) { |
|
goQuery := make(MessageQuery, len(query)) |
|
for i, phrase := range query { |
|
var err error |
|
goQuery[i], err = strPhraseToGoPhrase(phrase) |
|
if err != nil { |
|
return nil, fmt.Errorf("Error with phrase %d: %s", i, err.Error()) |
|
} |
|
} |
|
return goQuery, nil |
|
} |
|
|
|
// Converts a MessageQuery into a StrMessageQuery |
|
func MsgQueryToStrQuery(query MessageQuery) (StrMessageQuery, error) { |
|
strQuery := make(StrMessageQuery, len(query)) |
|
for i, phrase := range query { |
|
var err error |
|
strQuery[i], err = goPhraseToStrPhrase(phrase) |
|
if err != nil { |
|
return nil, fmt.Errorf("Error with phrase %d: %s", i, err.Error()) |
|
} |
|
} |
|
return strQuery, nil |
|
}
|
|
|