25 #include "session_p.h"
29 #include "kimap_debug.h"
32 #include "message_p.h"
33 #include "sessionlogger_p.h"
35 #include "imapstreamparser.h"
39 static const int _kimap_sslVersionId = qRegisterMetaType<QSsl::SslProtocol>();
41 using namespace KIMAP2;
43 Session::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;
67 d, &SessionPrivate::socketConnected);
69 d, &SessionPrivate::handleSslErrors);
71 d, &SessionPrivate::socketError);
74 d, &SessionPrivate::socketActivity);
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);
112 QString Session::hostName()
const
117 quint16 Session::port()
const
122 Session::State Session::state()
const
127 bool Session::isConnected()
const
129 return (d->state == Authenticated || d->state == Selected);
132 QString Session::userName()
const
142 int Session::jobQueueSize()
const
144 return d->queue.
size() + (d->jobRunning ? 1 : 0);
147 void Session::close()
154 d->socket->ignoreSslErrors(errors);
157 void Session::setTimeout(
int timeout)
159 d->setSocketTimeout(timeout * 1000);
162 int Session::timeout()
const
164 return d->socketTimeout() / 1000;
167 QString Session::selectedMailBox()
const
173 SessionPrivate::SessionPrivate(Session *session)
177 hostLookupInProgress(false),
179 currentJob(Q_NULLPTR),
181 socketTimerInterval(30000),
182 socketProgressInterval(3000),
185 accumulatedWaitTime(0),
186 accumulatedProcessingTime(0),
192 stream->onResponseReceived([
this](
const Message &
message) {
197 SessionPrivate::~SessionPrivate()
203 emit q->sslErrors(errors);
206 void SessionPrivate::addJob(Job *job)
209 emit q->jobQueueSizeChanged(q->jobQueueSize());
216 void SessionPrivate::startNext()
221 void SessionPrivate::doStartNext()
231 currentJob = queue.dequeue();
235 qCDebug(KIMAP2_LOG) <<
"Cancelling job due to lack of connection: " << currentJob->metaObject()->className();
236 currentJob->connectionLost();
243 restartSocketTimer();
245 currentJob->doStart();
248 void SessionPrivate::jobDone(
KJob *job)
251 Q_ASSERT(job == currentJob);
257 currentJob = Q_NULLPTR;
258 emit q->jobQueueSizeChanged(q->jobQueueSize());
262 void SessionPrivate::jobDestroyed(
QObject *job)
264 queue.removeAll(
static_cast<KIMAP2::Job *
>(job));
265 if (currentJob == job) {
266 currentJob = Q_NULLPTR;
270 void 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();
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:
307 simplified.content.removeFirst();
308 simplified.content.removeFirst();
309 greeting = simplified.toString().trimmed();
310 setState(Session::NotAuthenticated);
311 }
else if (code ==
"PREAUTH") {
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() +
')';
365 void 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") {
399 void SessionPrivate::sendData(
const QByteArray &data)
401 restartSocketTimer();
404 qCInfo(KIMAP2_LOG) <<
"C: " << data;
406 if (logger && q->isConnected()) {
410 dataQueue.enqueue(data +
"\r\n");
414 void SessionPrivate::socketConnected()
416 qCInfo(KIMAP2_LOG) <<
"Socket connected.";
422 void 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();
445 void 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);
470 void 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);
490 qCDebug(KIMAP2_LOG) <<
"Starting client encryption";
492 socket->startClientEncryption();
494 qCWarning(KIMAP2_LOG) <<
"The socket is not yet connected";
498 void SessionPrivate::sslConnected()
500 qCDebug(KIMAP2_LOG) <<
"ssl is connected";
501 emit encryptionNegotiationResult(
true);
504 void SessionPrivate::setSocketTimeout(
int ms)
506 bool timerActive = socketTimer.isActive();
512 socketTimerInterval = ms;
519 int SessionPrivate::socketTimeout()
const
521 return socketTimerInterval;
524 void SessionPrivate::startSocketTimer()
526 if (socketTimerInterval < 0) {
529 Q_ASSERT(!socketTimer.isActive());
531 socketTimer.start(socketTimerInterval);
532 socketProgressTimer.start(socketProgressInterval);
535 void SessionPrivate::stopSocketTimer()
538 socketProgressTimer.stop();
541 void SessionPrivate::restartSocketTimer()
547 void 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();
561 QString 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";
580 void 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);
589 void SessionPrivate::writeDataQueue()
591 while (!dataQueue.isEmpty()) {
592 socket->write(dataQueue.dequeue());
596 void 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;
615 void SessionPrivate::closeSocket()
617 qCDebug(KIMAP2_LOG) <<
"Closing socket.";
621 #include "moc_session.cpp"
622 #include "moc_session_p.cpp"