Add Request object to hook package to simplify API

To avoid having to pass around so many parameters to the hook package,
create a Request object to store all request-specific data.  Update APIs
accordingly.
This commit is contained in:
Cameron Moore 2020-09-25 19:46:06 -05:00
parent b8498c564d
commit c6603894c1
4 changed files with 269 additions and 183 deletions

View file

@ -17,6 +17,7 @@ import (
"log"
"math"
"net"
"net/http"
"net/textproto"
"os"
"reflect"
@ -47,6 +48,30 @@ const (
EnvNamespace string = "HOOK_"
)
// Request represents a webhook request.
type Request struct {
// The request ID set by the RequestID middleware.
ID string
// The Content-Type of the request.
ContentType string
// The raw request body.
Body []byte
// Headers is a map of the parsed headers.
Headers map[string]interface{}
// Query is a map of the parsed URL query values.
Query map[string]interface{}
// Payload is a map of the parsed payload.
Payload map[string]interface{}
// The underlying HTTP request.
RawRequest *http.Request
}
// ParameterNodeError describes an error walking a parameter node.
type ParameterNodeError struct {
key string
@ -222,22 +247,26 @@ func CheckPayloadSignature512(payload []byte, secret, signature string) (string,
return ValidateMAC(payload, hmac.New(sha512.New, []byte(secret)), signatures)
}
func CheckScalrSignature(headers map[string]interface{}, body []byte, signingKey string, checkDate bool) (bool, error) {
// Check for the signature and date headers
if _, ok := headers["X-Signature"]; !ok {
func CheckScalrSignature(r *Request, signingKey string, checkDate bool) (bool, error) {
if r.Headers == nil {
return false, nil
}
if _, ok := headers["Date"]; !ok {
// Check for the signature and date headers
if _, ok := r.Headers["X-Signature"]; !ok {
return false, nil
}
if _, ok := r.Headers["Date"]; !ok {
return false, nil
}
if signingKey == "" {
return false, errors.New("signature validation signing key can not be empty")
}
providedSignature := headers["X-Signature"].(string)
dateHeader := headers["Date"].(string)
providedSignature := r.Headers["X-Signature"].(string)
dateHeader := r.Headers["Date"].(string)
mac := hmac.New(sha1.New, []byte(signingKey))
mac.Write(body)
mac.Write(r.Body)
mac.Write([]byte(dateHeader))
expectedSignature := hex.EncodeToString(mac.Sum(nil))
@ -426,41 +455,41 @@ type Argument struct {
// Get Argument method returns the value for the Argument's key name
// based on the Argument's source
func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string, error) {
func (ha *Argument) Get(r *Request) (string, error) {
var source *map[string]interface{}
key := ha.Name
switch ha.Source {
case SourceHeader:
source = headers
source = &r.Headers
key = textproto.CanonicalMIMEHeaderKey(ha.Name)
case SourceQuery, SourceQueryAlias:
source = query
source = &r.Query
case SourcePayload:
source = payload
source = &r.Payload
case SourceString:
return ha.Name, nil
case SourceEntirePayload:
r, err := json.Marshal(payload)
res, err := json.Marshal(&r.Payload)
if err != nil {
return "", err
}
return string(r), nil
return string(res), nil
case SourceEntireHeaders:
r, err := json.Marshal(headers)
res, err := json.Marshal(&r.Headers)
if err != nil {
return "", err
}
return string(r), nil
return string(res), nil
case SourceEntireQuery:
r, err := json.Marshal(query)
res, err := json.Marshal(&r.Query)
if err != nil {
return "", err
}
return string(r), nil
return string(res), nil
}
if source != nil {
@ -545,11 +574,11 @@ type Hook struct {
// ParseJSONParameters decodes specified arguments to JSON objects and replaces the
// string with the newly created object
func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface{}) []error {
func (h *Hook) ParseJSONParameters(r *Request) []error {
errors := make([]error, 0)
for i := range h.JSONStringParameters {
arg, err := h.JSONStringParameters[i].Get(headers, query, payload)
arg, err := h.JSONStringParameters[i].Get(r)
if err != nil {
errors = append(errors, &ArgumentError{h.JSONStringParameters[i]})
} else {
@ -568,11 +597,11 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
switch h.JSONStringParameters[i].Source {
case SourceHeader:
source = headers
source = &r.Headers
case SourcePayload:
source = payload
source = &r.Payload
case SourceQuery, SourceQueryAlias:
source = query
source = &r.Query
}
if source != nil {
@ -598,14 +627,14 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface
// ExtractCommandArguments creates a list of arguments, based on the
// PassArgumentsToCommand property that is ready to be used with exec.Command()
func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]interface{}) ([]string, []error) {
func (h *Hook) ExtractCommandArguments(r *Request) ([]string, []error) {
args := make([]string, 0)
errors := make([]error, 0)
args = append(args, h.ExecuteCommand)
for i := range h.PassArgumentsToCommand {
arg, err := h.PassArgumentsToCommand[i].Get(headers, query, payload)
arg, err := h.PassArgumentsToCommand[i].Get(r)
if err != nil {
args = append(args, "")
errors = append(errors, &ArgumentError{h.PassArgumentsToCommand[i]})
@ -625,11 +654,11 @@ func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]inter
// ExtractCommandArgumentsForEnv creates a list of arguments in key=value
// format, based on the PassEnvironmentToCommand property that is ready to be used
// with exec.Command().
func (h *Hook) ExtractCommandArgumentsForEnv(headers, query, payload *map[string]interface{}) ([]string, []error) {
func (h *Hook) ExtractCommandArgumentsForEnv(r *Request) ([]string, []error) {
args := make([]string, 0)
errors := make([]error, 0)
for i := range h.PassEnvironmentToCommand {
arg, err := h.PassEnvironmentToCommand[i].Get(headers, query, payload)
arg, err := h.PassEnvironmentToCommand[i].Get(r)
if err != nil {
errors = append(errors, &ArgumentError{h.PassEnvironmentToCommand[i]})
continue
@ -661,11 +690,11 @@ type FileParameter struct {
// ExtractCommandArgumentsForFile creates a list of arguments in key=value
// format, based on the PassFileToCommand property that is ready to be used
// with exec.Command().
func (h *Hook) ExtractCommandArgumentsForFile(headers, query, payload *map[string]interface{}) ([]FileParameter, []error) {
func (h *Hook) ExtractCommandArgumentsForFile(r *Request) ([]FileParameter, []error) {
args := make([]FileParameter, 0)
errors := make([]error, 0)
for i := range h.PassFileToCommand {
arg, err := h.PassFileToCommand[i].Get(headers, query, payload)
arg, err := h.PassFileToCommand[i].Get(r)
if err != nil {
errors = append(errors, &ArgumentError{h.PassFileToCommand[i]})
continue
@ -772,16 +801,16 @@ type Rules struct {
// Evaluate finds the first rule property that is not nil and returns the value
// it evaluates to
func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
func (r Rules) Evaluate(req *Request) (bool, error) {
switch {
case r.And != nil:
return r.And.Evaluate(headers, query, payload, body, remoteAddr)
return r.And.Evaluate(req)
case r.Or != nil:
return r.Or.Evaluate(headers, query, payload, body, remoteAddr)
return r.Or.Evaluate(req)
case r.Not != nil:
return r.Not.Evaluate(headers, query, payload, body, remoteAddr)
return r.Not.Evaluate(req)
case r.Match != nil:
return r.Match.Evaluate(headers, query, payload, body, remoteAddr)
return r.Match.Evaluate(req)
}
return false, nil
@ -791,11 +820,11 @@ func (r Rules) Evaluate(headers, query, payload *map[string]interface{}, body *[
type AndRule []Rules
// Evaluate AndRule will return true if and only if all of ChildRules evaluate to true
func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
func (r AndRule) Evaluate(req *Request) (bool, error) {
res := true
for _, v := range r {
rv, err := v.Evaluate(headers, query, payload, body, remoteAddr)
rv, err := v.Evaluate(req)
if err != nil {
return false, err
}
@ -813,11 +842,11 @@ func (r AndRule) Evaluate(headers, query, payload *map[string]interface{}, body
type OrRule []Rules
// Evaluate OrRule will return true if any of ChildRules evaluate to true
func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
func (r OrRule) Evaluate(req *Request) (bool, error) {
res := false
for _, v := range r {
rv, err := v.Evaluate(headers, query, payload, body, remoteAddr)
rv, err := v.Evaluate(req)
if err != nil {
return false, err
}
@ -835,8 +864,8 @@ func (r OrRule) Evaluate(headers, query, payload *map[string]interface{}, body *
type NotRule Rules
// Evaluate NotRule will return true if and only if ChildRule evaluates to false
func (r NotRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
rv, err := Rules(r).Evaluate(headers, query, payload, body, remoteAddr)
func (r NotRule) Evaluate(req *Request) (bool, error) {
rv, err := Rules(r).Evaluate(req)
return !rv, err
}
@ -862,15 +891,15 @@ const (
)
// Evaluate MatchRule will return based on the type
func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, body *[]byte, remoteAddr string) (bool, error) {
func (r MatchRule) Evaluate(req *Request) (bool, error) {
if r.Type == IPWhitelist {
return CheckIPWhitelist(remoteAddr, r.IPRange)
return CheckIPWhitelist(req.RawRequest.RemoteAddr, r.IPRange)
}
if r.Type == ScalrSignature {
return CheckScalrSignature(*headers, *body, r.Secret, true)
return CheckScalrSignature(req, r.Secret, true)
}
arg, err := r.Parameter.Get(headers, query, payload)
arg, err := r.Parameter.Get(req)
if err == nil {
switch r.Type {
case MatchValue:
@ -878,13 +907,13 @@ func (r MatchRule) Evaluate(headers, query, payload *map[string]interface{}, bod
case MatchRegex:
return regexp.MatchString(r.Regex, arg)
case MatchHashSHA1:
_, err := CheckPayloadSignature(*body, r.Secret, arg)
_, err := CheckPayloadSignature(req.Body, r.Secret, arg)
return err == nil, err
case MatchHashSHA256:
_, err := CheckPayloadSignature256(*body, r.Secret, arg)
_, err := CheckPayloadSignature256(req.Body, r.Secret, arg)
return err == nil, err
case MatchHashSHA512:
_, err := CheckPayloadSignature512(*body, r.Secret, arg)
_, err := CheckPayloadSignature512(req.Body, r.Secret, arg)
return err == nil, err
}
}

View file

@ -1,6 +1,7 @@
package hook
import (
"net/http"
"os"
"reflect"
"strings"
@ -136,7 +137,7 @@ func TestCheckPayloadSignature512(t *testing.T) {
var checkScalrSignatureTests = []struct {
description string
headers map[string]interface{}
payload []byte
body []byte
secret string
expectedSignature string
ok bool
@ -175,7 +176,11 @@ var checkScalrSignatureTests = []struct {
func TestCheckScalrSignature(t *testing.T) {
for _, testCase := range checkScalrSignatureTests {
valid, err := CheckScalrSignature(testCase.headers, testCase.payload, testCase.secret, false)
r := &Request{
Headers: testCase.headers,
Body: testCase.body,
}
valid, err := CheckScalrSignature(r, testCase.secret, false)
if valid != testCase.ok {
t.Errorf("failed to check scalr signature fot test case: %s\nexpected ok:%#v, got ok:%#v}",
testCase.description, testCase.ok, valid)
@ -249,25 +254,30 @@ func TestExtractParameter(t *testing.T) {
var argumentGetTests = []struct {
source, name string
headers, query, payload *map[string]interface{}
headers, query, payload map[string]interface{}
value string
ok bool
}{
{"header", "a", &map[string]interface{}{"A": "z"}, nil, nil, "z", true},
{"url", "a", nil, &map[string]interface{}{"a": "z"}, nil, "z", true},
{"payload", "a", nil, nil, &map[string]interface{}{"a": "z"}, "z", true},
{"string", "a", nil, nil, &map[string]interface{}{"a": "z"}, "a", true},
{"header", "a", map[string]interface{}{"A": "z"}, nil, nil, "z", true},
{"url", "a", nil, map[string]interface{}{"a": "z"}, nil, "z", true},
{"payload", "a", nil, nil, map[string]interface{}{"a": "z"}, "z", true},
{"string", "a", nil, nil, map[string]interface{}{"a": "z"}, "a", true},
// failures
{"header", "a", nil, &map[string]interface{}{"a": "z"}, &map[string]interface{}{"a": "z"}, "", false}, // nil headers
{"url", "a", &map[string]interface{}{"A": "z"}, nil, &map[string]interface{}{"a": "z"}, "", false}, // nil query
{"payload", "a", &map[string]interface{}{"A": "z"}, &map[string]interface{}{"a": "z"}, nil, "", false}, // nil payload
{"foo", "a", &map[string]interface{}{"A": "z"}, nil, nil, "", false}, // invalid source
{"header", "a", nil, map[string]interface{}{"a": "z"}, map[string]interface{}{"a": "z"}, "", false}, // nil headers
{"url", "a", map[string]interface{}{"A": "z"}, nil, map[string]interface{}{"a": "z"}, "", false}, // nil query
{"payload", "a", map[string]interface{}{"A": "z"}, map[string]interface{}{"a": "z"}, nil, "", false}, // nil payload
{"foo", "a", map[string]interface{}{"A": "z"}, nil, nil, "", false}, // invalid source
}
func TestArgumentGet(t *testing.T) {
for _, tt := range argumentGetTests {
a := Argument{tt.source, tt.name, "", false}
value, err := a.Get(tt.headers, tt.query, tt.payload)
r := &Request{
Headers: tt.headers,
Query: tt.query,
Payload: tt.payload,
}
value, err := a.Get(r)
if (err == nil) != tt.ok || value != tt.value {
t.Errorf("failed to get {%q, %q}:\nexpected {value:%#v, ok:%#v},\ngot {value:%#v, err:%v}", tt.source, tt.name, tt.value, tt.ok, value, err)
}
@ -276,26 +286,31 @@ func TestArgumentGet(t *testing.T) {
var hookParseJSONParametersTests = []struct {
params []Argument
headers, query, payload *map[string]interface{}
rheaders, rquery, rpayload *map[string]interface{}
headers, query, payload map[string]interface{}
rheaders, rquery, rpayload map[string]interface{}
ok bool
}{
{[]Argument{Argument{"header", "a", "", false}}, &map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, true},
{[]Argument{Argument{"url", "a", "", false}}, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, &map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, &map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
{[]Argument{Argument{"header", "z", "", false}}, &map[string]interface{}{"Z": `{}`}, nil, nil, &map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true},
{[]Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": `{"b": "y"}`}, nil, nil, map[string]interface{}{"A": map[string]interface{}{"b": "y"}}, nil, nil, true},
{[]Argument{Argument{"url", "a", "", false}}, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, nil, true},
{[]Argument{Argument{"payload", "a", "", false}}, nil, nil, map[string]interface{}{"a": `{"b": "y"}`}, nil, nil, map[string]interface{}{"a": map[string]interface{}{"b": "y"}}, true},
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": `{}`}, nil, nil, map[string]interface{}{"Z": map[string]interface{}{}}, nil, nil, true},
// failures
{[]Argument{Argument{"header", "z", "", false}}, &map[string]interface{}{"Z": ``}, nil, nil, &map[string]interface{}{"Z": ``}, nil, nil, false}, // empty string
{[]Argument{Argument{"header", "y", "", false}}, &map[string]interface{}{"X": `{}`}, nil, nil, &map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter
{[]Argument{Argument{"string", "z", "", false}}, &map[string]interface{}{"Z": ``}, nil, nil, &map[string]interface{}{"Z": ``}, nil, nil, false}, // invalid argument source
{[]Argument{Argument{"header", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false}, // empty string
{[]Argument{Argument{"header", "y", "", false}}, map[string]interface{}{"X": `{}`}, nil, nil, map[string]interface{}{"X": `{}`}, nil, nil, false}, // missing parameter
{[]Argument{Argument{"string", "z", "", false}}, map[string]interface{}{"Z": ``}, nil, nil, map[string]interface{}{"Z": ``}, nil, nil, false}, // invalid argument source
}
func TestHookParseJSONParameters(t *testing.T) {
for _, tt := range hookParseJSONParametersTests {
h := &Hook{JSONStringParameters: tt.params}
err := h.ParseJSONParameters(tt.headers, tt.query, tt.payload)
r := &Request{
Headers: tt.headers,
Query: tt.query,
Payload: tt.payload,
}
err := h.ParseJSONParameters(r)
if (err == nil) != tt.ok || !reflect.DeepEqual(tt.headers, tt.rheaders) {
t.Errorf("failed to parse %v:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.params, *tt.rheaders, tt.ok, *tt.headers, (err == nil))
t.Errorf("failed to parse %v:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.params, tt.rheaders, tt.ok, tt.headers, (err == nil))
}
}
}
@ -303,19 +318,24 @@ func TestHookParseJSONParameters(t *testing.T) {
var hookExtractCommandArgumentsTests = []struct {
exec string
args []Argument
headers, query, payload *map[string]interface{}
headers, query, payload map[string]interface{}
value []string
ok bool
}{
{"test", []Argument{Argument{"header", "a", "", false}}, &map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true},
{"test", []Argument{Argument{"header", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"test", "z"}, true},
// failures
{"fail", []Argument{Argument{"payload", "a", "", false}}, &map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false},
{"fail", []Argument{Argument{"payload", "a", "", false}}, map[string]interface{}{"A": "z"}, nil, nil, []string{"fail", ""}, false},
}
func TestHookExtractCommandArguments(t *testing.T) {
for _, tt := range hookExtractCommandArgumentsTests {
h := &Hook{ExecuteCommand: tt.exec, PassArgumentsToCommand: tt.args}
value, err := h.ExtractCommandArguments(tt.headers, tt.query, tt.payload)
r := &Request{
Headers: tt.headers,
Query: tt.query,
Payload: tt.payload,
}
value, err := h.ExtractCommandArguments(r)
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
t.Errorf("failed to extract args {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil))
}
@ -344,7 +364,7 @@ func TestHookExtractCommandArguments(t *testing.T) {
var hookExtractCommandArgumentsForEnvTests = []struct {
exec string
args []Argument
headers, query, payload *map[string]interface{}
headers, query, payload map[string]interface{}
value []string
ok bool
}{
@ -352,14 +372,14 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
{
"test",
[]Argument{Argument{"header", "a", "", false}},
&map[string]interface{}{"A": "z"}, nil, nil,
map[string]interface{}{"A": "z"}, nil, nil,
[]string{"HOOK_a=z"},
true,
},
{
"test",
[]Argument{Argument{"header", "a", "MYKEY", false}},
&map[string]interface{}{"A": "z"}, nil, nil,
map[string]interface{}{"A": "z"}, nil, nil,
[]string{"MYKEY=z"},
true,
},
@ -367,7 +387,7 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
{
"fail",
[]Argument{Argument{"payload", "a", "", false}},
&map[string]interface{}{"A": "z"}, nil, nil,
map[string]interface{}{"A": "z"}, nil, nil,
[]string{},
false,
},
@ -376,7 +396,12 @@ var hookExtractCommandArgumentsForEnvTests = []struct {
func TestHookExtractCommandArgumentsForEnv(t *testing.T) {
for _, tt := range hookExtractCommandArgumentsForEnvTests {
h := &Hook{ExecuteCommand: tt.exec, PassEnvironmentToCommand: tt.args}
value, err := h.ExtractCommandArgumentsForEnv(tt.headers, tt.query, tt.payload)
r := &Request{
Headers: tt.headers,
Query: tt.query,
Payload: tt.payload,
}
value, err := h.ExtractCommandArgumentsForEnv(r)
if (err == nil) != tt.ok || !reflect.DeepEqual(value, tt.value) {
t.Errorf("failed to extract args for env {cmd=%q, args=%v}:\nexpected %#v, ok: %v\ngot %#v, ok: %v", tt.exec, tt.args, tt.value, tt.ok, value, (err == nil))
}
@ -454,24 +479,24 @@ func TestHooksMatch(t *testing.T) {
var matchRuleTests = []struct {
typ, regex, secret, value, ipRange string
param Argument
headers, query, payload *map[string]interface{}
headers, query, payload map[string]interface{}
body []byte
remoteAddr string
ok bool
err bool
}{
{"value", "", "", "z", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
{"value", "", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
{"regex", "^z", "", "z", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", true, false},
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "b17e04cbb22afa8ffbff8796fc1894ed27badd9e"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "f417af3a21bd70379b5796d5f013915e7029f62c580fb0f500f59a35a6f04c89"}, nil, nil, []byte(`{"a": "z"}`), "", true, false},
// failures
{"value", "", "", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, &map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
{"value", "", "", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
{"regex", "^X", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, false},
{"value", "", "2", "X", "", Argument{"header", "a", "", false}, map[string]interface{}{"Y": "z"}, nil, nil, []byte{}, "", false, true}, // reference invalid header
// errors
{"regex", "*", "", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, &map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
{"regex", "*", "", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, "", false, true}, // invalid regex
{"payload-hash-sha1", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
{"payload-hash-sha256", "", "secret", "", "", Argument{"header", "a", "", false}, map[string]interface{}{"A": ""}, nil, nil, []byte{}, "", false, true}, // invalid hmac
// IP whitelisting, valid cases
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
{"ip-whitelist", "", "", "", "192.168.0.1/24", Argument{}, nil, nil, nil, []byte{}, "192.168.0.2:9000", true, false}, // valid IPv4, with range
@ -490,7 +515,16 @@ var matchRuleTests = []struct {
func TestMatchRule(t *testing.T) {
for i, tt := range matchRuleTests {
r := MatchRule{tt.typ, tt.regex, tt.secret, tt.value, tt.param, tt.ipRange}
ok, err := r.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, tt.remoteAddr)
req := &Request{
Headers: tt.headers,
Query: tt.query,
Payload: tt.payload,
Body: tt.body,
RawRequest: &http.Request{
RemoteAddr: tt.remoteAddr,
},
}
ok, err := r.Evaluate(req)
if ok != tt.ok || (err != nil) != tt.err {
t.Errorf("%d failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", i, r, tt.ok, tt.err, ok, err)
}
@ -500,7 +534,7 @@ func TestMatchRule(t *testing.T) {
var andRuleTests = []struct {
desc string // description of the test case
rule AndRule
headers, query, payload *map[string]interface{}
headers, query, payload map[string]interface{}
body []byte
ok bool
err bool
@ -511,7 +545,7 @@ var andRuleTests = []struct {
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
},
&map[string]interface{}{"A": "z", "B": "y"}, nil, nil,
map[string]interface{}{"A": "z", "B": "y"}, nil, nil,
[]byte{},
true, false,
},
@ -521,7 +555,7 @@ var andRuleTests = []struct {
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
},
&map[string]interface{}{"A": "z", "B": "Y"}, nil, nil,
map[string]interface{}{"A": "z", "B": "Y"}, nil, nil,
[]byte{},
false, false,
},
@ -548,7 +582,7 @@ var andRuleTests = []struct {
},
},
},
&map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil,
map[string]interface{}{"A": "z", "B": "y", "C": "x", "D": "w", "E": "X", "F": "X"}, nil, nil,
[]byte{},
true, false,
},
@ -557,14 +591,20 @@ var andRuleTests = []struct {
{
"invalid rule",
AndRule{{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}},
&map[string]interface{}{"Y": "z"}, nil, nil, nil,
map[string]interface{}{"Y": "z"}, nil, nil, nil,
false, true,
},
}
func TestAndRule(t *testing.T) {
for _, tt := range andRuleTests {
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
r := &Request{
Headers: tt.headers,
Query: tt.query,
Payload: tt.payload,
Body: tt.body,
}
ok, err := tt.rule.Evaluate(r)
if ok != tt.ok || (err != nil) != tt.err {
t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.desc, tt.ok, tt.err, ok, err)
}
@ -574,7 +614,7 @@ func TestAndRule(t *testing.T) {
var orRuleTests = []struct {
desc string // description of the test case
rule OrRule
headers, query, payload *map[string]interface{}
headers, query, payload map[string]interface{}
body []byte
ok bool
err bool
@ -585,7 +625,7 @@ var orRuleTests = []struct {
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
},
&map[string]interface{}{"A": "z", "B": "X"}, nil, nil,
map[string]interface{}{"A": "z", "B": "X"}, nil, nil,
[]byte{},
true, false,
},
@ -595,7 +635,7 @@ var orRuleTests = []struct {
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
},
&map[string]interface{}{"A": "X", "B": "y"}, nil, nil,
map[string]interface{}{"A": "X", "B": "y"}, nil, nil,
[]byte{},
true, false,
},
@ -605,7 +645,7 @@ var orRuleTests = []struct {
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
{Match: &MatchRule{"value", "", "", "y", Argument{"header", "b", "", false}, ""}},
},
&map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil,
map[string]interface{}{"A": "Z", "B": "Y"}, nil, nil,
[]byte{},
false, false,
},
@ -615,7 +655,7 @@ var orRuleTests = []struct {
OrRule{
{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}},
},
&map[string]interface{}{"Y": "Z"}, nil, nil,
map[string]interface{}{"Y": "Z"}, nil, nil,
[]byte{},
false, true,
},
@ -623,7 +663,13 @@ var orRuleTests = []struct {
func TestOrRule(t *testing.T) {
for _, tt := range orRuleTests {
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
r := &Request{
Headers: tt.headers,
Query: tt.query,
Payload: tt.payload,
Body: tt.body,
}
ok, err := tt.rule.Evaluate(r)
if ok != tt.ok || (err != nil) != tt.err {
t.Errorf("%#v:\nexpected ok: %#v, err: %v\ngot ok: %#v err: %v", tt.desc, tt.ok, tt.err, ok, err)
}
@ -633,18 +679,24 @@ func TestOrRule(t *testing.T) {
var notRuleTests = []struct {
desc string // description of the test case
rule NotRule
headers, query, payload *map[string]interface{}
headers, query, payload map[string]interface{}
body []byte
ok bool
err bool
}{
{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, &map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false},
{"(a=z): !a=X", NotRule{Match: &MatchRule{"value", "", "", "X", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, true, false},
{"(a=z): !a=z", NotRule{Match: &MatchRule{"value", "", "", "z", Argument{"header", "a", "", false}, ""}}, map[string]interface{}{"A": "z"}, nil, nil, []byte{}, false, false},
}
func TestNotRule(t *testing.T) {
for _, tt := range notRuleTests {
ok, err := tt.rule.Evaluate(tt.headers, tt.query, tt.payload, &tt.body, "")
r := &Request{
Headers: tt.headers,
Query: tt.query,
Payload: tt.payload,
Body: tt.body,
}
ok, err := tt.rule.Evaluate(r)
if ok != tt.ok || (err != nil) != tt.err {
t.Errorf("failed to match %#v:\nexpected ok: %#v, err: %v\ngot ok: %#v, err: %v", tt.rule, tt.ok, tt.err, ok, err)
}

View file

@ -302,10 +302,14 @@ func main() {
}
func hookHandler(w http.ResponseWriter, r *http.Request) {
rid := middleware.GetReqID(r.Context())
req := &hook.Request{
ID: middleware.GetReqID(r.Context()),
RawRequest: r,
}
log.Printf("[%s] incoming HTTP %s request from %s\n", rid, r.Method, r.RemoteAddr)
log.Printf("[%s] incoming HTTP %s request from %s\n", req.ID, r.Method, r.RemoteAddr)
// TODO: rename this to avoid confusion with Request.ID
id := mux.Vars(r)["id"]
matchedHook := matchLoadedHook(id)
@ -341,57 +345,54 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
if !allowedMethod {
w.WriteHeader(http.StatusMethodNotAllowed)
log.Printf("[%s] HTTP %s method not allowed for hook %q", rid, r.Method, id)
log.Printf("[%s] HTTP %s method not allowed for hook %q", req.ID, r.Method, id)
return
}
log.Printf("[%s] %s got matched\n", rid, id)
log.Printf("[%s] %s got matched\n", req.ID, id)
for _, responseHeader := range responseHeaders {
w.Header().Set(responseHeader.Name, responseHeader.Value)
}
var (
body []byte
err error
)
var err error
// set contentType to IncomingPayloadContentType or header value
contentType := r.Header.Get("Content-Type")
req.ContentType = r.Header.Get("Content-Type")
if len(matchedHook.IncomingPayloadContentType) != 0 {
contentType = matchedHook.IncomingPayloadContentType
req.ContentType = matchedHook.IncomingPayloadContentType
}
isMultipart := strings.HasPrefix(contentType, "multipart/form-data;")
isMultipart := strings.HasPrefix(req.ContentType, "multipart/form-data;")
if !isMultipart {
body, err = ioutil.ReadAll(r.Body)
req.Body, err = ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("[%s] error reading the request body: %+v\n", rid, err)
log.Printf("[%s] error reading the request body: %+v\n", req.ID, err)
}
}
// parse headers
headers := valuesToMap(r.Header)
req.Headers = valuesToMap(r.Header)
// parse query variables
query := valuesToMap(r.URL.Query())
req.Query = valuesToMap(r.URL.Query())
// parse body
var payload map[string]interface{}
// var payload map[string]interface{}
switch {
case strings.Contains(contentType, "json"):
decoder := json.NewDecoder(bytes.NewReader(body))
case strings.Contains(req.ContentType, "json"):
decoder := json.NewDecoder(bytes.NewReader(req.Body))
decoder.UseNumber()
var firstChar byte
for i := 0; i < len(body); i++ {
if unicode.IsSpace(rune(body[i])) {
for i := 0; i < len(req.Body); i++ {
if unicode.IsSpace(rune(req.Body[i])) {
continue
}
firstChar = body[i]
firstChar = req.Body[i]
break
}
@ -399,36 +400,36 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
var arrayPayload interface{}
err := decoder.Decode(&arrayPayload)
if err != nil {
log.Printf("[%s] error parsing JSON array payload %+v\n", rid, err)
log.Printf("[%s] error parsing JSON array payload %+v\n", req.ID, err)
}
payload = make(map[string]interface{}, 1)
payload["root"] = arrayPayload
req.Payload = make(map[string]interface{}, 1)
req.Payload["root"] = arrayPayload
} else {
err := decoder.Decode(&payload)
err := decoder.Decode(&req.Payload)
if err != nil {
log.Printf("[%s] error parsing JSON payload %+v\n", rid, err)
log.Printf("[%s] error parsing JSON payload %+v\n", req.ID, err)
}
}
case strings.Contains(contentType, "x-www-form-urlencoded"):
fd, err := url.ParseQuery(string(body))
case strings.Contains(req.ContentType, "x-www-form-urlencoded"):
fd, err := url.ParseQuery(string(req.Body))
if err != nil {
log.Printf("[%s] error parsing form payload %+v\n", rid, err)
log.Printf("[%s] error parsing form payload %+v\n", req.ID, err)
} else {
payload = valuesToMap(fd)
req.Payload = valuesToMap(fd)
}
case strings.Contains(contentType, "xml"):
payload, err = mxj.NewMapXmlReader(bytes.NewReader(body))
case strings.Contains(req.ContentType, "xml"):
req.Payload, err = mxj.NewMapXmlReader(bytes.NewReader(req.Body))
if err != nil {
log.Printf("[%s] error parsing XML payload: %+v\n", rid, err)
log.Printf("[%s] error parsing XML payload: %+v\n", req.ID, err)
}
case isMultipart:
err = r.ParseMultipartForm(*maxMultipartMem)
if err != nil {
msg := fmt.Sprintf("[%s] error parsing multipart form: %+v\n", rid, err)
msg := fmt.Sprintf("[%s] error parsing multipart form: %+v\n", req.ID, err)
log.Println(msg)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "Error occurred while parsing multipart form.")
@ -436,14 +437,14 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
}
for k, v := range r.MultipartForm.Value {
log.Printf("[%s] found multipart form value %q", rid, k)
log.Printf("[%s] found multipart form value %q", req.ID, k)
if payload == nil {
payload = make(map[string]interface{})
if req.Payload == nil {
req.Payload = make(map[string]interface{})
}
// TODO(moorereason): support duplicate, named values
payload[k] = v[0]
req.Payload[k] = v[0]
}
for k, v := range r.MultipartForm.File {
@ -472,11 +473,11 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
}
if parseAsJSON {
log.Printf("[%s] parsing multipart form file %q as JSON\n", rid, k)
log.Printf("[%s] parsing multipart form file %q as JSON\n", req.ID, k)
f, err := v[0].Open()
if err != nil {
msg := fmt.Sprintf("[%s] error parsing multipart form file: %+v\n", rid, err)
msg := fmt.Sprintf("[%s] error parsing multipart form file: %+v\n", req.ID, err)
log.Println(msg)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "Error occurred while parsing multipart form file.")
@ -489,24 +490,24 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
var part map[string]interface{}
err = decoder.Decode(&part)
if err != nil {
log.Printf("[%s] error parsing JSON payload file: %+v\n", rid, err)
log.Printf("[%s] error parsing JSON payload file: %+v\n", req.ID, err)
}
if payload == nil {
payload = make(map[string]interface{})
if req.Payload == nil {
req.Payload = make(map[string]interface{})
}
payload[k] = part
req.Payload[k] = part
}
}
default:
log.Printf("[%s] error parsing body payload due to unsupported content type header: %s\n", rid, contentType)
log.Printf("[%s] error parsing body payload due to unsupported content type header: %s\n", req.ID, req.ContentType)
}
// handle hook
errors := matchedHook.ParseJSONParameters(&headers, &query, &payload)
errors := matchedHook.ParseJSONParameters(req)
for _, err := range errors {
log.Printf("[%s] error parsing JSON parameters: %s\n", rid, err)
log.Printf("[%s] error parsing JSON parameters: %s\n", req.ID, err)
}
var ok bool
@ -514,29 +515,29 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
if matchedHook.TriggerRule == nil {
ok = true
} else {
ok, err = matchedHook.TriggerRule.Evaluate(&headers, &query, &payload, &body, r.RemoteAddr)
ok, err = matchedHook.TriggerRule.Evaluate(req)
if err != nil {
if !hook.IsParameterNodeError(err) {
msg := fmt.Sprintf("[%s] error evaluating hook: %s", rid, err)
msg := fmt.Sprintf("[%s] error evaluating hook: %s", req.ID, err)
log.Println(msg)
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "Error occurred while evaluating hook rules.")
return
}
log.Printf("[%s] %v", rid, err)
log.Printf("[%s] %v", req.ID, err)
}
}
if ok {
log.Printf("[%s] %s hook triggered successfully\n", rid, matchedHook.ID)
log.Printf("[%s] %s hook triggered successfully\n", req.ID, matchedHook.ID)
for _, responseHeader := range matchedHook.ResponseHeaders {
w.Header().Set(responseHeader.Name, responseHeader.Value)
}
if matchedHook.CaptureCommandOutput {
response, err := handleHook(matchedHook, rid, &headers, &query, &payload, &body)
response, err := handleHook(matchedHook, req)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
@ -549,16 +550,16 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
} else {
// Check if a success return code is configured for the hook
if matchedHook.SuccessHttpResponseCode != 0 {
writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
}
fmt.Fprint(w, response)
}
} else {
go handleHook(matchedHook, rid, &headers, &query, &payload, &body)
go handleHook(matchedHook, req)
// Check if a success return code is configured for the hook
if matchedHook.SuccessHttpResponseCode != 0 {
writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.SuccessHttpResponseCode)
}
fmt.Fprint(w, matchedHook.ResponseMessage)
@ -568,16 +569,16 @@ func hookHandler(w http.ResponseWriter, r *http.Request) {
// Check if a return code is configured for the hook
if matchedHook.TriggerRuleMismatchHttpResponseCode != 0 {
writeHttpResponseCode(w, rid, matchedHook.ID, matchedHook.TriggerRuleMismatchHttpResponseCode)
writeHttpResponseCode(w, req.ID, matchedHook.ID, matchedHook.TriggerRuleMismatchHttpResponseCode)
}
// if none of the hooks got triggered
log.Printf("[%s] %s got matched, but didn't get triggered because the trigger rules were not satisfied\n", rid, matchedHook.ID)
log.Printf("[%s] %s got matched, but didn't get triggered because the trigger rules were not satisfied\n", req.ID, matchedHook.ID)
fmt.Fprint(w, "Hook rules were not satisfied.")
}
func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]interface{}, body *[]byte) (string, error) {
func handleHook(h *hook.Hook, r *hook.Request) (string, error) {
var errors []error
// check the command exists
@ -590,12 +591,12 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
cmdPath, err := exec.LookPath(lookpath)
if err != nil {
log.Printf("[%s] error in %s", rid, err)
log.Printf("[%s] error in %s", r.ID, err)
// check if parameters specified in execute-command by mistake
if strings.IndexByte(h.ExecuteCommand, ' ') != -1 {
s := strings.Fields(h.ExecuteCommand)[0]
log.Printf("[%s] use 'pass-arguments-to-command' to specify args for '%s'", rid, s)
log.Printf("[%s] use 'pass-arguments-to-command' to specify args for '%s'", r.ID, s)
}
return "", err
@ -604,37 +605,37 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
cmd := exec.Command(cmdPath)
cmd.Dir = h.CommandWorkingDirectory
cmd.Args, errors = h.ExtractCommandArguments(headers, query, payload)
cmd.Args, errors = h.ExtractCommandArguments(r)
for _, err := range errors {
log.Printf("[%s] error extracting command arguments: %s\n", rid, err)
log.Printf("[%s] error extracting command arguments: %s\n", r.ID, err)
}
var envs []string
envs, errors = h.ExtractCommandArgumentsForEnv(headers, query, payload)
envs, errors = h.ExtractCommandArgumentsForEnv(r)
for _, err := range errors {
log.Printf("[%s] error extracting command arguments for environment: %s\n", rid, err)
log.Printf("[%s] error extracting command arguments for environment: %s\n", r.ID, err)
}
files, errors := h.ExtractCommandArgumentsForFile(headers, query, payload)
files, errors := h.ExtractCommandArgumentsForFile(r)
for _, err := range errors {
log.Printf("[%s] error extracting command arguments for file: %s\n", rid, err)
log.Printf("[%s] error extracting command arguments for file: %s\n", r.ID, err)
}
for i := range files {
tmpfile, err := ioutil.TempFile(h.CommandWorkingDirectory, files[i].EnvName)
if err != nil {
log.Printf("[%s] error creating temp file [%s]", rid, err)
log.Printf("[%s] error creating temp file [%s]", r.ID, err)
continue
}
log.Printf("[%s] writing env %s file %s", rid, files[i].EnvName, tmpfile.Name())
log.Printf("[%s] writing env %s file %s", r.ID, files[i].EnvName, tmpfile.Name())
if _, err := tmpfile.Write(files[i].Data); err != nil {
log.Printf("[%s] error writing file %s [%s]", rid, tmpfile.Name(), err)
log.Printf("[%s] error writing file %s [%s]", r.ID, tmpfile.Name(), err)
continue
}
if err := tmpfile.Close(); err != nil {
log.Printf("[%s] error closing file %s [%s]", rid, tmpfile.Name(), err)
log.Printf("[%s] error closing file %s [%s]", r.ID, tmpfile.Name(), err)
continue
}
@ -644,27 +645,27 @@ func handleHook(h *hook.Hook, rid string, headers, query, payload *map[string]in
cmd.Env = append(os.Environ(), envs...)
log.Printf("[%s] executing %s (%s) with arguments %q and environment %s using %s as cwd\n", rid, h.ExecuteCommand, cmd.Path, cmd.Args, envs, cmd.Dir)
log.Printf("[%s] executing %s (%s) with arguments %q and environment %s using %s as cwd\n", r.ID, h.ExecuteCommand, cmd.Path, cmd.Args, envs, cmd.Dir)
out, err := cmd.CombinedOutput()
log.Printf("[%s] command output: %s\n", rid, out)
log.Printf("[%s] command output: %s\n", r.ID, out)
if err != nil {
log.Printf("[%s] error occurred: %+v\n", rid, err)
log.Printf("[%s] error occurred: %+v\n", r.ID, err)
}
for i := range files {
if files[i].File != nil {
log.Printf("[%s] removing file %s\n", rid, files[i].File.Name())
log.Printf("[%s] removing file %s\n", r.ID, files[i].File.Name())
err := os.Remove(files[i].File.Name())
if err != nil {
log.Printf("[%s] error removing file %s [%s]", rid, files[i].File.Name(), err)
log.Printf("[%s] error removing file %s [%s]", r.ID, files[i].File.Name(), err)
}
}
}
log.Printf("[%s] finished handling %s\n", rid, h.ID)
log.Printf("[%s] finished handling %s\n", r.ID, h.ID)
return string(out), err
}

View file

@ -53,7 +53,11 @@ func TestStaticParams(t *testing.T) {
b := &bytes.Buffer{}
log.SetOutput(b)
_, err = handleHook(spHook, "test", &spHeaders, &map[string]interface{}{}, &map[string]interface{}{}, &[]byte{})
r := &hook.Request{
ID: "test",
Headers: spHeaders,
}
_, err = handleHook(spHook, r)
if err != nil {
t.Fatalf("Unexpected error: %v\n", err)
}