/* dispatcher.cpp - msn p2p protocol Copyright (c) 2003-2005 by Olivier Goffart Copyright (c) 2005 by Gregg Edghill ************************************************************************* * * * 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 #include #include #include // TQt includes #include #include #include #include #include // Kopete includes #include // Just for getting the contact #include #include #include Dispatcher::Dispatcher(TQObject *parent, const TQString& contact, const TQStringList &ip) : TQObject(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(); } TQString Dispatcher::localContact() { return m_contact; } void Dispatcher::requestDisplayIcon(const TQString& from, const TQString& msnObject) { TQ_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; TQString context = TQString::fromUtf8(KCodecs::base64Encode(msnObject.utf8())); // NOTE remove the \0 character automatically // appended to a TQCString. context.replace("=", TQString()); TQString content = "EUF-GUID: {A4268EEC-FEC5-49E5-95C3-F126696BDBF6}\r\n" "SessionID: " + TQString::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 TQString& path, TQ_INT64 fileSize, const TQString& to) { // Create a new transfer context that will handle // the file transfer. TQ_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 TQFile(path); // Create the file context data. TQString context; TQByteArray header(638); header.fill('\0'); TQDataStream writer(header, IO_WriteOnly); writer.setByteOrder(TQDataStream::LittleEndian); // Write the header length to the stream. writer << (TQ_INT32)638; // Write client version to the stream. writer << (TQ_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 << (TQ_INT32)1; // Write the file name in utf-16 to the stream. TQTextStream ts(header, IO_WriteOnly); ts.setEncoding(TQTextStream::RawUnicode); ts.tqdevice()->tqat(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.tqdevice()->tqat(570); writer << (TQ_UINT32)0xFFFFFFFF; // Encode the file context header to base64 encoding. context = TQString::fromUtf8(KCodecs::base64Encode(header)); // Send an INVITE message to the recipient. TQString content = "EUF-GUID: {5D3E02AB-6190-11D3-BBBB-00C04F795683}\r\n" "SessionID: " + TQString::number(sessionId) + "\r\n" "AppID: 2\r\n" "Context: " + context + "\r\n" "\r\n"; current->sendMessage(INVITE, content); } void Dispatcher::sendImage(const TQString& /*fileName*/, const TQString& /*to*/) { // TODO kdDebug(14140) << k_funcinfo << endl; // TQFile 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 TQString &/*myHandle*/, const TQString &msgHandle, bool wantToReceive) { TQ_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 TQString GUID= (who==Webcam::wProducer) ? "4BD96FC0-AB17-4425-A14A-439185962DC8" : "1C9AA97E-9C05-4583-A3BD-908A196F1E92" ; TQString content="EUF-GUID: {"+GUID+"}\r\n" "SessionID: "+ TQString::number(sessionId)+"\r\n" "AppID: 4\r\n" "Context: ewBCADgAQgBFADcAMABEAEUALTQBFADIAQwBBAC0ANAA0ADAAMAAtAEEARTQAwADMALQA4ADgARgBGADgANTQBCADkARgA0AEUAOAB9AA==\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 TQString &from, const TQByteArray& 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; TQMap::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 << TQString("retrieving buffered messsage, %1").tqarg(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(TQ_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 { TQString body = TQCString(message.body.data(), message.header.dataSize); TQRegExp regex("SessionID: ([0-9]*)\r\n"); if(regex.search(body) > 0) { TQ_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 = TQRegExp("Call-ID: \\{([0-9A-F\\-]*)\\}\r\n"); regex.search(body); TQString callId = regex.cap(1); TransferContext *current = 0l; TQMap::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 << TQString("Buffering messsage, %1").tqarg(message.header.identifier) << endl; m_messageBuffer.insert(message.header.identifier, message); return; } TQString body = TQCString(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. TQRegExp regex(";branch=\\{([0-9A-F\\-]*)\\}\r\n"); regex.search(body); TQString branch = regex.cap(1); regex = TQRegExp("Call-ID: \\{([0-9A-F\\-]*)\\}\r\n"); regex.search(body); TQString callId = regex.cap(1); regex = TQRegExp("SessionID: ([0-9]*)\r\n"); regex.search(body); TQString sessionId = regex.cap(1); // Retrieve the contact that requested the session. regex = TQRegExp("From: ]*)>"); regex.search(body); TQString from = regex.cap(1); // Retrieve the application identifier which // is used to determine what type of session // is being requested. regex = TQRegExp("AppID: ([0-9]*)\r\n"); regex.search(body); TQ_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 = TQRegExp("Context: ([0-9a-zA-Z+/=]*)"); regex.search(body); TQCString 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. TQString fileName = objectList.contains(msnobj) ? objectList[msnobj] : m_pictureUrl; TQFile *source = new TQFile(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. TQString content = TQString("SessionID: %1\r\n\r\n").tqarg(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 = TQRegExp("Context: ([0-9a-zA-Z+/=]*)"); regex.search(body); TQByteArray context; // Decode the file context from base64 encoding. KCodecs::base64Decode(regex.cap(1).utf8(), context); TQDataStream reader(context, IO_ReadOnly); reader.setByteOrder(TQDataStream::LittleEndian); //Retrieve the file info from the context field. // File Size [8..15] Int64 reader.tqdevice()->tqat(8); TQ_INT64 fileSize; reader >> fileSize; // Flag [15..18] Int32 // 0x00 File transfer with preview data. // 0x01 File transfer without preview data. // 0x02 Background sharing. TQ_INT32 flag; reader >> flag; kdDebug(14140) << flag << endl; // FileName UTF16 (Unicode) [19..539] TQByteArray bytes(520); reader.readRawBytes(bytes.data(), bytes.size()); TQTextStream ts(bytes, IO_ReadOnly); ts.setEncoding(TQTextStream::Unicode); TQString fileName; fileName = ts.readLine().utf8(); emit incomingTransfer(from, fileName, fileSize); kdDebug(14140) << TQString("%1, %2 bytes.").tqarg(fileName, TQString::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; TQObject::connect(Kopete::TransferManager::transferManager(), TQT_SIGNAL(accepted(Kopete::Transfer*, const TQString&)), transfer, TQT_SLOT(slotTransferAccepted(Kopete::Transfer*, const TQString&))); TQObject::connect(Kopete::TransferManager::transferManager(), TQT_SIGNAL(refused(const Kopete::FileTransferInfo&)), transfer, TQT_SLOT(slotTransferRefused(const Kopete::FileTransferInfo&))); // Show the file transfer accept/decline dialog. Kopete::TransferManager::transferManager()->askIncomingTransfer(contact, fileName, fileSize, TQString(), 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 = TQRegExp("EUF-GUID: \\{([0-9a-zA-Z\\-]*)\\}"); regex.search(body); TQString 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); TQTimer::singleShot(0,current, TQT_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. TQString body = ""; for (TQ_UINT32 i=0; i < message.header.totalDataSize; i++){ if (message.body[i] != TQChar('\0')){ body += TQChar(message.body[i]); } } TQRegExp regex("Content-Type: ([A-Za-z0-9$!*/\\-]*)"); regex.search(body); TQString contentType = regex.cap(1); if(contentType == "image/gif") { IncomingTransfer transfer(message.source, this, message.header.sessionId); transfer.acknowledge(message); regex = TQRegExp("base64:([0-9a-zA-Z+/=]*)"); regex.search(body); TQString base64 = regex.cap(1); TQByteArray 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; TQMap::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 TQString& accountId) { Kopete::Contact *contact = 0l; if(parent()) { // Retrieve the contact from the current chat session context. Kopete::ChatSession *session = dynamic_cast(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() {} TQ_UINT32 Dispatcher::CallbackChannel::send(const TQByteArray& stream) { return m_switchboard->sendCommand("MSG", "D", true, stream, true); } Dispatcher::CallbackChannel* Dispatcher::callbackChannel() { if(m_callbackChannel == 0l){ MSNSwitchBoardSocket *callback = dynamic_cast(parent()); if(callback == 0l) return 0l; m_callbackChannel = new Dispatcher::CallbackChannel(callback); } return m_callbackChannel; } #include "dispatcher.moc"