//
// MessageHeader.cpp
//
// Library: Net
// Package: Messages
// Module:  MessageHeader
//
// Copyright (c) 2005-2006, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#include "Poco/Net/MessageHeader.h"
#include "Poco/Net/NetException.h"
#include "Poco/String.h"
#include "Poco/Ascii.h"
#include "Poco/TextConverter.h"
#include "Poco/StringTokenizer.h"
#include "Poco/Base64Decoder.h"
#include "Poco/UTF8Encoding.h"
#include <sstream>


namespace Poco {
namespace Net {


MessageHeader::MessageHeader():
	_fieldLimit(DFL_FIELD_LIMIT),
	_nameLengthLimit(DFL_NAME_LENGTH_LIMIT),
	_valueLengthLimit(DFL_VALUE_LENGTH_LIMIT),
	_autoDecode(true),
	_decodedOnRead(false)
{
}


MessageHeader::MessageHeader(const MessageHeader& messageHeader):
	NameValueCollection(messageHeader),
	_fieldLimit(DFL_FIELD_LIMIT),
	_nameLengthLimit(DFL_NAME_LENGTH_LIMIT),
	_valueLengthLimit(DFL_VALUE_LENGTH_LIMIT)
{
}


MessageHeader::~MessageHeader()
{
}


MessageHeader& MessageHeader::operator = (const MessageHeader& messageHeader)
{
	NameValueCollection::operator = (messageHeader);
	return *this;
}


void MessageHeader::write(std::ostream& ostr) const
{
	NameValueCollection::ConstIterator it = begin();
	while (it != end())
	{
		ostr << it->first << ": " << it->second << "\r\n";
		++it;
	}
}


void MessageHeader::read(std::istream& istr)
{
	static const int eof = std::char_traits<char>::eof();
	std::streambuf& buf = *istr.rdbuf();

	std::string name;
	std::string value;
	name.reserve(32);
	value.reserve(64);
	int ch = buf.sbumpc();
	int fields = 0;
	while (ch != eof && ch != '\r' && ch != '\n')
	{
		if (_fieldLimit > 0 && fields == _fieldLimit)
			throw MessageException("Too many header fields");
		name.clear();
		value.clear();
		while (ch != eof && ch != ':' && ch != '\n' && static_cast<int>(name.length()) < _nameLengthLimit)
		{
			name += ch;
			ch = buf.sbumpc();
		}
		if (ch == '\n') { ch = buf.sbumpc(); continue; } // ignore invalid header lines
		if (ch != ':') throw MessageException("Field name too long/no colon found");
		if (ch != eof) ch = buf.sbumpc(); // ':'
		while (ch != eof && Poco::Ascii::isSpace(ch) && ch != '\r' && ch != '\n') ch = buf.sbumpc();
		while (ch != eof && ch != '\r' && ch != '\n' && static_cast<int>(value.length()) < _valueLengthLimit)
		{
			value += ch;
			ch = buf.sbumpc();
		}
		if (ch == '\r') ch = buf.sbumpc();
		if (ch == '\n')
			ch = buf.sbumpc();
		else if (ch != eof)
			throw MessageException("Field value too long/no CRLF found");
		while (ch == ' ' || ch == '\t') // folding
		{
			while (ch != eof && ch != '\r' && ch != '\n' && static_cast<int>(value.length()) < _valueLengthLimit)
			{
				value += ch;
				ch = buf.sbumpc();
			}
			if (ch == '\r') ch = buf.sbumpc();
			if (ch == '\n')
				ch = buf.sbumpc();
			else if (ch != eof)
				throw MessageException("Folded field value too long/no CRLF found");
		}

		// TODO: Add to the if below?
		Poco::trimRightInPlace(value);

		if (_autoDecode)
			add(name, decodeWord(value));
		else
			add(name, value);

		++fields;
	}
	// Save the state of the auto decode at the time of reading.
	_decodedOnRead = _autoDecode;
	if (istr.good() && ch != eof)
		istr.putback(ch);
}


void MessageHeader::setAutoDecode(bool decode)
{
	_autoDecode = decode;
}


bool MessageHeader::getAutoDecode() const
{
	return _autoDecode;
}


std::string MessageHeader::getDecoded(const std::string& name) const
{
	const auto& value { get(name) };
	if (_decodedOnRead)
	{
		// Already decoded, just return the value
		return value;
	}
	return decodeWord(value);
}


std::string MessageHeader::getDecoded(const std::string& name, const std::string& defaultValue) const
{
	if (!has(name))
	{
		return defaultValue;
	}
	return getDecoded(name);
}


int MessageHeader::getFieldLimit() const
{
	return _fieldLimit;
}


void MessageHeader::setFieldLimit(int limit)
{
	poco_assert (limit >= 0);

	_fieldLimit = limit;
}


int MessageHeader::getNameLengthLimit() const
{
	return _nameLengthLimit;
}

void MessageHeader::setNameLengthLimit(int limit)
{
	poco_assert(limit >= 0);

	_nameLengthLimit = limit;
}


int MessageHeader::getValueLengthLimit() const
{
	return _valueLengthLimit;
}

void MessageHeader::setValueLengthLimit(int limit)
{
	poco_assert(limit >= 0);

	_valueLengthLimit = limit;
}


bool MessageHeader::hasToken(const std::string& fieldName, const std::string& token) const
{
	std::string field = get(fieldName, "");
	std::vector<std::string> tokens;
	splitElements(field, tokens, true);
	for (const auto& t: tokens)
	{
		if (Poco::icompare(t, token) == 0)
			return true;
	}
	return false;
}


void MessageHeader::splitElements(const std::string& s, std::vector<std::string>& elements, bool ignoreEmpty)
{
	elements.clear();
	std::string::const_iterator it  = s.begin();
	std::string::const_iterator end = s.end();
	std::string elem;
	elem.reserve(64);
	while (it != end)
	{
		if (*it == '"')
		{
			elem += *it++;
			while (it != end && *it != '"')
			{
				if (*it == '\\')
				{
					++it;
					if (it != end) elem += *it++;
				}
				else elem += *it++;
			}
			if (it != end) elem += *it++;
		}
		else if (*it == '\\')
		{
			++it;
			if (it != end) elem += *it++;
		}
		else if (*it == ',')
		{
			Poco::trimInPlace(elem);
			if (!ignoreEmpty || !elem.empty())
				elements.push_back(elem);
			elem.clear();
			++it;
		}
		else elem += *it++;
	}
	if (!elem.empty())
	{
		Poco::trimInPlace(elem);
		if (!ignoreEmpty || !elem.empty())
			elements.push_back(elem);
	}
}


void MessageHeader::splitParameters(const std::string& s, std::string& value, NameValueCollection& parameters)
{
	value.clear();
	parameters.clear();
	std::string::const_iterator it  = s.begin();
	std::string::const_iterator end = s.end();
	while (it != end && Poco::Ascii::isSpace(*it)) ++it;
	while (it != end && *it != ';') value += *it++;
	Poco::trimRightInPlace(value);
	if (it != end) ++it;
	splitParameters(it, end, parameters);
}


void MessageHeader::splitParameters(const std::string::const_iterator& begin, const std::string::const_iterator& end, NameValueCollection& parameters)
{
	std::string pname;
	std::string pvalue;
	pname.reserve(32);
	pvalue.reserve(64);
	std::string::const_iterator it = begin;
	while (it != end)
	{
		pname.clear();
		pvalue.clear();
		while (it != end && Poco::Ascii::isSpace(*it)) ++it;
		while (it != end && *it != '=' && *it != ';') pname += *it++;
		Poco::trimRightInPlace(pname);
		if (it != end && *it != ';') ++it;
		while (it != end && Poco::Ascii::isSpace(*it)) ++it;
		while (it != end && *it != ';')
		{
			if (*it == '"')
			{
				++it;
				while (it != end && *it != '"')
				{
					if (*it == '\\')
					{
						++it;
						if (it != end) pvalue += *it++;
					}
					else pvalue += *it++;
				}
				if (it != end) ++it;
			}
			else if (*it == '\\')
			{
				++it;
				if (it != end) pvalue += *it++;
			}
			else pvalue += *it++;
		}
		Poco::trimRightInPlace(pvalue);
		if (!pname.empty()) parameters.add(pname, pvalue);
		if (it != end) ++it;
	}
}


void MessageHeader::quote(const std::string& value, std::string& result, bool allowSpace)
{
	bool mustQuote = false;
	for (std::string::const_iterator it = value.begin(); !mustQuote && it != value.end(); ++it)
	{
		if (!Poco::Ascii::isAlphaNumeric(*it) && *it != '.' && *it != '_' && *it != '-' && !(Poco::Ascii::isSpace(*it) && allowSpace))
			mustQuote = true;
	}
	if (mustQuote) result += '"';
	result.append(value);
	if (mustQuote) result += '"';
}


void MessageHeader::decodeRFC2047(const std::string& ins, std::string& outs, const std::string& charset_to)
{
	std::string tempout;
	StringTokenizer tokens(ins, "?");

	std::string charset = toUpper(tokens[0]);
	std::string encoding = toUpper(tokens[1]);
	std::string text = tokens[2];

	std::istringstream istr(text);

	if (encoding == "B")
	{
		// Base64 encoding.
		Base64Decoder decoder(istr);
		for (char c; decoder.get(c); tempout += c) {}
	}
	else if (encoding == "Q")
	{
		// Quoted encoding.
		for (char c; istr.get(c);)
		{
			if (c == '_')
			{
				//RFC 2047  _ is a space.
				tempout += " ";
				continue;
			}

			// FIXME: check that we have enought chars-
			if (c == '=')
			{
				// The next two chars are hex representation of the complete byte.
				std::string hex;
				for (int i = 0; i < 2; i++)
				{
					istr.get(c);
					hex += c;
				}
				hex = toUpper(hex);
				tempout += (char)(int)::strtol(hex.c_str(), nullptr, 16);
				continue;
			}
			tempout += c;
		}
	}
	else
	{
		// Wrong encoding
		outs = ins;
		return;
	}

	// convert to the right charset.
	if (charset != charset_to)
	{
		try
		{
			TextEncoding& enc = TextEncoding::byName(charset);
			TextEncoding& dec = TextEncoding::byName(charset_to);
			TextConverter converter(enc, dec);
			converter.convert(tempout, outs);
		}
		catch (...)
		{
			// FIXME: Unsuported encoding...
			outs = tempout;
		}
	}
	else
	{
		// Not conversion necesary.
		outs = tempout;
	}
}


std::string MessageHeader::decodeWord(const std::string& text, const std::string& charset)
{
	std::string outs, tmp = text;
	size_t pos = tmp.find("=?");
	do {
		std::string tmp2;
		// find the begining of the next rfc2047 chunk
		if (pos == std::string::npos) {
			// No more found, return
			outs += tmp;
			break;
		}

		// check if there are standar text before the rfc2047 chunk, and if so, copy it.
		if (pos > 0) {
			outs += tmp.substr(0, pos);
		}

		// remove text already copied.
		tmp = tmp.substr(pos + 2);

		// find the first separator
		size_t pos1 = tmp.find("?");
		if (pos1 == std::string::npos) {
			// not found.
			outs += tmp;
			break;
		}

		// find the second separator
		size_t pos2 = tmp.find("?", pos1 + 1);
		if (pos2 == std::string::npos) {
			// not found
			outs += tmp;
			break;
		}

		// find the end of the actual rfc2047 chunk
		size_t pos3 = tmp.find("?=", pos2 + 1);
		if (pos3 == std::string::npos) {
			// not found.
			outs += tmp;
			break;
		}

		// At this place, there are a valid rfc2047 chunk, so decode and copy the result.
		decodeRFC2047(tmp.substr(0, pos3), tmp2, charset);
		outs += tmp2;

		// Jump at the rest of the string and repeat the whole process.
		tmp = tmp.substr(pos3 + 2);
		pos = tmp.find("=?");
		if (pos != std::string::npos)
		{
			std::string betweenChunks = tmp.substr(0, pos);
			if (betweenChunks.find_first_not_of(" \t\v\n") == std::string::npos)
			{
				tmp = tmp.substr(pos);
				pos = 0;
			}
		}
	} while (true);

	return outs;
}


} } // namespace Poco::Net
