1 /* 2 * Copyright 2011-2015, 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 "Commands.h" 9 10 #include <stdlib.h> 11 12 #include <AutoDeleter.h> 13 14 15 #define DEBUG_IMAP_HANDLER 16 #ifdef DEBUG_IMAP_HANDLER 17 # include <stdio.h> 18 # define TRACE(x...) printf(x) 19 #else 20 # define TRACE(x...) ; 21 #endif 22 23 24 using namespace BPrivate; 25 26 27 /*! Maximum size of commands to the server (soft limit) */ 28 const size_t kMaxCommandLength = 2048; 29 30 31 static void 32 PutFlag(BString& string, const char* flag) 33 { 34 if (!string.IsEmpty()) 35 string += " "; 36 string += flag; 37 } 38 39 40 static BString 41 GenerateFlagString(uint32 flags) 42 { 43 BString string; 44 45 if ((flags & IMAP::kSeen) != 0) 46 PutFlag(string, "\\Seen "); 47 if ((flags & IMAP::kAnswered) != 0) 48 PutFlag(string, "\\Answered "); 49 if ((flags & IMAP::kFlagged) != 0) 50 PutFlag(string, "\\Flagged "); 51 if ((flags & IMAP::kDeleted) != 0) 52 PutFlag(string, "\\Deleted "); 53 if ((flags & IMAP::kDraft) != 0) 54 PutFlag(string, "\\Draft "); 55 56 return string; 57 } 58 59 60 static uint32 61 ParseFlags(IMAP::ArgumentList& list) 62 { 63 uint32 flags = 0; 64 for (int32 i = 0; i < list.CountItems(); i++) { 65 if (list.EqualsAt(i, "\\Seen")) 66 flags |= IMAP::kSeen; 67 else if (list.EqualsAt(i, "\\Answered")) 68 flags |= IMAP::kAnswered; 69 else if (list.EqualsAt(i, "\\Flagged")) 70 flags |= IMAP::kFlagged; 71 else if (list.EqualsAt(i, "\\Deleted")) 72 flags |= IMAP::kDeleted; 73 else if (list.EqualsAt(i, "\\Draft")) 74 flags |= IMAP::kDraft; 75 } 76 return flags; 77 } 78 79 80 // #pragma mark - 81 82 83 namespace IMAP { 84 85 86 Handler::Handler() 87 { 88 } 89 90 91 Handler::~Handler() 92 { 93 } 94 95 96 // #pragma mark - 97 98 99 Command::~Command() 100 { 101 } 102 103 104 status_t 105 Command::HandleTagged(Response& response) 106 { 107 if (response.StringAt(0) == "OK") 108 return B_OK; 109 if (response.StringAt(0) == "BAD") 110 return B_BAD_VALUE; 111 if (response.StringAt(0) == "NO") 112 return B_NOT_ALLOWED; 113 114 return B_ERROR; 115 } 116 117 118 // #pragma mark - 119 120 #if 0 121 HandlerListener::~HandlerListener() 122 { 123 } 124 125 126 void 127 HandlerListener::ExpungeReceived(int32 number) 128 { 129 } 130 131 132 void 133 HandlerListener::ExistsReceived(int32 number) 134 { 135 } 136 #endif 137 138 // #pragma mark - 139 140 141 RawCommand::RawCommand(const BString& command) 142 : 143 fCommand(command) 144 { 145 } 146 147 148 BString 149 RawCommand::CommandString() 150 { 151 return fCommand; 152 } 153 154 155 // #pragma mark - 156 157 158 LoginCommand::LoginCommand(const char* user, const char* password) 159 : 160 fUser(user), 161 fPassword(password) 162 { 163 } 164 165 166 BString 167 LoginCommand::CommandString() 168 { 169 BString command = "LOGIN "; 170 command << "\"" << fUser << "\" " << "\"" << fPassword << "\""; 171 172 return command; 173 } 174 175 176 bool 177 LoginCommand::HandleUntagged(Response& response) 178 { 179 if (!response.EqualsAt(0, "OK") || !response.IsListAt(1, '[')) 180 return false; 181 182 // TODO: we only support capabilities at the moment 183 ArgumentList& list = response.ListAt(1); 184 if (!list.EqualsAt(0, "CAPABILITY")) 185 return false; 186 187 fCapabilities.MakeEmpty(); 188 while (list.CountItems() > 1) 189 fCapabilities.AddItem(list.RemoveItemAt(1)); 190 191 TRACE("CAPABILITY: %s\n", fCapabilities.ToString().String()); 192 return true; 193 } 194 195 196 // #pragma mark - 197 198 199 SelectCommand::SelectCommand() 200 : 201 fNextUID(0), 202 fUIDValidity(0) 203 { 204 } 205 206 207 SelectCommand::SelectCommand(const char* name) 208 : 209 fNextUID(0), 210 fUIDValidity(0) 211 { 212 SetTo(name); 213 } 214 215 216 BString 217 SelectCommand::CommandString() 218 { 219 if (fMailboxName == "") 220 return ""; 221 222 BString command = "SELECT \""; 223 command += fMailboxName; 224 command += "\""; 225 return command; 226 } 227 228 229 bool 230 SelectCommand::HandleUntagged(Response& response) 231 { 232 if (response.EqualsAt(0, "OK") && response.IsListAt(1, '[')) { 233 const ArgumentList& arguments = response.ListAt(1); 234 if (arguments.EqualsAt(0, "UIDVALIDITY") 235 && arguments.IsNumberAt(1)) { 236 fUIDValidity = arguments.NumberAt(1); 237 return true; 238 } else if (arguments.EqualsAt(0, "UIDNEXT") 239 && arguments.IsNumberAt(1)) { 240 fNextUID = arguments.NumberAt(1); 241 return true; 242 } 243 } 244 245 return false; 246 } 247 248 249 void 250 SelectCommand::SetTo(const char* mailboxName) 251 { 252 RFC3501Encoding encoding; 253 fMailboxName = encoding.Encode(mailboxName); 254 } 255 256 257 // #pragma mark - 258 259 260 BString 261 CapabilityHandler::CommandString() 262 { 263 return "CAPABILITY"; 264 } 265 266 267 bool 268 CapabilityHandler::HandleUntagged(Response& response) 269 { 270 if (!response.IsCommand("CAPABILITY")) 271 return false; 272 273 fCapabilities.MakeEmpty(); 274 while (response.CountItems() > 1) 275 fCapabilities.AddItem(response.RemoveItemAt(1)); 276 277 TRACE("CAPABILITY: %s\n", fCapabilities.ToString().String()); 278 return true; 279 } 280 281 282 // #pragma mark - 283 284 285 FetchMessageEntriesCommand::FetchMessageEntriesCommand( 286 MessageEntryList& entries, uint32 from, uint32 to, bool uids) 287 : 288 fEntries(entries), 289 fFrom(from), 290 fTo(to), 291 fUIDs(uids) 292 { 293 } 294 295 296 BString 297 FetchMessageEntriesCommand::CommandString() 298 { 299 BString command = fUIDs ? "UID FETCH " : "FETCH "; 300 command << fFrom; 301 if (fFrom != fTo) 302 command << ":" << fTo; 303 304 command << " (FLAGS RFC822.SIZE"; 305 if (!fUIDs) 306 command << " UID"; 307 command << ")"; 308 309 return command; 310 } 311 312 313 bool 314 FetchMessageEntriesCommand::HandleUntagged(Response& response) 315 { 316 if (!response.EqualsAt(1, "FETCH") || !response.IsListAt(2)) 317 return false; 318 319 MessageEntry entry; 320 321 ArgumentList& list = response.ListAt(2); 322 for (int32 i = 0; i < list.CountItems(); i += 2) { 323 if (list.EqualsAt(i, "UID") && list.IsNumberAt(i + 1)) 324 entry.uid = list.NumberAt(i + 1); 325 else if (list.EqualsAt(i, "RFC822.SIZE") && list.IsNumberAt(i + 1)) 326 entry.size = list.NumberAt(i + 1); 327 else if (list.EqualsAt(i, "FLAGS") && list.IsListAt(i + 1)) { 328 // Parse flags 329 ArgumentList& flags = list.ListAt(i + 1); 330 entry.flags = ParseFlags(flags); 331 } 332 } 333 334 if (entry.uid == 0) 335 return false; 336 337 fEntries.push_back(entry); 338 return true; 339 } 340 341 342 // #pragma mark - 343 344 345 FetchCommand::FetchCommand(uint32 from, uint32 to, uint32 flags) 346 : 347 fFlags(flags) 348 { 349 fSequence << from; 350 if (from != to) 351 fSequence << ":" << to; 352 } 353 354 355 /*! Builds the sequence from the passed in UID list, and takes \a max entries 356 at maximum. If the sequence gets too large, it might fetch less entries 357 than specified. The fetched UIDs will be removed from the \uids list. 358 */ 359 FetchCommand::FetchCommand(MessageUIDList& uids, size_t max, uint32 flags) 360 : 361 fFlags(flags) 362 { 363 // Build sequence string 364 max = std::min(max, uids.size()); 365 366 size_t index = 0; 367 while (index < max && (size_t)fSequence.Length() < kMaxCommandLength) { 368 // Get start of range 369 uint32 first = uids[index++]; 370 uint32 last = first; 371 if (!fSequence.IsEmpty()) 372 fSequence += ","; 373 fSequence << first; 374 375 for (; index < max; index++) { 376 uint32 uid = uids[index]; 377 if (uid != last + 1) 378 break; 379 380 last = uid; 381 } 382 383 if (last != first) 384 fSequence << ":" << last; 385 } 386 387 uids.erase(uids.begin(), uids.begin() + index); 388 } 389 390 391 void 392 FetchCommand::SetListener(FetchListener* listener) 393 { 394 fListener = listener; 395 } 396 397 398 BString 399 FetchCommand::CommandString() 400 { 401 BString command = "UID FETCH "; 402 command += fSequence; 403 404 command += " (UID "; 405 if ((fFlags & kFetchFlags) != 0) 406 command += "FLAGS "; 407 switch (fFlags & kFetchAll) { 408 case kFetchHeader: 409 command += "RFC822.HEADER"; 410 break; 411 case kFetchBody: 412 command += "BODY.PEEK[TEXT]"; 413 break; 414 case kFetchAll: 415 command += "BODY.PEEK[]"; 416 break; 417 } 418 command += ")"; 419 420 return command; 421 } 422 423 424 bool 425 FetchCommand::HandleUntagged(Response& response) 426 { 427 if (!response.EqualsAt(1, "FETCH") || !response.IsListAt(2)) 428 return false; 429 430 ArgumentList& list = response.ListAt(2); 431 uint32 uid = 0; 432 uint32 flags = 0; 433 434 for (int32 i = 0; i < list.CountItems(); i += 2) { 435 if (list.EqualsAt(i, "UID") && list.IsNumberAt(i + 1)) 436 uid = list.NumberAt(i + 1); 437 else if (list.EqualsAt(i, "FLAGS") && list.IsListAt(i + 1)) { 438 // Parse flags 439 ArgumentList& flagList = list.ListAt(i + 1); 440 flags = ParseFlags(flagList); 441 } 442 } 443 444 if (fListener != NULL) 445 fListener->FetchedData(fFlags, uid, flags); 446 return true; 447 } 448 449 450 bool 451 FetchCommand::HandleLiteral(Response& response, ArgumentList& arguments, 452 BDataIO& stream, size_t& length) 453 { 454 if (fListener == NULL || !response.EqualsAt(1, "FETCH") 455 || !response.IsListAt(2)) 456 return false; 457 458 return fListener->FetchData(fFlags, stream, length); 459 } 460 461 462 // #pragma mark - 463 464 465 SetFlagsCommand::SetFlagsCommand(uint32 uid, uint32 flags) 466 : 467 fUID(uid), 468 fFlags(flags) 469 { 470 } 471 472 473 BString 474 SetFlagsCommand::CommandString() 475 { 476 BString command = "STORE "; 477 command << fUID << " FLAGS (" << GenerateFlagString(fFlags) << ")"; 478 479 return command; 480 } 481 482 483 bool 484 SetFlagsCommand::HandleUntagged(Response& response) 485 { 486 // We're not that interested in the outcome, apparently 487 return response.EqualsAt(1, "FETCH"); 488 } 489 490 491 // #pragma mark - 492 493 494 #if 0 495 AppendCommand::AppendCommand(IMAPMailbox& mailbox, BPositionIO& message, 496 off_t size, int32 flags, time_t time) 497 : 498 IMAPMailboxCommand(mailbox), 499 500 fMessageData(message), 501 fDataSize(size), 502 fFlags(flags), 503 fTime(time) 504 { 505 } 506 507 508 BString 509 AppendCommand::CommandString() 510 { 511 BString command = "APPEND "; 512 command << fIMAPMailbox.Mailbox(); 513 command += " ("; 514 command += SetFlagsCommand::GenerateFlagList(fFlags); 515 command += ")"; 516 command += " {"; 517 command << fDataSize; 518 command += "}"; 519 return command; 520 } 521 522 523 bool 524 AppendCommand::HandleUntagged(const BString& response) 525 { 526 if (response.FindFirst("+") != 0) 527 return false; 528 fMessageData.Seek(0, SEEK_SET); 529 530 const int32 kBunchSize = 1024; // 1Kb 531 char buffer[kBunchSize]; 532 533 int32 writeSize = fDataSize; 534 while (writeSize > 0) { 535 int32 bunchSize = writeSize < kBunchSize ? writeSize : kBunchSize; 536 fMessageData.Read(buffer, bunchSize); 537 int nWritten = fIMAPMailbox.SendRawData(buffer, bunchSize); 538 if (nWritten < 0) 539 return false; 540 writeSize -= nWritten; 541 TRACE("%i\n", (int)writeSize); 542 } 543 544 fIMAPMailbox.SendRawData(CRLF, strlen(CRLF)); 545 return true; 546 } 547 #endif 548 549 550 // #pragma mark - 551 552 553 ExistsHandler::ExistsHandler() 554 { 555 } 556 557 558 void 559 ExistsHandler::SetListener(ExistsListener* listener) 560 { 561 fListener = listener; 562 } 563 564 565 bool 566 ExistsHandler::HandleUntagged(Response& response) 567 { 568 if (!response.EqualsAt(1, "EXISTS") || !response.IsNumberAt(0)) 569 return false; 570 571 uint32 count = response.NumberAt(0); 572 573 if (fListener != NULL) 574 fListener->MessageExistsReceived(count); 575 576 return true; 577 } 578 579 580 // #pragma mark - 581 582 583 ExpungeCommand::ExpungeCommand() 584 { 585 } 586 587 588 BString 589 ExpungeCommand::CommandString() 590 { 591 return "EXPUNGE"; 592 } 593 594 595 // #pragma mark - 596 597 598 ExpungeHandler::ExpungeHandler() 599 { 600 } 601 602 603 void 604 ExpungeHandler::SetListener(ExpungeListener* listener) 605 { 606 fListener = listener; 607 } 608 609 610 bool 611 ExpungeHandler::HandleUntagged(Response& response) 612 { 613 if (!response.EqualsAt(1, "EXPUNGE") || !response.IsNumberAt(0)) 614 return false; 615 616 uint32 index = response.NumberAt(0); 617 618 if (fListener != NULL) 619 fListener->MessageExpungeReceived(index); 620 621 return true; 622 } 623 624 625 // #pragma mark - 626 627 628 #if 0 629 FlagsHandler::FlagsHandler(IMAPMailbox& mailbox) 630 : 631 IMAPMailboxCommand(mailbox) 632 { 633 } 634 635 636 bool 637 FlagsHandler::HandleUntagged(const BString& response) 638 { 639 if (response.FindFirst("FETCH") < 0) 640 return false; 641 642 int32 fetch = 0; 643 if (!IMAPParser::ExtractUntagedFromLeft(response, "FETCH", fetch)) 644 return false; 645 646 int32 flags = FetchMinMessageCommand::ExtractFlags(response); 647 int32 uid = fIMAPMailbox.MessageNumberToUID(fetch); 648 fStorage.SetFlags(uid, flags); 649 TRACE("FlagsHandler id %i flags %i\n", (int)fetch, (int)flags); 650 fIMAPMailbox.SendRawCommand("DONE"); 651 652 return true; 653 } 654 #endif 655 656 657 // #pragma mark - 658 659 660 ListCommand::ListCommand(const char* prefix, bool subscribedOnly) 661 : 662 fPrefix(prefix), 663 fSubscribedOnly(subscribedOnly) 664 { 665 } 666 667 668 BString 669 ListCommand::CommandString() 670 { 671 BString command = _Command(); 672 command += " \"\" \""; 673 if (fPrefix != NULL) 674 command << fEncoding.Encode(fPrefix) << "%"; 675 else 676 command += "*"; 677 command += "\""; 678 679 return command; 680 } 681 682 683 bool 684 ListCommand::HandleUntagged(Response& response) 685 { 686 if (response.IsCommand(_Command()) && response.IsStringAt(2) 687 && response.IsStringAt(3)) { 688 fSeparator = response.StringAt(2); 689 690 BString folder = response.StringAt(3); 691 if (folder == "") 692 return true; 693 694 try { 695 folder = fEncoding.Decode(folder); 696 // The folder INBOX is always case insensitive 697 if (folder.ICompare("INBOX") == 0) 698 folder = "Inbox"; 699 fFolders.Add(folder); 700 } catch (ParseException& exception) { 701 // Decoding failed, just add the plain text 702 fprintf(stderr, "Decoding \"%s\" failed: %s\n", folder.String(), 703 exception.Message()); 704 fFolders.Add(folder); 705 } 706 return true; 707 } 708 709 return false; 710 } 711 712 713 const BStringList& 714 ListCommand::FolderList() 715 { 716 return fFolders; 717 } 718 719 720 const char* 721 ListCommand::_Command() const 722 { 723 return fSubscribedOnly ? "LSUB" : "LIST"; 724 } 725 726 727 // #pragma mark - 728 729 730 SubscribeCommand::SubscribeCommand(const char* mailboxName) 731 : 732 fMailboxName(mailboxName) 733 { 734 } 735 736 737 BString 738 SubscribeCommand::CommandString() 739 { 740 BString command = "SUBSCRIBE \""; 741 command += fMailboxName; 742 command += "\""; 743 return command; 744 } 745 746 747 // #pragma mark - 748 749 750 UnsubscribeCommand::UnsubscribeCommand(const char* mailboxName) 751 : 752 fMailboxName(mailboxName) 753 { 754 } 755 756 757 BString 758 UnsubscribeCommand::CommandString() 759 { 760 BString command = "UNSUBSCRIBE \""; 761 command += fMailboxName; 762 command += "\""; 763 return command; 764 } 765 766 767 // #pragma mark - 768 769 770 GetQuotaCommand::GetQuotaCommand(const char* mailboxName) 771 : 772 fMailboxName(mailboxName), 773 fUsedStorage(0), 774 fTotalStorage(0) 775 { 776 } 777 778 779 BString 780 GetQuotaCommand::CommandString() 781 { 782 BString command = "GETQUOTAROOT \""; 783 command += fMailboxName; 784 command += "\""; 785 return command; 786 } 787 788 789 bool 790 GetQuotaCommand::HandleUntagged(Response& response) 791 { 792 if (!response.IsCommand("QUOTA") || !response.IsListAt(2)) 793 return false; 794 795 const ArgumentList& arguments = response.ListAt(2); 796 if (!arguments.EqualsAt(0, "STORAGE")) 797 return false; 798 799 fUsedStorage = (uint64)arguments.NumberAt(1) * 1024; 800 fTotalStorage = (uint64)arguments.NumberAt(2) * 1024; 801 802 return true; 803 } 804 805 806 uint64 807 GetQuotaCommand::UsedStorage() 808 { 809 return fUsedStorage; 810 } 811 812 813 uint64 814 GetQuotaCommand::TotalStorage() 815 { 816 return fTotalStorage; 817 } 818 819 820 } // namespace 821