@@ -19,12 +19,21 @@ import (
1919 "net"
2020 "os"
2121 "reflect"
22+ "runtime"
2223 "strings"
2324 "testing"
2425 ttpl "text/template"
2526 "time"
2627)
2728
29+ type msgContentTest struct {
30+ line int
31+ data string
32+ exact bool
33+ dataIsPrefix bool
34+ dataIsSuffix bool
35+ }
36+
2837var (
2938 charsetTests = []struct {
3039 name string
@@ -5939,6 +5948,260 @@ func TestMsg_WriteTo(t *testing.T) {
59395948 t .Errorf ("expected S/MIME signing error to contain: %q, got: %s" , expErr , err )
59405949 }
59415950 })
5951+ t .Run ("WriteTo Multipart plain text with attachment" , func (t * testing.T ) {
5952+ message := testMessage (t )
5953+ message .AttachFile ("testdata/attachment.txt" )
5954+ buffer := bytes .NewBuffer (nil )
5955+ if _ , err := message .WriteTo (buffer ); err != nil {
5956+ t .Fatalf ("failed to write message to buffer: %s" , err )
5957+ }
5958+ fileContentType := "text/plain; charset=utf-8"
5959+ if runtime .GOOS == "freebsd" {
5960+ fileContentType = "application/octet-stream"
5961+ }
5962+ wants := []msgContentTest {
5963+ {0 , "Date:" , false , true , false },
5964+ {1 , "MIME-Version: 1.0" , true , true , false },
5965+ {2 , "Message-ID: <" , false , true , false },
5966+ {2 , ">" , false , false , true },
5967+ {8 , "Content-Type: multipart/mixed;" , true , true , false },
5968+ {9 , " boundary=" , false , true , false },
5969+ {10 , "" , true , false , false },
5970+ {11 , "--" , false , true , false },
5971+ {12 , "Content-Transfer-Encoding: quoted-printable" , true , true , false },
5972+ {13 , "Content-Type: text/plain; charset=UTF-8" , true , true , false },
5973+ {14 , "" , true , false , false },
5974+ {15 , "Testmail" , true , true , false },
5975+ {16 , "--" , false , true , false },
5976+ {17 , `Content-Disposition: attachment; filename="attachment.txt"` , true , true , false },
5977+ {18 , `Content-Transfer-Encoding: base64` , true , true , false },
5978+ {19 , `Content-Type: ` + fileContentType + `; name="attachment.txt"` , true , true , false },
5979+ {20 , "" , true , false , false },
5980+ {21 , "VGhpcyBpcyBhIHRlc3Qg" , false , true , false },
5981+ {22 , "" , true , false , false },
5982+ {23 , "--" , false , true , true },
5983+ }
5984+ checkMessageContent (t , buffer , wants )
5985+ })
5986+ t .Run ("WriteTo Multipart plain text with alternative" , func (t * testing.T ) {
5987+ message := testMessage (t )
5988+ message .AddAlternativeString (TypeTextHTML , "<p>HTML alternative</p>" )
5989+ buffer := bytes .NewBuffer (nil )
5990+ if _ , err := message .WriteTo (buffer ); err != nil {
5991+ t .Fatalf ("failed to write message to buffer: %s" , err )
5992+ }
5993+ wants := []msgContentTest {
5994+ {0 , "Date:" , false , true , false },
5995+ {1 , "MIME-Version: 1.0" , true , true , false },
5996+ {2 , "Message-ID: <" , false , true , false },
5997+ {2 , ">" , false , false , true },
5998+ {8 , "Content-Type: multipart/alternative;" , true , true , false },
5999+ {9 , " boundary=" , false , true , false },
6000+ {10 , "" , true , false , false },
6001+ {11 , "--" , false , true , false },
6002+ {12 , "Content-Transfer-Encoding: quoted-printable" , true , true , false },
6003+ {13 , "Content-Type: text/plain; charset=UTF-8" , true , true , false },
6004+ {14 , "" , true , false , false },
6005+ {15 , "Testmail" , true , true , false },
6006+ {16 , "--" , false , true , false },
6007+ {17 , `Content-Transfer-Encoding: quoted-printable` , true , true , false },
6008+ {18 , `Content-Type: text/html; charset=UTF-8` , true , true , false },
6009+ {19 , "" , true , false , false },
6010+ {20 , `<p>HTML alternative</p>` , true , true , false },
6011+ {21 , "--" , false , true , true },
6012+ }
6013+ checkMessageContent (t , buffer , wants )
6014+ })
6015+ t .Run ("WriteTo Multipart two alternative parts" , func (t * testing.T ) {
6016+ message := NewMsg ()
6017+ if message == nil {
6018+ t .Fatal ("failed to create new message" )
6019+ }
6020+ if err := message .From (TestSenderValid ); err != nil {
6021+ t .Errorf ("failed to set sender address: %s" , err )
6022+ }
6023+ if err := message .To (TestRcptValid ); err != nil {
6024+ t .Errorf ("failed to set recipient address: %s" , err )
6025+ }
6026+ message .Subject ("Testmail" )
6027+ message .AddAlternativeString (TypeTextPlain , "Plain alternative" )
6028+ message .AddAlternativeString (TypeTextHTML , "<p>HTML main part</p>" )
6029+ buffer := bytes .NewBuffer (nil )
6030+ if _ , err := message .WriteTo (buffer ); err != nil {
6031+ t .Fatalf ("failed to write message to buffer: %s" , err )
6032+ }
6033+ wants := []msgContentTest {
6034+ {0 , "Date:" , false , true , false },
6035+ {1 , "MIME-Version: 1.0" , true , true , false },
6036+ {2 , "Message-ID: <" , false , true , false },
6037+ {2 , ">" , false , false , true },
6038+ {8 , "Content-Type: multipart/alternative;" , true , true , false },
6039+ {9 , " boundary=" , false , true , false },
6040+ {10 , "" , true , false , false },
6041+ {11 , "--" , false , true , false },
6042+ {12 , "Content-Transfer-Encoding: quoted-printable" , true , true , false },
6043+ {13 , "Content-Type: text/plain; charset=UTF-8" , true , true , false },
6044+ {14 , "" , true , false , false },
6045+ {15 , "Plain alternative" , true , true , false },
6046+ {16 , "--" , false , true , false },
6047+ {17 , "Content-Transfer-Encoding: quoted-printable" , true , true , false },
6048+ {18 , "Content-Type: text/html; charset=UTF-8" , true , true , false },
6049+ {19 , "" , true , false , false },
6050+ {20 , "<p>HTML main part</p>" , true , true , false },
6051+ {21 , "--" , false , true , true },
6052+ }
6053+ checkMessageContent (t , buffer , wants )
6054+ })
6055+ t .Run ("WriteTo Multipart plain body, alternative html, attachment and embed" , func (t * testing.T ) {
6056+ message := testMessage (t )
6057+ message .AddAlternativeString (TypeTextHTML , "<p>HTML alternative part</p>" )
6058+ message .AttachFile ("testdata/attachment.txt" )
6059+ message .EmbedFile ("testdata/embed.txt" )
6060+ buffer := bytes .NewBuffer (nil )
6061+ if _ , err := message .WriteTo (buffer ); err != nil {
6062+ t .Fatalf ("failed to write message to buffer: %s" , err )
6063+ }
6064+ fileContentType := "text/plain; charset=utf-8"
6065+ if runtime .GOOS == "freebsd" {
6066+ fileContentType = "application/octet-stream"
6067+ }
6068+ wants := []msgContentTest {
6069+ {0 , "Date:" , false , true , false },
6070+ {1 , "MIME-Version: 1.0" , true , true , false },
6071+ {2 , "Message-ID: <" , false , true , false },
6072+ {2 , ">" , false , false , true },
6073+ {6 , "From: <valid-from@domain.tld>" , true , true , false },
6074+ {7 , "To: <valid-to@domain.tld>" , true , true , false },
6075+ {8 , `Content-Type: multipart/mixed;` , true , true , false },
6076+ {9 , ` boundary=` , false , true , false },
6077+ {10 , "" , true , false , false },
6078+ {11 , "--" , false , true , false },
6079+ {12 , `Content-Type: multipart/related;` , true , true , false },
6080+ {13 , ` boundary=` , false , true , false },
6081+ {14 , "" , true , false , false },
6082+ {15 , "--" , false , true , false },
6083+ {16 , `Content-Type: multipart/alternative;` , true , true , false },
6084+ {17 , ` boundary=` , false , true , false },
6085+ {18 , "" , true , false , false },
6086+ {19 , "--" , false , true , false },
6087+ {20 , "Content-Transfer-Encoding: quoted-printable" , true , true , false },
6088+ {21 , "Content-Type: text/plain; charset=UTF-8" , true , true , false },
6089+ {22 , "" , true , false , false },
6090+ {23 , "Testmail" , true , true , false },
6091+ {24 , "--" , false , true , false },
6092+ {25 , "Content-Transfer-Encoding: quoted-printable" , true , true , false },
6093+ {26 , "Content-Type: text/html; charset=UTF-8" , true , true , false },
6094+ {27 , "" , true , false , false },
6095+ {28 , `<p>HTML alternative part</p>` , true , true , false },
6096+ {29 , "--" , false , true , true },
6097+ {30 , "" , true , false , false },
6098+ {31 , "--" , false , true , false },
6099+ {32 , `Content-Disposition: inline; filename="embed.txt"` , true , true , false },
6100+ {33 , "Content-Id: <embed.txt>" , true , true , false },
6101+ {34 , "Content-Transfer-Encoding: base64" , true , true , false },
6102+ {35 , `Content-Type: ` + fileContentType + `; name="embed.txt"` , true , true , false },
6103+ {36 , "" , true , false , false },
6104+ {37 , "VGhp" , false , true , false },
6105+ {38 , "" , true , false , false },
6106+ {39 , "--" , false , true , true },
6107+ {40 , "" , true , false , false },
6108+ {41 , "--" , false , true , false },
6109+ {42 , `Content-Disposition: attachment; filename="attachment.txt"` , true , true , false },
6110+ {43 , "Content-Transfer-Encoding: base64" , true , true , false },
6111+ {44 , `Content-Type: ` + fileContentType + `; name="attachment.txt"` , true , true , false },
6112+ {45 , "" , true , false , false },
6113+ {46 , "VGhp" , false , true , false },
6114+ {47 , "" , true , false , false },
6115+ {48 , "--" , false , true , true },
6116+ }
6117+ checkMessageContent (t , buffer , wants )
6118+ })
6119+ t .Run ("WriteTo Multipart plain body, alternative html, attachment, embed and S/MIME signed" , func (t * testing.T ) {
6120+ message := testMessage (t )
6121+ message .AddAlternativeString (TypeTextHTML , "<p>HTML alternative part</p>" )
6122+ message .AttachFile ("testdata/attachment.txt" )
6123+ message .EmbedFile ("testdata/embed.txt" )
6124+ keypair , err := getDummyKeyPairTLS ()
6125+ if err != nil {
6126+ t .Fatalf ("failed to load dummy key material: %s" , err )
6127+ }
6128+ if err = message .SignWithTLSCertificate (keypair ); err != nil {
6129+ t .Fatalf ("failed to initialize S/MIME signing: %s" , err )
6130+ }
6131+ buffer := bytes .NewBuffer (nil )
6132+ if _ , err := message .WriteTo (buffer ); err != nil {
6133+ t .Fatalf ("failed to write message to buffer: %s" , err )
6134+ }
6135+ fileContentType := "text/plain; charset=utf-8"
6136+ if runtime .GOOS == "freebsd" {
6137+ fileContentType = "application/octet-stream"
6138+ }
6139+ wants := []msgContentTest {
6140+ {0 , "Date:" , false , true , false },
6141+ {1 , "MIME-Version: 1.0" , true , true , false },
6142+ {2 , "Message-ID: <" , false , true , false },
6143+ {2 , ">" , false , false , true },
6144+ {6 , "From: <valid-from@domain.tld>" , true , true , false },
6145+ {7 , "To: <valid-to@domain.tld>" , true , true , false },
6146+ {
6147+ 8 , `Content-Type: multipart/signed; protocol="application/pkcs7-signature"; micalg=sha-256;` , true ,
6148+ true , false ,
6149+ },
6150+ {9 , ` boundary=` , false , true , false },
6151+ {10 , "" , true , false , false },
6152+ {11 , "--" , false , true , false },
6153+ {12 , `Content-Type: multipart/mixed;` , true , true , false },
6154+ {13 , ` boundary=` , false , true , false },
6155+ {14 , "" , true , false , false },
6156+ {15 , "--" , false , true , false },
6157+ {16 , `Content-Type: multipart/related;` , true , true , false },
6158+ {17 , ` boundary=` , false , true , false },
6159+ {18 , "" , true , false , false },
6160+ {19 , "--" , false , true , false },
6161+ {20 , `Content-Type: multipart/alternative;` , true , true , false },
6162+ {21 , ` boundary=` , false , true , false },
6163+ {22 , "" , true , false , false },
6164+ {23 , "--" , false , true , false },
6165+ {24 , "Content-Transfer-Encoding: quoted-printable" , true , true , false },
6166+ {25 , "Content-Type: text/plain; charset=UTF-8" , true , true , false },
6167+ {26 , "" , true , false , false },
6168+ {27 , "Testmail" , true , true , false },
6169+ {28 , "--" , false , true , false },
6170+ {29 , "Content-Transfer-Encoding: quoted-printable" , true , true , false },
6171+ {30 , "Content-Type: text/html; charset=UTF-8" , true , true , false },
6172+ {31 , "" , true , false , false },
6173+ {32 , `<p>HTML alternative part</p>` , true , true , false },
6174+ {33 , "--" , false , true , true },
6175+ {34 , "" , true , false , false },
6176+ {35 , "--" , false , true , false },
6177+ {36 , `Content-Disposition: inline; filename="embed.txt"` , true , true , false },
6178+ {37 , "Content-Id: <embed.txt>" , true , true , false },
6179+ {38 , "Content-Transfer-Encoding: base64" , true , true , false },
6180+ {39 , `Content-Type: ` + fileContentType + `; name="embed.txt"` , true , true , false },
6181+ {40 , "" , true , false , false },
6182+ {41 , "VGhp" , false , true , false },
6183+ {42 , "" , true , false , false },
6184+ {43 , "--" , false , true , true },
6185+ {44 , "" , true , false , false },
6186+ {45 , "--" , false , true , false },
6187+ {46 , `Content-Disposition: attachment; filename="attachment.txt"` , true , true , false },
6188+ {47 , "Content-Transfer-Encoding: base64" , true , true , false },
6189+ {48 , `Content-Type: ` + fileContentType + `; name="attachment.txt"` , true , true , false },
6190+ {49 , "" , true , false , false },
6191+ {50 , "VGhp" , false , true , false },
6192+ {51 , "" , true , false , false },
6193+ {52 , "--" , false , true , true },
6194+ {53 , "" , true , false , false },
6195+ {54 , "--" , false , true , false },
6196+ {55 , "Content-Transfer-Encoding: base64" , true , true , false },
6197+ {56 , `Content-Type: application/pkcs7-signature; name="smime.p7s"` , true , true , false },
6198+ {57 , "" , true , false , false },
6199+ {58 , "MII" , false , true , false },
6200+ {121 , "" , true , false , false },
6201+ {122 , "--" , false , true , true },
6202+ }
6203+ checkMessageContent (t , buffer , wants )
6204+ })
59426205}
59436206
59446207func TestMsg_WriteToFile (t * testing.T ) {
@@ -7177,6 +7440,35 @@ func hasSendmail() bool {
71777440 return false
71787441}
71797442
7443+ func checkMessageContent (t * testing.T , buffer * bytes.Buffer , wants []msgContentTest ) {
7444+ t .Helper ()
7445+ lines := strings .Split (buffer .String (), "\r \n " )
7446+ for _ , want := range wants {
7447+ if len (lines ) <= want .line {
7448+ t .Errorf ("expected line %d to be present, got: %d lines in total" , want .line , len (lines )- 1 )
7449+ continue
7450+ }
7451+ if ! strings .Contains (lines [want .line ], want .data ) {
7452+ t .Errorf ("expected line %d to contain %q, got: %q" , want .line , want .data , lines [want .line ])
7453+ }
7454+ if want .exact {
7455+ if ! strings .EqualFold (lines [want .line ], want .data ) {
7456+ t .Errorf ("expected line %d to be exactly %q, got: %q" , want .line , want .data , lines [want .line ])
7457+ }
7458+ }
7459+ if want .dataIsPrefix {
7460+ if ! strings .HasPrefix (lines [want .line ], want .data ) {
7461+ t .Errorf ("expected line %d to start with %q, got: %q" , want .line , want .data , lines [want .line ])
7462+ }
7463+ }
7464+ if want .dataIsSuffix {
7465+ if ! strings .HasSuffix (lines [want .line ], want .data ) {
7466+ t .Errorf ("expected line %d to end with %q, got: %q" , want .line , want .data , lines [want .line ])
7467+ }
7468+ }
7469+ }
7470+ }
7471+
71807472// Fuzzing tests
71817473func FuzzMsg_Subject (f * testing.F ) {
71827474 f .Add ("Testsubject" )
0 commit comments