diff --git a/hook/hook.go b/hook/hook.go index 5555fd4d..0cc2740e 100644 --- a/hook/hook.go +++ b/hook/hook.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io/ioutil" + "net/textproto" "reflect" "regexp" "strconv" @@ -18,6 +19,7 @@ import ( const ( SourceHeader string = "header" SourceQuery string = "url" + SourceQueryAlias string = "query" SourcePayload string = "payload" SourceString string = "string" SourceEntirePayload string = "entire-payload" @@ -205,11 +207,13 @@ type Argument struct { // based on the Argument's source func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string, bool) { var source *map[string]interface{} + key := ha.Name switch ha.Source { case SourceHeader: source = headers - case SourceQuery: + key = textproto.CanonicalMIMEHeaderKey(ha.Name) + case SourceQuery, SourceQueryAlias: source = query case SourcePayload: source = payload @@ -242,7 +246,7 @@ func (ha *Argument) Get(headers, query, payload *map[string]interface{}) (string } if source != nil { - return ExtractParameterAsString(ha.Name, *source) + return ExtractParameterAsString(key, *source) } return "", false @@ -300,7 +304,9 @@ 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(headers, query, payload *map[string]interface{}) []error { + var errors = make([]error, 0) + for i := range h.JSONStringParameters { if arg, ok := h.JSONStringParameters[i].Get(headers, query, payload); ok { var newArg map[string]interface{} @@ -311,7 +317,8 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface err := decoder.Decode(&newArg) if err != nil { - return &ParseError{err} + errors = append(errors, &ParseError{err}) + continue } var source *map[string]interface{} @@ -321,27 +328,38 @@ func (h *Hook) ParseJSONParameters(headers, query, payload *map[string]interface source = headers case SourcePayload: source = payload - case SourceQuery: + case SourceQuery, SourceQueryAlias: source = query } if source != nil { - ReplaceParameter(h.JSONStringParameters[i].Name, source, newArg) + key := h.JSONStringParameters[i].Name + + if h.JSONStringParameters[i].Source == SourceHeader { + key = textproto.CanonicalMIMEHeaderKey(h.JSONStringParameters[i].Name) + } + + ReplaceParameter(key, source, newArg) } else { - return &SourceError{h.JSONStringParameters[i]} + errors = append(errors, &SourceError{h.JSONStringParameters[i]}) } } else { - return &ArgumentError{h.JSONStringParameters[i]} + errors = append(errors, &ArgumentError{h.JSONStringParameters[i]}) } } + if len(errors) > 0 { + return errors + } + return nil } // 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(headers, query, payload *map[string]interface{}) ([]string, []error) { var args = make([]string, 0) + var errors = make([]error, 0) args = append(args, h.ExecuteCommand) @@ -350,19 +368,23 @@ func (h *Hook) ExtractCommandArguments(headers, query, payload *map[string]inter args = append(args, arg) } else { args = append(args, "") - return args, &ArgumentError{h.PassArgumentsToCommand[i]} + errors = append(errors, &ArgumentError{h.PassArgumentsToCommand[i]}) } } + if len(errors) > 0 { + return args, errors + } + return args, nil } // 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(headers, query, payload *map[string]interface{}) ([]string, []error) { var args = make([]string, 0) - + var errors = make([]error, 0) for i := range h.PassEnvironmentToCommand { if arg, ok := h.PassEnvironmentToCommand[i].Get(headers, query, payload); ok { if h.PassEnvironmentToCommand[i].EnvName != "" { @@ -373,10 +395,14 @@ func (h *Hook) ExtractCommandArgumentsForEnv(headers, query, payload *map[string args = append(args, EnvNamespace+h.PassEnvironmentToCommand[i].Name+"="+arg) } } else { - return args, &ArgumentError{h.PassEnvironmentToCommand[i]} + errors = append(errors, &ArgumentError{h.PassEnvironmentToCommand[i]}) } } + if len(errors) > 0 { + return args, errors + } + return args, nil } diff --git a/webhook.go b/webhook.go index 20ac190f..d4908de0 100644 --- a/webhook.go +++ b/webhook.go @@ -21,20 +21,21 @@ import ( ) const ( - version = "2.5.0" + version = "2.6.0" ) var ( - ip = flag.String("ip", "0.0.0.0", "ip the webhook should serve hooks on") - port = flag.Int("port", 9000, "port the webhook should serve hooks on") - verbose = flag.Bool("verbose", false, "show verbose output") - noPanic = flag.Bool("nopanic", false, "do not panic if hooks cannot be loaded when webhook is not running in verbose mode") - hotReload = flag.Bool("hotreload", false, "watch hooks file for changes and reload them automatically") - hooksFilePath = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve") - hooksURLPrefix = flag.String("urlprefix", "hooks", "url prefix to use for served hooks (protocol://yourserver:port/PREFIX/:hook-id)") - secure = flag.Bool("secure", false, "use HTTPS instead of HTTP") - cert = flag.String("cert", "cert.pem", "path to the HTTPS certificate pem file") - key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file") + ip = flag.String("ip", "0.0.0.0", "ip the webhook should serve hooks on") + port = flag.Int("port", 9000, "port the webhook should serve hooks on") + verbose = flag.Bool("verbose", false, "show verbose output") + noPanic = flag.Bool("nopanic", false, "do not panic if hooks cannot be loaded when webhook is not running in verbose mode") + hotReload = flag.Bool("hotreload", false, "watch hooks file for changes and reload them automatically") + hooksFilePath = flag.String("hooks", "hooks.json", "path to the json file containing defined hooks the webhook should serve") + hooksURLPrefix = flag.String("urlprefix", "hooks", "url prefix to use for served hooks (protocol://yourserver:port/PREFIX/:hook-id)") + secure = flag.Bool("secure", false, "use HTTPS instead of HTTP") + cert = flag.String("cert", "cert.pem", "path to the HTTPS certificate pem file") + key = flag.String("key", "key.pem", "path to the HTTPS certificate private key pem file") + justDisplayVersion = flag.Bool("version", false, "display webhook version and quit") responseHeaders hook.ResponseHeaders @@ -51,6 +52,11 @@ func main() { flag.Parse() + if *justDisplayVersion { + fmt.Println("webhook version " + version) + os.Exit(0) + } + log.SetPrefix("[webhook] ") log.SetFlags(log.Ldate | log.Ltime) @@ -191,12 +197,10 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { } // handle hook - if err = matchedHook.ParseJSONParameters(&headers, &query, &payload); err != nil { - msg := fmt.Sprintf("error parsing JSON parameters: %s", err) - log.Printf(msg) - w.WriteHeader(http.StatusBadRequest) - fmt.Fprintf(w, "Unable to parse JSON parameters.") - return + if errors := matchedHook.ParseJSONParameters(&headers, &query, &payload); errors != nil { + for _, err := range errors { + log.Printf("error parsing JSON parameters: %s\n", err) + } } var ok bool @@ -249,21 +253,27 @@ func hookHandler(w http.ResponseWriter, r *http.Request) { } func handleHook(h *hook.Hook, headers, query, payload *map[string]interface{}, body *[]byte) (string, error) { - var err error + var errors []error cmd := exec.Command(h.ExecuteCommand) cmd.Dir = h.CommandWorkingDirectory - cmd.Args, err = h.ExtractCommandArguments(headers, query, payload) - if err != nil { - log.Printf("error extracting command arguments: %s", err) + cmd.Args, errors = h.ExtractCommandArguments(headers, query, payload) + if errors != nil { + for _, err := range errors { + log.Printf("error extracting command arguments: %s\n", err) + } } var envs []string - envs, err = h.ExtractCommandArgumentsForEnv(headers, query, payload) - if err != nil { - log.Printf("error extracting command arguments for environment: %s", err) + envs, errors = h.ExtractCommandArgumentsForEnv(headers, query, payload) + + if errors != nil { + for _, err := range errors { + log.Printf("error extracting command arguments for environment: %s\n", err) + } } + cmd.Env = append(os.Environ(), envs...) log.Printf("executing %s (%s) with arguments %q and environment %s using %s as cwd\n", h.ExecuteCommand, cmd.Path, cmd.Args, envs, cmd.Dir)