1 /* 2 * Copyright 2012-2016, Axel Dörfler, axeld@pinc-software.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "IMAPFolder.h" 8 9 #include <set> 10 11 #include <ByteOrder.h> 12 #include <Debug.h> 13 #include <Directory.h> 14 #include <File.h> 15 #include <fs_attr.h> 16 #include <Messenger.h> 17 #include <Node.h> 18 #include <Path.h> 19 20 #include <NodeMessage.h> 21 22 #include "IMAPProtocol.h" 23 24 25 static const char* kMailboxNameAttribute = "IMAP:mailbox"; 26 static const char* kUIDValidityAttribute = "IMAP:uidvalidity"; 27 static const char* kLastUIDAttribute = "IMAP:lastuid"; 28 static const char* kStateAttribute = "IMAP:state"; 29 static const char* kFlagsAttribute = "IMAP:flags"; 30 static const char* kUIDAttribute = "MAIL:unique_id"; 31 32 33 class TemporaryFile : public BFile { 34 public: 35 TemporaryFile(BFile& file) 36 : 37 fFile(file), 38 fDeleteFile(false) 39 { 40 } 41 42 ~TemporaryFile() 43 { 44 if (fDeleteFile) { 45 fFile.Unset(); 46 BEntry(fPath.Path()).Remove(); 47 } 48 } 49 50 status_t Init(const BPath& path, entry_ref& ref) 51 { 52 int32 tries = 53; 53 while (true) { 54 BString name("temp-mail-"); 55 name << system_time(); 56 57 fPath = path; 58 fPath.Append(name.String()); 59 60 status_t status = fFile.SetTo(fPath.Path(), 61 B_CREATE_FILE | B_FAIL_IF_EXISTS | B_READ_WRITE); 62 if (status == B_FILE_EXISTS && tries-- > 0) 63 continue; 64 if (status != B_OK) 65 return status; 66 67 fDeleteFile = true; 68 return get_ref_for_path(fPath.Path(), &ref); 69 } 70 } 71 72 void KeepFile() 73 { 74 fDeleteFile = false; 75 } 76 77 private: 78 BFile& fFile; 79 BPath fPath; 80 bool fDeleteFile; 81 }; 82 83 84 // #pragma mark - 85 86 87 IMAPFolder::IMAPFolder(IMAPProtocol& protocol, const BString& mailboxName, 88 const entry_ref& ref) 89 : 90 BHandler(mailboxName.String()), 91 fProtocol(protocol), 92 fRef(ref), 93 fMailboxName(mailboxName), 94 fUIDValidity(UINT32_MAX), 95 fLastUID(0), 96 fListener(NULL), 97 fFolderStateInitialized(false), 98 fQuitFolderState(false) 99 { 100 mutex_init(&fLock, "imap folder lock"); 101 mutex_init(&fFolderStateLock, "imap folder state lock"); 102 } 103 104 105 IMAPFolder::~IMAPFolder() 106 { 107 MutexLocker locker(fLock); 108 if (!fFolderStateInitialized && fListener != NULL) { 109 fQuitFolderState = true; 110 locker.Unlock(); 111 wait_for_thread(fReadFolderStateThread, NULL); 112 } 113 } 114 115 116 status_t 117 IMAPFolder::Init() 118 { 119 // Initialize from folder attributes 120 BNode node(&fRef); 121 status_t status = node.InitCheck(); 122 if (status != B_OK) 123 return status; 124 125 node_ref nodeRef; 126 status = node.GetNodeRef(&nodeRef); 127 if (status != B_OK) 128 return status; 129 130 fNodeID = nodeRef.node; 131 132 BString originalMailboxName; 133 if (node.ReadAttrString(kMailboxNameAttribute, &originalMailboxName) == B_OK 134 && originalMailboxName != fMailboxName) { 135 // TODO: mailbox name has changed 136 } 137 138 fUIDValidity = _ReadUInt32(node, kUIDValidityAttribute); 139 fLastUID = _ReadUInt32(node, kLastUIDAttribute); 140 printf("IMAP: %s, last UID %" B_PRIu32 "\n", fMailboxName.String(), 141 fLastUID); 142 143 attr_info info; 144 status = node.GetAttrInfo(kStateAttribute, &info); 145 if (status == B_OK) { 146 struct entry { 147 uint32 uid; 148 uint32 flags; 149 } _PACKED; 150 struct entry* entries = (struct entry*)malloc(info.size); 151 if (entries == NULL) 152 return B_NO_MEMORY; 153 154 ssize_t bytesRead = node.ReadAttr(kStateAttribute, B_RAW_TYPE, 0, 155 entries, info.size); 156 if (bytesRead != info.size) 157 return B_BAD_DATA; 158 159 for (size_t i = 0; i < info.size / sizeof(entry); i++) { 160 uint32 uid = B_BENDIAN_TO_HOST_INT32(entries[i].uid); 161 uint32 flags = B_BENDIAN_TO_HOST_INT32(entries[i].flags); 162 163 fFlagsMap.insert(std::make_pair(uid, flags)); 164 } 165 } 166 167 return B_OK; 168 } 169 170 171 void 172 IMAPFolder::SetListener(FolderListener* listener) 173 { 174 ASSERT(fListener == NULL); 175 176 fListener = listener; 177 178 // Initialize current state from actual folder 179 // TODO: this should be done in another thread 180 _InitializeFolderState(); 181 } 182 183 184 void 185 IMAPFolder::SetUIDValidity(uint32 uidValidity) 186 { 187 if (fUIDValidity == uidValidity) 188 return; 189 190 // TODO: delete all mails that have the same UID validity value we had 191 fUIDValidity = uidValidity; 192 193 BNode node(&fRef); 194 _WriteUInt32(node, kUIDValidityAttribute, uidValidity); 195 } 196 197 198 status_t 199 IMAPFolder::GetMessageEntryRef(uint32 uid, entry_ref& ref) 200 { 201 MutexLocker locker(fLock); 202 return _GetMessageEntryRef(uid, ref); 203 } 204 205 206 status_t 207 IMAPFolder::GetMessageUID(const entry_ref& ref, uint32& uid) const 208 { 209 BNode node(&ref); 210 status_t status = node.InitCheck(); 211 if (status != B_OK) 212 return status; 213 214 uid = _ReadUniqueID(node); 215 if (uid == 0) 216 return B_ENTRY_NOT_FOUND; 217 218 return B_OK; 219 } 220 221 222 uint32 223 IMAPFolder::MessageFlags(uint32 uid) 224 { 225 MutexLocker locker(fLock); 226 UIDToFlagsMap::const_iterator found = fFlagsMap.find(uid); 227 if (found == fFlagsMap.end()) 228 return 0; 229 230 return found->second; 231 } 232 233 234 /*! Synchronizes the message flags/state from the server with the local 235 one. 236 */ 237 void 238 IMAPFolder::SyncMessageFlags(uint32 uid, uint32 mailboxFlags) 239 { 240 if (uid > LastUID()) 241 return; 242 243 entry_ref ref; 244 BNode node; 245 246 while (true) { 247 status_t status = GetMessageEntryRef(uid, ref); 248 if (status == B_ENTRY_NOT_FOUND) { 249 // The message does not exist anymore locally, delete it on the 250 // server 251 // TODO: copy it to the trash directory first! 252 if (fProtocol.Settings()->DeleteRemoteWhenLocal()) 253 fProtocol.UpdateMessageFlags(*this, uid, IMAP::kDeleted); 254 return; 255 } 256 if (status == B_OK) 257 status = node.SetTo(&ref); 258 if (status == B_TIMED_OUT) { 259 // We don't know the message state yet 260 fPendingFlagsMap.insert(std::make_pair(uid, mailboxFlags)); 261 } 262 if (status != B_OK) 263 return; 264 265 break; 266 } 267 fSynchronizedUIDsSet.insert(uid); 268 269 uint32 previousFlags = MessageFlags(uid); 270 uint32 currentFlags = previousFlags; 271 if (_MailToIMAPFlags(node, currentFlags) != B_OK) 272 return; 273 274 // Compare flags to previous/current flags, and update either the 275 // message on the server, or the message locally (or even both) 276 277 uint32 nextFlags = mailboxFlags; 278 _TestMessageFlags(previousFlags, mailboxFlags, currentFlags, 279 IMAP::kSeen, nextFlags); 280 _TestMessageFlags(previousFlags, mailboxFlags, currentFlags, 281 IMAP::kAnswered, nextFlags); 282 283 if (nextFlags != previousFlags) 284 _WriteFlags(node, nextFlags); 285 if (currentFlags != nextFlags) { 286 // Update mail message attributes 287 BMessage attributes; 288 _IMAPToMailFlags(nextFlags, attributes); 289 node << attributes; 290 291 fFlagsMap[uid] = nextFlags; 292 } 293 if (mailboxFlags != nextFlags) { 294 // Update server flags 295 fProtocol.UpdateMessageFlags(*this, uid, nextFlags); 296 } 297 } 298 299 300 void 301 IMAPFolder::MessageEntriesFetched() 302 { 303 _WaitForFolderState(); 304 305 // Synchronize all pending flags first 306 UIDToFlagsMap::const_iterator pendingIterator = fPendingFlagsMap.begin(); 307 for (; pendingIterator != fPendingFlagsMap.end(); pendingIterator++) 308 SyncMessageFlags(pendingIterator->first, pendingIterator->second); 309 310 fPendingFlagsMap.clear(); 311 312 // Delete all local messages that are no longer found on the server 313 314 MutexLocker locker(fLock); 315 UIDSet deleteUIDs; 316 UIDToRefMap::const_iterator iterator = fRefMap.begin(); 317 for (; iterator != fRefMap.end(); iterator++) { 318 uint32 uid = iterator->first; 319 if (fSynchronizedUIDsSet.find(uid) == fSynchronizedUIDsSet.end()) 320 deleteUIDs.insert(uid); 321 } 322 323 fSynchronizedUIDsSet.clear(); 324 locker.Unlock(); 325 326 UIDSet::const_iterator deleteIterator = deleteUIDs.begin(); 327 for (; deleteIterator != deleteUIDs.end(); deleteIterator++) 328 _DeleteLocalMessage(*deleteIterator); 329 } 330 331 332 /*! Stores the given \a stream into a temporary file using the provided 333 BFile object. A new file will be created, and the \a ref object will 334 point to it. The file will remain open when this method exits without 335 an error. 336 337 \a length will reflect how many bytes are left to read in case there 338 was an error. 339 */ 340 status_t 341 IMAPFolder::StoreMessage(uint32 fetchFlags, BDataIO& stream, 342 size_t& length, entry_ref& ref, BFile& file) 343 { 344 BPath path; 345 status_t status = path.SetTo(&fRef); 346 if (status != B_OK) 347 return status; 348 349 TemporaryFile temporaryFile(file); 350 status = temporaryFile.Init(path, ref); 351 if (status != B_OK) 352 return status; 353 354 status = _WriteStream(file, stream, length); 355 if (status == B_OK) 356 temporaryFile.KeepFile(); 357 358 return status; 359 } 360 361 362 /*! Writes UID, and flags to the message, and notifies the protocol that a 363 message has been fetched. This method also closes the \a file passed in. 364 */ 365 void 366 IMAPFolder::MessageStored(entry_ref& ref, BFile& file, uint32 fetchFlags, 367 uint32 uid, uint32 flags) 368 { 369 _WriteUniqueIDValidity(file); 370 _WriteUniqueID(file, uid); 371 if ((fetchFlags & IMAP::kFetchFlags) != 0) 372 _WriteFlags(file, flags); 373 374 BMessage attributes; 375 _IMAPToMailFlags(flags, attributes); 376 377 fProtocol.MessageStored(*this, ref, file, fetchFlags, attributes); 378 file.Unset(); 379 380 fRefMap.insert(std::make_pair(uid, ref)); 381 382 if (uid > fLastUID) { 383 // Update last known UID 384 fLastUID = uid; 385 386 BNode directory(&fRef); 387 status_t status = _WriteUInt32(directory, kLastUIDAttribute, uid); 388 if (status != B_OK) { 389 fprintf(stderr, "IMAP: Could not write last UID for mailbox " 390 "%s: %s\n", fMailboxName.String(), strerror(status)); 391 } 392 } 393 } 394 395 396 /*! Pushes the refs for the pending bodies to the pending bodies list. 397 This allows to prevent retrieving bodies more than once. 398 */ 399 void 400 IMAPFolder::RegisterPendingBodies(IMAP::MessageUIDList& uids, 401 const BMessenger* replyTo) 402 { 403 MutexLocker locker(fLock); 404 405 MessengerList messengers; 406 if (replyTo != NULL) 407 messengers.push_back(*replyTo); 408 409 IMAP::MessageUIDList::const_iterator iterator = uids.begin(); 410 for (; iterator != uids.end(); iterator++) { 411 if (replyTo != NULL) 412 fPendingBodies[*iterator].push_back(*replyTo); 413 else 414 fPendingBodies[*iterator].begin(); 415 } 416 } 417 418 419 /*! Appends the given \a stream as body to the message file for the 420 specified unique ID. The file will remain open when this method exits 421 without an error. 422 423 \a length will reflect how many bytes are left to read in case there 424 were an error. 425 */ 426 status_t 427 IMAPFolder::StoreBody(uint32 uid, BDataIO& stream, size_t& length, 428 entry_ref& ref, BFile& file) 429 { 430 status_t status = GetMessageEntryRef(uid, ref); 431 if (status != B_OK) 432 return status; 433 434 status = file.SetTo(&ref, B_OPEN_AT_END | B_WRITE_ONLY); 435 if (status != B_OK) 436 return status; 437 438 BPath path(&ref); 439 printf("IMAP: write body to %s\n", path.Path()); 440 441 return _WriteStream(file, stream, length); 442 } 443 444 445 /*! Notifies the protocol that a body has been fetched. 446 This method also closes the \a file passed in. 447 */ 448 void 449 IMAPFolder::BodyStored(entry_ref& ref, BFile& file, uint32 uid) 450 { 451 BMessage attributes; 452 fProtocol.MessageStored(*this, ref, file, IMAP::kFetchBody, attributes); 453 file.Unset(); 454 455 _NotifyStoredBody(ref, uid, B_OK); 456 } 457 458 459 void 460 IMAPFolder::StoringBodyFailed(const entry_ref& ref, uint32 uid, status_t error) 461 { 462 _NotifyStoredBody(ref, uid, error); 463 } 464 465 466 void 467 IMAPFolder::DeleteMessage(uint32 uid) 468 { 469 // TODO: move message to trash (server side) 470 471 _DeleteLocalMessage(uid); 472 } 473 474 475 void 476 IMAPFolder::MessageReceived(BMessage* message) 477 { 478 switch (message->what) { 479 default: 480 BHandler::MessageReceived(message); 481 break; 482 } 483 } 484 485 486 void 487 IMAPFolder::_WaitForFolderState() 488 { 489 while (true) { 490 MutexLocker locker(fFolderStateLock); 491 if (fFolderStateInitialized) 492 return; 493 } 494 } 495 496 497 void 498 IMAPFolder::_InitializeFolderState() 499 { 500 mutex_lock(&fFolderStateLock); 501 502 fReadFolderStateThread = spawn_thread(&IMAPFolder::_ReadFolderState, 503 "IMAP folder state", B_NORMAL_PRIORITY, this); 504 if (fReadFolderStateThread >= 0) 505 resume_thread(fReadFolderStateThread); 506 else 507 mutex_unlock(&fFolderStateLock); 508 } 509 510 511 void 512 IMAPFolder::_ReadFolderState() 513 { 514 BDirectory directory(&fRef); 515 BEntry entry; 516 while (directory.GetNextEntry(&entry) == B_OK) { 517 entry_ref ref; 518 BNode node; 519 if (!entry.IsFile() || entry.GetRef(&ref) != B_OK 520 || node.SetTo(&entry) != B_OK) 521 continue; 522 523 uint32 uidValidity = _ReadUniqueIDValidity(node); 524 if (uidValidity != fUIDValidity) { 525 // TODO: add file to mailbox 526 continue; 527 } 528 uint32 uid = _ReadUniqueID(node); 529 uint32 flags = _ReadFlags(node); 530 531 MutexLocker locker(fLock); 532 if (fQuitFolderState) 533 return; 534 535 fRefMap.insert(std::make_pair(uid, ref)); 536 fFlagsMap.insert(std::make_pair(uid, flags)); 537 538 // // TODO: make sure a listener exists at this point! 539 // std::set<uint32>::iterator found = lastUIDs.find(uid); 540 // if (found != lastUIDs.end()) { 541 // // The message is still around 542 // lastUIDs.erase(found); 543 // 544 // uint32 flagsFound = MessageFlags(uid); 545 // if (flagsFound != flags) { 546 // // Its flags have changed locally, and need to be updated 547 // fListener->MessageFlagsChanged(_Token(uid), ref, 548 // flagsFound, flags); 549 // } 550 // } else { 551 // // This is a new message 552 // // TODO: the token must be the originating token! 553 // uid = fListener->MessageAdded(_Token(uid), ref); 554 // _WriteUniqueID(node, uid); 555 // } 556 // 557 } 558 559 fFolderStateInitialized = true; 560 mutex_unlock(&fFolderStateLock); 561 } 562 563 564 /*static*/ status_t 565 IMAPFolder::_ReadFolderState(void* self) 566 { 567 ((IMAPFolder*)self)->_ReadFolderState(); 568 return B_OK; 569 } 570 571 572 const MessageToken 573 IMAPFolder::_Token(uint32 uid) const 574 { 575 MessageToken token; 576 token.mailboxName = fMailboxName; 577 token.uidValidity = fUIDValidity; 578 token.uid = uid; 579 580 return token; 581 } 582 583 584 void 585 IMAPFolder::_NotifyStoredBody(const entry_ref& ref, uint32 uid, status_t status) 586 { 587 MutexLocker locker(fLock); 588 MessengerMap::iterator found = fPendingBodies.find(uid); 589 if (found != fPendingBodies.end()) { 590 MessengerList messengers = found->second; 591 fPendingBodies.erase(found); 592 locker.Unlock(); 593 594 MessengerList::iterator iterator = messengers.begin(); 595 for (; iterator != messengers.end(); iterator++) 596 BInboundMailProtocol::ReplyBodyFetched(*iterator, ref, status); 597 } 598 } 599 600 601 status_t 602 IMAPFolder::_GetMessageEntryRef(uint32 uid, entry_ref& ref) const 603 { 604 UIDToRefMap::const_iterator found = fRefMap.find(uid); 605 if (found == fRefMap.end()) 606 return !fFolderStateInitialized ? B_TIMED_OUT : B_ENTRY_NOT_FOUND; 607 608 ref = found->second; 609 return B_OK; 610 } 611 612 613 status_t 614 IMAPFolder::_DeleteLocalMessage(uint32 uid) 615 { 616 entry_ref ref; 617 status_t status = GetMessageEntryRef(uid, ref); 618 if (status != B_OK) 619 return status; 620 621 fRefMap.erase(uid); 622 fFlagsMap.erase(uid); 623 624 BEntry entry(&ref); 625 return entry.Remove(); 626 } 627 628 629 void 630 IMAPFolder::_IMAPToMailFlags(uint32 flags, BMessage& attributes) 631 { 632 // TODO: add some utility function for this in libmail.so 633 if ((flags & IMAP::kAnswered) != 0) 634 attributes.AddString(B_MAIL_ATTR_STATUS, "Answered"); 635 else if ((flags & IMAP::kFlagged) != 0) 636 attributes.AddString(B_MAIL_ATTR_STATUS, "Starred"); 637 else if ((flags & IMAP::kSeen) != 0) 638 attributes.AddString(B_MAIL_ATTR_STATUS, "Read"); 639 } 640 641 642 status_t 643 IMAPFolder::_MailToIMAPFlags(BNode& node, uint32& flags) 644 { 645 BString mailStatus; 646 status_t status = node.ReadAttrString(B_MAIL_ATTR_STATUS, &mailStatus); 647 if (status != B_OK) 648 return status; 649 650 flags &= ~(IMAP::kAnswered | IMAP::kSeen); 651 652 // TODO: add some utility function for this in libmail.so 653 if (mailStatus == "Answered") 654 flags |= IMAP::kAnswered | IMAP::kSeen; 655 else if (mailStatus == "Read") 656 flags |= IMAP::kSeen; 657 else if (mailStatus == "Starred") 658 flags |= IMAP::kFlagged | IMAP::kSeen; 659 660 return B_OK; 661 } 662 663 664 void 665 IMAPFolder::_TestMessageFlags(uint32 previousFlags, uint32 mailboxFlags, 666 uint32 currentFlags, uint32 testFlag, uint32& nextFlags) 667 { 668 if ((previousFlags & testFlag) != (mailboxFlags & testFlag)) { 669 if ((previousFlags & testFlag) == (currentFlags & testFlag)) { 670 // The flags on the mailbox changed, update local flags 671 nextFlags &= ~testFlag; 672 nextFlags |= mailboxFlags & testFlag; 673 } else { 674 // Both flags changed. Since we don't have the means to do 675 // conflict resolution, we use a best effort mechanism 676 nextFlags |= testFlag; 677 } 678 return; 679 } 680 681 // Previous message flags, and mailbox flags are identical, see 682 // if the user changed the flag locally 683 if ((currentFlags & testFlag) != (previousFlags & testFlag)) { 684 // Flag changed, update mailbox 685 nextFlags &= ~testFlag; 686 nextFlags |= currentFlags & testFlag; 687 } 688 } 689 690 691 uint32 692 IMAPFolder::_ReadUniqueID(BNode& node) const 693 { 694 // For compatibility we must assume that the UID is stored as a string 695 BString string; 696 if (node.ReadAttrString(kUIDAttribute, &string) != B_OK) 697 return 0; 698 699 return strtoul(string.String(), NULL, 0); 700 } 701 702 703 status_t 704 IMAPFolder::_WriteUniqueID(BNode& node, uint32 uid) const 705 { 706 // For compatibility we must assume that the UID is stored as a string 707 BString string; 708 string << uid; 709 710 return node.WriteAttrString(kUIDAttribute, &string); 711 } 712 713 714 uint32 715 IMAPFolder::_ReadUniqueIDValidity(BNode& node) const 716 { 717 718 return _ReadUInt32(node, kUIDValidityAttribute); 719 } 720 721 722 status_t 723 IMAPFolder::_WriteUniqueIDValidity(BNode& node) const 724 { 725 return _WriteUInt32(node, kUIDValidityAttribute, fUIDValidity); 726 } 727 728 729 uint32 730 IMAPFolder::_ReadFlags(BNode& node) const 731 { 732 return _ReadUInt32(node, kFlagsAttribute); 733 } 734 735 736 status_t 737 IMAPFolder::_WriteFlags(BNode& node, uint32 flags) const 738 { 739 return _WriteUInt32(node, kFlagsAttribute, flags); 740 } 741 742 743 uint32 744 IMAPFolder::_ReadUInt32(BNode& node, const char* attribute) const 745 { 746 uint32 value; 747 ssize_t bytesRead = node.ReadAttr(attribute, B_UINT32_TYPE, 0, 748 &value, sizeof(uint32)); 749 if (bytesRead == (ssize_t)sizeof(uint32)) 750 return value; 751 752 return 0; 753 } 754 755 756 status_t 757 IMAPFolder::_WriteUInt32(BNode& node, const char* attribute, uint32 value) const 758 { 759 ssize_t bytesWritten = node.WriteAttr(attribute, B_UINT32_TYPE, 0, 760 &value, sizeof(uint32)); 761 if (bytesWritten == (ssize_t)sizeof(uint32)) 762 return B_OK; 763 764 return bytesWritten < 0 ? bytesWritten : B_IO_ERROR; 765 } 766 767 768 status_t 769 IMAPFolder::_WriteStream(BFile& file, BDataIO& stream, size_t& length) const 770 { 771 char buffer[65535]; 772 while (length > 0) { 773 ssize_t bytesRead = stream.Read(buffer, 774 std::min(sizeof(buffer), length)); 775 if (bytesRead < 0) 776 return bytesRead; 777 if (bytesRead <= 0) 778 break; 779 780 length -= bytesRead; 781 782 ssize_t bytesWritten = file.Write(buffer, bytesRead); 783 if (bytesWritten < 0) 784 return bytesWritten; 785 if (bytesWritten != bytesRead) 786 return B_IO_ERROR; 787 } 788 789 return B_OK; 790 } 791