xref: /haiku/src/add-ons/mail_daemon/inbound_protocols/imap/imap_lib/Response.cpp (revision 97dfeb96704e5dbc5bec32ad7b21379d0125e031)
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) throw(ParseException)
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
482 Response::IsCommand(const char* command) const
483 {
484 	return IsUntagged() && EqualsAt(0, command);
485 }
486 
487 
488 char
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
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
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
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 ParseException("Unexpected end of literal");
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
624 Response::ParseString(ArgumentList& arguments, BDataIO& stream)
625 {
626 	arguments.AddItem(new StringArgument(ExtractString(stream)));
627 }
628 
629 
630 BString
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
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 		ParseException("Invalid number!");
659 
660 	return number;
661 }
662 
663 
664 void
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
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
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
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 ParseException("Unexpected end of stream");
710 
711 	throw StreamException(bytesRead);
712 }
713 
714 
715 void
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 ParseException("Unexpected end of literal");
725 		if (bytesRead < 0)
726 			throw StreamException(bytesRead);
727 
728 		totalRead += bytesRead;
729 	}
730 }
731 
732 
733 // #pragma mark -
734 
735 
736 ResponseParser::ResponseParser(BDataIO& stream)
737 	:
738 	fLiteralHandler(NULL)
739 {
740 	SetTo(stream);
741 }
742 
743 
744 ResponseParser::~ResponseParser()
745 {
746 }
747 
748 
749 void
750 ResponseParser::SetTo(BDataIO& stream)
751 {
752 	fStream = &stream;
753 }
754 
755 
756 void
757 ResponseParser::SetLiteralHandler(LiteralHandler* handler)
758 {
759 	fLiteralHandler = handler;
760 }
761 
762 
763 status_t
764 ResponseParser::NextResponse(Response& response, bigtime_t timeout)
765 	throw(ParseException)
766 {
767 	response.Parse(*fStream, fLiteralHandler);
768 	return B_OK;
769 }
770 
771 
772 }	// namespace IMAP
773