From a2a006a5d5a542c5c31bdce4647d2401eab88475 Mon Sep 17 00:00:00 2001
From: Simon <simon@hilchenba.ch>
Date: Sun, 5 May 2019 20:09:02 +0200
Subject: [PATCH] Add GET requests to webhook (#6771)

* Add GET requests to webhook

* make fmt

* Handle invalid http method on webhook

* Uppercase http method in webhook

* Rename v85.go to v86.go

* make fmt
---
 models/migrations/migrations.go            |  2 +
 models/migrations/v86.go                   | 17 +++++++++
 models/webhook.go                          | 43 ++++++++++++++--------
 modules/auth/repo_form.go                  |  1 +
 options/locale/locale_en-US.ini            |  1 +
 public/js/index.js                         |  9 +++++
 routers/repo/webhook.go                    |  1 +
 templates/repo/settings/webhook/gitea.tmpl | 12 ++++++
 8 files changed, 71 insertions(+), 15 deletions(-)
 create mode 100644 models/migrations/v86.go

diff --git a/models/migrations/migrations.go b/models/migrations/migrations.go
index 1d8cf65785..b86c20576f 100644
--- a/models/migrations/migrations.go
+++ b/models/migrations/migrations.go
@@ -225,6 +225,8 @@ var migrations = []Migration{
 	NewMigration("add table to store original imported gpg keys", addGPGKeyImport),
 	// v85 -> v86
 	NewMigration("hash application token", hashAppToken),
+	// v86 -> v87
+	NewMigration("add http method to webhook", addHTTPMethodToWebhook),
 }
 
 // Migrate database to current version
diff --git a/models/migrations/v86.go b/models/migrations/v86.go
new file mode 100644
index 0000000000..492a08c71e
--- /dev/null
+++ b/models/migrations/v86.go
@@ -0,0 +1,17 @@
+// Copyright 2019 The Gitea Authors. All rights reserved.
+// Use of this source code is governed by a MIT-style
+// license that can be found in the LICENSE file.
+
+package migrations
+
+import (
+	"github.com/go-xorm/xorm"
+)
+
+func addHTTPMethodToWebhook(x *xorm.Engine) error {
+	type Webhook struct {
+		HTTPMethod string `xorm:"http_method DEFAULT 'POST'"`
+	}
+
+	return x.Sync2(new(Webhook))
+}
diff --git a/models/webhook.go b/models/webhook.go
index 9be89241a4..8a7350bb6e 100644
--- a/models/webhook.go
+++ b/models/webhook.go
@@ -13,6 +13,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
+	"net/http"
 	"strings"
 	"time"
 
@@ -105,6 +106,7 @@ type Webhook struct {
 	OrgID        int64  `xorm:"INDEX"`
 	URL          string `xorm:"url TEXT"`
 	Signature    string `xorm:"TEXT"`
+	HTTPMethod   string `xorm:"http_method"`
 	ContentType  HookContentType
 	Secret       string `xorm:"TEXT"`
 	Events       string `xorm:"TEXT"`
@@ -553,6 +555,7 @@ type HookTask struct {
 	Signature       string `xorm:"TEXT"`
 	api.Payloader   `xorm:"-"`
 	PayloadContent  string `xorm:"TEXT"`
+	HTTPMethod      string `xorm:"http_method"`
 	ContentType     HookContentType
 	EventType       HookEventType
 	IsSSL           bool
@@ -707,6 +710,7 @@ func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType,
 		URL:         w.URL,
 		Signature:   signature,
 		Payloader:   payloader,
+		HTTPMethod:  w.HTTPMethod,
 		ContentType: w.ContentType,
 		EventType:   event,
 		IsSSL:       w.IsSSL,
@@ -751,9 +755,32 @@ func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payl
 
 func (t *HookTask) deliver() {
 	t.IsDelivered = true
+	t.RequestInfo = &HookRequest{
+		Headers: map[string]string{},
+	}
+	t.ResponseInfo = &HookResponse{
+		Headers: map[string]string{},
+	}
 
 	timeout := time.Duration(setting.Webhook.DeliverTimeout) * time.Second
-	req := httplib.Post(t.URL).SetTimeout(timeout, timeout).
+
+	var req *httplib.Request
+	if t.HTTPMethod == http.MethodPost {
+		req = httplib.Post(t.URL)
+		switch t.ContentType {
+		case ContentTypeJSON:
+			req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
+		case ContentTypeForm:
+			req.Param("payload", t.PayloadContent)
+		}
+	} else if t.HTTPMethod == http.MethodGet {
+		req = httplib.Get(t.URL).Param("payload", t.PayloadContent)
+	} else {
+		t.ResponseInfo.Body = fmt.Sprintf("Invalid http method: %v", t.HTTPMethod)
+		return
+	}
+
+	req = req.SetTimeout(timeout, timeout).
 		Header("X-Gitea-Delivery", t.UUID).
 		Header("X-Gitea-Event", string(t.EventType)).
 		Header("X-Gitea-Signature", t.Signature).
@@ -764,25 +791,11 @@ func (t *HookTask) deliver() {
 		HeaderWithSensitiveCase("X-GitHub-Event", string(t.EventType)).
 		SetTLSClientConfig(&tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify})
 
-	switch t.ContentType {
-	case ContentTypeJSON:
-		req = req.Header("Content-Type", "application/json").Body(t.PayloadContent)
-	case ContentTypeForm:
-		req.Param("payload", t.PayloadContent)
-	}
-
 	// Record delivery information.
-	t.RequestInfo = &HookRequest{
-		Headers: map[string]string{},
-	}
 	for k, vals := range req.Headers() {
 		t.RequestInfo.Headers[k] = strings.Join(vals, ",")
 	}
 
-	t.ResponseInfo = &HookResponse{
-		Headers: map[string]string{},
-	}
-
 	defer func() {
 		t.Delivered = time.Now().UnixNano()
 		if t.IsSucceed {
diff --git a/modules/auth/repo_form.go b/modules/auth/repo_form.go
index fd6891288d..f48f2f5e66 100644
--- a/modules/auth/repo_form.go
+++ b/modules/auth/repo_form.go
@@ -196,6 +196,7 @@ func (f WebhookForm) ChooseEvents() bool {
 // NewWebhookForm form for creating web hook
 type NewWebhookForm struct {
 	PayloadURL  string `binding:"Required;ValidUrl"`
+	HTTPMethod  string `binding:"Required;In(POST,GET)"`
 	ContentType int    `binding:"Required"`
 	Secret      string
 	WebhookForm
diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini
index 4e2ae51538..9af1f80669 100644
--- a/options/locale/locale_en-US.ini
+++ b/options/locale/locale_en-US.ini
@@ -1192,6 +1192,7 @@ settings.githook_content = Hook Content
 settings.update_githook = Update Hook
 settings.add_webhook_desc = Gitea will send <code>POST</code> requests with a specified content type to the target URL. Read more in the <a target="_blank" rel="noopener noreferrer" href="%s">webhooks guide</a>.
 settings.payload_url = Target URL
+settings.http_method = HTTP Method
 settings.content_type = POST Content Type
 settings.secret = Secret
 settings.slack_username = Username
diff --git a/public/js/index.js b/public/js/index.js
index 062ed7ce4d..9a9052eba7 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -1430,6 +1430,15 @@ function initWebhook() {
         }
     });
 
+    var updateContentType = function () {
+        var visible = $('#http_method').val() === 'POST';
+        $('#content_type').parent().parent()[visible ? 'show' : 'hide']();
+    };
+    updateContentType();
+    $('#http_method').change(function () {
+        updateContentType();
+    });
+
     // Test delivery
     $('#test-delivery').click(function () {
         var $this = $(this);
diff --git a/routers/repo/webhook.go b/routers/repo/webhook.go
index 6cf636f247..2e76cbfe02 100644
--- a/routers/repo/webhook.go
+++ b/routers/repo/webhook.go
@@ -176,6 +176,7 @@ func WebHooksNewPost(ctx *context.Context, form auth.NewWebhookForm) {
 	w := &models.Webhook{
 		RepoID:       orCtx.RepoID,
 		URL:          form.PayloadURL,
+		HTTPMethod:   form.HTTPMethod,
 		ContentType:  contentType,
 		Secret:       form.Secret,
 		HookEvent:    ParseHookEvent(form.WebhookForm),
diff --git a/templates/repo/settings/webhook/gitea.tmpl b/templates/repo/settings/webhook/gitea.tmpl
index 605022256d..ff52158cb3 100644
--- a/templates/repo/settings/webhook/gitea.tmpl
+++ b/templates/repo/settings/webhook/gitea.tmpl
@@ -6,6 +6,18 @@
 			<label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label>
 			<input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required>
 		</div>
+		<div class="field">
+			<label>{{.i18n.Tr "repo.settings.http_method"}}</label>
+			<div class="ui selection dropdown">
+				<input type="hidden" id="http_method" name="http_method" value="{{if .Webhook.HTTPMethod}}{{.Webhook.HTTPMethod}}{{else}}POST{{end}}">
+				<div class="default text"></div>
+				<i class="dropdown icon"></i>
+				<div class="menu">
+					<div class="item" data-value="POST">POST</div>
+					<div class="item" data-value="GET">GET</div>
+				</div>
+			</div>
+		</div>
 		<div class="field">
 			<label>{{.i18n.Tr "repo.settings.content_type"}}</label>
 			<div class="ui selection dropdown">