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 | { |
42 | // Just like the Qt docs now recommend, for event-driven threads: |
43 | // don't derive from QThread, create one directly and move the object to it. |
44 | QThread* thread = new QThread(); |
45 | moveToThread( thread ); |
46 | thread->start(); |
47 | QMetaObject::invokeMethod( this, "threadInit" ); |
48 | } |
49 | |
50 | SessionThread::~SessionThread() |
51 | { |
52 | QMetaObject::invokeMethod( this, "threadQuit" ); |
53 | if ( !thread()->wait( 10 * 1000 ) ) { |
54 | kWarning() << "Session thread refuses to die, killing harder..." ; |
55 | thread()->terminate(); |
56 | // Make sure to wait until it's done, otherwise it can crash when the pthread callback is called |
57 | thread()->wait(); |
58 | } |
59 | delete thread(); |
60 | } |
61 | |
62 | // Called in primary thread |
63 | void SessionThread::sendData( const QByteArray &payload ) |
64 | { |
65 | QMutexLocker locker( &m_mutex ); |
66 | |
67 | m_dataQueue.enqueue( payload ); |
68 | QMetaObject::invokeMethod( this, "writeDataQueue" ); |
69 | } |
70 | |
71 | // Called in secondary thread |
72 | void SessionThread::writeDataQueue() |
73 | { |
74 | Q_ASSERT( QThread::currentThread() == thread() ); |
75 | if ( !m_socket ) |
76 | return; |
77 | QMutexLocker locker( &m_mutex ); |
78 | |
79 | while ( !m_dataQueue.isEmpty() ) { |
80 | m_socket->write( m_dataQueue.dequeue() ); |
81 | } |
82 | } |
83 | |
84 | // Called in secondary thread |
85 | void SessionThread::readMessage() |
86 | { |
87 | Q_ASSERT( QThread::currentThread() == thread() ); |
88 | if ( !m_stream || m_stream->availableDataSize() == 0 ) { |
89 | return; |
90 | } |
91 | |
92 | Message message; |
93 | QList<Message::Part> *payload = &message.content; |
94 | |
95 | try { |
96 | while ( !m_stream->atCommandEnd() ) { |
97 | if ( m_stream->hasString() ) { |
98 | QByteArray string = m_stream->readString(); |
99 | if ( string == "NIL" ) { |
100 | *payload << Message::Part( QList<QByteArray>() ); |
101 | } else { |
102 | *payload << Message::Part( string ); |
103 | } |
104 | } else if ( m_stream->hasList() ) { |
105 | *payload << Message::Part( m_stream->readParenthesizedList() ); |
106 | } else if ( m_stream->hasResponseCode() ) { |
107 | payload = &message.responseCode; |
108 | } else if ( m_stream->atResponseCodeEnd() ) { |
109 | payload = &message.content; |
110 | } else if ( m_stream->hasLiteral() ) { |
111 | QByteArray literal; |
112 | while ( !m_stream->atLiteralEnd() ) { |
113 | literal += m_stream->readLiteralPart(); |
114 | } |
115 | *payload << Message::Part( literal ); |
116 | } else { |
117 | // Oops! Something really bad happened, we won't be able to recover |
118 | // so close the socket immediately |
119 | qWarning( "Inconsistent state, probably due to some packet loss" ); |
120 | doCloseSocket(); |
121 | return; |
122 | } |
123 | } |
124 | |
125 | emit responseReceived( message ); |
126 | |
127 | } catch ( KIMAP::ImapParserException e ) { |
128 | qWarning() << "The stream parser raised an exception:" << e.what(); |
129 | } |
130 | |
131 | if ( m_stream->availableDataSize() > 1 ) { |
132 | QMetaObject::invokeMethod( this, "readMessage" , Qt::QueuedConnection ); |
133 | } |
134 | |
135 | } |
136 | |
137 | // Called in main thread |
138 | void SessionThread::closeSocket() |
139 | { |
140 | QMetaObject::invokeMethod( this, "doCloseSocket" , Qt::QueuedConnection ); |
141 | } |
142 | |
143 | // Called in secondary thread |
144 | void SessionThread::doCloseSocket() |
145 | { |
146 | Q_ASSERT( QThread::currentThread() == thread() ); |
147 | if ( !m_socket ) |
148 | return; |
149 | m_encryptedMode = false; |
150 | kDebug() << "close" ; |
151 | m_socket->close(); |
152 | } |
153 | |
154 | // Called in secondary thread |
155 | void SessionThread::reconnect() |
156 | { |
157 | Q_ASSERT( QThread::currentThread() == thread() ); |
158 | if ( m_socket == 0 ) // threadQuit already called |
159 | return; |
160 | if ( m_socket->state() != SessionSocket::ConnectedState && |
161 | m_socket->state() != SessionSocket::ConnectingState ) { |
162 | if ( m_encryptedMode ) { |
163 | kDebug() << "connectToHostEncrypted" << m_hostName << m_port; |
164 | m_socket->connectToHostEncrypted( m_hostName, m_port ); |
165 | } else { |
166 | kDebug() << "connectToHost" << m_hostName << m_port; |
167 | m_socket->connectToHost( m_hostName, m_port ); |
168 | } |
169 | } |
170 | } |
171 | |
172 | // Called in secondary thread |
173 | void SessionThread::threadInit() |
174 | { |
175 | Q_ASSERT( QThread::currentThread() == thread() ); |
176 | m_socket = new SessionSocket; |
177 | m_stream = new ImapStreamParser( m_socket ); |
178 | connect( m_socket, SIGNAL(readyRead()), |
179 | this, SLOT(readMessage()), Qt::QueuedConnection ); |
180 | |
181 | // Delay the call to slotSocketDisconnected so that it finishes disconnecting before we call reconnect() |
182 | connect( m_socket, SIGNAL(disconnected()), |
183 | this, SLOT(slotSocketDisconnected()), Qt::QueuedConnection ); |
184 | connect( m_socket, SIGNAL(connected()), |
185 | this, SIGNAL(socketConnected()) ); |
186 | connect( m_socket, SIGNAL(error(KTcpSocket::Error)), |
187 | this, SLOT(slotSocketError(KTcpSocket::Error)) ); |
188 | connect( m_socket, SIGNAL(bytesWritten(qint64)), |
189 | this, SIGNAL(socketActivity()) ); |
190 | if ( m_socket->metaObject()->indexOfSignal( "encryptedBytesWritten(qint64)" ) > -1 ) { |
191 | connect( m_socket, SIGNAL(encryptedBytesWritten(qint64)), // needs kdelibs > 4.8 |
192 | this, SIGNAL(socketActivity()) ); |
193 | } |
194 | connect( m_socket, SIGNAL(readyRead()), |
195 | this, SIGNAL(socketActivity()) ); |
196 | |
197 | QMetaObject::invokeMethod(this, "reconnect" , Qt::QueuedConnection); |
198 | } |
199 | |
200 | // Called in secondary thread |
201 | void SessionThread::threadQuit() |
202 | { |
203 | Q_ASSERT( QThread::currentThread() == thread() ); |
204 | delete m_stream; |
205 | m_stream = 0; |
206 | delete m_socket; |
207 | m_socket = 0; |
208 | thread()->quit(); |
209 | } |
210 | |
211 | // Called in primary thread |
212 | void SessionThread::startSsl( KTcpSocket::SslVersion version ) |
213 | { |
214 | QMetaObject::invokeMethod( this, "doStartSsl" , Q_ARG(KTcpSocket::SslVersion, version) ); |
215 | } |
216 | |
217 | // Called in secondary thread (via invokeMethod) |
218 | void SessionThread::doStartSsl( KTcpSocket::SslVersion version ) |
219 | { |
220 | Q_ASSERT( QThread::currentThread() == thread() ); |
221 | if ( !m_socket ) |
222 | return; |
223 | |
224 | m_socket->setAdvertisedSslVersion( version ); |
225 | m_socket->ignoreSslErrors(); |
226 | connect( m_socket, SIGNAL(encrypted()), this, SLOT(sslConnected()) ); |
227 | m_socket->startClientEncryption(); |
228 | } |
229 | |
230 | // Called in secondary thread |
231 | void SessionThread::slotSocketDisconnected() |
232 | { |
233 | Q_ASSERT( QThread::currentThread() == thread() ); |
234 | emit socketDisconnected(); |
235 | } |
236 | |
237 | // Called in secondary thread |
238 | void SessionThread::slotSocketError(KTcpSocket::Error error) |
239 | { |
240 | Q_ASSERT( QThread::currentThread() == thread() ); |
241 | if ( !m_socket ) |
242 | return; |
243 | Q_UNUSED( error ); // can be used for debugging |
244 | emit socketError(error); |
245 | } |
246 | |
247 | // Called in secondary thread |
248 | void SessionThread::sslConnected() |
249 | { |
250 | Q_ASSERT( QThread::currentThread() == thread() ); |
251 | if ( !m_socket ) |
252 | return; |
253 | KSslCipher cipher = m_socket->sessionCipher(); |
254 | |
255 | if ( m_socket->sslErrors().count() > 0 || |
256 | m_socket->encryptionMode() != KTcpSocket::SslClientMode || |
257 | cipher.isNull() || cipher.usedBits() == 0 ) { |
258 | kDebug() << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull() |
259 | << ", cipher.usedBits() is" << cipher.usedBits() |
260 | << ", the socket says:" << m_socket->errorString() |
261 | << "and the list of SSL errors contains" |
262 | << m_socket->sslErrors().count() << "items." ; |
263 | KSslErrorUiData errorData( m_socket ); |
264 | emit sslError( errorData ); |
265 | } else { |
266 | kDebug() << "TLS negotiation done." ; |
267 | m_encryptedMode = true; |
268 | emit encryptionNegotiationResult( true, m_socket->negotiatedSslVersion() ); |
269 | } |
270 | } |
271 | |
272 | void SessionThread::sslErrorHandlerResponse(bool response) |
273 | { |
274 | QMetaObject::invokeMethod(this, "doSslErrorHandlerResponse" , Q_ARG(bool, response)); |
275 | } |
276 | |
277 | // Called in secondary thread (via invokeMethod) |
278 | void SessionThread::doSslErrorHandlerResponse(bool response) |
279 | { |
280 | Q_ASSERT( QThread::currentThread() == thread() ); |
281 | if ( !m_socket ) |
282 | return; |
283 | if ( response ) { |
284 | m_encryptedMode = true; |
285 | emit encryptionNegotiationResult( true, m_socket->negotiatedSslVersion() ); |
286 | } else { |
287 | m_encryptedMode = false; |
288 | //reconnect in unencrypted mode, so new commands can be issued |
289 | m_socket->disconnectFromHost(); |
290 | m_socket->waitForDisconnected(); |
291 | m_socket->connectToHost( m_hostName, m_port ); |
292 | emit encryptionNegotiationResult( false, KTcpSocket::UnknownSslVersion ); |
293 | } |
294 | } |
295 | |
296 | #include "moc_sessionthread_p.cpp" |
297 | |