1 /* 2 * Copyright 2012-2013, 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 <Node.h> 17 #include <Path.h> 18 19 #include "Commands.h" 20 21 #include "IMAPProtocol.h" 22 23 24 static const char* kMailboxNameAttribute = "IMAP:mailbox"; 25 static const char* kUIDValidityAttribute = "IMAP:uidvalidity"; 26 static const char* kLastUIDAttribute = "IMAP:lastuid"; 27 static const char* kStateAttribute = "IMAP:state"; 28 static const char* kFlagsAttribute = "IMAP:flags"; 29 static const char* kUIDAttribute = "MAIL:unique_id"; 30 31 32 class TemporaryFile : public BFile { 33 public: 34 TemporaryFile(BFile& file) 35 : 36 fFile(file), 37 fDeleteFile(false) 38 { 39 } 40 41 ~TemporaryFile() 42 { 43 if (fDeleteFile) { 44 fFile.Unset(); 45 BEntry(fPath.Path()).Remove(); 46 } 47 } 48 49 status_t Init(const BPath& path, entry_ref& ref) 50 { 51 int32 tries = 53; 52 while (true) { 53 BString name("temp-mail-"); 54 name << system_time(); 55 56 fPath = path; 57 fPath.Append(name.String()); 58 59 status_t status = fFile.SetTo(fPath.Path(), 60 B_CREATE_FILE | B_FAIL_IF_EXISTS | B_READ_WRITE); 61 if (status == B_FILE_EXISTS && tries-- > 0) 62 continue; 63 if (status != B_OK) 64 return status; 65 66 fDeleteFile = true; 67 return get_ref_for_path(fPath.Path(), &ref); 68 } 69 } 70 71 void KeepFile() 72 { 73 fDeleteFile = false; 74 } 75 76 private: 77 BFile& fFile; 78 BPath fPath; 79 bool fDeleteFile; 80 }; 81 82 83 // #pragma mark - 84 85 86 IMAPFolder::IMAPFolder(IMAPProtocol& protocol, const BString& mailboxName, 87 const entry_ref& ref) 88 : 89 BHandler(mailboxName.String()), 90 fProtocol(protocol), 91 fRef(ref), 92 fMailboxName(mailboxName), 93 fUIDValidity(UINT32_MAX), 94 fLastUID(0) 95 { 96 // Initialize from folder attributes 97 BNode node(&ref); 98 if (node.InitCheck() != B_OK) 99 return; 100 101 BString originalMailboxName; 102 if (node.ReadAttrString(kMailboxNameAttribute, &originalMailboxName) == B_OK 103 && originalMailboxName != mailboxName) { 104 // TODO: mailbox name has changed 105 } 106 107 fUIDValidity = _ReadUInt32(node, kUIDValidityAttribute); 108 fLastUID = _ReadUInt32(node, kLastUIDAttribute); 109 printf("IMAP: %s, last UID %" B_PRIu32 "\n", fMailboxName.String(), 110 fLastUID); 111 112 attr_info info; 113 status_t status = node.GetAttrInfo(kStateAttribute, &info); 114 if (status == B_OK) { 115 struct entry { 116 uint32 uid; 117 uint32 flags; 118 } _PACKED; 119 struct entry* entries = (struct entry*)malloc(info.size); 120 if (entries == NULL) { 121 // TODO: indicate B_NO_MEMORY 122 return; 123 } 124 125 ssize_t bytesRead = node.ReadAttr(kStateAttribute, B_RAW_TYPE, 0, 126 entries, info.size); 127 if (bytesRead != info.size) { 128 // TODO: indicate read error resp. corrupted data 129 return; 130 } 131 132 for (size_t i = 0; i < info.size / sizeof(entry); i++) { 133 uint32 uid = B_BENDIAN_TO_HOST_INT32(entries[i].uid); 134 uint32 flags = B_BENDIAN_TO_HOST_INT32(entries[i].flags); 135 136 fFlagsMap.insert(std::make_pair(uid, flags)); 137 } 138 } 139 140 // Initialize current state from actual folder 141 // TODO: this should be done in another thread 142 //_InitializeFolderState(); 143 } 144 145 146 IMAPFolder::~IMAPFolder() 147 { 148 } 149 150 151 void 152 IMAPFolder::SetListener(FolderListener* listener) 153 { 154 fListener = listener; 155 } 156 157 158 void 159 IMAPFolder::SetUIDValidity(uint32 uidValidity) 160 { 161 if (fUIDValidity == uidValidity) 162 return; 163 164 fUIDValidity = uidValidity; 165 166 BNode node(&fRef); 167 _WriteUInt32(node, kUIDValidityAttribute, uidValidity); 168 } 169 170 171 status_t 172 IMAPFolder::GetMessageEntryRef(uint32 uid, entry_ref& ref) const 173 { 174 UIDToRefMap::const_iterator found = fRefMap.find(uid); 175 if (found == fRefMap.end()) 176 return B_ENTRY_NOT_FOUND; 177 178 ref = found->second; 179 return B_OK; 180 } 181 182 183 uint32 184 IMAPFolder::MessageFlags(uint32 uid) const 185 { 186 UIDToFlagsMap::const_iterator found = fFlagsMap.find(uid); 187 if (found == fFlagsMap.end()) 188 return 0; 189 190 return found->second; 191 } 192 193 194 /*! Stores the given \a stream into a temporary file using the provided 195 BFile object. A new file will be created, and the \a ref object will 196 point to it. The file will remain open when this method exits without 197 an error. 198 199 \a length will reflect how many bytes are left to read in case there 200 were an error. 201 */ 202 status_t 203 IMAPFolder::StoreMessage(uint32 fetchFlags, BDataIO& stream, 204 size_t& length, entry_ref& ref, BFile& file) 205 { 206 BPath path; 207 status_t status = path.SetTo(&fRef); 208 if (status != B_OK) 209 return status; 210 211 TemporaryFile temporaryFile(file); 212 status = temporaryFile.Init(path, ref); 213 if (status != B_OK) 214 return status; 215 216 status = _WriteStream(file, stream, length); 217 if (status == B_OK) 218 temporaryFile.KeepFile(); 219 220 return status; 221 } 222 223 224 /*! Writes UID, and flags to the message, and notifies the protocol that a 225 message has been fetched. This method also closes the \a file passed in. 226 */ 227 void 228 IMAPFolder::MessageStored(entry_ref& ref, BFile& file, uint32 fetchFlags, 229 uint32 uid, uint32 flags) 230 { 231 _WriteUniqueID(file, uid); 232 if ((fetchFlags & IMAP::kFetchFlags) != 0) 233 _WriteFlags(file, flags); 234 235 // TODO: add some utility function for this in libmail.so 236 BMessage attributes; 237 if ((flags & IMAP::kAnswered) != 0) 238 attributes.AddString(B_MAIL_ATTR_STATUS, "Answered"); 239 else if ((flags & IMAP::kSeen) != 0) 240 attributes.AddString(B_MAIL_ATTR_STATUS, "Read"); 241 242 fProtocol.MessageStored(*this, ref, file, fetchFlags, attributes); 243 file.Unset(); 244 245 fRefMap.insert(std::make_pair(uid, ref)); 246 247 if (uid > fLastUID) { 248 // Update last known UID 249 fLastUID = uid; 250 251 BNode directory(&fRef); 252 status_t status = _WriteUInt32(directory, kLastUIDAttribute, uid); 253 if (status != B_OK) { 254 fprintf(stderr, "IMAP: Could not write last UID for mailbox " 255 "%s: %s\n", fMailboxName.String(), strerror(status)); 256 } 257 } 258 } 259 260 261 /*! Appends the given \a stream as body to the message file for the 262 specified unique ID. The file will remain open when this method exits 263 without an error. 264 265 \a length will reflect how many bytes are left to read in case there 266 were an error. 267 */ 268 status_t 269 IMAPFolder::StoreBody(uint32 uid, BDataIO& stream, size_t& length, 270 entry_ref& ref, BFile& file) 271 { 272 status_t status = GetMessageEntryRef(uid, ref); 273 if (status != B_OK) 274 return status; 275 276 status = file.SetTo(&ref, B_OPEN_AT_END | B_WRITE_ONLY); 277 if (status != B_OK) 278 return status; 279 280 BPath path(&ref); 281 printf("IMAP: write body to %s\n", path.Path()); 282 283 return _WriteStream(file, stream, length); 284 } 285 286 287 /*! Notifies the protocol that a body has been fetched. 288 This method also closes the \a file passed in. 289 */ 290 void 291 IMAPFolder::BodyStored(entry_ref& ref, BFile& file, uint32 uid) 292 { 293 BMessage attributes; 294 fProtocol.MessageStored(*this, ref, file, IMAP::kFetchBody, attributes); 295 file.Unset(); 296 } 297 298 299 void 300 IMAPFolder::DeleteMessage(uint32 uid) 301 { 302 } 303 304 305 /*! Called when the flags of a message changed on the server. This will update 306 the flags for the local file. 307 */ 308 void 309 IMAPFolder::SetMessageFlags(uint32 uid, uint32 flags) 310 { 311 } 312 313 314 void 315 IMAPFolder::MessageReceived(BMessage* message) 316 { 317 } 318 319 320 void 321 IMAPFolder::_InitializeFolderState() 322 { 323 // Create set of the last known UID state - if an entry is found, it 324 // is being removed from the list. The remaining entries were deleted. 325 std::set<uint32> lastUIDs; 326 UIDToFlagsMap::iterator iterator = fFlagsMap.begin(); 327 for (; iterator != fFlagsMap.end(); iterator++) 328 lastUIDs.insert(iterator->first); 329 330 BDirectory directory(&fRef); 331 BEntry entry; 332 while (directory.GetNextEntry(&entry) == B_OK) { 333 entry_ref ref; 334 BNode node; 335 if (!entry.IsFile() || entry.GetRef(&ref) != B_OK 336 || node.SetTo(&entry) != B_OK) 337 continue; 338 339 uint32 uid = _ReadUniqueID(node); 340 uint32 flags = _ReadFlags(node); 341 342 // TODO: make sure a listener exists at this point! 343 std::set<uint32>::iterator found = lastUIDs.find(uid); 344 if (found != lastUIDs.end()) { 345 // The message is still around 346 lastUIDs.erase(found); 347 348 uint32 flagsFound = MessageFlags(uid); 349 if (flagsFound != flags) { 350 // Its flags have changed locally, and need to be updated 351 fListener->MessageFlagsChanged(_Token(uid), ref, 352 flagsFound, flags); 353 } 354 } else { 355 // This is a new message 356 // TODO: the token must be the originating token! 357 uid = fListener->MessageAdded(_Token(uid), ref); 358 _WriteUniqueID(node, uid); 359 } 360 361 fRefMap.insert(std::make_pair(uid, ref)); 362 } 363 } 364 365 366 const MessageToken 367 IMAPFolder::_Token(uint32 uid) const 368 { 369 MessageToken token; 370 token.mailboxName = fMailboxName; 371 token.uidValidity = fUIDValidity; 372 token.uid = uid; 373 374 return token; 375 } 376 377 378 uint32 379 IMAPFolder::_ReadUniqueID(BNode& node) 380 { 381 // For compatibility we must assume that the UID is stored as a string 382 BString string; 383 if (node.ReadAttrString(kUIDAttribute, &string) != B_OK) 384 return 0; 385 386 return strtoul(string.String(), NULL, 0); 387 } 388 389 390 status_t 391 IMAPFolder::_WriteUniqueID(BNode& node, uint32 uid) 392 { 393 BString string; 394 string << uid; 395 396 return node.WriteAttrString(kUIDAttribute, &string); 397 } 398 399 400 uint32 401 IMAPFolder::_ReadFlags(BNode& node) 402 { 403 return _ReadUInt32(node, kFlagsAttribute); 404 } 405 406 407 status_t 408 IMAPFolder::_WriteFlags(BNode& node, uint32 flags) 409 { 410 return _WriteUInt32(node, kFlagsAttribute, flags); 411 } 412 413 414 uint32 415 IMAPFolder::_ReadUInt32(BNode& node, const char* attribute) 416 { 417 uint32 value; 418 ssize_t bytesRead = node.ReadAttr(attribute, B_UINT32_TYPE, 0, 419 &value, sizeof(uint32)); 420 if (bytesRead == (ssize_t)sizeof(uint32)) 421 return value; 422 423 return 0; 424 } 425 426 427 status_t 428 IMAPFolder::_WriteUInt32(BNode& node, const char* attribute, uint32 value) 429 { 430 ssize_t bytesWritten = node.WriteAttr(attribute, B_UINT32_TYPE, 0, 431 &value, sizeof(uint32)); 432 if (bytesWritten == (ssize_t)sizeof(uint32)) 433 return B_OK; 434 435 return bytesWritten < 0 ? bytesWritten : B_IO_ERROR; 436 } 437 438 439 status_t 440 IMAPFolder::_WriteStream(BFile& file, BDataIO& stream, size_t& length) 441 { 442 char buffer[65535]; 443 while (length > 0) { 444 ssize_t bytesRead = stream.Read(buffer, 445 std::min(sizeof(buffer), length)); 446 if (bytesRead < 0) 447 return bytesRead; 448 if (bytesRead <= 0) 449 break; 450 451 length -= bytesRead; 452 453 ssize_t bytesWritten = file.Write(buffer, bytesRead); 454 if (bytesWritten < 0) 455 return bytesWritten; 456 if (bytesWritten != bytesRead) 457 return B_IO_ERROR; 458 } 459 460 return B_OK; 461 } 462