@@ -6,10 +6,14 @@ package mailer
66
77import (
88 "bytes"
9+ "code.gitea.io/gitea/modules/storage"
910 "context"
11+ "encoding/base64"
1012 "fmt"
13+ "golang.org/x/net/html"
1114 "html/template"
1215 "mime"
16+ "net/http"
1317 "regexp"
1418 "strconv"
1519 "strings"
@@ -232,6 +236,15 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
232236 return nil , err
233237 }
234238
239+ if setting .MailService .B64EmbedImages {
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,106 @@ 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+ // Extract the UUID from the attachment path
430+ parts := strings .Split (attachmentPath , "/attachments/" )
431+ attachmentUUID := ""
432+ if len (parts ) > 1 {
433+ attachmentUUID = parts [1 ]
434+ } else {
435+ return "" , fmt .Errorf ("Invalid attachment path: %s" , attachmentPath )
436+ }
437+ // Retrieve the attachment using the UUID
438+ attachment , err := repo_model .GetAttachmentByUUID (ctx , attachmentUUID )
439+ log .Trace ("attachmentUUID: %s" , attachmentUUID )
440+ if err != nil {
441+ return "" , err
442+ }
443+
444+ //log attachment.DownloadPath
445+ log .Trace ("attachment.DownloadURL(): %s" , attachment .DownloadURL ())
446+
447+ fr , err := storage .Attachments .Open (attachment .RelativePath ())
448+ if err != nil {
449+ return "" , err
450+ }
451+ defer func (fr storage.Object ) {
452+ err := fr .Close ()
453+ if err != nil {
454+ }
455+ }(fr )
456+
457+ //fr is io.ReadSeeker
458+ //get this as bytes:
459+ content := make ([]byte , attachment .Size )
460+ _ , err = fr .Read (content )
461+ if err != nil {
462+ return "" , err
463+ }
464+
465+ // Detect the MIME type
466+ mimeType := http .DetectContentType (content )
467+
468+ log .Trace ("MIME type: %s" , mimeType )
469+
470+ // Encode the content to base64
471+ encoded := base64 .StdEncoding .EncodeToString (content )
472+ log .Trace ("First 100 characters of encoded content: %s" , encoded [:100 ])
473+
474+ // Construct the data URI
475+ dataURI := fmt .Sprintf ("data:%s;base64,%s" , mimeType , encoded )
476+ return dataURI , nil
477+ }
478+
366479func generateMessageIDForIssue (issue * issues_model.Issue , comment * issues_model.Comment , actionType activities_model.ActionType ) string {
367480 var path string
368481 if issue .IsPull {
0 commit comments