From 9bd2e0c84fef66007bcab027883c01414b99b77c Mon Sep 17 00:00:00 2001
From: Robin Jarry <robin@jarry.cc>
Date: Wed, 12 Oct 2022 23:52:45 +0200
Subject: [PATCH] msgpart: factorize mime type and filename construction

Reduce code duplication.

Signed-off-by: Robin Jarry <robin@jarry.cc>
Acked-by: Moritz Poldrack <moritz@poldrack.dev>
---
 commands/msg/forward.go  |  2 +-
 commands/msg/recall.go   |  2 +-
 commands/msg/reply.go    |  2 +-
 commands/msgview/open.go |  3 +--
 commands/msgview/save.go |  7 +------
 lib/structure_helpers.go |  6 ++----
 models/models.go         | 16 ++++++++++++++++
 widgets/msgviewer.go     | 20 +++++++-------------
 8 files changed, 30 insertions(+), 28 deletions(-)

diff --git a/commands/msg/forward.go b/commands/msg/forward.go
index 4adfd12..78c2438 100644
--- a/commands/msg/forward.go
+++ b/commands/msg/forward.go
@@ -198,7 +198,7 @@ func (forward) Execute(aerc *widgets.Aerc, args []string) error {
 						continue
 					}
 					store.FetchBodyPart(msg.Uid, p, func(reader io.Reader) {
-						mime := fmt.Sprintf("%s/%s", bs.MIMEType, bs.MIMESubType)
+						mime := bs.FullMIMEType()
 						params := lib.SetUtf8Charset(bs.Params)
 						name, ok := params["name"]
 						if !ok {
diff --git a/commands/msg/recall.go b/commands/msg/recall.go
index c5585b0..866266f 100644
--- a/commands/msg/recall.go
+++ b/commands/msg/recall.go
@@ -204,7 +204,7 @@ func (Recall) Execute(aerc *widgets.Aerc, args []string) error {
 						continue
 					}
 					msg.FetchBodyPart(p, func(reader io.Reader) {
-						mime := fmt.Sprintf("%s/%s", bs.MIMEType, bs.MIMESubType)
+						mime := bs.FullMIMEType()
 						params := lib.SetUtf8Charset(bs.Params)
 						name, ok := params["name"]
 						if !ok {
diff --git a/commands/msg/reply.go b/commands/msg/reply.go
index 1baef83..f577a96 100644
--- a/commands/msg/reply.go
+++ b/commands/msg/reply.go
@@ -326,7 +326,7 @@ func addMimeType(msg *models.MessageInfo, part []int,
 	if err != nil {
 		return err
 	}
-	orig.MIMEType = fmt.Sprintf("%s/%s", bs.MIMEType, bs.MIMESubType)
+	orig.MIMEType = bs.FullMIMEType()
 	return nil
 }
 
diff --git a/commands/msgview/open.go b/commands/msgview/open.go
index bb22026..13bd4b1 100644
--- a/commands/msgview/open.go
+++ b/commands/msgview/open.go
@@ -1,7 +1,6 @@
 package msgview
 
 import (
-	"fmt"
 	"io"
 	"mime"
 	"os"
@@ -52,7 +51,7 @@ func (Open) Execute(aerc *widgets.Aerc, args []string) error {
 
 		// try to determine the correct extension based on mimetype
 		if part, err := mv.MessageView().BodyStructure().PartAtIndex(p.Index); err == nil {
-			mimeType = fmt.Sprintf("%s/%s", part.MIMEType, part.MIMESubType)
+			mimeType = part.FullMIMEType()
 			if exts, _ := mime.ExtensionsByType(mimeType); len(exts) > 0 {
 				extension = exts[0]
 			}
diff --git a/commands/msgview/save.go b/commands/msgview/save.go
index 993f427..4820ec0 100644
--- a/commands/msgview/save.go
+++ b/commands/msgview/save.go
@@ -215,12 +215,7 @@ func isAbsPath(path string) bool {
 // generateFilename tries to get the filename from the given part.
 // if that fails it will fallback to a generated one based on the date
 func generateFilename(part *models.BodyStructure) string {
-	var filename string
-	if fn, ok := part.DispositionParams["filename"]; ok {
-		filename = fn
-	} else if fn, ok := part.Params["name"]; ok {
-		filename = fn
-	}
+	filename := part.FileName()
 	// Some MUAs send attachments with names like /some/stupid/idea/happy.jpeg
 	// Assuming non hostile intent it does make sense to use just the last
 	// portion of the pathname as the filename for saving it.
diff --git a/lib/structure_helpers.go b/lib/structure_helpers.go
index 6f25e45..0189dc8 100644
--- a/lib/structure_helpers.go
+++ b/lib/structure_helpers.go
@@ -9,8 +9,7 @@ import (
 func FindPlaintext(bs *models.BodyStructure, path []int) []int {
 	for i, part := range bs.Parts {
 		cur := append(path, i+1) //nolint:gocritic // intentional append to different slice
-		if strings.ToLower(part.MIMEType) == "text" &&
-			strings.ToLower(part.MIMESubType) == "plain" {
+		if part.FullMIMEType() == "text/plain" {
 			return cur
 		}
 		if strings.ToLower(part.MIMEType) == "multipart" {
@@ -25,8 +24,7 @@ func FindPlaintext(bs *models.BodyStructure, path []int) []int {
 func FindCalendartext(bs *models.BodyStructure, path []int) []int {
 	for i, part := range bs.Parts {
 		cur := append(path, i+1) //nolint:gocritic // intentional append to different slice
-		if strings.ToLower(part.MIMEType) == "text" &&
-			strings.ToLower(part.MIMESubType) == "calendar" {
+		if part.FullMIMEType() == "text/calendar" {
 			return cur
 		}
 		if strings.ToLower(part.MIMEType) == "multipart" {
diff --git a/models/models.go b/models/models.go
index 820bc3f..b55024a 100644
--- a/models/models.go
+++ b/models/models.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"strings"
 	"time"
 
 	"github.com/emersion/go-message/mail"
@@ -175,6 +176,21 @@ func (bs *BodyStructure) PartAtIndex(index []int) (*BodyStructure, error) {
 	return bs.Parts[curidx].PartAtIndex(rest)
 }
 
+func (bs *BodyStructure) FullMIMEType() string {
+	mime := fmt.Sprintf("%s/%s", bs.MIMEType, bs.MIMESubType)
+	return strings.ToLower(mime)
+}
+
+func (bs *BodyStructure) FileName() string {
+	if filename, ok := bs.DispositionParams["filename"]; ok {
+		return filename
+	} else if filename, ok := bs.Params["name"]; ok {
+		// workaround golang not supporting RFC2231 besides ASCII and UTF8
+		return filename
+	}
+	return ""
+}
+
 type Envelope struct {
 	Date      time.Time
 	Subject   string
diff --git a/widgets/msgviewer.go b/widgets/msgviewer.go
index 19a28f0..70c079a 100644
--- a/widgets/msgviewer.go
+++ b/widgets/msgviewer.go
@@ -230,8 +230,7 @@ func createSwitcher(acct *AccountView, switcher *PartSwitcher,
 			if switcher.selected == -1 && pv.part.MIMEType != "multipart" {
 				switcher.selected = i
 			}
-			mime := strings.ToLower(pv.part.MIMEType) +
-				"/" + strings.ToLower(pv.part.MIMESubType)
+			mime := pv.part.FullMIMEType()
 			for idx, m := range conf.Viewer.Alternatives {
 				if m != mime {
 					continue
@@ -422,13 +421,9 @@ func (ps *PartSwitcher) Draw(ctx *ui.Context) {
 			style = ps.mv.uiConfig.GetStyleSelected(config.STYLE_DEFAULT)
 		}
 		ctx.Fill(0, y+i, ctx.Width(), 1, ' ', style)
-		name := fmt.Sprintf("%s/%s",
-			strings.ToLower(part.part.MIMEType),
-			strings.ToLower(part.part.MIMESubType))
-		if filename, ok := part.part.DispositionParams["filename"]; ok {
-			name += fmt.Sprintf(" (%s)", filename)
-		} else if filename, ok := part.part.Params["name"]; ok {
-			// workaround golang not supporting RFC2231 besides ASCII and UTF8
+		name := part.part.FullMIMEType()
+		filename := part.part.FileName()
+		if filename != "" {
 			name += fmt.Sprintf(" (%s)", filename)
 		}
 		ctx.Printf(len(part.index)*2, y+i, style, "%s", name)
@@ -547,8 +542,7 @@ func NewPartViewer(acct *AccountView, conf *config.AercConfig,
 
 	info := msg.MessageInfo()
 	for _, f := range conf.Filters {
-		mime := strings.ToLower(part.MIMEType) +
-			"/" + strings.ToLower(part.MIMESubType)
+		mime := part.FullMIMEType()
 		switch f.FilterType {
 		case config.FILTER_MIMETYPE:
 			if fnmatch.Match(f.Filter, mime, 0) {
@@ -813,8 +807,8 @@ func newNoFilterConfigured(pv *PartViewer) *ui.Grid {
 
 	uiConfig := pv.conf.Ui
 
-	noFilter := fmt.Sprintf(`No filter configured for this mimetype ('%s/%s')
-What would you like to do?`, pv.part.MIMEType, pv.part.MIMESubType)
+	noFilter := fmt.Sprintf(`No filter configured for this mimetype ('%s')
+What would you like to do?`, pv.part.FullMIMEType())
 	grid.AddChild(ui.NewText(noFilter,
 		uiConfig.GetStyle(config.STYLE_TITLE))).At(0, 0)
 	for i, action := range actions {