Code

rearrange effect submenus, rename some effects, fix capitalization
[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 (current is not a subset of required); discarding message.");
91                         std::string s2 = recvcontext.to_string< char, std::char_traits< char >, std::allocator< char > >();
94                         g_warning("current context=%s required context=%s (msgtype %s)", s1.c_str(), s2.c_str(), MessageHandler::ink_type_to_string(type));
95                 }
97                 return ((status & recvcontext).to_ulong() >= status.to_ulong());
98         }
99 }
101 bool
102 MessageHandler::_isValidMessage(LmMessage* message)
104         // Global sanity checks
105         LmMessageNode* root;
106         LmMessageNode* protocolver;
107         LmMessageNode* offline;
108         LmMessageType mtype;
109         LmMessageSubType msubtype;
110         gchar const* tmp;
112         Glib::ustring sender;
114         
115         // 0.  The message must have a root node.
116         root = lm_message_get_node(message);
117         if (root == NULL) {
118                 g_warning("Check 0 failed (message has no root node)");
119                 return false;
120         }
121         
123         // 1.  The message must be of LM_MESSAGE_TYPE_MESSAGE to continue the sanity checks.
124         // If it is not, check to see if it is either
125         // a presence message or an error message.  If it is either of these, then automatically
126         // consider it sane.  
127         //
128         // FIXME:
129         // (That is probably a dangerous assumption.  We should probably at least validate
130         // the source for error messages.)
131         // 
132         // We do not handle IQ stanzas or STREAM messages (yet), and we certainly don't
133         // handle unknowns.
134         
135         mtype = lm_message_get_type(message);
136         switch(mtype) {
137                 case LM_MESSAGE_TYPE_PRESENCE:
138                 case LM_MESSAGE_TYPE_STREAM_ERROR:
139                         return true;
140                 case LM_MESSAGE_TYPE_IQ:
141                 case LM_MESSAGE_TYPE_STREAM:
142                 case LM_MESSAGE_TYPE_UNKNOWN:
143                         g_warning("Check 1 failed (Loudmouth reported type IQ, STREAM, or UNKNOWN)");
144                         return false;
145                 case LM_MESSAGE_TYPE_MESSAGE:
146                         break;
147         }
148         
149         // 2.  The message must contain the JID of the sender.
150         tmp = lm_message_node_get_attribute(root, MESSAGE_FROM);
152         if (tmp == NULL) {
153                 g_warning("Check 2 failed (no sender attribute present)");
154                 return false;
155         } else {
156                 sender = tmp;
157         }
159         // 3.  We do not yet handle messages from offline storage, so ensure that this is not
160         // such a message.
161         offline = lm_message_node_get_child(root, "x");
162         if (offline != NULL) {
163                 if (strcmp(lm_message_node_get_value(offline), "Offline Storage") == 0) {
164                         return false;
165                 }
166         }
168         // 4.  If this is a regular chat message...
169         msubtype = lm_message_get_sub_type(message);
171         if (msubtype == LM_MESSAGE_SUB_TYPE_CHAT) {
172                 // 4a.  A protocol version node must be present.
173                 protocolver = lm_message_node_get_child(root, MESSAGE_PROTOCOL_VER);
174                 if (protocolver == NULL) {
175                         g_warning("Check 4a failed (no protocol attribute in chat message)");
176                         return false;
177                 } else {
178                         tmp = lm_message_node_get_value(protocolver);
179                         if (tmp == NULL) {
180                                 g_warning("Check 4a failed (no protocol attribute in chat message)");
181                                 return false;
182                         }
183                 }
185                 // 5a.  The protocol version must be supported.
186                 if (atoi(tmp) > HIGHEST_SUPPORTED) {
187                         g_warning("Check 5a failed (received a message with protocol version %s, but version %s is not supported)", tmp, tmp);
188                         return false;
189                 }
191         // ...otherwise, if this is a groupchat message, we may not have a protocol version 
192         // (since it may be communication from the Jabber server).  In this case, we have a
193         // different set of sanity checks.
194         } else if (msubtype == LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
195                 // 4b.
196                 // In a chatroom situation, we need to ensure that we don't process messages that
197                 // originated from us.
198                 int cutoff = sender.find_last_of('/') + 1;
199                 if (sender.substr(cutoff, sender.length()) == this->_sm->session_data->chat_handle) {
200                         return false;
201                 }
202                 // TODO: 6b.  If the message is NOT from the Jabber server, then check the protocol version.
203         }
205         // If all tests pass, then the message is at least valid.  
206         // Correct context has not yet been established, however; that is the job of the default handler
207         // and hasValidReceiveContext.
209         return true;
212 MessageType
213 MessageHandler::_getType(LmMessage* message)
215         LmMessageNode* root;
216         LmMessageNode* typenode;
218         root = lm_message_get_node(message);
219         if (root != NULL) {
220                 typenode = lm_message_node_get_child(root, MESSAGE_TYPE);
221                 if (typenode != NULL) {
222                         return static_cast< MessageType >(atoi(lm_message_node_get_value(typenode)));
223                 }
224         }
225         return UNKNOWN;
228 JabberMessage
229 MessageHandler::_extractData(LmMessage* message)
232         JabberMessage jm(message);
233         LmMessageNode* root;
234         LmMessageNode* sequence;
235         LmMessageNode* body;
236         gchar const* tmp;
238         root = lm_message_get_node(message);
240         if (root != NULL) {
241                 sequence = lm_message_node_get_child(root, MESSAGE_SEQNUM);
242                 body = lm_message_node_get_child(root, MESSAGE_BODY);
244                 jm.sender = lm_message_node_get_attribute(root, MESSAGE_FROM);
246                 if (sequence) {
247                         tmp = lm_message_node_get_value(sequence);
248                         if (tmp != NULL) {
249                                 jm.sequence = atoi(tmp);
250                         }
251                 }
253                 if (body) {
254                         tmp = lm_message_node_get_value(body);
255                         if (tmp != NULL) {
256                                 jm.body = tmp;
257                         }
258                 }
260         } else {
261                 jm.sequence = 0;
262                 jm.sender = "";
263                 jm.body = "";
264         }
266         return jm;
268         
269 LmHandlerResult
270 MessageHandler::_default(LmMessage* message)
272         std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status;
274         // Pass groupchat messages with no Inkboard type off to the chat message handler
275         if (this->_getType(message) == UNKNOWN) {
276                 if (lm_message_get_sub_type(message) == LM_MESSAGE_SUB_TYPE_GROUPCHAT) {
277                         if (status[IN_CHATROOM] || status[CONNECTING_TO_CHAT]) {
278                                 return this->_sm->chat_handler()->parse(message);
279                         }
280                 } else {
281                         return LM_HANDLER_RESULT_REMOVE_MESSAGE;
282                 }
283         }
284         
285         if (this->_hasValidReceiveContext(message)) {
286                 // Extract message data
287                 JabberMessage msg = this->_extractData(message);
288                 MessageType type = this->_getType(message);
290                 // Call message handler and return instruction value to Loudmouth
292                 return (*this->_received_message_processors[type])(type, msg);
293         } else {
294                 g_warning("Default message handler received message in invalid receive context; discarding message.");
295                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
296         }
299 LmHandlerResult
300 MessageHandler::_presence(LmMessage* message)
302         LmMessageNode* root;
303         LmMessageSubType msubtype;
304         gchar const* tmp;
305         std::string sender;
307         SessionData* sd = this->_sm->session_data;
308         std::bitset< NUM_FLAGS >& status = this->_sm->session_data->status;
310         root = lm_message_get_node(message);
311         if (root == NULL) {
312                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
313         }
315         tmp = lm_message_node_get_attribute(root, MESSAGE_FROM);
316         if (tmp == NULL) {
317                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
318         } else {
319                 sender = tmp;
320         }
322         msubtype = lm_message_get_sub_type(message);
323         if (status[CONNECTING_TO_CHAT] || status[IN_CHATROOM]) {
324                 this->_sm->chat_handler()->parse(message);
325         } else {
326                 switch(msubtype) {
327                         case LM_MESSAGE_SUB_TYPE_UNAVAILABLE:
328                                 // remove buddy from online roster
329                                 sd->buddyList.erase(sender);
331                                 // if this buddy is in a 1-1 session with us, we need to exit
332                                 // the whiteboard
333                                 if (status[IN_WHITEBOARD] && !(status[IN_CHATROOM]) && strcasecmp(sender.c_str(), sd->recipient) == 0) {
334                                         status.set(IN_WHITEBOARD, 0);
335                                         this->_sm->userDisconnectedFromWhiteboard(sender);
336                                         this->_sm->closeSession();
337                                 }
338                                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
339                                 
340                         case LM_MESSAGE_SUB_TYPE_AVAILABLE:
341                                 // we don't want to insert an entry into a buddy list
342                                 // if it's our own presence
343                                 if (sender != lm_connection_get_jid(this->_sm->session_data->connection)) {
344                                         sd->buddyList.insert(sender);
345                                 }
346                                 return LM_HANDLER_RESULT_REMOVE_MESSAGE;
347                         default:
348                                 break;
349                 }
350         }
351         return LM_HANDLER_RESULT_REMOVE_MESSAGE;
354 LmHandlerResult
355 MessageHandler::_error(LmMessage* message)
357         LmMessageNode* root;
358         LmMessageSubType msubtype;
360         root = lm_message_get_node(message);
361         if (root != NULL) {
362                 msubtype = lm_message_get_sub_type(message);
363                 if (msubtype == LM_MESSAGE_SUB_TYPE_ERROR) {
364                         gchar* error = g_strdup(lm_message_node_get_value(root));
365                         g_warning(error);
367                         // TODO: more robust error handling code
368                         this->_sm->disconnectFromDocument();
369                         this->_sm->disconnectFromServer();
370                         this->_sm->connectionError(error);
371                 }
372         }
374         return LM_HANDLER_RESULT_REMOVE_MESSAGE;
377 void
378 MessageHandler::_initializeContexts()
380         initialize_received_message_contexts(_received_message_contexts);       
381         message_contexts_initialized = true;
384 void
385 MessageHandler::_initializeProcessors()
387         initialize_received_message_processors(this->_sm, this->_received_message_processors);  
390 void
391 MessageHandler::_destructProcessors()
393         destroy_received_message_processors(this->_received_message_processors);
397 char const*
398 MessageHandler::ink_type_to_string(gint ink_type) {
399         switch(ink_type) {
400                 case Inkscape::Whiteboard::CHANGE_NOT_REPEATABLE:
401                         return "CHANGE_NOT_REPEATABLE";
402                 case Inkscape::Whiteboard::CHANGE_REPEATABLE:
403                         return "CHANGE_REPEATABLE";
404                 case Inkscape::Whiteboard::DUMMY_CHANGE:
405                         return "DUMMY_CHANGE";
406                 case Inkscape::Whiteboard::CHANGE_COMMIT:
407                         return "CHANGE_COMMIT";
408                 case Inkscape::Whiteboard::CONNECT_REQUEST_USER:
409                         return "CONNECT_REQUEST_USER";
410                 case Inkscape::Whiteboard::CONNECT_REQUEST_RESPONSE_USER:
411                         return "CONNECT_REQUEST_RESPONSE_USER";
412                 case Inkscape::Whiteboard::CONNECT_REQUEST_RESPONSE_CHAT:
413                         return "CONNECT_REQUEST_RESPONSE_CHAT";
414                 case Inkscape::Whiteboard::DOCUMENT_SENDER_REQUEST:
415                         return "DOCUMENT_SENDER_REQUEST";
416                 case Inkscape::Whiteboard::DOCUMENT_SENDER_REQUEST_RESPONSE:
417                         return "DOCUMENT_SENDER_REQUEST_RESPONSE";
418                 case Inkscape::Whiteboard::DOCUMENT_REQUEST:
419                         return "DOCUMENT_REQUEST";
420                 case Inkscape::Whiteboard::DOCUMENT_BEGIN:
421                         return "DOCUMENT_BEGIN";
422                 case Inkscape::Whiteboard::DOCUMENT_END:
423                         return "DOCUMENT_END";
424                 case Inkscape::Whiteboard::CONNECTED_SIGNAL:
425                         return "CONNECTED_SIGNAL";
426                 case Inkscape::Whiteboard::DISCONNECTED_FROM_USER_SIGNAL:
427                         return "DISCONNECTED_FROM_USER_SIGNAL";
428                 case Inkscape::Whiteboard::CONNECT_REQUEST_REFUSED_BY_PEER:
429                         return "CONNECT_REQUEST_REFUSED_BY_PEER";
430                 case Inkscape::Whiteboard::UNSUPPORTED_PROTOCOL_VERSION:
431                         return "UNSUPPORTED_PROTOCOL_VERSION";
432                 case Inkscape::Whiteboard::CHATROOM_SYNCHRONIZE_REQUEST:
433                         return "CHATROOM_SYNCHRONIZE_REQUEST";
434                 case Inkscape::Whiteboard::CHATROOM_SYNCHRONIZE_RESPONSE:
435                         return "CHATROOM_SYNCHRONIZE_RESPONSE";
436                 case Inkscape::Whiteboard::ALREADY_IN_SESSION:
437                         return "ALREADY_IN_SESSION";
438                 default:
439                         return "UNKNOWN";
440         }
447 /*
448   Local Variables:
449   mode:c++
450   c-file-style:"stroustrup"
451   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
452   indent-tabs-mode:nil
453   fill-column:99
454   End:
455 */
456 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :