• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdepimlibs-4.9.4 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 void SessionPrivate::responseReceived( const Message &response )
199 {
200  if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) {
201  logger->dataReceived( response.toString() );
202  }
203 
204  QByteArray tag;
205  QByteArray code;
206 
207  if ( response.content.size()>=1 ) {
208  tag = response.content[0].toString();
209  }
210 
211  if ( response.content.size()>=2 ) {
212  code = response.content[1].toString();
213  }
214 
215  switch ( state ) {
216  case Session::Disconnected:
217  if (socketTimer.isActive()) {
218  stopSocketTimer();
219  }
220  if ( code=="OK" ) {
221  setState(Session::NotAuthenticated);
222 
223  Message simplified = response;
224  simplified.content.removeFirst(); // Strip the tag
225  simplified.content.removeFirst(); // Strip the code
226  greeting = simplified.toString().trimmed(); // Save the server greeting
227 
228  startNext();
229  } else if ( code=="PREAUTH" ) {
230  setState(Session::Authenticated);
231 
232  Message simplified = response;
233  simplified.content.removeFirst(); // Strip the tag
234  simplified.content.removeFirst(); // Strip the code
235  greeting = simplified.toString().trimmed(); // Save the server greeting
236 
237  startNext();
238  } else {
239  thread->closeSocket();
240  }
241  return;
242  case Session::NotAuthenticated:
243  if ( code=="OK" && tag==authTag ) {
244  setState(Session::Authenticated);
245  }
246  break;
247  case Session::Authenticated:
248  if ( code=="OK" && tag==selectTag ) {
249  setState(Session::Selected);
250  currentMailBox = upcomingMailBox;
251  }
252  break;
253  case Session::Selected:
254  if ( ( code=="OK" && tag==closeTag )
255  || ( code!="OK" && tag==selectTag) ) {
256  setState(Session::Authenticated);
257  currentMailBox = QByteArray();
258  } else if ( code=="OK" && tag==selectTag ) {
259  currentMailBox = upcomingMailBox;
260  }
261  break;
262  }
263 
264  if (tag==authTag) authTag.clear();
265  if (tag==selectTag) selectTag.clear();
266  if (tag==closeTag) closeTag.clear();
267 
268  // If a job is running forward it the response
269  if ( currentJob!=0 ) {
270  restartSocketTimer();
271  currentJob->handleResponse( response );
272  } else {
273  qWarning() << "A message was received from the server with no job to handle it:"
274  << response.toString()
275  << '('+response.toString().toHex()+')';
276  }
277 }
278 
279 void SessionPrivate::setState(Session::State s)
280 {
281  if (s != state) {
282  Session::State oldState = state;
283  state = s;
284  emit q->stateChanged(state, oldState);
285  }
286 }
287 
288 QByteArray SessionPrivate::sendCommand( const QByteArray &command, const QByteArray &args )
289 {
290  QByteArray tag = 'A' + QByteArray::number(++tagCount).rightJustified(6, '0');
291 
292  QByteArray payload = tag+' '+command;
293  if ( !args.isEmpty() ) {
294  payload+= ' '+args;
295  }
296 
297  sendData( payload );
298 
299  if ( command=="LOGIN" || command=="AUTHENTICATE" ) {
300  authTag = tag;
301  } else if ( command=="SELECT" || command=="EXAMINE" ) {
302  selectTag = tag;
303  upcomingMailBox = args;
304  upcomingMailBox.remove( 0, 1 );
305  upcomingMailBox.chop( 1 );
306  upcomingMailBox = KIMAP::decodeImapFolderName( upcomingMailBox );
307  } else if ( command=="CLOSE" ) {
308  closeTag = tag;
309  }
310 
311  return tag;
312 }
313 
314 void SessionPrivate::sendData( const QByteArray &data )
315 {
316  restartSocketTimer();
317 
318  if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) {
319  logger->dataSent( data );
320  }
321 
322  thread->sendData(data+"\r\n");
323 }
324 
325 void SessionPrivate::socketConnected()
326 {
327  stopSocketTimer();
328  isSocketConnected = true;
329 
330  bool willUseSsl = false;
331  if ( !queue.isEmpty() ) {
332  KIMAP::LoginJob *login = qobject_cast<KIMAP::LoginJob*>( queue.first() );
333  if ( login ) {
334  willUseSsl = ( login->encryptionMode() == KIMAP::LoginJob::SslV2 )
335  || ( login->encryptionMode() == KIMAP::LoginJob::SslV3 )
336  || ( login->encryptionMode() == KIMAP::LoginJob::SslV3_1 )
337  || ( login->encryptionMode() == KIMAP::LoginJob::AnySslVersion );
338 
339  userName = login->userName();
340  }
341  }
342 
343  if ( state == Session::Disconnected && willUseSsl ) {
344  startNext();
345  } else {
346  startSocketTimer();
347  }
348 }
349 
350 void SessionPrivate::socketDisconnected()
351 {
352  if (socketTimer.isActive()) {
353  stopSocketTimer();
354  }
355 
356  if ( logger && ( state==Session::Authenticated || state==Session::Selected ) ) {
357  logger->disconnectionOccured();
358  }
359 
360  if ( state != Session::Disconnected ) {
361  setState(Session::Disconnected);
362  emit q->connectionLost();
363  } else {
364  emit q->connectionFailed();
365  }
366 
367  isSocketConnected = false;
368 
369  clearJobQueue();
370 }
371 
372 void SessionPrivate::socketActivity()
373 {
374  restartSocketTimer();
375 }
376 
377 void SessionPrivate::socketError()
378 {
379  if (socketTimer.isActive()) {
380  stopSocketTimer();
381  }
382 
383  if ( isSocketConnected ) {
384  thread->closeSocket();
385  } else {
386  emit q->connectionFailed();
387  emit q->connectionLost(); // KDE5: Remove this. We shouldn't emit connectionLost() if we weren't connected in the first place
388  clearJobQueue();
389  }
390 }
391 
392 void SessionPrivate::clearJobQueue()
393 {
394  if ( currentJob ) {
395  currentJob->connectionLost();
396  } else if ( !queue.isEmpty() ) {
397  currentJob = queue.takeFirst();
398  currentJob->connectionLost();
399  }
400 
401  qDeleteAll(queue);
402  queue.clear();
403  emit q->jobQueueSizeChanged( 0 );
404 }
405 
406 void SessionPrivate::startSsl(const KTcpSocket::SslVersion &version)
407 {
408  QMetaObject::invokeMethod( thread, "startSsl", Qt::QueuedConnection, Q_ARG(KTcpSocket::SslVersion, version) );
409 }
410 
411 QString Session::selectedMailBox() const
412 {
413  return QString::fromUtf8( d->currentMailBox );
414 }
415 
416 void SessionPrivate::onEncryptionNegotiationResult(bool isEncrypted, KTcpSocket::SslVersion version)
417 {
418  if ( isEncrypted ) {
419  sslVersion = version;
420  } else {
421  sslVersion = KTcpSocket::UnknownSslVersion;
422  }
423  emit encryptionNegotiationResult( isEncrypted );
424 }
425 
426 KTcpSocket::SslVersion SessionPrivate::negotiatedEncryption() const
427 {
428  return sslVersion;
429 }
430 
431 void SessionPrivate::setSocketTimeout( int ms )
432 {
433  bool timerActive = socketTimer.isActive();
434 
435  if ( timerActive ) {
436  stopSocketTimer();
437  }
438 
439  socketTimerInterval = ms;
440 
441  if ( timerActive ) {
442  startSocketTimer();
443  }
444 }
445 
446 int SessionPrivate::socketTimeout() const
447 {
448  return socketTimerInterval;
449 }
450 
451 void SessionPrivate::startSocketTimer()
452 {
453  if ( socketTimerInterval<0 ) {
454  return;
455  }
456  Q_ASSERT( !socketTimer.isActive() );
457 
458  connect( &socketTimer, SIGNAL(timeout()),
459  this, SLOT(onSocketTimeout()) );
460 
461  socketTimer.setSingleShot( true );
462  socketTimer.start( socketTimerInterval );
463 }
464 
465 void SessionPrivate::stopSocketTimer()
466 {
467  if ( socketTimerInterval<0 ) {
468  return;
469  }
470 
471  socketTimer.stop();
472 
473  disconnect( &socketTimer, SIGNAL(timeout()),
474  this, SLOT(onSocketTimeout()) );
475 }
476 
477 void SessionPrivate::restartSocketTimer()
478 {
479  if ( socketTimer.isActive() ) {
480  stopSocketTimer();
481  }
482  startSocketTimer();
483 }
484 
485 void SessionPrivate::onSocketTimeout()
486 {
487  kDebug() << "Socket timeout!";
488  thread->closeSocket();
489 }
490 
491 void Session::setTimeout( int timeout )
492 {
493  d->setSocketTimeout( timeout * 1000 );
494 }
495 
496 #include "session.moc"
497 #include "session_p.moc"
This file is part of the KDE documentation.
Documentation copyright © 1996-2012 The KDE developers.
Generated on Tue Dec 11 2012 12:12:47 by doxygen 1.8.1.2 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.9.4 API Reference

Skip menu "kdepimlibs-4.9.4 API Reference"
  • akonadi
  •   contact
  •   kmime
  • kabc
  • kalarmcal
  • kblog
  • kcal
  • kcalcore
  • kcalutils
  • kholidays
  • kimap
  • kioslave
  •   imap4
  •   mbox
  •   nntp
  • kldap
  • kmbox
  • kmime
  • kontactinterface
  • kpimidentities
  • kpimtextedit
  •   richtextbuilders
  • 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