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


#include "Poco/Net/HTMLForm.h"
#include "Poco/Net/HTTPRequest.h"
#include "Poco/Net/PartSource.h"
#include "Poco/Net/PartHandler.h"
#include "Poco/Net/MultipartWriter.h"
#include "Poco/Net/MultipartReader.h"
#include "Poco/Net/MessageHeader.h"
#include "Poco/Net/NullPartHandler.h"
#include "Poco/Net/NetException.h"
#include "Poco/NullStream.h"
#include "Poco/CountingStream.h"
#include "Poco/StreamCopier.h"
#include "Poco/URI.h"
#include "Poco/String.h"
#include "Poco/CountingStream.h"
#include "Poco/UTF8String.h"
#include <sstream>


using Poco::NullInputStream;
using Poco::StreamCopier;
using Poco::SyntaxException;
using Poco::URI;
using Poco::icompare;


namespace Poco {
namespace Net {


const std::string HTMLForm::ENCODING_URL           = "application/x-www-form-urlencoded";
const std::string HTMLForm::ENCODING_MULTIPART     = "multipart/form-data";
const int         HTMLForm::UNKNOWN_CONTENT_LENGTH = -1;


class HTMLFormCountingOutputStream: public CountingOutputStream
{
public:
	HTMLFormCountingOutputStream():
		_valid(true)
	{
	}

	bool isValid() const
	{
		return _valid;
	}

