29#include "kimap_debug.h"
33#include "sessionlogger_p.h"
35#include "imapstreamparser.h"
38Q_DECLARE_METATYPE(QSslSocket::SslMode)
39static const int _kimap_sslVersionId = qRegisterMetaType<QSsl::SslProtocol>();
41using namespace KIMAP2;
43Session::Session(
const QString &hostName, quint16 port,
QObject *parent)
44 :
QObject(parent), d(new SessionPrivate(this))
46 if (!qEnvironmentVariableIsEmpty(
"KIMAP2_LOGFILE")) {
47 d->logger.reset(
new SessionLogger);
48 qCInfo(KIMAP2_LOG) <<
"Logging traffic to: " <<
QLatin1String(qgetenv(
"KIMAP2_LOGFILE"));
50 if (qEnvironmentVariableIsSet(
"KIMAP2_TRAFFIC")) {
51 d->dumpTraffic =
true;
52 qCInfo(KIMAP2_LOG) <<
"Dumping traffic.";
54 if (qEnvironmentVariableIsSet(
"KIMAP2_TIMING")) {
56 qCInfo(KIMAP2_LOG) <<
"Tracking timings.";
60 d->jobRunning =
false;
61 d->hostName = hostName;
66 connect(d->socket.data(), &QSslSocket::connected,
67 d, &SessionPrivate::socketConnected);
69 d, &SessionPrivate::handleSslErrors);
71 d, &SessionPrivate::socketError);
74 d, &SessionPrivate::socketActivity);
75 connect(d->socket.data(), &QSslSocket::encryptedBytesWritten,
76 d, &SessionPrivate::socketActivity);
78 d, &SessionPrivate::socketActivity);
80 qCDebug(KIMAP2_LOG) <<
"Socket state changed: " << state;
82 if (state == QAbstractSocket::UnconnectedState) {
83 d->socketDisconnected();
86 d->hostLookupInProgress =
true;
88 d->hostLookupInProgress =
false;
92 d->socketTimer.setSingleShot(
true);
94 d, &SessionPrivate::onSocketTimeout);
96 d->socketProgressTimer.setSingleShot(
false);
98 d, &SessionPrivate::onSocketProgressTimeout);
100 d->startSocketTimer();
101 qCDebug(KIMAP2_LOG) <<
"Connecting to: " << hostName << port;
102 d->socket->connectToHost(hostName, port);
112QString Session::hostName()
const
117quint16 Session::port()
const
122Session::State Session::state()
const
127bool Session::isConnected()
const
129 return (d->state == Authenticated || d->state == Selected);
132QString Session::userName()
const
142int Session::jobQueueSize()
const
144 return d->queue.
size() + (d->jobRunning ? 1 : 0);
154 d->socket->ignoreSslErrors(errors);
157void Session::setTimeout(
int timeout)
159 d->setSocketTimeout(timeout * 1000);
162int Session::timeout()
const
164 return d->socketTimeout() / 1000;
167QString Session::selectedMailBox()
const
173SessionPrivate::SessionPrivate(
Session *session)
177 hostLookupInProgress(false),
179 currentJob(Q_NULLPTR),
181 socketTimerInterval(30000),
182 socketProgressInterval(3000),
183 socket(new QSslSocket),
185 accumulatedWaitTime(0),
186 accumulatedProcessingTime(0),
192 stream->onResponseReceived([
this](
const Message &message) {
193 responseReceived(message);
197SessionPrivate::~SessionPrivate()
203 emit q->sslErrors(errors);
206void SessionPrivate::addJob(Job *job)
209 emit q->jobQueueSizeChanged(q->jobQueueSize());
216void SessionPrivate::startNext()
221void SessionPrivate::doStartNext()
226 || socket->state() == QSslSocket::ConnectingState
227 || socket->state() == QSslSocket::HostLookupState) {
231 currentJob = queue.dequeue();
234 if (socket->state() == QSslSocket::UnconnectedState) {
235 qCDebug(KIMAP2_LOG) <<
"Cancelling job due to lack of connection: " << currentJob->metaObject()->className();
236 currentJob->connectionLost();
243 restartSocketTimer();
245 currentJob->doStart();
248void SessionPrivate::jobDone(
KJob *job)
251 Q_ASSERT(job == currentJob);
257 currentJob = Q_NULLPTR;
258 emit q->jobQueueSizeChanged(q->jobQueueSize());
262void SessionPrivate::jobDestroyed(
QObject *job)
264 queue.removeAll(
static_cast<KIMAP2::Job *
>(job));
265 if (currentJob == job) {
266 currentJob = Q_NULLPTR;
270void SessionPrivate::responseReceived(
const Message &response)
275 if (logger && q->isConnected()) {
276 logger->dataReceived(response.toString());
282 if (response.content.size() >= 1) {
283 tag = response.content[0].toString();
286 if (response.content.size() >= 2) {
287 code = response.content[1].toString();
293 Message simplified = response;
294 if (simplified.content.size() >= 2) {
295 simplified.content.removeFirst();
296 simplified.content.removeFirst();
298 qCDebug(KIMAP2_LOG) <<
"Received BYE: " << simplified.toString();
303 case Session::Disconnected:
306 Message simplified = response;
307 simplified.content.removeFirst();
308 simplified.content.removeFirst();
309 greeting = simplified.toString().trimmed();
310 setState(Session::NotAuthenticated);
311 }
else if (code ==
"PREAUTH") {
312 Message simplified = response;
313 simplified.content.removeFirst();
314 simplified.content.removeFirst();
315 greeting = simplified.toString().trimmed();
316 setState(Session::Authenticated);
322 case Session::NotAuthenticated:
323 if (code ==
"OK" && tag == authTag) {
324 setState(Session::Authenticated);
327 case Session::Authenticated:
328 if (code ==
"OK" && tag == selectTag) {
329 setState(Session::Selected);
330 currentMailBox = upcomingMailBox;
333 case Session::Selected:
334 if ((code ==
"OK" && tag == closeTag) ||
335 (code !=
"OK" && tag == selectTag)) {
336 setState(Session::Authenticated);
338 }
else if (code ==
"OK" && tag == selectTag) {
339 currentMailBox = upcomingMailBox;
344 if (tag == authTag) {
347 if (tag == selectTag) {
350 if (tag == closeTag) {
356 restartSocketTimer();
357 currentJob->handleResponse(response);
359 qCWarning(KIMAP2_LOG) <<
"A message was received from the server with no job to handle it:"
360 << response.toString()
361 <<
'(' + response.toString().toHex() +
')';
365void SessionPrivate::setState(Session::State s)
368 Session::State oldState = state;
370 emit q->stateChanged(state, oldState);
380 payload +=
' ' + args;
385 if (command ==
"LOGIN" || command ==
"AUTHENTICATE") {
387 }
else if (command ==
"SELECT" || command ==
"EXAMINE") {
389 upcomingMailBox = args;
390 upcomingMailBox.
remove(0, 1);
391 upcomingMailBox = upcomingMailBox.
left(upcomingMailBox.indexOf(
'\"'));
393 }
else if (command ==
"CLOSE") {
399void SessionPrivate::sendData(
const QByteArray &data)
401 restartSocketTimer();
404 qCInfo(KIMAP2_LOG) <<
"C: " << data;
406 if (logger && q->isConnected()) {
410 dataQueue.enqueue(data +
"\r\n");
414void SessionPrivate::socketConnected()
416 qCInfo(KIMAP2_LOG) <<
"Socket connected.";
422void SessionPrivate::socketDisconnected()
424 qCInfo(KIMAP2_LOG) <<
"Socket disconnected.";
427 if (logger && q->isConnected()) {
428 logger->disconnectionOccured();
431 if (state != Session::Disconnected) {
432 setState(Session::Disconnected);
435 if (hostLookupInProgress) {
437 hostLookupInProgress =
false;
439 emit q->connectionFailed();
445void SessionPrivate::socketActivity()
449 restartSocketTimer();
455 qCDebug(KIMAP2_LOG) <<
"Socket error: " <<
error;
459 qCWarning(KIMAP2_LOG) <<
"Socket error:" <<
error;
460 currentJob->setSocketError(error);
461 }
else if (!queue.isEmpty()) {
462 qCWarning(KIMAP2_LOG) <<
"Socket error:" <<
error;
463 currentJob = queue.takeFirst();
464 currentJob->setSocketError(error);
470void SessionPrivate::clearJobQueue()
472 if (!currentJob && !queue.isEmpty()) {
473 currentJob = queue.takeFirst();
476 currentJob->connectionLost();
480 qDeleteAll(queueCopy);
482 emit q->jobQueueSizeChanged(0);
487 socket->setProtocol(protocol);
488 connect(socket.data(), &QSslSocket::encrypted,
this, &SessionPrivate::sslConnected);
490 qCDebug(KIMAP2_LOG) <<
"Starting client encryption";
491 Q_ASSERT(socket->mode() == QSslSocket::UnencryptedMode);
492 socket->startClientEncryption();
494 qCWarning(KIMAP2_LOG) <<
"The socket is not yet connected";
498void SessionPrivate::sslConnected()
500 qCDebug(KIMAP2_LOG) <<
"ssl is connected";
501 emit encryptionNegotiationResult(
true);
504void SessionPrivate::setSocketTimeout(
int ms)
506 bool timerActive = socketTimer.isActive();
512 socketTimerInterval = ms;
519int SessionPrivate::socketTimeout()
const
521 return socketTimerInterval;
524void SessionPrivate::startSocketTimer()
526 if (socketTimerInterval < 0) {
529 Q_ASSERT(!socketTimer.isActive());
531 socketTimer.start(socketTimerInterval);
532 socketProgressTimer.start(socketProgressInterval);
535void SessionPrivate::stopSocketTimer()
538 socketProgressTimer.stop();
541void SessionPrivate::restartSocketTimer()
547void SessionPrivate::onSocketTimeout()
549 qCWarning(KIMAP2_LOG) <<
"Aborting on socket timeout. " << socketTimerInterval;
550 if (!currentJob && !queue.isEmpty()) {
551 currentJob = queue.takeFirst();
554 qCWarning(KIMAP2_LOG) <<
"Current job: " << currentJob->metaObject()->className();
555 currentJob->setErrorMessage(
"Aborting on socket timeout. Interval " +
QString::number(socketTimerInterval) +
" ms");
558 socketProgressTimer.stop();
561QString SessionPrivate::getStateName()
const
563 if (hostLookupInProgress) {
564 return "Host lookup";
567 case Session::Disconnected:
568 return "Disconnected";
569 case Session::NotAuthenticated:
570 return "NotAuthenticated";
571 case Session::Authenticated:
572 return "Authenticated";
573 case Session::Selected:
577 return "Unknown State";
580void SessionPrivate::onSocketProgressTimeout()
583 qCDebug(KIMAP2_LOG) <<
"Processing job: " << currentJob->metaObject()->className() <<
"Current state: " << getStateName() << (socket ? socket->state() :
QAbstractSocket::UnconnectedState);
585 qCDebug(KIMAP2_LOG) <<
"Next job: " << (queue.isEmpty() ?
"No job" : queue.head()->metaObject()->className()) <<
"Current state: " << getStateName() << (socket ? socket->state() :
QAbstractSocket::UnconnectedState);
589void SessionPrivate::writeDataQueue()
591 while (!dataQueue.isEmpty()) {
592 socket->write(dataQueue.dequeue());
596void SessionPrivate::readMessage()
599 accumulatedWaitTime += time.elapsed();
602 stream->parseStream();
603 if (stream->error()) {
604 qCWarning(KIMAP2_LOG) <<
"Error while parsing, closing connection.";
605 qCDebug(KIMAP2_LOG) <<
"Current buffer: " << stream->currentBuffer();
609 accumulatedProcessingTime += time.elapsed();
611 qCDebug(KIMAP2_LOG) <<
"Wait vs process vs total: " << accumulatedWaitTime << accumulatedProcessingTime << accumulatedWaitTime + accumulatedProcessingTime;
615void SessionPrivate::closeSocket()
617 qCDebug(KIMAP2_LOG) <<
"Closing socket.";
621#include "moc_session.cpp"
622#include "moc_session_p.cpp"
Parser for IMAP messages that operates on a local socket stream.
void error(QWidget *parent, const QString &text, const QString &title, const KGuiItem &buttonOk, Options options=Notify)
QCA_EXPORT Logger * logger()
void stateChanged(QAbstractSocket::SocketState socketState)
bool isEmpty() const const
QByteArray left(qsizetype len) const const
QByteArray number(double n, char format, int precision)
QByteArray & remove(qsizetype pos, qsizetype len)
QByteArray rightJustified(qsizetype width, char fill, bool truncate) const const
qsizetype size() const const
void bytesWritten(qint64 bytes)
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void destroyed(QObject *obj)
QString fromLatin1(QByteArrayView str)
QString fromUtf8(QByteArrayView str)
QString number(double n, char format, int precision)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the IMAP support library and defines the RfcCodecs class.
KIMAP2_EXPORT QByteArray decodeImapFolderName(const QByteArray &inSrc)
Converts an UTF-7 encoded IMAP mailbox to a QByteArray.