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(const KSslErrorUiData&)), this, SLOT(handleSslError(const KSslErrorUiData&))); 00061 00062 d->thread->start(); 00063 } 00064 00065 Session::~Session() 00066 { 00067 delete d->thread; 00068 } 00069 00070 void Session::setUiProxy(SessionUiProxy::Ptr proxy) 00071 { 00072 d->uiProxy = proxy; 00073 } 00074 00075 void Session::setUiProxy(SessionUiProxy *proxy) 00076 { 00077 setUiProxy( SessionUiProxy::Ptr( proxy ) ); 00078 } 00079 00080 QString Session::hostName() const 00081 { 00082 return d->thread->hostName(); 00083 } 00084 00085 quint16 Session::port() const 00086 { 00087 return d->thread->port(); 00088 } 00089 00090 Session::State Session::state() const 00091 { 00092 return d->state; 00093 } 00094 00095 QByteArray Session::serverGreeting() const 00096 { 00097 return d->greeting; 00098 } 00099 00100 int Session::jobQueueSize() const 00101 { 00102 return d->queue.size() + ( d->jobRunning ? 1 : 0 ); 00103 } 00104 00105 void KIMAP::Session::close() 00106 { 00107 d->socketDisconnected(); 00108 } 00109 00110 void SessionPrivate::handleSslError(const KSslErrorUiData& errorData) 00111 { 00112 if (uiProxy && uiProxy->ignoreSslError(errorData)) { 00113 QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG(bool, true) ); 00114 } else { 00115 QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG(bool, false) ); 00116 } 00117 } 00118 00119 SessionPrivate::SessionPrivate( Session *session ) 00120 : QObject( session ), 00121 q(session), 00122 state(Session::Disconnected), 00123 logger(0), 00124 currentJob(0), 00125 tagCount(0), 00126 sslVersion(KTcpSocket::UnknownSslVersion), 00127 socketTimerInterval(30000) // By default timeouts on 30s 00128 { 00129 } 00130 00131 SessionPrivate::~SessionPrivate() 00132 { 00133 delete logger; 00134 } 00135 00136 void SessionPrivate::addJob(Job *job) 00137 { 00138 queue.append(job); 00139 emit q->jobQueueSizeChanged( q->jobQueueSize() ); 00140 00141 QObject::connect( job, SIGNAL(result(KJob*)), q, SLOT(jobDone(KJob*)) ); 00142 QObject::connect( job, SIGNAL(destroyed(QObject*)), q, SLOT(jobDestroyed(QObject*)) ); 00143 00144 if ( state!=Session::Disconnected ) { 00145 startNext(); 00146 } 00147 } 00148 00149 void SessionPrivate::startNext() 00150 { 00151 QTimer::singleShot( 0, q, SLOT(doStartNext()) ); 00152 } 00153 00154 void SessionPrivate::doStartNext() 00155 { 00156 if ( queue.isEmpty() || jobRunning || !isSocketConnected ) { 00157 return; 00158 } 00159 00160 startSocketTimer(); 00161 jobRunning = true; 00162 00163 currentJob = queue.dequeue(); 00164 currentJob->doStart(); 00165 } 00166 00167 void SessionPrivate::jobDone( KJob *job ) 00168 { 00169 Q_UNUSED( job ); 00170 Q_ASSERT( job == currentJob ); 00171 00172 // If we're in disconnected state it's because we ended up 00173 // here because the inactivity timer triggered, so no need to 00174 // stop it (it is single shot) 00175 if ( state!=Session::Disconnected ) { 00176 stopSocketTimer(); 00177 } 00178 00179 jobRunning = false; 00180 currentJob = 0; 00181 emit q->jobQueueSizeChanged( q->jobQueueSize() ); 00182 startNext(); 00183 } 00184 00185 void SessionPrivate::jobDestroyed( QObject *job ) 00186 { 00187 queue.removeAll( static_cast<KIMAP::Job*>( job ) ); 00188 if ( currentJob == job ) 00189 currentJob = 0; 00190 } 00191 00192 void SessionPrivate::responseReceived( const Message &response ) 00193 { 00194 if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) { 00195 logger->dataReceived( response.toString() ); 00196 } 00197 00198 QByteArray tag; 00199 QByteArray code; 00200 00201 if ( response.content.size()>=1 ) { 00202 tag = response.content[0].toString(); 00203 } 00204 00205 if ( response.content.size()>=2 ) { 00206 code = response.content[1].toString(); 00207 } 00208 00209 switch ( state ) { 00210 case Session::Disconnected: 00211 if ( code=="OK" ) { 00212 state = Session::NotAuthenticated; 00213 00214 Message simplified = response; 00215 simplified.content.removeFirst(); // Strip the tag 00216 simplified.content.removeFirst(); // Strip the code 00217 greeting = simplified.toString().trimmed(); // Save the server greeting 00218 00219 startNext(); 00220 } else if ( code=="PREAUTH" ) { 00221 state = Session::Authenticated; 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 { 00230 thread->closeSocket(); 00231 QTimer::singleShot( 1000, thread, SLOT( reconnect() ) ); 00232 } 00233 return; 00234 case Session::NotAuthenticated: 00235 if ( code=="OK" && tag==authTag ) { 00236 state = Session::Authenticated; 00237 } 00238 break; 00239 case Session::Authenticated: 00240 if ( code=="OK" && tag==selectTag ) { 00241 state = Session::Selected; 00242 currentMailBox = upcomingMailBox; 00243 } 00244 break; 00245 case Session::Selected: 00246 if ( ( code=="OK" && tag==closeTag ) 00247 || ( code!="OK" && tag==selectTag) ) { 00248 state = Session::Authenticated; 00249 currentMailBox = QByteArray(); 00250 } else if ( code=="OK" && tag==selectTag ) { 00251 currentMailBox = upcomingMailBox; 00252 } 00253 break; 00254 } 00255 00256 if (tag==authTag) authTag.clear(); 00257 if (tag==selectTag) selectTag.clear(); 00258 if (tag==closeTag) closeTag.clear(); 00259 00260 // If a job is running forward it the response 00261 if ( currentJob!=0 ) { 00262 restartSocketTimer(); 00263 currentJob->handleResponse( response ); 00264 } else { 00265 qWarning() << "A message was received from the server with no job to handle it:" 00266 << response.toString() 00267 << '('+response.toString().toHex()+')'; 00268 } 00269 } 00270 00271 QByteArray SessionPrivate::sendCommand( const QByteArray &command, const QByteArray &args ) 00272 { 00273 QByteArray tag = 'A' + QByteArray::number(++tagCount).rightJustified(6, '0'); 00274 00275 QByteArray payload = tag+' '+command; 00276 if ( !args.isEmpty() ) { 00277 payload+= ' '+args; 00278 } 00279 00280 sendData( payload ); 00281 00282 if ( command=="LOGIN" || command=="AUTHENTICATE" ) { 00283 authTag = tag; 00284 } else if ( command=="SELECT" || command=="EXAMINE" ) { 00285 selectTag = tag; 00286 upcomingMailBox = args; 00287 upcomingMailBox.remove( 0, 1 ); 00288 upcomingMailBox.chop( 1 ); 00289 upcomingMailBox = KIMAP::decodeImapFolderName( upcomingMailBox ); 00290 } else if ( command=="CLOSE" ) { 00291 closeTag = tag; 00292 } 00293 00294 return tag; 00295 } 00296 00297 void SessionPrivate::sendData( const QByteArray &data ) 00298 { 00299 restartSocketTimer(); 00300 00301 if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) { 00302 logger->dataSent( data ); 00303 } 00304 00305 thread->sendData(data+"\r\n"); 00306 } 00307 00308 void SessionPrivate::socketConnected() 00309 { 00310 isSocketConnected = true; 00311 00312 bool willUseSsl = false; 00313 if ( !queue.isEmpty() ) { 00314 KIMAP::LoginJob *login = qobject_cast<KIMAP::LoginJob*>( queue.first() ); 00315 if ( login ) { 00316 willUseSsl = ( login->encryptionMode() == KIMAP::LoginJob::SslV2 ) 00317 || ( login->encryptionMode() == KIMAP::LoginJob::SslV3 ) 00318 || ( login->encryptionMode() == KIMAP::LoginJob::SslV3_1 ) 00319 || ( login->encryptionMode() == KIMAP::LoginJob::AnySslVersion ); 00320 } 00321 } 00322 00323 if ( state == Session::Disconnected && willUseSsl ) { 00324 startNext(); 00325 } 00326 } 00327 00328 void SessionPrivate::socketDisconnected() 00329 { 00330 if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) { 00331 logger->disconnectionOccured(); 00332 } 00333 00334 00335 if ( state != Session::Disconnected ) { 00336 emit q->connectionLost(); 00337 } 00338 00339 isSocketConnected = false; 00340 state = Session::Disconnected; 00341 thread->closeSocket(); 00342 00343 if ( currentJob ) { 00344 currentJob->connectionLost(); 00345 } 00346 } 00347 00348 void SessionPrivate::socketError() 00349 { 00350 //qWarning() << "Socket error occurred:" << socket->errorString(); 00351 if ( isSocketConnected ) 00352 socketDisconnected(); 00353 else 00354 emit q->connectionLost(); 00355 } 00356 00357 void SessionPrivate::startSsl(const KTcpSocket::SslVersion &version) 00358 { 00359 QMetaObject::invokeMethod( thread, "startSsl", Qt::QueuedConnection, Q_ARG(KTcpSocket::SslVersion, version) ); 00360 } 00361 00362 QString Session::selectedMailBox() const 00363 { 00364 return QString::fromUtf8( d->currentMailBox ); 00365 } 00366 00367 void SessionPrivate::onEncryptionNegotiationResult(bool isEncrypted, KTcpSocket::SslVersion version) 00368 { 00369 if ( isEncrypted ) { 00370 sslVersion = version; 00371 } else { 00372 sslVersion = KTcpSocket::UnknownSslVersion; 00373 } 00374 emit encryptionNegotiationResult( isEncrypted ); 00375 } 00376 00377 KTcpSocket::SslVersion SessionPrivate::negotiatedEncryption() const 00378 { 00379 return sslVersion; 00380 } 00381 00382 void SessionPrivate::setSocketTimeout( int ms ) 00383 { 00384 bool timerActive = socketTimer.isActive(); 00385 00386 if ( timerActive ) { 00387 stopSocketTimer(); 00388 } 00389 00390 socketTimerInterval = ms; 00391 00392 if ( timerActive ) { 00393 startSocketTimer(); 00394 } 00395 } 00396 00397 int SessionPrivate::socketTimeout() const 00398 { 00399 return socketTimerInterval; 00400 } 00401 00402 void SessionPrivate::startSocketTimer() 00403 { 00404 if ( socketTimerInterval<0 ) { 00405 return; 00406 } 00407 Q_ASSERT( !socketTimer.isActive() ); 00408 00409 connect( &socketTimer, SIGNAL(timeout()), 00410 this, SLOT(onSocketTimeout()) ); 00411 00412 socketTimer.setSingleShot( true ); 00413 socketTimer.start( socketTimerInterval ); 00414 } 00415 00416 void SessionPrivate::stopSocketTimer() 00417 { 00418 if ( socketTimerInterval<0 ) { 00419 return; 00420 } 00421 Q_ASSERT( socketTimer.isActive() ); 00422 00423 socketTimer.stop(); 00424 00425 disconnect( &socketTimer, SIGNAL(timeout()), 00426 this, SLOT(onSocketTimeout()) ); 00427 } 00428 00429 void SessionPrivate::restartSocketTimer() 00430 { 00431 stopSocketTimer(); 00432 startSocketTimer(); 00433 } 00434 00435 void SessionPrivate::onSocketTimeout() 00436 { 00437 kDebug(); 00438 socketDisconnected(); 00439 } 00440 00441 void Session::setTimeout( int timeout ) 00442 { 00443 d->setSocketTimeout( timeout * 1000 ); 00444 } 00445 00446 #include "session.moc" 00447 #include "session_p.moc"