|
|
|
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
|
|
|
|
}
|