File indexing completed on 2024-05-12 05:17:17
0001 /* 0002 SPDX-FileCopyrightText: 2009 Kevin Ottens <ervin@kde.org> 0003 0004 SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 0005 SPDX-FileContributor: Kevin Ottens <kevin@kdab.com> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "session.h" 0011 #include "session_p.h" 0012 0013 #include <QPointer> 0014 #include <QTimer> 0015 0016 #include "kimap_debug.h" 0017 0018 #include "job.h" 0019 #include "job_p.h" 0020 #include "loginjob.h" 0021 #include "response_p.h" 0022 #include "rfccodecs.h" 0023 #include "sessionlogger_p.h" 0024 #include "sessionthread_p.h" 0025 0026 Q_DECLARE_METATYPE(QSsl::SslProtocol) 0027 Q_DECLARE_METATYPE(QSslSocket::SslMode) 0028 static const int _kimap_sslVersionId = qRegisterMetaType<QSsl::SslProtocol>(); 0029 0030 using namespace KIMAP; 0031 0032 Session::Session(const QString &hostName, quint16 port, QObject *parent) 0033 : QObject(parent) 0034 , d(new SessionPrivate(this)) 0035 { 0036 if (!qEnvironmentVariableIsEmpty("KIMAP_LOGFILE")) { 0037 d->logger = new SessionLogger; 0038 } 0039 0040 d->isSocketConnected = false; 0041 d->state = Disconnected; 0042 d->jobRunning = false; 0043 0044 d->thread = new SessionThread(hostName, port); 0045 connect(d->thread, &SessionThread::encryptionNegotiationResult, d, &SessionPrivate::onEncryptionNegotiationResult); 0046 connect(d->thread, &SessionThread::sslError, d, &SessionPrivate::handleSslError); 0047 connect(d->thread, &SessionThread::socketDisconnected, d, &SessionPrivate::socketDisconnected); 0048 connect(d->thread, &SessionThread::responseReceived, d, &SessionPrivate::responseReceived); 0049 connect(d->thread, &SessionThread::socketConnected, d, &SessionPrivate::socketConnected); 0050 connect(d->thread, &SessionThread::socketActivity, d, &SessionPrivate::socketActivity); 0051 connect(d->thread, &SessionThread::socketError, d, &SessionPrivate::socketError); 0052 0053 d->socketTimer.setSingleShot(true); 0054 connect(&d->socketTimer, &QTimer::timeout, d, &SessionPrivate::onSocketTimeout); 0055 0056 d->startSocketTimer(); 0057 } 0058 0059 Session::~Session() 0060 { 0061 // Make sure all jobs know we're done 0062 d->socketDisconnected(); 0063 delete d->thread; 0064 d->thread = nullptr; 0065 } 0066 0067 void Session::setUiProxy(const SessionUiProxy::Ptr &proxy) 0068 { 0069 d->uiProxy = proxy; 0070 } 0071 0072 void Session::setUiProxy(SessionUiProxy *proxy) 0073 { 0074 setUiProxy(SessionUiProxy::Ptr(proxy)); 0075 } 0076 0077 QString Session::hostName() const 0078 { 0079 return d->thread->hostName(); 0080 } 0081 0082 quint16 Session::port() const 0083 { 0084 return d->thread->port(); 0085 } 0086 0087 void Session::setUseNetworkProxy(bool useProxy) 0088 { 0089 d->thread->setUseNetworkProxy(useProxy); 0090 } 0091 0092 Session::State Session::state() const 0093 { 0094 return d->state; 0095 } 0096 0097 QString Session::userName() const 0098 { 0099 return d->userName; 0100 } 0101 0102 QByteArray Session::serverGreeting() const 0103 { 0104 return d->greeting; 0105 } 0106 0107 int Session::jobQueueSize() const 0108 { 0109 return d->queue.size() + (d->jobRunning ? 1 : 0); 0110 } 0111 0112 void KIMAP::Session::close() 0113 { 0114 d->thread->closeSocket(); 0115 } 0116 0117 void SessionPrivate::handleSslError(const KSslErrorUiData &errorData) 0118 { 0119 // ignoreSslError is async, so the thread might already be gone when it returns 0120 QPointer<SessionThread> _t = thread; 0121 const bool ignoreSslError = uiProxy && uiProxy->ignoreSslError(errorData); 0122 if (_t) { 0123 _t->sslErrorHandlerResponse(ignoreSslError); 0124 } 0125 } 0126 0127 SessionPrivate::SessionPrivate(Session *session) 0128 : QObject(session) 0129 , q(session) 0130 , isSocketConnected(false) 0131 , state(Session::Disconnected) 0132 , logger(nullptr) 0133 , thread(nullptr) 0134 , jobRunning(false) 0135 , currentJob(nullptr) 0136 , tagCount(0) 0137 , sslVersion(QSsl::UnknownProtocol) 0138 , socketTimerInterval(30000) // By default timeouts on 30s 0139 { 0140 } 0141 0142 SessionPrivate::~SessionPrivate() 0143 { 0144 delete logger; 0145 } 0146 0147 void SessionPrivate::addJob(Job *job) 0148 { 0149 queue.append(job); 0150 Q_EMIT q->jobQueueSizeChanged(q->jobQueueSize()); 0151 0152 QObject::connect(job, &KJob::result, this, &SessionPrivate::jobDone); 0153 QObject::connect(job, &QObject::destroyed, this, &SessionPrivate::jobDestroyed); 0154 0155 if (state != Session::Disconnected) { 0156 startNext(); 0157 } 0158 } 0159 0160 void SessionPrivate::startNext() 0161 { 0162 QMetaObject::invokeMethod(this, &SessionPrivate::doStartNext); 0163 } 0164 0165 void SessionPrivate::doStartNext() 0166 { 0167 if (queue.isEmpty() || jobRunning || !isSocketConnected) { 0168 return; 0169 } 0170 0171 restartSocketTimer(); 0172 jobRunning = true; 0173 0174 currentJob = queue.dequeue(); 0175 currentJob->doStart(); 0176 } 0177 0178 void SessionPrivate::jobDone(KJob *job) 0179 { 0180 Q_UNUSED(job) 0181 Q_ASSERT(job == currentJob); 0182 0183 stopSocketTimer(); 0184 0185 jobRunning = false; 0186 currentJob = nullptr; 0187 Q_EMIT q->jobQueueSizeChanged(q->jobQueueSize()); 0188 startNext(); 0189 } 0190 0191 void SessionPrivate::jobDestroyed(QObject *job) 0192 { 0193 queue.removeAll(static_cast<KIMAP::Job *>(job)); 0194 if (currentJob == job) { 0195 currentJob = nullptr; 0196 } 0197 } 0198 0199 void SessionPrivate::responseReceived(const Response &response) 0200 { 0201 if (logger && isConnected()) { 0202 logger->dataReceived(response.toString()); 0203 } 0204 0205 QByteArray tag; 0206 QByteArray code; 0207 0208 if (response.content.size() >= 1) { 0209 tag = response.content[0].toString(); 0210 } 0211 0212 if (response.content.size() >= 2) { 0213 code = response.content[1].toString(); 0214 } 0215 0216 // BYE may arrive as part of a LOGOUT sequence or before the server closes the connection after an error. 0217 // In any case we should wait until the server closes the connection, so we don't have to do anything. 0218 if (code == "BYE") { 0219 Response simplified = response; 0220 if (simplified.content.size() >= 2) { 0221 simplified.content.removeFirst(); // Strip the tag 0222 simplified.content.removeFirst(); // Strip the code 0223 } 0224 qCDebug(KIMAP_LOG) << "Received BYE: " << simplified.toString(); 0225 return; 0226 } 0227 0228 switch (state) { 0229 case Session::Disconnected: 0230 if (socketTimer.isActive()) { 0231 stopSocketTimer(); 0232 } 0233 if (code == "OK") { 0234 setState(Session::NotAuthenticated); 0235 0236 Response simplified = response; 0237 simplified.content.removeFirst(); // Strip the tag 0238 simplified.content.removeFirst(); // Strip the code 0239 greeting = simplified.toString().trimmed(); // Save the server greeting 0240 startNext(); 0241 } else if (code == "PREAUTH") { 0242 setState(Session::Authenticated); 0243 0244 Response simplified = response; 0245 simplified.content.removeFirst(); // Strip the tag 0246 simplified.content.removeFirst(); // Strip the code 0247 greeting = simplified.toString().trimmed(); // Save the server greeting 0248 0249 startNext(); 0250 } else { 0251 thread->closeSocket(); 0252 } 0253 return; 0254 case Session::NotAuthenticated: 0255 if (code == "OK" && tag == authTag) { 0256 setState(Session::Authenticated); 0257 } 0258 break; 0259 case Session::Authenticated: 0260 if (code == "OK" && tag == selectTag) { 0261 setState(Session::Selected); 0262 currentMailBox = upcomingMailBox; 0263 } 0264 break; 0265 case Session::Selected: 0266 if ((code == "OK" && tag == closeTag) || (code != "OK" && tag == selectTag)) { 0267 setState(Session::Authenticated); 0268 currentMailBox = QByteArray(); 0269 } else if (code == "OK" && tag == selectTag) { 0270 currentMailBox = upcomingMailBox; 0271 } 0272 break; 0273 } 0274 0275 if (tag == authTag) { 0276 authTag.clear(); 0277 } 0278 if (tag == selectTag) { 0279 selectTag.clear(); 0280 } 0281 if (tag == closeTag) { 0282 closeTag.clear(); 0283 } 0284 0285 // If a job is running forward it the response 0286 if (currentJob != nullptr) { 0287 restartSocketTimer(); 0288 currentJob->handleResponse(response); 0289 } else { 0290 qCWarning(KIMAP_LOG) << "A message was received from the server with no job to handle it:" << response.toString() 0291 << '(' + response.toString().toHex() + ')'; 0292 } 0293 } 0294 0295 void SessionPrivate::setState(Session::State s) 0296 { 0297 if (s != state) { 0298 Session::State oldState = state; 0299 state = s; 0300 Q_EMIT q->stateChanged(state, oldState); 0301 } 0302 } 0303 0304 QByteArray SessionPrivate::sendCommand(const QByteArray &command, const QByteArray &args) 0305 { 0306 QByteArray tag = 'A' + QByteArray::number(++tagCount).rightJustified(6, '0'); 0307 0308 QByteArray payload = tag + ' ' + command; 0309 if (!args.isEmpty()) { 0310 payload += ' ' + args; 0311 } 0312 0313 sendData(payload); 0314 0315 if (command == "LOGIN" || command == "AUTHENTICATE") { 0316 authTag = tag; 0317 } else if (command == "SELECT" || command == "EXAMINE") { 0318 selectTag = tag; 0319 upcomingMailBox = args; 0320 upcomingMailBox.remove(0, 1); 0321 upcomingMailBox = upcomingMailBox.left(upcomingMailBox.indexOf('\"')); 0322 upcomingMailBox = KIMAP::decodeImapFolderName(upcomingMailBox); 0323 } else if (command == "CLOSE") { 0324 closeTag = tag; 0325 } 0326 return tag; 0327 } 0328 0329 void SessionPrivate::sendData(const QByteArray &data) 0330 { 0331 restartSocketTimer(); 0332 0333 if (logger && isConnected()) { 0334 logger->dataSent(data); 0335 } 0336 0337 thread->sendData(data + "\r\n"); 0338 } 0339 0340 void SessionPrivate::socketConnected() 0341 { 0342 stopSocketTimer(); 0343 isSocketConnected = true; 0344 0345 bool willUseSsl = false; 0346 if (!queue.isEmpty()) { 0347 auto login = qobject_cast<KIMAP::LoginJob *>(queue.first()); 0348 if (login) { 0349 willUseSsl = (login->encryptionMode() == KIMAP::LoginJob::SSLorTLS); 0350 0351 userName = login->userName(); 0352 } 0353 } 0354 0355 if (state == Session::Disconnected && willUseSsl) { 0356 startSsl(QSsl::SecureProtocols); 0357 } else { 0358 startSocketTimer(); 0359 } 0360 } 0361 0362 bool SessionPrivate::isConnected() const 0363 { 0364 return state == Session::Authenticated || state == Session::Selected; 0365 } 0366 0367 void SessionPrivate::socketDisconnected() 0368 { 0369 if (socketTimer.isActive()) { 0370 stopSocketTimer(); 0371 } 0372 0373 if (logger && isConnected()) { 0374 logger->disconnectionOccured(); 0375 } 0376 0377 if (isSocketConnected) { 0378 setState(Session::Disconnected); 0379 Q_EMIT q->connectionLost(); 0380 } else { 0381 Q_EMIT q->connectionFailed(); 0382 } 0383 0384 isSocketConnected = false; 0385 0386 clearJobQueue(); 0387 } 0388 0389 void SessionPrivate::socketActivity() 0390 { 0391 restartSocketTimer(); 0392 } 0393 0394 void SessionPrivate::socketError(QAbstractSocket::SocketError error) 0395 { 0396 if (socketTimer.isActive()) { 0397 stopSocketTimer(); 0398 } 0399 0400 if (currentJob) { 0401 currentJob->d_ptr->setSocketError(error); 0402 } else if (!queue.isEmpty()) { 0403 currentJob = queue.takeFirst(); 0404 currentJob->d_ptr->setSocketError(error); 0405 } 0406 0407 if (isSocketConnected) { 0408 thread->closeSocket(); 0409 } else { 0410 Q_EMIT q->connectionFailed(); 0411 clearJobQueue(); 0412 } 0413 } 0414 0415 void SessionPrivate::clearJobQueue() 0416 { 0417 if (currentJob) { 0418 currentJob->connectionLost(); 0419 } else if (!queue.isEmpty()) { 0420 currentJob = queue.takeFirst(); 0421 currentJob->connectionLost(); 0422 } 0423 0424 QQueue<Job *> queueCopy = queue; // copy because jobDestroyed calls removeAll 0425 qDeleteAll(queueCopy); 0426 queue.clear(); 0427 Q_EMIT q->jobQueueSizeChanged(0); 0428 } 0429 0430 void SessionPrivate::startSsl(QSsl::SslProtocol protocol) 0431 { 0432 thread->startSsl(protocol); 0433 } 0434 0435 QString Session::selectedMailBox() const 0436 { 0437 return QString::fromUtf8(d->currentMailBox); 0438 } 0439 0440 void SessionPrivate::onEncryptionNegotiationResult(bool isEncrypted, QSsl::SslProtocol protocol) 0441 { 0442 if (isEncrypted) { 0443 sslVersion = protocol; 0444 } else { 0445 sslVersion = QSsl::UnknownProtocol; 0446 } 0447 Q_EMIT encryptionNegotiationResult(isEncrypted); 0448 } 0449 0450 QSsl::SslProtocol SessionPrivate::negotiatedEncryption() const 0451 { 0452 return sslVersion; 0453 } 0454 0455 void SessionPrivate::setSocketTimeout(int ms) 0456 { 0457 bool timerActive = socketTimer.isActive(); 0458 0459 if (timerActive) { 0460 stopSocketTimer(); 0461 } 0462 0463 socketTimerInterval = ms; 0464 0465 if (timerActive) { 0466 startSocketTimer(); 0467 } 0468 } 0469 0470 int SessionPrivate::socketTimeout() const 0471 { 0472 return socketTimerInterval; 0473 } 0474 0475 void SessionPrivate::startSocketTimer() 0476 { 0477 if (socketTimerInterval < 0) { 0478 return; 0479 } 0480 Q_ASSERT(!socketTimer.isActive()); 0481 0482 socketTimer.start(socketTimerInterval); 0483 } 0484 0485 void SessionPrivate::stopSocketTimer() 0486 { 0487 if (socketTimerInterval < 0) { 0488 return; 0489 } 0490 0491 socketTimer.stop(); 0492 } 0493 0494 void SessionPrivate::restartSocketTimer() 0495 { 0496 if (socketTimer.isActive()) { 0497 stopSocketTimer(); 0498 } 0499 startSocketTimer(); 0500 } 0501 0502 void SessionPrivate::onSocketTimeout() 0503 { 0504 qCDebug(KIMAP_LOG) << "Socket timeout!"; 0505 thread->closeSocket(); 0506 } 0507 0508 void Session::setTimeout(int timeout) 0509 { 0510 d->setSocketTimeout(timeout * 1000); 0511 } 0512 0513 int Session::timeout() const 0514 { 0515 return d->socketTimeout() / 1000; 0516 } 0517 0518 #include "moc_session.cpp" 0519 #include "moc_session_p.cpp"