mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 10:22:50 +00:00
366 lines
11 KiB
C++
366 lines
11 KiB
C++
/*
|
|
Copyright (c) 2007 Volker Krause <vkrause@kde.org>
|
|
|
|
Based on KMail code by:
|
|
Copyright (c) 1996-1998 Stefan Taferner <taferner@kde.org>
|
|
|
|
This library is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Library General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or (at your
|
|
option) any later version.
|
|
|
|
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 Library General Public
|
|
License for more details.
|
|
|
|
You should have received a copy of the GNU Library General Public License
|
|
along with this library; see the file COPYING.LIB. If not, write to the
|
|
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
|
02110-1301, USA.
|
|
*/
|
|
|
|
#include "smtpjob.h"
|
|
#include "transport.h"
|
|
#include "mailtransport_defs.h"
|
|
#include "precommandjob.h"
|
|
#include "smtp/smtpsession.h"
|
|
|
|
#include <QBuffer>
|
|
#include <QHash>
|
|
#include <QPointer>
|
|
|
|
#include <KLocalizedString>
|
|
#include <KUrl>
|
|
#include <KDebug>
|
|
#include <KIO/Job>
|
|
#include <KIO/Scheduler>
|
|
#include <KPasswordDialog>
|
|
|
|
using namespace MailTransport;
|
|
|
|
class SlavePool
|
|
{
|
|
public:
|
|
SlavePool() : ref( 0 ) {}
|
|
int ref;
|
|
QHash<int, KIO::Slave*> slaves;
|
|
|
|
void removeSlave( KIO::Slave *slave, bool disconnect = false )
|
|
{
|
|
kDebug() << "Removing slave" << slave << "from pool";
|
|
const int slaveKey = slaves.key( slave );
|
|
if ( slaveKey > 0 ) {
|
|
slaves.remove( slaveKey );
|
|
if ( disconnect ) {
|
|
KIO::Scheduler::disconnectSlave( slave );
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
K_GLOBAL_STATIC( SlavePool, s_slavePool )
|
|
|
|
/**
|
|
* Private class that helps to provide binary compatibility between releases.
|
|
* @internal
|
|
*/
|
|
class SmtpJobPrivate
|
|
{
|
|
public:
|
|
SmtpJobPrivate( SmtpJob *parent ) : q( parent ) {}
|
|
|
|
void smtpSessionResult( SmtpSession *session )
|
|
{
|
|
#ifndef MAILTRANSPORT_INPROCESS_SMTP
|
|
Q_UNUSED( session );
|
|
#else
|
|
if ( !session->errorMessage().isEmpty() ) {
|
|
q->setError( KJob::UserDefinedError );
|
|
q->setErrorText( session->errorMessage() );
|
|
}
|
|
q->emitResult();
|
|
#endif
|
|
}
|
|
|
|
SmtpJob *q;
|
|
KIO::Slave *slave;
|
|
enum State {
|
|
Idle, Precommand, Smtp
|
|
} currentState;
|
|
bool finished;
|
|
};
|
|
|
|
SmtpJob::SmtpJob( Transport *transport, QObject *parent )
|
|
: TransportJob( transport, parent ), d( new SmtpJobPrivate( this ) )
|
|
{
|
|
d->currentState = SmtpJobPrivate::Idle;
|
|
d->slave = 0;
|
|
d->finished = false;
|
|
if ( !s_slavePool.isDestroyed() ) {
|
|
s_slavePool->ref++;
|
|
}
|
|
KIO::Scheduler::connect( SIGNAL(slaveError(KIO::Slave*,int,QString)),
|
|
this, SLOT(slaveError(KIO::Slave*,int,QString)) );
|
|
}
|
|
|
|
SmtpJob::~SmtpJob()
|
|
{
|
|
if ( !s_slavePool.isDestroyed() ) {
|
|
s_slavePool->ref--;
|
|
if ( s_slavePool->ref == 0 ) {
|
|
kDebug() << "clearing SMTP slave pool" << s_slavePool->slaves.count();
|
|
foreach ( KIO::Slave *slave, s_slavePool->slaves ) {
|
|
if ( slave ) {
|
|
KIO::Scheduler::disconnectSlave( slave );
|
|
}
|
|
}
|
|
s_slavePool->slaves.clear();
|
|
}
|
|
}
|
|
delete d;
|
|
}
|
|
|
|
void SmtpJob::doStart()
|
|
{
|
|
if ( s_slavePool.isDestroyed() ) {
|
|
return;
|
|
}
|
|
|
|
if ( ( !s_slavePool->slaves.isEmpty() &&
|
|
s_slavePool->slaves.contains( transport()->id() ) ) ||
|
|
transport()->precommand().isEmpty() ) {
|
|
d->currentState = SmtpJobPrivate::Smtp;
|
|
startSmtpJob();
|
|
} else {
|
|
d->currentState = SmtpJobPrivate::Precommand;
|
|
PrecommandJob *job = new PrecommandJob( transport()->precommand(), this );
|
|
addSubjob( job );
|
|
job->start();
|
|
}
|
|
}
|
|
|
|
void SmtpJob::startSmtpJob()
|
|
{
|
|
if ( s_slavePool.isDestroyed() ) {
|
|
return;
|
|
}
|
|
|
|
KUrl destination;
|
|
destination.setProtocol( ( transport()->encryption() == Transport::EnumEncryption::SSL ) ?
|
|
SMTPS_PROTOCOL : SMTP_PROTOCOL );
|
|
destination.setHost( transport()->host().trimmed() );
|
|
destination.setPort( transport()->port() );
|
|
|
|
destination.addQueryItem( QLatin1String( "headers" ), QLatin1String( "0" ) );
|
|
destination.addQueryItem( QLatin1String( "from" ), sender() );
|
|
|
|
foreach ( const QString &str, to() ) {
|
|
destination.addQueryItem( QLatin1String( "to" ), str );
|
|
}
|
|
foreach ( const QString &str, cc() ) {
|
|
destination.addQueryItem( QLatin1String( "cc" ), str );
|
|
}
|
|
foreach ( const QString &str, bcc() ) {
|
|
destination.addQueryItem( QLatin1String( "bcc" ), str );
|
|
}
|
|
|
|
if ( transport()->specifyHostname() ) {
|
|
destination.addQueryItem( QLatin1String( "hostname" ), transport()->localHostname() );
|
|
}
|
|
|
|
if ( transport()->requiresAuthentication() ) {
|
|
QString user = transport()->userName();
|
|
QString passwd = transport()->password();
|
|
if ( ( user.isEmpty() || passwd.isEmpty() ) &&
|
|
transport()->authenticationType() != Transport::EnumAuthenticationType::GSSAPI ) {
|
|
|
|
QPointer<KPasswordDialog> dlg =
|
|
new KPasswordDialog(
|
|
0,
|
|
KPasswordDialog::ShowUsernameLine |
|
|
KPasswordDialog::ShowKeepPassword );
|
|
dlg->setPrompt( i18n( "You need to supply a username and a password "
|
|
"to use this SMTP server." ) );
|
|
dlg->setKeepPassword( transport()->storePassword() );
|
|
dlg->addCommentLine( QString(), transport()->name() );
|
|
dlg->setUsername( user );
|
|
dlg->setPassword( passwd );
|
|
|
|
bool gotIt = false;
|
|
if ( dlg->exec() ) {
|
|
transport()->setUserName( dlg->username() );
|
|
transport()->setPassword( dlg->password() );
|
|
transport()->setStorePassword( dlg->keepPassword() );
|
|
transport()->writeConfig();
|
|
gotIt = true;
|
|
}
|
|
delete dlg;
|
|
|
|
if ( !gotIt ) {
|
|
setError( KilledJobError );
|
|
emitResult();
|
|
return;
|
|
}
|
|
}
|
|
destination.setUser( transport()->userName() );
|
|
destination.setPass( transport()->password() );
|
|
}
|
|
|
|
// dotstuffing is now done by the slave (see setting of metadata)
|
|
if ( !data().isEmpty() ) {
|
|
// allow +5% for subsequent LF->CRLF and dotstuffing (an average
|
|
// over 2G-lines gives an average line length of 42-43):
|
|
destination.addQueryItem( QLatin1String( "size" ),
|
|
QString::number( qRound( data().length() * 1.05 ) ) );
|
|
}
|
|
|
|
destination.setPath( QLatin1String( "/send" ) );
|
|
|
|
#ifndef MAILTRANSPORT_INPROCESS_SMTP
|
|
d->slave = s_slavePool->slaves.value( transport()->id() );
|
|
if ( !d->slave ) {
|
|
KIO::MetaData slaveConfig;
|
|
slaveConfig.insert( QLatin1String( "tls" ),
|
|
( transport()->encryption() == Transport::EnumEncryption::TLS ) ?
|
|
QLatin1String( "on" ) : QLatin1String( "off" ) );
|
|
if ( transport()->requiresAuthentication() ) {
|
|
slaveConfig.insert( QLatin1String( "sasl" ), transport()->authenticationTypeString() );
|
|
}
|
|
d->slave = KIO::Scheduler::getConnectedSlave( destination, slaveConfig );
|
|
kDebug() << "Created new SMTP slave" << d->slave;
|
|
s_slavePool->slaves.insert( transport()->id(), d->slave );
|
|
} else {
|
|
kDebug() << "Re-using existing slave" << d->slave;
|
|
}
|
|
|
|
KIO::TransferJob *job = KIO::put( destination, -1, KIO::HideProgressInfo );
|
|
if ( !d->slave || !job ) {
|
|
setError( UserDefinedError );
|
|
setErrorText( i18n( "Unable to create SMTP job." ) );
|
|
emitResult();
|
|
return;
|
|
}
|
|
|
|
job->addMetaData( QLatin1String( "lf2crlf+dotstuff" ), QLatin1String( "slave" ) );
|
|
connect( job, SIGNAL(dataReq(KIO::Job*,QByteArray&)),
|
|
SLOT(dataRequest(KIO::Job*,QByteArray&)) );
|
|
|
|
addSubjob( job );
|
|
KIO::Scheduler::assignJobToSlave( d->slave, job );
|
|
#else
|
|
SmtpSession *session = new SmtpSession( this );
|
|
connect( session, SIGNAL(result(MailTransport::SmtpSession*)),
|
|
SLOT(smtpSessionResult(MailTransport::SmtpSession*)) );
|
|
session->setUseTLS( transport()->encryption() == Transport::EnumEncryption::TLS );
|
|
if ( transport()->requiresAuthentication() ) {
|
|
session->setSaslMethod( transport()->authenticationTypeString() );
|
|
}
|
|
session->sendMessage( destination, buffer() );
|
|
#endif
|
|
|
|
setTotalAmount( KJob::Bytes, data().length() );
|
|
}
|
|
|
|
bool SmtpJob::doKill()
|
|
{
|
|
if ( s_slavePool.isDestroyed() ) {
|
|
return false;
|
|
}
|
|
|
|
if ( !hasSubjobs() ) {
|
|
return true;
|
|
}
|
|
if ( d->currentState == SmtpJobPrivate::Precommand ) {
|
|
return subjobs().first()->kill();
|
|
} else if ( d->currentState == SmtpJobPrivate::Smtp ) {
|
|
KIO::SimpleJob *job = static_cast<KIO::SimpleJob*>( subjobs().first() );
|
|
clearSubjobs();
|
|
KIO::Scheduler::cancelJob( job );
|
|
s_slavePool->removeSlave( d->slave );
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void SmtpJob::slotResult( KJob *job )
|
|
{
|
|
if ( s_slavePool.isDestroyed() ) {
|
|
return;
|
|
}
|
|
|
|
// The job has finished, so we don't care about any further errors. Set
|
|
// d->finished to true, so slaveError() knows about this and doesn't call
|
|
// emitResult() anymore.
|
|
// Sometimes, the SMTP slave emits more than one error
|
|
//
|
|
// The first error causes slotResult() to be called, but not slaveError(), since
|
|
// the scheduler doesn't emit errors for connected slaves.
|
|
//
|
|
// The second error then causes slaveError() to be called (as the slave is no
|
|
// longer connected), which does emitResult() a second time, which is invalid
|
|
// (and triggers an assert in KMail).
|
|
d->finished = true;
|
|
|
|
// Normally, calling TransportJob::slotResult() whould set the proper error code
|
|
// for error() via KComposite::slotResult(). However, we can't call that here,
|
|
// since that also emits the result signal.
|
|
// In KMail, when there are multiple mails in the outbox, KMail tries to send
|
|
// the next mail when it gets the result signal, which then would reuse the
|
|
// old broken slave from the slave pool if there was an error.
|
|
// To prevent that, we call TransportJob::slotResult() only after removing the
|
|
// slave from the pool and calculate the error code ourselves.
|
|
int errorCode = error();
|
|
if ( !errorCode ) {
|
|
errorCode = job->error();
|
|
}
|
|
|
|
if ( errorCode && d->currentState == SmtpJobPrivate::Smtp ) {
|
|
s_slavePool->removeSlave( d->slave, errorCode != KIO::ERR_SLAVE_DIED );
|
|
TransportJob::slotResult( job );
|
|
return;
|
|
}
|
|
|
|
TransportJob::slotResult( job );
|
|
if ( !error() && d->currentState == SmtpJobPrivate::Precommand ) {
|
|
d->currentState = SmtpJobPrivate::Smtp;
|
|
startSmtpJob();
|
|
return;
|
|
}
|
|
if ( !error() ) {
|
|
emitResult();
|
|
}
|
|
}
|
|
|
|
void SmtpJob::dataRequest( KIO::Job *job, QByteArray &data )
|
|
{
|
|
if ( s_slavePool.isDestroyed() ) {
|
|
return;
|
|
}
|
|
|
|
Q_UNUSED( job );
|
|
Q_ASSERT( job );
|
|
if ( buffer()->atEnd() ) {
|
|
data.clear();
|
|
} else {
|
|
Q_ASSERT( buffer()->isOpen() );
|
|
data = buffer()->read( 32 * 1024 );
|
|
}
|
|
setProcessedAmount( KJob::Bytes, buffer()->pos() );
|
|
}
|
|
|
|
void SmtpJob::slaveError( KIO::Slave *slave, int errorCode, const QString &errorMsg )
|
|
{
|
|
if ( s_slavePool.isDestroyed() ) {
|
|
return;
|
|
}
|
|
|
|
s_slavePool->removeSlave( slave, errorCode != KIO::ERR_SLAVE_DIED );
|
|
if ( d->slave == slave && !d->finished ) {
|
|
setError( errorCode );
|
|
setErrorText( KIO::buildErrorString( errorCode, errorMsg ) );
|
|
emitResult();
|
|
}
|
|
}
|
|
|
|
#include "moc_smtpjob.cpp"
|