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