cdcef74b833f28e302f9cb7d66a008439a4826d9
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 (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)
103 {
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;
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 }
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.
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 }
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;
210 }
212 MessageType
213 MessageHandler::_getType(LmMessage* message)
214 {
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;
226 }
228 JabberMessage
229 MessageHandler::_extractData(LmMessage* message)
230 {
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;
267 }
269 LmHandlerResult
270 MessageHandler::_default(LmMessage* message)
271 {
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 }
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 }
297 }
299 LmHandlerResult
300 MessageHandler::_presence(LmMessage* message)
301 {
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;
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;
352 }
354 LmHandlerResult
355 MessageHandler::_error(LmMessage* message)
356 {
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;
375 }
377 void
378 MessageHandler::_initializeContexts()
379 {
380 initialize_received_message_contexts(_received_message_contexts);
381 message_contexts_initialized = true;
382 }
384 void
385 MessageHandler::_initializeProcessors()
386 {
387 initialize_received_message_processors(this->_sm, this->_received_message_processors);
388 }
390 void
391 MessageHandler::_destructProcessors()
392 {
393 destroy_received_message_processors(this->_received_message_processors);
394 }
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 }
441 }
443 }
445 }
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 :