github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+incompatible/model/incoming_webhook.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package model 5 6 import ( 7 "bytes" 8 "encoding/json" 9 "io" 10 "net/http" 11 "regexp" 12 ) 13 14 const ( 15 DEFAULT_WEBHOOK_USERNAME = "webhook" 16 ) 17 18 type IncomingWebhook struct { 19 Id string `json:"id"` 20 CreateAt int64 `json:"create_at"` 21 UpdateAt int64 `json:"update_at"` 22 DeleteAt int64 `json:"delete_at"` 23 UserId string `json:"user_id"` 24 ChannelId string `json:"channel_id"` 25 TeamId string `json:"team_id"` 26 DisplayName string `json:"display_name"` 27 Description string `json:"description"` 28 Username string `json:"username"` 29 IconURL string `json:"icon_url"` 30 } 31 32 type IncomingWebhookRequest struct { 33 Text string `json:"text"` 34 Username string `json:"username"` 35 IconURL string `json:"icon_url"` 36 ChannelName string `json:"channel"` 37 Props StringInterface `json:"props"` 38 Attachments []*SlackAttachment `json:"attachments"` 39 Type string `json:"type"` 40 } 41 42 func (o *IncomingWebhook) ToJson() string { 43 b, _ := json.Marshal(o) 44 return string(b) 45 } 46 47 func IncomingWebhookFromJson(data io.Reader) *IncomingWebhook { 48 var o *IncomingWebhook 49 json.NewDecoder(data).Decode(&o) 50 return o 51 } 52 53 func IncomingWebhookListToJson(l []*IncomingWebhook) string { 54 b, _ := json.Marshal(l) 55 return string(b) 56 } 57 58 func IncomingWebhookListFromJson(data io.Reader) []*IncomingWebhook { 59 var o []*IncomingWebhook 60 json.NewDecoder(data).Decode(&o) 61 return o 62 } 63 64 func (o *IncomingWebhook) IsValid() *AppError { 65 66 if len(o.Id) != 26 { 67 return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.id.app_error", nil, "", http.StatusBadRequest) 68 69 } 70 71 if o.CreateAt == 0 { 72 return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) 73 } 74 75 if o.UpdateAt == 0 { 76 return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest) 77 } 78 79 if len(o.UserId) != 26 { 80 return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.user_id.app_error", nil, "", http.StatusBadRequest) 81 } 82 83 if len(o.ChannelId) != 26 { 84 return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.channel_id.app_error", nil, "", http.StatusBadRequest) 85 } 86 87 if len(o.TeamId) != 26 { 88 return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.team_id.app_error", nil, "", http.StatusBadRequest) 89 } 90 91 if len(o.DisplayName) > 64 { 92 return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.display_name.app_error", nil, "", http.StatusBadRequest) 93 } 94 95 if len(o.Description) > 128 { 96 return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.description.app_error", nil, "", http.StatusBadRequest) 97 } 98 99 if len(o.Username) > 64 { 100 return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.username.app_error", nil, "", http.StatusBadRequest) 101 } 102 103 if len(o.IconURL) > 1024 { 104 return NewAppError("IncomingWebhook.IsValid", "model.incoming_hook.icon_url.app_error", nil, "", http.StatusBadRequest) 105 } 106 107 return nil 108 } 109 110 func (o *IncomingWebhook) PreSave() { 111 if o.Id == "" { 112 o.Id = NewId() 113 } 114 115 o.CreateAt = GetMillis() 116 o.UpdateAt = o.CreateAt 117 } 118 119 func (o *IncomingWebhook) PreUpdate() { 120 o.UpdateAt = GetMillis() 121 } 122 123 // escapeControlCharsFromPayload escapes control chars (\n, \t) from a byte slice. 124 // Context: 125 // JSON strings are not supposed to contain control characters such as \n, \t, 126 // ... but some incoming webhooks might still send invalid JSON and we want to 127 // try to handle that. An example invalid JSON string from an incoming webhook 128 // might look like this (strings for both "text" and "fallback" attributes are 129 // invalid JSON strings because they contain unescaped newlines and tabs): 130 // `{ 131 // "text": "this is a test 132 // that contains a newline and tabs", 133 // "attachments": [ 134 // { 135 // "fallback": "Required plain-text summary of the attachment 136 // that contains a newline and tabs", 137 // "color": "#36a64f", 138 // ... 139 // "text": "Optional text that appears within the attachment 140 // that contains a newline and tabs", 141 // ... 142 // "thumb_url": "http://example.com/path/to/thumb.png" 143 // } 144 // ] 145 // }` 146 // This function will search for `"key": "value"` pairs, and escape \n, \t 147 // from the value. 148 func escapeControlCharsFromPayload(by []byte) []byte { 149 // we'll search for `"text": "..."` or `"fallback": "..."`, ... 150 keys := "text|fallback|pretext|author_name|title|value" 151 152 // the regexp reads like this: 153 // (?s): this flag let . match \n (default is false) 154 // "(keys)": we search for the keys defined above 155 // \s*:\s*: followed by 0..n spaces/tabs, a colon then 0..n spaces/tabs 156 // ": a double-quote 157 // (\\"|[^"])*: any number of times the `\"` string or any char but a double-quote 158 // ": a double-quote 159 r := `(?s)"(` + keys + `)"\s*:\s*"(\\"|[^"])*"` 160 re := regexp.MustCompile(r) 161 162 // the function that will escape \n and \t on the regexp matches 163 repl := func(b []byte) []byte { 164 if bytes.Contains(b, []byte("\n")) { 165 b = bytes.Replace(b, []byte("\n"), []byte("\\n"), -1) 166 } 167 if bytes.Contains(b, []byte("\t")) { 168 b = bytes.Replace(b, []byte("\t"), []byte("\\t"), -1) 169 } 170 171 return b 172 } 173 174 return re.ReplaceAllFunc(by, repl) 175 } 176 177 func decodeIncomingWebhookRequest(by []byte) (*IncomingWebhookRequest, error) { 178 decoder := json.NewDecoder(bytes.NewReader(by)) 179 var o IncomingWebhookRequest 180 err := decoder.Decode(&o) 181 if err == nil { 182 return &o, nil 183 } else { 184 return nil, err 185 } 186 } 187 188 func IncomingWebhookRequestFromJson(data io.Reader) (*IncomingWebhookRequest, *AppError) { 189 buf := new(bytes.Buffer) 190 buf.ReadFrom(data) 191 by := buf.Bytes() 192 193 // Try to decode the JSON data. Only if it fails, try to escape control 194 // characters from the strings contained in the JSON data. 195 o, err := decodeIncomingWebhookRequest(by) 196 if err != nil { 197 o, err = decodeIncomingWebhookRequest(escapeControlCharsFromPayload(by)) 198 if err != nil { 199 return nil, NewAppError("IncomingWebhookRequestFromJson", "Unable to parse incoming data", nil, err.Error(), http.StatusBadRequest) 200 } 201 } 202 203 o.Attachments = StringifySlackFieldValue(o.Attachments) 204 205 return o, nil 206 } 207 208 func (o *IncomingWebhookRequest) ToJson() string { 209 b, err := json.Marshal(o) 210 if err != nil { 211 return "" 212 } else { 213 return string(b) 214 } 215 }