summaryrefslogtreecommitdiffstats
path: root/vendor/github.com/mattermost/platform/model/incoming_webhook.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/mattermost/platform/model/incoming_webhook.go')
-rw-r--r--vendor/github.com/mattermost/platform/model/incoming_webhook.go135
1 files changed, 130 insertions, 5 deletions
diff --git a/vendor/github.com/mattermost/platform/model/incoming_webhook.go b/vendor/github.com/mattermost/platform/model/incoming_webhook.go
index 0763b443..c567edfd 100644
--- a/vendor/github.com/mattermost/platform/model/incoming_webhook.go
+++ b/vendor/github.com/mattermost/platform/model/incoming_webhook.go
@@ -4,13 +4,15 @@
package model
import (
+ "bytes"
"encoding/json"
"io"
+ "regexp"
+ "strings"
)
const (
DEFAULT_WEBHOOK_USERNAME = "webhook"
- DEFAULT_WEBHOOK_ICON = "/static/images/webhook_icon.jpg"
)
type IncomingWebhook struct {
@@ -125,13 +127,136 @@ func (o *IncomingWebhook) PreUpdate() {
o.UpdateAt = GetMillis()
}
-func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest {
- decoder := json.NewDecoder(data)
+// escapeControlCharsFromPayload escapes control chars (\n, \t) from a byte slice.
+// Context:
+// JSON strings are not supposed to contain control characters such as \n, \t,
+// ... but some incoming webhooks might still send invalid JSON and we want to
+// try to handle that. An example invalid JSON string from an incoming webhook
+// might look like this (strings for both "text" and "fallback" attributes are
+// invalid JSON strings because they contain unescaped newlines and tabs):
+// `{
+// "text": "this is a test
+// that contains a newline and tabs",
+// "attachments": [
+// {
+// "fallback": "Required plain-text summary of the attachment
+// that contains a newline and tabs",
+// "color": "#36a64f",
+// ...
+// "text": "Optional text that appears within the attachment
+// that contains a newline and tabs",
+// ...
+// "thumb_url": "http://example.com/path/to/thumb.png"
+// }
+// ]
+// }`
+// This function will search for `"key": "value"` pairs, and escape \n, \t
+// from the value.
+func escapeControlCharsFromPayload(by []byte) []byte {
+ // we'll search for `"text": "..."` or `"fallback": "..."`, ...
+ keys := "text|fallback|pretext|author_name|title|value"
+
+ // the regexp reads like this:
+ // (?s): this flag let . match \n (default is false)
+ // "(keys)": we search for the keys defined above
+ // \s*:\s*: followed by 0..n spaces/tabs, a colon then 0..n spaces/tabs
+ // ": a double-quote
+ // (\\"|[^"])*: any number of times the `\"` string or any char but a double-quote
+ // ": a double-quote
+ r := `(?s)"(` + keys + `)"\s*:\s*"(\\"|[^"])*"`
+ re := regexp.MustCompile(r)
+
+ // the function that will escape \n and \t on the regexp matches
+ repl := func(b []byte) []byte {
+ if bytes.Contains(b, []byte("\n")) {
+ b = bytes.Replace(b, []byte("\n"), []byte("\\n"), -1)
+ }
+ if bytes.Contains(b, []byte("\t")) {
+ b = bytes.Replace(b, []byte("\t"), []byte("\\t"), -1)
+ }
+
+ return b
+ }
+
+ return re.ReplaceAllFunc(by, repl)
+}
+
+func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) {
+ decoder := json.NewDecoder(bytes.NewReader(by))
var o IncomingWebhookRequest
err := decoder.Decode(&o)
if err == nil {
- return &o
+ return &o, nil
} else {
- return nil
+ return nil, err
+ }
+}
+
+// To mention @channel via a webhook in Slack, the message should contain
+// <!channel>, as explained at the bottom of this article:
+// https://get.slack.help/hc/en-us/articles/202009646-Making-announcements
+func expandAnnouncement(text string) string {
+ c1 := "<!channel>"
+ c2 := "@channel"
+ if strings.Contains(text, c1) {
+ return strings.Replace(text, c1, c2, -1)
+ }
+ return text
+}
+
+// Expand announcements in incoming webhooks from Slack. Those announcements
+// can be found in the text attribute, or in the pretext, text, title and value
+// attributes of the attachment structure. The Slack attachment structure is
+// documented here: https://api.slack.com/docs/attachments
+func expandAnnouncements(i *IncomingWebhookRequest) {
+ i.Text = expandAnnouncement(i.Text)
+
+ if i.Attachments != nil {
+ attachments := i.Attachments.([]interface{})
+ for _, attachment := range attachments {
+ a := attachment.(map[string]interface{})
+
+ if a["pretext"] != nil {
+ a["pretext"] = expandAnnouncement(a["pretext"].(string))
+ }
+
+ if a["text"] != nil {
+ a["text"] = expandAnnouncement(a["text"].(string))
+ }
+
+ if a["title"] != nil {
+ a["title"] = expandAnnouncement(a["title"].(string))
+ }
+
+ if a["fields"] != nil {
+ fields := a["fields"].([]interface{})
+ for _, field := range fields {
+ f := field.(map[string]interface{})
+ if f["value"] != nil {
+ f["value"] = expandAnnouncement(f["value"].(string))
+ }
+ }
+ }
+ }
}
}
+
+func IncomingWebhookRequestFromJson(data io.Reader) *IncomingWebhookRequest {
+ buf := new(bytes.Buffer)
+ buf.ReadFrom(data)
+ by := buf.Bytes()
+
+ // Try to decode the JSON data. Only if it fails, try to escape control
+ // characters from the strings contained in the JSON data.
+ o, err := decodeIncomingWebhookRequest(by)
+ if err != nil {
+ o, err = decodeIncomingWebhookRequest(escapeControlCharsFromPayload(by))
+ if err != nil {
+ return nil
+ }
+ }
+
+ expandAnnouncements(o)
+
+ return o
+}