/* msnsocket.cpp - Base class for the sockets used in MSN Copyright (c) 2002-2003 by Martijn Klingens Copyright (c) 2002-2005 by Olivier Goffart Copyright (c) 2005 by Gregg Edghill Kopete (c) 2002-2005 by the Kopete developers Portions of this code are taken from KMerlin, (c) 2001 by Olaf Lueg ************************************************************************* * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ************************************************************************* */ #include "msnsocket.h" //#include "msnprotocol.h" #include #include #include #include #include #include #include #include #include #include #include "kopeteuiglobal.h" using namespace KNetwork; class MimeMessage { public: MimeMessage(const TQString &msg) : message(msg) {} TQString getValue(const TQString &key) { TQRegExp rx(key+": ([^\r\n]+)"); rx.search(message); return rx.cap(1); } private: TQString message; }; MSNSocket::MSNSocket(TQObject* parent) : TQObject (parent) { m_onlineStatus = Disconnected; m_socket = 0L; m_useHttp = false; m_timer = 0L; } MSNSocket::~MSNSocket() { //if ( m_onlineStatus != Disconnected ) // disconnect(); delete m_timer; m_timer = 0L; doneDisconnect(); if ( m_socket ) m_socket->deleteLater(); } void MSNSocket::connect( const TQString &server, uint port ) { if ( m_onlineStatus == Connected || m_onlineStatus == Connecting ) { kdWarning( 14140 ) << k_funcinfo << "Already connected or connecting! Not connecting again." << endl; return; } if( m_onlineStatus == Disconnecting ) { // Cleanup first. // FIXME: More generic!!! kdWarning( 14140 ) << k_funcinfo << "We're still disconnecting! Deleting socket the hard way first." << endl; delete m_socket; } setOnlineStatus( Connecting ); m_id = 0; //m_lastId = 0; m_waitBlockSize = 0; m_buffer = Buffer( 0 ); //m_sendQueue.clear(); m_server = server; m_port = port; if(!m_useHttp) m_socket = new KBufferedSocket( server, TQString::number(port) ); else { m_socket = new KBufferedSocket( m_gateway, "80" ); } m_socket->enableRead( true ); // enableWrite eats the CPU, and we only need it when the queue is // non-empty, so disable it until we have actual data in the queue m_socket->enableWrite( false ); TQObject::connect( m_socket, TQT_SIGNAL( readyRead() ), this, TQT_SLOT( slotDataReceived() ) ); TQObject::connect( m_socket, TQT_SIGNAL( readyWrite() ), this, TQT_SLOT( slotReadyWrite() ) ); TQObject::connect( m_socket, TQT_SIGNAL( hostFound() ), this, TQT_SLOT( slotHostFound() ) ); TQObject::connect( m_socket, TQT_SIGNAL( connected( const KResolverEntry&) ), this, TQT_SLOT( slotConnectionSuccess() ) ); TQObject::connect( m_socket, TQT_SIGNAL( gotError( int ) ), this, TQT_SLOT( slotSocketError( int ) ) ); TQObject::connect( m_socket, TQT_SIGNAL( closed( ) ), this, TQT_SLOT( slotSocketClosed( ) ) ); if(m_useHttp) { if(m_timer == 0L) { m_timer = new TQTimer(this, "Http poll timer"); // Connect the slot HttpPoll with the timer timeout signal. TQObject::connect(m_timer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotHttpPoll())); } } aboutToConnect(); // start the asynchronous connection m_socket->connect(); } void MSNSocket::disconnect() { if(m_useHttp) if(m_timer->isActive()) { // If the timer is still active, stop the timer. m_timer->stop(); } if ( m_socket ) m_socket->closeNow(); else slotSocketClosed(); } void MSNSocket::aboutToConnect() { /* Empty default implementation */ } void MSNSocket::doneConnect() { setOnlineStatus( Connected ); } void MSNSocket::doneDisconnect() { setOnlineStatus( Disconnected ); } void MSNSocket::setOnlineStatus( MSNSocket::OnlineStatus status ) { if ( m_onlineStatus == status ) return; m_onlineStatus = status; emit onlineStatusChanged( status ); } void MSNSocket::slotSocketError( int error ) { kdWarning( 14140 ) << k_funcinfo << "Error: " << error << " (" << m_socket->KSocketBase::errorString() << ")" << endl; if(!KSocketBase::isFatalError(error)) return; //we only care about fatal error TQString errormsg = i18n( "There was an error while connecting to the MSN server.\nError message:\n" ); if ( error == KSocketBase::LookupFailure ) errormsg += i18n( "Unable to lookup %1" ).tqarg( m_socket->peerResolver().nodeName() ); else errormsg += m_socket->KSocketBase::errorString() ; //delete m_socket; m_socket->deleteLater(); m_socket = 0L; setOnlineStatus( Disconnected ); emit connectionFailed(); //like if the socket is closed emit socketClosed(); emit errorMessage( ErrorConnectionError, errormsg ); } void MSNSocket::slotDataReceived() { int avail = m_socket->bytesAvailable(); if ( avail < 0 ) { // error! kdWarning( 14140 ) << k_funcinfo << "bytesAvailable() returned " << avail << ". This should not happen!" << endl << "Are we disconnected? Backtrace:" << endl << kdBacktrace() << endl; return; } // incoming data, plus an extra char where we pretend a NUL is so the conversion // to TQCString doesn't go over the end of the allocated memory. char *buffer = new char[ avail + 1 ]; int ret = m_socket->readBlock( buffer, avail ); if ( ret < 0 ) { kdWarning( 14140 ) << k_funcinfo << "readBlock() returned " << ret << "!" < 0) { // The packet contains the headers but does not contain the content data; // buffer the data received and read again. m_buffer.add(buffer, avail); delete[] buffer; // Update how much data remains. m_remaining = l; return; } } } } else { // Write the received data to the buffer. m_buffer.add(buffer, avail); m_remaining -= avail; if(m_remaining != 0) { // We have not received all the content data, read again. delete[] buffer; return; } // At this point, we have all the bytes returned from the web request. bytes = m_buffer.take(m_buffer.size()); } if(bytes.size() == 0) { // The response headers and the content came in one packet. bytes.assign(buffer, avail); } // Create the web response object from the response bytes. WebResponse response(bytes); if(response.getStatusCode() == 100) { return; } if(response.getStatusCode() == 200) { // If we received a valid response, read the required headers. // Retrieve the X-MSN-Messenger header. TQString header = response.getHeaders()->getValue("X-MSN-Messenger"); TQStringList parts = TQStringList::split(";", header.replace(" ", "")); if(!header.isNull() && (parts.count() >= 2)) { if(parts[0].find("SessionID", 0) != -1) { // Assign the session id. m_sessionId = parts[0].section("=", 1, 1); }else error = true; if(parts[1].find("GW-IP", 0) != -1) { // Assign the gateway IP address. m_gwip = parts[1].section("=", 1, 1); }else error = true; if(parts.count() > 2) if((parts[2].find("Session", 0) != -1) && (parts[2].section("=", 1, 1) == "close")) { // The http session has been closed by the server, disconnect. kdDebug(14140) << k_funcinfo << "Session closed." << endl; m_bCanPoll = false; disconnect(); return; } }else error = true; // Retrieve the content length header. header = response.getHeaders()->getValue("Content-Length"); if(!header.isNull()) { bool valid; int length = header.toInt(&valid); if(valid && (length == 0)) { // If the response content length is zero, there is nothing to do. m_pending = false; return; } if(valid && (length > 0)) { // Otherwise, if the content length is greater than zero, get the web response stream. TQDataStream *stream = response.getResponseStream(); buffer = new char[length]; // Read the web response content. stream->readRawBytes(buffer, length); ret = length; }else error = true; }else error = true; }else error = true; if(error) { kdDebug(14140) << k_funcinfo << "Http error: " << response.getStatusCode() << " " << response.getStatusDescription() << endl; // If we encountered an error, disconnect and return. m_bCanPoll = false; // Disconnect from the service. disconnect(); return; } } // Simple check to avoid dumping the binary data from the icons and emoticons to kdDebug: // all MSN commands start with one or more uppercase characters. // For now just check the first three chars, let's see how accurate it is. // Additionally, if we receive an MSN-P2P packet, strip off anything after the P2P header. rawData = TQString( TQCString( buffer, ((!m_useHttp)? avail : ret) + 1 ) ).stripWhiteSpace().replace( TQRegExp( "(P2P-Dest:.[a-zA-Z@.]*).*" ), "\\1\n\n(Stripped binary data)" ); bool isBinary = false; for ( uint i = 0; i < 3 ; ++i ) { if ( (rawData[ i ] < 'A' || rawData[ i ] > 'Z') && (rawData[ i ] < '0' || rawData[ i ] > '9') ) isBinary = true; } if ( isBinary ) kdDebug( 14141 ) << k_funcinfo << "(Stripped binary data)" << endl; else kdDebug( 14141 ) << k_funcinfo << rawData << endl; // fill the buffer with the received data m_buffer.add( buffer, ret ); slotReadLine(); if(m_useHttp) { // Set data pending to false. m_pending = false; } } // Cleanup. delete[] buffer; } void MSNSocket::slotReadLine() { // We have data, first check if it's meant for a block read, otherwise // parse the first line (which will recursively parse the other lines) if ( !pollReadBlock() ) { if ( m_buffer.size() >= 3 && ( m_buffer.data()[ 0 ] == '\0' || m_buffer.data()[ 0 ]== '\1' ) ) { bytesReceived( m_buffer.take( 3 ) ); TQTimer::singleShot( 0, this, TQT_SLOT( slotReadLine() ) ); return; } int index = -1; for ( uint x = 0; m_buffer.size() > x + 1; ++x ) { if ( ( m_buffer[ x ] == '\r' ) && ( m_buffer[ x + 1 ] == '\n' ) ) { index = x; break; } } if ( index != -1 ) { TQString command = TQString::fromUtf8( m_buffer.take( index + 2 ), index ); command.replace( "\r\n", "" ); //kdDebug( 14141 ) << k_funcinfo << command << endl; // Don't block the GUI while parsing data, only do a single line! // (Done before parseLine() to prevent a potential crash) TQTimer::singleShot( 0, this, TQT_SLOT( slotReadLine() ) ); parseLine( command ); // WARNING: At this point 'this' can be deleted (when disconnecting) } } } void MSNSocket::readBlock( uint len ) { if ( m_waitBlockSize ) { kdWarning( 14140 ) << k_funcinfo << "Cannot wait for data block: still waiting for other block of size " << m_waitBlockSize << "! Data will not be returned." << endl; return; } m_waitBlockSize = len; //kdDebug( 14140 ) << k_funcinfo << "Preparing for block read of size " << len << endl; // Try to return the data now, if available. Otherwise slotDataReady // will do this whenever all data is there. pollReadBlock(); } bool MSNSocket::pollReadBlock() { if ( !m_waitBlockSize ) { return false; } else if ( m_buffer.size() < m_waitBlockSize ) { kdDebug( 14140 ) << k_funcinfo << "Waiting for data. Received: " << m_buffer.size() << ", required: " << m_waitBlockSize << endl; return true; } TQByteArray block = m_buffer.take( m_waitBlockSize ); //kdDebug( 14140 ) << k_funcinfo << "Successfully read block of size " << m_waitBlockSize << endl; m_waitBlockSize = 0; emit blockRead( block); return false; } void MSNSocket::parseLine( const TQString &str ) { TQString cmd = str.section( ' ', 0, 0 ); TQString data = str.section( ' ', 2 ).replace( "\r\n" , "" ); bool isNum; uint id = str.section( ' ', 1, 1 ).toUInt( &isNum ); // In some rare cases, like the 'NLN' / 'FLN' commands no id at all // is sent. Here it's actually a real parameter... if ( !isNum ) data = str.section( ' ', 1, 1 ) + " " + data; //if ( isNum && id ) // m_lastId = id; //kdDebug( 14140 ) << k_funcinfo << "Parsing command " << cmd << " (ID " << id << "): '" << data << "'" << endl; data.replace( "\r\n", "" ); bool isError; uint errorCode = cmd.toUInt( &isError ); if ( isError ) handleError( errorCode, id ); else parseCommand( cmd, id, data ); } void MSNSocket::handleError( uint code, uint /* id */ ) { kdDebug(14140) << k_funcinfo << endl; TQString msg; ErrorType type = ErrorServerError; switch ( code ) { /* // We cant show message for error we don't know what they are or not related to the correct socket // Theses following messages are not so instructive case 205: msg = i18n ( "An invalid username has been specified.\nPlease correct it, and try to reconnect.\n" ); break; case 201: msg = i18n ( "Fully Qualified domain name missing.\n" ); break; case 207: msg = i18n ( "You are already logged in!\n" ); break; case 208: msg = i18n ( "You specified an invalid username.\nPlease correct it, and try to reconnect.\n"); break; case 209: msg = i18n ( "Your nickname is invalid. Please check it, correct it,\nand try to reconnect.\n" ); break; case 210: msg = i18n ( "Your list has reached its maximum capacity.\nNo more contacts can be added, unless you remove some first.\n" ); break; case 216: msg = i18n ( "This user is not in your contact list.\n " ); break; case 300: msg = i18n ( "Some required fields are missing. Please fill them in and try again.\n" ); break; case 302: msg = i18n ( "You are not logged in.\n" ); break; */ case 500: msg = i18n ( "An internal server error occurred. Please try again later." ); type = MSNSocket::ErrorCannotConnect; break; case 502: msg = i18n ( "It is no longer possible to perform this operation. The MSN server does not allow it anymore." ); type = MSNSocket::ErrorServerError; break; case 600: case 910: case 912: case 921: case 922: msg = i18n ( "The MSN server is busy. Please try again later." ); type = MSNSocket::ErrorConnectionError; break; case 601: case 604: case 605: case 914: case 915: case 916: case 917: msg = i18n ( "The server is not available at the moment. Please try again later." ); type = MSNSocket::ErrorCannotConnect; break; // Server error default: // FIXME: if the error causes a disconnect, it will crash, but we can't disconnect every time msg = i18n( "Unhandled MSN error code %1 \n" "Please fill a bug report with a detailed description and if possible the last console debug output." ).tqarg( code ); // "See http://www.hypothetic.org/docs/msn/basics.php for a description of all error codes." break; } if ( !msg.isEmpty() ) emit errorMessage( type, msg ); return; } int MSNSocket::sendCommand( const TQString &cmd, const TQString &args, bool addId, const TQByteArray &body, bool binary ) { if ( !m_socket ) { kdWarning( 14140 ) << k_funcinfo << "m_socket == NULL!" << endl; return -1; } TQCString data = cmd.utf8(); if ( addId ) data += " " + TQString::number( m_id ).utf8(); if ( !args.isEmpty() ) data += " " + args.utf8(); // Add length in bytes, not characters if ( !body.isEmpty() ) data += " " + TQString::number( body.size() - (binary ? 0 : 1 ) ).utf8(); data += "\r\n"; // the command will be sent in slotReadyWrite TQByteArray bytes; const uint length = data.length(); bytes.duplicate(data.data(), length); if(!body.isEmpty()) { uint l = body.size() - (binary ? 0 : 1); bytes.resize(length + l); for(uint i=0; i < l; i++) bytes[length + i] = body[i]; } // Add the request to the queue. m_sendQueue.append(bytes); m_socket->enableWrite(true); if ( addId ) { ++m_id; return m_id - 1; } return 0; } void MSNSocket::slotReadyWrite() { if ( !m_sendQueue.isEmpty() ) { // If the command queue is not empty, retrieve the first command. TQValueList::Iterator it = m_sendQueue.begin(); if(m_useHttp) { // If web response data is not pending, send the http request. if(!m_pending) { m_pending = true; // Temporarily disable http polling. m_bCanPoll = false; // Set the host to the msn gateway by default. TQString host = m_gateway; TQString query; // Web request query string. if(m_bIsFirstInTransaction) { query.append("Action=open&Server="); query.append(m_type); query += "&IP=" + m_server; m_bIsFirstInTransaction = false; } else { // If this is not the first request sent in the transaction, // only add the session Id. host = m_gwip; query += "SessionID=" + m_sessionId; } // Create the web request headers. TQString s = makeHttpRequestString(host, query, (*it).size()); uint length = s.length(); // Create the web request bytes. TQByteArray bytes(length + (*it).size()); // Copy the request headers into the request bytes. for(uint i=0; i < length; i++) bytes[i] = s.ascii()[i]; // Copy the request body into the request bytes. for(uint i=0; i < (*it).size(); i++) bytes[length + i] = (*it)[i]; kdDebug( 14141 ) << k_funcinfo << "Sending http command: " << TQString(*it).stripWhiteSpace() << endl; // Write the request bytes to the socket. m_socket->writeBlock(bytes.data(), bytes.size()); // Remove the request from the request queue. m_sendQueue.remove(it); if(m_sendQueue.isEmpty()) { // Disable sending requests. m_socket->enableWrite(false); // If the request queue is empty, poll the server. m_bCanPoll = true; } } } else { // Otherwise, send the command normally. // Simple check to avoid dumping the binary data from the icons and emoticons to kdDebug: // When sending an MSN-P2P packet, strip off anything after the P2P header. TQString debugData = TQString( *it ).stripWhiteSpace().replace( TQRegExp( "(P2P-Dest:.[a-zA-Z@.]*).*" ), "\\1\n\n(Stripped binary data)" ); kdDebug( 14141 ) << k_funcinfo << "Sending command: " << debugData << endl; m_socket->writeBlock( *it, ( *it ).size() ); m_sendQueue.remove( it ); // If the queue is empty agalin stop waiting for readyWrite signals // because of the CPU usage if ( m_sendQueue.isEmpty() ) m_socket->enableWrite( false ); } } else { m_socket->enableWrite( false ); if(m_useHttp) { // If the request queue is empty, poll the server. m_bCanPoll = true; } } } TQString MSNSocket::escape( const TQString &str ) { //return ( KURL::encode_string( str, 106 ) ); //It's not needed to encode everything. The official msn client only encode spaces and % //If we encode more, the size can be longer than excepted. int old_length= str.length(); TQChar *new_segment = new TQChar[ old_length * 3 + 1 ]; int new_length = 0; for ( int i = 0; i < old_length; i++ ) { unsigned short character = str[i].tqunicode(); if( character <= 32 || character == '%' ) { new_segment[ new_length++ ] = '%'; unsigned int c = character / 16; c += (c > 9) ? ('A' - 10) : '0'; new_segment[ new_length++ ] = c; c = character % 16; c += (c > 9) ? ('A' - 10) : '0'; new_segment[ new_length++ ] = c; } else new_segment[ new_length++ ] = str[i]; } TQString result = TQString(new_segment, new_length); delete [] new_segment; return result; } TQString MSNSocket::unescape( const TQString &str ) { TQString str2 = KURL::decode_string( str, 106 ); //remove msn+ colors code str2 = str2.replace( TQRegExp("[\\x1-\\x8]"), "" ); // old msn+ colors // added by kaoul str2 = str2.replace( TQRegExp("\\xB7[&@\'#0]"),""); // dot ... str2 = str2.replace( TQRegExp("\\xB7\\$,?\\d{1,2}"),""); // dot dollar (comma)? 0-99 return str2; } void MSNSocket::slotConnectionSuccess() { if(m_useHttp) { // If we are connected, set the data pending flag to false, // and disable http polling. m_pending = false; m_bCanPoll = false; // If we are connected, start the timer. m_timer->start(2000, false); } //kdDebug( 14140 ) << k_funcinfo << endl; doneConnect(); } void MSNSocket::slotHostFound() { // nothing to do } void MSNSocket::slotSocketClosed() { kdDebug( 14140 ) << k_funcinfo << "Socket closed. " << endl; if ( !m_socket || m_onlineStatus == Disconnected ) { kdDebug( 14140 ) << k_funcinfo << "Socket already deleted or already disconnected" << endl; return; } doneDisconnect(); m_buffer = Buffer( 0 ); //delete m_socket; m_socket->deleteLater(); m_socket = 0L; emit socketClosed(); } void MSNSocket::slotHttpPoll() { if(m_pending || !m_bCanPoll){ // If data is pending or poll has been temporary disabled, return. return; } // Create the http request headers. const TQCString headers = makeHttpRequestString(m_gwip, "Action=poll&SessionID=" + m_sessionId, 0).utf8(); m_socket->writeBlock(headers, headers.length()); // Wait for the response. m_pending = true; m_socket->enableWrite(true); } // Used in MSNFileTransferSocket // FIXME: Why is this here if it's only used for file transfer? - Martijn void MSNSocket::bytesReceived( const TQByteArray & /* data */ ) { kdWarning( 14140 ) << k_funcinfo << "Unknown bytes were received" << endl; } void MSNSocket::sendBytes( const TQByteArray &data ) { if ( !m_socket ) { kdWarning( 14140 ) << k_funcinfo << "Not yet connected" << endl; return; } m_socket->writeBlock( data, data.size() ); m_socket->enableWrite( true ); } bool MSNSocket::setUseHttpMethod( bool useHttp ) { if( m_useHttp == useHttp ) return true; if( useHttp ) { TQString s = TQString( this->className() ).lower(); if( s == "msnnotifysocket" ) m_type = "NS"; else if( s == "msnswitchboardsocket" ) m_type = "SB"; else m_type = TQString(); if( m_type.isNull() ) return false; m_bCanPoll = false; m_bIsFirstInTransaction = true; m_pending = false; m_remaining = 0; m_gateway = "gateway.messenger.hotmail.com"; } if ( m_onlineStatus != Disconnected ) disconnect(); m_useHttp = useHttp; return true; } bool MSNSocket::useHttpMethod() const { return m_useHttp; } bool MSNSocket::accept( KServerSocket *server ) { if ( m_socket ) { kdWarning( 14140 ) << k_funcinfo << "Socket already exists!" << endl; return false; } m_socket = static_cast(server->accept()); if ( !m_socket ) { // kdWarning( 14140 ) << k_funcinfo << "Socket not created. Error nb" << server->error() << " : " << server->errorString() << endl; return false; } kdDebug( 14140 ) << k_funcinfo << "incoming connection accepted" << endl; setOnlineStatus( Connecting ); m_id = 0; //m_lastId = 0; m_waitBlockSize = 0; m_socket->setBlocking( false ); m_socket->enableRead( true ); m_socket->enableWrite( true ); TQObject::connect( m_socket, TQT_SIGNAL( readyRead() ), this, TQT_SLOT( slotDataReceived() ) ); TQObject::connect( m_socket, TQT_SIGNAL( readyWrite() ), this, TQT_SLOT( slotReadyWrite() ) ); TQObject::connect( m_socket, TQT_SIGNAL( closed() ), this, TQT_SLOT( slotSocketClosed() ) ); TQObject::connect( m_socket, TQT_SIGNAL( gotError( int ) ), this, TQT_SLOT( slotSocketError( int ) ) ); doneConnect(); return true; } TQString MSNSocket::getLocalIP() { if ( !m_socket ) return TQString(); const KSocketAddress address = m_socket->localAddress(); TQString ip = address.nodeName(); kdDebug( 14140 ) << k_funcinfo << "IP: " << ip <getValue("Content-Length"); if(!header.isNull()) { bool valid; int length = header.toInt(&valid); if(valid && length > 0) { // If the content length is valid, and not zero, // copy the web response content bytes. int offset = bytes.size() - length; TQByteArray content(length); for(int i=0; i < length; i++) content[i] = bytes[offset + i]; // Create the web response stream from the response content bytes. m_stream = new TQDataStream(content, IO_ReadOnly); } } } MSNSocket::WebResponse::~WebResponse() { delete m_headers; m_headers = 0; delete m_stream; m_stream = 0; } MimeMessage* MSNSocket::WebResponse::getHeaders() { return m_headers; } TQDataStream* MSNSocket::WebResponse::getResponseStream() { return m_stream; } int MSNSocket::WebResponse::getStatusCode() { return m_statusCode; } TQString MSNSocket::WebResponse::getStatusDescription() { return m_statusDescription; } #include "msnsocket.moc" // vim: set noet ts=4 sts=4 sw=4: