/** 
 * @file llmime.cpp
 * @author Phoenix
 * @date 2006-12-20
 * @brief Implementation of mime tools.
 *
 * $LicenseInfo:firstyear=2006&license=viewerlgpl$
 * Second Life Viewer Source Code
 * Copyright (C) 2010, Linden Research, Inc.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation;
 * version 2.1 of the License only.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
 * $/LicenseInfo$
 */

#include "linden_common.h"
#include "llmime.h"

#include <vector>

#include "llmemorystream.h"

/**
 * Useful constants.
 */
// Headers specified in rfc-2045 will be canonicalized below.
static const std::string CONTENT_LENGTH("Content-Length");
static const std::string CONTENT_TYPE("Content-Type");
static const S32 KNOWN_HEADER_COUNT = 6;
static const std::string KNOWN_HEADER[KNOWN_HEADER_COUNT] =
{
	CONTENT_LENGTH,
	CONTENT_TYPE,
	std::string("MIME-Version"),
	std::string("Content-Transfer-Encoding"),
	std::string("Content-ID"),
	std::string("Content-Description"),
};

// parser helpers
static const std::string MULTIPART("multipart");
static const std::string BOUNDARY("boundary");
static const std::string END_OF_CONTENT_PARAMETER("\r\n ;\t");
static const std::string SEPARATOR_PREFIX("--");
//static const std::string SEPARATOR_SUFFIX("\r\n");

/*
Content-Type: multipart/mixed; boundary="segment"
Content-Length: 24832

--segment
Content-Type: image/j2c
Content-Length: 23715

<data>

--segment
Content-Type: text/xml; charset=UTF-8

<meta data>
EOF

*/

/**
 * LLMimeIndex
 */

/** 
 * @class LLMimeIndex::Impl
 * @brief Implementation details of the mime index class.
 * @see LLMimeIndex
 */
class LLMimeIndex::Impl
{
public:
	Impl() : mOffset(-1), mUseCount(1)
	{}
	Impl(LLSD headers, S32 offset) :
		mHeaders(headers), mOffset(offset), mUseCount(1)
	{}
public:
	LLSD mHeaders;
	S32 mOffset;
	S32 mUseCount;

	typedef std::vector<LLMimeIndex> sub_part_t;
	sub_part_t mAttachments;
};

LLSD LLMimeIndex::headers() const
{
	return mImpl->mHeaders;
}

S32 LLMimeIndex::offset() const
{
	return mImpl->mOffset;
}

S32 LLMimeIndex::contentLength() const
{
	// Find the content length in the headers.
	S32 length = -1;
	LLSD content_length = mImpl->mHeaders[CONTENT_LENGTH];
	if(content_length.isDefined())
	{
		length = content_length.asInteger();
	}
	return length;
}

std::string LLMimeIndex::contentType() const
{
	std::string type;
	LLSD content_type = mImpl->mHeaders[CONTENT_TYPE];
	if(content_type.isDefined())
	{
		type = content_type.asString();
	}
	return type;
}

bool LLMimeIndex::isMultipart() const
{
	bool multipart = false;
	LLSD content_type = mImpl->mHeaders[CONTENT_TYPE];
	if(content_type.isDefined())
	{
		std::string type = content_type.asString();
		int comp = type.compare(0, MULTIPART.size(), MULTIPART);
		if(0 == comp)
		{
			multipart = true;
		}
	}
	return multipart;
}

S32 LLMimeIndex::subPartCount() const
{
	return mImpl->mAttachments.size();
}

LLMimeIndex LLMimeIndex::subPart(S32 index) const
{
	LLMimeIndex part;
	if((index >= 0) && (index < (S32)mImpl->mAttachments.size()))
	{
		part = mImpl->mAttachments[index];
	}
	return part;
}

LLMimeIndex::LLMimeIndex() : mImpl(new LLMimeIndex::Impl)
{
}

LLMimeIndex::LLMimeIndex(LLSD headers, S32 content_offset) :
	mImpl(new LLMimeIndex::Impl(headers, content_offset))
{
}

