• 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.12
  • kdepimlibs
  • kimap
sessionthread.cpp
1 /*
2  Copyright (c) 2009 Kevin Ottens <ervin@kde.org>
3 
4  This library is free software; you can redistribute it and/or modify it
5  under the terms of the GNU Library General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or (at your
7  option) any later version.
8 
9  This library is distributed in the hope that it will be useful, but WITHOUT
10  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
12  License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to the
16  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17  02110-1301, USA.
18 */
19 
20 #include "sessionthread_p.h"
21 
22 #include <QtCore/QDebug>
23 #include <QtCore/QThread>
24 
25 #include <KDE/KDebug>
26 
27 #include "imapstreamparser.h"
28 #include "message_p.h"
29 
30 using namespace KIMAP;
31 
32 Q_DECLARE_METATYPE( KTcpSocket::Error )
33 Q_DECLARE_METATYPE( KSslErrorUiData )
34 static const int _kimap_socketErrorTypeId = qRegisterMetaType<KTcpSocket::Error>();
35 static const int _kimap_sslErrorUiData = qRegisterMetaType<KSslErrorUiData>();
36 
37 SessionThread::SessionThread( const QString &hostName, quint16 port )
38  : QObject(), m_hostName( hostName ), m_port( port ),
39  m_socket( 0 ), m_stream( 0 ), m_mutex(),
40  m_encryptedMode( false ),
41  triedSslVersions( 0 ), doSslFallback( false )
42 {
43  // Just like the Qt docs now recommend, for event-driven threads:
44  // don't derive from QThread, create one directly and move the object to it.
45  QThread* thread = new QThread();
46  moveToThread( thread );
47  thread->start();
48  QMetaObject::invokeMethod( this, "threadInit" );
49 }
50 
51 SessionThread::~SessionThread()
52 {
53  QMetaObject::invokeMethod( this, "threadQuit" );
54  if ( !thread()->wait( 10 * 1000 ) ) {
55  kWarning() << "Session thread refuses to die, killing harder...";
56  thread()->terminate();
57  // Make sure to wait until it's done, otherwise it can crash when the pthread callback is called
58  thread()->wait();
59  }
60  delete thread();
61 }
62 
63 // Called in primary thread
64 void SessionThread::sendData( const QByteArray &payload )
65 {
66  QMutexLocker locker( &m_mutex );
67 
68  m_dataQueue.enqueue( payload );
69  QMetaObject::invokeMethod( this, "writeDataQueue" );
70 }
71 
72 // Called in secondary thread
73 void SessionThread::writeDataQueue()
74 {
75  Q_ASSERT( QThread::currentThread() == thread() );
76  if ( !m_socket )
77  return;
78  QMutexLocker locker( &m_mutex );
79 
80  while ( !m_dataQueue.isEmpty() ) {
81  m_socket->write( m_dataQueue.dequeue() );
82  }
83 }
84 
85 // Called in secondary thread
86 void SessionThread::readMessage()
87 {
88  Q_ASSERT( QThread::currentThread() == thread() );
89  if ( !m_stream || m_stream->availableDataSize() == 0 ) {
90  return;
91  }
92 
93  Message message;
94  QList<Message::Part> *payload = &message.content;
95 
96  try {
97  while ( !m_stream->atCommandEnd() ) {
98  if ( m_stream->hasString() ) {
99  QByteArray string = m_stream->readString();
100  if ( string == "NIL" ) {
101  *payload << Message::Part( QList<QByteArray>() );
102  } else {
103  *payload << Message::Part( string );
104  }
105  } else if ( m_stream->hasList() ) {
106  *payload << Message::Part( m_stream->readParenthesizedList() );
107  } else if ( m_stream->hasResponseCode() ) {
108  payload = &message.responseCode;
109  } else if ( m_stream->atResponseCodeEnd() ) {
110  payload = &message.content;
111  } else if ( m_stream->hasLiteral() ) {
112  QByteArray literal;
113  while ( !m_stream->atLiteralEnd() ) {
114  literal += m_stream->readLiteralPart();
115  }
116  *payload << Message::Part( literal );
117  } else {
118  // Oops! Something really bad happened, we won't be able to recover
119  // so close the socket immediately
120  qWarning( "Inconsistent state, probably due to some packet loss" );
121  doCloseSocket();
122  return;
123  }
124  }
125 
126  emit responseReceived( message );
127 
128  } catch ( KIMAP::ImapParserException e ) {
129  qWarning() << "The stream parser raised an exception:" << e.what();
130  }
131 
132  if ( m_stream->availableDataSize() > 1 ) {
133  QMetaObject::invokeMethod( this, "readMessage", Qt::QueuedConnection );
134  }
135 
136 }
137 
138 // Called in main thread
139 void SessionThread::closeSocket()
140 {
141  QMetaObject::invokeMethod( this, "doCloseSocket", Qt::QueuedConnection );
142 }
143 
144 // Called in secondary thread
145 void SessionThread::doCloseSocket()
146 {
147  Q_ASSERT( QThread::currentThread() == thread() );
148  if ( !m_socket )
149  return;
150  m_encryptedMode = false;
151  kDebug() << "close";
152  m_socket->close();
153 }
154 
155 // Called in secondary thread
156 void SessionThread::reconnect()
157 {
158  Q_ASSERT( QThread::currentThread() == thread() );
159  if ( m_socket == 0 ) // threadQuit already called
160  return;
161  if ( m_socket->state() != SessionSocket::ConnectedState &&
162  m_socket->state() != SessionSocket::ConnectingState ) {
163  if ( m_encryptedMode ) {
164  kDebug() << "connectToHostEncrypted" << m_hostName << m_port;
165  m_socket->connectToHostEncrypted( m_hostName, m_port );
166  } else {
167  kDebug() << "connectToHost" << m_hostName << m_port;
168  m_socket->connectToHost( m_hostName, m_port );
169  }
170  }
171 }
172 
173 // Called in secondary thread
174 void SessionThread::threadInit()
175 {
176  Q_ASSERT( QThread::currentThread() == thread() );
177  m_socket = new SessionSocket;
178  m_stream = new ImapStreamParser( m_socket );
179  connect( m_socket, SIGNAL(readyRead()),
180  this, SLOT(readMessage()), Qt::QueuedConnection );
181 
182  // Delay the call to slotSocketDisconnected so that it finishes disconnecting before we call reconnect()
183  connect( m_socket, SIGNAL(disconnected()),
184  this, SLOT(slotSocketDisconnected()), Qt::QueuedConnection );
185  connect( m_socket, SIGNAL(connected()),
186  this, SIGNAL(socketConnected()) );
187  connect( m_socket, SIGNAL(error(KTcpSocket::Error)),
188  this, SLOT(socketError(KTcpSocket::Error)) );
189  connect( m_socket, SIGNAL(bytesWritten(qint64)),
190  this, SIGNAL(socketActivity()) );
191  if ( m_socket->metaObject()->indexOfSignal( "encryptedBytesWritten(qint64)" ) > -1 ) {
192  connect( m_socket, SIGNAL(encryptedBytesWritten(qint64)), // needs kdelibs > 4.8
193  this, SIGNAL(socketActivity()) );
194  }
195  connect( m_socket, SIGNAL(readyRead()),
196  this, SIGNAL(socketActivity()) );
197 
198  QMetaObject::invokeMethod(this, "reconnect", Qt::QueuedConnection);
199 }
200 
201 // Called in secondary thread
202 void SessionThread::threadQuit()
203 {
204  Q_ASSERT( QThread::currentThread() == thread() );
205  delete m_stream;
206  m_stream = 0;
207  delete m_socket;
208  m_socket = 0;
209  thread()->quit();
210 }
211 
212 // Called in primary thread
213 void SessionThread::startSsl( KTcpSocket::SslVersion version )
214 {
215  QMetaObject::invokeMethod( this, "doStartSsl", Q_ARG(KTcpSocket::SslVersion, version) );
216 }
217 
218 // Called in secondary thread (via invokeMethod)
219 void SessionThread::doStartSsl( KTcpSocket::SslVersion version )
220 {
221  Q_ASSERT( QThread::currentThread() == thread() );
222  if ( !m_socket )
223  return;
224  if ( version == KTcpSocket::AnySslVersion ) {
225  doSslFallback = true;
226  if ( m_socket->advertisedSslVersion() == KTcpSocket::UnknownSslVersion ) {
227  m_socket->setAdvertisedSslVersion( KTcpSocket::AnySslVersion );
228  } else if ( !( triedSslVersions & KTcpSocket::TlsV1 ) ) {
229  triedSslVersions |= KTcpSocket::TlsV1;
230  m_socket->setAdvertisedSslVersion( KTcpSocket::TlsV1 );
231  } else if ( !( triedSslVersions & KTcpSocket::SslV3 ) ) {
232  triedSslVersions |= KTcpSocket::SslV3;
233  m_socket->setAdvertisedSslVersion( KTcpSocket::SslV3 );
234  } else if ( !( triedSslVersions & KTcpSocket::SslV2 ) ) {
235  triedSslVersions |= KTcpSocket::SslV2;
236  m_socket->setAdvertisedSslVersion( KTcpSocket::SslV2 );
237  doSslFallback = false;
238  }
239  } else {
240  m_socket->setAdvertisedSslVersion( version );
241  }
242 
243  m_socket->ignoreSslErrors();
244  connect( m_socket, SIGNAL(encrypted()), this, SLOT(sslConnected()) );
245  m_socket->startClientEncryption();
246 }
247 
248 // Called in secondary thread
249 void SessionThread::slotSocketDisconnected()
250 {
251  Q_ASSERT( QThread::currentThread() == thread() );
252  if ( doSslFallback ) {
253  reconnect();
254  } else {
255  emit socketDisconnected();
256  }
257 }
258 
259 // Called in secondary thread
260 void SessionThread::socketError(KTcpSocket::Error error)
261 {
262  Q_ASSERT( QThread::currentThread() == thread() );
263  if ( !m_socket )
264  return;
265  Q_UNUSED( error ); // can be used for debugging
266  if ( doSslFallback ) {
267  //do not call disconnectFromhost here, as that can trigger an error again.
268  m_socket->abort();
269  } else {
270  emit socketError();
271  }
272 }
273 
274 // Called in secondary thread
275 void SessionThread::sslConnected()
276 {
277  doSslFallback = false;
278  Q_ASSERT( QThread::currentThread() == thread() );
279  if ( !m_socket )
280  return;
281  KSslCipher cipher = m_socket->sessionCipher();
282 
283  if ( m_socket->sslErrors().count() > 0 ||
284  m_socket->encryptionMode() != KTcpSocket::SslClientMode ||
285  cipher.isNull() || cipher.usedBits() == 0 ) {
286  kDebug() << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull()
287  << ", cipher.usedBits() is" << cipher.usedBits()
288  << ", the socket says:" << m_socket->errorString()
289  << "and the list of SSL errors contains"
290  << m_socket->sslErrors().count() << "items.";
291  KSslErrorUiData errorData( m_socket );
292  emit sslError( errorData );
293  } else {
294  kDebug() << "TLS negotiation done.";
295  m_encryptedMode = true;
296  emit encryptionNegotiationResult( true, m_socket->negotiatedSslVersion() );
297  }
298 }
299 
300 void SessionThread::sslErrorHandlerResponse(bool response)
301 {
302  QMetaObject::invokeMethod(this, "doSslErrorHandlerResponse", Q_ARG(bool, response));
303 }
304 
305 // Called in secondary thread (via invokeMethod)
306 void SessionThread::doSslErrorHandlerResponse(bool response)
307 {
308  Q_ASSERT( QThread::currentThread() == thread() );
309  if ( !m_socket )
310  return;
311  if ( response ) {
312  m_encryptedMode = true;
313  emit encryptionNegotiationResult( true, m_socket->negotiatedSslVersion() );
314  } else {
315  m_encryptedMode = false;
316  //reconnect in unencrypted mode, so new commands can be issued
317  m_socket->disconnectFromHost();
318  m_socket->waitForDisconnected();
319  m_socket->connectToHost( m_hostName, m_port );
320  emit encryptionNegotiationResult( false, KTcpSocket::UnknownSslVersion );
321  }
322 }
323 
324 #include "moc_sessionthread_p.cpp"
KIMAP::ImapStreamParser
Parser for IMAP messages that operates on a local socket stream.
Definition: imapstreamparser.h:53
This file is part of the KDE documentation.
Documentation copyright © 1996-2014 The KDE developers.
Generated on Tue Oct 14 2014 23:00:08 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
  • kldap
  • kmbox
  • kmime
  • kpimidentities
  • kpimtextedit
  • kresources
  • ktnef
  • kxmlrpcclient
  • microblog

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