1/* 2 * Copyright 2011, Haiku, Inc. All rights reserved. 3 * Copyright 2011, Clemens Zeidler <haiku@clemens-zeidler.de> 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8#include "IMAPHandler.h" 9 10#include <stdlib.h> 11 12#include <AutoDeleter.h> 13 14#include "IMAPMailbox.h" 15#include "IMAPParser.h" 16#include "IMAPStorage.h" 17 18 19#define DEBUG_IMAP_HANDLER 20#ifdef DEBUG_IMAP_HANDLER 21# include <stdio.h> 22# define TRACE(x...) printf(x) 23#else 24# define TRACE(x...) ; 25#endif 26 27 28using namespace BPrivate; 29 30 31IMAPCommand::~IMAPCommand() 32{ 33} 34 35 36BString 37IMAPCommand::Command() 38{ 39 return ""; 40} 41 42 43IMAPMailboxCommand::IMAPMailboxCommand(IMAPMailbox& mailbox) 44 : 45 fIMAPMailbox(mailbox), 46 fStorage(mailbox.GetStorage()), 47 fConnectionReader(mailbox.GetConnectionReader()) 48{ 49} 50 51 52IMAPMailboxCommand::~IMAPMailboxCommand() 53{ 54} 55 56 57// #pragma mark - 58 59 60MailboxSelectHandler::MailboxSelectHandler(IMAPMailbox& mailbox) 61 : 62 IMAPMailboxCommand(mailbox), 63 64 fMailboxName(""), 65 fNextUID(-1), 66 fUIDValidity(-1) 67{ 68} 69 70 71BString 72MailboxSelectHandler::Command() 73{ 74 if (fMailboxName == "") 75 return ""; 76 77 BString command = "SELECT \""; 78 command += fMailboxName; 79 command += "\""; 80 return command; 81} 82 83 84bool 85MailboxSelectHandler::Handle(const BString& response) 86{ 87 BString extracted = IMAPParser::ExtractStringAfter(response, 88 "* OK [UIDVALIDITY"); 89 if (extracted != "") { 90 fUIDValidity = IMAPParser::RemoveIntegerFromLeft(extracted); 91 TRACE("UIDValidity %i\n", (int)fUIDValidity); 92 return true; 93 } 94 95 extracted = IMAPParser::ExtractStringAfter(response, "* OK [UIDNEXT"); 96 if (extracted != "") { 97 fNextUID = IMAPParser::RemoveIntegerFromLeft(extracted); 98 TRACE("NextUID %i\n", (int)fNextUID); 99 return true; 100 } 101 102 return false; 103} 104 105 106// #pragma mark - 107 108 109CapabilityHandler::CapabilityHandler() 110 : 111 fCapabilities("") 112{ 113} 114 115 116BString 117CapabilityHandler::Command() 118{ 119 return "CAPABILITY"; 120} 121 122 123bool 124CapabilityHandler::Handle(const BString& response) 125{ 126 BString cap = IMAPParser::ExtractStringAfter(response, "* CAPABILITY"); 127 if (cap == "") 128 return false; 129 fCapabilities = cap; 130 TRACE("CAPABILITY: %s\n", fCapabilities.String()); 131 return true; 132} 133 134 135BString& 136CapabilityHandler::Capabilities() 137{ 138 return fCapabilities; 139} 140 141 142// #pragma mark - 143 144 145FetchMinMessageCommand::FetchMinMessageCommand(IMAPMailbox& mailbox, 146 int32 message, MinMessageList* list, BPositionIO** data) 147 : 148 IMAPMailboxCommand(mailbox), 149 150 fMessage(message), 151 fEndMessage(-1), 152 fMinMessageList(list), 153 fData(data) 154{ 155} 156 157 158FetchMinMessageCommand::FetchMinMessageCommand(IMAPMailbox& mailbox, 159 int32 firstMessage, int32 lastMessage, MinMessageList* list, 160 BPositionIO** data) 161 : 162 IMAPMailboxCommand(mailbox), 163 164 fMessage(firstMessage), 165 fEndMessage(lastMessage), 166 fMinMessageList(list), 167 fData(data) 168{ 169} 170 171 172BString 173FetchMinMessageCommand::Command() 174{ 175 if (fMessage <= 0) 176 return ""; 177 BString command = "FETCH "; 178 command << fMessage; 179 if (fEndMessage > 0) { 180 command += ":"; 181 command << fEndMessage; 182 } 183 command += " (UID FLAGS)"; 184 return command; 185} 186 187 188bool 189FetchMinMessageCommand::Handle(const BString& response) 190{ 191 BString extracted = response; 192 int32 message; 193 if (!IMAPParser::RemoveUntagedFromLeft(extracted, "FETCH", message)) 194 return false; 195 196 // check if we requested this message 197 int32 end = message; 198 if (fEndMessage > 0) 199 end = fEndMessage; 200 if (message < fMessage && message > end) 201 return false; 202 203 MinMessage minMessage; 204 if (!ParseMinMessage(extracted, minMessage)) 205 return false; 206 207 fMinMessageList->push_back(minMessage); 208 fStorage.AddNewMessage(minMessage.uid, minMessage.flags, fData); 209 return true; 210} 211 212 213bool 214FetchMinMessageCommand::ParseMinMessage(const BString& response, 215 MinMessage& minMessage) 216{ 217 BString extracted = IMAPParser::ExtractNextElement(response); 218 BString uid = IMAPParser::ExtractElementAfter(extracted, "UID"); 219 if (uid == "") 220 return false; 221 minMessage.uid = atoi(uid); 222 223 int32 flags = ExtractFlags(extracted); 224 minMessage.flags = flags; 225 226 return true; 227} 228 229 230int32 231FetchMinMessageCommand::ExtractFlags(const BString& response) 232{ 233 int32 flags = 0; 234 BString flagsString = IMAPParser::ExtractElementAfter(response, "FLAGS"); 235 236 while (true) { 237 BString flag = IMAPParser::RemovePrimitiveFromLeft(flagsString); 238 if (flag == "") 239 break; 240 241 if (flag == "\\Seen") 242 flags |= kSeen; 243 else if (flag == "\\Answered") 244 flags |= kAnswered; 245 else if (flag == "\\Flagged") 246 flags |= kFlagged; 247 else if (flag == "\\Deleted") 248 flags |= kDeleted; 249 else if (flag == "\\Draft") 250 flags |= kDraft; 251 } 252 return flags; 253} 254 255 256// #pragma mark - 257 258 259FetchMessageListCommand::FetchMessageListCommand(IMAPMailbox& mailbox, 260 MinMessageList* list, int32 nextId) 261 : 262 IMAPMailboxCommand(mailbox), 263 264 fMinMessageList(list), 265 fNextId(nextId) 266{ 267} 268 269 270BString 271FetchMessageListCommand::Command() 272{ 273 BString command = "UID FETCH 1:"; 274 command << fNextId - 1; 275 command << " FLAGS"; 276 return command; 277} 278 279 280bool 281FetchMessageListCommand::Handle(const BString& response) 282{ 283 BString extracted = response; 284 int32 message; 285 if (!IMAPParser::RemoveUntagedFromLeft(extracted, "FETCH", message)) 286 return false; 287 288 MinMessage minMessage; 289 if (!FetchMinMessageCommand::ParseMinMessage(extracted, minMessage)) 290 return false; 291 292 fMinMessageList->push_back(minMessage); 293 return true; 294} 295 296 297// #pragma mark - 298 299 300FetchMessageCommand::FetchMessageCommand(IMAPMailbox& mailbox, int32 message, 301 BPositionIO* data, int32 fetchBodyLimit) 302 : 303 IMAPMailboxCommand(mailbox), 304 305 fMessage(message), 306 fEndMessage(-1), 307 fOutData(data), 308 fFetchBodyLimit(fetchBodyLimit) 309{ 310} 311 312 313FetchMessageCommand::FetchMessageCommand(IMAPMailbox& mailbox, 314 int32 firstMessage, int32 lastMessage, int32 fetchBodyLimit) 315 : 316 IMAPMailboxCommand(mailbox), 317 318 fMessage(firstMessage), 319 fEndMessage(lastMessage), 320 fOutData(NULL), 321 fFetchBodyLimit(fetchBodyLimit) 322{ 323 if (fEndMessage > 0) 324 fUnhandled = fEndMessage - fMessage + 1; 325 else 326 fUnhandled = 1; 327} 328 329 330FetchMessageCommand::~FetchMessageCommand() 331{ 332 for (int32 i = 0; i < fUnhandled; i++) 333 fIMAPMailbox.Listener().FetchEnd(); 334} 335 336 337BString 338FetchMessageCommand::Command() 339{ 340 BString command = "FETCH "; 341 command << fMessage; 342 if (fEndMessage > 0) { 343 command += ":"; 344 command << fEndMessage; 345 } 346 command += " (RFC822.SIZE RFC822.HEADER)"; 347 return command; 348} 349 350 351bool 352FetchMessageCommand::Handle(const BString& response) 353{ 354 BString extracted = response; 355 int32 message; 356 if (!IMAPParser::RemoveUntagedFromLeft(extracted, "FETCH", message)) 357 return false; 358 359 // check if we requested this message 360 int32 end = message; 361 if (fEndMessage > 0) 362 end = fEndMessage; 363 if (message < fMessage && message > end) 364 return false; 365 366 const MinMessageList& list = fIMAPMailbox.GetMessageList(); 367 int32 index = message - 1; 368 if (index < 0 || index >= (int32)list.size()) 369 return false; 370 const MinMessage& minMessage = list[index]; 371 372 BPositionIO* data = fOutData; 373 ObjectDeleter<BPositionIO> deleter; 374 if (!data) { 375 status_t status = fStorage.OpenMessage(minMessage.uid, &data); 376 if (status != B_OK) { 377 status = fStorage.AddNewMessage(minMessage.uid, minMessage.flags, 378 &data); 379 } 380 if (status != B_OK) 381 return false; 382 deleter.SetTo(data); 383 } 384 385 // read message size 386 BString messageSizeString = IMAPParser::ExtractElementAfter(extracted, 387 "RFC822.SIZE"); 388 int32 messageSize = atoi(messageSizeString); 389 fStorage.SetCompleteMessageSize(minMessage.uid, messageSize); 390 391 // read header 392 int32 headerPos = extracted.FindFirst("RFC822.HEADER"); 393 if (headerPos < 0) { 394 if (!fOutData) 395 fStorage.DeleteMessage(minMessage.uid); 396 return false; 397 } 398 extracted.Remove(0, headerPos + strlen("RFC822.HEADER") + 1); 399 BString headerSize = IMAPParser::RemovePrimitiveFromLeft(extracted); 400 headerSize = IMAPParser::ExtractNextElement(headerSize); 401 int32 size = atoi(headerSize); 402 403 status_t status = fConnectionReader.ReadToFile(size, data); 404 if (status != B_OK) { 405 if (!fOutData) 406 fStorage.DeleteMessage(minMessage.uid); 407 return false; 408 } 409 410 // read last ")" line 411 BString lastLine; 412 fConnectionReader.GetNextLine(lastLine); 413 414 fUnhandled--; 415 416 bool bodyIsComing = true; 417 if (fFetchBodyLimit >= 0 && fFetchBodyLimit <= messageSize) 418 bodyIsComing = false; 419 420 int32 uid = fIMAPMailbox.MessageNumberToUID(message); 421 if (uid >= 0) 422 fIMAPMailbox.Listener().HeaderFetched(uid, data, bodyIsComing); 423 424 if (!bodyIsComing) 425 return true; 426 427 deleter.Detach(); 428 FetchBodyCommand* bodyCommand = new FetchBodyCommand(fIMAPMailbox, message, 429 data); 430 fIMAPMailbox.AddAfterQuakeCommand(bodyCommand); 431 432 return true; 433} 434 435 436// #pragma mark - 437 438 439FetchBodyCommand::FetchBodyCommand(IMAPMailbox& mailbox, int32 message, 440 BPositionIO* data) 441 : 442 IMAPMailboxCommand(mailbox), 443 444 fMessage(message), 445 fOutData(data) 446{ 447} 448 449 450FetchBodyCommand::~FetchBodyCommand() 451{ 452 delete fOutData; 453} 454 455 456BString 457FetchBodyCommand::Command() 458{ 459 BString command = "FETCH "; 460 command << fMessage; 461 command += " (FLAGS BODY.PEEK[TEXT])"; 462 return command; 463} 464 465 466bool 467FetchBodyCommand::Handle(const BString& response) 468{ 469 if (response.FindFirst("FETCH") < 0) 470 return false; 471 472 BString extracted = response; 473 int32 message; 474 if (!IMAPParser::RemoveUntagedFromLeft(extracted, "FETCH", message)) 475 return false; 476 if (message != fMessage) 477 return false; 478 479 int32 flags = FetchMinMessageCommand::ExtractFlags(extracted); 480 fStorage.SetFlags(fIMAPMailbox.MessageNumberToUID(message), flags); 481 482 int32 textPos = extracted.FindFirst("BODY[TEXT]"); 483 if (textPos < 0) 484 return false; 485 extracted.Remove(0, textPos + strlen("BODY[TEXT]") + 1); 486 BString bodySize = IMAPParser::ExtractBetweenBrackets(extracted, "{", "}"); 487 bodySize = IMAPParser::ExtractNextElement(bodySize); 488 int32 size = atoi(bodySize); 489 TRACE("Body size %i\n", (int)size); 490 fOutData->Seek(0, SEEK_END); 491 status_t status = fConnectionReader.ReadToFile(size, fOutData); 492 if (status != B_OK) 493 return false; 494 495 // read last ")" line 496 BString lastLine; 497 fConnectionReader.GetNextLine(lastLine); 498 499 int32 uid = fIMAPMailbox.MessageNumberToUID(message); 500 if (uid >= 0) 501 fIMAPMailbox.Listener().BodyFetched(uid, fOutData); 502 else 503 fIMAPMailbox.Listener().FetchEnd(); 504 505 return true; 506} 507 508 509// #pragma mark - 510 511 512SetFlagsCommand::SetFlagsCommand(IMAPMailbox& mailbox, int32 message, 513 int32 flags) 514 : 515 IMAPMailboxCommand(mailbox), 516 517 fMessage(message), 518 fFlags(flags) 519{ 520} 521 522 523BString 524SetFlagsCommand::Command() 525{ 526 BString command = "STORE "; 527 command << fMessage; 528 command += " FLAGS ("; 529 command += GenerateFlagList(fFlags); 530 command += ")"; 531 return command; 532} 533 534 535bool 536SetFlagsCommand::Handle(const BString& response) 537{ 538 return false; 539} 540 541 542BString 543SetFlagsCommand::GenerateFlagList(int32 flags) 544{ 545 BString flagList; 546 547 if ((flags & kSeen) != 0) 548 flagList += "\\Seen "; 549 if ((flags & kAnswered) != 0) 550 flagList += "\\Answered "; 551 if ((flags & kFlagged) != 0) 552 flagList += "\\Flagged "; 553 if ((flags & kDeleted) != 0) 554 flagList += "\\Deleted "; 555 if ((flags & kDraft) != 0) 556 flagList += "\\Draft "; 557 558 return flagList.Trim(); 559} 560 561 562// #pragma mark - 563 564 565AppendCommand::AppendCommand(IMAPMailbox& mailbox, BPositionIO& message, 566 off_t size, int32 flags, time_t time) 567 : 568 IMAPMailboxCommand(mailbox), 569 570 fMessageData(message), 571 fDataSize(size), 572 fFlags(flags), 573 fTime(time) 574{ 575} 576 577 578BString 579AppendCommand::Command() 580{ 581 BString command = "APPEND "; 582 command << fIMAPMailbox.Mailbox(); 583 command += " ("; 584 command += SetFlagsCommand::GenerateFlagList(fFlags); 585 command += ")"; 586 command += " {"; 587 command << fDataSize; 588 command += "}"; 589 return command; 590} 591 592 593bool 594AppendCommand::Handle(const BString& response) 595{ 596 if (response.FindFirst("+") != 0) 597 return false; 598 fMessageData.Seek(0, SEEK_SET); 599 600 const int32 kBunchSize = 1024; // 1Kb 601 char buffer[kBunchSize]; 602 603 int32 writeSize = fDataSize; 604 while (writeSize > 0) { 605 int32 bunchSize = writeSize < kBunchSize ? writeSize : kBunchSize; 606 fMessageData.Read(buffer, bunchSize); 607 int nWritten = fIMAPMailbox.SendRawData(buffer, bunchSize); 608 if (nWritten < 0) 609 return false; 610 writeSize -= nWritten; 611 TRACE("%i\n", (int)writeSize); 612 } 613 614 fIMAPMailbox.SendRawData(CRLF, strlen(CRLF)); 615 return true; 616} 617 618 619// #pragma mark - 620 621 622ExistsHandler::ExistsHandler(IMAPMailbox& mailbox) 623 : 624 IMAPMailboxCommand(mailbox) 625{ 626} 627 628 629bool 630ExistsHandler::Handle(const BString& response) 631{ 632 if (response.FindFirst("EXISTS") < 0) 633 return false; 634 635 int32 exists = 0; 636 if (!IMAPParser::ExtractUntagedFromLeft(response, "EXISTS", exists)) 637 return false; 638 639 int32 nMessages = fIMAPMailbox.GetCurrentMessageCount(); 640 if (exists <= nMessages) 641 return true; 642 643 MinMessageList& list = const_cast<MinMessageList&>( 644 fIMAPMailbox.GetMessageList()); 645 IMAPCommand* command = new FetchMinMessageCommand(fIMAPMailbox, 646 nMessages + 1, exists, &list, NULL); 647 fIMAPMailbox.AddAfterQuakeCommand(command); 648 649 fIMAPMailbox.Listener().NewMessagesToFetch(exists - nMessages); 650 651 command = new FetchMessageCommand(fIMAPMailbox, nMessages + 1, exists, 652 fIMAPMailbox.FetchBodyLimit()); 653 fIMAPMailbox.AddAfterQuakeCommand(command); 654 655 TRACE("EXISTS %i\n", (int)exists); 656 fIMAPMailbox.SendRawCommand("DONE"); 657 658 return true; 659} 660 661 662// #pragma mark - 663 664 665ExpungeCommmand::ExpungeCommmand(IMAPMailbox& mailbox) 666 : 667 IMAPMailboxCommand(mailbox) 668{ 669} 670 671 672BString 673ExpungeCommmand::Command() 674{ 675 return "EXPUNGE"; 676} 677 678 679bool 680ExpungeCommmand::Handle(const BString& response) 681{ 682 return false; 683} 684 685 686// #pragma mark - 687 688 689ExpungeHandler::ExpungeHandler(IMAPMailbox& mailbox) 690 : 691 IMAPMailboxCommand(mailbox) 692{ 693} 694 695 696bool 697ExpungeHandler::Handle(const BString& response) 698{ 699 if (response.FindFirst("EXPUNGE") < 0) 700 return false; 701 702 int32 expunge = 0; 703 if (!IMAPParser::ExtractUntagedFromLeft(response, "EXPUNGE", expunge)) 704 return false; 705 706 // remove from storage 707 IMAPStorage& storage = fIMAPMailbox.GetStorage(); 708 storage.DeleteMessage(fIMAPMailbox.MessageNumberToUID(expunge)); 709 710 // remove from min message list 711 MinMessageList& messageList = const_cast<MinMessageList&>( 712 fIMAPMailbox.GetMessageList()); 713 messageList.erase(messageList.begin() + expunge - 1); 714 715 TRACE("EXPUNGE %i\n", (int)expunge); 716 717 // the watching loop restarts again, we need to watch again to because 718 // some IDLE implementation stop sending notifications 719 fIMAPMailbox.SendRawCommand("DONE"); 720 return true; 721} 722 723 724// #pragma mark - 725 726 727FlagsHandler::FlagsHandler(IMAPMailbox& mailbox) 728 : 729 IMAPMailboxCommand(mailbox) 730{ 731} 732 733 734bool 735FlagsHandler::Handle(const BString& response) 736{ 737 if (response.FindFirst("FETCH") < 0) 738 return false; 739 740 int32 fetch = 0; 741 if (!IMAPParser::ExtractUntagedFromLeft(response, "FETCH", fetch)) 742 return false; 743 744 int32 flags = FetchMinMessageCommand::ExtractFlags(response); 745 int32 uid = fIMAPMailbox.MessageNumberToUID(fetch); 746 fStorage.SetFlags(uid, flags); 747 TRACE("FlagsHandler id %i flags %i\n", (int)fetch, (int)flags); 748 fIMAPMailbox.SendRawCommand("DONE"); 749 750 return true; 751} 752 753 754// #pragma mark - 755 756 757BString 758ListCommand::Command() 759{ 760 fFolders.clear(); 761 return "LIST \"\" \"*\""; 762} 763 764 765bool 766ListCommand::Handle(const BString& response) 767{ 768 return ParseList("LIST", response, fFolders); 769} 770 771 772const StringList& 773ListCommand::FolderList() 774{ 775 return fFolders; 776} 777 778 779bool 780ListCommand::ParseList(const char* command, const BString& response, 781 StringList& list) 782{ 783 int32 textPos = response.FindFirst(command); 784 if (textPos < 0) 785 return false; 786 BString extracted = response; 787 788 extracted.Remove(0, textPos + strlen(command) + 1); 789 extracted.Trim(); 790 if (extracted[0] == '(') { 791 BString flags = IMAPParser::ExtractBetweenBrackets(extracted, "(", ")"); 792 if (flags.IFindFirst("\\Noselect") >= 0) 793 return true; 794 textPos = extracted.FindFirst(")"); 795 extracted.Remove(0, textPos + 1); 796 } 797 798 IMAPParser::RemovePrimitiveFromLeft(extracted); 799 extracted.Trim(); 800 // remove quotation marks 801 extracted.Remove(0, 1); 802 extracted.Truncate(extracted.Length() - 1); 803 804 list.push_back(extracted); 805 return true; 806} 807 808 809// #pragma mark - 810 811 812BString 813ListSubscribedCommand::Command() 814{ 815 fFolders.clear(); 816 return "LSUB \"\" \"*\""; 817} 818 819 820bool 821ListSubscribedCommand::Handle(const BString& response) 822{ 823 return ListCommand::ParseList("LSUB", response, fFolders); 824} 825 826 827const StringList& 828ListSubscribedCommand::FolderList() 829{ 830 return fFolders; 831} 832 833 834// #pragma mark - 835 836 837SubscribeCommand::SubscribeCommand(const char* mailboxName) 838 : 839 fMailboxName(mailboxName) 840{ 841} 842 843 844BString 845SubscribeCommand::Command() 846{ 847 BString command = "SUBSCRIBE \""; 848 command += fMailboxName; 849 command += "\""; 850 return command; 851} 852 853 854bool 855SubscribeCommand::Handle(const BString& response) 856{ 857 return false; 858} 859 860 861// #pragma mark - 862 863 864UnsubscribeCommand::UnsubscribeCommand(const char* mailboxName) 865 : 866 fMailboxName(mailboxName) 867{ 868} 869 870 871BString 872UnsubscribeCommand::Command() 873{ 874 BString command = "UNSUBSCRIBE \""; 875 command += fMailboxName; 876 command += "\""; 877 return command; 878} 879 880 881bool 882UnsubscribeCommand::Handle(const BString& response) 883{ 884 return false; 885} 886 887 888// #pragma mark - 889 890 891GetQuotaCommand::GetQuotaCommand(const char* mailboxName) 892 : 893 fMailboxName(mailboxName), 894 fUsedStorage(0), 895 fTotalStorage(0) 896{ 897} 898 899 900BString 901GetQuotaCommand::Command() 902{ 903 BString command = "GETQUOTA \""; 904 command += fMailboxName; 905 command += "\""; 906 return command; 907} 908 909 910bool 911GetQuotaCommand::Handle(const BString& response) 912{ 913 if (response.FindFirst("QUOTA") < 0) 914 return false; 915 916 BString data = IMAPParser::ExtractBetweenBrackets(response, "(", ")"); 917 IMAPParser::RemovePrimitiveFromLeft(data); 918 fUsedStorage = (uint64)IMAPParser::RemoveIntegerFromLeft(data) * 1024; 919 fTotalStorage = (uint64)IMAPParser::RemoveIntegerFromLeft(data) * 1024; 920 921 return true; 922} 923 924 925uint64 926GetQuotaCommand::UsedStorage() 927{ 928 return fUsedStorage; 929} 930 931 932uint64 933GetQuotaCommand::TotalStorage() 934{ 935 return fTotalStorage; 936} 937