diff --git a/docs/Hook-Definition.md b/docs/Hook-Definition.md index 8a7e744..5618235 100644 --- a/docs/Hook-Definition.md +++ b/docs/Hook-Definition.md @@ -23,6 +23,7 @@ Hooks are defined as objects in the JSON or YAML hooks configuration file. Pleas * `trigger-rule` - specifies the rule that will be evaluated in order to determine should the hook be triggered. Check [Hook rules page](Hook-Rules.md) to see the list of valid rules and their usage * `trigger-rule-mismatch-http-response-code` - specifies the HTTP status code to be returned when the trigger rule is not satisfied * `trigger-signature-soft-failures` - allow signature validation failures within Or rules; by default, signature failures are treated as errors. +* `keep-file-environment` - Keep all submitted files. Sending `curl -d 'pkg=@res.tar.gz'` will retrieve the environment variable `HOOK_FILE_PKG`, which contains the file path, and `HOOK_FILENAME_PKG`, which contains the file name as `res.tar.gz`. If `keep-file-environment` is true, the file will be preserved after the hook is executed. By default, the corresponding file will be removed after the webhook exits. ## Examples Check out [Hook examples page](Hook-Examples.md) for more complex examples of hooks. diff --git a/internal/hook/hook.go b/internal/hook/hook.go index 0510095..fbd4d67 100644 --- a/internal/hook/hook.go +++ b/internal/hook/hook.go @@ -581,6 +581,7 @@ type Hook struct { IncomingPayloadContentType string `json:"incoming-payload-content-type,omitempty"` SuccessHttpResponseCode int `json:"success-http-response-code,omitempty"` HTTPMethods []string `json:"http-methods"` + KeepFileEnvironment bool `json:"keep-file-environment,omitempty"` } // ParseJSONParameters decodes specified arguments to JSON objects and replaces the diff --git a/webhook.go b/webhook.go index d23cd02..f3070ac 100644 --- a/webhook.go +++ b/webhook.go @@ -5,6 +5,7 @@ import ( "encoding/json" "flag" "fmt" + "io" "io/ioutil" "log" "net" @@ -615,6 +616,52 @@ func handleHook(h *hook.Hook, r *hook.Request) (string, error) { envs = append(envs, files[i].EnvName+"="+tmpfile.Name()) } + if h.KeepFileEnvironment && r.RawRequest != nil && r.RawRequest.MultipartForm != nil { + for k, v := range r.RawRequest.MultipartForm.File { + env_name := hook.EnvNamespace + "FILE_" + strings.ToUpper(k) + f, err := v[0].Open() + if err != nil { + log.Printf("[%s] error open form %s file [%s]", r.ID, k, err) + continue + } + if f1, ok := f.(*os.File); ok { + log.Printf("[%s] temporary file %s", r.ID, f1.Name()) + _ = f1.Close() + files = append(files, hook.FileParameter{File: f1, EnvName: env_name}) + envs = append(envs, + env_name+"="+f1.Name(), + hook.EnvNamespace+"FILENAME_"+strings.ToUpper(k)+"="+v[0].Filename, + ) + continue + } + tmpfile, err := os.CreateTemp("", ".hook-"+r.ID+"-"+k+"-*") + if err != nil { + _ = f.Close() + log.Printf("[%s] error creating temp file [%s]", r.ID, err) + continue + } + log.Printf("[%s] writing env %s file %s", r.ID, env_name, tmpfile.Name()) + if _, err = io.Copy(tmpfile, f); err != nil { + log.Printf("[%s] error writing file %s [%s]", r.ID, tmpfile.Name(), err) + _ = f.Close() + _ = tmpfile.Close() + _ = os.Remove(tmpfile.Name()) + continue + } + if err := tmpfile.Close(); err != nil { + log.Printf("[%s] error closing file %s [%s]", r.ID, tmpfile.Name(), err) + _ = os.Remove(tmpfile.Name()) + continue + } + _ = f.Close() + files = append(files, hook.FileParameter{File: tmpfile, EnvName: env_name}) + envs = append(envs, + env_name+"="+tmpfile.Name(), + hook.EnvNamespace+"FILENAME_"+strings.ToUpper(k)+"="+v[0].Filename, + ) + } + } + cmd.Env = append(os.Environ(), envs...) 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)