xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp (revision 12dba4e70f831d6d27a7f769cc9dab19c19a155d)
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 
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 
51 RFC3501Encoding::~RFC3501Encoding()
52 {
53 }
54 
55 
56 BString
57 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
98 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
154 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
176 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 
194 ArgumentList::ArgumentList()
195 	:
196 	BObjectList<Argument>(5, true)
197 {
198 }
199 
200 
201 ArgumentList::~ArgumentList()
202 {
203 }
204 
205 
206 bool
207 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
221 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
233 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
244 ArgumentList::EqualsAt(int32 index, const char* string) const
245 {
246 	return StringAt(index).ICompare(string) == 0;
247 }
248 
249 
250 ArgumentList&
251 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
264 ArgumentList::IsListAt(int32 index) const
265 {
266 	return index >= 0 && index < CountItems()
267 		&& dynamic_cast<ListArgument*>(ItemAt(index)) != NULL;
268 }
269 
270 
271 bool
272 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
283 ArgumentList::NumberAt(int32 index) const
284 {
285 	return atoul(StringAt(index).String());
286 }
287 
288 
289 bool
290 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
302 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 
318 Argument::Argument()
319 {
320 }
321 
322 
323 Argument::~Argument()
324 {
325 }
326 
327 
328 // #pragma mark -
329 
330 
331 ListArgument::ListArgument(char kind)
332 	:
333 	fKind(kind)
334 {
335 }
336 
337 
338 BString
339 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 
352 StringArgument::StringArgument(const BString& string)
353 	:
354 	fString(string)
355 {
356 }
357 
358 
359 StringArgument::StringArgument(const StringArgument& other)
360 	:
361 	fString(other.fString)
362 {
363 }
364 
365 
366 BString
367 StringArgument::ToString() const
368 {
369 	return fString;
370 }
371 
372 
373 // #pragma mark -
374 
375 
376 ParseException::ParseException()
377 {
378 	fBuffer[0] = '\0';
379 }
380 
381 
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 
394 StreamException::StreamException(status_t status)
395 	:
396 	fStatus(status)
397 {
398 }
399 
400 
401 // #pragma mark -
402 
403 
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*
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 
425 LiteralHandler::LiteralHandler()
426 {
427 }
428 
429 
430 LiteralHandler::~LiteralHandler()
431 {
432 }
433 
434 
435 // #pragma mark -
436 
437 
438 Response::Response()
439 	:
440 	fTag(0),
441 	fContinuation(false),
442 	fHasNextChar(false)
443 {
444 }
445 
446 
447 Response::~Response()
448 {
449 }
450 
451 
452 void
453 Response::Parse(BDataIO& stream, LiteralHandler* handler)
454 	throw(ParseException, StreamException)
455 {
456 	MakeEmpty();
457 	fLiteralHandler = handler;
458 	fTag = 0;
459 	fContinuation = false;
460 	fHasNextChar = false;
461 
462 	char begin = Next(stream);
463 	if (begin == '*') {
464 		// Untagged response
465 		Consume(stream, ' ');
466 	} else if (begin == '+') {
467 		// Continuation
468 		fContinuation = true;
469 	} else if (begin == 'A') {
470 		// Tagged response
471 		fTag = ExtractNumber(stream);
472 		Consume(stream, ' ');
473 	} else
474 		throw ParseException("Unexpected response begin");
475 
476 	char c = ParseLine(*this, stream);
477 	if (c != '\0')
478 		throw ExpectedParseException('\0', c);
479 }
480 
481 
482 bool
483 Response::IsCommand(const char* command) const
484 {
485 	return IsUntagged() && EqualsAt(0, command);
486 }
487 
488 
489 char
490 Response::ParseLine(ArgumentList& arguments, BDataIO& stream)
491 {
492 	while (true) {
493 		char c = Peek(stream);
494 		if (c == '\0')
495 			break;
496 
497 		switch (c) {
498 			case '(':
499 				ParseList(arguments, stream, '(', ')');
500 				break;
501 			case '[':
502 				ParseList(arguments, stream, '[', ']');
503 				break;
504 			case ')':
505 			case ']':
506 				Consume(stream, c);
507 				return c;
508 			case '"':
509 				ParseQuoted(arguments, stream);
510 				break;
511 			case '{':
512 				ParseLiteral(arguments, stream);
513 				break;
514 
515 			case ' ':
516 			case '\t':
517 				// whitespace
518 				Consume(stream, c);
519 				break;
520 
521 			case '\r':
522 				Consume(stream, '\r');
523 				Consume(stream, '\n');
524 				return '\0';
525 			case '\n':
526 				Consume(stream, '\n');
527 				return '\0';
528 
529 			default:
530 				ParseString(arguments, stream);
531 				break;
532 		}
533 	}
534 
535 	return '\0';
536 }
537 
538 
539 void
540 Response::ParseList(ArgumentList& arguments, BDataIO& stream, char start,
541 	char end)
542 {
543 	Consume(stream, start);
544 
545 	ListArgument* argument = new ListArgument(start);
546 	arguments.AddItem(argument);
547 
548 	char c = ParseLine(argument->List(), stream);
549 	if (c != end)
550 		throw ExpectedParseException(end, c);
551 }
552 
553 
554 void
555 Response::ParseQuoted(ArgumentList& arguments, BDataIO& stream)
556 {
557 	Consume(stream, '"');
558 
559 	BString string;
560 	while (true) {
561 		char c = Next(stream);
562 		if (c == '\\') {
563 			c = Next(stream);
564 		} else if (c == '"') {
565 			arguments.AddItem(new StringArgument(string));
566 			return;
567 		}
568 		if (c == '\0')
569 			break;
570 
571 		string += c;
572 	}
573 
574 	throw ParseException("Unexpected end of qouted string!");
575 }
576 
577 
578 void
579 Response::ParseLiteral(ArgumentList& arguments, BDataIO& stream)
580 {
581 	Consume(stream, '{');
582 	size_t size = ExtractNumber(stream);
583 	Consume(stream, '}');
584 	Consume(stream, '\r');
585 	Consume(stream, '\n');
586 
587 	bool handled = false;
588 	if (fLiteralHandler != NULL) {
589 		handled = fLiteralHandler->HandleLiteral(*this, arguments, stream,
590 			size);
591 	}
592 
593 	if (!handled && size <= 65536) {
594 		// The default implementation just adds the data as a string
595 		TRACE("Trying to read literal with %" B_PRIuSIZE " bytes.\n", size);
596 		BString string;
597 		char* buffer = string.LockBuffer(size);
598 		if (buffer == NULL) {
599 			throw ParseException("Not enough memory for literal of %"
600 				B_PRIuSIZE " bytes.", size);
601 		}
602 
603 		size_t totalRead = 0;
604 		while (totalRead < size) {
605 			ssize_t bytesRead = stream.Read(buffer + totalRead,
606 				size - totalRead);
607 			if (bytesRead == 0)
608 				throw StreamException(B_IO_ERROR);
609 			if (bytesRead < 0)
610 				throw StreamException(bytesRead);
611 
612 			totalRead += bytesRead;
613 		}
614 
615 		string.UnlockBuffer(size);
616 		arguments.AddItem(new StringArgument(string));
617 	} else {
618 		// Skip any bytes left in the literal stream
619 		_SkipLiteral(stream, size);
620 	}
621 }
622 
623 
624 void
625 Response::ParseString(ArgumentList& arguments, BDataIO& stream)
626 {
627 	arguments.AddItem(new StringArgument(ExtractString(stream)));
628 }
629 
630 
631 BString
632 Response::ExtractString(BDataIO& stream)
633 {
634 	BString string;
635 
636 	// TODO: parse modified UTF-7 as described in RFC 3501, 5.1.3
637 	while (true) {
638 		char c = Peek(stream);
639 		if (c == '\0')
640 			break;
641 		if (c <= ' ' || strchr("()[]{}\"", c) != NULL)
642 			return string;
643 
644 		string += Next(stream);
645 	}
646 
647 	throw ParseException("Unexpected end of string");
648 }
649 
650 
651 size_t
652 Response::ExtractNumber(BDataIO& stream)
653 {
654 	BString string = ExtractString(stream);
655 
656 	const char* end;
657 	size_t number = strtoul(string.String(), (char**)&end, 10);
658 	if (end == NULL || end[0] != '\0')
659 		ParseException("Invalid number!");
660 
661 	return number;
662 }
663 
664 
665 void
666 Response::Consume(BDataIO& stream, char expected)
667 {
668 	char c = Next(stream);
669 	if (c != expected)
670 		throw ExpectedParseException(expected, c);
671 }
672 
673 
674 char
675 Response::Next(BDataIO& stream)
676 {
677 	if (fHasNextChar) {
678 		fHasNextChar = false;
679 		return fNextChar;
680 	}
681 
682 	return Read(stream);
683 }
684 
685 
686 char
687 Response::Peek(BDataIO& stream)
688 {
689 	if (fHasNextChar)
690 		return fNextChar;
691 
692 	fNextChar = Read(stream);
693 	fHasNextChar = true;
694 
695 	return fNextChar;
696 }
697 
698 
699 char
700 Response::Read(BDataIO& stream)
701 {
702 	char c;
703 	ssize_t bytesRead = stream.Read(&c, 1);
704 	if (bytesRead == 1) {
705 		printf("%c", c);
706 		return c;
707 	}
708 
709 	if (bytesRead == 0)
710 		throw StreamException(B_IO_ERROR);
711 
712 	throw StreamException(bytesRead);
713 }
714 
715 
716 void
717 Response::_SkipLiteral(BDataIO& stream, size_t size)
718 {
719 	char buffer[4096];
720 	size_t totalRead = 0;
721 	while (totalRead < size) {
722 		size_t toRead = std::min(sizeof(buffer), size - totalRead);
723 		ssize_t bytesRead = stream.Read(buffer, toRead);
724 		if (bytesRead == 0)
725 			throw StreamException(B_IO_ERROR);
726 		if (bytesRead < 0)
727 			throw StreamException(bytesRead);
728 
729 		totalRead += bytesRead;
730 	}
731 }
732 
733 
734 // #pragma mark -
735 
736 
737 ResponseParser::ResponseParser(BDataIO& stream)
738 	:
739 	fLiteralHandler(NULL)
740 {
741 	SetTo(stream);
742 }
743 
744 
745 ResponseParser::~ResponseParser()
746 {
747 }
748 
749 
750 void
751 ResponseParser::SetTo(BDataIO& stream)
752 {
753 	fStream = &stream;
754 }
755 
756 
757 void
758 ResponseParser::SetLiteralHandler(LiteralHandler* handler)
759 {
760 	fLiteralHandler = handler;
761 }
762 
763 
764 status_t
765 ResponseParser::NextResponse(Response& response, bigtime_t timeout)
766 	throw(ParseException, StreamException)
767 {
768 	response.Parse(*fStream, fLiteralHandler);
769 	return B_OK;
770 }
771 
772 
773 }	// namespace IMAP
774