summaryrefslogtreecommitdiffstats
path: root/src/jabber_whiteboard/message-handler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/jabber_whiteboard/message-handler.cpp')
-rw-r--r--src/jabber_whiteboard/message-handler.cpp456
1 files changed, 456 insertions, 0 deletions
diff --git a/src/jabber_whiteboard/message-handler.cpp b/src/jabber_whiteboard/message-handler.cpp
new file mode 100644
index 000000000..cdcef74b8
--- /dev/null
+++ b/src/jabber_whiteboard/message-handler.cpp
@@ -0,0 +1,456 @@
+/**
+ * Whiteboard session manager
+ * Jabber received message handling
+ *
+ * Authors:
+ * David Yip <yipdw@rose-hulman.edu>
+ * Steven Montgomery, Jonas Collaros (original C version)
+ *
+ * Copyright (c) 2004-2005 Authors
+ *
+ * Released under GNU GPL, read the file 'COPYING' for more information
+ */
+
+extern "C" {
+#include <loudmouth/loudmouth.h>
+}
+
+#include <glibmm.h>
+#include <glibmm/i18n.h>
+#include <glib.h>
+#include <map>
+
+#include "jabber_whiteboard/defines.h"
+#include "jabber_whiteboard/typedefs.h"
+#include "jabber_whiteboard/message-processors.h"
+#include "jabber_whiteboard/message-handler.h"
+#include "jabber_whiteboard/session-manager.h"
+#include "jabber_whiteboard/chat-handler.h"
+#include "jabber_whiteboard/buddy-list-manager.h"
+
+namespace Inkscape {
+
+namespace Whiteboard {
+
+bool message_contexts_initialized = false;
+MessageContextMap _received_message_contexts;
+
+MessageHandler::MessageHandler(SessionManager* sm) : _sm(sm)
+{
+ if (message_contexts_initialized == false) {
+// this->_initializeContexts();
+ MessageHandler::_initializeContexts();
+ }
+ this->_initializeProcessors();
+}
+
+MessageHandler::~MessageHandler()
+{
+ this->_destructProcessors();
+}
+
+LmHandlerResult
+MessageHandler::handle(LmMessage* message, HandlerMode mode)
+{
+ if (this->_isValidMessage(message)) {
+ switch(mode) {
+ case DEFAULT:
+ return this->_default(message);
+ case PRESENCE:
+ return this->_presence(message);
+ case ERROR:
+ return this->_error(message);
+ default:
+ g_warning("Jabber message handler was asked to process a message of an unhandled type; discarding message.");
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ }
+ } else {
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ }
+}
+
+bool
+MessageHandler::_hasValidReceiveContext(LmMessage* message)
+{
+ MessageType type = this->_getType(message);
+ std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status;
+
+ std::string s1 = status.to_string< char, std::char_traits< char >, std::allocator< char > >();
+
+
+ if (type == UNKNOWN) {
+ // unknown types never have a valid receive context
+ return false;
+ } else {
+ std::bitset< NUM_FLAGS >& recvcontext = _received_message_contexts[type];
+
+ // TODO: remove this debug block
+ if ((status & recvcontext).to_ulong() < status.to_ulong()) {
+ g_warning("Received message in incorrect context (current is not a subset of required); discarding message.");
+
+ std::string s2 = recvcontext.to_string< char, std::char_traits< char >, std::allocator< char > >();
+
+
+ g_warning("current context=%s required context=%s (msgtype %s)", s1.c_str(), s2.c_str(), MessageHandler::ink_type_to_string(type));
+ }
+
+ return ((status & recvcontext).to_ulong() >= status.to_ulong());
+ }
+}
+
+bool
+MessageHandler::_isValidMessage(LmMessage* message)
+{
+ // Global sanity checks
+ LmMessageNode* root;
+ LmMessageNode* protocolver;
+ LmMessageNode* offline;
+ LmMessageType mtype;
+ LmMessageSubType msubtype;
+ gchar const* tmp;
+
+ Glib::ustring sender;
+
+
+ // 0. The message must have a root node.
+ root = lm_message_get_node(message);
+ if (root == NULL) {
+ g_warning("Check 0 failed (message has no root node)");
+ return false;
+ }
+
+
+ // 1. The message must be of LM_MESSAGE_TYPE_MESSAGE to continue the sanity checks.
+ // If it is not, check to see if it is either
+ // a presence message or an error message. If it is either of these, then automatically
+ // consider it sane.
+ //
+ // FIXME:
+ // (That is probably a dangerous assumption. We should probably at least validate
+ // the source for error messages.)
+ //
+ // We do not handle IQ stanzas or STREAM messages (yet), and we certainly don't
+ // handle unknowns.
+
+ mtype = lm_message_get_type(message);
+ switch(mtype) {
+ case LM_MESSAGE_TYPE_PRESENCE:
+ case LM_MESSAGE_TYPE_STREAM_ERROR:
+ return true;
+ case LM_MESSAGE_TYPE_IQ:
+ case LM_MESSAGE_TYPE_STREAM:
+ case LM_MESSAGE_TYPE_UNKNOWN:
+ g_warning("Check 1 failed (Loudmouth reported type IQ, STREAM, or UNKNOWN)");
+ return false;
+ case LM_MESSAGE_TYPE_MESSAGE:
+ break;
+ }
+
+ // 2. The message must contain the JID of the sender.
+ tmp = lm_message_node_get_attribute(root, MESSAGE_FROM);
+
+ if (tmp == NULL) {
+ g_warning("Check 2 failed (no sender attribute present)");
+ return false;
+ } else {
+ sender = tmp;
+ }
+
+ // 3. We do not yet handle messages from offline storage, so ensure that this is not
+ // such a message.
+ offline = lm_message_node_get_child(root, "x");
+ if (offline != NULL) {
+ if (strcmp(lm_message_node_get_value(offline), "Offline Storage") == 0) {
+ return false;
+ }
+ }
+
+ // 4. If this is a regular chat message...
+ msubtype = lm_message_get_sub_type(message);
+
+ if (msubtype == LM_MESSAGE_SUB_TYPE_CHAT) {
+ // 4a. A protocol version node must be present.
+ protocolver = lm_message_node_get_child(root, MESSAGE_PROTOCOL_VER);
+ if (protocolver == NULL) {
+ g_warning("Check 4a failed (no protocol attribute in chat message)");
+ return false;
+ } else {
+ tmp = lm_message_node_get_value(protocolver);
+ if (tmp == NULL) {
+ g_warning("Check 4a failed (no protocol attribute in chat message)");
+ return false;
+ }
+ }
+
+ // 5a. The protocol version must be supported.
+ if (atoi(tmp) > HIGHEST_SUPPORTED) {
+ g_warning("Check 5a failed (received a message with protocol version %s, but version %s is not supported)", tmp, tmp);
+ return false;
+ }
+
+ // ...otherwise, if this is a groupchat message, we may not have a protocol version
+ // (since it may be communication from the Jabber server). In this case, we have a
+ // different set of sanity checks.
+ } else if (msubtype == LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
+ // 4b.
+ // In a chatroom situation, we need to ensure that we don't process messages that
+ // originated from us.
+ int cutoff = sender.find_last_of('/') + 1;
+ if (sender.substr(cutoff, sender.length()) == this->_sm->session_data->chat_handle) {
+ return false;
+ }
+ // TODO: 6b. If the message is NOT from the Jabber server, then check the protocol version.
+ }
+
+ // If all tests pass, then the message is at least valid.
+ // Correct context has not yet been established, however; that is the job of the default handler
+ // and hasValidReceiveContext.
+
+ return true;
+}
+
+MessageType
+MessageHandler::_getType(LmMessage* message)
+{
+ LmMessageNode* root;
+ LmMessageNode* typenode;
+
+ root = lm_message_get_node(message);
+ if (root != NULL) {
+ typenode = lm_message_node_get_child(root, MESSAGE_TYPE);
+ if (typenode != NULL) {
+ return static_cast< MessageType >(atoi(lm_message_node_get_value(typenode)));
+ }
+ }
+ return UNKNOWN;
+}
+
+JabberMessage
+MessageHandler::_extractData(LmMessage* message)
+{
+
+ JabberMessage jm(message);
+ LmMessageNode* root;
+ LmMessageNode* sequence;
+ LmMessageNode* body;
+ gchar const* tmp;
+
+ root = lm_message_get_node(message);
+
+ if (root != NULL) {
+ sequence = lm_message_node_get_child(root, MESSAGE_SEQNUM);
+ body = lm_message_node_get_child(root, MESSAGE_BODY);
+
+ jm.sender = lm_message_node_get_attribute(root, MESSAGE_FROM);
+
+ if (sequence) {
+ tmp = lm_message_node_get_value(sequence);
+ if (tmp != NULL) {
+ jm.sequence = atoi(tmp);
+ }
+ }
+
+ if (body) {
+ tmp = lm_message_node_get_value(body);
+ if (tmp != NULL) {
+ jm.body = tmp;
+ }
+ }
+
+ } else {
+ jm.sequence = 0;
+ jm.sender = "";
+ jm.body = "";
+ }
+
+ return jm;
+}
+
+LmHandlerResult
+MessageHandler::_default(LmMessage* message)
+{
+ std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status;
+
+ // Pass groupchat messages with no Inkboard type off to the chat message handler
+ if (this->_getType(message) == UNKNOWN) {
+ if (lm_message_get_sub_type(message) == LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
+ if (status[IN_CHATROOM] || status[CONNECTING_TO_CHAT]) {
+ return this->_sm->chat_handler()->parse(message);
+ }
+ } else {
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ }
+ }
+
+ if (this->_hasValidReceiveContext(message)) {
+ // Extract message data
+ JabberMessage msg = this->_extractData(message);
+ MessageType type = this->_getType(message);
+
+ // Call message handler and return instruction value to Loudmouth
+
+ return (*this->_received_message_processors[type])(type, msg);
+ } else {
+ g_warning("Default message handler received message in invalid receive context; discarding message.");
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ }
+}
+
+LmHandlerResult
+MessageHandler::_presence(LmMessage* message)
+{
+ LmMessageNode* root;
+ LmMessageSubType msubtype;
+ gchar const* tmp;
+ std::string sender;
+
+ SessionData* sd = this->_sm->session_data;
+ std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status;
+
+ root = lm_message_get_node(message);
+ if (root == NULL) {
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ }
+
+ tmp = lm_message_node_get_attribute(root, MESSAGE_FROM);
+ if (tmp == NULL) {
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ } else {
+ sender = tmp;
+ }
+
+ msubtype = lm_message_get_sub_type(message);
+ if (status[CONNECTING_TO_CHAT] || status[IN_CHATROOM]) {
+ this->_sm->chat_handler()->parse(message);
+ } else {
+ switch(msubtype) {
+ case LM_MESSAGE_SUB_TYPE_UNAVAILABLE:
+ // remove buddy from online roster
+ sd->buddyList.erase(sender);
+
+ // if this buddy is in a 1-1 session with us, we need to exit
+ // the whiteboard
+ if (status[IN_WHITEBOARD] && !(status[IN_CHATROOM]) && strcasecmp(sender.c_str(), sd->recipient) == 0) {
+ status.set(IN_WHITEBOARD, 0);
+ this->_sm->userDisconnectedFromWhiteboard(sender);
+ this->_sm->closeSession();
+ }
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+
+ case LM_MESSAGE_SUB_TYPE_AVAILABLE:
+ // we don't want to insert an entry into a buddy list
+ // if it's our own presence
+ if (sender != lm_connection_get_jid(this->_sm->session_data->connection)) {
+ sd->buddyList.insert(sender);
+ }
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+ default:
+ break;
+ }
+ }
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+LmHandlerResult
+MessageHandler::_error(LmMessage* message)
+{
+ LmMessageNode* root;
+ LmMessageSubType msubtype;
+
+ root = lm_message_get_node(message);
+ if (root != NULL) {
+ msubtype = lm_message_get_sub_type(message);
+ if (msubtype == LM_MESSAGE_SUB_TYPE_ERROR) {
+ gchar* error = g_strdup(lm_message_node_get_value(root));
+ g_warning(error);
+
+ // TODO: more robust error handling code
+ this->_sm->disconnectFromDocument();
+ this->_sm->disconnectFromServer();
+ this->_sm->connectionError(error);
+ }
+ }
+
+ return LM_HANDLER_RESULT_REMOVE_MESSAGE;
+}
+
+void
+MessageHandler::_initializeContexts()
+{
+ initialize_received_message_contexts(_received_message_contexts);
+ message_contexts_initialized = true;
+}
+
+void
+MessageHandler::_initializeProcessors()
+{
+ initialize_received_message_processors(this->_sm, this->_received_message_processors);
+}
+
+void
+MessageHandler::_destructProcessors()
+{
+ destroy_received_message_processors(this->_received_message_processors);
+}
+
+
+char const*
+MessageHandler::ink_type_to_string(gint ink_type) {
+ switch(ink_type) {
+ case Inkscape::Whiteboard::CHANGE_NOT_REPEATABLE:
+ return "CHANGE_NOT_REPEATABLE";
+ case Inkscape::Whiteboard::CHANGE_REPEATABLE:
+ return "CHANGE_REPEATABLE";
+ case Inkscape::Whiteboard::DUMMY_CHANGE:
+ return "DUMMY_CHANGE";
+ case Inkscape::Whiteboard::CHANGE_COMMIT:
+ return "CHANGE_COMMIT";
+ case Inkscape::Whiteboard::CONNECT_REQUEST_USER:
+ return "CONNECT_REQUEST_USER";
+ case Inkscape::Whiteboard::CONNECT_REQUEST_RESPONSE_USER:
+ return "CONNECT_REQUEST_RESPONSE_USER";
+ case Inkscape::Whiteboard::CONNECT_REQUEST_RESPONSE_CHAT:
+ return "CONNECT_REQUEST_RESPONSE_CHAT";
+ case Inkscape::Whiteboard::DOCUMENT_SENDER_REQUEST:
+ return "DOCUMENT_SENDER_REQUEST";
+ case Inkscape::Whiteboard::DOCUMENT_SENDER_REQUEST_RESPONSE:
+ return "DOCUMENT_SENDER_REQUEST_RESPONSE";
+ case Inkscape::Whiteboard::DOCUMENT_REQUEST:
+ return "DOCUMENT_REQUEST";
+ case Inkscape::Whiteboard::DOCUMENT_BEGIN:
+ return "DOCUMENT_BEGIN";
+ case Inkscape::Whiteboard::DOCUMENT_END:
+ return "DOCUMENT_END";
+ case Inkscape::Whiteboard::CONNECTED_SIGNAL:
+ return "CONNECTED_SIGNAL";
+ case Inkscape::Whiteboard::DISCONNECTED_FROM_USER_SIGNAL:
+ return "DISCONNECTED_FROM_USER_SIGNAL";
+ case Inkscape::Whiteboard::CONNECT_REQUEST_REFUSED_BY_PEER:
+ return "CONNECT_REQUEST_REFUSED_BY_PEER";
+ case Inkscape::Whiteboard::UNSUPPORTED_PROTOCOL_VERSION:
+ return "UNSUPPORTED_PROTOCOL_VERSION";
+ case Inkscape::Whiteboard::CHATROOM_SYNCHRONIZE_REQUEST:
+ return "CHATROOM_SYNCHRONIZE_REQUEST";
+ case Inkscape::Whiteboard::CHATROOM_SYNCHRONIZE_RESPONSE:
+ return "CHATROOM_SYNCHRONIZE_RESPONSE";
+ case Inkscape::Whiteboard::ALREADY_IN_SESSION:
+ return "ALREADY_IN_SESSION";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+}
+
+}
+
+/*
+ Local Variables:
+ mode:c++
+ c-file-style:"stroustrup"
+ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
+ indent-tabs-mode:nil
+ fill-column:99
+ End:
+*/
+// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :