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.
tdenetwork/kopete/protocols/msn/dispatcher.cpp

648 lines
20 KiB

/*
dispatcher.cpp - msn p2p protocol
Copyright (c) 2003-2005 by Olivier Goffart <ogoffart@ kde.org>
Copyright (c) 2005 by Gregg Edghill <gregg.edghill@gmail.com>
*************************************************************************
* *
* 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 "dispatcher.h"
#include "incomingtransfer.h"
#include "outgoingtransfer.h"
#if MSN_WEBCAM
#include "webcam.h"
#endif
using P2P::Dispatcher;
using P2P::Message;
using P2P::TransferContext;
using P2P::IncomingTransfer;
using P2P::OutgoingTransfer;
#include "msnswitchboardsocket.h"
// Kde includes
#include <kdebug.h>
#include <kmdcodec.h>
#include <kstandarddirs.h>
#include <ktempfile.h>
// Qt includes
#include <qdatastream.h>
#include <qfile.h>
#include <qregexp.h>
#include <qtextcodec.h>
#include <qtextstream.h>
// Kopete includes
#include <kopetechatsession.h> // Just for getting the contact
#include <kopeteaccount.h>
#include <kopetetransfermanager.h>
#include <stdlib.h>
Dispatcher::Dispatcher(QObject *parent, const QString& contact, const QStringList &ip)
: QObject(parent) , m_contact(contact) , m_callbackChannel(0l) , m_ip(ip)
{}
Dispatcher::~Dispatcher()
{
kdDebug(14140) << k_funcinfo << endl;
if(m_callbackChannel)
{
delete m_callbackChannel;
m_callbackChannel = 0l;
}
}
void Dispatcher::detach(TransferContext* transfer)
{
m_sessions.remove(transfer->m_sessionId);
transfer->deleteLater();
}
QString Dispatcher::localContact()
{
return m_contact;
}
void Dispatcher::requestDisplayIcon(const QString& from, const QString& msnObject)
{
Q_UINT32 sessionId = rand()%0xFFFFFF00 + 4;
TransferContext* current =
new IncomingTransfer(from, this, sessionId);
current->m_branch = P2P::Uid::createUid();
current->m_callId = P2P::Uid::createUid();
current->setType(P2P::UserDisplayIcon);
current->m_object = msnObject;
// Add the transfer to the list.
m_sessions.insert(sessionId, current);
kdDebug(14140) << k_funcinfo << "Requesting, " << msnObject << endl;
QString context = QString::fromUtf8(KCodecs::base64Encode(msnObject.utf8()));
// NOTE remove the \0 character automatically
// appended to a QCString.
context.replace("=", QString::null);
QString content =
"EUF-GUID: {A4268EEC-FEC5-49E5-95C3-F126696BDBF6}\r\n"
"SessionID: " + QString::number(sessionId) + "\r\n"
"AppID: 1\r\n"
"Context: " + context + "\r\n"
"\r\n";
// Send the sending client an invitation message.
current->sendMessage(INVITE, content);
}
void Dispatcher::sendFile(const QString& path, Q_INT64 fileSize, const QString& to)
{
// Create a new transfer context that will handle
// the file transfer.
Q_UINT32 sessionId = rand()%0xFFFFFF00 + 4;
TransferContext *current =
new OutgoingTransfer(to, this, sessionId);
current->m_branch = P2P::Uid::createUid();
current->m_callId = P2P::Uid::createUid();
current->setType(P2P::File);
// Add the transfer to the list.
m_sessions.insert(sessionId, current);
// Set the transfer context file.
current->m_file = new QFile(path);
// Create the file context data.
QString context;
QByteArray header(638);
header.fill('\0');
QDataStream writer(header, IO_WriteOnly);
writer.setByteOrder(QDataStream::LittleEndian);
// Write the header length to the stream.
writer << (Q_INT32)638;
// Write client version to the stream.
writer << (Q_INT32)3;
// Write the file size to the stream.
writer << fileSize;
// Write the file transfer flag to the stream.
// TODO support file preview. For now disable file preview.
writer << (Q_INT32)1;
// Write the file name in utf-16 to the stream.
QTextStream ts(header, IO_WriteOnly);
ts.setEncoding(QTextStream::RawUnicode);
ts.device()->at(20);
ts << path.section('/', -1);
// NOTE Background Sharing base64 [540..569]
// TODO add support for background sharing.
// Write file exchange type to the stream.
// NOTE File - 0xFFFFFFFF
// NOTE Background Sharing - 0xFFFFFFFE
writer.device()->at(570);
writer << (Q_UINT32)0xFFFFFFFF;
// Encode the file context header to base64 encoding.
context = QString::fromUtf8(KCodecs::base64Encode(header));
// Send an INVITE message to the recipient.
QString content = "EUF-GUID: {5D3E02AB-6190-11D3-BBBB-00C04F795683}\r\n"
"SessionID: " + QString::number(sessionId) + "\r\n"
"AppID: 2\r\n"
"Context: " + context + "\r\n"
"\r\n";
current->sendMessage(INVITE, content);
}
void Dispatcher::sendImage(const QString& /*fileName*/, const QString& /*to*/)
{
// TODO kdDebug(14140) << k_funcinfo << endl;
// QFile imageFile(fileName);
// if(!imageFile.open(IO_ReadOnly))
// {
// kdDebug(14140) << k_funcinfo << "Error opening image file."
// << endl;
// return;
// }
//
// OutgoingTransfer *outbound =
// new OutgoingTransfer(to, this, 64);
//
// outbound->sendImage(imageFile.readAll());
}
#if MSN_WEBCAM
void Dispatcher::startWebcam(const QString &/*myHandle*/, const QString &msgHandle, bool wantToReceive)
{
Q_UINT32 sessionId = rand()%0xFFFFFF00 + 4;
Webcam::Who who= wantToReceive ? Webcam::wViewer : Webcam::wProducer;
TransferContext* current =
new Webcam(who, msgHandle, this, sessionId);
current->m_branch = P2P::Uid::createUid();
current->m_callId = P2P::Uid::createUid();
current->setType(P2P::WebcamType);
// Add the transfer to the list.
m_sessions.insert(sessionId, current);
// {4BD96FC0-AB17-4425-A14A-439185962DC8} <- i want to show you my webcam
// {1C9AA97E-9C05-4583-A3BD-908A196F1E92} <- i want to see your webcam
QString GUID= (who==Webcam::wProducer) ? "4BD96FC0-AB17-4425-A14A-439185962DC8" : "1C9AA97E-9C05-4583-A3BD-908A196F1E92" ;
QString content="EUF-GUID: {"+GUID+"}\r\n"
"SessionID: "+ QString::number(sessionId)+"\r\n"
"AppID: 4\r\n"
"Context: ewBCADgAQgBFADcAMABEAEUALQBFADIAQwBBAC0ANAA0ADAAMAAtAEEARQAwADMALQA4ADgARgBGADgANQBCADkARgA0AEUAOAB9AA==\r\n\r\n";
// context is the base64 of the utf16 of {B8BE70DE-E2CA-4400-AE03-88FF85B9F4E8}
current->sendMessage( INVITE , content );
}
#endif
void Dispatcher::slotReadMessage(const QString &from, const QByteArray& stream)
{
P2P::Message receivedMessage =
m_messageFormatter.readMessage(stream);
receivedMessage.source = from;
if(receivedMessage.contentType == "application/x-msnmsgrp2p")
{
if((receivedMessage.header.dataSize == 0)/* && ((receivedMessage.header.flag & 0x02) == 0x02)*/)
{
TransferContext *current = 0l;
QMap<Q_UINT32, TransferContext*>::Iterator it = m_sessions.begin();
for(; it != m_sessions.end(); it++)
{
if(receivedMessage.header.ackSessionIdentifier == it.data()->m_identifier){
current = it.data();
break;
}
}
if(current){
// Inform the transfer object of the acknowledge.
current->m_ackSessionIdentifier = receivedMessage.header.identifier;
current->m_ackUniqueIdentifier = receivedMessage.header.ackSessionIdentifier;
current->acknowledged();
}
else
{
kdDebug(14140) << k_funcinfo
<< "no transfer context with identifier, "
<< receivedMessage.header.ackSessionIdentifier
<< endl;
}
return;
}
if(m_messageBuffer.contains(receivedMessage.header.identifier))
{
kdDebug(14140) << k_funcinfo
<< QString("retrieving buffered messsage, %1").arg(receivedMessage.header.identifier)
<< endl;
// The message was split, try to reconstruct the message
// with this received piece.
Message bufferedMessage = m_messageBuffer[receivedMessage.header.identifier];
// Remove the buffered message.
m_messageBuffer.remove(receivedMessage.header.identifier);
bufferedMessage.body.resize(bufferedMessage.body.size() + receivedMessage.header.dataSize);
for(Q_UINT32 i=0; i < receivedMessage.header.dataSize; i++){
// Add the remaining message data to the buffered message.
bufferedMessage.body[receivedMessage.header.dataOffset + i] = receivedMessage.body[i];
}
bufferedMessage.header.dataSize += receivedMessage.header.dataSize;
bufferedMessage.header.dataOffset = 0;
receivedMessage = bufferedMessage;
}
// Dispatch the received message.
dispatch(receivedMessage);
}
}
void Dispatcher::dispatch(const P2P::Message& message)
{
TransferContext *messageHandler = 0l;
if(message.header.sessionId > 0)
{
if(m_sessions.contains(message.header.sessionId)){
messageHandler = m_sessions[message.header.sessionId];
}
}
else
{
QString body =
QCString(message.body.data(), message.header.dataSize);
QRegExp regex("SessionID: ([0-9]*)\r\n");
if(regex.search(body) > 0)
{
Q_UINT32 sessionId = regex.cap(1).toUInt();
if(m_sessions.contains(sessionId)){
// Retrieve the message handler associated with the specified session Id.
messageHandler = m_sessions[sessionId];
}
}
else
{
// Otherwise, try to retrieve the message handler
// based on the acknowlegded unique identifier.
if(m_sessions.contains(message.header.ackUniqueIdentifier)){
messageHandler =
m_sessions[message.header.ackUniqueIdentifier];
}
if(!messageHandler)
{
// If the message handler still has not been found,
// try to retrieve the handler based on the call id.
regex = QRegExp("Call-ID: \\{([0-9A-F\\-]*)\\}\r\n");
regex.search(body);
QString callId = regex.cap(1);
TransferContext *current = 0l;
QMap<Q_UINT32, TransferContext*>::Iterator it = m_sessions.begin();
for(; it != m_sessions.end(); it++)
{
current = it.data();
if(current->m_callId == callId){
messageHandler = current;
break;
}
}
}
}
}
if(messageHandler){
// Process the received message using the
// retrieved registered handler.
messageHandler->m_ackSessionIdentifier = message.header.identifier;
messageHandler->m_ackUniqueIdentifier = message.header.ackSessionIdentifier;
messageHandler->processMessage(message);
}
else
{
// There are no objects registered, with the retrieved session Id,
// to handle the received message; default to this dispatcher.
if(message.header.totalDataSize > message.header.dataOffset + message.header.dataSize)
{
// The entire message has not been received;
// buffer the recevied portion of the original message.
kdDebug(14140) << k_funcinfo
<< QString("Buffering messsage, %1").arg(message.header.identifier)
<< endl;
m_messageBuffer.insert(message.header.identifier, message);
return;
}
QString body =
QCString(message.body.data(), message.header.dataSize);
kdDebug(14140) << k_funcinfo << "received, " << body << endl;
if(body.startsWith("INVITE"))
{
// Retrieve the branch, call id, and session id.
// These fields will be used later on in the p2p
// transaction.
QRegExp regex(";branch=\\{([0-9A-F\\-]*)\\}\r\n");
regex.search(body);
QString branch = regex.cap(1);
regex = QRegExp("Call-ID: \\{([0-9A-F\\-]*)\\}\r\n");
regex.search(body);
QString callId = regex.cap(1);
regex = QRegExp("SessionID: ([0-9]*)\r\n");
regex.search(body);
QString sessionId = regex.cap(1);
// Retrieve the contact that requested the session.
regex = QRegExp("From: <msnmsgr:([^>]*)>");
regex.search(body);
QString from = regex.cap(1);
// Retrieve the application identifier which
// is used to determine what type of session
// is being requested.
regex = QRegExp("AppID: ([0-9]*)\r\n");
regex.search(body);
Q_UINT32 applicationId = regex.cap(1).toUInt();
if(applicationId == 1 || applicationId == 11 || applicationId == 12 )
{ //the AppID is 12 since Messenger 7.5
// A contact has requested a session to download
// a display icon (User Display Icon or CustomEmotion).
regex = QRegExp("Context: ([0-9a-zA-Z+/=]*)");
regex.search(body);
QCString msnobj;
// Decode the msn object from base64 encoding.
KCodecs::base64Decode(regex.cap(1).utf8() , msnobj);
kdDebug(14140) << k_funcinfo << "Contact requested, "
<< msnobj << endl;
// Create a new transfer context that will handle
// the user display icon transfer.
TransferContext *current =
new OutgoingTransfer(from, this, sessionId.toUInt());
current->m_branch = branch;
current->m_callId = callId;
current->setType(P2P::UserDisplayIcon);
// Add the transfer to the list.
m_sessions.insert(sessionId.toUInt(), current);
// Determine the display icon being requested.
QString fileName = objectList.contains(msnobj)
? objectList[msnobj]
: m_pictureUrl;
QFile *source = new QFile(fileName);
// Try to open the source file for reading.
// If an error occurs, send an internal
// error message to the recipient.
if(!source->open(IO_ReadOnly))
{
current->error();
return;
}
current->m_file = source;
// Acknowledge the session request.
current->acknowledge(message);
current->m_ackSessionIdentifier = message.header.identifier;
current->m_ackUniqueIdentifier = message.header.ackSessionIdentifier;
// Send a 200 OK message to the recipient.
QString content = QString("SessionID: %1\r\n\r\n").arg(sessionId);
current->sendMessage(OK, content);
}
else if(applicationId == 2)
{
// A contact has requested a session to
// send a file.
kdDebug(14140) << k_funcinfo << "File transfer invitation." << endl;
// Create a new transfer context that will handle
// the file transfer.
TransferContext *transfer =
new IncomingTransfer(from, this, sessionId.toUInt());
transfer->m_branch = branch;
transfer->m_callId = callId;
transfer->setType(P2P::File);
// Add the transfer to the list.
m_sessions.insert(sessionId.toUInt(), transfer);
regex = QRegExp("Context: ([0-9a-zA-Z+/=]*)");
regex.search(body);
QByteArray context;
// Decode the file context from base64 encoding.
KCodecs::base64Decode(regex.cap(1).utf8(), context);
QDataStream reader(context, IO_ReadOnly);
reader.setByteOrder(QDataStream::LittleEndian);
//Retrieve the file info from the context field.
// File Size [8..15] Int64
reader.device()->at(8);
Q_INT64 fileSize;
reader >> fileSize;
// Flag [15..18] Int32
// 0x00 File transfer with preview data.
// 0x01 File transfer without preview data.
// 0x02 Background sharing.
Q_INT32 flag;
reader >> flag;
kdDebug(14140) << flag << endl;
// FileName UTF16 (Unicode) [19..539]
QByteArray bytes(520);
reader.readRawBytes(bytes.data(), bytes.size());
QTextStream ts(bytes, IO_ReadOnly);
ts.setEncoding(QTextStream::Unicode);
QString fileName;
fileName = ts.readLine().utf8();
emit incomingTransfer(from, fileName, fileSize);
kdDebug(14140) <<
QString("%1, %2 bytes.").arg(fileName, QString::number(fileSize))
<< endl
<< endl;
// Get the contact that is sending the file.
Kopete::Contact *contact = getContactByAccountId(from);
if(contact)
{
// Acknowledge the file invitation message.
transfer->acknowledge(message);
transfer->m_ackSessionIdentifier = message.header.identifier;
transfer->m_ackUniqueIdentifier = message.header.ackSessionIdentifier;
QObject::connect(Kopete::TransferManager::transferManager(), SIGNAL(accepted(Kopete::Transfer*, const QString&)), transfer, SLOT(slotTransferAccepted(Kopete::Transfer*, const QString&)));
QObject::connect(Kopete::TransferManager::transferManager(), SIGNAL(refused(const Kopete::FileTransferInfo&)), transfer, SLOT(slotTransferRefused(const Kopete::FileTransferInfo&)));
// Show the file transfer accept/decline dialog.
Kopete::TransferManager::transferManager()->askIncomingTransfer(contact, fileName, fileSize, QString::null, sessionId);
}
else
{
kdWarning(14140) << fileName << " from " << from
<< " has failed; could not retrieve contact from contact list."
<< endl;
transfer->m_ackSessionIdentifier = message.header.identifier;
transfer->m_ackUniqueIdentifier = message.header.ackSessionIdentifier;
transfer->sendMessage(ERROR);
}
}
else if(applicationId == 4)
{
#if MSN_WEBCAM
regex = QRegExp("EUF-GUID: \\{([0-9a-zA-Z\\-]*)\\}");
regex.search(body);
QString GUID=regex.cap(1);
kdDebug(14140) << k_funcinfo << "webcam " << GUID << endl;
Webcam::Who who;
if(GUID=="4BD96FC0-AB17-4425-A14A-439185962DC8")
{ //that mean "I want to send MY webcam"
who=Webcam::wViewer;
}
else if(GUID=="1C9AA97E-9C05-4583-A3BD-908A196F1E92")
{ //that mean "I want YOU to send YOUR webcam"
who=Webcam::wProducer;
}
else
{ //unknown GUID
//current->error();
kdWarning(14140) << k_funcinfo << "Unknown GUID " << GUID << endl;
return;
}
TransferContext *current = new P2P::Webcam(who, from, this, sessionId.toUInt());
current->m_branch = branch;
current->m_callId = callId;
// Add the transfer to the list.
m_sessions.insert(sessionId.toUInt(), current);
// Acknowledge the session request.
current->acknowledge(message);
QTimer::singleShot(0,current, SLOT(askIncommingInvitation()) );
#endif
}
}
else if(message.header.sessionId == 64)
{
// A contact has sent an inkformat (handwriting) gif.
// NOTE The entire message body is UTF16 encoded.
QString body = "";
for (Q_UINT32 i=0; i < message.header.totalDataSize; i++){
if (message.body[i] != QChar('\0')){
body += QChar(message.body[i]);
}
}
QRegExp regex("Content-Type: ([A-Za-z0-9$!*/\\-]*)");
regex.search(body);
QString contentType = regex.cap(1);
if(contentType == "image/gif")
{
IncomingTransfer transfer(message.source, this, message.header.sessionId);
transfer.acknowledge(message);
regex = QRegExp("base64:([0-9a-zA-Z+/=]*)");
regex.search(body);
QString base64 = regex.cap(1);
QByteArray image;
// Convert from base64 encoding to byte array.
KCodecs::base64Decode(base64.utf8(), image);
// Create a temporary file to store the image data.
KTempFile *ink = new KTempFile(locateLocal("tmp", "inkformatgif-" ), ".gif");
ink->setAutoDelete(true);
// Save the image data to disk.
ink->file()->writeBlock(image);
ink->file()->close();
displayIconReceived(ink, "inkformatgif");
ink = 0l;
}
}
}
}
void Dispatcher::messageAcknowledged(unsigned int correlationId, bool fullReceive)
{
if(fullReceive)
{
TransferContext *current = 0l;
QMap<Q_UINT32, TransferContext*>::Iterator it = m_sessions.begin();
for(; it != m_sessions.end(); it++)
{
current = it.data();
if(current->m_transactionId == correlationId)
{
// Inform the transfer object of the acknowledge.
current->readyWrite();
break;
}
}
}
}
Kopete::Contact* Dispatcher::getContactByAccountId(const QString& accountId)
{
Kopete::Contact *contact = 0l;
if(parent())
{
// Retrieve the contact from the current chat session context.
Kopete::ChatSession *session = dynamic_cast<Kopete::ChatSession*>(parent()->parent());
if(session)
{
contact = session->account()->contacts()[accountId];
session->setCanBeDeleted(false);
}
}
return contact;
}
Dispatcher::CallbackChannel::CallbackChannel(MSNSwitchBoardSocket *switchboard)
{
m_switchboard = switchboard;
}
Dispatcher::CallbackChannel::~CallbackChannel()
{}
Q_UINT32 Dispatcher::CallbackChannel::send(const QByteArray& stream)
{
return m_switchboard->sendCommand("MSG", "D", true, stream, true);
}
Dispatcher::CallbackChannel* Dispatcher::callbackChannel()
{
if(m_callbackChannel == 0l){
MSNSwitchBoardSocket *callback = dynamic_cast<MSNSwitchBoardSocket *>(parent());
if(callback == 0l) return 0l;
m_callbackChannel = new Dispatcher::CallbackChannel(callback);
}
return m_callbackChannel;
}
#include "dispatcher.moc"