From fe765cd49e6a55fb1637488e50b8407769ed566e Mon Sep 17 00:00:00 2001 From: Winni Neessen Date: Tue, 26 Nov 2024 16:17:34 +0100 Subject: [PATCH] Add filename sanitization tests for attachments Introduced tests to validate filename sanitization for attachments, ensuring disallowed characters are handled correctly. These tests cover various scenarios, including different character sets such as Japanese, Chinese, and Cyrillic. --- msgwriter_test.go | 76 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/msgwriter_test.go b/msgwriter_test.go index a3d2e56..31aae21 100644 --- a/msgwriter_test.go +++ b/msgwriter_test.go @@ -10,6 +10,7 @@ import ( "fmt" "io" "mime" + "os" "runtime" "strings" "testing" @@ -304,6 +305,81 @@ func TestMsgWriter_addFiles(t *testing.T) { charset: CharsetUTF8, encoder: getEncoder(EncodingQP), } + tests := []struct { + name string + filename string + expect string + }{ + {"normal US-ASCII filename", "test.txt", "test.txt"}, + {"normal US-ASCII filename with space", "test file.txt", "test file.txt"}, + {"filename with new lines", "test\r\n.txt", "test__.txt"}, + {"filename with disallowed character:\x22", "test\x22.txt", "test_.txt"}, + {"filename with disallowed character:\x2f", "test\x2f.txt", "test_.txt"}, + {"filename with disallowed character:\x3a", "test\x3a.txt", "test_.txt"}, + {"filename with disallowed character:\x3c", "test\x3c.txt", "test_.txt"}, + {"filename with disallowed character:\x3e", "test\x3e.txt", "test_.txt"}, + {"filename with disallowed character:\x3f", "test\x3f.txt", "test_.txt"}, + {"filename with disallowed character:\x5c", "test\x5c.txt", "test_.txt"}, + {"filename with disallowed character:\x7c", "test\x7c.txt", "test_.txt"}, + {"filename with disallowed character:\x7f", "test\x7f.txt", "test_.txt"}, + { + "japanese characters filename", "添付ファイル.txt", + "=?UTF-8?q?=E6=B7=BB=E4=BB=98=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB.txt?=", + }, + { + "simplified chinese characters filename", "测试附件文件.txt", + "=?UTF-8?q?=E6=B5=8B=E8=AF=95=E9=99=84=E4=BB=B6=E6=96=87=E4=BB=B6.txt?=", + }, + { + "cyrillic characters filename", "Тестовый прикрепленный файл.txt", + "=?UTF-8?q?=D0=A2=D0=B5=D1=81=D1=82=D0=BE=D0=B2=D1=8B=D0=B9_=D0=BF=D1=80?= " + + "=?UTF-8?q?=D0=B8=D0=BA=D1=80=D0=B5=D0=BF=D0=BB=D0=B5=D0=BD=D0=BD=D1=8B?= " + + "=?UTF-8?q?=D0=B9_=D1=84=D0=B0=D0=B9=D0=BB.txt?=", + }, + } + for _, tt := range tests { + t.Run("addFile with filename sanitization: "+tt.name, func(t *testing.T) { + buffer := bytes.NewBuffer(nil) + msgwriter.writer = buffer + message := testMessage(t) + tmpfile, err := os.CreateTemp("", "attachment.*.tmp") + if err != nil { + t.Fatalf("failed to create tempfile: %s", err) + } + t.Cleanup(func() { + if err = os.Remove(tmpfile.Name()); err != nil { + t.Errorf("failed to remove tempfile: %s", err) + } + }) + + source, err := os.Open("testdata/attachment.txt") + if err != nil { + t.Fatalf("failed to open source file: %s", err) + } + if _, err = io.Copy(tmpfile, source); err != nil { + t.Fatalf("failed to copy source file: %s", err) + } + if err = tmpfile.Close(); err != nil { + t.Fatalf("failed to close tempfile: %s", err) + } + if err = source.Close(); err != nil { + t.Fatalf("failed to close source file: %s", err) + } + message.AttachFile(tmpfile.Name(), WithFileName(tt.filename)) + msgwriter.writeMsg(message) + if msgwriter.err != nil { + t.Errorf("msgWriter failed to write: %s", msgwriter.err) + } + ctExpect := fmt.Sprintf(`Content-Type: text/plain; charset=utf-8; name="%s"`, tt.expect) + cdExpect := fmt.Sprintf(`Content-Disposition: attachment; filename="%s"`, tt.expect) + if !strings.Contains(buffer.String(), ctExpect) { + t.Errorf("expected content-type: %q, got: %q", ctExpect, buffer.String()) + } + if !strings.Contains(buffer.String(), cdExpect) { + t.Errorf("expected content-disposition: %q, got: %q", cdExpect, buffer.String()) + } + }) + } t.Run("message with a single file attached", func(t *testing.T) { buffer := bytes.NewBuffer(nil) msgwriter.writer = buffer