Skip to content

Commit

Permalink
[MM-25677] Content-Type is optional (#14705) (#14720)
Browse files Browse the repository at this point in the history
* Content-Type is optional

mime.ParseMimeType returns and "no media type" error if the passed
string is empty.

Given that the Content-Type header is optional we shouldn't return an error
in that case, so we're fixing that allowing the users to call the webhook
without passing that header

* Include webhook id in the error message

Given that the number of webhooks could be big the user could
need the id to check which one of the multiple webhooks are failing
so include the id aids in that part
  • Loading branch information
ethervoid committed Jun 1, 2020
1 parent ed1576d commit 8bd90a9
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 25 deletions.
26 changes: 21 additions & 5 deletions web/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,22 @@ func incomingWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
r.ParseForm()

var err *model.AppError
var mediaType string
incomingWebhookPayload := &model.IncomingWebhookRequest{}
mediaType, _, mimeErr := mime.ParseMediaType(r.Header.Get("Content-Type"))
if mimeErr != nil && mimeErr != mime.ErrInvalidMediaParameter {
c.Err = model.NewAppError("incomingWebhook", "api.webhook.incoming.error", nil, mimeErr.Error(), http.StatusBadRequest)
return
contentType := r.Header.Get("Content-Type")
// Content-Type header is optional so could be empty
if contentType != "" {
var mimeErr error
mediaType, _, mimeErr = mime.ParseMediaType(contentType)
if mimeErr != nil && mimeErr != mime.ErrInvalidMediaParameter {
c.Err = model.NewAppError("incomingWebhook",
"api.webhook.incoming.error",
nil,
"webhook_id="+id+", error: "+mimeErr.Error(),
http.StatusBadRequest,
)
return
}
}

defer func() {
Expand All @@ -58,7 +69,12 @@ func incomingWebhook(c *Context, w http.ResponseWriter, r *http.Request) {
err := decoder.Decode(incomingWebhookPayload, r.PostForm)

if err != nil {
c.Err = model.NewAppError("incomingWebhook", "api.webhook.incoming.error", nil, err.Error(), http.StatusBadRequest)
c.Err = model.NewAppError("incomingWebhook",
"api.webhook.incoming.error",
nil,
"webhook_id="+id+", error: "+err.Error(),
http.StatusBadRequest,
)
return
}
} else {
Expand Down
44 changes: 24 additions & 20 deletions web/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,89 +40,93 @@ func TestIncomingWebhook(t *testing.T) {
payload := "payload={\"text\": \"test text\"}"
resp, err := http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload))
require.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

payload = "payload={\"text\": \"\"}"
resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload))
require.Nil(t, err)
assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - no text to post")
assert.NotEqual(t, http.StatusOK, resp.StatusCode, "should have errored - no text post")

payload = "payload={\"text\": \"test text\", \"channel\": \"junk\"}"
resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader(payload))
require.Nil(t, err)
assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - bad channel")
assert.NotEqual(t, http.StatusOK, resp.StatusCode, "should have errored - bad channel")

payload = "payload={\"text\": \"test text\"}"
resp, err = http.Post(ApiClient.Url+"/hooks/abc123", "application/x-www-form-urlencoded", strings.NewReader(payload))
require.Nil(t, err)
assert.True(t, resp.StatusCode != http.StatusOK, "should have errored - bad hook")
assert.NotEqual(t, http.StatusOK, resp.StatusCode, "should have errored - bad hook")

resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\"this is a test\"}"))
require.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

text := `this is a \"test\"
that contains a newline and a tab`
resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\""+text+"\"}"))
require.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"%s\"}", th.BasicChannel.Name)))
require.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"#%s\"}", th.BasicChannel.Name)))
require.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Post(url, "application/json", strings.NewReader(fmt.Sprintf("{\"text\":\"this is a test\", \"channel\":\"@%s\"}", th.BasicUser.Username)))
require.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader("payload={\"text\":\"this is a test\"}"))
require.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader("payload={\"text\":\""+text+"\"}"))
assert.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Post(url, "AppLicaTion/x-www-Form-urlencoded", strings.NewReader("payload={\"text\":\""+text+"\"}"))
assert.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Post(url, "application/x-www-form-urlencoded;charset=utf-8", strings.NewReader("payload={\"text\":\""+text+"\"}"))
assert.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Post(url, "application/x-www-form-urlencoded; charset=utf-8", strings.NewReader("payload={\"text\":\""+text+"\"}"))
assert.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Post(url, "application/x-www-form-urlencoded wrongtext", strings.NewReader("payload={\"text\":\""+text+"\"}"))
assert.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusBadRequest)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)

resp, err = http.Post(url, "application/json", strings.NewReader("{\"text\":\""+tooLongText+"\"}"))
require.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Post(url, "application/x-www-form-urlencoded", strings.NewReader("{\"text\":\""+tooLongText+"\"}"))
assert.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusBadRequest)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)

resp, err = http.Post(url, "application/json", strings.NewReader("payload={\"text\":\""+text+"\"}"))
assert.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusBadRequest)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)

payloadMultiPart := "------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"username\"\r\n\r\nwebhook-bot\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nthis is a test :tada:\r\n------WebKitFormBoundary7MA4YWxkTrZu0gW--"
resp, err = http.Post(ApiClient.Url+"/hooks/"+hook.Id, "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW", strings.NewReader(payloadMultiPart))
require.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusOK)
assert.Equal(t, http.StatusOK, resp.StatusCode)

resp, err = http.Post(url, "mimetype/wrong", strings.NewReader("payload={\"text\":\""+text+"\"}"))
assert.Nil(t, err)
assert.True(t, resp.StatusCode == http.StatusBadRequest)
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)

resp, err = http.Post(url, "", strings.NewReader("{\"text\":\""+text+"\"}"))
assert.Nil(t, err)
assert.Equal(t, http.StatusOK, resp.StatusCode)
})

t.Run("WebhookExperimentalReadOnly", func(t *testing.T) {
Expand Down

0 comments on commit 8bd90a9

Please sign in to comment.