Skip to content

Commit fd6971d

Browse files
committed
B64EmbedImages init
1 parent f528df9 commit fd6971d

File tree

3 files changed

+117
-0
lines changed

3 files changed

+117
-0
lines changed

custom/conf/app.example.ini

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1704,6 +1704,9 @@ LEVEL = Info
17041704
;;
17051705
;; convert \r\n to \n for Sendmail
17061706
;SENDMAIL_CONVERT_CRLF = true
1707+
;;
1708+
;; convert links of attached images to inline images
1709+
;B64_EMBED_IMAGES = true
17071710

17081711
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
17091712
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

modules/setting/mailer.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Mailer struct {
2828
SendAsPlainText bool `ini:"SEND_AS_PLAIN_TEXT"`
2929
SubjectPrefix string `ini:"SUBJECT_PREFIX"`
3030
OverrideHeader map[string][]string `ini:"-"`
31+
B64EmbedImages bool `ini:"-"`
3132

3233
// SMTP sender
3334
Protocol string `ini:"PROTOCOL"`

services/mailer/mail.go

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ package mailer
66

77
import (
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+
366479
func 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

Comments
 (0)