summaryrefslogtreecommitdiffstats
path: root/kopete/protocols/msn/dispatcher.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kopete/protocols/msn/dispatcher.cpp')
-rw-r--r--kopete/protocols/msn/dispatcher.cpp647
1 files changed, 647 insertions, 0 deletions
diff --git a/kopete/protocols/msn/dispatcher.cpp b/kopete/protocols/msn/dispatcher.cpp
new file mode 100644
index 00000000..8b8bbed6
--- /dev/null
+++ b/kopete/protocols/msn/dispatcher.cpp
@@ -0,0 +1,647 @@
+/*
+ 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"