	void setValid(bool v)
	{
		_valid = v;
	}

private:
	bool _valid;
};


HTMLForm::HTMLForm():
	_fieldLimit(DFL_FIELD_LIMIT),
	_valueLengthLimit(DFL_MAX_VALUE_LENGTH),
	_encoding(ENCODING_URL)
{
}


HTMLForm::HTMLForm(const std::string& encoding):
	_fieldLimit(DFL_FIELD_LIMIT),
	_valueLengthLimit(DFL_MAX_VALUE_LENGTH),
	_encoding(encoding)
{
}


HTMLForm::HTMLForm(const HTTPRequest& request, std::istream& requestBody, PartHandler& handler):
	_fieldLimit(DFL_FIELD_LIMIT),
	_valueLengthLimit(DFL_MAX_VALUE_LENGTH)
{
	load(request, requestBody, handler);
}


HTMLForm::HTMLForm(const HTTPRequest& request, std::istream& requestBody):
	_fieldLimit(DFL_FIELD_LIMIT),
	_valueLengthLimit(DFL_MAX_VALUE_LENGTH)
{
	load(request, requestBody);
}


HTMLForm::HTMLForm(const HTTPRequest& request):
	_fieldLimit(DFL_FIELD_LIMIT),
	_valueLengthLimit(DFL_MAX_VALUE_LENGTH)
{
	load(request);
}


HTMLForm::~HTMLForm()
{
	for (auto& part: _parts)
	{
		delete part.pSource;
	}
}


void HTMLForm::setEncoding(const std::string& encoding)
{
	_encoding = encoding;
}


void HTMLForm::addPart(const std::string& name, PartSource* pSource)
{
	poco_check_ptr (pSource);

	Part part;
	part.name    = name;
	part.pSource = pSource;
	_parts.push_back(part);
}


void HTMLForm::load(const HTTPRequest& request, std::istream& requestBody, PartHandler& handler)
{
	clear();

	URI uri(request.getURI());
	const std::string& query = uri.getRawQuery();
	if (!query.empty())
	{
		std::istringstream istr(query);
		readUrl(istr);
	}

	if (request.getMethod() == HTTPRequest::HTTP_POST || request.getMethod() == HTTPRequest::HTTP_PUT)
	{
		std::string mediaType;
		NameValueCollection params;
		MessageHeader::splitParameters(request.getContentType(), mediaType, params);
		_encoding = mediaType;
		if (_encoding == ENCODING_MULTIPART)
		{
			_boundary = params["boundary"];
			readMultipart(requestBody, handler);
		}
		else
		{
			readUrl(requestBody);
		}
	}
}


void HTMLForm::load(const HTTPRequest& request, std::istream& requestBody)
{
	NullPartHandler nah;
	load(request, requestBody, nah);
}


void HTMLForm::load(const HTTPRequest& request)
{
	NullPartHandler nah;
	NullInputStream nis;
	load(request, nis, nah);
}


void HTMLForm::read(std::istream& istr, PartHandler& handler)
{
	if (_encoding == ENCODING_URL)
		readUrl(istr);
	else
		readMultipart(istr, handler);
}


void HTMLForm::read(std::istream& istr)
{
	readUrl(istr);
}


void HTMLForm::read(const std::string& queryString)
{
	std::istringstream istr(queryString);
	readUrl(istr);
}


void HTMLForm::prepareSubmit(HTTPRequest& request, int options)
{
	if (request.getMethod() == HTTPRequest::HTTP_POST || request.getMethod() == HTTPRequest::HTTP_PUT)
	{
		if (_encoding == ENCODING_URL)
		{
			request.setContentType(_encoding);
			request.setChunkedTransferEncoding(false);
			Poco::CountingOutputStream ostr;
			writeUrl(ostr);
			request.setContentLength(ostr.chars());
		}
		else
		{
			_boundary = MultipartWriter::createBoundary();
			std::string ct(_encoding);
			ct.append("; boundary=");
			MessageHeader::quote(_boundary, ct);
			request.setContentType(ct);
		}
		if (request.getVersion() == HTTPMessage::HTTP_1_0)
		{
			request.setKeepAlive(false);
			request.setChunkedTransferEncoding(false);
		}
		else if (_encoding != ENCODING_URL && (options & OPT_USE_CONTENT_LENGTH) == 0)
		{
			request.setChunkedTransferEncoding(true);
		}
		if (!request.getChunkedTransferEncoding() && !request.hasContentLength())
		{
			request.setContentLength(calculateContentLength());
		}
	}
	else
	{
		std::string uri = request.getURI();
		std::ostringstream ostr;
		writeUrl(ostr);
		uri.append("?");
		uri.append(ostr.str());
		request.setURI(uri);
	}
}


std::streamsize HTMLForm::calculateContentLength()
{
	if (_encoding == ENCODING_MULTIPART && _boundary.empty())
		throw HTMLFormException("Form must be prepared");

	HTMLFormCountingOutputStream c;
	write(c);
	if (c.isValid())
		return c.chars();
	else
		return UNKNOWN_CONTENT_LENGTH;
}


void HTMLForm::write(std::ostream& ostr, const std::string& boundary)
{
	if (_encoding == ENCODING_URL)
	{
		writeUrl(ostr);
	}
	else
	{
		_boundary = boundary;
		writeMultipart(ostr);
	}
}


void HTMLForm::write(std::ostream& ostr)
{
	if (_encoding == ENCODING_URL)
		writeUrl(ostr);
	else
		writeMultipart(ostr);
}


void HTMLForm::readUrl(std::istream& istr)
{
	static const int eof = std::char_traits<char>::eof();

	int fields = 0;
	int ch = istr.get();
	bool isFirst = true;
	while (ch != eof)
	{
		if (_fieldLimit > 0 && fields == _fieldLimit)
			throw HTMLFormException("Too many form fields");
		std::string name;
		std::string value;
		while (ch != eof && ch != '=' && ch != '&')
		{
			if (ch == '+') ch = ' ';
			if (name.size() < MAX_NAME_LENGTH)
				name += (char) ch;
			else
				throw HTMLFormException("Field name too long");
			ch = istr.get();
		}
		if (ch == '=')
		{
			ch = istr.get();
			while (ch != eof && ch != '&')
			{
				if (ch == '+') ch = ' ';
				if (value.size() < _valueLengthLimit)
					value += (char) ch;
				else
					throw HTMLFormException("Field value too long");
				ch = istr.get();
			}
		}
		// remove UTF-8 byte order mark from first name, if present
		if (isFirst)
		{
			UTF8::removeBOM(name);
		}
		std::string decodedName;
		std::string decodedValue;
		URI::decode(name, decodedName);
		URI::decode(value, decodedValue);
		add(decodedName, decodedValue);
		++fields;
		if (ch == '&') ch = istr.get();
		isFirst = false;
	}
}


void HTMLForm::readMultipart(std::istream& istr, PartHandler& handler)
{
	static const int eof = std::char_traits<char>::eof();

	int fields = 0;
	MultipartReader reader(istr, _boundary);
	while (reader.hasNextPart())
	{
		if (_fieldLimit > 0 && fields == _fieldLimit)
			throw HTMLFormException("Too many form fields");
		MessageHeader header;
		reader.nextPart(header);
		std::string disp;
		NameValueCollection params;
		if (header.has("Content-Disposition"))
		{
			std::string cd = header.get("Content-Disposition");
			MessageHeader::splitParameters(cd, disp, params);
		}
		if (params.has("filename"))
		{
			handler.handlePart(header, reader.stream());
			// Ensure that the complete part has been read.
			while (reader.stream().good()) reader.stream().get();
		}
		else
		{
			std::string name = params["name"];
			std::string value;
			std::istream& partStream = reader.stream();
			int ch = partStream.get();
			while (ch != eof)
			{
				if (value.size() < _valueLengthLimit)
					value += (char) ch;
				else
					throw HTMLFormException("Field value too long");
				ch = partStream.get();
			}
			add(name, value);
		}
		++fields;
	}
}


void HTMLForm::writeUrl(std::ostream& ostr)
{
	for (auto it = begin(); it != end(); ++it)
	{
		if (it != begin()) ostr << "&";
		std::string name;
		URI::encode(it->first, "!?#/'\",;:$&()[]*+=@", name);
		std::string value;
		URI::encode(it->second, "!?#/'\",;:$&()[]*+=@", value);
		ostr << name << "=" << value;
	}
}


void HTMLForm::writeMultipart(std::ostream& ostr)
{
	HTMLFormCountingOutputStream* pCountingOutputStream(dynamic_cast<HTMLFormCountingOutputStream*>(&ostr));

	MultipartWriter writer(ostr, _boundary);
	for (auto it = begin(); it != end(); ++it)
	{
		MessageHeader header;
		std::string disp("form-data; name=\"");
		disp.append(it->first);
		disp.append("\"");
		header.set("Content-Disposition", disp);
		writer.nextPart(header);
		ostr << it->second;
	}
	for (const auto& part: _parts)
	{
		MessageHeader header(part.pSource->headers());
		std::string disp("form-data; name=\"");
		disp.append(part.name);
		disp.append("\"");
		std::string filename = part.pSource->filename();
		if (!filename.empty())
		{
			disp.append("; filename=\"");
			disp.append(filename);
			disp.append("\"");
		}
		header.set("Content-Disposition", disp);
		header.set("Content-Type", part.pSource->mediaType());
		writer.nextPart(header);
		if (pCountingOutputStream)
		{
			// count only, don't move stream position
			std::streamsize partlen = part.pSource->getContentLength();
			if (partlen != PartSource::UNKNOWN_CONTENT_LENGTH)
				pCountingOutputStream->addChars(partlen);
			else
				pCountingOutputStream->setValid(false);
		}
		else
		{
			StreamCopier::copyStream(part.pSource->stream(), ostr);
		}
	}
	writer.close();
	_boundary = writer.boundary();
}


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

	_fieldLimit = limit;
}


void HTMLForm::setValueLengthLimit(std::size_t limit)
{
	_valueLengthLimit = limit;
}


} } // namespace Poco::Net
