Skip to content

Commit

Permalink
Merge branch 'main' into avoid-deprecation-notice
Browse files Browse the repository at this point in the history
  • Loading branch information
zeripath authored Jan 23, 2022
2 parents 18cdc0f + 3349fd8 commit 923e703
Show file tree
Hide file tree
Showing 24 changed files with 477 additions and 23 deletions.
1 change: 1 addition & 0 deletions docs/content/doc/features/webhooks.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ All event pushes are POST requests. The methods currently supported are:
- Microsoft Teams
- Feishu
- Wechatwork
- Packagist

### Event information

Expand Down
1 change: 1 addition & 0 deletions docs/content/doc/features/webhooks.zh-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ Gitea 的存储 webhook。这可以有存储库管路设定页 `/:username/:repo
- Microsoft Teams
- Feishu
- Wechatwork
- Packagist

## TBD
1 change: 1 addition & 0 deletions docs/content/doc/features/webhooks.zh-tw.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Gitea 的儲存庫事件支援 web hook。這可以有儲存庫管理員在設
- Microsoft Teams
- Feishu
- Wechatwork
- Packagist

### 事件資訊

Expand Down
1 change: 1 addition & 0 deletions models/webhook/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ const (
FEISHU HookType = "feishu"
MATRIX HookType = "matrix"
WECHATWORK HookType = "wechatwork"
PACKAGIST HookType = "packagist"
)

// HookStatus is the status of a web hook
Expand Down
41 changes: 41 additions & 0 deletions modules/public/mime_types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2022 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 public

import "strings"

// wellKnownMimeTypesLower comes from Golang's builtin mime package: `builtinTypesLower`, see the comment of detectWellKnownMimeType
var wellKnownMimeTypesLower = map[string]string{
".avif": "image/avif",
".css": "text/css; charset=utf-8",
".gif": "image/gif",
".htm": "text/html; charset=utf-8",
".html": "text/html; charset=utf-8",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "text/javascript; charset=utf-8",
".json": "application/json",
".mjs": "text/javascript; charset=utf-8",
".pdf": "application/pdf",
".png": "image/png",
".svg": "image/svg+xml",
".wasm": "application/wasm",
".webp": "image/webp",
".xml": "text/xml; charset=utf-8",

// well, there are some types missing from the builtin list
".txt": "text/plain; charset=utf-8",
}

// detectWellKnownMimeType will return the mime-type for a well-known file ext name
// The purpose of this function is to bypass the unstable behavior of Golang's mime.TypeByExtension
// mime.TypeByExtension would use OS's mime-type config to overwrite the well-known types (see its document).
// If the user's OS has incorrect mime-type config, it would make Gitea can not respond a correct Content-Type to browsers.
// For example, if Gitea returns `text/plain` for a `.js` file, the browser couldn't run the JS due to security reasons.
// detectWellKnownMimeType makes the Content-Type for well-known files stable.
func detectWellKnownMimeType(ext string) string {
ext = strings.ToLower(ext)
return wellKnownMimeTypesLower[ext]
}
11 changes: 11 additions & 0 deletions modules/public/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,15 @@ func parseAcceptEncoding(val string) map[string]bool {
return types
}

// setWellKnownContentType will set the Content-Type if the file is a well-known type.
// See the comments of detectWellKnownMimeType
func setWellKnownContentType(w http.ResponseWriter, file string) {
mimeType := detectWellKnownMimeType(filepath.Ext(file))
if mimeType != "" {
w.Header().Set("Content-Type", mimeType)
}
}

func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.FileSystem, file string) bool {
// use clean to keep the file is a valid path with no . or ..
f, err := fs.Open(path.Clean(file))
Expand Down Expand Up @@ -122,6 +131,8 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, fs http.Fi
return true
}

setWellKnownContentType(w, file)

serveContent(w, req, fi, fi.ModTime(), f)
return true
}
File renamed without changes.
29 changes: 9 additions & 20 deletions modules/public/static.go → modules/public/serve_static.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,12 @@ package public

import (
"bytes"
"compress/gzip"
"io"
"mime"
"net/http"
"os"
"path/filepath"
"time"

"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
)

Expand Down Expand Up @@ -66,24 +63,16 @@ func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modt
encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding"))
if encodings["gzip"] {
if cf, ok := fi.(*vfsgen۰CompressedFileInfo); ok {
rd := bytes.NewReader(cf.GzipBytes())
w.Header().Set("Content-Encoding", "gzip")
ctype := mime.TypeByExtension(filepath.Ext(fi.Name()))
if ctype == "" {
// read a chunk to decide between utf-8 text and binary
var buf [512]byte
grd, _ := gzip.NewReader(rd)
n, _ := io.ReadFull(grd, buf[:])
ctype = http.DetectContentType(buf[:n])
_, err := rd.Seek(0, io.SeekStart) // rewind to output whole file
if err != nil {
log.Error("rd.Seek error: %v", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
rdGzip := bytes.NewReader(cf.GzipBytes())
// all static files are managed by Gitea, so we can make sure every file has the correct ext name
// then we can get the correct Content-Type, we do not need to do http.DetectContentType on the decompressed data
mimeType := detectWellKnownMimeType(filepath.Ext(fi.Name()))
if mimeType == "" {
mimeType = "application/octet-stream"
}
w.Header().Set("Content-Type", ctype)
http.ServeContent(w, req, fi.Name(), modtime, rd)
w.Header().Set("Content-Type", mimeType)
w.Header().Set("Content-Encoding", "gzip")
http.ServeContent(w, req, fi.Name(), modtime, rdGzip)
return
}
}
Expand Down
2 changes: 1 addition & 1 deletion modules/setting/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func newWebhookService() {
Webhook.DeliverTimeout = sec.Key("DELIVER_TIMEOUT").MustInt(5)
Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool()
Webhook.AllowedHostList = sec.Key("ALLOWED_HOST_LIST").MustString("")
Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "matrix", "wechatwork"}
Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams", "feishu", "matrix", "wechatwork", "packagist"}
Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10)
Webhook.ProxyURL = sec.Key("PROXY_URL").MustString("")
if Webhook.ProxyURL != "" {
Expand Down
2 changes: 1 addition & 1 deletion modules/structs/hook.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ type CreateHookOptionConfig map[string]string
// CreateHookOption options when create a hook
type CreateHookOption struct {
// required: true
// enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,wechatwork
// enum: dingtalk,discord,gitea,gogs,msteams,slack,telegram,feishu,wechatwork,packagist
Type string `json:"type" binding:"Required"`
// required: true
Config CreateHookOptionConfig `json:"config" binding:"Required"`
Expand Down
4 changes: 4 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -1947,6 +1947,10 @@ settings.add_matrix_hook_desc = Integrate <a href="%s">Matrix</a> into your repo
settings.add_msteams_hook_desc = Integrate <a href="%s">Microsoft Teams</a> into your repository.
settings.add_feishu_hook_desc = Integrate <a href="%s">Feishu</a> into your repository.
settings.add_Wechat_hook_desc = Integrate <a href="%s">Wechatwork</a> into your repository.
settings.add_packagist_hook_desc = Integrate <a href="%s">Packagist</a> into your repository.
settings.packagist_username = Packagist username
settings.packagist_api_token = API token
settings.packagist_package_url = Packagist package URL
settings.deploy_keys = Deploy Keys
settings.add_deploy_key = Add Deploy Key
settings.deploy_key_desc = Deploy keys have read-only pull access to the repository.
Expand Down
Binary file added public/img/packagist.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
99 changes: 99 additions & 0 deletions routers/web/repo/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,59 @@ func WechatworkHooksNewPost(ctx *context.Context) {
ctx.Redirect(orCtx.Link)
}

// PackagistHooksNewPost response for creating packagist hook
func PackagistHooksNewPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewPackagistHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksNew"] = true
ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}}
ctx.Data["HookType"] = webhook.PACKAGIST

orCtx, err := getOrgRepoCtx(ctx)
if err != nil {
ctx.ServerError("getOrgRepoCtx", err)
return
}

if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}

meta, err := json.Marshal(&webhook_service.PackagistMeta{
Username: form.Username,
APIToken: form.APIToken,
PackageURL: form.PackageURL,
})
if err != nil {
ctx.ServerError("Marshal", err)
return
}

w := &webhook.Webhook{
RepoID: orCtx.RepoID,
URL: fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken)),
ContentType: webhook.ContentTypeJSON,
HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active,
Type: webhook.PACKAGIST,
Meta: string(meta),
OrgID: orCtx.OrgID,
IsSystemWebhook: orCtx.IsSystemWebhook,
}
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := webhook.CreateWebhook(db.DefaultContext, w); err != nil {
ctx.ServerError("CreateWebhook", err)
return
}

ctx.Flash.Success(ctx.Tr("repo.settings.add_hook_success"))
ctx.Redirect(orCtx.Link)
}

func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) {
ctx.Data["RequireHighlightJS"] = true

Expand Down Expand Up @@ -719,6 +772,8 @@ func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) {
ctx.Data["TelegramHook"] = webhook_service.GetTelegramHook(w)
case webhook.MATRIX:
ctx.Data["MatrixHook"] = webhook_service.GetMatrixHook(w)
case webhook.PACKAGIST:
ctx.Data["PackagistHook"] = webhook_service.GetPackagistHook(w)
}

ctx.Data["History"], err = w.History(1)
Expand Down Expand Up @@ -1137,6 +1192,50 @@ func WechatworkHooksEditPost(ctx *context.Context) {
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}

// PackagistHooksEditPost response for editing packagist hook
func PackagistHooksEditPost(ctx *context.Context) {
form := web.GetForm(ctx).(*forms.NewPackagistHookForm)
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["PageIsSettingsHooks"] = true
ctx.Data["PageIsSettingsHooksEdit"] = true

orCtx, w := checkWebhook(ctx)
if ctx.Written() {
return
}
ctx.Data["Webhook"] = w

if ctx.HasError() {
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
return
}

meta, err := json.Marshal(&webhook_service.PackagistMeta{
Username: form.Username,
APIToken: form.APIToken,
PackageURL: form.PackageURL,
})
if err != nil {
ctx.ServerError("Marshal", err)
return
}

w.Meta = string(meta)
w.URL = fmt.Sprintf("https://packagist.org/api/update-package?username=%s&apiToken=%s", url.QueryEscape(form.Username), url.QueryEscape(form.APIToken))
w.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil {
ctx.ServerError("UpdateEvent", err)
return
} else if err := webhook.UpdateWebhook(w); err != nil {
ctx.ServerError("UpdateWebhook", err)
return
}

ctx.Flash.Success(ctx.Tr("repo.settings.update_hook_success"))
ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
}

// TestWebhook test if web hook is work fine
func TestWebhook(ctx *context.Context) {
hookID := ctx.ParamsInt64(":id")
Expand Down
4 changes: 4 additions & 0 deletions routers/web/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/msteams/{id}", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
m.Post("/feishu/{id}", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
m.Post("/packagist/{id}", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost)
}, webhooksEnabled)

m.Group("/{configType:default-hooks|system-hooks}", func() {
Expand All @@ -462,6 +463,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Post("/packagist/new", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost)
})

m.Group("/auths", func() {
Expand Down Expand Up @@ -657,6 +659,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
m.Post("/packagist/new", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksNewPost)
m.Group("/{id}", func() {
m.Get("", repo.WebHooksEdit)
m.Post("/test", repo.TestWebhook)
Expand All @@ -672,6 +675,7 @@ func RegisterRoutes(m *web.Route) {
m.Post("/msteams/{id}", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksEditPost)
m.Post("/feishu/{id}", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksEditPost)
m.Post("/wechatwork/{id}", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksEditPost)
m.Post("/packagist/{id}", bindIgnErr(forms.NewPackagistHookForm{}), repo.PackagistHooksEditPost)
}, webhooksEnabled)

m.Group("/keys", func() {
Expand Down
14 changes: 14 additions & 0 deletions services/forms/repo_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,20 @@ func (f *NewWechatWorkHookForm) Validate(req *http.Request, errs binding.Errors)
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
}

// NewPackagistHookForm form for creating packagist hook
type NewPackagistHookForm struct {
Username string `binding:"Required"`
APIToken string `binding:"Required"`
PackageURL string `binding:"Required;ValidUrl"`
WebhookForm
}

// Validate validates the fields
func (f *NewPackagistHookForm) Validate(req *http.Request, errs binding.Errors) binding.Errors {
ctx := context.GetContext(req)
return middleware.Validate(errs, ctx.Data, f, ctx.Locale)
}

// .___
// | | ______ ________ __ ____
// | |/ ___// ___/ | \_/ __ \
Expand Down
Loading

0 comments on commit 923e703

Please sign in to comment.