/*
 * Copyright 2011-2016, Axel Dörfler, axeld@pinc-software.de.
 * Distributed under the terms of the MIT License.
 */
 
 
#include "Response.h"
 
#include <algorithm>
#include <stdlib.h>
 
#include <UnicodeChar.h>
 
 
#define TRACE_IMAP
#ifdef TRACE_IMAP
#	define TRACE(...) printf(__VA_ARGS__)
#else
#	define TRACE(...) ;
#endif
 
 
namespace IMAP {
 
 
// Note, the following alphabet is a modified base64; the '/' is replaced by
// a ',' here.
static const char kBase64Alphabet[64] = {
  'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
  'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
  'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
  'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
  '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
  '+', ','
};
static char kInverseBase64Alphabet[128];
static bool kInverseBase64Initialized = false;
 
 
RFC3501Encoding::RFC3501Encoding()
{
	if (!kInverseBase64Initialized) {
		// This is not thread safe, but it's not harmful
		for (size_t i = 0; i < sizeof(kBase64Alphabet); i++)
			kInverseBase64Alphabet[(int)kBase64Alphabet[i]] = i + 1;
		kInverseBase64Initialized = true;
	}
}
 
 
RFC3501Encoding::~RFC3501Encoding()
{
}
 
 
BString
RFC3501Encoding::Encode(const BString& clearText) const
{
	const char* clear = clearText.String();
	bool shifted = false;
	int32 bitsToWrite = 0;
	int32 sextet = 0;
	BString buffer;
 
	while (true) {
		uint32 c = BUnicodeChar::FromUTF8(&clear);
		if (c == 0)
			break;
 
		if (!shifted && c == '&')
			buffer += "&-";
		else if (c >= 0x20 && c <= 0x7e) {
			_Unshift(buffer, bitsToWrite, sextet, shifted);
			buffer += c;
		} else {
			// Enter shifted mode, encode in base64
			if (!shifted) {
				buffer += '&';
				shifted = true;
			}
 
			bitsToWrite += 16;
			while (bitsToWrite >= 6) {
				bitsToWrite -= 6;
				buffer += kBase64Alphabet[(sextet + (c >> bitsToWrite)) & 0x3f];
				sextet = 0;
			}
			sextet = (c << (6 - bitsToWrite)) & 0x3f;
		}
	}
 
	_Unshift(buffer, bitsToWrite, sextet, shifted);
	return buffer;
}
 
 
BString
RFC3501Encoding::Decode(const BString& encodedText) const
{
	int32 end = encodedText.Length();
	BString buffer;
	for (int32 i = 0; i < end; i++) {
		uint8 c = (uint8)encodedText.ByteAt(i);
		if (c == '&') {
			if (i < end - 1 && encodedText.ByteAt(i + 1) == '-') {
				// just add an ampersand
				buffer += '&';
				i++;
			} else {
				// base64 encoded chunk
				uint32 value = 0;
				int32 bitsRead = 0;
				while (true) {
					if (++i >= end)
						throw ParseException("Malformed base64!");
 
					c = encodedText.ByteAt(i);
					if (c == '-') {
						if (value != 0 || bitsRead >= 6)
							throw ParseException("Base64 encoding ends early!");
						break;
					}
					if (c >= 128)
						throw ParseException("Malformed base64!");
					int32 sextet = kInverseBase64Alphabet[c] - 1;
					if (sextet >= 0) {
						bitsRead += 6;
						if (bitsRead < 16) {
							value += sextet << (16 - bitsRead);
						} else {
							bitsRead -= 16;
							value += sextet >> bitsRead;
							_ToUTF8(buffer, value);
 
							// Move on to next character
							value = (sextet << (16 - bitsRead)) & 0xffff;
						}
					} else {
						buffer += c;
						if (value != 0 || bitsRead >= 6)
							throw ParseException("Malformed base64!");
						break;
					}
				}
			}
		} else
			buffer += c;
	}
	return buffer;
}
 
 
void
RFC3501Encoding::_ToUTF8(BString& string, uint32 c) const
{
	if (c < 0x80)
		string += (char)c;
	else if (c < 0x800) {
		string += 0xc0 | (c >> 6);
		string += 0x80 | (c & 0x3f);
	} else if (c < 0x10000) {
		string += 0xe0 | (c >> 12);
		string += 0x80 | ((c >> 6) & 0x3f);
		string += 0x80 | (c & 0x3f);
	} else if (c <= 0x10ffff) {
		string += 0xf0 | (c >> 18);
		string += 0x80 | ((c >> 12) & 0x3f);
		string += 0x80 | ((c >> 6) & 0x3f);
		string += 0x80 | (c & 0x3f);
	}
}
 
 
//!	Exit base64, or "shifted" mode.
void
RFC3501Encoding::_Unshift(BString& buffer, int32& bitsToWrite, int32& sextet,
	bool& shifted) const
{
	if (!shifted)
		return;
 
	if (bitsToWrite != 0)
		buffer += kBase64Alphabet[sextet];
	buffer += '-';
	sextet = 0;
	bitsToWrite = 0;
	shifted = false;
}
 
 
// #pragma mark -
 
 
ArgumentList::ArgumentList()
	:
	BObjectList<Argument>(5, true)
{
}
 
 
ArgumentList::~ArgumentList()
{
}
 
 
bool
ArgumentList::Contains(const char* string) const
{
	for (int32 i = 0; i < CountItems(); i++) {
		if (StringArgument* argument
				= dynamic_cast<StringArgument*>(ItemAt(i))) {
			if (argument->String().ICompare(string) == 0)
				return true;
		}
	}
	return false;
}
 
 
BString
ArgumentList::StringAt(int32 index) const
{
	if (index >= 0 && index < CountItems()) {
		if (StringArgument* argument
				= dynamic_cast<StringArgument*>(ItemAt(index)))
			return argument->String();
	}
	return "";
}
 
 
bool
ArgumentList::IsStringAt(int32 index) const
{
	if (index >= 0 && index < CountItems()) {
		if (dynamic_cast<StringArgument*>(ItemAt(index)) != NULL)
			return true;
	}
	return false;
}
 
 
bool
ArgumentList::EqualsAt(int32 index, const char* string) const
{
	return StringAt(index).ICompare(string) == 0;
}
 
 
ArgumentList&
ArgumentList::ListAt(int32 index) const
{
	if (index >= 0 && index < CountItems()) {
		if (ListArgument* argument = dynamic_cast<ListArgument*>(ItemAt(index)))
			return argument->List();
	}
 
	static ArgumentList empty;
	return empty;
}
 
 
bool
ArgumentList::IsListAt(int32 index) const
{
	return index >= 0 && index < CountItems()
		&& dynamic_cast<ListArgument*>(ItemAt(index)) != NULL;
}
 
 
bool
ArgumentList::IsListAt(int32 index, char kind) const
{
	if (index >= 0 && index < CountItems()) {
		if (ListArgument* argument = dynamic_cast<ListArgument*>(ItemAt(index)))
			return argument->Kind() == kind;
	}
	return false;
}
 
 
uint32
ArgumentList::NumberAt(int32 index) const
{
	return atoul(StringAt(index).String());
}
 
 
bool
ArgumentList::IsNumberAt(int32 index) const
{
	BString string = StringAt(index);
	for (int32 i = 0; i < string.Length(); i++) {
		if (!isdigit(string.ByteAt(i)))
			return false;
	}
	return string.Length() > 0;
}
 
 
BString
ArgumentList::ToString() const
{
	BString string;
 
	for (int32 i = 0; i < CountItems(); i++) {
		if (i > 0)
			string += ", ";
		string += ItemAt(i)->ToString();
	}
	return string;
}
 
 
// #pragma mark -
 
 
Argument::Argument()
{
}
 
 
Argument::~Argument()
{
}
 
 
// #pragma mark -
 
 
ListArgument::ListArgument(char kind)
	:
	fKind(kind)
{
}
 
 
BString
ListArgument::ToString() const
{
	BString string(fKind == '[' ? "[" : "(");
	string += fList.ToString();
	string += fKind == '[' ? "]" : ")";
 
	return string;
}
 
 
// #pragma mark -
 
 
StringArgument::StringArgument(const BString& string)
	:
	fString(string)
{
}
 
 
StringArgument::StringArgument(const StringArgument& other)
	:
	fString(other.fString)
{
}
 
 
BString
StringArgument::ToString() const
{
	return fString;
}
 
 
// #pragma mark -
 
 
ParseException::ParseException()
{
	fBuffer[0] = '\0';
}
 
 
ParseException::ParseException(const char* format, ...)
{
	va_list args;
	va_start(args, format);
	vsnprintf(fBuffer, sizeof(fBuffer), format, args);
	va_end(args);
}
 
 
// #pragma mark -
 
 
StreamException::StreamException(status_t status)
	:
	fStatus(status)
{
}
 
 
// #pragma mark -
 
 
ExpectedParseException::ExpectedParseException(char expected, char instead)
{
	char bufferA[8];
	char bufferB[8];
	snprintf(fBuffer, sizeof(fBuffer), "Expected %s, but got %s instead!",
		CharToString(bufferA, sizeof(bufferA), expected),
		CharToString(bufferB, sizeof(bufferB), instead));
}
 
 
const char*
ExpectedParseException::CharToString(char* buffer, size_t size, char c)
{
	snprintf(buffer, size, isprint(c) ? "\"%c\"" : "(%x)", c);
	return buffer;
}
 
 
// #pragma mark -
 
 
LiteralHandler::LiteralHandler()
{
}
 
 
LiteralHandler::~LiteralHandler()
{
}
 
 
// #pragma mark -
 
 
Response::Response()
	:
	fTag(0),
	fContinuation(false),
	fHasNextChar(false)
{
}
 
 
Response::~Response()
{
}
 
 
void
Response::Parse(BDataIO& stream, LiteralHandler* handler)
	throw(ParseException, StreamException)
{
	MakeEmpty();
	fLiteralHandler = handler;
	fTag = 0;
	fContinuation = false;
	fHasNextChar = false;
 
	char begin = Next(stream);
	if (begin == '*') {
		// Untagged response
		Consume(stream, ' ');
	} else if (begin == '+') {
		// Continuation
		fContinuation = true;
	} else if (begin == 'A') {
		// Tagged response
		fTag = ExtractNumber(stream);
		Consume(stream, ' ');
	} else
		throw ParseException("Unexpected response begin");
 
	char c = ParseLine(*this, stream);
	if (c != '\0')
		throw ExpectedParseException('\0', c);
}
 
 
bool
Response::IsCommand(const char* command) const
{
	return IsUntagged() && EqualsAt(0, command);
}
 
 
char
Response::ParseLine(ArgumentList& arguments, BDataIO& stream)
{
	while (true) {
		char c = Peek(stream);
		if (c == '\0')
			break;
 
		switch (c) {
			case '(':
				ParseList(arguments, stream, '(', ')');
				break;
			case '[':
				ParseList(arguments, stream, '[', ']');
				break;
			case ')':
			case ']':
				Consume(stream, c);
				return c;
			case '"':
				ParseQuoted(arguments, stream);
				break;
			case '{':
				ParseLiteral(arguments, stream);
				break;
 
			case ' ':
			case '\t':
				// whitespace
				Consume(stream, c);
				break;
 
			case '\r':
				Consume(stream, '\r');
				Consume(stream, '\n');
				return '\0';
			case '\n':
				Consume(stream, '\n');
				return '\0';
 
			default:
				ParseString(arguments, stream);
				break;
		}
	}
 
	return '\0';
}
 
 
void
Response::ParseList(ArgumentList& arguments, BDataIO& stream, char start,
	char end)
{
	Consume(stream, start);
 
	ListArgument* argument = new ListArgument(start);
	arguments.AddItem(argument);
 
	char c = ParseLine(argument->List(), stream);
	if (c != end)
		throw ExpectedParseException(end, c);
}
 
 
void
Response::ParseQuoted(ArgumentList& arguments, BDataIO& stream)
{
	Consume(stream, '"');
 
	BString string;
	while (true) {
		char c = Next(stream);
		if (c == '\\') {
			c = Next(stream);
		} else if (c == '"') {
			arguments.AddItem(new StringArgument(string));
			return;
		}
		if (c == '\0')
			break;
 
		string += c;
	}
 
	throw ParseException("Unexpected end of qouted string!");
}
 
 
void
Response::ParseLiteral(ArgumentList& arguments, BDataIO& stream)
{
	Consume(stream, '{');
	size_t size = ExtractNumber(stream);
	Consume(stream, '}');
	Consume(stream, '\r');
	Consume(stream, '\n');
 
	bool handled = false;
	if (fLiteralHandler != NULL) {
		handled = fLiteralHandler->HandleLiteral(*this, arguments, stream,
			size);
	}
 
	if (!handled && size <= 65536) {
		// The default implementation just adds the data as a string
		TRACE("Trying to read literal with %" B_PRIuSIZE " bytes.\n", size);
		BString string;
		char* buffer = string.LockBuffer(size);
		if (buffer == NULL) {
			throw ParseException("Not enough memory for literal of %"
				B_PRIuSIZE " bytes.", size);
		}
 
		size_t totalRead = 0;
		while (totalRead < size) {
			ssize_t bytesRead = stream.Read(buffer + totalRead,
				size - totalRead);
			if (bytesRead == 0)
				throw StreamException(B_IO_ERROR);
			if (bytesRead < 0)
				throw StreamException(bytesRead);
 
			totalRead += bytesRead;
		}
 
		string.UnlockBuffer(size);
		arguments.AddItem(new StringArgument(string));
	} else {
		// Skip any bytes left in the literal stream
		_SkipLiteral(stream, size);
	}
}
 
 
void
Response::ParseString(ArgumentList& arguments, BDataIO& stream)
{
	arguments.AddItem(new StringArgument(ExtractString(stream)));
}
 
 
BString
Response::ExtractString(BDataIO& stream)
{
	BString string;
 
	// TODO: parse modified UTF-7 as described in RFC 3501, 5.1.3
	while (true) {
		char c = Peek(stream);
		if (c == '\0')
			break;
		if (c <= ' ' || strchr("()[]{}\"", c) != NULL)
			return string;
 
		string += Next(stream);
	}
 
	throw ParseException("Unexpected end of string");
}
 
 
size_t
Response::ExtractNumber(BDataIO& stream)
{
	BString string = ExtractString(stream);
 
	const char* end;
	size_t number = strtoul(string.String(), (char**)&end, 10);
	if (end == NULL || end[0] != '\0')
		ParseException("Invalid number!");
 
	return number;
}
 
 
void
Response::Consume(BDataIO& stream, char expected)
{
	char c = Next(stream);
	if (c != expected)
		throw ExpectedParseException(expected, c);
}
 
 
char
Response::Next(BDataIO& stream)
{
	if (fHasNextChar) {
		fHasNextChar = false;
		return fNextChar;
	}
 
	return Read(stream);
}
 
 
char
Response::Peek(BDataIO& stream)
{
	if (fHasNextChar)
		return fNextChar;
 
	fNextChar = Read(stream);
	fHasNextChar = true;
 
	return fNextChar;
}
 
 
char
Response::Read(BDataIO& stream)
{
	char c;
	ssize_t bytesRead = stream.Read(&c, 1);
	if (bytesRead == 1) {
		printf("%c", c);
		return c;
	}
 
	if (bytesRead == 0)
		throw StreamException(B_IO_ERROR);
 
	throw StreamException(bytesRead);
}
 
 
void
Response::_SkipLiteral(BDataIO& stream, size_t size)
{
	char buffer[4096];
	size_t totalRead = 0;
	while (totalRead < size) {
		size_t toRead = std::min(sizeof(buffer), size - totalRead);
		ssize_t bytesRead = stream.Read(buffer, toRead);
		if (bytesRead == 0)
			throw StreamException(B_IO_ERROR);
		if (bytesRead < 0)
			throw StreamException(bytesRead);
 
		totalRead += bytesRead;
	}
}
 
 
// #pragma mark -
 
 
ResponseParser::ResponseParser(BDataIO& stream)
	:
	fLiteralHandler(NULL)
{
	SetTo(stream);
}
 
 
ResponseParser::~ResponseParser()
{
}
 
 
void
ResponseParser::SetTo(BDataIO& stream)
{
	fStream = &stream;
}
 
 
void
ResponseParser::SetLiteralHandler(LiteralHandler* handler)
{
	fLiteralHandler = handler;
}
 
 
status_t
ResponseParser::NextResponse(Response& response, bigtime_t timeout)
	throw(ParseException, StreamException)
{
	response.Parse(*fStream, fLiteralHandler);
	return B_OK;
}
 
 
}	// namespace IMAP

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fLiteralHandler, fNextChar.

V596 The object was created but it is not being used. The 'throw' keyword could be missing: throw ParseException(FOO);