From 173ba6c347e3a4a158fa976cb8a3a7913aa4cede Mon Sep 17 00:00:00 2001 From: Nicolas Carlier Date: Mon, 9 May 2022 23:17:49 +0200 Subject: [PATCH] feat(): support application/x-www-form-urlencoded --- README.md | 29 +++++++++++++++-------------- pkg/api/helper.go | 4 ++-- pkg/api/index.go | 27 +++++++++++++++++++++------ pkg/api/test/helper_test.go | 2 +- 4 files changed, 39 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 6eba5ef..0f07ba0 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,14 @@ $ docker run -d --name=webhookd \ webhookd --scripts=/var/opt/webhookd/scripts ``` -> Note that this image extends `docker:dind` Docker image. +> Note: This image extends `docker:dind` Docker image. > Therefore you are able to interact with a Docker daemon with yours shell scripts. **Or** use APT: Finally, it is possible to install Webhookd using the Debian packaging system through this [custom repository](https://packages.azlux.fr/). -> Note that custom configuration variables can be set into `/etc/webhookd.env` file. +> Note: Custom configuration variables can be set into `/etc/webhookd.env` file. > Sytemd service is already set and enable, you just have to start it with `systemctl start webhookd`. ## Configuration @@ -78,7 +78,7 @@ You can override the default using the `WHD_SCRIPTS` environment variable or `-s |--> ... ``` -Note that Webhookd is able to run any type of file in this directory as long as the file is executable. +> Note: Webhookd is able to run any type of file in this directory as long as the file is executable. For example, you can execute a Node.js file if you give execution rights to the file and add the appropriate `#!` header (in this case: `#!/usr/bin/env node`). You can find sample scripts in the [example folder](./scripts/examples). @@ -139,15 +139,17 @@ data: bar bar bar ### Webhook parameters -You have several way to provide parameters to your webhook script: +You have several ways to provide parameters to your webhook script: -- URL query parameters and HTTP headers are converted into environment variables. - Variable names follows "snakecase" naming convention. - Therefore the name can be altered. +- URL request parameters are converted to script variables +- HTTP headers are converted to script variables +- Request body (depending the Media Type): + - `application/x-www-form-urlencoded`: keys and values are converted to script variables + - `text/*` or `application/json`: payload is transmit to the script as first parameter. - *ex: `CONTENT-TYPE` will become `content_type`.* - -- When using `POST`, body content (text/plain or application/json) is transmit to the script as parameter. +> Note: Variable name follows "snakecase" naming convention. +Therefore the name can be altered. +*ex: `CONTENT-TYPE` will become `content_type`.* *Example:* @@ -226,7 +228,7 @@ $ # or $ webhookd --notification-uri=http://requestb.in/v9b229v9 ``` -Note that only the output of the script prefixed by "notify:" is sent to the notification channel. +> Note: Only the output of the script prefixed by "notify:" is sent to the notification channel. If the output does not contain a prefixed line, no notification will be sent. **Example:** @@ -261,7 +263,7 @@ The following JSON payload is POST to the target URL: } ``` -Note that because the payload have a `text` attribute, you can use a [Mattermost][mattermost], [Slack][slack] or [Discord][discord] webhook endpoint. +> Note: that because the payload have a `text` attribute, you can use a [Mattermost][mattermost], [Slack][slack] or [Discord][discord] webhook endpoint. [mattermost]: https://docs.mattermost.com/developer/webhooks-incoming.html [discord]: https://discord.com/developers/docs/resources/webhook#execute-slackcompatible-webhook @@ -293,8 +295,7 @@ $ htpasswd -B -c .htpasswd api ``` This command will ask for a password and store it in the htpawsswd file. -Please note that by default, the daemon will try to load the `.htpasswd` file. - +By default, the daemon will try to load the `.htpasswd` file. But you can override this behavior by specifying the location of the file: ```bash diff --git a/pkg/api/helper.go b/pkg/api/helper.go index 3bb9082..3b61015 100644 --- a/pkg/api/helper.go +++ b/pkg/api/helper.go @@ -11,8 +11,8 @@ import ( "github.com/ncarlier/webhookd/pkg/strcase" ) -// QueryParamsToShellVars convert URL query parameters to shell vars. -func QueryParamsToShellVars(q url.Values) []string { +// URLValuesToShellVars convert URL values to shell vars. +func URLValuesToShellVars(q url.Values) []string { var params []string for k, v := range q { var buf bytes.Buffer diff --git a/pkg/api/index.go b/pkg/api/index.go index ed3210e..64a7fb3 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "io/ioutil" + "mime" "net/http" "path" "path/filepath" @@ -51,7 +52,7 @@ func triggerWebhook(w http.ResponseWriter, r *http.Request) { // Check that streaming is supported flusher, ok := w.(http.Flusher) if !ok { - http.Error(w, "Streaming not supported!", http.StatusInternalServerError) + http.Error(w, "streaming not supported", http.StatusInternalServerError) return } @@ -68,14 +69,28 @@ func triggerWebhook(w http.ResponseWriter, r *http.Request) { return } - body, err := ioutil.ReadAll(r.Body) - if err != nil { - logger.Error.Printf("error reading body: %v", err) - http.Error(w, "can't read body", http.StatusBadRequest) + if err = r.ParseForm(); err != nil { + logger.Error.Printf("error reading from-data: %v", err) + http.Error(w, "unable to parse request form", http.StatusBadRequest) return } - params := QueryParamsToShellVars(r.URL.Query()) + // parse body + var body []byte + ct := r.Header.Get("Content-Type") + if ct != "" { + mediatype, _, _ := mime.ParseMediaType(ct) + if strings.HasPrefix(mediatype, "text/") || mediatype == "application/json" { + body, err = ioutil.ReadAll(r.Body) + if err != nil { + logger.Error.Printf("error reading body: %v", err) + http.Error(w, "unable to read request body", http.StatusBadRequest) + return + } + } + } + + params := URLValuesToShellVars(r.Form) params = append(params, HTTPHeadersToShellVars(r.Header)...) // logger.Debug.Printf("API REQUEST: \"%s\" with params %s...\n", p, params) diff --git a/pkg/api/test/helper_test.go b/pkg/api/test/helper_test.go index eca8306..7d908fc 100644 --- a/pkg/api/test/helper_test.go +++ b/pkg/api/test/helper_test.go @@ -14,7 +14,7 @@ func TestQueryParamsToShellVars(t *testing.T) { "string": []string{"foo"}, "list": []string{"foo", "bar"}, } - values := api.QueryParamsToShellVars(tc) + values := api.URLValuesToShellVars(tc) assert.ContainsStr(t, "string=foo", values, "") assert.ContainsStr(t, "list=foo,bar", values, "") }