Commit 784a0792 authored by David Faure's avatar David Faure
Browse files

Merge kio_pop3 into the pop3 resource

* POPSession instanciates POP3Protocol directly
* Port POP3Protocol from TCPSlaveBase methods to using QSslSocket directly
* Replace KIO metadata with direct poking into the Settings class
* This means jobs are synchronous, which is fine in an akonadi resource (AFAIK)
* Use the Result class from kio_ftp for returning success+errcode+errstring
* Remove support for prompting for user/pass, this is supposed to be
  done in the settings dialog already. Might be convenient to
  reintroduce this somehow though, in case of a typo in the password.

The unittest passes.
parent ed39cfac
add_subdirectory(akonadi)
add_subdirectory(pop3)
########### next target ###############
add_library(kio_pop3 MODULE)
target_sources(kio_pop3 PRIVATE pop3.cpp )
ecm_qt_declare_logging_category(kio_pop3 HEADER pop3_debug.h IDENTIFIER POP3_LOG CATEGORY_NAME org.kde.pim.pop3
DESCRIPTION "kioslave (kdepim-runtime)"
OLD_CATEGORY_NAMES log_pop3resource
EXPORT KDEPIMRUNTIME
)
target_link_libraries(kio_pop3 KF5::KIOCore KF5::I18n Qt::Network Sasl2::Sasl2)
set_target_properties(kio_pop3 PROPERTIES OUTPUT_NAME "pop3")
install(TARGETS kio_pop3 DESTINATION ${KDE_INSTALL_PLUGINDIR}/kf5/kio/ )
#! /usr/bin/env bash
$XGETTEXT *.cpp -o $podir/kio_pop3.pot
This diff is collapsed.
/*
* SPDX-FileCopyrightText: 1999, 2000 Alex Zepeda <zipzippy@sonic.net>
* All rights reserved.
*
* SPDX-License-Identifier: BSD-2-Clause
*
*/
#pragma once
#include <KIO/TCPSlaveBase>
#include <QUrl>
#include <stdio.h>
#include <sys/types.h>
#define MAX_PACKET_LEN 4096
class POP3Protocol : public KIO::TCPSlaveBase
{
public:
POP3Protocol(const QByteArray &pool, const QByteArray &app, bool SSL);
~POP3Protocol() override;
void setHost(const QString &host, quint16 port, const QString &user, const QString &pass) override;
void get(const QUrl &url) override;
void stat(const QUrl &url) override;
void del(const QUrl &url, bool isfile) override;
void listDir(const QUrl &url) override;
protected:
ssize_t myRead(void *data, ssize_t len);
ssize_t myReadLine(char *data, ssize_t len);
/**
* This returns the size of a message as a long integer.
* This is useful as an internal member, because the "other"
* getSize command will emit a signal, which would be harder
* to trap when doing something like listing a directory.
*/
size_t realGetSize(unsigned int msg_num);
/**
* Send a command to the server. Using this function, getResponse
* has to be called separately.
*/
bool sendCommand(const QByteArray &cmd);
enum Resp {
Err,
Ok,
Cont,
Invalid,
};
/**
* Send a command to the server, and wait for the one-line-status
* reply via getResponse. Similar rules apply. If no buffer is
* specified, no data is passed back.
*/
Resp command(const QByteArray &buf, char *r_buf = nullptr, unsigned int r_len = 0);
/**
* All POP3 commands will generate a response. Each response will
* either be prefixed with a "+OK " or a "-ERR ". The getResponse
* function will wait until there's data to be read, and then read in
* the first line (the response), and copy the response sans +OK/-ERR
* into a buffer (up to len bytes) if one was passed to it.
*/
Resp getResponse(char *buf, unsigned int len);
/** Call int pop3_open() and report an error, if if fails */
void openConnection() override;
/**
* Attempt to properly shut down the POP3 connection by sending
* "QUIT\r\n" before closing the socket.
*/
void closeConnection() override;
/**
* Attempt to initiate a POP3 connection via a TCP socket. If no port
* is passed, port 110 is assumed, if no user || password is
* specified, the user is prompted for them.
*/
bool pop3_open();
enum class AuthResult {
Success,
Failure, // try fallbacks
Abort
};
/**
* Authenticate via APOP
*/
AuthResult loginAPOP(const char *challenge, KIO::AuthInfo &ai);
bool saslInteract(void *in, KIO::AuthInfo &ai);
/**
* Authenticate via SASL
*/
AuthResult loginSASL(KIO::AuthInfo &ai);
/**
* Authenticate via traditional USER/PASS
*/
AuthResult loginPASS(KIO::AuthInfo &ai);
// int m_cmd;
unsigned short int m_iOldPort;
unsigned short int m_iPort;
QString m_sOldServer, m_sOldPass, m_sOldUser;
QString m_sServer, m_sPass, m_sUser;
bool m_try_apop, m_try_sasl, opened, supports_apop;
QString m_sError;
char readBuffer[MAX_PACKET_LEN];
ssize_t readBufferLen;
};
{
"KDE-KIO-Protocols": {
"pop3": {
"Capabilities": [
"SASL"
],
"Icon": "mail-folder-inbox",
"X-DocPath": "kioslave5/pop3/index.html",
"deleting": true,
"exec": "kf5/kio/pop3",
"input": "none",
"linking": false,
"listing": [
"Name",
"Type",
"Size"
],
"makedir": false,
"moving": false,
"output": "filesystem",
"protocol": "pop3",
"reading": true,
"source": true,
"writing": false
},
"pop3s": {
"Capabilities": [
"SASL"
],
"Icon": "mail-folder-inbox",
"X-DocPath": "kioslave5/pop3/index.html",
"deleting": true,
"exec": "kf5/kio/pop3",
"input": "none",
"linking": false,
"listing": [
"Name",
"Type",
"Size"
],
"makedir": false,
"moving": false,
"output": "filesystem",
"protocol": "pop3s",
"reading": true,
"source": true,
"writing": false
}
}
}
......@@ -10,6 +10,10 @@ ecm_qt_declare_logging_category(pop3_common_SRCS HEADER pop3resource_debug.h IDE
OLD_CATEGORY_NAMES log_pop3resource
EXPORT KDEPIMRUNTIME
)
ecm_qt_declare_logging_category(pop3_common_SRCS HEADER pop3protocol_debug.h IDENTIFIER POP3_LOG CATEGORY_NAME org.kde.pim.pop3protocol
DESCRIPTION "pop3 protocol (kdepim-runtime)"
EXPORT KDEPIMRUNTIME
)
kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/settings.kcfg org.kde.Akonadi.POP3.Settings)
qt_add_dbus_adaptor(pop3_common_SRCS
......@@ -20,6 +24,7 @@ qt_add_dbus_adaptor(pop3_common_SRCS
set( pop3resource_SRCS
pop3resource.cpp
pop3protocol.cpp
jobs.cpp
${pop3_common_SRCS}
)
......@@ -41,15 +46,16 @@ endif ()
target_link_libraries( akonadi_pop3_resource
KF5::AkonadiCore
KF5::AkonadiMime
KF5::KIOCore
KF5::Mime
KF5::MailTransport
KF5::AkonadiAgentBase
KF5::I18n
KF5::Notifications
Qt::DBus
Qt::Network # QNetworkProxy
qt5keychain
KF5::WidgetsAddons
Sasl2::Sasl2
)
if (COMPILE_WITH_UNITY_CMAKE_SUPPORT)
set_target_properties(akonadi_pop3_resource PROPERTIES UNITY_BUILD ON)
......
......@@ -111,8 +111,7 @@ QByteArray FakeServer::parseResponse(const QByteArray &expectedData, const QByte
{
// Only called from dataAvailable, which is already thread-safe
QByteArray result;
result = parseDeleteMark(expectedData, dataReceived);
const QByteArray result = parseDeleteMark(expectedData, dataReceived);
if (result != expectedData) {
return result;
} else {
......@@ -120,15 +119,11 @@ QByteArray FakeServer::parseResponse(const QByteArray &expectedData, const QByte
}
}
/*
// Used only for the debug output in dataAvailable()
static QByteArray removeCRLF( const QByteArray &ba )
static QByteArray removeCRLF(const QByteArray &ba)
{
QByteArray returnArray = ba;
returnArray.replace( "\r\n", QByteArray() );
return returnArray;
return returnArray.replace(QByteArrayLiteral("\r\n"), QByteArray());
}
*/
void FakeServer::dataAvailable()
{
......@@ -140,11 +135,13 @@ void FakeServer::dataAvailable()
Q_ASSERT(!mWriteData.isEmpty());
const QByteArray data = mTcpServerConnection->readAll();
// qDebug() << "Got data:" << removeCRLF( data );
const QByteArray expected(mReadData.takeFirst());
// qDebug() << "Expected data:" << removeCRLF( expected );
const QByteArray reallyExpected = parseResponse(expected, data);
// qDebug() << "Really expected:" << removeCRLF( reallyExpected );
if (data != reallyExpected) {
qDebug() << "Got data:" << removeCRLF(data);
qDebug() << "Expected data:" << removeCRLF(expected);
qDebug() << "Really expected:" << removeCRLF(reallyExpected);
}
Q_ASSERT(data == reallyExpected);
......
......@@ -10,19 +10,14 @@
#include <MailTransport/Transport>
#include "pop3resource_debug.h"
#include <KIO/Job>
#include <KIO/Scheduler>
#include <KIO/Slave>
#include <KIO/TransferJob>
#include <KLocalizedString>
#include "pop3protocol.h"
POPSession::POPSession(Settings &settings, const QString &password)
: mPassword(password)
, mSettings(settings)
{
// clang-format off
KIO::Scheduler::connect(SIGNAL(slaveError(KIO::Slave*,int,QString)), this, SLOT(slotSlaveError(KIO::Slave*,int,QString)));
// clang-format on
}
POPSession::~POPSession()
......@@ -30,120 +25,25 @@ POPSession::~POPSession()
closeSession();
}
void POPSession::slotSlaveError(KIO::Slave *slave, int errorCode, const QString &errorMessage)
{
Q_UNUSED(slave)
qCWarning(POP3RESOURCE_LOG) << "Got a slave error:" << errorMessage;
if (slave != mSlave) {
return;
}
if (errorCode == KIO::ERR_SLAVE_DIED) {
mSlave = nullptr;
}
// Explicitly disconnect the slave if the connection went down
if (errorCode == KIO::ERR_CONNECTION_BROKEN && mSlave) {
KIO::Scheduler::disconnectSlave(mSlave);
mSlave = nullptr;
}
if (!mCurrentJob) {
Q_EMIT slaveError(errorCode, errorMessage);
} else {
// Let the job deal with the problem
auto slaveBaseJob = qobject_cast<SlaveBaseJob *>(mCurrentJob);
Q_ASSERT(slaveBaseJob);
slaveBaseJob->slaveError(errorCode, errorMessage);
}
}
void POPSession::setCurrentJob(BaseJob *job)
{
mCurrentJob = job;
}
KIO::MetaData POPSession::slaveConfig() const
{
KIO::MetaData m;
m.insert(QStringLiteral("progress"), QStringLiteral("off"));
m.insert(QStringLiteral("tls"), mSettings.useTLS() ? QStringLiteral("on") : QStringLiteral("off"));
m.insert(QStringLiteral("pipelining"), (mSettings.pipelining()) ? QStringLiteral("on") : QStringLiteral("off"));
m.insert(QStringLiteral("useProxy"), mSettings.useProxy() ? QStringLiteral("on") : QStringLiteral("off"));
int type = mSettings.authenticationMethod();
switch (type) {
case MailTransport::Transport::EnumAuthenticationType::PLAIN:
case MailTransport::Transport::EnumAuthenticationType::LOGIN:
case MailTransport::Transport::EnumAuthenticationType::CRAM_MD5:
case MailTransport::Transport::EnumAuthenticationType::DIGEST_MD5:
case MailTransport::Transport::EnumAuthenticationType::NTLM:
case MailTransport::Transport::EnumAuthenticationType::GSSAPI:
m.insert(QStringLiteral("auth"), QStringLiteral("SASL"));
m.insert(QStringLiteral("sasl"), authenticationToString(type));
break;
case MailTransport::Transport::EnumAuthenticationType::CLEAR:
m.insert(QStringLiteral("auth"), QStringLiteral("USER"));
break;
default:
m.insert(QStringLiteral("auth"), authenticationToString(type));
break;
}
return m;
}
QString POPSession::authenticationToString(int type) const
{
switch (type) {
case MailTransport::Transport::EnumAuthenticationType::LOGIN:
return QStringLiteral("LOGIN");
case MailTransport::Transport::EnumAuthenticationType::PLAIN:
return QStringLiteral("PLAIN");
case MailTransport::Transport::EnumAuthenticationType::CRAM_MD5:
return QStringLiteral("CRAM-MD5");
case MailTransport::Transport::EnumAuthenticationType::DIGEST_MD5:
return QStringLiteral("DIGEST-MD5");
case MailTransport::Transport::EnumAuthenticationType::GSSAPI:
return QStringLiteral("GSSAPI");
case MailTransport::Transport::EnumAuthenticationType::NTLM:
return QStringLiteral("NTLM");
case MailTransport::Transport::EnumAuthenticationType::CLEAR:
return QStringLiteral("USER");
case MailTransport::Transport::EnumAuthenticationType::APOP:
return QStringLiteral("APOP");
default:
break;
}
return QString();
}
QUrl POPSession::getUrl() const
Result POPSession::createProtocol()
{
QUrl url;
if (mSettings.useSSL()) {
url.setScheme(QStringLiteral("pop3s"));
} else {
url.setScheme(QStringLiteral("pop3"));
}
url.setUserName(mSettings.login());
url.setPassword(mPassword);
url.setHost(mSettings.host());
url.setPort(mSettings.port());
return url;
mProtocol.reset(new POP3Protocol(mSettings, mPassword));
return mProtocol->openConnection();
}
bool POPSession::connectSlave()
POP3Protocol *POPSession::getProtocol() const
{
mSlave = KIO::Scheduler::getConnectedSlave(getUrl(), slaveConfig());
return mSlave != nullptr;
return mProtocol.get();
}
void POPSession::abortCurrentJob()
{
// This is never the case anymore, since all jobs are sync
if (mCurrentJob) {
mCurrentJob->kill(KJob::Quietly);
mCurrentJob = nullptr;
......@@ -152,17 +52,10 @@ void POPSession::abortCurrentJob()
void POPSession::closeSession()
{
if (mSlave) {
KIO::Scheduler::disconnectSlave(mSlave);
}
mProtocol->closeConnection();
}
KIO::Slave *POPSession::getSlave() const
{
return mSlave;
}
static QByteArray cleanupListRespone(const QByteArray &response)
static QByteArray cleanupListResponse(const QByteArray &response)
{
QByteArray ret = response.simplified(); // Workaround for Maillennium POP3/UNIBOX
......@@ -197,118 +90,49 @@ BaseJob::~BaseJob()
// mPOPSession->setCurrentJob( 0 );
}
SlaveBaseJob::SlaveBaseJob(POPSession *POPSession)
: BaseJob(POPSession)
{
}
SlaveBaseJob::~SlaveBaseJob()
void BaseJob::startJob(const QString &path)
{
}
connect(mPOPSession->getProtocol(), &POP3Protocol::data, this, &BaseJob::slotData);
const Result result = mPOPSession->getProtocol()->get(path);
bool SlaveBaseJob::doKill()
{
if (mJob) {
return mJob->kill();
} else {
return KJob::doKill();
}
}
void SlaveBaseJob::slotSlaveResult(KJob *job)
{
// get() is sync, so we're done
disconnect(mPOPSession->getProtocol(), &POP3Protocol::data, this, &BaseJob::slotData);
mPOPSession->setCurrentJob(nullptr);
if (job->error()) {
setError(job->error());
setErrorText(job->errorText());
if (!result.success) {
setError(result.error);
setErrorText(result.errorString);
}
emitResult();
mJob = nullptr;
}
void SlaveBaseJob::slotSlaveData(KIO::Job *job, const QByteArray &data)
void BaseJob::slotData(const QByteArray &data)
{
Q_UNUSED(job)
qCWarning(POP3RESOURCE_LOG) << "Got unexpected slave data:" << data.data();
}
void SlaveBaseJob::slaveError(int errorCode, const QString &errorMessage)
{
// The slave experienced some problem while running our job.
// Just treat this as an error.
// Derived jobs can do something more sophisticated here
setError(errorCode);
setErrorText(errorMessage);
emitResult();
mJob = nullptr;
}
void SlaveBaseJob::connectJob()
{
connect(mJob, &KIO::TransferJob::data, this, &SlaveBaseJob::slotSlaveData);
connect(mJob, &KIO::TransferJob::result, this, &SlaveBaseJob::slotSlaveResult);
}
void SlaveBaseJob::startJob(const QString &path)
{
QUrl url = mPOPSession->getUrl();
url.setPath(path);
mJob = KIO::get(url, KIO::NoReload, KIO::HideProgressInfo);
KIO::Scheduler::assignJobToSlave(mPOPSession->getSlave(), mJob);
connectJob();
}
QString SlaveBaseJob::errorString() const
{
if (mJob) {
return mJob->errorString();
} else {
return KJob::errorString();
}
qCWarning(POP3RESOURCE_LOG) << "Got unexpected job data:" << data.data();
}
LoginJob::LoginJob(POPSession *popSession)
: SlaveBaseJob(popSession)
: BaseJob(popSession)
{
}
void LoginJob::start()
{
// This will create a connected slave, which means it will also try to login.
KIO::Scheduler::connect(SIGNAL(slaveConnected(KIO::Slave *)), this, SLOT(slaveConnected(KIO::Slave *)));
if (!mPOPSession->connectSlave()) {
if (!POP3Protocol::initSASL()) {
setError(KJob::UserDefinedError);
setErrorText(i18n("Unable to create POP3 slave, aborting mail check."));
setErrorText(i18n("Unable to initialize SASL, aborting mail check."));
emitResult();
}
}
void LoginJob::slaveConnected(KIO::Slave *slave)
{
if (slave != mPOPSession->getSlave()) {
// Odd, not our slave...
return;
const Result result = mPOPSession->createProtocol();
if (!result.success) {
setError(result.error);
setErrorText(result.errorString);
}
// Yeah it connected, so login was successful!
emitResult();
}
void LoginJob::slaveError(int errorCode, const QString &errorMessage)
{
setError(errorCode);
setErrorText(errorMessage);
mErrorString = KIO::buildErrorString(errorCode, errorMessage);
emitResult();
}
QString LoginJob::errorString() const
{
return mErrorString;
}
ListJob::ListJob(POPSession *popSession)
: SlaveBaseJob(popSession)
: BaseJob(popSession)
{
}
......@@ -317,16 +141,11 @@ void ListJob::start()
startJob(QStringLiteral("/index"));
}
void ListJob::slotSlaveData(KIO::Job *job, const QByteArray &data)
void ListJob::slotData(const QByteArray &data)
{
Q_UNUSED(job)
// Silly slave, why are you sending us empty data?
if (data.isEmpty()) {
return;
}
Q_ASSERT(!data.isEmpty());
QByteArray cleanData = cleanupListRespone(data);
const QByteArray cleanData = cleanupListResponse(data);
const int space = cleanData.indexOf(' ');
if (space > 0) {
......@@ -357,7 +176,7 @@ QMap<int, int> ListJob::idList() const
}
UIDListJob::UIDListJob(POPSession *popSession)
: SlaveBaseJob(popSession)
: BaseJob(popSession)
{
}
......@@ -366,16 +185,11 @@ void UIDListJob::start()
startJob(QStringLiteral("/uidl"));
}
void UIDListJob::slotSlaveData(KIO::Job *job, const QByteArray &data)
void UIDListJob::slotData(const QByteArray &data)