From 26fb1fc34dab6ab0b2436cc094ae6a83b01a8758 Mon Sep 17 00:00:00 2001 From: Michal Horejsek Date: Fri, 16 Oct 2020 08:33:52 +0200 Subject: [PATCH] Sanizize mailbox name for exporting --- Changelog.md | 1 + internal/transfer/provider_eml_target.go | 4 ++-- internal/transfer/provider_mbox_target.go | 2 +- internal/transfer/utils.go | 22 +++++++++++++++++++++ internal/transfer/utils_test.go | 24 +++++++++++++++++++++++ 5 files changed, 50 insertions(+), 3 deletions(-) diff --git a/Changelog.md b/Changelog.md index a6738eaf..61a6276d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -8,6 +8,7 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) * GODT-749 Don't force PGP/Inline when sending plaintext messages. * GODT-764 Fix deadlock in integration tests for Import-Export. * GODT-662 Do not resume paused transfer progress after dismissing cancel popup. +* GODT-772 Sanitize mailbox names for exporting to follow OS restrictions. ### Changed * Bump crypto version to v0.0.0-20200818122824-ed5d25e28db8 diff --git a/internal/transfer/provider_eml_target.go b/internal/transfer/provider_eml_target.go index 64e33dca..89c03b3f 100644 --- a/internal/transfer/provider_eml_target.go +++ b/internal/transfer/provider_eml_target.go @@ -61,7 +61,7 @@ func (p *EMLProvider) TransferFrom(rules transferRules, progress *Progress, ch < func (p *EMLProvider) createFolders(rules transferRules) error { for rule := range rules.iterateActiveRules() { for _, mailbox := range rule.TargetMailboxes { - path := filepath.Join(p.root, mailbox.Name) + path := filepath.Join(p.root, sanitizeFileName(mailbox.Name)) if err := os.MkdirAll(path, os.ModePerm); err != nil { return err } @@ -71,7 +71,7 @@ func (p *EMLProvider) createFolders(rules transferRules) error { } func (p *EMLProvider) writeFile(msg Message) error { - fileName := filepath.Base(msg.ID) + fileName := sanitizeFileName(filepath.Base(msg.ID)) if filepath.Ext(fileName) != ".eml" { fileName += ".eml" } diff --git a/internal/transfer/provider_mbox_target.go b/internal/transfer/provider_mbox_target.go index 44f450f3..6251a70d 100644 --- a/internal/transfer/provider_mbox_target.go +++ b/internal/transfer/provider_mbox_target.go @@ -57,7 +57,7 @@ func (p *MBOXProvider) TransferFrom(rules transferRules, progress *Progress, ch func (p *MBOXProvider) writeMessage(msg Message) error { var multiErr error for _, mailbox := range msg.Targets { - mboxName := filepath.Base(mailbox.Name) + mboxName := sanitizeFileName(mailbox.Name) if !strings.HasSuffix(mboxName, ".mbox") { mboxName += ".mbox" } diff --git a/internal/transfer/utils.go b/internal/transfer/utils.go index 17bd62c7..afa71c7d 100644 --- a/internal/transfer/utils.go +++ b/internal/transfer/utils.go @@ -24,6 +24,7 @@ import ( "net/mail" "net/textproto" "path/filepath" + "runtime" "sort" "strings" @@ -139,3 +140,24 @@ func getMessageHeader(body []byte) (mail.Header, error) { } return mail.Header(header), nil } + +// sanitizeFileName replaces problematic special characters with underscore. +func sanitizeFileName(fileName string) string { + if len(fileName) == 0 { + return fileName + } + if runtime.GOOS != "windows" && (fileName[0] == '-' || fileName[0] == '.') { //nolint[goconst] + fileName = "_" + fileName[1:] + } + return strings.Map(func(r rune) rune { + switch r { + case '\\', '/', ':', '*', '?', '"', '<', '>', '|': + return '_' + case '[', ']', '(', ')', '{', '}', '^', '#', '%', '&', '!', '@', '+', '=', '\'', '~': + if runtime.GOOS != "windows" { + return '_' + } + } + return r + }, fileName) +} diff --git a/internal/transfer/utils_test.go b/internal/transfer/utils_test.go index fc2d83e0..1fa841ce 100644 --- a/internal/transfer/utils_test.go +++ b/internal/transfer/utils_test.go @@ -21,6 +21,7 @@ import ( "io/ioutil" "os" "path/filepath" + "runtime" "testing" r "github.com/stretchr/testify/require" @@ -188,3 +189,26 @@ Body r.Equal(t, header.Get("subject"), "Hello") r.Equal(t, header.Get("from"), "user@example.com") } + +func TestSanitizeFileName(t *testing.T) { + tests := map[string]string{ + "hello": "hello", + "a\\b/c:*?d\"<>|e": "a_b_c___d____e", + } + if runtime.GOOS == "darwin" || runtime.GOOS == "linux" { + tests[".hello"] = "_hello" + tests["-hello"] = "_hello" + } + if runtime.GOOS == "windows" { + tests["[hello]&@=~~"] = "_hello______" + } + + for path, wantPath := range tests { + path := path + wantPath := wantPath + t.Run(path, func(t *testing.T) { + gotPath := sanitizeFileName(path) + r.Equal(t, wantPath, gotPath) + }) + } +}