• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdepimlibs API Reference
  • KDE Home
  • Contact Us
 

KIMAP Library

  • sources
  • kde-4.14
  • kdepimlibs
  • kimap
session.cpp
1 /*
2  Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
3 
4  Copyright (c) 2010 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
5  Author: Kevin Ottens <kevin@kdab.com>
6 
7  This library is free software; you can redistribute it and/or modify it
8  under the terms of the GNU Library General Public License as published by
9  the Free Software Foundation; either version 2 of the License, or (at your
10  option) any later version.
11 
12  This library is distributed in the hope that it will be useful, but WITHOUT
13  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
15  License for more details.
16 
17  You should have received a copy of the GNU Library General Public License
18  along with this library; see the file COPYING.LIB. If not, write to the
19  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  02110-1301, USA.
21 */
22 
23 #include "session.h"
24 #include "session_p.h"
25 #include "sessionuiproxy.h"
26 
27 #include <QtCore/QDebug>
28 #include <QtCore/QTimer>
29 
30 #include <KDebug>
31 #include <KDE/KLocalizedString>
32 
33 #include "job.h"
34 #include "loginjob.h"
35 #include "message_p.h"
36 #include "sessionlogger_p.h"
37 #include "sessionthread_p.h"
38 #include "rfccodecs.h"
39 
40 Q_DECLARE_METATYPE( KTcpSocket::SslVersion )
41 Q_DECLARE_METATYPE( QSslSocket::SslMode )
42 static const int _kimap_sslVersionId = qRegisterMetaType<KTcpSocket::SslVersion>();
43 
44 using namespace KIMAP;
45 
46 Session::Session( const QString &hostName, quint16 port, QObject *parent)
47  : QObject( parent ), d( new SessionPrivate( this ) )
48 {
49  if ( !qgetenv( "KIMAP_LOGFILE" ).isEmpty() ) {
50  d->logger = new SessionLogger;
51  }
52 
53  d->isSocketConnected = false;
54  d->state = Disconnected;
55  d->jobRunning = false;
56 
57  d->thread = new SessionThread( hostName, port );
58  connect( d->thread, SIGNAL(encryptionNegotiationResult(bool,KTcpSocket::SslVersion)),
59  d, SLOT(onEncryptionNegotiationResult(bool,KTcpSocket::SslVersion)) );
60  connect( d->thread, SIGNAL(sslError(KSslErrorUiData)),
61  d, SLOT(handleSslError(KSslErrorUiData)) );
62  connect( d->thread, SIGNAL(socketDisconnected()),
63  d, SLOT(socketDisconnected()) );
64  connect( d->thread, SIGNAL(responseReceived(KIMAP::Message)),
65  d, SLOT(responseReceived(KIMAP::Message)) );
66  connect( d->thread, SIGNAL(socketConnected()),
67  d, SLOT(socketConnected()) );
68  connect( d->thread, SIGNAL(socketActivity()),
69  d, SLOT(socketActivity()) );
70  connect( d->thread, SIGNAL(socketError(KTcpSocket::Error)),
71  d, SLOT(socketError(KTcpSocket::Error)) );
72 
73  d->socketTimer.setSingleShot( true );
74  connect( &d->socketTimer, SIGNAL(timeout()),
75  d, SLOT(onSocketTimeout()) );
76 
77  d->startSocketTimer();
78 }
79 
80 Session::~Session()
81 {
82  delete d->thread;
83  d->thread = 0;
84 }
85 
86 void Session::setUiProxy(SessionUiProxy::Ptr proxy)
87 {
88  d->uiProxy = proxy;
89 }
90 
91 void Session::setUiProxy(SessionUiProxy *proxy)
92 {
93  setUiProxy( SessionUiProxy::Ptr( proxy ) );
94 }
95 
96 QString Session::hostName() const
97 {
98  return d->thread->hostName();
99 }
100 
101 quint16 Session::port() const
102 {
103  return d->thread->port();
104 }
105 
106 Session::State Session::state() const
107 {
108  return d->state;
109 }
110 
111 QString Session::userName() const
112 {
113  return d->userName;
114 }
115 
116 QByteArray Session::serverGreeting() const
117 {
118  return d->greeting;
119 }
120 
121 int Session::jobQueueSize() const
122 {
123  return d->queue.size() + ( d->jobRunning ? 1 : 0 );
124 }
125 
126 void KIMAP::Session::close()
127 {
128  d->thread->closeSocket();
129 }
130 
131 void SessionPrivate::handleSslError(const KSslErrorUiData& errorData)
132 {
133  const bool ignoreSslError = uiProxy && uiProxy->ignoreSslError( errorData );
134  //ignoreSslError is async, so the thread might already be gone when it returns
135  if ( thread ) {
136  thread->sslErrorHandlerResponse(ignoreSslError);
137  }
138 }
139 
140 SessionPrivate::SessionPrivate( Session *session )
141  : QObject( session ),
142  q( session ),
143  state( Session::Disconnected ),
144  logger( 0 ),
145  currentJob( 0 ),
146  tagCount( 0 ),
147  sslVersion( KTcpSocket::UnknownSslVersion ),
148  socketTimerInterval( 30000 ) // By default timeouts on 30s
149 {
150 }
151 
152 SessionPrivate::~SessionPrivate()
153 {
154  delete logger;
155 }
156 
157 void SessionPrivate::addJob(Job *job)
158 {
159  queue.append( job );
160  emit q->jobQueueSizeChanged( q->jobQueueSize() );
161 
162  QObject::connect( job, SIGNAL(result(KJob*)), this, SLOT(jobDone(KJob*)) );
163  QObject::connect( job, SIGNAL(destroyed(QObject*)), this, SLOT(jobDestroyed(QObject*)) );
164 
165  if ( state != Session::Disconnected ) {
166  startNext();
167  }
168 }
169 
170 void SessionPrivate::startNext()
171 {
172  QMetaObject::invokeMethod( this, "doStartNext" );
173 }
174 
175 void SessionPrivate::doStartNext()
176 {
177  if ( queue.isEmpty() || jobRunning || !isSocketConnected ) {
178  return;
179  }
180 
181  restartSocketTimer();
182  jobRunning = true;
183 
184  currentJob = queue.dequeue();
185  currentJob->doStart();
186 }
187 
188 void SessionPrivate::jobDone( KJob *job )
189 {
190  Q_UNUSED( job );
191  Q_ASSERT( job == currentJob );
192 
193  stopSocketTimer();
194 
195  jobRunning = false;
196  currentJob = 0;
197  emit q->jobQueueSizeChanged( q->jobQueueSize() );
198  startNext();
199 }
200 
201 void SessionPrivate::jobDestroyed( QObject *job )
202 {
203  queue.removeAll( static_cast<KIMAP::Job*>( job ) );
204  if ( currentJob == job ) {
205  currentJob = 0;
206  }
207 }
208 
209 void SessionPrivate::responseReceived( const Message &response )
210 {
211  if ( logger && ( state == Session::Authenticated || state == Session::Selected ) ) {
212  logger->dataReceived( response.toString() );
213  }
214 
215  QByteArray tag;
216  QByteArray code;
217 
218  if ( response.content.size()>=1 ) {
219  tag = response.content[0].toString();
220  }
221 
222  if ( response.content.size()>=2 ) {
223  code = response.content[1].toString();
224  }
225 
226  // BYE may arrive as part of a LOGOUT sequence or before the server closes the connection after an error.
227  // In any case we should wait until the server closes the connection, so we don't have to do anything.
228  if ( code == "BYE" ) {
229  Message simplified = response;
230  if ( simplified.content.size() >= 2 ) {
231  simplified.content.removeFirst(); // Strip the tag
232  simplified.content.removeFirst(); // Strip the code
233  }
234  kDebug() << "Received BYE: " << simplified.toString();
235  return;
236  }
237 
238  switch ( state ) {
239  case Session::Disconnected:
240  if ( socketTimer.isActive() ) {
241  stopSocketTimer();
242  }
243  if ( code == "OK" ) {
244  setState( Session::NotAuthenticated );
245 
246  Message simplified = response;
247  simplified.content.removeFirst(); // Strip the tag
248  simplified.content.removeFirst(); // Strip the code
249  greeting = simplified.toString().trimmed(); // Save the server greeting
250 
251  startNext();
252  } else if ( code == "PREAUTH" ) {
253  setState( Session::Authenticated );
254 
255  Message simplified = response;
256  simplified.content.removeFirst(); // Strip the tag
257  simplified.content.removeFirst(); // Strip the code
258  greeting = simplified.toString().trimmed(); // Save the server greeting
259 
260  startNext();
261  } else {
262  thread->closeSocket();
263  }
264  return;
265  case Session::NotAuthenticated:
266  if ( code == "OK" && tag == authTag ) {
267  setState( Session::Authenticated );
268  }
269  break;
270  case Session::Authenticated:
271  if ( code == "OK" && tag == selectTag ) {
272  setState( Session::Selected );
273  currentMailBox = upcomingMailBox;
274  }
275  break;
276  case Session::Selected:
277  if ( ( code == "OK" && tag == closeTag ) ||
278  ( code != "OK" && tag == selectTag ) ) {
279  setState( Session::Authenticated );
280  currentMailBox = QByteArray();
281  } else if ( code == "OK" && tag == selectTag ) {
282  currentMailBox = upcomingMailBox;
283  }
284  break;
285  }
286 
287  if ( tag == authTag ) {
288  authTag.clear();
289  }
290  if ( tag == selectTag ) {
291  selectTag.clear();
292  }
293  if ( tag == closeTag ) {
294  closeTag.clear();
295  }
296 
297  // If a job is running forward it the response
298  if ( currentJob != 0 ) {
299  restartSocketTimer();
300  currentJob->handleResponse( response );
301  } else {
302  qWarning() << "A message was received from the server with no job to handle it:"
303  << response.toString()
304  << '(' + response.toString().toHex() + ')';
305  }
306 }
307 
308 void SessionPrivate::setState(Session::State s)
309 {
310  if ( s != state ) {
311  Session::State oldState = state;
312  state = s;
313  emit q->stateChanged( state, oldState );
314  }
315 }
316 
317 QByteArray SessionPrivate::sendCommand( const QByteArray &command, const QByteArray &args )
318 {
319  QByteArray tag = 'A' + QByteArray::number( ++tagCount ).rightJustified( 6, '0' );
320 
321  QByteArray payload = tag + ' ' + command;
322  if ( !args.isEmpty() ) {
323  payload += ' ' + args;
324  }
325 
326  sendData( payload );
327 
328  if ( command == "LOGIN" || command == "AUTHENTICATE" ) {
329  authTag = tag;
330  } else if ( command == "SELECT" || command == "EXAMINE" ) {
331  selectTag = tag;
332  upcomingMailBox = args;
333  upcomingMailBox.remove( 0, 1 );
334  upcomingMailBox = upcomingMailBox.left( upcomingMailBox.indexOf( '\"') );
335  upcomingMailBox = KIMAP::decodeImapFolderName( upcomingMailBox );
336  } else if ( command == "CLOSE" ) {
337  closeTag = tag;
338  }
339  return tag;
340 }
341 
342 void SessionPrivate::sendData( const QByteArray &data )
343 {
344  restartSocketTimer();
345 
346  if ( logger && ( state == Session::Authenticated || state == Session::Selected ) ) {
347  logger->dataSent( data );
348  }
349 
350  thread->sendData( data + "\r\n" );
351 }
352 
353 void SessionPrivate::socketConnected()
354 {
355  stopSocketTimer();
356  isSocketConnected = true;
357 
358  bool willUseSsl = false;
359  if ( !queue.isEmpty() ) {
360  KIMAP::LoginJob *login = qobject_cast<KIMAP::LoginJob*>( queue.first() );
361  if ( login ) {
362  willUseSsl = ( login->encryptionMode() == KIMAP::LoginJob::SslV2 ) ||
363  ( login->encryptionMode() == KIMAP::LoginJob::SslV3 ) ||
364  ( login->encryptionMode() == KIMAP::LoginJob::SslV3_1 ) ||
365  ( login->encryptionMode() == KIMAP::LoginJob::AnySslVersion );
366 
367  userName = login->userName();
368  }
369  }
370 
371  if ( state == Session::Disconnected && willUseSsl ) {
372  startNext();
373  } else {
374  startSocketTimer();
375  }
376 }
377 
378 void SessionPrivate::socketDisconnected()
379 {
380  if ( socketTimer.isActive() ) {
381  stopSocketTimer();
382  }
383 
384  if ( logger && ( state == Session::Authenticated || state == Session::Selected ) ) {
385  logger->disconnectionOccured();
386  }
387 
388  if ( state != Session::Disconnected ) {
389  setState( Session::Disconnected );
390  emit q->connectionLost();
391  } else {
392  emit q->connectionFailed();
393  }
394 
395  isSocketConnected = false;
396 
397  clearJobQueue();
398 }
399 
400 void SessionPrivate::socketActivity()
401 {
402  restartSocketTimer();
403 }
404 
405 void SessionPrivate::socketError(KTcpSocket::Error error)
406 {
407  if ( socketTimer.isActive() ) {
408  stopSocketTimer();
409  }
410 
411  if ( currentJob ) {
412  currentJob->setSocketError(error);
413  } else if ( !queue.isEmpty() ) {
414  currentJob = queue.takeFirst();
415  currentJob->setSocketError(error);
416  }
417 
418  if ( isSocketConnected ) {
419  thread->closeSocket();
420  } else {
421  emit q->connectionFailed();
422  emit q->connectionLost(); // KDE5: Remove this. We shouldn't emit connectionLost() if we weren't connected in the first place
423  clearJobQueue();
424  }
425 }
426 
427 void SessionPrivate::clearJobQueue()
428 {
429  if ( currentJob ) {
430  currentJob->connectionLost();
431  } else if ( !queue.isEmpty() ) {
432  currentJob = queue.takeFirst();
433  currentJob->connectionLost();
434  }
435 
436  QQueue<Job*> queueCopy = queue; // copy because jobDestroyed calls removeAll
437  qDeleteAll(queueCopy);
438  queue.clear();
439  emit q->jobQueueSizeChanged( 0 );
440 }
441 
442 void SessionPrivate::startSsl(const KTcpSocket::SslVersion &version)
443 {
444  thread->startSsl( version );
445 }
446 
447 QString Session::selectedMailBox() const
448 {
449  return QString::fromUtf8( d->currentMailBox );
450 }
451 
452 void SessionPrivate::onEncryptionNegotiationResult(bool isEncrypted, KTcpSocket::SslVersion version)
453 {
454  if ( isEncrypted ) {
455  sslVersion = version;
456  } else {
457  sslVersion = KTcpSocket::UnknownSslVersion;
458  }
459  emit encryptionNegotiationResult( isEncrypted );
460 }
461 
462 KTcpSocket::SslVersion SessionPrivate::negotiatedEncryption() const
463 {
464  return sslVersion;
465 }
466 
467 void SessionPrivate::setSocketTimeout( int ms )
468 {
469  bool timerActive = socketTimer.isActive();
470 
471  if ( timerActive ) {
472  stopSocketTimer();
473  }
474 
475  socketTimerInterval = ms;
476 
477  if ( timerActive ) {
478  startSocketTimer();
479  }
480 }
481 
482 int SessionPrivate::socketTimeout() const
483 {
484  return socketTimerInterval;
485 }
486 
487 void SessionPrivate::startSocketTimer()
488 {
489  if ( socketTimerInterval < 0 ) {
490  return;
491  }
492  Q_ASSERT( !socketTimer.isActive() );
493 
494  socketTimer.start( socketTimerInterval );
495 }
496 
497 void SessionPrivate::stopSocketTimer()
498 {
499  if ( socketTimerInterval < 0 ) {
500  return;
501  }
502 
503  socketTimer.stop();
504 }
505 
506 void SessionPrivate::restartSocketTimer()
507 {
508  if ( socketTimer.isActive() ) {
509  stopSocketTimer();
510  }
511  startSocketTimer();
512 }
513 
514 void SessionPrivate::onSocketTimeout()
515 {
516  kDebug() << "Socket timeout!";
517  thread->closeSocket();
518 }
519 
520 void Session::setTimeout( int timeout )
521 {
522  d->setSocketTimeout( timeout * 1000 );
523 }
524 
525 int Session::timeout() const
526 {
527  return d->socketTimeout() / 1000;
528 }
529 
530 #include "moc_session.cpp"
531 #include "moc_session_p.cpp"
rfccodecs.h
This file is part of the IMAP support library and defines the RfcCodecs class.
QQueue< Job * >
QByteArray
QByteArray::isEmpty
bool isEmpty() const
QObject::thread
QThread * thread() const
QString::fromUtf8
QString fromUtf8(const char *str, int size)
QObject
QByteArray::number
QByteArray number(int n, int base)
QString
QMetaObject::invokeMethod
bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType type, QGenericReturnArgument ret, QGenericArgument val0, QGenericArgument val1, QGenericArgument val2, QGenericArgument val3, QGenericArgument val4, QGenericArgument val5, QGenericArgument val6, QGenericArgument val7, QGenericArgument val8, QGenericArgument val9)
QByteArray::left
QByteArray left(int len) const
QSslSocket
KIMAP::SessionUiProxy
Interface to display communication errors and wait for user feedback.
Definition: sessionuiproxy.h:34
QByteArray::rightJustified
QByteArray rightJustified(int width, char fill, bool truncate) const
QByteArray::size
int size() const
QObject::connect
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QByteArray::remove
QByteArray & remove(int pos, int len)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:37:03 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

KIMAP Library

Skip menu "KIMAP Library"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • Related Pages

kdepimlibs API Reference

Skip menu "kdepimlibs API Reference"
  • akonadi
  •   contact
  •   kmime
  •   socialutils
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  • kpimutils
  • kresources
  • ktnef
  • kxmlrpcclient
  • mailtransport
  • microblog
  • qgpgme
  • syndication
  •   atom
  •   rdf
  •   rss2

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal