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

KIMAP Library

  • 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/KLocale>
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, this );
58  connect( d->thread, SIGNAL(encryptionNegotiationResult(bool,KTcpSocket::SslVersion)),
59  d, SLOT(onEncryptionNegotiationResult(bool,KTcpSocket::SslVersion)) );
60  connect( d->thread, SIGNAL(sslError(KSslErrorUiData)), this, SLOT(handleSslError(KSslErrorUiData)) );
61 
62  d->startSocketTimer();
63  d->thread->start();
64 }
65 
66 Session::~Session()
67 {
68  delete d->thread;
69 }
70 
71 void Session::setUiProxy(SessionUiProxy::Ptr proxy)
72 {
73  d->uiProxy = proxy;
74 }
75 
76 void Session::setUiProxy(SessionUiProxy *proxy)
77 {
78  setUiProxy( SessionUiProxy::Ptr( proxy ) );
79 }
80 
81 QString Session::hostName() const
82 {
83  return d->thread->hostName();
84 }
85 
86 quint16 Session::port() const
87 {
88  return d->thread->port();
89 }
90 
91 Session::State Session::state() const
92 {
93  return d->state;
94 }
95 
96 QString Session::userName() const
97 {
98  return d->userName;
99 }
100 
101 QByteArray Session::serverGreeting() const
102 {
103  return d->greeting;
104 }
105 
106 int Session::jobQueueSize() const
107 {
108  return d->queue.size() + ( d->jobRunning ? 1 : 0 );
109 }
110 
111 void KIMAP::Session::close()
112 {
113  d->thread->closeSocket();
114 }
115 
116 void SessionPrivate::handleSslError(const KSslErrorUiData& errorData)
117 {
118  if ( uiProxy && uiProxy->ignoreSslError( errorData ) ) {
119  QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG( bool, true ) );
120  } else {
121  QMetaObject::invokeMethod( thread, "sslErrorHandlerResponse", Q_ARG( bool, false ) );
122  }
123 }
124 
125 SessionPrivate::SessionPrivate( Session *session )
126  : QObject( session ),
127  q( session ),
128  state( Session::Disconnected ),
129  logger( 0 ),
130  currentJob( 0 ),
131  tagCount( 0 ),
132  sslVersion( KTcpSocket::UnknownSslVersion ),
133  socketTimerInterval( 30000 ) // By default timeouts on 30s
134 {
135 }
136 
137 SessionPrivate::~SessionPrivate()
138 {
139  delete logger;
140 }
141 
142 void SessionPrivate::addJob(Job *job)
143 {
144  queue.append( job );
145  emit q->jobQueueSizeChanged( q->jobQueueSize() );
146 
147  QObject::connect( job, SIGNAL(result(KJob*)), q, SLOT(jobDone(KJob*)) );
148  QObject::connect( job, SIGNAL(destroyed(QObject*)), q, SLOT(jobDestroyed(QObject*)) );
149 
150  if ( state!=Session::Disconnected ) {
151  startNext();
152  }
153 }
154 
155 void SessionPrivate::startNext()
156 {
157  QTimer::singleShot( 0, q, SLOT(doStartNext()) );
158 }
159 
160 void SessionPrivate::doStartNext()
161 {
162  if ( queue.isEmpty() || jobRunning || !isSocketConnected ) {
163  return;
164  }
165 
166  startSocketTimer();
167  jobRunning = true;
168 
169  currentJob = queue.dequeue();
170  currentJob->doStart();
171 }
172 
173 void SessionPrivate::jobDone( KJob *job )
174 {
175  Q_UNUSED( job );
176  Q_ASSERT( job == currentJob );
177 
178  // If we're in disconnected state it's because we ended up
179  // here because the inactivity timer triggered, so no need to
180  // stop it (it is single shot)
181  if ( state!=Session::Disconnected ) {
182  stopSocketTimer();
183  }
184 
185  jobRunning = false;
186  currentJob = 0;
187  emit q->jobQueueSizeChanged( q->jobQueueSize() );
188  startNext();
189 }
190 
191 void SessionPrivate::jobDestroyed( QObject *job )
192 {
193  queue.removeAll( static_cast<KIMAP::Job*>( job ) );
194  if ( currentJob == job ) {
195  currentJob = 0;
196  }
197 }
198 
199 void SessionPrivate::responseReceived( const Message &response )
200 {
201  if ( logger && ( state == Session::Authenticated || state == Session::Selected ) ) {
202  logger->dataReceived( response.toString() );
203  }
204 
205  QByteArray tag;
206  QByteArray code;
207 
208  if ( response.content.size()>=1 ) {
209  tag = response.content[0].toString();
210  }
211 
212  if ( response.content.size()>=2 ) {
213  code = response.content[1].toString();
214  }
215 
216  switch ( state ) {
217  case Session::Disconnected:
218  if ( socketTimer.isActive() ) {
219  stopSocketTimer();
220  }
221  if ( code == "OK" ) {
222  setState( Session::NotAuthenticated );
223 
224  Message simplified = response;
225  simplified.content.removeFirst(); // Strip the tag
226  simplified.content.removeFirst(); // Strip the code
227  greeting = simplified.toString().trimmed(); // Save the server greeting
228 
229  startNext();
230  } else if ( code == "PREAUTH" ) {
231  setState( Session::Authenticated );
232 
233  Message simplified = response;
234  simplified.content.removeFirst(); // Strip the tag
235  simplified.content.removeFirst(); // Strip the code
236  greeting = simplified.toString().trimmed(); // Save the server greeting
237 
238  startNext();
239  } else {
240  thread->closeSocket();
241  }
242  return;
243  case Session::NotAuthenticated:
244  if ( code == "OK" && tag == authTag ) {
245  setState( Session::Authenticated );
246  }
247  break;
248  case Session::Authenticated:
249  if ( code == "OK" && tag == selectTag ) {
250  setState( Session::Selected );
251  currentMailBox = upcomingMailBox;
252  }
253  break;
254  case Session::Selected:
255  if ( ( code == "OK" && tag == closeTag ) ||
256  ( code != "OK" && tag == selectTag ) ) {
257  setState( Session::Authenticated );
258  currentMailBox = QByteArray();
259  } else if ( code == "OK" && tag == selectTag ) {
260  currentMailBox = upcomingMailBox;
261  }
262  break;
263  }
264 
265  if ( tag == authTag ) {
266  authTag.clear();
267  }
268  if ( tag == selectTag ) {
269  selectTag.clear();
270  }
271  if ( tag == closeTag ) {
272  closeTag.clear();
273  }
274 
275  // If a job is running forward it the response
276  if ( currentJob != 0 ) {
277  restartSocketTimer();
278  currentJob->handleResponse( response );
279  } else {
280  qWarning() << "A message was received from the server with no job to handle it:"
281  << response.toString()
282  << '(' + response.toString().toHex() + ')';
283  }
284 }
285 
286 void SessionPrivate::setState(Session::State s)
287 {
288  if ( s != state ) {
289  Session::State oldState = state;
290  state = s;
291  emit q->stateChanged( state, oldState );
292  }
293 }
294 
295 QByteArray SessionPrivate::sendCommand( const QByteArray &command, const QByteArray &args )
296 {
297  QByteArray tag = 'A' + QByteArray::number( ++tagCount ).rightJustified( 6, '0' );
298 
299  QByteArray payload = tag + ' ' + command;
300  if ( !args.isEmpty() ) {
301  payload += ' ' + args;
302  }
303 
304  sendData( payload );
305 
306  if ( command == "LOGIN" || command == "AUTHENTICATE" ) {
307  authTag = tag;
308  } else if ( command == "SELECT" || command == "EXAMINE" ) {
309  selectTag = tag;
310  upcomingMailBox = args;
311  upcomingMailBox.remove( 0, 1 );
312  upcomingMailBox.chop( 1 );
313  upcomingMailBox = KIMAP::decodeImapFolderName( upcomingMailBox );
314  } else if ( command == "CLOSE" ) {
315  closeTag = tag;
316  }
317  return tag;
318 }
319 
320 void SessionPrivate::sendData( const QByteArray &data )
321 {
322  restartSocketTimer();
323 
324  if ( logger && ( state == Session::Authenticated || state == Session::Selected ) ) {
325  logger->dataSent( data );
326  }
327 
328  thread->sendData( data + "\r\n" );
329 }
330 
331 void SessionPrivate::socketConnected()
332 {
333  stopSocketTimer();
334  isSocketConnected = true;
335 
336  bool willUseSsl = false;
337  if ( !queue.isEmpty() ) {
338  KIMAP::LoginJob *login = qobject_cast<KIMAP::LoginJob*>( queue.first() );
339  if ( login ) {
340  willUseSsl = ( login->encryptionMode() == KIMAP::LoginJob::SslV2 ) ||
341  ( login->encryptionMode() == KIMAP::LoginJob::SslV3 ) ||
342  ( login->encryptionMode() == KIMAP::LoginJob::SslV3_1 ) ||
343  ( login->encryptionMode() == KIMAP::LoginJob::AnySslVersion );
344 
345  userName = login->userName();
346  }
347  }
348 
349  if ( state == Session::Disconnected && willUseSsl ) {
350  startNext();
351  } else {
352  startSocketTimer();
353  }
354 }
355 
356 void SessionPrivate::socketDisconnected()
357 {
358  if ( socketTimer.isActive() ) {
359  stopSocketTimer();
360  }
361 
362  if ( logger && ( state == Session::Authenticated || state == Session::Selected ) ) {
363  logger->disconnectionOccured();
364  }
365 
366  if ( state != Session::Disconnected ) {
367  setState( Session::Disconnected );
368  emit q->connectionLost();
369  } else {
370  emit q->connectionFailed();
371  }
372 
373  isSocketConnected = false;
374 
375  clearJobQueue();
376 }
377 
378 void SessionPrivate::socketActivity()
379 {
380  restartSocketTimer();
381 }
382 
383 void SessionPrivate::socketError()
384 {
385  if ( socketTimer.isActive() ) {
386  stopSocketTimer();
387  }
388 
389  if ( isSocketConnected ) {
390  thread->closeSocket();
391  } else {
392  emit q->connectionFailed();
393  emit q->connectionLost(); // KDE5: Remove this. We shouldn't emit connectionLost() if we weren't connected in the first place
394  clearJobQueue();
395  }
396 }
397 
398 void SessionPrivate::clearJobQueue()
399 {
400  if ( currentJob ) {
401  currentJob->connectionLost();
402  } else if ( !queue.isEmpty() ) {
403  currentJob = queue.takeFirst();
404  currentJob->connectionLost();
405  }
406 
407  QQueue<Job*> queueCopy = queue; // copy because jobDestroyed calls removeAll
408  qDeleteAll(queueCopy);
409  queue.clear();
410  emit q->jobQueueSizeChanged( 0 );
411 }
412 
413 void SessionPrivate::startSsl(const KTcpSocket::SslVersion &version)
414 {
415  QMetaObject::invokeMethod( thread, "startSsl", Qt::QueuedConnection, Q_ARG( KTcpSocket::SslVersion, version ) );
416 }
417 
418 QString Session::selectedMailBox() const
419 {
420  return QString::fromUtf8( d->currentMailBox );
421 }
422 
423 void SessionPrivate::onEncryptionNegotiationResult(bool isEncrypted, KTcpSocket::SslVersion version)
424 {
425  if ( isEncrypted ) {
426  sslVersion = version;
427  } else {
428  sslVersion = KTcpSocket::UnknownSslVersion;
429  }
430  emit encryptionNegotiationResult( isEncrypted );
431 }
432 
433 KTcpSocket::SslVersion SessionPrivate::negotiatedEncryption() const
434 {
435  return sslVersion;
436 }
437 
438 void SessionPrivate::setSocketTimeout( int ms )
439 {
440  bool timerActive = socketTimer.isActive();
441 
442  if ( timerActive ) {
443  stopSocketTimer();
444  }
445 
446  socketTimerInterval = ms;
447 
448  if ( timerActive ) {
449  startSocketTimer();
450  }
451 }
452 
453 int SessionPrivate::socketTimeout() const
454 {
455  return socketTimerInterval;
456 }
457 
458 void SessionPrivate::startSocketTimer()
459 {
460  if ( socketTimerInterval < 0 ) {
461  return;
462  }
463  Q_ASSERT( !socketTimer.isActive() );
464 
465  connect( &socketTimer, SIGNAL(timeout()),
466  this, SLOT(onSocketTimeout()) );
467 
468  socketTimer.setSingleShot( true );
469  socketTimer.start( socketTimerInterval );
470 }
471 
472 void SessionPrivate::stopSocketTimer()
473 {
474  if ( socketTimerInterval < 0 ) {
475  return;
476  }
477 
478  socketTimer.stop();
479 
480  disconnect( &socketTimer, SIGNAL(timeout()),
481  this, SLOT(onSocketTimeout()) );
482 }
483 
484 void SessionPrivate::restartSocketTimer()
485 {
486  if ( socketTimer.isActive() ) {
487  stopSocketTimer();
488  }
489  startSocketTimer();
490 }
491 
492 void SessionPrivate::onSocketTimeout()
493 {
494  kDebug() << "Socket timeout!";
495  thread->closeSocket();
496 }
497 
498 void Session::setTimeout( int timeout )
499 {
500  d->setSocketTimeout( timeout * 1000 );
501 }
502 
503 #include "moc_session.cpp"
504 #include "moc_session_p.cpp"
This file is part of the KDE documentation.
Documentation copyright © 1996-2013 The KDE developers.
Generated on Sat Jul 13 2013 01:25:17 by doxygen 1.8.3.1 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-4.10.5 API Reference

Skip menu "kdepimlibs-4.10.5 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
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