//
// HTMLForm.cpp
//
// $Id: //poco/1.4/Net/src/HTMLForm.cpp#4 $
//
// Library: Net
// Package: HTML
// Module:  HTMLForm
//
// Copyright (c) 2005-2006, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
// 
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
//


#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/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 <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";


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

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


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


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


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

	
HTMLForm::~HTMLForm()
{
	for (PartVec::iterator it = _parts.begin(); it != _parts.end(); ++it)
	{
		delete it->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();
	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);
		}
	}
	else
	{
		URI uri(request.getURI());
		std::istringstream istr(uri.getRawQuery());
		readUrl(istr);
	}
}


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)
{
	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=\"");
			ct.append(_boundary);
			ct.append("\"");
			request.setContentType(ct);
		}
		if (request.getVersion() == HTTPMessage::HTTP_1_0)
		{
			request.setKeepAlive(false);
			request.setChunkedTransferEncoding(false);
		}
		else if (_encoding != ENCODING_URL)
		{
			request.setChunkedTransferEncoding(true);
		}
	}
	else
	{
		std::string uri = request.getURI();
		std::ostringstream ostr;
		writeUrl(ostr);
		uri.append("?");
		uri.append(ostr.str());
		request.setURI(uri);
	}
}


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();
	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 = ' ';
			name += (char) ch;
			ch = istr.get();
		}
		if (ch == '=')
		{
			ch = istr.get();
			while (ch != eof && ch != '&')
			{
				if (ch == '+') ch = ' ';
				value += (char) ch;
				ch = istr.get();
			}
		}
		std::string decodedName;
		std::string decodedValue;
		URI::decode(name, decodedName);
		URI::decode(value, decodedValue);
		add(decodedName, decodedValue);
		++fields;
		if (ch == '&') ch = istr.get();
	}
}


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& istr = reader.stream();
			int ch = istr.get();
			while (ch != eof)
			{
				value += (char) ch;
				ch = istr.get();
			}
			add(name, value);
		}
		++fields;
	}
}


void HTMLForm::writeUrl(std::ostream& ostr)
{
	for (NameValueCollection::ConstIterator 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)
{
	MultipartWriter writer(ostr, _boundary);
	for (NameValueCollection::ConstIterator 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 (PartVec::iterator ita = _parts.begin(); ita != _parts.end(); ++ita)
	{
		MessageHeader header(ita->pSource->headers());
		std::string disp("form-data; name=\"");
		disp.append(ita->name);
		disp.append("\"");
		std::string filename = ita->pSource->filename();
		if (!filename.empty())
		{
			disp.append("; filename=\"");
			disp.append(filename);
			disp.append("\"");
		}
		header.set("Content-Disposition", disp);
		header.set("Content-Type", ita->pSource->mediaType());
		writer.nextPart(header);
		StreamCopier::copyStream(ita->pSource->stream(), ostr);
	}
	writer.close();
	_boundary = writer.boundary();
}


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


} } // namespace Poco::Net
