@@ -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"
3134incoming_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
232236return 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+
235248actType ,actName ,tplName := actionToTemplate (ctx .Issue ,ctx .ActionType ,commentType ,reviewType )
236249
237250if actName != "new" {
@@ -363,6 +376,78 @@ func composeIssueCommentMessages(ctx *mailCommentContext, lang string, recipient
363376return 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 .Trace ("attachmentSrcToDataURI not possible: %v" ,err )// Not an error, just skip. This is probably an image from outside the gitea instance.
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 {
367452var path string
368453if issue .IsPull {