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
PutFlag(BString & string,const char * flag)32 PutFlag(BString& string, const char* flag)
33 {
34 if (!string.IsEmpty())
35 string += " ";
36 string += flag;
37 }
38
39
40 static BString
GenerateFlagString(uint32 flags)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
ParseFlags(IMAP::ArgumentList & list)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
Handler()86 Handler::Handler()
87 {
88 }
89
90
~Handler()91 Handler::~Handler()
92 {
93 }
94
95
96 // #pragma mark -
97
98
~Command()99 Command::~Command()
100 {
101 }
102
103
104 status_t
HandleTagged(Response & response)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
RawCommand(const BString & command)141 RawCommand::RawCommand(const BString& command)
142 :
143 fCommand(command)
144 {
145 }
146
147
148 BString
CommandString()149 RawCommand::CommandString()
150 {
151 return fCommand;
152 }
153
154
155 // #pragma mark -
156
157
LoginCommand(const char * user,const char * password)158 LoginCommand::LoginCommand(const char* user, const char* password)
159 :
160 fUser(user),
161 fPassword(password)
162 {
163 }
164
165
166 BString
CommandString()167 LoginCommand::CommandString()
168 {
169 BString command = "LOGIN ";
170 command << "\"" << fUser << "\" " << "\"" << fPassword << "\"";
171
172 return command;
173 }
174
175
176 bool
HandleUntagged(Response & response)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
SelectCommand()199 SelectCommand::SelectCommand()
200 :
201 fNextUID(0),
202 fUIDValidity(0)
203 {
204 }
205
206
SelectCommand(const char * name)207 SelectCommand::SelectCommand(const char* name)
208 :
209 fNextUID(0),
210 fUIDValidity(0)
211 {
212 SetTo(name);
213 }
214
215
216 BString
CommandString()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
HandleUntagged(Response & response)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
SetTo(const char * mailboxName)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
CommandString()261 CapabilityHandler::CommandString()
262 {
263 return "CAPABILITY";
264 }
265
266
267 bool
HandleUntagged(Response & response)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
FetchMessageEntriesCommand(MessageEntryList & entries,uint32 from,uint32 to,bool uids)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
CommandString()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
HandleUntagged(Response & response)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
FetchCommand(uint32 from,uint32 to,uint32 flags)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 */
FetchCommand(MessageUIDList & uids,size_t max,uint32 flags)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
SetListener(FetchListener * listener)392 FetchCommand::SetListener(FetchListener* listener)
393 {
394 fListener = listener;
395 }
396
397
398 BString
CommandString()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
HandleUntagged(Response & response)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
HandleLiteral(Response & response,ArgumentList & arguments,BDataIO & stream,size_t & length)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
SetFlagsCommand(uint32 uid,uint32 flags)465 SetFlagsCommand::SetFlagsCommand(uint32 uid, uint32 flags)
466 :
467 fUID(uid),
468 fFlags(flags)
469 {
470 }
471
472
473 BString
CommandString()474 SetFlagsCommand::CommandString()
475 {
476 BString command = "UID STORE ";
477 command << fUID << " FLAGS (" << GenerateFlagString(fFlags) << ")";
478
479 return command;
480 }
481
482
483 bool
HandleUntagged(Response & response)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
ExistsHandler()553 ExistsHandler::ExistsHandler()
554 {
555 }
556
557
558 void
SetListener(ExistsListener * listener)559 ExistsHandler::SetListener(ExistsListener* listener)
560 {
561 fListener = listener;
562 }
563
564
565 bool
HandleUntagged(Response & response)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
ExpungeCommand()583 ExpungeCommand::ExpungeCommand()
584 {
585 }
586
587
588 BString
CommandString()589 ExpungeCommand::CommandString()
590 {
591 return "EXPUNGE";
592 }
593
594
595 // #pragma mark -
596
597
ExpungeHandler()598 ExpungeHandler::ExpungeHandler()
599 {
600 }
601
602
603 void
SetListener(ExpungeListener * listener)604 ExpungeHandler::SetListener(ExpungeListener* listener)
605 {
606 fListener = listener;
607 }
608
609
610 bool
HandleUntagged(Response & response)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
ListCommand(const char * prefix,bool subscribedOnly)660 ListCommand::ListCommand(const char* prefix, bool subscribedOnly)
661 :
662 fPrefix(prefix),
663 fSubscribedOnly(subscribedOnly)
664 {
665 }
666
667
668 BString
CommandString()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
HandleUntagged(Response & response)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&
FolderList()722 ListCommand::FolderList()
723 {
724 return fFolders;
725 }
726
727
728 const char*
_Command() const729 ListCommand::_Command() const
730 {
731 return fSubscribedOnly ? "LSUB" : "LIST";
732 }
733
734
735 // #pragma mark -
736
737
SubscribeCommand(const char * mailboxName)738 SubscribeCommand::SubscribeCommand(const char* mailboxName)
739 :
740 fMailboxName(mailboxName)
741 {
742 }
743
744
745 BString
CommandString()746 SubscribeCommand::CommandString()
747 {
748 BString command = "SUBSCRIBE \"";
749 command += fMailboxName;
750 command += "\"";
751 return command;
752 }
753
754
755 // #pragma mark -
756
757
UnsubscribeCommand(const char * mailboxName)758 UnsubscribeCommand::UnsubscribeCommand(const char* mailboxName)
759 :
760 fMailboxName(mailboxName)
761 {
762 }
763
764
765 BString
CommandString()766 UnsubscribeCommand::CommandString()
767 {
768 BString command = "UNSUBSCRIBE \"";
769 command += fMailboxName;
770 command += "\"";
771 return command;
772 }
773
774
775 // #pragma mark -
776
777
GetQuotaCommand(const char * mailboxName)778 GetQuotaCommand::GetQuotaCommand(const char* mailboxName)
779 :
780 fMailboxName(mailboxName),
781 fUsedStorage(0),
782 fTotalStorage(0)
783 {
784 }
785
786
787 BString
CommandString()788 GetQuotaCommand::CommandString()
789 {
790 BString command = "GETQUOTAROOT \"";
791 command += fMailboxName;
792 command += "\"";
793 return command;
794 }
795
796
797 bool
HandleUntagged(Response & response)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
UsedStorage()815 GetQuotaCommand::UsedStorage()
816 {
817 return fUsedStorage;
818 }
819
820
821 uint64
TotalStorage()822 GetQuotaCommand::TotalStorage()
823 {
824 return fTotalStorage;
825 }
826
827
828 } // namespace
829