Code

Fix for bug #1352522 (Inkboard: problem when invited person invites you back). I...
[inkscape.git] / src / jabber_whiteboard / message-handler.cpp
1 /**
2  * Whiteboard session manager
3  * Jabber received message handling
4  *
5  * Authors:
6  * David Yip <yipdw@rose-hulman.edu>
7  * Steven Montgomery, Jonas Collaros (original C version)
8  *
9  * Copyright (c) 2004-2005 Authors
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 extern "C" {
15 #include <loudmouth/loudmouth.h>
16 }
18 #include <glibmm.h>
19 #include <glibmm/i18n.h>
20 #include <glib.h>
21 #include <map>
23 #include "jabber_whiteboard/defines.h"
24 #include "jabber_whiteboard/typedefs.h"
25 #include "jabber_whiteboard/message-processors.h"
26 #include "jabber_whiteboard/message-handler.h"
27 #include "jabber_whiteboard/session-manager.h"
28 #include "jabber_whiteboard/chat-handler.h"
29 #include "jabber_whiteboard/buddy-list-manager.h"
31 namespace Inkscape {
33 namespace Whiteboard {
35 bool message_contexts_initialized = false;
36 MessageContextMap _received_message_contexts;
38 MessageHandler::MessageHandler(SessionManager* sm) : _sm(sm)
39 {
40         if (message_contexts_initialized == false) {
41 //              this->_initializeContexts();
42                 MessageHandler::_initializeContexts();
43         }
44         this->_initializeProcessors();
45 }
47 MessageHandler::~MessageHandler()
48 {
49         this->_destructProcessors();
50 }
52 LmHandlerResult
53 MessageHandler::handle(LmMessage* message, HandlerMode mode)
54 {
55         if (this->_isValidMessage(message)) {
56                 switch(mode) {
57                         case DEFAULT:
58                                 return this->_default(message);
59                         case PRESENCE:
60                                 return this->_presence(message);
61                         case ERROR:
62                                 return this->_error(message);
63                         default:
64                                 g_warning("Jabber message handler was asked to process a message of an unhandled type; discarding message.");
65                                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
66                 }
67         } else {
68                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
69         }
70 }
72 bool
73 MessageHandler::_hasValidReceiveContext(LmMessage* message)
74 {
75         MessageType type = this->_getType(message);
76         std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status;
78         std::string s1 = status.to_string< char, std::char_traits< char >, std::allocator< char > >();
81         if (type == UNKNOWN) {
82                 // unknown types never have a valid receive context
83                 return false;
84         } else {
85                 std::bitset< NUM_FLAGS >& recvcontext = _received_message_contexts[type];
86                 
87                 // TODO: remove this debug block
88                 if ((status & recvcontext).to_ulong() < status.to_ulong()) {
89                         g_warning("Received message in incorrect context; discarding message.");
91                         std::string s2 = recvcontext.to_string< char, std::char_traits< char >, std::allocator< char > >();
93                         g_warning("current context=%s required context=%s (msgtype %s)", s1.c_str(), s2.c_str(), MessageHandler::ink_type_to_string(type));
94                 }
96                 return ((status & recvcontext).to_ulong() >= status.to_ulong());
97         }
98 }
100 bool
101 MessageHandler::_isValidMessage(LmMessage* message)
103         // Global sanity checks
104         LmMessageNode* root;
105         LmMessageNode* protocolver;
106         LmMessageNode* offline;
107         LmMessageType mtype;
108         LmMessageSubType msubtype;
109         gchar const* tmp;
111         Glib::ustring sender;
113         
114         // 0.  The message must have a root node.
115         root = lm_message_get_node(message);
116         if (root == NULL) {
117                 g_warning("Check 0 failed (message has no root node)");
118                 return false;
119         }
120         
122         // 1.  The message must be of LM_MESSAGE_TYPE_MESSAGE to continue the sanity checks.
123         // If it is not, check to see if it is either
124         // a presence message or an error message.  If it is either of these, then automatically
125         // consider it sane.  
126         //
127         // FIXME:
128         // (That is probably a dangerous assumption.  We should probably at least validate
129         // the source for error messages.)
130         // 
131         // We do not handle IQ stanzas or STREAM messages (yet), and we certainly don't
132         // handle unknowns.
133         
134         mtype = lm_message_get_type(message);
135         switch(mtype) {
136                 case LM_MESSAGE_TYPE_PRESENCE:
137                 case LM_MESSAGE_TYPE_STREAM_ERROR:
138                         return true;
139                 case LM_MESSAGE_TYPE_IQ:
140                 case LM_MESSAGE_TYPE_STREAM:
141                 case LM_MESSAGE_TYPE_UNKNOWN:
142                         g_warning("Check 1 failed (Loudmouth reported type IQ, STREAM, or UNKNOWN)");
143                         return false;
144                 case LM_MESSAGE_TYPE_MESSAGE:
145                         break;
146         }
147         
148         // 2.  The message must contain the JID of the sender.
149         tmp = lm_message_node_get_attribute(root, MESSAGE_FROM);
151         if (tmp == NULL) {
152                 g_warning("Check 2 failed (no sender attribute present)");
153                 return false;
154         } else {
155                 sender = tmp;
156         }
158         // 3.  We do not yet handle messages from offline storage, so ensure that this is not
159         // such a message.
160         offline = lm_message_node_get_child(root, "x");
161         if (offline != NULL) {
162                 if (strcmp(lm_message_node_get_value(offline), "Offline Storage") == 0) {
163                         return false;
164                 }
165         }
167         // 4.  If this is a regular chat message...
168         msubtype = lm_message_get_sub_type(message);
170         if (msubtype == LM_MESSAGE_SUB_TYPE_CHAT) {
171                 // 4a.  A protocol version node must be present.
172                 protocolver = lm_message_node_get_child(root, MESSAGE_PROTOCOL_VER);
173                 if (protocolver == NULL) {
174                         g_warning("Check 4a failed (no protocol attribute in chat message)");
175                         return false;
176                 } else {
177                         tmp = lm_message_node_get_value(protocolver);
178                         if (tmp == NULL) {
179                                 g_warning("Check 4a failed (no protocol attribute in chat message)");
180                                 return false;
181                         }
182                 }
184                 // 5a.  The protocol version must be supported.
185                 if (atoi(tmp) > HIGHEST_SUPPORTED) {
186                         g_warning("Check 5a failed (received a message with protocol version %s, but version %s is not supported)", tmp, tmp);
187                         return false;
188                 }
190         // ...otherwise, if this is a groupchat message, we may not have a protocol version 
191         // (since it may be communication from the Jabber server).  In this case, we have a
192         // different set of sanity checks.
193         } else if (msubtype == LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
194                 // 4b.
195                 // In a chatroom situation, we need to ensure that we don't process messages that
196                 // originated from us.
197                 int cutoff = sender.find_last_of('/') + 1;
198                 if (sender.substr(cutoff, sender.length()) == this->_sm->session_data->chat_handle) {
199                         return false;
200                 }
201                 // TODO: 6b.  If the message is NOT from the Jabber server, then check the protocol version.
202         }
204         // If all tests pass, then the message is at least valid.  
205         // Correct context has not yet been established, however; that is the job of the default handler
206         // and hasValidReceiveContext.
208         return true;
211 MessageType
212 MessageHandler::_getType(LmMessage* message)
214         LmMessageNode* root;
215         LmMessageNode* typenode;
217         root = lm_message_get_node(message);
218         if (root != NULL) {
219                 typenode = lm_message_node_get_child(root, MESSAGE_TYPE);
220                 if (typenode != NULL) {
221                         return static_cast< MessageType >(atoi(lm_message_node_get_value(typenode)));
222                 }
223         }
224         return UNKNOWN;
227 JabberMessage
228 MessageHandler::_extractData(LmMessage* message)
231         JabberMessage jm(message);
232         LmMessageNode* root;
233         LmMessageNode* sequence;
234         LmMessageNode* body;
235         gchar const* tmp;
237         root = lm_message_get_node(message);
239         if (root != NULL) {
240                 sequence = lm_message_node_get_child(root, MESSAGE_SEQNUM);
241                 body = lm_message_node_get_child(root, MESSAGE_BODY);
243                 jm.sender = lm_message_node_get_attribute(root, MESSAGE_FROM);
245                 if (sequence) {
246                         tmp = lm_message_node_get_value(sequence);
247                         if (tmp != NULL) {
248                                 jm.sequence = atoi(tmp);
249                         }
250                 }
252                 if (body) {
253                         tmp = lm_message_node_get_value(body);
254                         if (tmp != NULL) {
255                                 jm.body = tmp;
256                         }
257                 }
259         } else {
260                 jm.sequence = 0;
261                 jm.sender = "";
262                 jm.body = "";
263         }
265         return jm;
267         
268 LmHandlerResult
269 MessageHandler::_default(LmMessage* message)
271         std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status;
273         // Pass groupchat messages with no Inkboard type off to the chat message handler
274         if (this->_getType(message) == UNKNOWN) {
275                 if (lm_message_get_sub_type(message) == LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
276                         if (status[IN_CHATROOM] || status[CONNECTING_TO_CHAT]) {
277                                 return this->_sm->chat_handler()->parse(message);
278                         }
279                 } else {
280                         return LM_HANDLER_RESULT_REMOVE_MESSAGE;
281                 }
282         }
283         
284         if (this->_hasValidReceiveContext(message)) {
285                 // Extract message data
286                 JabberMessage msg = this->_extractData(message);
287                 MessageType type = this->_getType(message);
289                 // Call message handler and return instruction value to Loudmouth
291                 return (*this->_received_message_processors[type])(type, msg);
292         } else {
293                 g_warning("Default message handler received message in invalid receive context; discarding message.");
294                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
295         }
298 LmHandlerResult
299 MessageHandler::_presence(LmMessage* message)
301         LmMessageNode* root;
302         LmMessageSubType msubtype;
303         gchar const* tmp;
304         std::string sender;
306         SessionData* sd = this->_sm->session_data;
307         std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status;
309         root = lm_message_get_node(message);
310         if (root == NULL) {
311                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
312         }
314         tmp = lm_message_node_get_attribute(root, MESSAGE_FROM);
315         if (tmp == NULL) {
316                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
317         } else {
318                 sender = tmp;
319         }
321         msubtype = lm_message_get_sub_type(message);
322         if (status[CONNECTING_TO_CHAT] || status[IN_CHATROOM]) {
323                 this->_sm->chat_handler()->parse(message);
324         } else {
325                 switch(msubtype) {
326                         case LM_MESSAGE_SUB_TYPE_UNAVAILABLE:
327                                 // remove buddy from online roster
328                                 sd->buddyList.erase(sender);
330                                 // if this buddy is in a 1-1 session with us, we need to exit
331                                 // the whiteboard
332                                 if (status[IN_WHITEBOARD] && !(status[IN_CHATROOM]) && strcasecmp(sender.c_str(), sd->recipient) == 0) {
333                                         status.set(IN_WHITEBOARD, 0);
334                                         this->_sm->userDisconnectedFromWhiteboard(sender);
335                                         this->_sm->closeSession();
336                                 }
337                                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
338                                 
339                         case LM_MESSAGE_SUB_TYPE_AVAILABLE:
340                                 // we don't want to insert an entry into a buddy list
341                                 // if it's our own presence
342                                 if (sender != lm_connection_get_jid(this->_sm->session_data->connection)) {
343                                         sd->buddyList.insert(sender);
344                                 }
345                                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
346                         default:
347                                 break;
348                 }
349         }
350         return LM_HANDLER_RESULT_REMOVE_MESSAGE;
353 LmHandlerResult
354 MessageHandler::_error(LmMessage* message)
356         LmMessageNode* root;
357         LmMessageSubType msubtype;
359         root = lm_message_get_node(message);
360         if (root != NULL) {
361                 msubtype = lm_message_get_sub_type(message);
362                 if (msubtype == LM_MESSAGE_SUB_TYPE_ERROR) {
363                         gchar* error = g_strdup(lm_message_node_get_value(root));
364                         g_warning(error);
366                         // TODO: more robust error handling code
367                         this->_sm->disconnectFromDocument();
368                         this->_sm->disconnectFromServer();
369                         this->_sm->connectionError(error);
370                 }
371         }
373         return LM_HANDLER_RESULT_REMOVE_MESSAGE;
376 void
377 MessageHandler::_initializeContexts()
379         initialize_received_message_contexts(_received_message_contexts);       
380         message_contexts_initialized = true;
383 void
384 MessageHandler::_initializeProcessors()
386         initialize_received_message_processors(this->_sm, this->_received_message_processors);  
389 void
390 MessageHandler::_destructProcessors()
392         destroy_received_message_processors(this->_received_message_processors);
396 char const*
397 MessageHandler::ink_type_to_string(gint ink_type) {
398         switch(ink_type) {
399                 case Inkscape::Whiteboard::CHANGE_NOT_REPEATABLE:
400                         return "CHANGE_NOT_REPEATABLE";
401                 case Inkscape::Whiteboard::CHANGE_REPEATABLE:
402                         return "CHANGE_REPEATABLE";
403                 case Inkscape::Whiteboard::DUMMY_CHANGE:
404                         return "DUMMY_CHANGE";
405                 case Inkscape::Whiteboard::CHANGE_COMMIT:
406                         return "CHANGE_COMMIT";
407                 case Inkscape::Whiteboard::CONNECT_REQUEST_USER:
408                         return "CONNECT_REQUEST_USER";
409                 case Inkscape::Whiteboard::CONNECT_REQUEST_RESPONSE_USER:
410                         return "CONNECT_REQUEST_RESPONSE_USER";
411                 case Inkscape::Whiteboard::CONNECT_REQUEST_RESPONSE_CHAT:
412                         return "CONNECT_REQUEST_RESPONSE_CHAT";
413                 case Inkscape::Whiteboard::DOCUMENT_SENDER_REQUEST:
414                         return "DOCUMENT_SENDER_REQUEST";
415                 case Inkscape::Whiteboard::DOCUMENT_SENDER_REQUEST_RESPONSE:
416                         return "DOCUMENT_SENDER_REQUEST_RESPONSE";
417                 case Inkscape::Whiteboard::DOCUMENT_REQUEST:
418                         return "DOCUMENT_REQUEST";
419                 case Inkscape::Whiteboard::DOCUMENT_BEGIN:
420                         return "DOCUMENT_BEGIN";
421                 case Inkscape::Whiteboard::DOCUMENT_END:
422                         return "DOCUMENT_END";
423                 case Inkscape::Whiteboard::CONNECTED_SIGNAL:
424                         return "CONNECTED_SIGNAL";
425                 case Inkscape::Whiteboard::DISCONNECTED_FROM_USER_SIGNAL:
426                         return "DISCONNECTED_FROM_USER_SIGNAL";
427                 case Inkscape::Whiteboard::CONNECT_REQUEST_REFUSED_BY_PEER:
428                         return "CONNECT_REQUEST_REFUSED_BY_PEER";
429                 case Inkscape::Whiteboard::UNSUPPORTED_PROTOCOL_VERSION:
430                         return "UNSUPPORTED_PROTOCOL_VERSION";
431                 case Inkscape::Whiteboard::CHATROOM_SYNCHRONIZE_REQUEST:
432                         return "CHATROOM_SYNCHRONIZE_REQUEST";
433                 case Inkscape::Whiteboard::CHATROOM_SYNCHRONIZE_RESPONSE:
434                         return "CHATROOM_SYNCHRONIZE_RESPONSE";
435                 case Inkscape::Whiteboard::ALREADY_IN_SESSION:
436                         return "ALREADY_IN_SESSION";
437                 default:
438                         return "UNKNOWN";
439         }
446 /*
447   Local Variables:
448   mode:c++
449   c-file-style:"stroustrup"
450   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
451   indent-tabs-mode:nil
452   fill-column:99
453   End:
454 */
455 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :