diff --git a/cmd/archive.go b/cmd/archive.go index c6282790b..9f5be8d7d 100644 --- a/cmd/archive.go +++ b/cmd/archive.go @@ -244,6 +244,10 @@ func (a *App) compileArchiveCampaigns(camps []models.Campaign) ([]manager.Campai ) for _, c := range camps { camp := c + + // Resolve data-embed images to media URLs as cid: cannot resolve on a web page. + a.manager.ApplyArchiveImages(&camp) + if err := camp.CompileTemplate(a.manager.TemplateFuncs(&camp)); err != nil { a.log.Printf("error compiling template: %v", err) return nil, echo.NewHTTPError(http.StatusInternalServerError, a.i18n.T("public.errorFetchingCampaign")) diff --git a/cmd/manager_store.go b/cmd/manager_store.go index d2972939b..c704737e2 100644 --- a/cmd/manager_store.go +++ b/cmd/manager_store.go @@ -128,6 +128,16 @@ func (s *store) GetInlineAttachmentByFilename(filename string) (models.Attachmen }, cid, nil } +// GetMediaURLByFilename returns the public URL of a media item by filename. +func (s *store) GetMediaURLByFilename(filename string) (string, error) { + m, err := s.core.GetMedia(0, "", filename, s.media) + if err != nil { + return "", err + } + + return m.URL, nil +} + // CreateLink registers a URL with a UUID for tracking clicks and returns the UUID. func (s *store) CreateLink(url string) (string, error) { // Create a new UUID for the URL. If the URL already exists in the DB diff --git a/cmd/public.go b/cmd/public.go index b81e51ba7..803ef1052 100644 --- a/cmd/public.go +++ b/cmd/public.go @@ -174,6 +174,9 @@ func (a *App) ViewCampaignMessage(c echo.Context) error { makeMsgTpl(a.i18n.T("public.errorTitle"), "", a.i18n.Ts("public.errorFetchingCampaign"))) } + // Resolve data-embed images to media URLs as cid: cannot resolve on a web page. + a.manager.ApplyArchiveImages(&camp) + // Compile the template. if err := camp.CompileTemplate(a.manager.TemplateFuncs(&camp)); err != nil { a.log.Printf("error compiling template: %v", err) diff --git a/internal/manager/manager.go b/internal/manager/manager.go index 643187b15..ae08f6443 100644 --- a/internal/manager/manager.go +++ b/internal/manager/manager.go @@ -52,6 +52,7 @@ type Store interface { GetCampaign(campID int) (*models.Campaign, error) GetAttachment(mediaID int) (models.Attachment, error) GetInlineAttachmentByFilename(filename string) (models.Attachment, string, error) + GetMediaURLByFilename(filename string) (string, error) UpdateCampaignStatus(campID int, status string) error UpdateCampaignCounts(campID int, toSend int, sent int, lastSubID int) error CreateLink(url string) (string, error) @@ -766,6 +767,51 @@ func (m *Manager) applyInlineImages(body string, cache map[string]string) (strin return out, atts } +// ApplyArchiveImages resolves any tags in the campaign +// body and template body to public media URLs. The archive is rendered as a +// standalone HTML page and not a MIME message, so cid: cannot resolve there. +func (m *Manager) ApplyArchiveImages(c *models.Campaign) { + if c.ContentType == models.CampaignContentTypePlain { + return + } + + cache := make(map[string]string) + c.Body = m.applyArchiveImages(c.Body, cache) + c.TemplateBody = m.applyArchiveImages(c.TemplateBody, cache) +} + +func (m *Manager) applyArchiveImages(body string, cache map[string]string) string { + if !strings.Contains(body, attribInlineEmbed) { + return body + } + + return reInlineImage.ReplaceAllStringFunc(body, func(tag string) string { + src := extractSrc(tag) + if src == "" || strings.HasPrefix(strings.ToLower(src), "cid:") { + return tag + } + + fname := filenameFromSrc(src) + if fname == "" { + return tag + } + + u, ok := cache[src] + if !ok { + s, err := m.store.GetMediaURLByFilename(fname) + if err != nil { + m.log.Printf("archive image %q not resolved: %v", src, err) + } + u = s + cache[src] = u + } + if u == "" { + return tag + } + return reImgSrc.ReplaceAllString(tag, `${1}src="`+u+`"`) + }) +} + // MakeContentID returns a standard `Content-ID` value (without the angle brackets). func MakeContentID(key string) string { sum := sha1.Sum([]byte(key))