KIMAP Library
session.cpp
00001 /* 00002 Copyright (c) 2009 Kevin Ottens <ervin@kde.org> 00003 00004 Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com> 00005 Author: Kevin Ottens <kevin@kdab.com> 00006 00007 This library is free software; you can redistribute it and/or modify it 00008 under the terms of the GNU Library General Public License as published by 00009 the Free Software Foundation; either version 2 of the License, or (at your 00010 option) any later version. 00011 00012 This library is distributed in the hope that it will be useful, but WITHOUT 00013 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00015 License for more details. 00016 00017 You should have received a copy of the GNU Library General Public License 00018 along with this library; see the file COPYING.LIB. If not, write to the 00019 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00020 02110-1301, USA. 00021 */ 00022 00023 #include "session.h" 00024 #include "session_p.h" 00025 #include "sessionuiproxy.h" 00026 00027 #include <QtCore/QDebug> 00028 #include <QtCore/QTimer> 00029 00030 #include <KDebug> 00031 #include <KDE/KLocale> 00032 00033 #include "job.h" 00034 #include "loginjob.h" 00035 #include "message_p.h" 00036 #include "sessionlogger_p.h" 00037 #include "sessionthread_p.h" 00038 #include "rfccodecs.h" 00039 00040 Q_DECLARE_METATYPE(KTcpSocket::SslVersion) 00041 Q_DECLARE_METATYPE(QSslSocket::SslMode) 00042 static const int _kimap_sslVersionId = qRegisterMetaType<KTcpSocket::SslVersion>(); 00043 00044 using namespace KIMAP; 00045 00046 Session::Session( const QString &hostName, quint16 port, QObject *parent) 00047 : QObject(parent), d(new SessionPrivate(this)) 00048 { 00049 if ( !qgetenv( "KIMAP_LOGFILE" ).isEmpty() ) { 00050 d->logger = new SessionLogger; 00051 } 00052 00053 d->isSocketConnected = false; 00054 d->state = Disconnected; 00055 d->jobRunning = false; 00056 00057 d->thread = new SessionThread(hostName, port, this); 00058 connect(d->thread, SIGNAL(encryptionNegotiationResult(bool,KTcpSocket::SslVersion)), 00059 d, SLOT(onEncryptionNegotiationResult(bool,KTcpSocket::SslVersion))); 00060 connect(d->thread, SIGNAL(sslError(KSslErrorUiData)), this, SLOT(handleSslError(KSslErrorUiData))); 00061 00062 d->startSocketTimer(); 00063 d->thread->start(); 00064 } 00065 00066 Session::~Session() 00067 { 00068 delete d->thread; 00069 } 00070 00071 void Session::setUiProxy(SessionUiProxy::Ptr proxy) 00072 { 00073 d->uiProxy = proxy; 00074 } 00075 00076 void Session::setUiProxy(SessionUiProxy *proxy) 00077 { 00078 setUiProxy( SessionUiProxy::Ptr( proxy ) ); 00079 } 00080 00081 QString Session::hostName() const 00082 { 00083 return d->thread->hostName(); 00084 } 00085 00086 quint16 Session::port() const 00087 { 00088 return d->thread->port(); 00089 } 00090 00091 Session::State Session::state() const 00092 { 00093 return d->state; 00094 } 00095 00096 QString Session::userName() const 00097 { 00098 return d->userName; 00099 } 00100 00101 QByteArray Session::serverGreeting() const 00102 { 00103 return d->greeting; 00104 } 00105 00106 int Session::jobQueueSize() const 00107 { 00108 return d->queue.size() + ( d->jobRunning ? 1 : 0 ); 00109 } 00110 00111 void KIMAP::Session::close() 00112 { 00113 d->thread->closeSocket(); 00114 } 00115 00116 void SessionPrivate::handleSslError(const KSslErrorUiData& errorData) 00117 { 00118 if (uiProxy && uiProxy->ignoreSslError(errorData)) { 00119 QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG(bool, true) ); 00120 } else { 00121 QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG(bool, false) ); 00122 } 00123 } 00124 00125 SessionPrivate::SessionPrivate( Session *session ) 00126 : QObject( session ), 00127 q(session), 00128 state(Session::Disconnected), 00129 logger(0), 00130 currentJob(0), 00131 tagCount(0), 00132 sslVersion(KTcpSocket::UnknownSslVersion), 00133 socketTimerInterval(30000) // By default timeouts on 30s 00134 { 00135 } 00136 00137 SessionPrivate::~SessionPrivate() 00138 { 00139 delete logger; 00140 } 00141 00142 void SessionPrivate::addJob(Job *job) 00143 { 00144 queue.append(job); 00145 emit q->jobQueueSizeChanged( q->jobQueueSize() ); 00146 00147 QObject::connect( job, SIGNAL(result(KJob*)), q, SLOT(jobDone(KJob*)) ); 00148 QObject::connect( job, SIGNAL(destroyed(QObject*)), q, SLOT(jobDestroyed(QObject*)) ); 00149 00150 if ( state!=Session::Disconnected ) { 00151 startNext(); 00152 } 00153 } 00154 00155 void SessionPrivate::startNext() 00156 { 00157 QTimer::singleShot( 0, q, SLOT(doStartNext()) ); 00158 } 00159 00160 void SessionPrivate::doStartNext() 00161 { 00162 if ( queue.isEmpty() || jobRunning || !isSocketConnected ) { 00163 return; 00164 } 00165 00166 startSocketTimer(); 00167 jobRunning = true; 00168 00169 currentJob = queue.dequeue(); 00170 currentJob->doStart(); 00171 } 00172 00173 void SessionPrivate::jobDone( KJob *job ) 00174 { 00175 Q_UNUSED( job ); 00176 Q_ASSERT( job == currentJob ); 00177 00178 // If we're in disconnected state it's because we ended up 00179 // here because the inactivity timer triggered, so no need to 00180 // stop it (it is single shot) 00181 if ( state!=Session::Disconnected ) { 00182 stopSocketTimer(); 00183 } 00184 00185 jobRunning = false; 00186 currentJob = 0; 00187 emit q->jobQueueSizeChanged( q->jobQueueSize() ); 00188 startNext(); 00189 } 00190 00191 void SessionPrivate::jobDestroyed( QObject *job ) 00192 { 00193 queue.removeAll( static_cast<KIMAP::Job*>( job ) ); 00194 if ( currentJob == job ) 00195 currentJob = 0; 00196 } 00197 00198 void SessionPrivate::responseReceived( const Message &response ) 00199 { 00200 if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) { 00201 logger->dataReceived( response.toString() ); 00202 } 00203 00204 QByteArray tag; 00205 QByteArray code; 00206 00207 if ( response.content.size()>=1 ) { 00208 tag = response.content[0].toString(); 00209 } 00210 00211 if ( response.content.size()>=2 ) { 00212 code = response.content[1].toString(); 00213 } 00214 00215 switch ( state ) { 00216 case Session::Disconnected: 00217 if (socketTimer.isActive()) { 00218 stopSocketTimer(); 00219 } 00220 if ( code=="OK" ) { 00221 setState(Session::NotAuthenticated); 00222 00223 Message simplified = response; 00224 simplified.content.removeFirst(); // Strip the tag 00225 simplified.content.removeFirst(); // Strip the code 00226 greeting = simplified.toString().trimmed(); // Save the server greeting 00227 00228 startNext(); 00229 } else if ( code=="PREAUTH" ) { 00230 setState(Session::Authenticated); 00231 00232 Message simplified = response; 00233 simplified.content.removeFirst(); // Strip the tag 00234 simplified.content.removeFirst(); // Strip the code 00235 greeting = simplified.toString().trimmed(); // Save the server greeting 00236 00237 startNext(); 00238 } else { 00239 thread->closeSocket(); 00240 } 00241 return; 00242 case Session::NotAuthenticated: 00243 if ( code=="OK" && tag==authTag ) { 00244 setState(Session::Authenticated); 00245 } 00246 break; 00247 case Session::Authenticated: 00248 if ( code=="OK" && tag==selectTag ) { 00249 setState(Session::Selected); 00250 currentMailBox = upcomingMailBox; 00251 } 00252 break; 00253 case Session::Selected: 00254 if ( ( code=="OK" && tag==closeTag ) 00255 || ( code!="OK" && tag==selectTag) ) { 00256 setState(Session::Authenticated); 00257 currentMailBox = QByteArray(); 00258 } else if ( code=="OK" && tag==selectTag ) { 00259 currentMailBox = upcomingMailBox; 00260 } 00261 break; 00262 } 00263 00264 if (tag==authTag) authTag.clear(); 00265 if (tag==selectTag) selectTag.clear(); 00266 if (tag==closeTag) closeTag.clear(); 00267 00268 // If a job is running forward it the response 00269 if ( currentJob!=0 ) { 00270 restartSocketTimer(); 00271 currentJob->handleResponse( response ); 00272 } else { 00273 qWarning() << "A message was received from the server with no job to handle it:" 00274 << response.toString() 00275 << '('+response.toString().toHex()+')'; 00276 } 00277 } 00278 00279 void SessionPrivate::setState(Session::State s) 00280 { 00281 if (s != state) { 00282 Session::State oldState = state; 00283 state = s; 00284 emit q->stateChanged(state, oldState); 00285 } 00286 } 00287 00288 QByteArray SessionPrivate::sendCommand( const QByteArray &command, const QByteArray &args ) 00289 { 00290 QByteArray tag = 'A' + QByteArray::number(++tagCount).rightJustified(6, '0'); 00291 00292 QByteArray payload = tag+' '+command; 00293 if ( !args.isEmpty() ) { 00294 payload+= ' '+args; 00295 } 00296 00297 sendData( payload ); 00298 00299 if ( command=="LOGIN" || command=="AUTHENTICATE" ) { 00300 authTag = tag; 00301 } else if ( command=="SELECT" || command=="EXAMINE" ) { 00302 selectTag = tag; 00303 upcomingMailBox = args; 00304 upcomingMailBox.remove( 0, 1 ); 00305 upcomingMailBox.chop( 1 ); 00306 upcomingMailBox = KIMAP::decodeImapFolderName( upcomingMailBox ); 00307 } else if ( command=="CLOSE" ) { 00308 closeTag = tag; 00309 } 00310 00311 return tag; 00312 } 00313 00314 void SessionPrivate::sendData( const QByteArray &data ) 00315 { 00316 restartSocketTimer(); 00317 00318 if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) { 00319 logger->dataSent( data ); 00320 } 00321 00322 thread->sendData(data+"\r\n"); 00323 } 00324 00325 void SessionPrivate::socketConnected() 00326 { 00327 stopSocketTimer(); 00328 isSocketConnected = true; 00329 00330 bool willUseSsl = false; 00331 if ( !queue.isEmpty() ) { 00332 KIMAP::LoginJob *login = qobject_cast<KIMAP::LoginJob*>( queue.first() ); 00333 if ( login ) { 00334 willUseSsl = ( login->encryptionMode() == KIMAP::LoginJob::SslV2 ) 00335 || ( login->encryptionMode() == KIMAP::LoginJob::SslV3 ) 00336 || ( login->encryptionMode() == KIMAP::LoginJob::SslV3_1 ) 00337 || ( login->encryptionMode() == KIMAP::LoginJob::AnySslVersion ); 00338 00339 userName = login->userName(); 00340 } 00341 } 00342 00343 if ( state == Session::Disconnected && willUseSsl ) { 00344 startNext(); 00345 } else { 00346 startSocketTimer(); 00347 } 00348 } 00349 00350 void SessionPrivate::socketDisconnected() 00351 { 00352 if (socketTimer.isActive()) { 00353 stopSocketTimer(); 00354 } 00355 00356 if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) { 00357 logger->disconnectionOccured(); 00358 } 00359 00360 if ( state != Session::Disconnected ) { 00361 setState(Session::Disconnected); 00362 emit q->connectionLost(); 00363 } else { 00364 emit q->connectionFailed(); 00365 } 00366 00367 isSocketConnected = false; 00368 00369 clearJobQueue(); 00370 } 00371 00372 void SessionPrivate::socketActivity() 00373 { 00374 restartSocketTimer(); 00375 } 00376 00377 void SessionPrivate::socketError() 00378 { 00379 if (socketTimer.isActive()) { 00380 stopSocketTimer(); 00381 } 00382 00383 if ( isSocketConnected ) { 00384 thread->closeSocket(); 00385 } else { 00386 emit q->connectionFailed(); 00387 emit q->connectionLost(); // KDE5: Remove this. We shouldn't emit connectionLost() if we weren't connected in the first place 00388 clearJobQueue(); 00389 } 00390 } 00391 00392 void SessionPrivate::clearJobQueue() 00393 { 00394 if ( currentJob ) { 00395 currentJob->connectionLost(); 00396 } else if ( !queue.isEmpty() ) { 00397 currentJob = queue.takeFirst(); 00398 currentJob->connectionLost(); 00399 } 00400 00401 qDeleteAll(queue); 00402 queue.clear(); 00403 emit q->jobQueueSizeChanged( 0 ); 00404 } 00405 00406 void SessionPrivate::startSsl(const KTcpSocket::SslVersion &version) 00407 { 00408 QMetaObject::invokeMethod( thread, "startSsl", Qt::QueuedConnection, Q_ARG(KTcpSocket::SslVersion, version) ); 00409 } 00410 00411 QString Session::selectedMailBox() const 00412 { 00413 return QString::fromUtf8( d->currentMailBox ); 00414 } 00415 00416 void SessionPrivate::onEncryptionNegotiationResult(bool isEncrypted, KTcpSocket::SslVersion version) 00417 { 00418 if ( isEncrypted ) { 00419 sslVersion = version; 00420 } else { 00421 sslVersion = KTcpSocket::UnknownSslVersion; 00422 } 00423 emit encryptionNegotiationResult( isEncrypted ); 00424 } 00425 00426 KTcpSocket::SslVersion SessionPrivate::negotiatedEncryption() const 00427 { 00428 return sslVersion; 00429 } 00430 00431 void SessionPrivate::setSocketTimeout( int ms ) 00432 { 00433 bool timerActive = socketTimer.isActive(); 00434 00435 if ( timerActive ) { 00436 stopSocketTimer(); 00437 } 00438 00439 socketTimerInterval = ms; 00440 00441 if ( timerActive ) { 00442 startSocketTimer(); 00443 } 00444 } 00445 00446 int SessionPrivate::socketTimeout() const 00447 { 00448 return socketTimerInterval; 00449 } 00450 00451 void SessionPrivate::startSocketTimer() 00452 { 00453 if ( socketTimerInterval<0 ) { 00454 return; 00455 } 00456 Q_ASSERT( !socketTimer.isActive() ); 00457 00458 connect( &socketTimer, SIGNAL(timeout()), 00459 this, SLOT(onSocketTimeout()) ); 00460 00461 socketTimer.setSingleShot( true ); 00462 socketTimer.start( socketTimerInterval ); 00463 } 00464 00465 void SessionPrivate::stopSocketTimer() 00466 { 00467 if ( socketTimerInterval<0 ) { 00468 return; 00469 } 00470 00471 socketTimer.stop(); 00472 00473 disconnect( &socketTimer, SIGNAL(timeout()), 00474 this, SLOT(onSocketTimeout()) ); 00475 } 00476 00477 void SessionPrivate::restartSocketTimer() 00478 { 00479 if ( socketTimer.isActive() ) { 00480 stopSocketTimer(); 00481 } 00482 startSocketTimer(); 00483 } 00484 00485 void SessionPrivate::onSocketTimeout() 00486 { 00487 kDebug() << "Socket timeout!"; 00488 thread->closeSocket(); 00489 } 00490 00491 void Session::setTimeout( int timeout ) 00492 { 00493 d->setSocketTimeout( timeout * 1000 ); 00494 } 00495 00496 #include "session.moc" 00497 #include "session_p.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu Aug 2 2012 15:24:24 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
Documentation copyright © 1996-2012 The KDE developers.
Generated on Thu Aug 2 2012 15:24:24 by doxygen 1.7.5 written by Dimitri van Heesch, © 1997-2006
KDE's Doxygen guidelines are available online.