@@ -28,9 +28,12 @@ import (
2828 "code.gitea.io/gitea/modules/templates"
2929 "code.gitea.io/gitea/modules/timeutil"
3030 "code.gitea.io/gitea/modules/translation"
31+ "code.gitea.io/gitea/modules/util"
3132 incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
3233 sender_service "code.gitea.io/gitea/services/mailer/sender"
3334 "code.gitea.io/gitea/services/mailer/token"
35+
36+ "golang.org/x/net/html"
3437)
3538
3639const (
@@ -228,6 +231,11 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
228231 return nil , err
229232 }
230233
234+ loadedBody , err := ParseMailBody (string (body ))
235+ if err != nil {
236+ return nil , err
237+ }
238+
231239 actType , actName , tplName := actionToTemplate (ctx .Issue , ctx .ActionType , commentType , reviewType )
232240
233241 if actName != "new" {
@@ -279,12 +287,6 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
279287
280288 mailMeta ["Subject" ] = subject
281289
282- var mailBody bytes.Buffer
283-
284- if err := bodyTemplates .ExecuteTemplate (& mailBody , tplName , mailMeta ); err != nil {
285- log .Error ("ExecuteTemplate [%s]: %v" , tplName + "/body" , err )
286- }
287-
288290 // Make sure to compose independent messages to avoid leaking user emails
289291 msgID := generateMessageIDForIssue (ctx .Issue , ctx .Comment , ctx .ActionType )
290292 reference := generateMessageIDForIssue (ctx .Issue , nil , activities_model .ActionType (0 ))
@@ -308,6 +310,20 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
308310
309311 msgs := make ([]* sender_service.Message , 0 , len (recipients ))
310312 for _ , recipient := range recipients {
313+ var mailBody bytes.Buffer
314+
315+ bodyStr , err := loadedBody .RenderBody (ctx , recipient )
316+ if err != nil {
317+ log .Error ("RenderBody: %v" , err )
318+ continue
319+ }
320+ mailMeta ["Body" ] = template .HTML (bodyStr )
321+
322+ if err := bodyTemplates .ExecuteTemplate (& mailBody , tplName , mailMeta ); err != nil {
323+ log .Error ("ExecuteTemplate [%s]: %v" , tplName + "/body" , err )
324+ continue
325+ }
326+
311327 msg := sender_service .NewMessageFrom (
312328 recipient .Email ,
313329 fromDisplayName (ctx .Doer ),
@@ -548,3 +564,119 @@ func fromDisplayName(u *user_model.User) string {
548564 }
549565 return u .GetCompleteName ()
550566}
567+
568+ type LoadedMailBody struct {
569+ doc * html.Node
570+ allLinks []* html.Attribute
571+ }
572+
573+ func (ml * LoadedMailBody ) RenderBody (ctx * mailCommentContext , user * user_model.User ) (string , error ) {
574+ allLinks := make (map [string ]string )
575+
576+ for _ , l := range ml .allLinks {
577+ if v , ok := allLinks [l .Val ]; ok {
578+ l .Val = v
579+ continue
580+ }
581+
582+ securityURI , err := AttachmentSrcToSecurityURI (ctx , l .Val , user )
583+ if err != nil {
584+ continue
585+ }
586+
587+ allLinks [l .Val ] = securityURI
588+ l .Val = securityURI
589+ }
590+
591+ var buf bytes.Buffer
592+ err := html .Render (& buf , ml .doc )
593+ if err != nil {
594+ log .Error ("Failed to render modified HTML: %v" , err )
595+ return "" , err
596+ }
597+
598+ return buf .String (), nil
599+ }
600+
601+ func ParseMailBody (body string ) (* LoadedMailBody , error ) {
602+ doc , err := html .Parse (strings .NewReader (body ))
603+ if err != nil {
604+ log .Error ("Failed to parse HTML body: %v" , err )
605+ return nil , err
606+ }
607+
608+ var processNode func (* html.Node )
609+ allLins := make ([]* html.Attribute , 0 )
610+
611+ processNode = func (n * html.Node ) {
612+ if n .Type == html .ElementNode {
613+ if n .Data == "img" {
614+ for i , attr := range n .Attr {
615+ if attr .Key != "src" {
616+ continue
617+ }
618+
619+ if ! strings .HasPrefix (attr .Val , setting .AppURL ) { // external image
620+ continue
621+ }
622+
623+ allLins = append (allLins , & n .Attr [i ])
624+ }
625+ }
626+
627+ if n .Data == "a" {
628+ for i , attr := range n .Attr {
629+ if attr .Key != "href" {
630+ continue
631+ }
632+
633+ if ! strings .HasPrefix (attr .Val , setting .AppURL ) { // external link
634+ continue
635+ }
636+
637+ allLins = append (allLins , & n .Attr [i ])
638+ }
639+ }
640+ }
641+
642+ for c := n .FirstChild ; c != nil ; c = c .NextSibling {
643+ processNode (c )
644+ }
645+ }
646+
647+ processNode (doc )
648+
649+ return & LoadedMailBody {
650+ doc : doc ,
651+ allLinks : allLins ,
652+ }, nil
653+ }
654+
655+ func AttachmentSrcToSecurityURI (ctx context.Context , attachmentPath string , user * user_model.User ) (string , error ) {
656+ if ! strings .HasPrefix (attachmentPath , setting .AppURL ) { // external image
657+ return "" , fmt .Errorf ("external image" )
658+ }
659+
660+ parts := strings .Split (attachmentPath , "/attachments/" )
661+ if len (parts ) <= 1 {
662+ return "" , fmt .Errorf ("invalid attachment path: %s" , attachmentPath )
663+ }
664+
665+ attachmentUUID := parts [len (parts )- 1 ]
666+ attachment , err := repo_model .GetAttachmentByUUID (ctx , attachmentUUID )
667+ if err != nil {
668+ return "" , err
669+ }
670+
671+ payload , err := util .PackData (attachment .ID )
672+ if err != nil {
673+ return "" , err
674+ }
675+
676+ key , err := token .CreateToken (token .ReadAttachmentHandlerType , user , payload )
677+ if err != nil {
678+ return "" , err
679+ }
680+
681+ return strings .Replace (attachmentPath , attachmentUUID , key , 1 ), nil
682+ }
0 commit comments