@@ -7,9 +7,11 @@ package mailer
77import (
88 "bytes"
99 "context"
10+ "encoding/base64"
1011 "fmt"
1112 "html/template"
1213 "mime"
14+ "net/http"
1315 "regexp"
1416 "strconv"
1517 "strings"
@@ -26,11 +28,13 @@ import (
2628 "code.gitea.io/gitea/modules/markup"
2729 "code.gitea.io/gitea/modules/markup/markdown"
2830 "code.gitea.io/gitea/modules/setting"
31+ "code.gitea.io/gitea/modules/storage"
2932 "code.gitea.io/gitea/modules/timeutil"
3033 "code.gitea.io/gitea/modules/translation"
3134 incoming_payload "code.gitea.io/gitea/services/mailer/incoming/payload"
3235 "code.gitea.io/gitea/services/mailer/token"
3336
37+ "golang.org/x/net/html"
3438 "gopkg.in/gomail.v2"
3539)
3640
@@ -232,6 +236,15 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
232236 return nil , err
233237 }
234238
239+ if setting .MailService .Base64EmbedImages {
240+ bodyStr := string (body )
241+ bodyStr , err = inlineImages (bodyStr , ctx )
242+ if err != nil {
243+ return nil , err
244+ }
245+ body = template .HTML (bodyStr )
246+ }
247+
235248 actType , actName , tplName := actionToTemplate (ctx .Issue , ctx .ActionType , commentType , reviewType )
236249
237250 if actName != "new" {
@@ -363,6 +376,85 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
363376 return msgs , nil
364377}
365378
379+ func inlineImages (body string , ctx * mailCommentContext ) (string , error ) {
380+ doc , err := html .Parse (strings .NewReader (body ))
381+ if err != nil {
382+ log .Error ("Failed to parse HTML body: %v" , err )
383+ return "" , err
384+ }
385+
386+ var processNode func (* html.Node )
387+ processNode = func (n * html.Node ) {
388+ if n .Type == html .ElementNode {
389+ if n .Data == "img" {
390+ // Process <img> tags
391+ for i , attr := range n .Attr {
392+ if attr .Key == "src" {
393+ attachmentPath := attr .Val
394+ // Read the attachment file and encode it as base64
395+ dataURI , err := attachmentSrcToDataURI (attachmentPath , ctx )
396+ if err != nil {
397+ log .Error ("attachmentSrcToDataURI failed: %v" , err )
398+ continue
399+ }
400+ log .Trace ("Old value of src attribute: %s, new value (first 100 characters): %s" , attr .Val , dataURI [:100 ])
401+ n .Attr [i ].Val = dataURI
402+ }
403+ }
404+ }
405+ }
406+
407+ if n .FirstChild != nil {
408+ log .Trace ("Processing child nodes of <%s>" , n .Data )
409+ }
410+ for c := n .FirstChild ; c != nil ; c = c .NextSibling {
411+ processNode (c )
412+ }
413+ }
414+
415+ processNode (doc )
416+
417+ var buf bytes.Buffer
418+ err = html .Render (& buf , doc )
419+ if err != nil {
420+ log .Error ("Failed to render modified HTML: %v" , err )
421+ return "" , err
422+ }
423+ return buf .String (), nil
424+ }
425+
426+ // Helper function to convert attachment source to data URI
427+
428+ func attachmentSrcToDataURI (attachmentPath string , ctx * mailCommentContext ) (string , error ) {
429+ parts := strings .Split (attachmentPath , "/attachments/" )
430+ if len (parts ) <= 1 {
431+ return "" , fmt .Errorf ("Invalid attachment path: %s" , attachmentPath )
432+ }
433+
434+ attachmentUUID := parts [len (parts )- 1 ]
435+ attachment , err := repo_model .GetAttachmentByUUID (ctx , attachmentUUID )
436+ if err != nil {
437+ return "" , err
438+ }
439+
440+ fr , err := storage .Attachments .Open (attachment .RelativePath ())
441+ if err != nil {
442+ return "" , err
443+ }
444+ defer fr .Close ()
445+
446+ content := make ([]byte , attachment .Size )
447+ if _ , err := fr .Read (content ); err != nil {
448+ return "" , err
449+ }
450+
451+ mimeType := http .DetectContentType (content )
452+ encoded := base64 .StdEncoding .EncodeToString (content )
453+ dataURI := fmt .Sprintf ("data:%s;base64,%s" , mimeType , encoded )
454+
455+ return dataURI , nil
456+ }
457+
366458func generateMessageIDForIssue (issue * issues_model.Issue , comment * issues_model.Comment , actionType activities_model.ActionType ) string {
367459 var path string
368460 if issue .IsPull {
0 commit comments