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