xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/IMAPFolder.cpp (revision 68ea01249e1e2088933cb12f9c28d4e5c5d1c9ef)
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