LLMimeIndex::LLMimeIndex(const LLMimeIndex& mime) :
	mImpl(mime.mImpl)
{
	++mImpl->mUseCount;
}

LLMimeIndex::~LLMimeIndex()
{
	if(0 == --mImpl->mUseCount)
	{
		delete mImpl;
	}
}

LLMimeIndex& LLMimeIndex::operator=(const LLMimeIndex& mime)
{
	// Increment use count first so that we handle self assignment
	// automatically.
	++mime.mImpl->mUseCount;
	if(0 == --mImpl->mUseCount)
	{
		delete mImpl;
	}
	mImpl = mime.mImpl;
	return *this;
}

bool LLMimeIndex::attachSubPart(LLMimeIndex sub_part)
{
	// *FIX: Should we check for multi-part?
	if(mImpl->mAttachments.size() < S32_MAX)
	{
		mImpl->mAttachments.push_back(sub_part);
		return true;
	}
	return false;
}

/**
 * LLMimeParser
 */
/** 
 * @class LLMimeParser::Impl
 * @brief Implementation details of the mime parser class.
 * @see LLMimeParser
 */
class LLMimeParser::Impl
{
public:
	// @brief Constructor.
	Impl();

	// @brief Reset this for a new parse.
	void reset();

	/** 
	 * @brief Parse a mime entity to find the index information.
	 *
	 * This method will scan the istr until a single complete mime
	 * entity is read, an EOF, or limit bytes have been scanned. The
	 * istr will be modified by this parsing, so pass in a temporary
	 * stream or rewind/reset the stream after this call.
	 * @param istr An istream which contains a mime entity.
	 * @param limit The maximum number of bytes to scan.
	 * @param separator The multipart separator if it is known.
	 * @param is_subpart Set true if parsing a multipart sub part.
	 * @param index[out] The parsed output.
	 * @return Returns true if an index was parsed and no errors occurred.
	 */
	bool parseIndex(
		std::istream& istr,
		S32 limit,
		const std::string& separator,
		bool is_subpart,
		LLMimeIndex& index);

protected:
	/**
	 * @brief parse the headers.
	 *
	 * At the end of a successful parse, mScanCount will be at the
	 * start of the content.
	 * @param istr The input stream.
	 * @param limit maximum number of bytes to process
	 * @param headers[out] A map of the headers found.
	 * @return Returns true if the parse was successful.
	 */
	bool parseHeaders(std::istream& istr, S32 limit, LLSD& headers);

	/**
	 * @brief Figure out the separator string from a content type header.
	 * 
	 * @param multipart_content_type The content type value from the headers.
	 * @return Returns the separator string.
	 */
	std::string findSeparator(std::string multipart_content_type);

	/**
	 * @brief Scan through istr past the separator.
	 *
	 * @param istr The input stream.
	 * @param limit Maximum number of bytes to scan.
	 * @param separator The multipart separator.
	 */
	void scanPastSeparator(
		std::istream& istr,
		S32 limit,
		const std::string& separator);

	/**
	 * @brief Scan through istr past the content of the current mime part.
	 *
	 * @param istr The input stream.
	 * @param limit Maximum number of bytes to scan.
	 * @param headers The headers for this mime part.
	 * @param separator The multipart separator if known.
	 */
	void scanPastContent(
		std::istream& istr,
		S32 limit,
		LLSD headers,
		const std::string separator);

	/**
	 * @brief Eat CRLF.
	 *
	 * This method has no concept of the limit, so ensure you have at
	 * least 2 characters left to eat before hitting the limit. This
	 * method will increment mScanCount as it goes.
	 * @param istr The input stream.
	 * @return Returns true if CRLF was found and consumed off of istr.
	 */
	bool eatCRLF(std::istream& istr);

	// @brief Returns true if parsing should continue.
	bool continueParse() const { return (!mError && mContinue); }

