1 /*
2 * Copyright 2011-2016, Axel Dörfler, axeld@pinc-software.de.
3 * Distributed under the terms of the MIT License.
4 */
5
6
7 #include "Response.h"
8
9 #include <algorithm>
10 #include <stdlib.h>
11
12 #include <UnicodeChar.h>
13
14
15 #define TRACE_IMAP
16 #ifdef TRACE_IMAP
17 # define TRACE(...) printf(__VA_ARGS__)
18 #else
19 # define TRACE(...) ;
20 #endif
21
22
23 namespace IMAP {
24
25
26 // Note, the following alphabet is a modified base64; the '/' is replaced by
27 // a ',' here.
28 static const char kBase64Alphabet[64] = {
29 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
30 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
31 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
32 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
33 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
34 '+', ','
35 };
36 static char kInverseBase64Alphabet[128];
37 static bool kInverseBase64Initialized = false;
38
39
RFC3501Encoding()40 RFC3501Encoding::RFC3501Encoding()
41 {
42 if (!kInverseBase64Initialized) {
43 // This is not thread safe, but it's not harmful
44 for (size_t i = 0; i < sizeof(kBase64Alphabet); i++)
45 kInverseBase64Alphabet[(int)kBase64Alphabet[i]] = i + 1;
46 kInverseBase64Initialized = true;
47 }
48 }
49
50
~RFC3501Encoding()51 RFC3501Encoding::~RFC3501Encoding()
52 {
53 }
54
55
56 BString
Encode(const BString & clearText) const57 RFC3501Encoding::Encode(const BString& clearText) const
58 {
59 const char* clear = clearText.String();
60 bool shifted = false;
61 int32 bitsToWrite = 0;
62 int32 sextet = 0;
63 BString buffer;
64
65 while (true) {
66 uint32 c = BUnicodeChar::FromUTF8(&clear);
67 if (c == 0)
68 break;
69
70 if (!shifted && c == '&')
71 buffer += "&-";
72 else if (c >= 0x20 && c <= 0x7e) {
73 _Unshift(buffer, bitsToWrite, sextet, shifted);
74 buffer += c;
75 } else {
76 // Enter shifted mode, encode in base64
77 if (!shifted) {
78 buffer += '&';
79 shifted = true;
80 }
81
82 bitsToWrite += 16;
83 while (bitsToWrite >= 6) {
84 bitsToWrite -= 6;
85 buffer += kBase64Alphabet[(sextet + (c >> bitsToWrite)) & 0x3f];
86 sextet = 0;
87 }
88 sextet = (c << (6 - bitsToWrite)) & 0x3f;
89 }
90 }
91
92 _Unshift(buffer, bitsToWrite, sextet, shifted);
93 return buffer;
94 }
95
96
97 BString
Decode(const BString & encodedText) const98 RFC3501Encoding::Decode(const BString& encodedText) const
99 {
100 int32 end = encodedText.Length();
101 BString buffer;
102 for (int32 i = 0; i < end; i++) {
103 uint8 c = (uint8)encodedText.ByteAt(i);
104 if (c == '&') {
105 if (i < end - 1 && encodedText.ByteAt(i + 1) == '-') {
106 // just add an ampersand
107 buffer += '&';
108 i++;
109 } else {
110 // base64 encoded chunk
111 uint32 value = 0;
112 int32 bitsRead = 0;
113 while (true) {
114 if (++i >= end)
115 throw ParseException("Malformed base64!");
116
117 c = encodedText.ByteAt(i);
118 if (c == '-') {
119 if (value != 0 || bitsRead >= 6)
120 throw ParseException("Base64 encoding ends early!");
121 break;
122 }
123 if (c >= 128)
124 throw ParseException("Malformed base64!");
125 int32 sextet = kInverseBase64Alphabet[c] - 1;
126 if (sextet >= 0) {
127 bitsRead += 6;
128 if (bitsRead < 16) {
129 value += sextet << (16 - bitsRead);
130 } else {
131 bitsRead -= 16;
132 value += sextet >> bitsRead;
133 _ToUTF8(buffer, value);
134
135 // Move on to next character
136 value = (sextet << (16 - bitsRead)) & 0xffff;
137 }
138 } else {
139 buffer += c;
140 if (value != 0 || bitsRead >= 6)
141 throw ParseException("Malformed base64!");
142 break;
143 }
144 }
145 }
146 } else
147 buffer += c;
148 }
149 return buffer;
150 }
151
152
153 void
_ToUTF8(BString & string,uint32 c) const154 RFC3501Encoding::_ToUTF8(BString& string, uint32 c) const
155 {
156 if (c < 0x80)
157 string += (char)c;
158 else if (c < 0x800) {
159 string += 0xc0 | (c >> 6);
160 string += 0x80 | (c & 0x3f);
161 } else if (c < 0x10000) {
162 string += 0xe0 | (c >> 12);
163 string += 0x80 | ((c >> 6) & 0x3f);
164 string += 0x80 | (c & 0x3f);
165 } else if (c <= 0x10ffff) {
166 string += 0xf0 | (c >> 18);
167 string += 0x80 | ((c >> 12) & 0x3f);
168 string += 0x80 | ((c >> 6) & 0x3f);
169 string += 0x80 | (c & 0x3f);
170 }
171 }
172
173
174 //! Exit base64, or "shifted" mode.
175 void
_Unshift(BString & buffer,int32 & bitsToWrite,int32 & sextet,bool & shifted) const176 RFC3501Encoding::_Unshift(BString& buffer, int32& bitsToWrite, int32& sextet,
177 bool& shifted) const
178 {
179 if (!shifted)
180 return;
181
182 if (bitsToWrite != 0)
183 buffer += kBase64Alphabet[sextet];
184 buffer += '-';
185 sextet = 0;
186 bitsToWrite = 0;
187 shifted = false;
188 }
189
190
191 // #pragma mark -
192
193
ArgumentList()194 ArgumentList::ArgumentList()
195 :
196 BObjectList<Argument>(5, true)
197 {
198 }
199
200
~ArgumentList()201 ArgumentList::~ArgumentList()
202 {
203 }
204
205
206 bool
Contains(const char * string) const207 ArgumentList::Contains(const char* string) const
208 {
209 for (int32 i = 0; i < CountItems(); i++) {
210 if (StringArgument* argument
211 = dynamic_cast<StringArgument*>(ItemAt(i))) {
212 if (argument->String().ICompare(string) == 0)
213 return true;
214 }
215 }
216 return false;
217 }
218
219
220 BString
StringAt(int32 index) const221 ArgumentList::StringAt(int32 index) const
222 {
223 if (index >= 0 && index < CountItems()) {
224 if (StringArgument* argument
225 = dynamic_cast<StringArgument*>(ItemAt(index)))
226 return argument->String();
227 }
228 return "";
229 }
230
231
232 bool
IsStringAt(int32 index) const233 ArgumentList::IsStringAt(int32 index) const
234 {
235 if (index >= 0 && index < CountItems()) {
236 if (dynamic_cast<StringArgument*>(ItemAt(index)) != NULL)
237 return true;
238 }
239 return false;
240 }
241
242
243 bool
EqualsAt(int32 index,const char * string) const244 ArgumentList::EqualsAt(int32 index, const char* string) const
245 {
246 return StringAt(index).ICompare(string) == 0;
247 }
248
249
250 ArgumentList&
ListAt(int32 index) const251 ArgumentList::ListAt(int32 index) const
252 {
253 if (index >= 0 && index < CountItems()) {
254 if (ListArgument* argument = dynamic_cast<ListArgument*>(ItemAt(index)))
255 return argument->List();
256 }
257
258 static ArgumentList empty;
259 return empty;
260 }
261
262
263 bool
IsListAt(int32 index) const264 ArgumentList::IsListAt(int32 index) const
265 {
266 return index >= 0 && index < CountItems()
267 && dynamic_cast<ListArgument*>(ItemAt(index)) != NULL;
268 }
269
270
271 bool
IsListAt(int32 index,char kind) const272 ArgumentList::IsListAt(int32 index, char kind) const
273 {
274 if (index >= 0 && index < CountItems()) {
275 if (ListArgument* argument = dynamic_cast<ListArgument*>(ItemAt(index)))
276 return argument->Kind() == kind;
277 }
278 return false;
279 }
280
281
282 uint32
NumberAt(int32 index) const283 ArgumentList::NumberAt(int32 index) const
284 {
285 return atoul(StringAt(index).String());
286 }
287
288
289 bool
IsNumberAt(int32 index) const290 ArgumentList::IsNumberAt(int32 index) const
291 {
292 BString string = StringAt(index);
293 for (int32 i = 0; i < string.Length(); i++) {
294 if (!isdigit(string.ByteAt(i)))
295 return false;
296 }
297 return string.Length() > 0;
298 }
299
300
301 BString
ToString() const302 ArgumentList::ToString() const
303 {
304 BString string;
305
306 for (int32 i = 0; i < CountItems(); i++) {
307 if (i > 0)
308 string += ", ";
309 string += ItemAt(i)->ToString();
310 }
311 return string;
312 }
313
314
315 // #pragma mark -
316
317
Argument()318 Argument::Argument()
319 {
320 }
321
322
~Argument()323 Argument::~Argument()
324 {
325 }
326
327
328 // #pragma mark -
329
330
ListArgument(char kind)331 ListArgument::ListArgument(char kind)
332 :
333 fKind(kind)
334 {
335 }
336
337
338 BString
ToString() const339 ListArgument::ToString() const
340 {
341 BString string(fKind == '[' ? "[" : "(");
342 string += fList.ToString();
343 string += fKind == '[' ? "]" : ")";
344
345 return string;
346 }
347
348
349 // #pragma mark -
350
351
StringArgument(const BString & string)352 StringArgument::StringArgument(const BString& string)
353 :
354 fString(string)
355 {
356 }
357
358
StringArgument(const StringArgument & other)359 StringArgument::StringArgument(const StringArgument& other)
360 :
361 fString(other.fString)
362 {
363 }
364
365
366 BString
ToString() const367 StringArgument::ToString() const
368 {
369 return fString;
370 }
371
372
373 // #pragma mark -
374
375
ParseException()376 ParseException::ParseException()
377 {
378 fBuffer[0] = '\0';
379 }
380
381
ParseException(const char * format,...)382 ParseException::ParseException(const char* format, ...)
383 {
384 va_list args;
385 va_start(args, format);
386 vsnprintf(fBuffer, sizeof(fBuffer), format, args);
387 va_end(args);
388 }
389
390
391 // #pragma mark -
392
393
StreamException(status_t status)394 StreamException::StreamException(status_t status)
395 :
396 fStatus(status)
397 {
398 }
399
400
401 // #pragma mark -
402
403
ExpectedParseException(char expected,char instead)404 ExpectedParseException::ExpectedParseException(char expected, char instead)
405 {
406 char bufferA[8];
407 char bufferB[8];
408 snprintf(fBuffer, sizeof(fBuffer), "Expected %s, but got %s instead!",
409 CharToString(bufferA, sizeof(bufferA), expected),
410 CharToString(bufferB, sizeof(bufferB), instead));
411 }
412
413
414 const char*
CharToString(char * buffer,size_t size,char c)415 ExpectedParseException::CharToString(char* buffer, size_t size, char c)
416 {
417 snprintf(buffer, size, isprint(c) ? "\"%c\"" : "(%x)", c);
418 return buffer;
419 }
420
421
422 // #pragma mark -
423
424
LiteralHandler()425 LiteralHandler::LiteralHandler()
426 {
427 }
428
429
~LiteralHandler()430 LiteralHandler::~LiteralHandler()
431 {
432 }
433
434
435 // #pragma mark -
436
437
Response()438 Response::Response()
439 :
440 fTag(0),
441 fContinuation(false),
442 fHasNextChar(false)
443 {
444 }
445
446
~Response()447 Response::~Response()
448 {
449 }
450
451
452 void
Parse(BDataIO & stream,LiteralHandler * handler)453 Response::Parse(BDataIO& stream, LiteralHandler* handler)
454 {
455 MakeEmpty();
456 fLiteralHandler = handler;
457 fTag = 0;
458 fContinuation = false;
459 fHasNextChar = false;
460
461 char begin = Next(stream);
462 if (begin == '*') {
463 // Untagged response
464 Consume(stream, ' ');
465 } else if (begin == '+') {
466 // Continuation
467 fContinuation = true;
468 } else if (begin == 'A') {
469 // Tagged response
470 fTag = ExtractNumber(stream);
471 Consume(stream, ' ');
472 } else
473 throw ParseException("Unexpected response begin");
474
475 char c = ParseLine(*this, stream);
476 if (c != '\0')
477 throw ExpectedParseException('\0', c);
478 }
479
480
481 bool
IsCommand(const char * command) const482 Response::IsCommand(const char* command) const
483 {
484 return IsUntagged() && EqualsAt(0, command);
485 }
486
487
488 char
ParseLine(ArgumentList & arguments,BDataIO & stream)489 Response::ParseLine(ArgumentList& arguments, BDataIO& stream)
490 {
491 while (true) {
492 char c = Peek(stream);
493 if (c == '\0')
494 break;
495
496 switch (c) {
497 case '(':
498 ParseList(arguments, stream, '(', ')');
499 break;
500 case '[':
501 ParseList(arguments, stream, '[', ']');
502 break;
503 case ')':
504 case ']':
505 Consume(stream, c);
506 return c;
507 case '"':
508 ParseQuoted(arguments, stream);
509 break;
510 case '{':
511 ParseLiteral(arguments, stream);
512 break;
513
514 case ' ':
515 case '\t':
516 // whitespace
517 Consume(stream, c);
518 break;
519
520 case '\r':
521 Consume(stream, '\r');
522 Consume(stream, '\n');
523 return '\0';
524 case '\n':
525 Consume(stream, '\n');
526 return '\0';
527
528 default:
529 ParseString(arguments, stream);
530 break;
531 }
532 }
533
534 return '\0';
535 }
536
537
538 void
ParseList(ArgumentList & arguments,BDataIO & stream,char start,char end)539 Response::ParseList(ArgumentList& arguments, BDataIO& stream, char start,
540 char end)
541 {
542 Consume(stream, start);
543
544 ListArgument* argument = new ListArgument(start);
545 arguments.AddItem(argument);
546
547 char c = ParseLine(argument->List(), stream);
548 if (c != end)
549 throw ExpectedParseException(end, c);
550 }
551
552
553 void
ParseQuoted(ArgumentList & arguments,BDataIO & stream)554 Response::ParseQuoted(ArgumentList& arguments, BDataIO& stream)
555 {
556 Consume(stream, '"');
557
558 BString string;
559 while (true) {
560 char c = Next(stream);
561 if (c == '\\') {
562 c = Next(stream);
563 } else if (c == '"') {
564 arguments.AddItem(new StringArgument(string));
565 return;
566 }
567 if (c == '\0')
568 break;
569
570 string += c;
571 }
572
573 throw ParseException("Unexpected end of qouted string!");
574 }
575
576
577 void
ParseLiteral(ArgumentList & arguments,BDataIO & stream)578 Response::ParseLiteral(ArgumentList& arguments, BDataIO& stream)
579 {
580 Consume(stream, '{');
581 size_t size = ExtractNumber(stream);
582 Consume(stream, '}');
583 Consume(stream, '\r');
584 Consume(stream, '\n');
585
586 bool handled = false;
587 if (fLiteralHandler != NULL) {
588 handled = fLiteralHandler->HandleLiteral(*this, arguments, stream,
589 size);
590 }
591
592 if (!handled && size <= 65536) {
593 // The default implementation just adds the data as a string
594 TRACE("Trying to read literal with %" B_PRIuSIZE " bytes.\n", size);
595 BString string;
596 char* buffer = string.LockBuffer(size);
597 if (buffer == NULL) {
598 throw ParseException("Not enough memory for literal of %"
599 B_PRIuSIZE " bytes.", size);
600 }
601
602 size_t totalRead = 0;
603 while (totalRead < size) {
604 ssize_t bytesRead = stream.Read(buffer + totalRead,
605 size - totalRead);
606 if (bytesRead == 0)
607 throw StreamException(B_IO_ERROR);
608 if (bytesRead < 0)
609 throw StreamException(bytesRead);
610
611 totalRead += bytesRead;
612 }
613
614 string.UnlockBuffer(size);
615 arguments.AddItem(new StringArgument(string));
616 } else {
617 // Skip any bytes left in the literal stream
618 _SkipLiteral(stream, size);
619 }
620 }
621
622
623 void
ParseString(ArgumentList & arguments,BDataIO & stream)624 Response::ParseString(ArgumentList& arguments, BDataIO& stream)
625 {
626 arguments.AddItem(new StringArgument(ExtractString(stream)));
627 }
628
629
630 BString
ExtractString(BDataIO & stream)631 Response::ExtractString(BDataIO& stream)
632 {
633 BString string;
634
635 // TODO: parse modified UTF-7 as described in RFC 3501, 5.1.3
636 while (true) {
637 char c = Peek(stream);
638 if (c == '\0')
639 break;
640 if (c <= ' ' || strchr("()[]{}\"", c) != NULL)
641 return string;
642
643 string += Next(stream);
644 }
645
646 throw ParseException("Unexpected end of string");
647 }
648
649
650 size_t
ExtractNumber(BDataIO & stream)651 Response::ExtractNumber(BDataIO& stream)
652 {
653 BString string = ExtractString(stream);
654
655 const char* end;
656 size_t number = strtoul(string.String(), (char**)&end, 10);
657 if (end == NULL || end[0] != '\0')
658 throw ParseException("Invalid number!");
659
660 return number;
661 }
662
663
664 void
Consume(BDataIO & stream,char expected)665 Response::Consume(BDataIO& stream, char expected)
666 {
667 char c = Next(stream);
668 if (c != expected)
669 throw ExpectedParseException(expected, c);
670 }
671
672
673 char
Next(BDataIO & stream)674 Response::Next(BDataIO& stream)
675 {
676 if (fHasNextChar) {
677 fHasNextChar = false;
678 return fNextChar;
679 }
680
681 return Read(stream);
682 }
683
684
685 char
Peek(BDataIO & stream)686 Response::Peek(BDataIO& stream)
687 {
688 if (fHasNextChar)
689 return fNextChar;
690
691 fNextChar = Read(stream);
692 fHasNextChar = true;
693
694 return fNextChar;
695 }
696
697
698 char
Read(BDataIO & stream)699 Response::Read(BDataIO& stream)
700 {
701 char c;
702 ssize_t bytesRead = stream.Read(&c, 1);
703 if (bytesRead == 1) {
704 printf("%c", c);
705 return c;
706 }
707
708 if (bytesRead == 0)
709 throw StreamException(B_IO_ERROR);
710
711 throw StreamException(bytesRead);
712 }
713
714
715 void
_SkipLiteral(BDataIO & stream,size_t size)716 Response::_SkipLiteral(BDataIO& stream, size_t size)
717 {
718 char buffer[4096];
719 size_t totalRead = 0;
720 while (totalRead < size) {
721 size_t toRead = std::min(sizeof(buffer), size - totalRead);
722 ssize_t bytesRead = stream.Read(buffer, toRead);
723 if (bytesRead == 0)
724 throw StreamException(B_IO_ERROR);
725 if (bytesRead < 0)
726 throw StreamException(bytesRead);
727
728 totalRead += bytesRead;
729 }
730 }
731
732
733 // #pragma mark -
734
735
ResponseParser(BDataIO & stream)736 ResponseParser::ResponseParser(BDataIO& stream)
737 :
738 fLiteralHandler(NULL)
739 {
740 SetTo(stream);
741 }
742
743
~ResponseParser()744 ResponseParser::~ResponseParser()
745 {
746 }
747
748
749 void
SetTo(BDataIO & stream)750 ResponseParser::SetTo(BDataIO& stream)
751 {
752 fStream = &stream;
753 }
754
755
756 void
SetLiteralHandler(LiteralHandler * handler)757 ResponseParser::SetLiteralHandler(LiteralHandler* handler)
758 {
759 fLiteralHandler = handler;
760 }
761
762
763 status_t
NextResponse(Response & response,bigtime_t timeout)764 ResponseParser::NextResponse(Response& response, bigtime_t timeout)
765 {
766 response.Parse(*fStream, fLiteralHandler);
767 return B_OK;
768 }
769
770
771 } // namespace IMAP
772