xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Commands.cpp (revision c5287be1f3d70804d8ea3bcf035ea60ed961fe26)
1 /*
2  * Copyright 2011-2016, 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 "Commands.h"
9 
10 #include <stdlib.h>
11 
12 #include <AutoDeleter.h>
13 
14 
15 #define DEBUG_IMAP_HANDLER
16 #ifdef DEBUG_IMAP_HANDLER
17 #	include <stdio.h>
18 #	define TRACE(x...) printf(x)
19 #else
20 #	define TRACE(x...) ;
21 #endif
22 
23 
24 using namespace BPrivate;
25 
26 
27 /*!	Maximum size of commands to the server (soft limit) */
28 const size_t kMaxCommandLength = 2048;
29 
30 
31 static void
32 PutFlag(BString& string, const char* flag)
33 {
34 	if (!string.IsEmpty())
35 		string += " ";
36 	string += flag;
37 }
38 
39 
40 static BString
41 GenerateFlagString(uint32 flags)
42 {
43 	BString string;
44 
45 	if ((flags & IMAP::kSeen) != 0)
46 		PutFlag(string, "\\Seen");
47 	if ((flags & IMAP::kAnswered) != 0)
48 		PutFlag(string, "\\Answered");
49 	if ((flags & IMAP::kFlagged) != 0)
50 		PutFlag(string, "\\Flagged");
51 	if ((flags & IMAP::kDeleted) != 0)
52 		PutFlag(string, "\\Deleted");
53 	if ((flags & IMAP::kDraft) != 0)
54 		PutFlag(string, "\\Draft");
55 
56 	return string;
57 }
58 
59 
60 static uint32
61 ParseFlags(IMAP::ArgumentList& list)
62 {
63 	uint32 flags = 0;
64 	for (int32 i = 0; i < list.CountItems(); i++) {
65 		if (list.EqualsAt(i, "\\Seen"))
66 			flags |= IMAP::kSeen;
67 		else if (list.EqualsAt(i, "\\Answered"))
68 			flags |= IMAP::kAnswered;
69 		else if (list.EqualsAt(i, "\\Flagged"))
70 			flags |= IMAP::kFlagged;
71 		else if (list.EqualsAt(i, "\\Deleted"))
72 			flags |= IMAP::kDeleted;
73 		else if (list.EqualsAt(i, "\\Draft"))
74 			flags |= IMAP::kDraft;
75 	}
76 	return flags;
77 }
78 
79 
80 // #pragma mark -
81 
82 
83 namespace IMAP {
84 
85 
86 Handler::Handler()
87 {
88 }
89 
90 
91 Handler::~Handler()
92 {
93 }
94 
95 
96 // #pragma mark -
97 
98 
99 Command::~Command()
100 {
101 }
102 
103 
104 status_t
105 Command::HandleTagged(Response& response)
106 {
107 	if (response.StringAt(0) == "OK")
108 		return B_OK;
109 	if (response.StringAt(0) == "BAD")
110 		return B_BAD_VALUE;
111 	if (response.StringAt(0) == "NO")
112 		return B_NOT_ALLOWED;
113 
114 	return B_ERROR;
115 }
116 
117 
118 // #pragma mark -
119 
120 #if 0
121 HandlerListener::~HandlerListener()
122 {
123 }
124 
125 
126 void
127 HandlerListener::ExpungeReceived(int32 number)
128 {
129 }
130 
131 
132 void
133 HandlerListener::ExistsReceived(int32 number)
134 {
135 }
136 #endif
137 
138 // #pragma mark -
139 
140 
141 RawCommand::RawCommand(const BString& command)
142 	:
143 	fCommand(command)
144 {
145 }
146 
147 
148 BString
149 RawCommand::CommandString()
150 {
151 	return fCommand;
152 }
153 
154 
155 // #pragma mark -
156 
157 
158 LoginCommand::LoginCommand(const char* user, const char* password)
159 	:
160 	fUser(user),
161 	fPassword(password)
162 {
163 }
164 
165 
166 BString
167 LoginCommand::CommandString()
168 {
169 	BString command = "LOGIN ";
170 	command << "\"" << fUser << "\" " << "\"" << fPassword << "\"";
171 
172 	return command;
173 }
174 
175 
176 bool
177 LoginCommand::HandleUntagged(Response& response)
178 {
179 	if (!response.EqualsAt(0, "OK") || !response.IsListAt(1, '['))
180 		return false;
181 
182 	// TODO: we only support capabilities at the moment
183 	ArgumentList& list = response.ListAt(1);
184 	if (!list.EqualsAt(0, "CAPABILITY"))
185 		return false;
186 
187 	fCapabilities.MakeEmpty();
188 	while (list.CountItems() > 1)
189 		fCapabilities.AddItem(list.RemoveItemAt(1));
190 
191 	TRACE("CAPABILITY: %s\n", fCapabilities.ToString().String());
192 	return true;
193 }
194 
195 
196 // #pragma mark -
197 
198 
199 SelectCommand::SelectCommand()
200 	:
201 	fNextUID(0),
202 	fUIDValidity(0)
203 {
204 }
205 
206 
207 SelectCommand::SelectCommand(const char* name)
208 	:
209 	fNextUID(0),
210 	fUIDValidity(0)
211 {
212 	SetTo(name);
213 }
214 
215 
216 BString
217 SelectCommand::CommandString()
218 {
219 	if (fMailboxName == "")
220 		return "";
221 
222 	BString command = "SELECT \"";
223 	command += fMailboxName;
224 	command += "\"";
225 	return command;
226 }
227 
228 
229 bool
230 SelectCommand::HandleUntagged(Response& response)
231 {
232 	if (response.EqualsAt(0, "OK") && response.IsListAt(1, '[')) {
233 		const ArgumentList& arguments = response.ListAt(1);
234 		if (arguments.EqualsAt(0, "UIDVALIDITY")
235 			&& arguments.IsNumberAt(1)) {
236 			fUIDValidity = arguments.NumberAt(1);
237 			return true;
238 		} else if (arguments.EqualsAt(0, "UIDNEXT")
239 			&& arguments.IsNumberAt(1)) {
240 			fNextUID = arguments.NumberAt(1);
241 			return true;
242 		}
243 	}
244 
245 	return false;
246 }
247 
248 
249 void
250 SelectCommand::SetTo(const char* mailboxName)
251 {
252 	RFC3501Encoding encoding;
253 	fMailboxName = encoding.Encode(mailboxName);
254 }
255 
256 
257 // #pragma mark -
258 
259 
260 BString
261 CapabilityHandler::CommandString()
262 {
263 	return "CAPABILITY";
264 }
265 
266 
267 bool
268 CapabilityHandler::HandleUntagged(Response& response)
269 {
270 	if (!response.IsCommand("CAPABILITY"))
271 		return false;
272 
273 	fCapabilities.MakeEmpty();
274 	while (response.CountItems() > 1)
275 		fCapabilities.AddItem(response.RemoveItemAt(1));
276 
277 	TRACE("CAPABILITY: %s\n", fCapabilities.ToString().String());
278 	return true;
279 }
280 
281 
282 // #pragma mark -
283 
284 
285 FetchMessageEntriesCommand::FetchMessageEntriesCommand(
286 	MessageEntryList& entries, uint32 from, uint32 to, bool uids)
287 	:
288 	fEntries(entries),
289 	fFrom(from),
290 	fTo(to),
291 	fUIDs(uids)
292 {
293 }
294 
295 
296 BString
297 FetchMessageEntriesCommand::CommandString()
298 {
299 	BString command = fUIDs ? "UID FETCH " : "FETCH ";
300 	command << fFrom;
301 	if (fFrom != fTo)
302 		command << ":" << fTo;
303 
304 	command << " (FLAGS RFC822.SIZE";
305 	if (!fUIDs)
306 		command << " UID";
307 	command << ")";
308 
309 	return command;
310 }
311 
312 
313 bool
314 FetchMessageEntriesCommand::HandleUntagged(Response& response)
315 {
316 	if (!response.EqualsAt(1, "FETCH") || !response.IsListAt(2))
317 		return false;
318 
319 	MessageEntry entry;
320 
321 	ArgumentList& list = response.ListAt(2);
322 	for (int32 i = 0; i < list.CountItems(); i += 2) {
323 		if (list.EqualsAt(i, "UID") && list.IsNumberAt(i + 1))
324 			entry.uid = list.NumberAt(i + 1);
325 		else if (list.EqualsAt(i, "RFC822.SIZE") && list.IsNumberAt(i + 1))
326 			entry.size = list.NumberAt(i + 1);
327 		else if (list.EqualsAt(i, "FLAGS") && list.IsListAt(i + 1)) {
328 			// Parse flags
329 			ArgumentList& flags = list.ListAt(i + 1);
330 			entry.flags = ParseFlags(flags);
331 		}
332 	}
333 
334 	if (entry.uid == 0)
335 		return false;
336 
337 	fEntries.push_back(entry);
338 	return true;
339 }
340 
341 
342 // #pragma mark -
343 
344 
345 FetchCommand::FetchCommand(uint32 from, uint32 to, uint32 flags)
346 	:
347 	fFlags(flags)
348 {
349 	fSequence << from;
350 	if (from != to)
351 		fSequence << ":" << to;
352 }
353 
354 
355 /*!	Builds the sequence from the passed in UID list, and takes \a max entries
356 	at maximum. If the sequence gets too large, it might fetch less entries
357 	than specified. The fetched UIDs will be removed from the \uids list.
358 */
359 FetchCommand::FetchCommand(MessageUIDList& uids, size_t max, uint32 flags)
360 	:
361 	fFlags(flags)
362 {
363 	// Build sequence string
364 	max = std::min(max, uids.size());
365 
366 	size_t index = 0;
367 	while (index < max && (size_t)fSequence.Length() < kMaxCommandLength) {
368 		// Get start of range
369 		uint32 first = uids[index++];
370 		uint32 last = first;
371 		if (!fSequence.IsEmpty())
372 			fSequence += ",";
373 		fSequence << first;
374 
375 		for (; index < max; index++) {
376 			uint32 uid = uids[index];
377 			if (uid != last + 1)
378 				break;
379 
380 			last = uid;
381 		}
382 
383 		if (last != first)
384 			fSequence << ":" << last;
385 	}
386 
387 	uids.erase(uids.begin(), uids.begin() + index);
388 }
389 
390 
391 void
392 FetchCommand::SetListener(FetchListener* listener)
393 {
394 	fListener = listener;
395 }
396 
397 
398 BString
399 FetchCommand::CommandString()
400 {
401 	BString command = "UID FETCH ";
402 	command += fSequence;
403 
404 	command += " (UID ";
405 	if ((fFlags & kFetchFlags) != 0)
406 		command += "FLAGS ";
407 	switch (fFlags & kFetchAll) {
408 		case kFetchHeader:
409 			command += "RFC822.HEADER";
410 			break;
411 		case kFetchBody:
412 			command += "BODY.PEEK[TEXT]";
413 			break;
414 		case kFetchAll:
415 			command += "BODY.PEEK[]";
416 			break;
417 	}
418 	command += ")";
419 
420 	return command;
421 }
422 
423 
424 bool
425 FetchCommand::HandleUntagged(Response& response)
426 {
427 	if (!response.EqualsAt(1, "FETCH") || !response.IsListAt(2))
428 		return false;
429 
430 	ArgumentList& list = response.ListAt(2);
431 	uint32 uid = 0;
432 	uint32 flags = 0;
433 
434 	for (int32 i = 0; i < list.CountItems(); i += 2) {
435 		if (list.EqualsAt(i, "UID") && list.IsNumberAt(i + 1))
436 			uid = list.NumberAt(i + 1);
437 		else if (list.EqualsAt(i, "FLAGS") && list.IsListAt(i + 1)) {
438 			// Parse flags
439 			ArgumentList& flagList = list.ListAt(i + 1);
440 			flags = ParseFlags(flagList);
441 		}
442 	}
443 
444 	if (fListener != NULL)
445 		fListener->FetchedData(fFlags, uid, flags);
446 	return true;
447 }
448 
449 
450 bool
451 FetchCommand::HandleLiteral(Response& response, ArgumentList& arguments,
452 	BDataIO& stream, size_t& length)
453 {
454 	if (fListener == NULL || !response.EqualsAt(1, "FETCH")
455 		|| !response.IsListAt(2))
456 		return false;
457 
458 	return fListener->FetchData(fFlags, stream, length);
459 }
460 
461 
462 // #pragma mark -
463 
464 
465 SetFlagsCommand::SetFlagsCommand(uint32 uid, uint32 flags)
466 	:
467 	fUID(uid),
468 	fFlags(flags)
469 {
470 }
471 
472 
473 BString
474 SetFlagsCommand::CommandString()
475 {
476 	BString command = "UID STORE ";
477 	command << fUID << " FLAGS (" << GenerateFlagString(fFlags) << ")";
478 
479 	return command;
480 }
481 
482 
483 bool
484 SetFlagsCommand::HandleUntagged(Response& response)
485 {
486 	// We're not that interested in the outcome, apparently
487 	return response.EqualsAt(1, "FETCH");
488 }
489 
490 
491 // #pragma mark -
492 
493 
494 #if 0
495 AppendCommand::AppendCommand(IMAPMailbox& mailbox, BPositionIO& message,
496 	off_t size, int32 flags, time_t time)
497 	:
498 	IMAPMailboxCommand(mailbox),
499 
500 	fMessageData(message),
501 	fDataSize(size),
502 	fFlags(flags),
503 	fTime(time)
504 {
505 }
506 
507 
508 BString
509 AppendCommand::CommandString()
510 {
511 	BString command = "APPEND ";
512 	command << fIMAPMailbox.Mailbox();
513 	command += " (";
514 	command += SetFlagsCommand::GenerateFlagList(fFlags);
515 	command += ")";
516 	command += " {";
517 	command << fDataSize;
518 	command += "}";
519 	return command;
520 }
521 
522 
523 bool
524 AppendCommand::HandleUntagged(const BString& response)
525 {
526 	if (response.FindFirst("+") != 0)
527 		return false;
528 	fMessageData.Seek(0, SEEK_SET);
529 
530 	const int32 kBunchSize = 1024; // 1Kb
531 	char buffer[kBunchSize];
532 
533 	int32 writeSize = fDataSize;
534 	while (writeSize > 0) {
535 		int32 bunchSize = writeSize < kBunchSize ? writeSize : kBunchSize;
536 		fMessageData.Read(buffer, bunchSize);
537 		int nWritten = fIMAPMailbox.SendRawData(buffer, bunchSize);
538 		if (nWritten < 0)
539 			return false;
540 		writeSize -= nWritten;
541 		TRACE("%i\n", (int)writeSize);
542 	}
543 
544 	fIMAPMailbox.SendRawData(CRLF, strlen(CRLF));
545 	return true;
546 }
547 #endif
548 
549 
550 // #pragma mark -
551 
552 
553 ExistsHandler::ExistsHandler()
554 {
555 }
556 
557 
558 void
559 ExistsHandler::SetListener(ExistsListener* listener)
560 {
561 	fListener = listener;
562 }
563 
564 
565 bool
566 ExistsHandler::HandleUntagged(Response& response)
567 {
568 	if (!response.EqualsAt(1, "EXISTS") || !response.IsNumberAt(0))
569 		return false;
570 
571 	uint32 count = response.NumberAt(0);
572 
573 	if (fListener != NULL)
574 		fListener->MessageExistsReceived(count);
575 
576 	return true;
577 }
578 
579 
580 // #pragma mark -
581 
582 
583 ExpungeCommand::ExpungeCommand()
584 {
585 }
586 
587 
588 BString
589 ExpungeCommand::CommandString()
590 {
591 	return "EXPUNGE";
592 }
593 
594 
595 // #pragma mark -
596 
597 
598 ExpungeHandler::ExpungeHandler()
599 {
600 }
601 
602 
603 void
604 ExpungeHandler::SetListener(ExpungeListener* listener)
605 {
606 	fListener = listener;
607 }
608 
609 
610 bool
611 ExpungeHandler::HandleUntagged(Response& response)
612 {
613 	if (!response.EqualsAt(1, "EXPUNGE") || !response.IsNumberAt(0))
614 		return false;
615 
616 	uint32 index = response.NumberAt(0);
617 
618 	if (fListener != NULL)
619 		fListener->MessageExpungeReceived(index);
620 
621 	return true;
622 }
623 
624 
625 // #pragma mark -
626 
627 
628 #if 0
629 FlagsHandler::FlagsHandler(IMAPMailbox& mailbox)
630 	:
631 	IMAPMailboxCommand(mailbox)
632 {
633 }
634 
635 
636 bool
637 FlagsHandler::HandleUntagged(const BString& response)
638 {
639 	if (response.FindFirst("FETCH") < 0)
640 		return false;
641 
642 	int32 fetch = 0;
643 	if (!IMAPParser::ExtractUntagedFromLeft(response, "FETCH", fetch))
644 		return false;
645 
646 	int32 flags = FetchMinMessageCommand::ExtractFlags(response);
647 	int32 uid = fIMAPMailbox.MessageNumberToUID(fetch);
648 	fStorage.SetFlags(uid, flags);
649 	TRACE("FlagsHandler id %i flags %i\n", (int)fetch, (int)flags);
650 	fIMAPMailbox.SendRawCommand("DONE");
651 
652 	return true;
653 }
654 #endif
655 
656 
657 // #pragma mark -
658 
659 
660 ListCommand::ListCommand(const char* prefix, bool subscribedOnly)
661 	:
662 	fPrefix(prefix),
663 	fSubscribedOnly(subscribedOnly)
664 {
665 }
666 
667 
668 BString
669 ListCommand::CommandString()
670 {
671 	BString command = _Command();
672 	command += " \"\" \"";
673 	if (fPrefix != NULL)
674 		command << fEncoding.Encode(fPrefix) << "%";
675 	else
676 		command += "*";
677 	command += "\"";
678 
679 	return command;
680 }
681 
682 
683 bool
684 ListCommand::HandleUntagged(Response& response)
685 {
686 	if (response.IsCommand(_Command()) && response.IsStringAt(2)
687 		&& response.IsStringAt(3)) {
688 		fSeparator = response.StringAt(2);
689 
690 		if (response.IsListAt(1)) {
691 			// We're not supposed to select \Noselect mailboxes,
692 			// so we'll just hide them
693 			ArgumentList& attributes = response.ListAt(1);
694 			if (attributes.Contains("\\Noselect"))
695 				return true;
696 		}
697 
698 		BString folder = response.StringAt(3);
699 		if (folder == "")
700 			return true;
701 
702 		try {
703 			folder = fEncoding.Decode(folder);
704 			// The folder INBOX is always case insensitive
705 			if (folder.ICompare("INBOX") == 0)
706 				folder = "Inbox";
707 			fFolders.Add(folder);
708 		} catch (ParseException& exception) {
709 			// Decoding failed, just add the plain text
710 			fprintf(stderr, "Decoding \"%s\" failed: %s\n", folder.String(),
711 				exception.Message());
712 			fFolders.Add(folder);
713 		}
714 		return true;
715 	}
716 
717 	return false;
718 }
719 
720 
721 const BStringList&
722 ListCommand::FolderList()
723 {
724 	return fFolders;
725 }
726 
727 
728 const char*
729 ListCommand::_Command() const
730 {
731 	return fSubscribedOnly ? "LSUB" : "LIST";
732 }
733 
734 
735 // #pragma mark -
736 
737 
738 SubscribeCommand::SubscribeCommand(const char* mailboxName)
739 	:
740 	fMailboxName(mailboxName)
741 {
742 }
743 
744 
745 BString
746 SubscribeCommand::CommandString()
747 {
748 	BString command = "SUBSCRIBE \"";
749 	command += fMailboxName;
750 	command += "\"";
751 	return command;
752 }
753 
754 
755 // #pragma mark -
756 
757 
758 UnsubscribeCommand::UnsubscribeCommand(const char* mailboxName)
759 	:
760 	fMailboxName(mailboxName)
761 {
762 }
763 
764 
765 BString
766 UnsubscribeCommand::CommandString()
767 {
768 	BString command = "UNSUBSCRIBE \"";
769 	command += fMailboxName;
770 	command += "\"";
771 	return command;
772 }
773 
774 
775 // #pragma mark -
776 
777 
778 GetQuotaCommand::GetQuotaCommand(const char* mailboxName)
779 	:
780 	fMailboxName(mailboxName),
781 	fUsedStorage(0),
782 	fTotalStorage(0)
783 {
784 }
785 
786 
787 BString
788 GetQuotaCommand::CommandString()
789 {
790 	BString command = "GETQUOTAROOT \"";
791 	command += fMailboxName;
792 	command += "\"";
793 	return command;
794 }
795 
796 
797 bool
798 GetQuotaCommand::HandleUntagged(Response& response)
799 {
800 	if (!response.IsCommand("QUOTA") || !response.IsListAt(2))
801 		return false;
802 
803 	const ArgumentList& arguments = response.ListAt(2);
804 	if (!arguments.EqualsAt(0, "STORAGE"))
805 		return false;
806 
807 	fUsedStorage = (uint64)arguments.NumberAt(1) * 1024;
808 	fTotalStorage = (uint64)arguments.NumberAt(2) * 1024;
809 
810 	return true;
811 }
812 
813 
814 uint64
815 GetQuotaCommand::UsedStorage()
816 {
817 	return fUsedStorage;
818 }
819 
820 
821 uint64
822 GetQuotaCommand::TotalStorage()
823 {
824 	return fTotalStorage;
825 }
826 
827 
828 }	// namespace
829