@@ -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,78 @@ 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+ for i , attr := range n .Attr {
391+ if attr .Key == "src" {
392+ attachmentPath := attr .Val
393+ dataURI , err := attachmentSrcToDataURI (attachmentPath , ctx )
394+ if err != nil {
395+ log .Error ("attachmentSrcToDataURI failed: %v" , err )
396+ continue
397+ }
398+ log .Trace ("Old value of src attribute: %s, new value (first 100 characters): %s" , attr .Val , dataURI [:100 ])
399+ n .Attr [i ].Val = dataURI
400+ }
401+ }
402+ }
403+ }
404+
405+ for c := n .FirstChild ; c != nil ; c = c .NextSibling {
406+ processNode (c )
407+ }
408+ }
409+
410+ processNode (doc )
411+
412+ var buf bytes.Buffer
413+ err = html .Render (& buf , doc )
414+ if err != nil {
415+ log .Error ("Failed to render modified HTML: %v" , err )
416+ return "" , err
417+ }
418+ return buf .String (), nil
419+ }
420+
421+ func attachmentSrcToDataURI (attachmentPath string , ctx * mailCommentContext ) (string , error ) {
422+ parts := strings .Split (attachmentPath , "/attachments/" )
423+ if len (parts ) <= 1 {
424+ return "" , fmt .Errorf ("invalid attachment path: %s" , attachmentPath )
425+ }
426+
427+ attachmentUUID := parts [len (parts )- 1 ]
428+ attachment , err := repo_model .GetAttachmentByUUID (ctx , attachmentUUID )
429+ if err != nil {
430+ return "" , err
431+ }
432+
433+ fr , err := storage .Attachments .Open (attachment .RelativePath ())
434+ if err != nil {
435+ return "" , err
436+ }
437+ defer fr .Close ()
438+
439+ content := make ([]byte , attachment .Size )
440+ if _ , err := fr .Read (content ); err != nil {
441+ return "" , err
442+ }
443+
444+ mimeType := http .DetectContentType (content )
445+ encoded := base64 .StdEncoding .EncodeToString (content )
446+ dataURI := fmt .Sprintf ("data:%s;base64,%s" , mimeType , encoded )
447+
448+ return dataURI , nil
449+ }
450+
366451func generateMessageIDForIssue (issue * issues_model.Issue , comment * issues_model.Comment , actionType activities_model.ActionType ) string {
367452 var path string
368453 if issue .IsPull {
0 commit comments