	// @brief anonymous enumeration for parse buffer size.
	enum
	{
		LINE_BUFFER_LENGTH = 1024
	};

protected:
	S32 mScanCount;
	bool mContinue;
	bool mError;
	char mBuffer[LINE_BUFFER_LENGTH];
};

LLMimeParser::Impl::Impl()
{
	reset();
}

void LLMimeParser::Impl::reset()
{
	mScanCount = 0;
	mContinue = true;
	mError = false;
	mBuffer[0] = '\0';
}

bool LLMimeParser::Impl::parseIndex(
	std::istream& istr,
	S32 limit,
	const std::string& separator,
	bool is_subpart,
	LLMimeIndex& index)
{
	LLSD headers;
	bool parsed_something = false;
	if(parseHeaders(istr, limit, headers))
	{
		parsed_something = true;
		LLMimeIndex mime(headers, mScanCount);
		index = mime;
		if(index.isMultipart())
		{
			// Figure out the separator, scan past it, and recurse.
			std::string ct = headers[CONTENT_TYPE].asString();
			std::string sep = findSeparator(ct);
			scanPastSeparator(istr, limit, sep);
			while(continueParse() && parseIndex(istr, limit, sep, true, mime))
			{
				index.attachSubPart(mime);
			}
		}
		else
		{
			// Scan to the end of content.
			scanPastContent(istr, limit, headers, separator);
			if(is_subpart)
			{
				scanPastSeparator(istr, limit, separator);
			}
		}
	}
	if(mError) return false;
	return parsed_something;
}

bool LLMimeParser::Impl::parseHeaders(
	std::istream& istr,
	S32 limit,
	LLSD& headers)
{
	while(continueParse())
	{
		// Get the next line.
		// We subtract 1 from the limit so that we make sure
		// not to read past limit when we get() the newline.
		S32 max_get = llmin((S32)LINE_BUFFER_LENGTH, limit - mScanCount - 1);
		istr.getline(mBuffer, max_get, '\r');
		mScanCount += istr.gcount();
		int c = istr.get();
		if(EOF == c)
		{
			mContinue = false;
			return false;
		}
		++mScanCount;
		if(c != '\n')
		{
			mError = true;
			return false;
		}
		if(mScanCount >= limit)
		{
			mContinue = false;
		}

		// Check if that's the end of headers.
		if('\0' == mBuffer[0])
		{
			break;
		}

		// Split out the name and value.
		// *NOTE: The use of strchr() here is safe since mBuffer is
		// guaranteed to be NULL terminated from the call to getline()
		// above.
		char* colon = strchr(mBuffer, ':');
		if(!colon)
		{
			mError = true;
			return false;
		}

		// Cononicalize the name part, and store the name: value in
		// the headers structure. We do this by iterating through
		// 'known' headers and replacing the value found with the
		// correct one.
		// *NOTE: Not so efficient, but iterating through a small
		// subset should not be too much of an issue.
		std::string name(mBuffer, colon++ - mBuffer);
		while(isspace(*colon)) ++colon;
		std::string value(colon);
		for(S32 ii = 0; ii < KNOWN_HEADER_COUNT; ++ii)
		{
			if(0 == LLStringUtil::compareInsensitive(name, KNOWN_HEADER[ii]))
			{
				name = KNOWN_HEADER[ii];
				break;
			}
		}
		headers[name] = value;
	}
	if(headers.isUndefined()) return false;
	return true;
}

std::string LLMimeParser::Impl::findSeparator(std::string header)
{
	//                               01234567890
	//Content-Type: multipart/mixed; boundary="segment"
	std::string separator;
	std::string::size_type pos = header.find(BOUNDARY);
	if(std::string::npos == pos) return separator;
	pos += BOUNDARY.size() + 1;
	std::string::size_type end;
	if(header[pos] == '"')
	{
		// the boundary is quoted, find the end from pos, and take the
		// substring.
		end = header.find('"', ++pos);
		if(std::string::npos == end)
		{
			// poorly formed boundary.
			mError = true;
		}
	}
	else
	{
		// otherwise, it's every character until a whitespace, end of
		// line, or another parameter begins.
		end = header.find_first_of(END_OF_CONTENT_PARAMETER, pos);
		if(std::string::npos == end)
		{
			// it goes to the end of the string.
			end = header.size();
		}
	}
	if(!mError) separator = header.substr(pos, end - pos);
	return separator;
}

