sessionthread.cpp
00001 /* 00002 Copyright (c) 2009 Kevin Ottens <ervin@kde.org> 00003 00004 This library is free software; you can redistribute it and/or modify it 00005 under the terms of the GNU Library General Public License as published by 00006 the Free Software Foundation; either version 2 of the License, or (at your 00007 option) any later version. 00008 00009 This library is distributed in the hope that it will be useful, but WITHOUT 00010 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 00011 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public 00012 License for more details. 00013 00014 You should have received a copy of the GNU Library General Public License 00015 along with this library; see the file COPYING.LIB. If not, write to the 00016 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 00017 02110-1301, USA. 00018 */ 00019 00020 #include "sessionthread_p.h" 00021 00022 #include <QtCore/QDebug> 00023 #include <QtCore/QTimer> 00024 00025 #include <KDE/KDebug> 00026 00027 #include "imapstreamparser.h" 00028 #include "message_p.h" 00029 #include "session.h" 00030 00031 using namespace KIMAP; 00032 00033 Q_DECLARE_METATYPE(KTcpSocket::Error) 00034 Q_DECLARE_METATYPE(KSslErrorUiData) 00035 static const int _kimap_socketErrorTypeId = qRegisterMetaType<KTcpSocket::Error>(); 00036 static const int _kimap_sslErrorUiData = qRegisterMetaType<KSslErrorUiData>(); 00037 00038 SessionThread::SessionThread( const QString &hostName, quint16 port, Session *parent ) 00039 : QThread(), m_hostName(hostName), m_port(port), 00040 m_session(parent), m_socket(0), m_stream(0), m_encryptedMode(false) 00041 { 00042 // Yeah, sounds weird, but QThread object is linked to the parent 00043 // thread not to itself, and I'm too lazy to introduce yet another 00044 // internal QObject 00045 moveToThread(this); 00046 } 00047 00048 SessionThread::~SessionThread() 00049 { 00050 // don't call quit() directly, this will deadlock in wait() if exec() hasn't run yet 00051 QMetaObject::invokeMethod( this, "quit" ); 00052 if ( !wait( 10 * 1000 ) ) { 00053 kWarning() << "Session thread refuses to die, killing harder..."; 00054 terminate(); 00055 // Make sure to wait until it's done, otherwise it can crash when the pthread callback is called 00056 wait(); 00057 } 00058 } 00059 00060 void SessionThread::sendData( const QByteArray &payload ) 00061 { 00062 QMutexLocker locker(&m_mutex); 00063 00064 m_dataQueue.enqueue( payload ); 00065 QTimer::singleShot( 0, this, SLOT(writeDataQueue()) ); 00066 } 00067 00068 void SessionThread::writeDataQueue() 00069 { 00070 QMutexLocker locker(&m_mutex); 00071 00072 while ( !m_dataQueue.isEmpty() ) { 00073 m_socket->write( m_dataQueue.dequeue() ); 00074 } 00075 } 00076 00077 void SessionThread::readMessage() 00078 { 00079 QMutexLocker locker(&m_mutex); 00080 00081 if ( m_stream->availableDataSize()==0 ) { 00082 return; 00083 } 00084 00085 Message message; 00086 QList<Message::Part> *payload = &message.content; 00087 00088 try { 00089 while ( !m_stream->atCommandEnd() ) { 00090 if ( m_stream->hasString() ) { 00091 QByteArray string = m_stream->readString(); 00092 if ( string == "NIL" ) { 00093 *payload << Message::Part( QList<QByteArray>() ); 00094 } else { 00095 *payload << Message::Part(string); 00096 } 00097 } else if ( m_stream->hasList() ) { 00098 *payload << Message::Part(m_stream->readParenthesizedList()); 00099 } else if ( m_stream->hasResponseCode() ) { 00100 payload = &message.responseCode; 00101 } else if ( m_stream->atResponseCodeEnd() ) { 00102 payload = &message.content; 00103 } else if ( m_stream->hasLiteral() ) { 00104 QByteArray literal; 00105 while ( !m_stream->atLiteralEnd() ) { 00106 literal+= m_stream->readLiteralPart(); 00107 } 00108 *payload << Message::Part(literal); 00109 } else { 00110 // Oops! Something really bad happened 00111 throw ImapParserException( "Inconsistent state, probably due to some packet loss" ); 00112 } 00113 } 00114 00115 emit responseReceived(message); 00116 00117 } catch (KIMAP::ImapParserException e) { 00118 qWarning() << "The stream parser raised an exception:" << e.what(); 00119 } 00120 00121 if ( m_stream->availableDataSize()>1 ) { 00122 QTimer::singleShot( 0, this, SLOT(readMessage()) ); 00123 } 00124 00125 } 00126 00127 void SessionThread::closeSocket() 00128 { 00129 QTimer::singleShot( 0, this, SLOT(doCloseSocket()) ); 00130 } 00131 00132 void SessionThread::doCloseSocket() 00133 { 00134 m_encryptedMode = false; 00135 m_socket->close(); 00136 } 00137 00138 void SessionThread::reconnect() 00139 { 00140 QMutexLocker locker(&m_mutex); 00141 00142 if ( m_socket->state() != SessionSocket::ConnectedState && 00143 m_socket->state() != SessionSocket::ConnectingState ) { 00144 if (m_encryptedMode) { 00145 m_socket->connectToHostEncrypted(m_hostName, m_port); 00146 } else { 00147 m_socket->connectToHost(m_hostName, m_port); 00148 } 00149 } 00150 } 00151 00152 void SessionThread::run() 00153 { 00154 m_socket = new SessionSocket; 00155 m_stream = new ImapStreamParser( m_socket ); 00156 connect( m_socket, SIGNAL(readyRead()), 00157 this, SLOT(readMessage()), Qt::QueuedConnection ); 00158 00159 connect( m_socket, SIGNAL(disconnected()), 00160 m_session, SLOT(socketDisconnected()) ); 00161 connect( m_socket, SIGNAL(connected()), 00162 m_session, SLOT(socketConnected()) ); 00163 connect( m_socket, SIGNAL(error(KTcpSocket::Error)), 00164 m_session, SLOT(socketError()) ); 00165 connect( m_socket, SIGNAL(bytesWritten(qint64)), 00166 m_session, SLOT(socketActivity()) ); 00167 if ( m_socket->metaObject()->indexOfSignal("encryptedBytesWritten(qint64)" ) > -1 ) { 00168 connect( m_socket, SIGNAL(encryptedBytesWritten(qint64)), // needs kdelibs > 4.8 00169 m_session, SLOT(socketActivity()) ); 00170 } 00171 connect( m_socket, SIGNAL(readyRead()), 00172 m_session, SLOT(socketActivity()) ); 00173 00174 connect( this, SIGNAL(responseReceived(KIMAP::Message)), 00175 m_session, SLOT(responseReceived(KIMAP::Message)) ); 00176 00177 QTimer::singleShot( 0, this, SLOT(reconnect()) ); 00178 exec(); 00179 00180 delete m_stream; 00181 delete m_socket; 00182 } 00183 00184 void SessionThread::startSsl(const KTcpSocket::SslVersion &version) 00185 { 00186 QMutexLocker locker(&m_mutex); 00187 00188 m_socket->setAdvertisedSslVersion(version); 00189 m_socket->ignoreSslErrors(); 00190 connect(m_socket, SIGNAL(encrypted()), this, SLOT(sslConnected())); 00191 m_socket->startClientEncryption(); 00192 } 00193 00194 void SessionThread::sslConnected() 00195 { 00196 QMutexLocker locker(&m_mutex); 00197 KSslCipher cipher = m_socket->sessionCipher(); 00198 00199 if ( m_socket->sslErrors().count() > 0 || m_socket->encryptionMode() != KTcpSocket::SslClientMode 00200 || cipher.isNull() || cipher.usedBits() == 0) { 00201 kDebug() << "Initial SSL handshake failed. cipher.isNull() is" << cipher.isNull() 00202 << ", cipher.usedBits() is" << cipher.usedBits() 00203 << ", the socket says:" << m_socket->errorString() 00204 << "and the list of SSL errors contains" 00205 << m_socket->sslErrors().count() << "items."; 00206 KSslErrorUiData errorData(m_socket); 00207 emit sslError(errorData); 00208 } else { 00209 kDebug() << "TLS negotiation done."; 00210 m_encryptedMode = true; 00211 emit encryptionNegotiationResult(true, m_socket->negotiatedSslVersion()); 00212 } 00213 } 00214 00215 void SessionThread::sslErrorHandlerResponse(bool response) 00216 { 00217 QMutexLocker locker(&m_mutex); 00218 if (response) { 00219 m_encryptedMode = true; 00220 emit encryptionNegotiationResult(true, m_socket->negotiatedSslVersion()); 00221 } else { 00222 m_encryptedMode = false; 00223 //reconnect in unencrypted mode, so new commands can be issued 00224 m_socket->disconnectFromHost(); 00225 m_socket->waitForDisconnected(); 00226 m_socket->connectToHost(m_hostName, m_port); 00227 emit encryptionNegotiationResult(false, KTcpSocket::UnknownSslVersion); 00228 } 00229 } 00230 00231 #include "sessionthread_p.moc" 00232