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
23namespace IMAP {
24
25
26// Note, the following alphabet is a modified base64; the '/' is replaced by
27// a ',' here.
28static 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};
36static char kInverseBase64Alphabet[128];
37static bool kInverseBase64Initialized = false;
38
39
40RFC3501Encoding::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
51RFC3501Encoding::~RFC3501Encoding()
52{
53}
54
55
56BString
57RFC3501Encoding::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
97BString
98RFC3501Encoding::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
153void
154RFC3501Encoding::_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.
175void
176RFC3501Encoding::_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
194ArgumentList::ArgumentList()
195	:
196	BObjectList<Argument>(5, true)
197{
198}
199
200
201ArgumentList::~ArgumentList()
202{
203}
204
205
206bool
207ArgumentList::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
220BString
221ArgumentList::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
232bool
233ArgumentList::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
243bool
244ArgumentList::EqualsAt(int32 index, const char* string) const
245{
246	return StringAt(index).ICompare(string) == 0;
247}
248
249
250ArgumentList&
251ArgumentList::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
263bool
264ArgumentList::IsListAt(int32 index) const
265{
266	return index >= 0 && index < CountItems()
267		&& dynamic_cast<ListArgument*>(ItemAt(index)) != NULL;
268}
269
270
271bool
272ArgumentList::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
282uint32
283ArgumentList::NumberAt(int32 index) const
284{
285	return atoul(StringAt(index).String());
286}
287
288
289bool
290ArgumentList::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
301BString
302ArgumentList::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
318Argument::Argument()
319{
320}
321
322
323Argument::~Argument()
324{
325}
326
327
328// #pragma mark -
329
330
331ListArgument::ListArgument(char kind)
332	:
333	fKind(kind)
334{
335}
336
337
338BString
339ListArgument::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
352StringArgument::StringArgument(const BString& string)
353	:
354	fString(string)
355{
356}
357
358
359StringArgument::StringArgument(const StringArgument& other)
360	:
361	fString(other.fString)
362{
363}
364
365
366BString
367StringArgument::ToString() const
368{
369	return fString;
370}
371
372
373// #pragma mark -
374
375
376ParseException::ParseException()
377{
378	fBuffer[0] = '\0';
379}
380
381
382ParseException::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
394StreamException::StreamException(status_t status)
395	:
396	fStatus(status)
397{
398}
399
400
401// #pragma mark -
402
403
404ExpectedParseException::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
414const char*
415ExpectedParseException::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
425LiteralHandler::LiteralHandler()
426{
427}
428
429
430LiteralHandler::~LiteralHandler()
431{
432}
433
434
435// #pragma mark -
436
437
438Response::Response()
439	:
440	fTag(0),
441	fContinuation(false),
442	fHasNextChar(false)
443{
444}
445
446
447Response::~Response()
448{
449}
450
451
452void
453Response::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
481bool
482Response::IsCommand(const char* command) const
483{
484	return IsUntagged() && EqualsAt(0, command);
485}
486
487
488char
489Response::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
538void
539Response::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
553void
554Response::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
577void
578Response::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
623void
624Response::ParseString(ArgumentList& arguments, BDataIO& stream)
625{
626	arguments.AddItem(new StringArgument(ExtractString(stream)));
627}
628
629
630BString
631Response::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
650size_t
651Response::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
664void
665Response::Consume(BDataIO& stream, char expected)
666{
667	char c = Next(stream);
668	if (c != expected)
669		throw ExpectedParseException(expected, c);
670}
671
672
673char
674Response::Next(BDataIO& stream)
675{
676	if (fHasNextChar) {
677		fHasNextChar = false;
678		return fNextChar;
679	}
680
681	return Read(stream);
682}
683
684
685char
686Response::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
698char
699Response::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
715void
716Response::_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
736ResponseParser::ResponseParser(BDataIO& stream)
737	:
738	fLiteralHandler(NULL)
739{
740	SetTo(stream);
741}
742
743
744ResponseParser::~ResponseParser()
745{
746}
747
748
749void
750ResponseParser::SetTo(BDataIO& stream)
751{
752	fStream = &stream;
753}
754
755
756void
757ResponseParser::SetLiteralHandler(LiteralHandler* handler)
758{
759	fLiteralHandler = handler;
760}
761
762
763status_t
764ResponseParser::NextResponse(Response& response, bigtime_t timeout)
765{
766	response.Parse(*fStream, fLiteralHandler);
767	return B_OK;
768}
769
770
771}	// namespace IMAP
772