You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1100 lines
28 KiB
1100 lines
28 KiB
/*
|
|
msnsocket.cpp - Base class for the sockets used in MSN
|
|
|
|
Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org>
|
|
Copyright (c) 2002-2005 by Olivier Goffart <ogoffart at kde.org>
|
|
Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com>
|
|
|
|
Kopete (c) 2002-2005 by the Kopete developers <kopete-devel@kde.org>
|
|
|
|
Portions of this code are taken from KMerlin,
|
|
(c) 2001 by Olaf Lueg <olueg@olsd.de>
|
|
|
|
*************************************************************************
|
|
* *
|
|
* 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 <tqregexp.h>
|
|
#include <tqtimer.h>
|
|
|
|
#include <kdebug.h>
|
|
#include <kconfig.h>
|
|
#include <kbufferedsocket.h>
|
|
#include <kserversocket.h>
|
|
#include <kresolver.h>
|
|
#include <klocale.h>
|
|
#include <kmessagebox.h>
|
|
#include <kurl.h>
|
|
|
|
#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_onlinetqStatus = Disconnected;
|
|
m_socket = 0L;
|
|
m_useHttp = false;
|
|
m_timer = 0L;
|
|
}
|
|
|
|
MSNSocket::~MSNSocket()
|
|
{
|
|
//if ( m_onlinetqStatus != 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_onlinetqStatus == Connected || m_onlinetqStatus == Connecting )
|
|
{
|
|
kdWarning( 14140 ) << k_funcinfo << "Already connected or connecting! Not connecting again." << endl;
|
|
return;
|
|
}
|
|
|
|
if( m_onlinetqStatus == 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_onlinetqStatus == status )
|
|
return;
|
|
|
|
m_onlinetqStatus = 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 << "!" <<endl;
|
|
}
|
|
else if ( ret == 0 )
|
|
{
|
|
kdWarning( 14140 ) << k_funcinfo << "readBlock() returned no data!" <<endl;
|
|
}
|
|
else
|
|
{
|
|
if ( avail )
|
|
{
|
|
if ( ret != avail)
|
|
{
|
|
kdWarning( 14140 ) << k_funcinfo << avail << " bytes were reported available, "
|
|
<< "but readBlock() returned only " << ret << " bytes! Proceeding anyway." << endl;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
kdDebug( 14140 ) << k_funcinfo << "Read " << ret << " bytes into 4kb block." << endl;
|
|
}
|
|
|
|
|
|
TQString rawData;
|
|
|
|
if(m_useHttp)
|
|
{
|
|
bool error = false;
|
|
TQByteArray bytes;
|
|
|
|
// Check if all data has arrived.
|
|
rawData = TQString(TQCString(buffer, avail + 1));
|
|
bool headers = (rawData.find(TQRegExp("HTTP/\\d\\.\\d (\\d+) ([^\r\n]+)")) != -1);
|
|
|
|
if(headers)
|
|
{
|
|
// The http header packet arrived.
|
|
int endOfHeaders = rawData.find("\r\n\r\n");
|
|
if((endOfHeaders + 4) == avail)
|
|
{
|
|
// Only the response headers data is included.
|
|
TQRegExp re("Content-Length: ([^\r\n]+)");
|
|
if(re.search(rawData) != -1)
|
|
{
|
|
bool valid;
|
|
int l = re.cap(1).toInt(&valid);
|
|
if(valid && l > 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<TQByteArray>::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 <erwin.kwolek at gmail.com>
|
|
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_onlinetqStatus == 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_onlinetqStatus != 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<KBufferedSocket*>(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 <<endl;
|
|
//delete address;
|
|
return ip;
|
|
}
|
|
|
|
MSNSocket::Buffer::Buffer( unsigned int sz )
|
|
: TQByteArray( sz )
|
|
{
|
|
}
|
|
|
|
MSNSocket::Buffer::~Buffer()
|
|
{
|
|
}
|
|
|
|
void MSNSocket::Buffer::add( char *str, unsigned int sz )
|
|
{
|
|
char *b = new char[ size() + sz ];
|
|
for ( uint f = 0; f < size(); f++ )
|
|
b[ f ] = data()[ f ];
|
|
for ( uint f = 0; f < sz; f++ )
|
|
b[ size() + f ] = str[ f ];
|
|
|
|
duplicate( b, size() + sz );
|
|
delete[] b;
|
|
}
|
|
|
|
TQByteArray MSNSocket::Buffer::take( unsigned blockSize )
|
|
{
|
|
if ( size() < blockSize )
|
|
{
|
|
kdWarning( 14140 ) << k_funcinfo << "Buffer size " << size() << " < asked size " << blockSize << "!" << endl;
|
|
return TQByteArray();
|
|
}
|
|
|
|
TQByteArray rep( blockSize );
|
|
for( uint i = 0; i < blockSize; i++ )
|
|
rep[ i ] = data()[ i ];
|
|
|
|
char *str = new char[ size() - blockSize ];
|
|
for ( uint i = 0; i < size() - blockSize; i++ )
|
|
str[ i ] = data()[ blockSize + i ];
|
|
duplicate( str, size() - blockSize );
|
|
delete[] str;
|
|
|
|
return rep;
|
|
}
|
|
|
|
TQString MSNSocket::makeHttpRequestString(const TQString& host, const TQString& query, uint contentLength)
|
|
{
|
|
TQString s(
|
|
"POST http://" + host + "/gateway/gateway.dll?" + query + " HTTP/1.1\r\n" +
|
|
"Accept: */*\r\n" +
|
|
"Accept-Language: en-us\r\n" +
|
|
"User-Agent: MSMSGS\r\n" +
|
|
"Host: " + host + "\r\n" +
|
|
"Proxy-Connection: Keep-Alive\r\n" +
|
|
"Connection: Keep-Alive\r\n" +
|
|
"Pragma: no-cache\r\n" +
|
|
"Content-Type: application/x-msn-messenger\r\n" +
|
|
"Content-Length: " + TQString::number(contentLength) + "\r\n" +
|
|
"\r\n");
|
|
return s;
|
|
}
|
|
|
|
MSNSocket::WebResponse::WebResponse(const TQByteArray& bytes)
|
|
{
|
|
m_statusCode = 0;
|
|
m_stream = 0;
|
|
|
|
int headerEnd;
|
|
TQString header;
|
|
TQString data(TQCString(bytes, bytes.size() + 1));
|
|
|
|
// Parse the HTTP status header
|
|
TQRegExp re("HTTP/\\d\\.\\d (\\d+) ([^\r\n]+)");
|
|
headerEnd = data.find("\r\n");
|
|
header = data.left( (headerEnd == -1) ? 20 : headerEnd );
|
|
|
|
re.search(header);
|
|
m_statusCode = re.cap(1).toInt();
|
|
m_statusDescription = re.cap(2);
|
|
|
|
// Remove the web response status header.
|
|
data = data.mid(headerEnd + 2, (data.find("\r\n\r\n") + 2) - (headerEnd + 2));
|
|
// Create a MimeMessage, removing the HTTP status header
|
|
m_headers = new MimeMessage(data);
|
|
|
|
// Retrieve the contentlength header.
|
|
header = m_headers->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:
|
|
|