-
Notifications
You must be signed in to change notification settings - Fork 51
Add template support #285
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add template support #285
Changes from 2 commits
f9f6d10
602af77
fe491bf
29adb8e
b6419db
a3a1c8a
887d70d
cc9a267
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ import ( | |
| "net/http" | ||
| "os" | ||
| "strings" | ||
| "text/template" | ||
| "time" | ||
|
|
||
| "github.com/gorilla/mux" | ||
|
|
@@ -32,33 +33,59 @@ func NewHTTPSrv(stats *base.StatsRegistry, debugConfig *base.ChatDebugOutputConf | |
| return h | ||
| } | ||
|
|
||
| type msgPayload struct { | ||
| Msg string | ||
| func injectTemplateVars(webhookName, webhookMethod, template string) string { | ||
| return fmt.Sprintf("{{$webhookName := %q}}{{$webhookMethod := %q}} %s", webhookName, webhookMethod, template) | ||
| } | ||
|
|
||
| func (h *HTTPSrv) getMessage(r *http.Request) (string, error) { | ||
| msg := r.URL.Query().Get("msg") | ||
| if len(msg) > 0 { | ||
| return msg, nil | ||
| func (h *HTTPSrv) getMessage(r *http.Request, hook webhook) (string, error) { | ||
| if r.Method == http.MethodGet && len(hook.template) == 0 { | ||
| j, err := json.Marshal(r.URL.Query()) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| return string(j), nil | ||
| } | ||
|
|
||
| if r.Method == http.MethodPost && len(hook.template) == 0 { | ||
| defer r.Body.Close() | ||
| body, err := ioutil.ReadAll(r.Body) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| return string(body), nil | ||
| } | ||
|
|
||
| defer r.Body.Close() | ||
| body, err := ioutil.ReadAll(r.Body) | ||
| tWithVars := injectTemplateVars(hook.name, r.Method, hook.template) | ||
| t, err := template.New("").Parse(tWithVars) | ||
| if err != nil { | ||
| return "", err | ||
| return "`Error: failed to parse template: " + err.Error() + "`", nil | ||
| } | ||
|
|
||
| var payload msgPayload | ||
| decoder := json.NewDecoder(bytes.NewReader(body)) | ||
| if err := decoder.Decode(&payload); err == nil && len(payload.Msg) > 0 { | ||
| return payload.Msg, nil | ||
| if r.Method == http.MethodGet { | ||
| buf := new(bytes.Buffer) | ||
| if err := t.Execute(buf, r.URL.Query()); err == nil { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps the template population should be timeboxed (context.WithTimeout) (and in a separate goroutine) so a malicious template (infinite loop) doesn't halt the bot
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i've spent some time looking into this and thinking about how it would be done and i'm struggling to think of how we would cancel the i also did some tests to see if it's possible to create some kind of malicious template with an infinite loop, and it turns out that if you create a template that ranges over nothing, like if you still think we should timebox this, i'd be interested in your thoughts about how to kill that
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would a recursive template like this be possible? https://play.golang.org/p/uMN1IsGB4xl It might be useful to look at this issue golang/go#31107
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I realized that my playground example might not be super realistic since playground has some pretty strict timing & memory restrictions. It seems like Execute gives up at some depth (100000 from the source). However, I came up with something that gets killed by the linux OOM killer when writing to a
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
i tried this on an instance of the bot that i'm currently running, and it did not work. i tried it exactly as you have it here, which takes no input from any fields on the webhook, and i also modified it a bit to take a but this was a really cool approach. i'm kind of curious why this works in the playground but not the bot. out of curiosity i also put your example in a file and compiled it and it runs in under a second before exiting, and it prints out a bunch of the
thank you for this! i skimmed it a but just now but will have to read through it a bit when i have more time. it looks like there may be some good ideas in there that i can use.
perhaps this explains the behavior i mentioned abovened above
i'll look into this, thanks for the suggestion. i'd be interested to see what you came up with. |
||
| return buf.String(), nil | ||
| } | ||
| } | ||
|
|
||
| msg = string(body) | ||
| if len(msg) > 0 { | ||
| return msg, nil | ||
| if r.Method == http.MethodPost { | ||
| defer r.Body.Close() | ||
| body, err := ioutil.ReadAll(r.Body) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
|
|
||
| m := map[string]interface{}{} | ||
| err = json.Unmarshal(body, &m) | ||
| if err != nil { | ||
| return "", err | ||
| } | ||
| buf := new(bytes.Buffer) | ||
| if err := t.Execute(buf, m); err == nil && len(buf.String()) > 0 { | ||
| return buf.String(), nil | ||
| } | ||
| } | ||
| return "`Error: no body found. To use a webhook URL, supply a 'msg' URL parameter, or a JSON POST body with a field 'msg'`", nil | ||
| return "", nil | ||
| } | ||
|
|
||
| func (h *HTTPSrv) handleHook(w http.ResponseWriter, r *http.Request) { | ||
|
|
@@ -71,15 +98,19 @@ func (h *HTTPSrv) handleHook(w http.ResponseWriter, r *http.Request) { | |
| w.WriteHeader(http.StatusNotFound) | ||
| return | ||
| } | ||
| msg, err := h.getMessage(r) | ||
| msg, err := h.getMessage(r, hook) | ||
| if err != nil { | ||
| h.Stats.Count("handle - no message") | ||
| h.Errorf("handleHook: failed to find message: %s", err) | ||
| w.WriteHeader(http.StatusBadRequest) | ||
| return | ||
| } | ||
| if strings.TrimSpace(msg) == "" { | ||
| h.Stats.Count("handle - empty msg") | ||
| return | ||
| } | ||
| h.Stats.Count("handle - success") | ||
| if _, err := h.Config().KBC.SendMessageByConvID(hook.convID, "[hook: *%s*]\n\n%s", hook.name, msg); err != nil { | ||
| if _, err := h.Config().KBC.SendMessageByConvID(hook.convID, " %s", msg); err != nil { | ||
| if base.IsDeletedConvError(err) { | ||
| h.Debug("ChatEcho: failed to send echo message: %s", err) | ||
| return | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.