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 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;
213 }
215 MessageType
216 MessageHandler::_getType(LmMessage* message)
217 {
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;
229 }
231 JabberMessage
232 MessageHandler::_extractData(LmMessage* message)
233 {
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;
270 }
272 LmHandlerResult
273 MessageHandler::_default(LmMessage* message)
274 {
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 }
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 }
300 }
302 LmHandlerResult
303 MessageHandler::_presence(LmMessage* message)
304 {
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;
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 != lm_connection_get_jid(this->_sm->session_data->connection)) {
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;
355 }
357 LmHandlerResult
358 MessageHandler::_error(LmMessage* message)
359 {
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;
378 }
380 void
381 MessageHandler::_initializeContexts()
382 {
383 initialize_received_message_contexts(_received_message_contexts);
384 message_contexts_initialized = true;
385 }
387 void
388 MessageHandler::_initializeProcessors()
389 {
390 initialize_received_message_processors(this->_sm, this->_received_message_processors);
391 }
393 void
394 MessageHandler::_destructProcessors()
395 {
396 destroy_received_message_processors(this->_received_message_processors);
397 }
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 }
444 }
446 }
448 }
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 :