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];
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)
102 {
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;
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 }
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.
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 }
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;
209 }
211 MessageType
212 MessageHandler::_getType(LmMessage* message)
213 {
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;
225 }
227 JabberMessage
228 MessageHandler::_extractData(LmMessage* message)
229 {
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;
266 }
268 LmHandlerResult
269 MessageHandler::_default(LmMessage* message)
270 {
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 }
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 }
296 }
298 LmHandlerResult
299 MessageHandler::_presence(LmMessage* message)
300 {
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;
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;
351 }
353 LmHandlerResult
354 MessageHandler::_error(LmMessage* message)
355 {
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;
374 }
376 void
377 MessageHandler::_initializeContexts()
378 {
379 initialize_received_message_contexts(_received_message_contexts);
380 message_contexts_initialized = true;
381 }
383 void
384 MessageHandler::_initializeProcessors()
385 {
386 initialize_received_message_processors(this->_sm, this->_received_message_processors);
387 }
389 void
390 MessageHandler::_destructProcessors()
391 {
392 destroy_received_message_processors(this->_received_message_processors);
393 }
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 }
440 }
442 }
444 }
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 :