Code

apply patch 1498946 by zbsz (fixes #1492545 "PNG resolution value export")
[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                 gchar const* val = lm_message_node_get_value(offline);
163                 if (val != NULL) {
164                         if (strcmp(val, "Offline Storage") == 0) {
165                                 return false;
166                         }
167                 }
168         }
171         // 4.  If this is a regular chat message...
172         msubtype = lm_message_get_sub_type(message);
174         if (msubtype == LM_MESSAGE_SUB_TYPE_CHAT) {
175                 // 4a.  A protocol version node must be present.
176                 protocolver = lm_message_node_get_child(root, MESSAGE_PROTOCOL_VER);
177                 if (protocolver == NULL) {
178                         g_warning("Check 4a failed (no protocol attribute in chat message)");
179                         return false;
180                 } else {
181                         tmp = lm_message_node_get_value(protocolver);
182                         if (tmp == NULL) {
183                                 g_warning("Check 4a failed (no protocol attribute in chat message)");
184                                 return false;
185                         }
186                 }
188                 // 5a.  The protocol version must be supported.
189                 if (atoi(tmp) > HIGHEST_SUPPORTED) {
190                         g_warning("Check 5a failed (received a message with protocol version %s, but version %s is not supported)", tmp, tmp);
191                         return false;
192                 }
194         // ...otherwise, if this is a groupchat message, we may not have a protocol version 
195         // (since it may be communication from the Jabber server).  In this case, we have a
196         // different set of sanity checks.
197         } else if (msubtype == LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
198                 // 4b.
199                 // In a chatroom situation, we need to ensure that we don't process messages that
200                 // originated from us.
201                 int cutoff = sender.find_last_of('/') + 1;
202                 if (sender.substr(cutoff, sender.length()) == this->_sm->session_data->chat_handle) {
203                         return false;
204                 }
205                 // TODO: 6b.  If the message is NOT from the Jabber server, then check the protocol version.
206         }
208         // If all tests pass, then the message is at least valid.  
209         // Correct context has not yet been established, however; that is the job of the default handler
210         // and hasValidReceiveContext.
212         return true;
215 MessageType
216 MessageHandler::_getType(LmMessage* message)
218         LmMessageNode* root;
219         LmMessageNode* typenode;
221         root = lm_message_get_node(message);
222         if (root != NULL) {
223                 typenode = lm_message_node_get_child(root, MESSAGE_TYPE);
224                 if (typenode != NULL) {
225                         return static_cast< MessageType >(atoi(lm_message_node_get_value(typenode)));
226                 }
227         }
228         return UNKNOWN;
231 JabberMessage
232 MessageHandler::_extractData(LmMessage* message)
235         JabberMessage jm(message);
236         LmMessageNode* root;
237         LmMessageNode* sequence;
238         LmMessageNode* body;
239         gchar const* tmp;
241         root = lm_message_get_node(message);
243         if (root != NULL) {
244                 sequence = lm_message_node_get_child(root, MESSAGE_SEQNUM);
245                 body = lm_message_node_get_child(root, MESSAGE_BODY);
247                 jm.sender = lm_message_node_get_attribute(root, MESSAGE_FROM);
249                 if (sequence) {
250                         tmp = lm_message_node_get_value(sequence);
251                         if (tmp != NULL) {
252                                 jm.sequence = atoi(tmp);
253                         }
254                 }
256                 if (body) {
257                         tmp = lm_message_node_get_value(body);
258                         if (tmp != NULL) {
259                                 jm.body = tmp;
260                         }
261                 }
263         } else {
264                 jm.sequence = 0;
265                 jm.sender = "";
266                 jm.body = "";
267         }
269         return jm;
271         
272 LmHandlerResult
273 MessageHandler::_default(LmMessage* message)
275         std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status;
277         // Pass groupchat messages with no Inkboard type off to the chat message handler
278         if (this->_getType(message) == UNKNOWN) {
279                 if (lm_message_get_sub_type(message) == LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
280                         if (status[IN_CHATROOM] || status[CONNECTING_TO_CHAT]) {
281                                 return this->_sm->chat_handler()->parse(message);
282                         }
283                 } else {
284                         return LM_HANDLER_RESULT_REMOVE_MESSAGE;
285                 }
286         }
287         
288         if (this->_hasValidReceiveContext(message)) {
289                 // Extract message data
290                 JabberMessage msg = this->_extractData(message);
291                 MessageType type = this->_getType(message);
293                 // Call message handler and return instruction value to Loudmouth
295                 return (*this->_received_message_processors[type])(type, msg);
296         } else {
297                 g_warning("Default message handler received message in invalid receive context; discarding message.");
298                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
299         }
302 LmHandlerResult
303 MessageHandler::_presence(LmMessage* message)
305         LmMessageNode* root;
306         LmMessageSubType msubtype;
307         gchar const* tmp;
308         std::string sender;
310         SessionData* sd = this->_sm->session_data;
311         std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status;
313         root = lm_message_get_node(message);
314         if (root == NULL) {
315                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
316         }
318         tmp = lm_message_node_get_attribute(root, MESSAGE_FROM);
319         if (tmp == NULL) {
320                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
321         } else {
322                 sender = tmp;
323         }
325         msubtype = lm_message_get_sub_type(message);
326         if (status[CONNECTING_TO_CHAT] || status[IN_CHATROOM]) {
327                 this->_sm->chat_handler()->parse(message);
328         } else {
329                 switch(msubtype) {
330                         case LM_MESSAGE_SUB_TYPE_UNAVAILABLE:
331                                 // remove buddy from online roster
332                                 sd->buddyList.erase(sender);
334                                 // if this buddy is in a 1-1 session with us, we need to exit
335                                 // the whiteboard
336                                 if (status[IN_WHITEBOARD] && !(status[IN_CHATROOM]) && strcasecmp(sender.c_str(), sd->recipient) == 0) {
337                                         status.set(IN_WHITEBOARD, 0);
338                                         this->_sm->userDisconnectedFromWhiteboard(sender);
339                                         this->_sm->closeSession();
340                                 }
341                                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
342                                 
343                         case LM_MESSAGE_SUB_TYPE_AVAILABLE:
344                                 // we don't want to insert an entry into a buddy list
345                                 // if it's our own presence
346                                 if (sender != this->_sm->session_data->jid.c_str()) {
347                                         sd->buddyList.insert(sender);
348                                 }
349                                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
350                         default:
351                                 break;
352                 }
353         }
354         return LM_HANDLER_RESULT_REMOVE_MESSAGE;
357 LmHandlerResult
358 MessageHandler::_error(LmMessage* message)
360         LmMessageNode* root;
361         LmMessageSubType msubtype;
363         root = lm_message_get_node(message);
364         if (root != NULL) {
365                 msubtype = lm_message_get_sub_type(message);
366                 if (msubtype == LM_MESSAGE_SUB_TYPE_ERROR) {
367                         gchar* error = g_strdup(lm_message_node_get_value(root));
368                         g_warning(error);
370                         // TODO: more robust error handling code
371                         this->_sm->disconnectFromDocument();
372                         this->_sm->disconnectFromServer();
373                         this->_sm->connectionError(error);
374                 }
375         }
377         return LM_HANDLER_RESULT_REMOVE_MESSAGE;
380 void
381 MessageHandler::_initializeContexts()
383         initialize_received_message_contexts(_received_message_contexts);       
384         message_contexts_initialized = true;
387 void
388 MessageHandler::_initializeProcessors()
390         initialize_received_message_processors(this->_sm, this->_received_message_processors);  
393 void
394 MessageHandler::_destructProcessors()
396         destroy_received_message_processors(this->_received_message_processors);
400 char const*
401 MessageHandler::ink_type_to_string(gint ink_type) {
402         switch(ink_type) {
403                 case Inkscape::Whiteboard::CHANGE_NOT_REPEATABLE:
404                         return "CHANGE_NOT_REPEATABLE";
405                 case Inkscape::Whiteboard::CHANGE_REPEATABLE:
406                         return "CHANGE_REPEATABLE";
407                 case Inkscape::Whiteboard::DUMMY_CHANGE:
408                         return "DUMMY_CHANGE";
409                 case Inkscape::Whiteboard::CHANGE_COMMIT:
410                         return "CHANGE_COMMIT";
411                 case Inkscape::Whiteboard::CONNECT_REQUEST_USER:
412                         return "CONNECT_REQUEST_USER";
413                 case Inkscape::Whiteboard::CONNECT_REQUEST_RESPONSE_USER:
414                         return "CONNECT_REQUEST_RESPONSE_USER";
415                 case Inkscape::Whiteboard::CONNECT_REQUEST_RESPONSE_CHAT:
416                         return "CONNECT_REQUEST_RESPONSE_CHAT";
417                 case Inkscape::Whiteboard::DOCUMENT_SENDER_REQUEST:
418                         return "DOCUMENT_SENDER_REQUEST";
419                 case Inkscape::Whiteboard::DOCUMENT_SENDER_REQUEST_RESPONSE:
420                         return "DOCUMENT_SENDER_REQUEST_RESPONSE";
421                 case Inkscape::Whiteboard::DOCUMENT_REQUEST:
422                         return "DOCUMENT_REQUEST";
423                 case Inkscape::Whiteboard::DOCUMENT_BEGIN:
424                         return "DOCUMENT_BEGIN";
425                 case Inkscape::Whiteboard::DOCUMENT_END:
426                         return "DOCUMENT_END";
427                 case Inkscape::Whiteboard::CONNECTED_SIGNAL:
428                         return "CONNECTED_SIGNAL";
429                 case Inkscape::Whiteboard::DISCONNECTED_FROM_USER_SIGNAL:
430                         return "DISCONNECTED_FROM_USER_SIGNAL";
431                 case Inkscape::Whiteboard::CONNECT_REQUEST_REFUSED_BY_PEER:
432                         return "CONNECT_REQUEST_REFUSED_BY_PEER";
433                 case Inkscape::Whiteboard::UNSUPPORTED_PROTOCOL_VERSION:
434                         return "UNSUPPORTED_PROTOCOL_VERSION";
435                 case Inkscape::Whiteboard::CHATROOM_SYNCHRONIZE_REQUEST:
436                         return "CHATROOM_SYNCHRONIZE_REQUEST";
437                 case Inkscape::Whiteboard::CHATROOM_SYNCHRONIZE_RESPONSE:
438                         return "CHATROOM_SYNCHRONIZE_RESPONSE";
439                 case Inkscape::Whiteboard::ALREADY_IN_SESSION:
440                         return "ALREADY_IN_SESSION";
441                 default:
442                         return "UNKNOWN";
443         }
450 /*
451   Local Variables:
452   mode:c++
453   c-file-style:"stroustrup"
454   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
455   indent-tabs-mode:nil
456   fill-column:99
457   End:
458 */
459 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :