//
// VMime library (http://www.vmime.org)
// Copyright (C) 2002-2013 Vincent Richard <vincent@vmime.org>
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation; either version 3 of
// the License, or (at your option) any later version.
//
// This program 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
// General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Linking this library statically or dynamically with other modules is making
// a combined work based on this library.  Thus, the terms and conditions of
// the GNU General Public License cover the whole combination.
//

#include "vmime/config.hpp"


#if VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SENDMAIL


#include "vmime/net/sendmail/sendmailTransport.hpp"

#include "vmime/exception.hpp"
#include "vmime/platform.hpp"
#include "vmime/message.hpp"
#include "vmime/mailboxList.hpp"

#include "vmime/utility/filteredStream.hpp"
#include "vmime/utility/childProcess.hpp"

#include "vmime/utility/streamUtils.hpp"

#include "vmime/net/defaultConnectionInfos.hpp"

#include "vmime/config.hpp"


// Helpers for service properties
#define GET_PROPERTY(type, prop) \
	(getInfos().getPropertyValue <type>(getSession(), \
		dynamic_cast <const sendmailServiceInfos&>(getInfos()).getProperties().prop))
#define HAS_PROPERTY(prop) \
	(getInfos().hasProperty(getSession(), \
		dynamic_cast <const sendmailServiceInfos&>(getInfos()).getProperties().prop))


namespace vmime {
namespace net {
namespace sendmail {


sendmailTransport::sendmailTransport(shared_ptr <session> sess, shared_ptr <security::authenticator> auth)
	: transport(sess, getInfosInstance(), auth), m_connected(false)
{
}


sendmailTransport::~sendmailTransport()
{
	try
	{
		if (isConnected())
			disconnect();
	}
	catch (...)
	{
		// Don't throw in destructor
	}
}


const string sendmailTransport::getProtocolName() const
{
	return "sendmail";
}


void sendmailTransport::connect()
{
	if (isConnected())
		throw exceptions::already_connected();

	// Use the specified path for 'sendmail' or a default one if no path is specified
	m_sendmailPath = GET_PROPERTY(string, PROPERTY_BINPATH);

	m_connected = true;
}


bool sendmailTransport::isConnected() const
{
	return (m_connected);
}


bool sendmailTransport::isSecuredConnection() const
{
	return false;
}


shared_ptr <connectionInfos> sendmailTransport::getConnectionInfos() const
{
	return make_shared <defaultConnectionInfos>("localhost", static_cast <port_t>(0));
}


void sendmailTransport::disconnect()
{
	if (!isConnected())
		throw exceptions::not_connected();

	internalDisconnect();
}


void sendmailTransport::internalDisconnect()
{
	m_connected = false;
}


void sendmailTransport::noop()
{
	// Do nothing
}


void sendmailTransport::send
	(const mailbox& expeditor, const mailboxList& recipients,
	 utility::inputStream& is, const size_t size,
	 utility::progressListener* progress, const mailbox& sender)
{
	// If no recipient/expeditor was found, throw an exception
	if (recipients.isEmpty())
		throw exceptions::no_recipient();
	else if (expeditor.isEmpty())
		throw exceptions::no_expeditor();

	// Construct the argument list
	std::vector <string> args;

	args.push_back("-i");
	args.push_back("-f");

	if (!sender.isEmpty())
		args.push_back(expeditor.getEmail().generate());
	else
		args.push_back(sender.getEmail().generate());

	args.push_back("--");

	for (size_t i = 0 ; i < recipients.getMailboxCount() ; ++i)
		args.push_back(recipients.getMailboxAt(i)->getEmail().generate());

	// Call sendmail
	try
	{
		internalSend(args, is, size, progress);
	}
	catch (vmime::exception& e)
	{
		throw exceptions::command_error("SEND", "", "sendmail failed", e);
	}
}


void sendmailTransport::internalSend
	(const std::vector <string> args, utility::inputStream& is,
	 const size_t size, utility::progressListener* progress)
{
	const utility::file::path path = vmime::platform::getHandler()->
		getFileSystemFactory()->stringToPath(m_sendmailPath);

	shared_ptr <utility::childProcess> proc =
		vmime::platform::getHandler()->
			getChildProcessFactory()->create(path);

	proc->start(args, utility::childProcess::FLAG_REDIRECT_STDIN);

	// Copy message data from input stream to output pipe
	utility::outputStream& os = *(proc->getStdIn());

	// Workaround for lame sendmail implementations that
	// can't handle CRLF eoln sequences: we transform CRLF
	// sequences into LF characters.
	utility::CRLFToLFFilteredOutputStream fos(os);

	// TODO: remove 'Bcc:' field from message header

	utility::bufferedStreamCopy(is, fos, size, progress);

	// Wait for sendmail to exit
	proc->waitForFinish();
}


// Service infos

sendmailServiceInfos sendmailTransport::sm_infos;


const serviceInfos& sendmailTransport::getInfosInstance()
{
	return sm_infos;
}


const serviceInfos& sendmailTransport::getInfos() const
{
	return sm_infos;
}


} // sendmail
} // net
} // vmime


#endif // VMIME_HAVE_MESSAGING_FEATURES && VMIME_HAVE_MESSAGING_PROTO_SENDMAIL