void LLMimeParser::Impl::scanPastSeparator(
	std::istream& istr,
	S32 limit,
	const std::string& sep)
{
	std::ostringstream ostr;
	ostr << SEPARATOR_PREFIX << sep;
	std::string separator = ostr.str();
	bool found_separator = false;
	while(!found_separator && continueParse())
	{
		// Subtract 1 from the limit so that we make sure not to read
		// past limit when we get() the newline.
		S32 max_get = llmin((S32)LINE_BUFFER_LENGTH, limit - mScanCount - 1);
		istr.getline(mBuffer, max_get, '\r');
		mScanCount += istr.gcount();
		if(istr.gcount() >= LINE_BUFFER_LENGTH - 1)
		{
			// that's way too long to be a separator, so ignore it.
			continue;
		}
		int c = istr.get();
		if(EOF == c)
		{
			mContinue = false;
			return;
		}
		++mScanCount;
		if(c != '\n')
		{
			mError = true;
			return;
		}
		if(mScanCount >= limit)
		{
			mContinue = false;
		}
		if(0 == LLStringUtil::compareStrings(std::string(mBuffer), separator))
		{
			found_separator = true;
		}
	}
}

void LLMimeParser::Impl::scanPastContent(
	std::istream& istr,
	S32 limit,
	LLSD headers,
	const std::string separator)
{
	if(headers.has(CONTENT_LENGTH))
	{
		S32 content_length = headers[CONTENT_LENGTH].asInteger();
		// Subtract 2 here for the \r\n after the content.
		S32 max_skip = llmin(content_length, limit - mScanCount - 2);
		istr.ignore(max_skip);
		mScanCount += max_skip;

		// *NOTE: Check for hitting the limit and eof here before
		// checking for the trailing EOF, because our mime parser has
		// to gracefully handle incomplete mime entites.
		if((mScanCount >= limit) || istr.eof())
		{
			mContinue = false;
		}
		else if(!eatCRLF(istr))
		{
			mError = true;
			return;
		}
	}
}

bool LLMimeParser::Impl::eatCRLF(std::istream& istr)
{
	int c = istr.get();
	++mScanCount;
	if(c != '\r')
	{
		return false;
	}
	c = istr.get();
	++mScanCount;
	if(c != '\n')
	{
		return false;
	}
	return true;
}
	

LLMimeParser::LLMimeParser() : mImpl(* new LLMimeParser::Impl)
{
}

LLMimeParser::~LLMimeParser()
{
	delete & mImpl;
}

void LLMimeParser::reset()
{
	mImpl.reset();
}

bool LLMimeParser::parseIndex(std::istream& istr, LLMimeIndex& index)
{
	std::string separator;
	return mImpl.parseIndex(istr, S32_MAX, separator, false, index);
}

bool LLMimeParser::parseIndex(
	const std::vector<U8>& buffer,
	LLMimeIndex& index)
{
	LLMemoryStream mstr(&buffer[0], buffer.size());
	return parseIndex(mstr, buffer.size() + 1, index);
}

bool LLMimeParser::parseIndex(
	std::istream& istr,
	S32 limit,
	LLMimeIndex& index)
{
	std::string separator;
	return mImpl.parseIndex(istr, limit, separator, false, index);
}

bool LLMimeParser::parseIndex(const U8* buffer, S32 length, LLMimeIndex& index)
{
	LLMemoryStream mstr(buffer, length);
	return parseIndex(mstr, length + 1, index);
}

/*
bool LLMimeParser::verify(std::istream& isr, LLMimeIndex& index) const
{
	return false;
}

bool LLMimeParser::verify(U8* buffer, S32 length, LLMimeIndex& index) const
{
	LLMemoryStream mstr(buffer, length);
	return verify(mstr, index);
}
*/
