1/*
2 * Copyright 2011, 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 "IMAPHandler.h"
9
10#include <stdlib.h>
11
12#include <AutoDeleter.h>
13
14#include "IMAPMailbox.h"
15#include "IMAPParser.h"
16#include "IMAPStorage.h"
17
18
19#define DEBUG_IMAP_HANDLER
20#ifdef DEBUG_IMAP_HANDLER
21#	include <stdio.h>
22#	define TRACE(x...) printf(x)
23#else
24#	define TRACE(x...) ;
25#endif
26
27
28using namespace BPrivate;
29
30
31IMAPCommand::~IMAPCommand()
32{
33}
34
35
36BString
37IMAPCommand::Command()
38{
39	return "";
40}
41
42
43IMAPMailboxCommand::IMAPMailboxCommand(IMAPMailbox& mailbox)
44	:
45	fIMAPMailbox(mailbox),
46	fStorage(mailbox.GetStorage()),
47	fConnectionReader(mailbox.GetConnectionReader())
48{
49}
50
51
52IMAPMailboxCommand::~IMAPMailboxCommand()
53{
54}
55
56
57// #pragma mark -
58
59
60MailboxSelectHandler::MailboxSelectHandler(IMAPMailbox& mailbox)
61	:
62	IMAPMailboxCommand(mailbox),
63
64	fMailboxName(""),
65	fNextUID(-1),
66	fUIDValidity(-1)
67{
68}
69
70
71BString
72MailboxSelectHandler::Command()
73{
74	if (fMailboxName == "")
75		return "";
76
77	BString command = "SELECT \"";
78	command += fMailboxName;
79	command += "\"";
80	return command;
81}
82
83
84bool
85MailboxSelectHandler::Handle(const BString& response)
86{
87	BString extracted = IMAPParser::ExtractStringAfter(response,
88		"* OK [UIDVALIDITY");
89	if (extracted != "") {
90		fUIDValidity = IMAPParser::RemoveIntegerFromLeft(extracted);
91		TRACE("UIDValidity %i\n", (int)fUIDValidity);
92		return true;
93	}
94
95	extracted = IMAPParser::ExtractStringAfter(response, "* OK [UIDNEXT");
96	if (extracted != "") {
97		fNextUID = IMAPParser::RemoveIntegerFromLeft(extracted);
98		TRACE("NextUID %i\n", (int)fNextUID);
99		return true;
100	}
101
102	return false;
103}
104
105
106// #pragma mark -
107
108
109CapabilityHandler::CapabilityHandler()
110	:
111	fCapabilities("")
112{
113}
114
115
116BString
117CapabilityHandler::Command()
118{
119	return "CAPABILITY";
120}
121
122
123bool
124CapabilityHandler::Handle(const BString& response)
125{
126	BString cap = IMAPParser::ExtractStringAfter(response, "* CAPABILITY");
127	if (cap == "")
128		return false;
129	fCapabilities = cap;
130	TRACE("CAPABILITY: %s\n", fCapabilities.String());
131	return true;
132}
133
134
135BString&
136CapabilityHandler::Capabilities()
137{
138	return fCapabilities;
139}
140
141
142// #pragma mark -
143
144
145FetchMinMessageCommand::FetchMinMessageCommand(IMAPMailbox& mailbox,
146	int32 message, MinMessageList* list, BPositionIO** data)
147	:
148	IMAPMailboxCommand(mailbox),
149
150	fMessage(message),
151	fEndMessage(-1),
152	fMinMessageList(list),
153	fData(data)
154{
155}
156
157
158FetchMinMessageCommand::FetchMinMessageCommand(IMAPMailbox& mailbox,
159	int32 firstMessage, int32 lastMessage, MinMessageList* list,
160	BPositionIO** data)
161	:
162	IMAPMailboxCommand(mailbox),
163
164	fMessage(firstMessage),
165	fEndMessage(lastMessage),
166	fMinMessageList(list),
167	fData(data)
168{
169}
170
171
172BString
173FetchMinMessageCommand::Command()
174{
175	if (fMessage <= 0)
176		return "";
177	BString command = "FETCH ";
178	command << fMessage;
179	if (fEndMessage > 0) {
180		command += ":";
181		command << fEndMessage;
182	}
183	command += " (UID FLAGS)";
184	return command;
185}
186
187
188bool
189FetchMinMessageCommand::Handle(const BString& response)
190{
191	BString extracted = response;
192	int32 message;
193	if (!IMAPParser::RemoveUntagedFromLeft(extracted, "FETCH", message))
194		return false;
195
196	// check if we requested this message
197	int32 end = message;
198	if (fEndMessage > 0)
199		end = fEndMessage;
200	if (message < fMessage && message > end)
201		return false;
202
203	MinMessage minMessage;
204	if (!ParseMinMessage(extracted, minMessage))
205		return false;
206
207	fMinMessageList->push_back(minMessage);
208	fStorage.AddNewMessage(minMessage.uid, minMessage.flags, fData);
209	return true;
210}
211
212
213bool
214FetchMinMessageCommand::ParseMinMessage(const BString& response,
215	MinMessage& minMessage)
216{
217	BString extracted = IMAPParser::ExtractNextElement(response);
218	BString uid = IMAPParser::ExtractElementAfter(extracted, "UID");
219	if (uid == "")
220		return false;
221	minMessage.uid = atoi(uid);
222
223	int32 flags = ExtractFlags(extracted);
224	minMessage.flags = flags;
225
226	return true;
227}
228
229
230int32
231FetchMinMessageCommand::ExtractFlags(const BString& response)
232{
233	int32 flags = 0;
234	BString flagsString = IMAPParser::ExtractElementAfter(response, "FLAGS");
235
236	while (true) {
237		BString flag = IMAPParser::RemovePrimitiveFromLeft(flagsString);
238		if (flag == "")
239			break;
240
241		if (flag == "\\Seen")
242			flags |= kSeen;
243		else if (flag == "\\Answered")
244			flags |= kAnswered;
245		else if (flag == "\\Flagged")
246			flags |= kFlagged;
247		else if (flag == "\\Deleted")
248			flags |= kDeleted;
249		else if (flag == "\\Draft")
250			flags |= kDraft;
251	}
252	return flags;
253}
254
255
256// #pragma mark -
257
258
259FetchMessageListCommand::FetchMessageListCommand(IMAPMailbox& mailbox,
260	MinMessageList* list, int32 nextId)
261	:
262	IMAPMailboxCommand(mailbox),
263
264	fMinMessageList(list),
265	fNextId(nextId)
266{
267}
268
269
270BString
271FetchMessageListCommand::Command()
272{
273	BString command = "UID FETCH 1:";
274	command << fNextId - 1;
275	command << " FLAGS";
276	return command;
277}
278
279
280bool
281FetchMessageListCommand::Handle(const BString& response)
282{
283	BString extracted = response;
284	int32 message;
285	if (!IMAPParser::RemoveUntagedFromLeft(extracted, "FETCH", message))
286		return false;
287
288	MinMessage minMessage;
289	if (!FetchMinMessageCommand::ParseMinMessage(extracted, minMessage))
290		return false;
291
292	fMinMessageList->push_back(minMessage);
293	return true;
294}
295
296
297// #pragma mark -
298
299
300FetchMessageCommand::FetchMessageCommand(IMAPMailbox& mailbox, int32 message,
301	BPositionIO* data, int32 fetchBodyLimit)
302	:
303	IMAPMailboxCommand(mailbox),
304
305	fMessage(message),
306	fEndMessage(-1),
307	fOutData(data),
308	fFetchBodyLimit(fetchBodyLimit)
309{
310}
311
312
313FetchMessageCommand::FetchMessageCommand(IMAPMailbox& mailbox,
314	int32 firstMessage, int32 lastMessage, int32 fetchBodyLimit)
315	:
316	IMAPMailboxCommand(mailbox),
317
318	fMessage(firstMessage),
319	fEndMessage(lastMessage),
320	fOutData(NULL),
321	fFetchBodyLimit(fetchBodyLimit)
322{
323	if (fEndMessage > 0)
324		fUnhandled = fEndMessage - fMessage + 1;
325	else
326		fUnhandled = 1;
327}
328
329
330FetchMessageCommand::~FetchMessageCommand()
331{
332	for (int32 i = 0; i < fUnhandled; i++)
333		fIMAPMailbox.Listener().FetchEnd();
334}
335
336
337BString
338FetchMessageCommand::Command()
339{
340	BString command = "FETCH ";
341	command << fMessage;
342	if (fEndMessage > 0) {
343		command += ":";
344		command << fEndMessage;
345	}
346	command += " (RFC822.SIZE RFC822.HEADER)";
347	return command;
348}
349
350
351bool
352FetchMessageCommand::Handle(const BString& response)
353{
354	BString extracted = response;
355	int32 message;
356	if (!IMAPParser::RemoveUntagedFromLeft(extracted, "FETCH", message))
357		return false;
358
359	// check if we requested this message
360	int32 end = message;
361	if (fEndMessage > 0)
362		end = fEndMessage;
363	if (message < fMessage && message > end)
364		return false;
365
366	const MinMessageList& list = fIMAPMailbox.GetMessageList();
367	int32 index = message - 1;
368	if (index < 0 || index >= (int32)list.size())
369		return false;
370	const MinMessage& minMessage = list[index];
371
372	BPositionIO* data = fOutData;
373	ObjectDeleter<BPositionIO> deleter;
374	if (!data) {
375		status_t status = fStorage.OpenMessage(minMessage.uid, &data);
376		if (status != B_OK) {
377			status = fStorage.AddNewMessage(minMessage.uid, minMessage.flags,
378				&data);
379		}
380		if (status != B_OK)
381			return false;
382		deleter.SetTo(data);
383	}
384
385	// read message size
386	BString messageSizeString = IMAPParser::ExtractElementAfter(extracted,
387		"RFC822.SIZE");
388	int32 messageSize = atoi(messageSizeString);
389	fStorage.SetCompleteMessageSize(minMessage.uid, messageSize);
390
391	// read header
392	int32 headerPos = extracted.FindFirst("RFC822.HEADER");
393	if (headerPos < 0) {
394		if (!fOutData)
395			fStorage.DeleteMessage(minMessage.uid);
396		return false;
397	}
398	extracted.Remove(0, headerPos + strlen("RFC822.HEADER") + 1);
399	BString headerSize = IMAPParser::RemovePrimitiveFromLeft(extracted);
400	headerSize = IMAPParser::ExtractNextElement(headerSize);
401	int32 size = atoi(headerSize);
402
403	status_t status = fConnectionReader.ReadToFile(size, data);
404	if (status != B_OK) {
405		if (!fOutData)
406			fStorage.DeleteMessage(minMessage.uid);
407		return false;
408	}
409
410	// read last ")" line
411	BString lastLine;
412	fConnectionReader.GetNextLine(lastLine);
413
414	fUnhandled--;
415
416	bool bodyIsComing = true;
417	if (fFetchBodyLimit >= 0 && fFetchBodyLimit <= messageSize)
418		bodyIsComing = false;
419
420	int32 uid = fIMAPMailbox.MessageNumberToUID(message);
421	if (uid >= 0)
422		fIMAPMailbox.Listener().HeaderFetched(uid, data, bodyIsComing);
423
424	if (!bodyIsComing)
425		return true;
426
427	deleter.Detach();
428	FetchBodyCommand* bodyCommand = new FetchBodyCommand(fIMAPMailbox, message,
429		data);
430	fIMAPMailbox.AddAfterQuakeCommand(bodyCommand);
431
432	return true;
433}
434
435
436// #pragma mark -
437
438
439FetchBodyCommand::FetchBodyCommand(IMAPMailbox& mailbox, int32 message,
440	BPositionIO* data)
441	:
442	IMAPMailboxCommand(mailbox),
443
444	fMessage(message),
445	fOutData(data)
446{
447}
448
449
450FetchBodyCommand::~FetchBodyCommand()
451{
452	delete fOutData;
453}
454
455
456BString
457FetchBodyCommand::Command()
458{
459	BString command = "FETCH ";
460	command << fMessage;
461	command += " (FLAGS BODY.PEEK[TEXT])";
462	return command;
463}
464
465
466bool
467FetchBodyCommand::Handle(const BString& response)
468{
469	if (response.FindFirst("FETCH") < 0)
470		return false;
471
472	BString extracted = response;
473	int32 message;
474	if (!IMAPParser::RemoveUntagedFromLeft(extracted, "FETCH", message))
475		return false;
476	if (message != fMessage)
477		return false;
478
479	int32 flags = FetchMinMessageCommand::ExtractFlags(extracted);
480	fStorage.SetFlags(fIMAPMailbox.MessageNumberToUID(message), flags);
481
482	int32 textPos = extracted.FindFirst("BODY[TEXT]");
483	if (textPos < 0)
484		return false;
485	extracted.Remove(0, textPos + strlen("BODY[TEXT]") + 1);
486	BString bodySize = IMAPParser::ExtractBetweenBrackets(extracted, "{", "}");
487	bodySize = IMAPParser::ExtractNextElement(bodySize);
488	int32 size = atoi(bodySize);
489	TRACE("Body size %i\n", (int)size);
490	fOutData->Seek(0, SEEK_END);
491	status_t status = fConnectionReader.ReadToFile(size, fOutData);
492	if (status != B_OK)
493		return false;
494
495	// read last ")" line
496	BString lastLine;
497	fConnectionReader.GetNextLine(lastLine);
498
499	int32 uid = fIMAPMailbox.MessageNumberToUID(message);
500	if (uid >= 0)
501		fIMAPMailbox.Listener().BodyFetched(uid, fOutData);
502	else
503		fIMAPMailbox.Listener().FetchEnd();
504
505	return true;
506}
507
508
509// #pragma mark -
510
511
512SetFlagsCommand::SetFlagsCommand(IMAPMailbox& mailbox, int32 message,
513	int32 flags)
514	:
515	IMAPMailboxCommand(mailbox),
516
517	fMessage(message),
518	fFlags(flags)
519{
520}
521
522
523BString
524SetFlagsCommand::Command()
525{
526	BString command = "STORE ";
527	command << fMessage;
528	command += " FLAGS (";
529	command += GenerateFlagList(fFlags);
530	command += ")";
531	return command;
532}
533
534
535bool
536SetFlagsCommand::Handle(const BString& response)
537{
538	return false;
539}
540
541
542BString
543SetFlagsCommand::GenerateFlagList(int32 flags)
544{
545	BString flagList;
546
547	if ((flags & kSeen) != 0)
548		flagList += "\\Seen ";
549	if ((flags & kAnswered) != 0)
550		flagList += "\\Answered ";
551	if ((flags & kFlagged) != 0)
552		flagList += "\\Flagged ";
553	if ((flags & kDeleted) != 0)
554		flagList += "\\Deleted ";
555	if ((flags & kDraft) != 0)
556		flagList += "\\Draft ";
557
558	return flagList.Trim();
559}
560
561
562// #pragma mark -
563
564
565AppendCommand::AppendCommand(IMAPMailbox& mailbox, BPositionIO& message,
566	off_t size, int32 flags, time_t time)
567	:
568	IMAPMailboxCommand(mailbox),
569
570	fMessageData(message),
571	fDataSize(size),
572	fFlags(flags),
573	fTime(time)
574{
575}
576
577
578BString
579AppendCommand::Command()
580{
581	BString command = "APPEND ";
582	command << fIMAPMailbox.Mailbox();
583	command += " (";
584	command += SetFlagsCommand::GenerateFlagList(fFlags);
585	command += ")";
586	command += " {";
587	command << fDataSize;
588	command += "}";
589	return command;
590}
591
592
593bool
594AppendCommand::Handle(const BString& response)
595{
596	if (response.FindFirst("+") != 0)
597		return false;
598	fMessageData.Seek(0, SEEK_SET);
599
600	const int32 kBunchSize = 1024; // 1Kb
601	char buffer[kBunchSize];
602
603	int32 writeSize = fDataSize;
604	while (writeSize > 0) {
605		int32 bunchSize = writeSize < kBunchSize ? writeSize : kBunchSize;
606		fMessageData.Read(buffer, bunchSize);
607		int nWritten = fIMAPMailbox.SendRawData(buffer, bunchSize);
608		if (nWritten < 0)
609			return false;
610		writeSize -= nWritten;
611		TRACE("%i\n", (int)writeSize);
612	}
613
614	fIMAPMailbox.SendRawData(CRLF, strlen(CRLF));
615	return true;
616}
617
618
619// #pragma mark -
620
621
622ExistsHandler::ExistsHandler(IMAPMailbox& mailbox)
623	:
624	IMAPMailboxCommand(mailbox)
625{
626}
627
628
629bool
630ExistsHandler::Handle(const BString& response)
631{
632	if (response.FindFirst("EXISTS") < 0)
633		return false;
634
635	int32 exists = 0;
636	if (!IMAPParser::ExtractUntagedFromLeft(response, "EXISTS", exists))
637		return false;
638
639	int32 nMessages = fIMAPMailbox.GetCurrentMessageCount();
640	if (exists <= nMessages)
641		return true;
642
643	MinMessageList& list = const_cast<MinMessageList&>(
644		fIMAPMailbox.GetMessageList());
645	IMAPCommand* command = new FetchMinMessageCommand(fIMAPMailbox,
646		nMessages + 1, exists, &list, NULL);
647	fIMAPMailbox.AddAfterQuakeCommand(command);
648
649	fIMAPMailbox.Listener().NewMessagesToFetch(exists - nMessages);
650
651	command = new FetchMessageCommand(fIMAPMailbox, nMessages + 1, exists,
652		fIMAPMailbox.FetchBodyLimit());
653	fIMAPMailbox.AddAfterQuakeCommand(command);
654
655	TRACE("EXISTS %i\n", (int)exists);
656	fIMAPMailbox.SendRawCommand("DONE");
657
658	return true;
659}
660
661
662// #pragma mark -
663
664
665ExpungeCommmand::ExpungeCommmand(IMAPMailbox& mailbox)
666	:
667	IMAPMailboxCommand(mailbox)
668{
669}
670
671
672BString
673ExpungeCommmand::Command()
674{
675	return "EXPUNGE";
676}
677
678
679bool
680ExpungeCommmand::Handle(const BString& response)
681{
682	return false;
683}
684
685
686// #pragma mark -
687
688
689ExpungeHandler::ExpungeHandler(IMAPMailbox& mailbox)
690	:
691	IMAPMailboxCommand(mailbox)
692{
693}
694
695
696bool
697ExpungeHandler::Handle(const BString& response)
698{
699	if (response.FindFirst("EXPUNGE") < 0)
700		return false;
701
702	int32 expunge = 0;
703	if (!IMAPParser::ExtractUntagedFromLeft(response, "EXPUNGE", expunge))
704		return false;
705
706	// remove from storage
707	IMAPStorage& storage = fIMAPMailbox.GetStorage();
708	storage.DeleteMessage(fIMAPMailbox.MessageNumberToUID(expunge));
709
710	// remove from min message list
711	MinMessageList& messageList = const_cast<MinMessageList&>(
712		fIMAPMailbox.GetMessageList());
713	messageList.erase(messageList.begin() + expunge - 1);
714
715	TRACE("EXPUNGE %i\n", (int)expunge);
716
717	// the watching loop restarts again, we need to watch again to because
718	// some IDLE implementation stop sending notifications
719	fIMAPMailbox.SendRawCommand("DONE");
720	return true;
721}
722
723
724// #pragma mark -
725
726
727FlagsHandler::FlagsHandler(IMAPMailbox& mailbox)
728	:
729	IMAPMailboxCommand(mailbox)
730{
731}
732
733
734bool
735FlagsHandler::Handle(const BString& response)
736{
737	if (response.FindFirst("FETCH") < 0)
738		return false;
739
740	int32 fetch = 0;
741	if (!IMAPParser::ExtractUntagedFromLeft(response, "FETCH", fetch))
742		return false;
743
744	int32 flags = FetchMinMessageCommand::ExtractFlags(response);
745	int32 uid = fIMAPMailbox.MessageNumberToUID(fetch);
746	fStorage.SetFlags(uid, flags);
747	TRACE("FlagsHandler id %i flags %i\n", (int)fetch, (int)flags);
748	fIMAPMailbox.SendRawCommand("DONE");
749
750	return true;
751}
752
753
754// #pragma mark -
755
756
757BString
758ListCommand::Command()
759{
760	fFolders.clear();
761	return "LIST \"\" \"*\"";
762}
763
764
765bool
766ListCommand::Handle(const BString& response)
767{
768	return ParseList("LIST", response, fFolders);
769}
770
771
772const StringList&
773ListCommand::FolderList()
774{
775	return fFolders;
776}
777
778
779bool
780ListCommand::ParseList(const char* command, const BString& response,
781	StringList& list)
782{
783	int32 textPos = response.FindFirst(command);
784	if (textPos < 0)
785		return false;
786	BString extracted = response;
787
788	extracted.Remove(0, textPos + strlen(command) + 1);
789	extracted.Trim();
790	if (extracted[0] == '(') {
791		BString flags = IMAPParser::ExtractBetweenBrackets(extracted, "(", ")");
792		if (flags.IFindFirst("\\Noselect") >= 0)
793			return true;
794		textPos = extracted.FindFirst(")");
795		extracted.Remove(0, textPos + 1);
796	}
797
798	IMAPParser::RemovePrimitiveFromLeft(extracted);
799	extracted.Trim();
800	// remove quotation marks
801	extracted.Remove(0, 1);
802	extracted.Truncate(extracted.Length() - 1);
803
804	list.push_back(extracted);
805	return true;
806}
807
808
809// #pragma mark -
810
811
812BString
813ListSubscribedCommand::Command()
814{
815	fFolders.clear();
816	return "LSUB \"\" \"*\"";
817}
818
819
820bool
821ListSubscribedCommand::Handle(const BString& response)
822{
823	return ListCommand::ParseList("LSUB", response, fFolders);
824}
825
826
827const StringList&
828ListSubscribedCommand::FolderList()
829{
830	return fFolders;
831}
832
833
834// #pragma mark -
835
836
837SubscribeCommand::SubscribeCommand(const char* mailboxName)
838	:
839	fMailboxName(mailboxName)
840{
841}
842
843
844BString
845SubscribeCommand::Command()
846{
847	BString command = "SUBSCRIBE \"";
848	command += fMailboxName;
849	command += "\"";
850	return command;
851}
852
853
854bool
855SubscribeCommand::Handle(const BString& response)
856{
857	return false;
858}
859
860
861// #pragma mark -
862
863
864UnsubscribeCommand::UnsubscribeCommand(const char* mailboxName)
865	:
866	fMailboxName(mailboxName)
867{
868}
869
870
871BString
872UnsubscribeCommand::Command()
873{
874	BString command = "UNSUBSCRIBE \"";
875	command += fMailboxName;
876	command += "\"";
877	return command;
878}
879
880
881bool
882UnsubscribeCommand::Handle(const BString& response)
883{
884	return false;
885}
886
887
888// #pragma mark -
889
890
891GetQuotaCommand::GetQuotaCommand(const char* mailboxName)
892	:
893	fMailboxName(mailboxName),
894	fUsedStorage(0),
895	fTotalStorage(0)
896{
897}
898
899
900BString
901GetQuotaCommand::Command()
902{
903	BString command = "GETQUOTA \"";
904	command += fMailboxName;
905	command += "\"";
906	return command;
907}
908
909
910bool
911GetQuotaCommand::Handle(const BString& response)
912{
913	if (response.FindFirst("QUOTA") < 0)
914		return false;
915
916	BString data = IMAPParser::ExtractBetweenBrackets(response, "(", ")");
917	IMAPParser::RemovePrimitiveFromLeft(data);
918	fUsedStorage = (uint64)IMAPParser::RemoveIntegerFromLeft(data) * 1024;
919	fTotalStorage = (uint64)IMAPParser::RemoveIntegerFromLeft(data) * 1024;
920
921	return true;
922}
923
924
925uint64
926GetQuotaCommand::UsedStorage()
927{
928	return fUsedStorage;
929}
930
931
932uint64
933GetQuotaCommand::TotalStorage()
934{
935	return fTotalStorage;
936}
937