1package imap
2
3import (
4	"bytes"
5	"fmt"
6	"reflect"
7	"testing"
8	"time"
9)
10
11func TestCanonicalFlag(t *testing.T) {
12	if got := CanonicalFlag("\\SEEN"); got != SeenFlag {
13		t.Errorf("Invalid canonical flag: expected %q but got %q", SeenFlag, got)
14	}
15
16	if got := CanonicalFlag("Junk"); got != "junk" {
17		t.Errorf("Invalid canonical flag: expected %q but got %q", "junk", got)
18	}
19}
20
21func TestNewMessage(t *testing.T) {
22	msg := NewMessage(42, []FetchItem{FetchBodyStructure, FetchFlags})
23
24	expected := &Message{
25		SeqNum:     42,
26		Items:      map[FetchItem]interface{}{FetchBodyStructure: nil, FetchFlags: nil},
27		Body:       make(map[*BodySectionName]Literal),
28		itemsOrder: []FetchItem{FetchBodyStructure, FetchFlags},
29	}
30
31	if !reflect.DeepEqual(expected, msg) {
32		t.Errorf("Invalid message: expected \n%+v\n but got \n%+v", expected, msg)
33	}
34}
35
36func formatFields(fields []interface{}) (string, error) {
37	b := &bytes.Buffer{}
38	w := NewWriter(b)
39
40	if err := w.writeList(fields); err != nil {
41		return "", fmt.Errorf("Cannot format \n%+v\n got error: \n%v", fields, err)
42	}
43
44	return b.String(), nil
45}
46
47var messageTests = []struct {
48	message *Message
49	fields  []interface{}
50}{
51	{
52		message: &Message{
53			Items: map[FetchItem]interface{}{
54				FetchEnvelope:   nil,
55				FetchBody:       nil,
56				FetchFlags:      nil,
57				FetchRFC822Size: nil,
58				FetchUid:        nil,
59			},
60			Body:          map[*BodySectionName]Literal{},
61			Envelope:      envelopeTests[0].envelope,
62			BodyStructure: bodyStructureTests[0].bodyStructure,
63			Flags:         []string{SeenFlag, AnsweredFlag},
64			Size:          4242,
65			Uid:           2424,
66			itemsOrder:    []FetchItem{FetchEnvelope, FetchBody, FetchFlags, FetchRFC822Size, FetchUid},
67		},
68		fields: []interface{}{
69			RawString("ENVELOPE"), envelopeTests[0].fields,
70			RawString("BODY"), bodyStructureTests[0].fields,
71			RawString("FLAGS"), []interface{}{RawString(SeenFlag), RawString(AnsweredFlag)},
72			RawString("RFC822.SIZE"), RawString("4242"),
73			RawString("UID"), RawString("2424"),
74		},
75	},
76}
77
78func TestMessage_Parse(t *testing.T) {
79	for i, test := range messageTests {
80		m := &Message{}
81		if err := m.Parse(test.fields); err != nil {
82			t.Errorf("Cannot parse message for #%v: %v", i, err)
83		} else if !reflect.DeepEqual(m, test.message) {
84			t.Errorf("Invalid parsed message for #%v: got \n%+v\n but expected \n%+v", i, m, test.message)
85		}
86	}
87}
88
89func TestMessage_Format(t *testing.T) {
90	for i, test := range messageTests {
91		fields := test.message.Format()
92
93		got, err := formatFields(fields)
94		if err != nil {
95			t.Error(err)
96			continue
97		}
98
99		expected, _ := formatFields(test.fields)
100
101		if got != expected {
102			t.Errorf("Invalid message fields for #%v: got \n%v\n but expected \n%v", i, got, expected)
103		}
104	}
105}
106
107var bodySectionNameTests = []struct {
108	raw       string
109	parsed    *BodySectionName
110	formatted string
111}{
112	{
113		raw:    "BODY[]",
114		parsed: &BodySectionName{BodyPartName: BodyPartName{}},
115	},
116	{
117		raw:       "RFC822",
118		parsed:    &BodySectionName{BodyPartName: BodyPartName{}},
119		formatted: "BODY[]",
120	},
121	{
122		raw:    "BODY[HEADER]",
123		parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: HeaderSpecifier}},
124	},
125	{
126		raw:    "BODY.PEEK[]",
127		parsed: &BodySectionName{BodyPartName: BodyPartName{}, Peek: true},
128	},
129	{
130		raw:    "BODY[TEXT]",
131		parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: TextSpecifier}},
132	},
133	{
134		raw:       "RFC822.TEXT",
135		parsed:    &BodySectionName{BodyPartName: BodyPartName{Specifier: TextSpecifier}},
136		formatted: "BODY[TEXT]",
137	},
138	{
139		raw:       "RFC822.HEADER",
140		parsed:    &BodySectionName{BodyPartName: BodyPartName{Specifier: HeaderSpecifier}, Peek: true},
141		formatted: "BODY.PEEK[HEADER]",
142	},
143	{
144		raw:    "BODY[]<0.512>",
145		parsed: &BodySectionName{BodyPartName: BodyPartName{}, Partial: []int{0, 512}},
146	},
147	{
148		raw:    "BODY[]<512>",
149		parsed: &BodySectionName{BodyPartName: BodyPartName{}, Partial: []int{512}},
150	},
151	{
152		raw:    "BODY[1.2.3]",
153		parsed: &BodySectionName{BodyPartName: BodyPartName{Path: []int{1, 2, 3}}},
154	},
155	{
156		raw:    "BODY[1.2.3.HEADER]",
157		parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: HeaderSpecifier, Path: []int{1, 2, 3}}},
158	},
159	{
160		raw:    "BODY[5.MIME]",
161		parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: MIMESpecifier, Path: []int{5}}},
162	},
163	{
164		raw:    "BODY[HEADER.FIELDS (From To)]",
165		parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: HeaderSpecifier, Fields: []string{"From", "To"}}},
166	},
167	{
168		raw:    "BODY[HEADER.FIELDS.NOT (Content-Id)]",
169		parsed: &BodySectionName{BodyPartName: BodyPartName{Specifier: HeaderSpecifier, Fields: []string{"Content-Id"}, NotFields: true}},
170	},
171}
172
173func TestNewBodySectionName(t *testing.T) {
174	for i, test := range bodySectionNameTests {
175		bsn, err := ParseBodySectionName(FetchItem(test.raw))
176		if err != nil {
177			t.Errorf("Cannot parse #%v: %v", i, err)
178			continue
179		}
180
181		if !reflect.DeepEqual(bsn.BodyPartName, test.parsed.BodyPartName) {
182			t.Errorf("Invalid body part name for #%v: %#+v", i, bsn.BodyPartName)
183		} else if bsn.Peek != test.parsed.Peek {
184			t.Errorf("Invalid peek value for #%v: %#+v", i, bsn.Peek)
185		} else if !reflect.DeepEqual(bsn.Partial, test.parsed.Partial) {
186			t.Errorf("Invalid partial for #%v: %#+v", i, bsn.Partial)
187		}
188	}
189}
190
191func TestBodySectionName_String(t *testing.T) {
192	for i, test := range bodySectionNameTests {
193		s := string(test.parsed.FetchItem())
194
195		expected := test.formatted
196		if expected == "" {
197			expected = test.raw
198		}
199
200		if expected != s {
201			t.Errorf("Invalid body section name for #%v: got %v but expected %v", i, s, expected)
202		}
203	}
204}
205
206func TestBodySectionName_ExtractPartial(t *testing.T) {
207	tests := []struct {
208		bsn     string
209		whole   string
210		partial string
211	}{
212		{
213			bsn:     "BODY[]",
214			whole:   "Hello World!",
215			partial: "Hello World!",
216		},
217		{
218			bsn:     "BODY[]<6.5>",
219			whole:   "Hello World!",
220			partial: "World",
221		},
222		{
223			bsn:     "BODY[]<6.1000>",
224			whole:   "Hello World!",
225			partial: "World!",
226		},
227		{
228			bsn:     "BODY[]<0.1>",
229			whole:   "Hello World!",
230			partial: "H",
231		},
232		{
233			bsn:     "BODY[]<1000.2000>",
234			whole:   "Hello World!",
235			partial: "",
236		},
237	}
238
239	for i, test := range tests {
240		bsn, err := ParseBodySectionName(FetchItem(test.bsn))
241		if err != nil {
242			t.Errorf("Cannot parse body section name #%v: %v", i, err)
243			continue
244		}
245
246		partial := string(bsn.ExtractPartial([]byte(test.whole)))
247		if partial != test.partial {
248			t.Errorf("Invalid partial for #%v: got %v but expected %v", i, partial, test.partial)
249		}
250	}
251}
252
253var t = time.Date(2009, time.November, 10, 23, 0, 0, 0, time.FixedZone("", -6*60*60))
254
255var envelopeTests = []struct {
256	envelope *Envelope
257	fields   []interface{}
258}{
259	{
260		envelope: &Envelope{
261			Date:      t,
262			Subject:   "Hello World!",
263			From:      []*Address{addrTests[0].addr},
264			Sender:    []*Address{},
265			ReplyTo:   []*Address{},
266			To:        []*Address{},
267			Cc:        []*Address{},
268			Bcc:       []*Address{},
269			InReplyTo: "42@example.org",
270			MessageId: "43@example.org",
271		},
272		fields: []interface{}{
273			"Tue, 10 Nov 2009 23:00:00 -0600",
274			"Hello World!",
275			[]interface{}{addrTests[0].fields},
276			[]interface{}{},
277			[]interface{}{},
278			[]interface{}{},
279			[]interface{}{},
280			[]interface{}{},
281			"42@example.org",
282			"43@example.org",
283		},
284	},
285}
286
287func TestEnvelope_Parse(t *testing.T) {
288	for i, test := range envelopeTests {
289		e := &Envelope{}
290		if err := e.Parse(test.fields); err != nil {
291			t.Error("Error parsing envelope:", err)
292		} else if !reflect.DeepEqual(e, test.envelope) {
293			t.Errorf("Invalid envelope for #%v: got %v but expected %v", i, e, test.envelope)
294		}
295	}
296}
297
298func TestEnvelope_Parse_literal(t *testing.T) {
299	subject := "Hello World!"
300	l := bytes.NewBufferString(subject)
301	fields := []interface{}{
302		"Tue, 10 Nov 2009 23:00:00 -0600",
303		l,
304		nil,
305		nil,
306		nil,
307		nil,
308		nil,
309		nil,
310		"42@example.org",
311		"43@example.org",
312	}
313
314	e := &Envelope{}
315	if err := e.Parse(fields); err != nil {
316		t.Error("Error parsing envelope:", err)
317	} else if e.Subject != subject {
318		t.Errorf("Invalid envelope subject: got %v but expected %v", e.Subject, subject)
319	}
320}
321
322func TestEnvelope_Format(t *testing.T) {
323	for i, test := range envelopeTests {
324		fields := test.envelope.Format()
325
326		got, err := formatFields(fields)
327		if err != nil {
328			t.Error(err)
329			continue
330		}
331
332		expected, _ := formatFields(test.fields)
333
334		if got != expected {
335			t.Errorf("Invalid envelope fields for #%v: got %v but expected %v", i, got, expected)
336		}
337	}
338}
339
340var addrTests = []struct {
341	fields []interface{}
342	addr   *Address
343}{
344	{
345		fields: []interface{}{"The NSA", nil, "root", "nsa.gov"},
346		addr: &Address{
347			PersonalName: "The NSA",
348			MailboxName:  "root",
349			HostName:     "nsa.gov",
350		},
351	},
352}
353
354func TestAddress_Parse(t *testing.T) {
355	for i, test := range addrTests {
356		addr := &Address{}
357
358		if err := addr.Parse(test.fields); err != nil {
359			t.Error("Error parsing address:", err)
360		} else if !reflect.DeepEqual(addr, test.addr) {
361			t.Errorf("Invalid address for #%v: got %v but expected %v", i, addr, test.addr)
362		}
363	}
364}
365
366func TestAddress_Format(t *testing.T) {
367	for i, test := range addrTests {
368		fields := test.addr.Format()
369		if !reflect.DeepEqual(fields, test.fields) {
370			t.Errorf("Invalid address fields for #%v: got %v but expected %v", i, fields, test.fields)
371		}
372	}
373}
374
375func TestAddressList(t *testing.T) {
376	fields := make([]interface{}, len(addrTests))
377	addrs := make([]*Address, len(addrTests))
378	for i, test := range addrTests {
379		fields[i] = test.fields
380		addrs[i] = test.addr
381	}
382
383	gotAddrs := ParseAddressList(fields)
384	if !reflect.DeepEqual(gotAddrs, addrs) {
385		t.Error("Invalid address list: got", gotAddrs, "but expected", addrs)
386	}
387
388	gotFields := FormatAddressList(addrs)
389	if !reflect.DeepEqual(gotFields, fields) {
390		t.Error("Invalid address list fields: got", gotFields, "but expected", fields)
391	}
392}
393
394var paramsListTest = []struct {
395	fields []interface{}
396	params map[string]string
397}{
398	{
399		fields: nil,
400		params: map[string]string{},
401	},
402	{
403		fields: []interface{}{"a", "b"},
404		params: map[string]string{"a": "b"},
405	},
406}
407
408func TestParseParamList(t *testing.T) {
409	for i, test := range paramsListTest {
410		if params, err := ParseParamList(test.fields); err != nil {
411			t.Errorf("Cannot parse params fields for #%v: %v", i, err)
412		} else if !reflect.DeepEqual(params, test.params) {
413			t.Errorf("Invalid params for #%v: got %v but expected %v", i, params, test.params)
414		}
415	}
416
417	// Malformed params lists
418
419	fields := []interface{}{"cc", []interface{}{"dille"}}
420	if params, err := ParseParamList(fields); err == nil {
421		t.Error("Parsed invalid params list:", params)
422	}
423
424	fields = []interface{}{"cc"}
425	if params, err := ParseParamList(fields); err == nil {
426		t.Error("Parsed invalid params list:", params)
427	}
428}
429
430func TestFormatParamList(t *testing.T) {
431	for i, test := range paramsListTest {
432		fields := FormatParamList(test.params)
433
434		if !reflect.DeepEqual(fields, test.fields) {
435			t.Errorf("Invalid params fields for #%v: got %v but expected %v", i, fields, test.fields)
436		}
437	}
438}
439
440var bodyStructureTests = []struct {
441	fields        []interface{}
442	bodyStructure *BodyStructure
443}{
444	{
445		fields: []interface{}{"image", "jpeg", []interface{}{}, "<foo4%25foo1@bar.net>", "A picture of cat", "base64", RawString("4242")},
446		bodyStructure: &BodyStructure{
447			MIMEType:    "image",
448			MIMESubType: "jpeg",
449			Params:      map[string]string{},
450			Id:          "<foo4%25foo1@bar.net>",
451			Description: "A picture of cat",
452			Encoding:    "base64",
453			Size:        4242,
454		},
455	},
456	{
457		fields: []interface{}{"text", "plain", []interface{}{"charset", "utf-8"}, nil, nil, "us-ascii", RawString("42"), RawString("2")},
458		bodyStructure: &BodyStructure{
459			MIMEType:    "text",
460			MIMESubType: "plain",
461			Params:      map[string]string{"charset": "utf-8"},
462			Encoding:    "us-ascii",
463			Size:        42,
464			Lines:       2,
465		},
466	},
467	{
468		fields: []interface{}{
469			"message", "rfc822", []interface{}{}, nil, nil, "us-ascii", RawString("42"),
470			(&Envelope{}).Format(),
471			(&BodyStructure{}).Format(),
472			RawString("67"),
473		},
474		bodyStructure: &BodyStructure{
475			MIMEType:    "message",
476			MIMESubType: "rfc822",
477			Params:      map[string]string{},
478			Encoding:    "us-ascii",
479			Size:        42,
480			Lines:       67,
481			Envelope: &Envelope{
482				From:    []*Address{},
483				Sender:  []*Address{},
484				ReplyTo: []*Address{},
485				To:      []*Address{},
486				Cc:      []*Address{},
487				Bcc:     []*Address{},
488			},
489			BodyStructure: &BodyStructure{
490				Params: map[string]string{},
491			},
492		},
493	},
494	{
495		fields: []interface{}{
496			"application", "pdf", []interface{}{}, nil, nil, "base64", RawString("4242"),
497			"e0323a9039add2978bf5b49550572c7c",
498			[]interface{}{"attachment", []interface{}{"filename", "document.pdf"}},
499			[]interface{}{"en-US"}, []interface{}{},
500		},
501		bodyStructure: &BodyStructure{
502			MIMEType:          "application",
503			MIMESubType:       "pdf",
504			Params:            map[string]string{},
505			Encoding:          "base64",
506			Size:              4242,
507			Extended:          true,
508			MD5:               "e0323a9039add2978bf5b49550572c7c",
509			Disposition:       "attachment",
510			DispositionParams: map[string]string{"filename": "document.pdf"},
511			Language:          []string{"en-US"},
512			Location:          []string{},
513		},
514	},
515	{
516		fields: []interface{}{
517			[]interface{}{"text", "plain", []interface{}{}, nil, nil, "us-ascii", RawString("87"), RawString("22")},
518			[]interface{}{"text", "html", []interface{}{}, nil, nil, "us-ascii", RawString("106"), RawString("36")},
519			"alternative",
520		},
521		bodyStructure: &BodyStructure{
522			MIMEType:    "multipart",
523			MIMESubType: "alternative",
524			Params:      map[string]string{},
525			Parts: []*BodyStructure{
526				{
527					MIMEType:    "text",
528					MIMESubType: "plain",
529					Params:      map[string]string{},
530					Encoding:    "us-ascii",
531					Size:        87,
532					Lines:       22,
533				},
534				{
535					MIMEType:    "text",
536					MIMESubType: "html",
537					Params:      map[string]string{},
538					Encoding:    "us-ascii",
539					Size:        106,
540					Lines:       36,
541				},
542			},
543		},
544	},
545	{
546		fields: []interface{}{
547			[]interface{}{"text", "plain", []interface{}{}, nil, nil, "us-ascii", RawString("87"), RawString("22")},
548			"alternative", []interface{}{"hello", "world"},
549			[]interface{}{"inline", []interface{}{}},
550			[]interface{}{"en-US"}, []interface{}{},
551		},
552		bodyStructure: &BodyStructure{
553			MIMEType:    "multipart",
554			MIMESubType: "alternative",
555			Params:      map[string]string{"hello": "world"},
556			Parts: []*BodyStructure{
557				{
558					MIMEType:    "text",
559					MIMESubType: "plain",
560					Params:      map[string]string{},
561					Encoding:    "us-ascii",
562					Size:        87,
563					Lines:       22,
564				},
565			},
566			Extended:          true,
567			Disposition:       "inline",
568			DispositionParams: map[string]string{},
569			Language:          []string{"en-US"},
570			Location:          []string{},
571		},
572	},
573}
574
575func TestBodyStructure_Parse(t *testing.T) {
576	for i, test := range bodyStructureTests {
577		bs := &BodyStructure{}
578
579		if err := bs.Parse(test.fields); err != nil {
580			t.Errorf("Cannot parse #%v: %v", i, err)
581		} else if !reflect.DeepEqual(bs, test.bodyStructure) {
582			t.Errorf("Invalid body structure for #%v: got \n%+v\n but expected \n%+v", i, bs, test.bodyStructure)
583		}
584	}
585}
586
587func TestBodyStructure_Format(t *testing.T) {
588	for i, test := range bodyStructureTests {
589		fields := test.bodyStructure.Format()
590		got, err := formatFields(fields)
591		if err != nil {
592			t.Error(err)
593			continue
594		}
595
596		expected, _ := formatFields(test.fields)
597
598		if got != expected {
599			t.Errorf("Invalid body structure fields for #%v: has \n%v\n but expected \n%v", i, got, expected)
600		}
601	}
602}
603
604func TestBodyStructureFilename(t *testing.T) {
605	tests := []struct {
606		bs       BodyStructure
607		filename string
608	}{
609		{
610			bs: BodyStructure{
611				DispositionParams: map[string]string{"filename": "cat.png"},
612			},
613			filename: "cat.png",
614		},
615		{
616			bs: BodyStructure{
617				Params: map[string]string{"name": "cat.png"},
618			},
619			filename: "cat.png",
620		},
621		{
622			bs:       BodyStructure{},
623			filename: "",
624		},
625		{
626			bs: BodyStructure{
627				DispositionParams: map[string]string{"filename": "=?UTF-8?Q?Opis_przedmiotu_zam=c3=b3wienia_-_za=c5=82=c4=85cznik_nr_1?= =?UTF-8?Q?=2epdf?="},
628			},
629			filename: "Opis przedmiotu zamówienia - załącznik nr 1.pdf",
630		},
631	}
632
633	for i, test := range tests {
634		got, err := test.bs.Filename()
635		if err != nil {
636			t.Errorf("Invalid body structure filename for #%v: error: %v", i, err)
637			continue
638		}
639
640		if got != test.filename {
641			t.Errorf("Invalid body structure filename for #%v: got '%v', want '%v'", i, got, test.filename)
642		}
643	}
644}
645
646func TestBodyStructureWalk(t *testing.T) {
647	textPlain := &BodyStructure{
648		MIMEType:    "text",
649		MIMESubType: "plain",
650	}
651
652	textHTML := &BodyStructure{
653		MIMEType:    "text",
654		MIMESubType: "plain",
655	}
656
657	multipartAlternative := &BodyStructure{
658		MIMEType:    "multipart",
659		MIMESubType: "alternative",
660		Parts:       []*BodyStructure{textPlain, textHTML},
661	}
662
663	imagePNG := &BodyStructure{
664		MIMEType:    "image",
665		MIMESubType: "png",
666	}
667
668	multipartMixed := &BodyStructure{
669		MIMEType:    "multipart",
670		MIMESubType: "mixed",
671		Parts:       []*BodyStructure{multipartAlternative, imagePNG},
672	}
673
674	type testNode struct {
675		path []int
676		part *BodyStructure
677	}
678
679	tests := []struct {
680		bs           *BodyStructure
681		nodes        []testNode
682		walkChildren bool
683	}{
684		{
685			bs: textPlain,
686			nodes: []testNode{
687				{path: []int{1}, part: textPlain},
688			},
689		},
690		{
691			bs: multipartAlternative,
692			nodes: []testNode{
693				{path: nil, part: multipartAlternative},
694				{path: []int{1}, part: textPlain},
695				{path: []int{2}, part: textHTML},
696			},
697			walkChildren: true,
698		},
699		{
700			bs: multipartMixed,
701			nodes: []testNode{
702				{path: nil, part: multipartMixed},
703				{path: []int{1}, part: multipartAlternative},
704				{path: []int{1, 1}, part: textPlain},
705				{path: []int{1, 2}, part: textHTML},
706				{path: []int{2}, part: imagePNG},
707			},
708			walkChildren: true,
709		},
710		{
711			bs: multipartMixed,
712			nodes: []testNode{
713				{path: nil, part: multipartMixed},
714			},
715			walkChildren: false,
716		},
717	}
718
719	for i, test := range tests {
720		j := 0
721		test.bs.Walk(func(path []int, part *BodyStructure) bool {
722			if j >= len(test.nodes) {
723				t.Errorf("Test #%v: invalid node count: got > %v, want %v", i, j, len(test.nodes))
724				return false
725			}
726			n := &test.nodes[j]
727			if !reflect.DeepEqual(path, n.path) {
728				t.Errorf("Test #%v: node #%v: invalid path: got %v, want %v", i, j, path, n.path)
729			}
730			if part != n.part {
731				t.Errorf("Test #%v: node #%v: invalid part: got %v, want %v", i, j, part, n.part)
732			}
733			j++
734			return test.walkChildren
735		})
736		if j != len(test.nodes) {
737			t.Errorf("Test #%v: invalid node count: got %v, want %v", i, j, len(test.nodes))
738		}
739	}
740}
741