2020-03-28 16:09:55 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2020-03-28 16:09:55 +03:00
package webhook
import (
2024-03-08 01:18:38 +03:00
"bytes"
"context"
2020-07-31 01:04:19 +03:00
"crypto/sha1"
2022-11-03 21:23:20 +03:00
"encoding/hex"
2020-03-28 16:09:55 +03:00
"fmt"
2024-03-08 01:18:38 +03:00
"net/http"
2021-11-16 21:18:25 +03:00
"net/url"
2020-03-28 16:09:55 +03:00
"regexp"
"strings"
2021-11-10 08:13:16 +03:00
webhook_model "code.gitea.io/gitea/models/webhook"
2020-03-28 16:09:55 +03:00
"code.gitea.io/gitea/modules/git"
2021-07-24 19:03:58 +03:00
"code.gitea.io/gitea/modules/json"
2020-03-28 16:09:55 +03:00
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
2021-11-16 21:18:25 +03:00
"code.gitea.io/gitea/modules/util"
2023-01-01 18:23:15 +03:00
webhook_module "code.gitea.io/gitea/modules/webhook"
2020-03-28 16:09:55 +03:00
)
2024-06-11 21:47:45 +03:00
func newMatrixRequest ( _ context . Context , w * webhook_model . Webhook , t * webhook_model . HookTask ) ( * http . Request , [ ] byte , error ) {
2024-03-08 01:18:38 +03:00
meta := & MatrixMeta { }
if err := json . Unmarshal ( [ ] byte ( w . Meta ) , meta ) ; err != nil {
return nil , nil , fmt . Errorf ( "GetMatrixPayload meta json: %w" , err )
}
2024-07-10 14:37:16 +03:00
var pc payloadConvertor [ MatrixPayload ] = matrixConvertor {
2024-03-08 01:18:38 +03:00
MsgType : messageTypeText [ meta . MessageType ] ,
}
2024-07-10 14:37:16 +03:00
payload , err := newPayload ( pc , [ ] byte ( t . PayloadContent ) , t . EventType )
2024-03-08 01:18:38 +03:00
if err != nil {
return nil , nil , err
}
body , err := json . MarshalIndent ( payload , "" , " " )
if err != nil {
return nil , nil , err
}
txnID , err := getMatrixTxnID ( body )
if err != nil {
return nil , nil , err
}
req , err := http . NewRequest ( http . MethodPut , w . URL + "/" + txnID , bytes . NewReader ( body ) )
if err != nil {
return nil , nil , err
}
req . Header . Set ( "Content-Type" , "application/json" )
return req , body , addDefaultHeaders ( req , [ ] byte ( w . Secret ) , t , body ) // likely useless, but has always been sent historially
}
2020-03-28 16:09:55 +03:00
const matrixPayloadSizeLimit = 1024 * 64
// MatrixMeta contains the Matrix metadata
type MatrixMeta struct {
HomeserverURL string ` json:"homeserver_url" `
Room string ` json:"room_id" `
MessageType int ` json:"message_type" `
}
var messageTypeText = map [ int ] string {
1 : "m.notice" ,
2 : "m.text" ,
}
// GetMatrixHook returns Matrix metadata
2021-11-10 08:13:16 +03:00
func GetMatrixHook ( w * webhook_model . Webhook ) * MatrixMeta {
2020-03-28 16:09:55 +03:00
s := & MatrixMeta { }
if err := json . Unmarshal ( [ ] byte ( w . Meta ) , s ) ; err != nil {
log . Error ( "webhook.GetMatrixHook(%d): %v" , w . ID , err )
}
return s
}
2022-11-03 21:23:20 +03:00
// MatrixPayload contains payload for a Matrix room
type MatrixPayload struct {
2020-03-28 16:09:55 +03:00
Body string ` json:"body" `
MsgType string ` json:"msgtype" `
Format string ` json:"format" `
FormattedBody string ` json:"formatted_body" `
Commits [ ] * api . PayloadCommit ` json:"io.gitea.commits,omitempty" `
}
2024-03-08 01:18:38 +03:00
type matrixConvertor struct {
MsgType string
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
func ( m matrixConvertor ) newPayload ( text string , commits ... * api . PayloadCommit ) ( MatrixPayload , error ) {
return MatrixPayload {
Body : getMessageBody ( text ) ,
MsgType : m . MsgType ,
Format : "org.matrix.custom.html" ,
FormattedBody : text ,
Commits : commits ,
} , nil
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
// Create implements payloadConvertor Create method
func ( m matrixConvertor ) Create ( p * api . CreatePayload ) ( MatrixPayload , error ) {
repoLink := htmlLinkFormatter ( p . Repo . HTMLURL , p . Repo . FullName )
2020-03-28 16:09:55 +03:00
refLink := MatrixLinkToRef ( p . Repo . HTMLURL , p . Ref )
text := fmt . Sprintf ( "[%s:%s] %s created by %s" , repoLink , refLink , p . RefType , p . Sender . UserName )
2024-03-08 01:18:38 +03:00
return m . newPayload ( text )
2020-03-28 16:09:55 +03:00
}
2020-09-05 05:57:13 +03:00
// Delete composes Matrix payload for delete a branch or tag.
2024-03-08 01:18:38 +03:00
func ( m matrixConvertor ) Delete ( p * api . DeletePayload ) ( MatrixPayload , error ) {
2023-05-26 04:04:48 +03:00
refName := git . RefName ( p . Ref ) . ShortName ( )
2024-03-08 01:18:38 +03:00
repoLink := htmlLinkFormatter ( p . Repo . HTMLURL , p . Repo . FullName )
2020-03-28 16:09:55 +03:00
text := fmt . Sprintf ( "[%s:%s] %s deleted by %s" , repoLink , refName , p . RefType , p . Sender . UserName )
2024-03-08 01:18:38 +03:00
return m . newPayload ( text )
2020-03-28 16:09:55 +03:00
}
2020-09-05 05:57:13 +03:00
// Fork composes Matrix payload for forked by a repository.
2024-03-08 01:18:38 +03:00
func ( m matrixConvertor ) Fork ( p * api . ForkPayload ) ( MatrixPayload , error ) {
baseLink := htmlLinkFormatter ( p . Forkee . HTMLURL , p . Forkee . FullName )
forkLink := htmlLinkFormatter ( p . Repo . HTMLURL , p . Repo . FullName )
2020-03-28 16:09:55 +03:00
text := fmt . Sprintf ( "%s is forked to %s" , baseLink , forkLink )
2024-03-08 01:18:38 +03:00
return m . newPayload ( text )
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
// Issue implements payloadConvertor Issue method
func ( m matrixConvertor ) Issue ( p * api . IssuePayload ) ( MatrixPayload , error ) {
text , _ , _ , _ := getIssuesPayloadInfo ( p , htmlLinkFormatter , true )
2020-03-28 16:09:55 +03:00
2024-03-08 01:18:38 +03:00
return m . newPayload ( text )
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
// IssueComment implements payloadConvertor IssueComment method
func ( m matrixConvertor ) IssueComment ( p * api . IssueCommentPayload ) ( MatrixPayload , error ) {
text , _ , _ := getIssueCommentPayloadInfo ( p , htmlLinkFormatter , true )
2020-03-28 16:09:55 +03:00
2024-03-08 01:18:38 +03:00
return m . newPayload ( text )
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
// Wiki implements payloadConvertor Wiki method
func ( m matrixConvertor ) Wiki ( p * api . WikiPayload ) ( MatrixPayload , error ) {
text , _ , _ := getWikiPayloadInfo ( p , htmlLinkFormatter , true )
2022-09-04 22:54:23 +03:00
2024-03-08 01:18:38 +03:00
return m . newPayload ( text )
2022-09-04 22:54:23 +03:00
}
2024-03-08 01:18:38 +03:00
// Release implements payloadConvertor Release method
func ( m matrixConvertor ) Release ( p * api . ReleasePayload ) ( MatrixPayload , error ) {
text , _ := getReleasePayloadInfo ( p , htmlLinkFormatter , true )
2020-03-28 16:09:55 +03:00
2024-03-08 01:18:38 +03:00
return m . newPayload ( text )
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
// Push implements payloadConvertor Push method
func ( m matrixConvertor ) Push ( p * api . PushPayload ) ( MatrixPayload , error ) {
2020-03-28 16:09:55 +03:00
var commitDesc string
2022-10-16 19:22:34 +03:00
if p . TotalCommits == 1 {
2020-03-28 16:09:55 +03:00
commitDesc = "1 commit"
} else {
2022-10-16 19:22:34 +03:00
commitDesc = fmt . Sprintf ( "%d commits" , p . TotalCommits )
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
repoLink := htmlLinkFormatter ( p . Repo . HTMLURL , p . Repo . FullName )
2020-03-28 16:09:55 +03:00
branchLink := MatrixLinkToRef ( p . Repo . HTMLURL , p . Ref )
text := fmt . Sprintf ( "[%s] %s pushed %s to %s:<br>" , repoLink , p . Pusher . UserName , commitDesc , branchLink )
// for each commit, generate a new line text
for i , commit := range p . Commits {
2024-03-08 01:18:38 +03:00
text += fmt . Sprintf ( "%s: %s - %s" , htmlLinkFormatter ( commit . URL , commit . ID [ : 7 ] ) , commit . Message , commit . Author . Name )
2020-03-28 16:09:55 +03:00
// add linebreak to each commit but the last
if i < len ( p . Commits ) - 1 {
text += "<br>"
}
}
2024-03-08 01:18:38 +03:00
return m . newPayload ( text , p . Commits ... )
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
// PullRequest implements payloadConvertor PullRequest method
func ( m matrixConvertor ) PullRequest ( p * api . PullRequestPayload ) ( MatrixPayload , error ) {
text , _ , _ , _ := getPullRequestPayloadInfo ( p , htmlLinkFormatter , true )
2020-03-28 16:09:55 +03:00
2024-03-08 01:18:38 +03:00
return m . newPayload ( text )
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
// Review implements payloadConvertor Review method
func ( m matrixConvertor ) Review ( p * api . PullRequestPayload , event webhook_module . HookEventType ) ( MatrixPayload , error ) {
senderLink := htmlLinkFormatter ( setting . AppURL + url . PathEscape ( p . Sender . UserName ) , p . Sender . UserName )
2020-03-28 16:09:55 +03:00
title := fmt . Sprintf ( "#%d %s" , p . Index , p . PullRequest . Title )
2024-03-08 01:18:38 +03:00
titleLink := htmlLinkFormatter ( p . PullRequest . HTMLURL , title )
repoLink := htmlLinkFormatter ( p . Repository . HTMLURL , p . Repository . FullName )
2020-03-28 16:09:55 +03:00
var text string
switch p . Action {
case api . HookIssueReviewed :
action , err := parseHookPullRequestEventType ( event )
if err != nil {
2024-03-08 01:18:38 +03:00
return MatrixPayload { } , err
2020-03-28 16:09:55 +03:00
}
2022-10-08 00:06:34 +03:00
text = fmt . Sprintf ( "[%s] Pull request review %s: %s by %s" , repoLink , action , titleLink , senderLink )
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
return m . newPayload ( text )
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
// Repository implements payloadConvertor Repository method
func ( m matrixConvertor ) Repository ( p * api . RepositoryPayload ) ( MatrixPayload , error ) {
senderLink := htmlLinkFormatter ( setting . AppURL + p . Sender . UserName , p . Sender . UserName )
repoLink := htmlLinkFormatter ( p . Repository . HTMLURL , p . Repository . FullName )
2020-03-28 16:09:55 +03:00
var text string
switch p . Action {
case api . HookRepoCreated :
text = fmt . Sprintf ( "[%s] Repository created by %s" , repoLink , senderLink )
case api . HookRepoDeleted :
text = fmt . Sprintf ( "[%s] Repository deleted by %s" , repoLink , senderLink )
}
2024-03-08 01:18:38 +03:00
return m . newPayload ( text )
2020-03-28 16:09:55 +03:00
}
2024-03-08 01:18:38 +03:00
func ( m matrixConvertor ) Package ( p * api . PackagePayload ) ( MatrixPayload , error ) {
senderLink := htmlLinkFormatter ( setting . AppURL + p . Sender . UserName , p . Sender . UserName )
packageLink := htmlLinkFormatter ( p . Package . HTMLURL , p . Package . Name )
2023-10-31 07:43:38 +03:00
var text string
switch p . Action {
case api . HookPackageCreated :
2023-11-17 14:17:33 +03:00
text = fmt . Sprintf ( "[%s] Package published by %s" , packageLink , senderLink )
2023-10-31 07:43:38 +03:00
case api . HookPackageDeleted :
2023-11-17 14:17:33 +03:00
text = fmt . Sprintf ( "[%s] Package deleted by %s" , packageLink , senderLink )
2023-10-31 07:43:38 +03:00
}
2024-03-08 01:18:38 +03:00
return m . newPayload ( text )
2020-03-28 16:09:55 +03:00
}
var urlRegex = regexp . MustCompile ( ` <a [^>]*?href="([^">]*?)">(.*?)</a> ` )
func getMessageBody ( htmlText string ) string {
htmlText = urlRegex . ReplaceAllString ( htmlText , "[$2]($1)" )
htmlText = strings . ReplaceAll ( htmlText , "<br>" , "\n" )
return htmlText
}
2022-11-03 21:23:20 +03:00
// getMatrixTxnID computes the transaction ID to ensure idempotency
func getMatrixTxnID ( payload [ ] byte ) ( string , error ) {
2020-03-28 16:09:55 +03:00
if len ( payload ) >= matrixPayloadSizeLimit {
2022-11-03 21:23:20 +03:00
return "" , fmt . Errorf ( "getMatrixTxnID: payload size %d > %d" , len ( payload ) , matrixPayloadSizeLimit )
2020-03-28 16:09:55 +03:00
}
2020-07-31 01:04:19 +03:00
h := sha1 . New ( )
_ , err := h . Write ( payload )
if err != nil {
return "" , err
}
2022-11-03 21:23:20 +03:00
return hex . EncodeToString ( h . Sum ( nil ) ) , nil
2020-07-31 01:04:19 +03:00
}
2024-03-08 01:18:38 +03:00
// MatrixLinkToRef Matrix-formatter link to a repo ref
func MatrixLinkToRef ( repoURL , ref string ) string {
refName := git . RefName ( ref ) . ShortName ( )
switch {
case strings . HasPrefix ( ref , git . BranchPrefix ) :
return htmlLinkFormatter ( repoURL + "/src/branch/" + util . PathEscapeSegments ( refName ) , refName )
case strings . HasPrefix ( ref , git . TagPrefix ) :
return htmlLinkFormatter ( repoURL + "/src/tag/" + util . PathEscapeSegments ( refName ) , refName )
default :
return htmlLinkFormatter ( repoURL + "/src/commit/" + util . PathEscapeSegments ( refName ) , refName )
}
}