Fix #193: Pidgin Status changes stop working (III)
[siplcs.git] / src / core / sipe-groupchat.c
blobd3ef3ae30a8601f5180f116dddf37cedb8e63853
1 /**
2 * @file sipe-groupchat.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2013 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 /**
24 * This module implements the OCS2007R2 Group Chat functionality
26 * Documentation references:
28 * Microsoft TechNet: Key Protocols and Windows Services Used by Group Chat
29 * <http://technet.microsoft.com/en-us/library/ee323484%28office.13%29.aspx>
30 * Microsoft TechNet: Group Chat Call Flows
31 * <http://technet.microsoft.com/en-us/library/ee323524%28office.13%29.aspx>
32 * Microsoft Office Communications Server 2007 R2 Technical Reference Guide
33 * <http://go.microsoft.com/fwlink/?LinkID=159649>
34 * XML XCCOS message specification
35 * <???> (searches on the internet currently reveal nothing)
38 * @TODO:
40 * -.cmd:getserverinfo
41 * <sib domain="<DOMAIN>" infoType="123" />
42 * rpl:getservinfo
43 * <sib infoType="123"
44 * serverTime="2010-09-14T14:26:17.6206356Z"
45 * searchLimit="999"
46 * messageSizeLimit="512"
47 * storySizeLimit="4096"
48 * rootUri="ma-cat://<DOMAIN>/<GUID>"
49 * dbVersion="3ea3a5a8-ef36-46cf-898f-7a5133931d63"
50 * />
52 * is there any information in there we would need/use?
54 * - cmd:getpref/rpl:getpref/cmd:setpref/rpl:setpref
55 * probably useless, as libpurple stores configuration locally
57 * can store base64 encoded "free text" in key/value fashion
58 * <cmd id="cmd:getpref" seqid="x">
59 * <data>
60 * <pref label="kedzie.GroupChannels"
61 * seqid="71"
62 * createdefault="true" />
63 * </data>
64 * </cmd>
65 * <cmd id="cmd:setpref" seqid="x">
66 * <data>
67 * <pref label="kedzie.GroupChannels"
68 * seqid="71"
69 * createdefault="false"
70 * content="<BASE64 text>" />
71 * </data>
72 * </cmd>
74 * use this to sync chats in buddy list on multiple clients?
76 * - cmd:bccontext
77 * send after cmd:join to trigger rpl:bccontext
79 * - rpl:bccontext
80 * according to available documentation delivers channel history, etc.
81 * [no log file examples]
82 * can we add the history to the window when we open the window, or would
83 * that confuse users that use history based on client log?
85 * - cmd:getinv
86 * <inv inviteId="1" domain="<DOMAIN>" />
87 * rpl:getinv
88 * ???
90 * according to documentation should provide list of outstanding invites.
91 * [no log file examples]
92 * should we automatically join those channels or ask user to join/add?
94 * - chatserver_command_message()
95 * needs to support multiple <chatgrp> nodes?
96 * [no log file examples]
98 * - create/delete chat rooms
99 * [no log file examples]
100 * are these related to this functionality?
102 * <cmd id="cmd:nodespermcreatechild" seqid="1">
103 * <data />
104 * </cmd>
105 * <rpl id="rpl:nodespermcreatechild" seqid="1">
106 * <commandid seqid="1" envid="xxx" />
107 * <resp code="200">SUCCESS_OK</resp>
108 * <data />
109 * </rpl>
111 * - file transfer (uses HTTPS PUT/GET via a filestore server)
112 * [no log file examples]
116 #ifdef HAVE_CONFIG_H
117 #include "config.h"
118 #endif
120 #include <stdlib.h>
121 #include <string.h>
123 #include <glib.h>
125 #include "sipe-common.h"
126 #include "sipmsg.h"
127 #include "sip-transport.h"
128 #include "sipe-backend.h"
129 #include "sipe-chat.h"
130 #include "sipe-core.h"
131 #include "sipe-core-private.h"
132 #include "sipe-dialog.h"
133 #include "sipe-groupchat.h"
134 #include "sipe-im.h"
135 #include "sipe-nls.h"
136 #include "sipe-schedule.h"
137 #include "sipe-session.h"
138 #include "sipe-utils.h"
139 #include "sipe-xml.h"
141 #define GROUPCHAT_RETRY_TIMEOUT 5*60 /* seconds */
144 * aib node - magic numbers?
146 * Example:
147 * <aib key="3984" value="0,1,2,3,4,5,7,9,10,12,13,14,15,16,17" />
148 * <aib key="12276" value="6,8,11" />
150 * "value" corresponds to the "id" attribute in uib nodes.
152 * @TODO: Confirm "guessed" meaning of the magic numbers:
153 * 3984 = normal users
154 * 12276 = channel operators
156 #define GROUPCHAT_AIB_KEY_USER "3984"
157 #define GROUPCHAT_AIB_KEY_CHANOP "12276"
159 struct sipe_groupchat {
160 struct sip_session *session;
161 gchar *domain;
162 GSList *join_queue;
163 GHashTable *uri_to_chat_session;
164 GHashTable *msgs;
165 guint envid;
166 gboolean connected;
169 struct sipe_groupchat_msg {
170 GHashTable *container;
171 struct sipe_chat_session *session;
172 gchar *content;
173 gchar *xccos;
174 guint envid;
177 /* GDestroyNotify */
178 static void sipe_groupchat_msg_free(gpointer data) {
179 struct sipe_groupchat_msg *msg = data;
180 g_free(msg->content);
181 g_free(msg->xccos);
182 g_free(msg);
185 /* GDestroyNotify */
186 static void sipe_groupchat_msg_remove(gpointer data) {
187 struct sipe_groupchat_msg *msg = data;
188 g_hash_table_remove(msg->container, &msg->envid);
191 static void sipe_groupchat_allocate(struct sipe_core_private *sipe_private)
193 struct sipe_groupchat *groupchat = g_new0(struct sipe_groupchat, 1);
195 groupchat->uri_to_chat_session = g_hash_table_new(g_str_hash, g_str_equal);
196 groupchat->msgs = g_hash_table_new_full(g_int_hash, g_int_equal,
197 NULL,
198 sipe_groupchat_msg_free);
199 groupchat->envid = rand();
200 groupchat->connected = FALSE;
201 sipe_private->groupchat = groupchat;
204 static void sipe_groupchat_free_join_queue(struct sipe_groupchat *groupchat)
206 sipe_utils_slist_free_full(groupchat->join_queue, g_free);
207 groupchat->join_queue = NULL;
210 void sipe_groupchat_free(struct sipe_core_private *sipe_private)
212 struct sipe_groupchat *groupchat = sipe_private->groupchat;
213 if (groupchat) {
214 sipe_groupchat_free_join_queue(groupchat);
215 g_hash_table_destroy(groupchat->msgs);
216 g_hash_table_destroy(groupchat->uri_to_chat_session);
217 g_free(groupchat->domain);
218 g_free(groupchat);
219 sipe_private->groupchat = NULL;
223 static struct sipe_groupchat_msg *generate_xccos_message(struct sipe_groupchat *groupchat,
224 const gchar *content)
226 struct sipe_groupchat_msg *msg = g_new0(struct sipe_groupchat_msg, 1);
228 msg->container = groupchat->msgs;
229 msg->envid = groupchat->envid++;
230 msg->xccos = g_strdup_printf("<xccos ver=\"1\" envid=\"%u\" xmlns=\"urn:parlano:xml:ns:xccos\">"
231 "%s"
232 "</xccos>",
233 msg->envid,
234 content);
236 g_hash_table_insert(groupchat->msgs, &msg->envid, msg);
238 return(msg);
242 * Create short-lived dialog with ocschat@<domain> (or user specified value)
243 * This initiates the Group Chat feature
245 void sipe_groupchat_init(struct sipe_core_private *sipe_private)
247 const gchar *setting = sipe_backend_setting(SIPE_CORE_PUBLIC,
248 SIPE_SETTING_GROUPCHAT_USER);
249 gboolean user_set = !is_empty(setting);
250 gchar **parts = g_strsplit(user_set ? setting : sipe_private->username,
251 "@", 2);
252 gboolean domain_found = !is_empty(parts[1]);
253 const gchar *user = "ocschat";
254 const gchar *domain = parts[domain_found ? 1 : 0];
255 gchar *chat_uri;
256 struct sip_session *session;
257 struct sipe_groupchat *groupchat;
259 /* User specified valid 'user@company.com' */
260 if (user_set && domain_found && !is_empty(parts[0]))
261 user = parts[0];
263 SIPE_DEBUG_INFO("sipe_groupchat_init: username '%s' setting '%s' split '%s'/'%s' GC user %s@%s",
264 sipe_private->username, setting ? setting : "(null)", parts[0],
265 parts[1] ? parts[1] : "(null)", user, domain);
267 if (!sipe_private->groupchat)
268 sipe_groupchat_allocate(sipe_private);
269 groupchat = sipe_private->groupchat;
271 chat_uri = g_strdup_printf("sip:%s@%s", user, domain);
272 session = sipe_session_find_or_add_im(sipe_private,
273 chat_uri);
274 session->is_groupchat = TRUE;
275 sipe_im_invite(sipe_private, session, chat_uri,
276 NULL, NULL, NULL, FALSE);
278 g_free(groupchat->domain);
279 groupchat->domain = g_strdup(domain);
281 g_free(chat_uri);
282 g_strfreev(parts);
285 /* sipe_schedule_action */
286 static void groupchat_init_retry_cb(struct sipe_core_private *sipe_private,
287 SIPE_UNUSED_PARAMETER gpointer data)
289 sipe_groupchat_init(sipe_private);
292 static void groupchat_init_retry(struct sipe_core_private *sipe_private)
294 struct sipe_groupchat *groupchat = sipe_private->groupchat;
296 SIPE_DEBUG_INFO_NOFORMAT("groupchat_init_retry: trying again later...");
298 groupchat->session = NULL;
299 groupchat->connected = FALSE;
301 sipe_schedule_seconds(sipe_private,
302 "<+grouchat-retry>",
303 NULL,
304 GROUPCHAT_RETRY_TIMEOUT,
305 groupchat_init_retry_cb,
306 NULL);
309 void sipe_groupchat_invite_failed(struct sipe_core_private *sipe_private,
310 struct sip_session *session)
312 struct sipe_groupchat *groupchat = sipe_private->groupchat;
313 const gchar *setting = sipe_backend_setting(SIPE_CORE_PUBLIC,
314 SIPE_SETTING_GROUPCHAT_USER);
315 gboolean retry = FALSE;
317 if (groupchat->session) {
318 /* response to group chat server invite */
319 SIPE_DEBUG_ERROR_NOFORMAT("can't connect to group chat server!");
321 /* group chat server exists, but communication failed */
322 retry = TRUE;
323 } else {
324 /* response to initial invite */
325 SIPE_DEBUG_INFO_NOFORMAT("no group chat server found.");
328 sipe_session_close(sipe_private, session);
330 if (!is_empty(setting)) {
331 gchar *msg = g_strdup_printf(_("Group Chat Proxy setting is incorrect:\n\n\t%s\n\nPlease update your Account."),
332 setting);
333 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
334 _("Couldn't find Group Chat server!"),
335 msg);
336 g_free(msg);
338 /* user specified group chat settings: we should retry */
339 retry = TRUE;
342 if (retry) {
343 groupchat_init_retry(sipe_private);
344 } else {
345 SIPE_DEBUG_INFO_NOFORMAT("disabling group chat feature.");
349 static gchar *generate_chanid_node(const gchar *uri, guint key)
351 /* ma-chan://<domain>/<value> */
352 gchar **parts = g_strsplit(uri, "/", 4);
353 gchar *chanid = NULL;
355 if (parts[2] && parts[3]) {
356 chanid = g_strdup_printf("<chanid key=\"%d\" domain=\"%s\" value=\"%s\"/>",
357 key, parts[2], parts[3]);
358 } else {
359 SIPE_DEBUG_ERROR("generate_chanid_node: mal-formed URI '%s'",
360 uri);
362 g_strfreev(parts);
364 return chanid;
367 static struct sipe_groupchat_msg *chatserver_command(struct sipe_core_private *sipe_private,
368 const gchar *cmd);
370 void sipe_groupchat_invite_response(struct sipe_core_private *sipe_private,
371 struct sip_dialog *dialog)
373 struct sipe_groupchat *groupchat = sipe_private->groupchat;
375 SIPE_DEBUG_INFO_NOFORMAT("sipe_groupchat_invite_response");
377 if (!groupchat->session) {
378 /* response to initial invite */
379 struct sipe_groupchat_msg *msg = generate_xccos_message(groupchat,
380 "<cmd id=\"cmd:requri\" seqid=\"1\"><data/></cmd>");
381 sip_transport_info(sipe_private,
382 "Content-Type: text/plain\r\n",
383 msg->xccos,
384 dialog,
385 NULL);
386 sipe_groupchat_msg_remove(msg);
388 } else {
389 /* response to group chat server invite */
390 gchar *invcmd;
392 SIPE_DEBUG_INFO_NOFORMAT("connection to group chat server established.");
394 groupchat->connected = TRUE;
396 /* Any queued joins? */
397 if (groupchat->join_queue) {
398 GString *cmd = g_string_new("<cmd id=\"cmd:bjoin\" seqid=\"1\">"
399 "<data>");
400 GSList *entry;
401 guint i = 0;
403 /* We used g_slist_prepend() to create the list */
404 groupchat->join_queue = entry = g_slist_reverse(groupchat->join_queue);
405 while (entry) {
406 gchar *chanid = generate_chanid_node(entry->data, i++);
407 g_string_append(cmd, chanid);
408 g_free(chanid);
409 entry = entry->next;
411 sipe_groupchat_free_join_queue(groupchat);
413 g_string_append(cmd, "</data></cmd>");
414 chatserver_command(sipe_private, cmd->str);
415 g_string_free(cmd, TRUE);
418 /* Request outstanding invites from server */
419 invcmd = g_strdup_printf("<cmd id=\"cmd:getinv\" seqid=\"1\">"
420 "<data>"
421 "<inv inviteId=\"1\" domain=\"%s\"/>"
422 "</data>"
423 "</cmd>", groupchat->domain);
424 chatserver_command(sipe_private, invcmd);
425 g_free(invcmd);
429 /* TransCallback */
430 static gboolean chatserver_command_response(struct sipe_core_private *sipe_private,
431 struct sipmsg *msg,
432 struct transaction *trans)
434 if (msg->response != 200) {
435 struct sipe_groupchat_msg *gmsg = trans->payload->data;
436 struct sipe_chat_session *chat_session = gmsg->session;
438 SIPE_DEBUG_INFO("chatserver_command_response: failure %d", msg->response);
440 if (chat_session) {
441 gchar *label = g_strdup_printf(_("This message was not delivered to chat room '%s'"),
442 chat_session->title);
443 gchar *errmsg = g_strdup_printf("%s:\n<font color=\"#888888\"></b>%s<b></font>",
444 label, gmsg->content);
445 g_free(label);
446 sipe_backend_notify_message_error(SIPE_CORE_PUBLIC,
447 chat_session->backend,
448 NULL,
449 errmsg);
450 g_free(errmsg);
453 return TRUE;
456 static struct sipe_groupchat_msg *chatserver_command(struct sipe_core_private *sipe_private,
457 const gchar *cmd)
459 struct sipe_groupchat *groupchat = sipe_private->groupchat;
460 struct sipe_groupchat_msg *msg = generate_xccos_message(groupchat, cmd);
462 struct sip_dialog *dialog = sipe_dialog_find(groupchat->session,
463 groupchat->session->with);
465 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
466 struct transaction *trans = sip_transport_info(sipe_private,
467 "Content-Type: text/plain\r\n",
468 msg->xccos,
469 dialog,
470 chatserver_command_response);
472 payload->destroy = sipe_groupchat_msg_remove;
473 payload->data = msg;
474 trans->payload = payload;
476 return(msg);
479 static void chatserver_response_uri(struct sipe_core_private *sipe_private,
480 struct sip_session *session,
481 SIPE_UNUSED_PARAMETER guint result,
482 SIPE_UNUSED_PARAMETER const gchar *message,
483 const sipe_xml *xml)
485 const sipe_xml *uib = sipe_xml_child(xml, "uib");
486 const gchar *uri = sipe_xml_attribute(uib, "uri");
488 /* drop connection to ocschat@<domain> again */
489 sipe_session_close(sipe_private, session);
491 if (uri) {
492 struct sipe_groupchat *groupchat = sipe_private->groupchat;
494 SIPE_DEBUG_INFO("chatserver_response_uri: '%s'", uri);
496 groupchat->session = session = sipe_session_find_or_add_im(sipe_private,
497 uri);
499 session->is_groupchat = TRUE;
500 sipe_im_invite(sipe_private, session, uri, NULL, NULL, NULL, FALSE);
501 } else {
502 SIPE_DEBUG_WARNING_NOFORMAT("process_incoming_info_groupchat: no server URI found!");
503 groupchat_init_retry(sipe_private);
507 static void chatserver_response_channel_search(struct sipe_core_private *sipe_private,
508 SIPE_UNUSED_PARAMETER struct sip_session *session,
509 guint result,
510 const gchar *message,
511 const sipe_xml *xml)
513 struct sipe_core_public *sipe_public = SIPE_CORE_PUBLIC;
515 if (result != 200) {
516 sipe_backend_notify_error(sipe_public,
517 _("Error retrieving room list"),
518 message);
519 } else {
520 const sipe_xml *chanib;
522 for (chanib = sipe_xml_child(xml, "chanib");
523 chanib;
524 chanib = sipe_xml_twin(chanib)) {
525 const gchar *name = sipe_xml_attribute(chanib, "name");
526 const gchar *desc = sipe_xml_attribute(chanib, "description");
527 const gchar *uri = sipe_xml_attribute(chanib, "uri");
528 const sipe_xml *node;
529 guint user_count = 0;
530 guint32 flags = 0;
532 /* information */
533 for (node = sipe_xml_child(chanib, "info");
534 node;
535 node = sipe_xml_twin(node)) {
536 const gchar *id = sipe_xml_attribute(node, "id");
537 gchar *data;
539 if (!id) continue;
541 data = sipe_xml_data(node);
542 if (data) {
543 if (sipe_strcase_equal(id, "urn:parlano:ma:info:ucnt")) {
544 user_count = g_ascii_strtoll(data, NULL, 10);
545 } else if (sipe_strcase_equal(id, "urn:parlano:ma:info:visibilty")) {
546 if (sipe_strcase_equal(data, "private")) {
547 flags |= SIPE_GROUPCHAT_ROOM_PRIVATE;
550 g_free(data);
554 /* properties */
555 for (node = sipe_xml_child(chanib, "prop");
556 node;
557 node = sipe_xml_twin(node)) {
558 const gchar *id = sipe_xml_attribute(node, "id");
559 gchar *data;
561 if (!id) continue;
563 data = sipe_xml_data(node);
564 if (data) {
565 gboolean value = sipe_strcase_equal(data, "true");
566 g_free(data);
568 if (value) {
569 guint32 add = 0;
570 if (sipe_strcase_equal(id, "urn:parlano:ma:prop:filepost")) {
571 add = SIPE_GROUPCHAT_ROOM_FILEPOST;
572 } else if (sipe_strcase_equal(id, "urn:parlano:ma:prop:invite")) {
573 add = SIPE_GROUPCHAT_ROOM_INVITE;
574 } else if (sipe_strcase_equal(id, "urn:parlano:ma:prop:logged")) {
575 add = SIPE_GROUPCHAT_ROOM_LOGGED;
577 flags |= add;
582 SIPE_DEBUG_INFO("group chat channel '%s': '%s' (%s) with %u users, flags 0x%x",
583 name, desc, uri, user_count, flags);
584 sipe_backend_groupchat_room_add(sipe_public,
585 uri, name, desc,
586 user_count, flags);
590 sipe_backend_groupchat_room_terminate(sipe_public);
593 static gboolean is_chanop(const sipe_xml *aib)
595 return sipe_strequal(sipe_xml_attribute(aib, "key"),
596 GROUPCHAT_AIB_KEY_CHANOP);
599 static void add_user(struct sipe_chat_session *chat_session,
600 const gchar *uri,
601 gboolean new, gboolean chanop)
603 SIPE_DEBUG_INFO("add_user: %s%s%s to room %s (%s)",
604 new ? "new " : "",
605 chanop ? "chanop " : "",
606 uri,
607 chat_session->title, chat_session->id);
608 sipe_backend_chat_add(chat_session->backend, uri, new);
609 if (chanop)
610 sipe_backend_chat_operator(chat_session->backend, uri);
613 static void chatserver_response_join(struct sipe_core_private *sipe_private,
614 SIPE_UNUSED_PARAMETER struct sip_session *session,
615 guint result,
616 const gchar *message,
617 const sipe_xml *xml)
619 if (result != 200) {
620 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
621 _("Error joining chat room"),
622 message);
623 } else {
624 struct sipe_groupchat *groupchat = sipe_private->groupchat;
625 const sipe_xml *node;
626 GHashTable *user_ids = g_hash_table_new(g_str_hash, g_str_equal);
628 /* Extract user IDs & URIs and generate ID -> URI map */
629 for (node = sipe_xml_child(xml, "uib");
630 node;
631 node = sipe_xml_twin(node)) {
632 const gchar *id = sipe_xml_attribute(node, "id");
633 const gchar *uri = sipe_xml_attribute(node, "uri");
634 if (id && uri)
635 g_hash_table_insert(user_ids,
636 (gpointer) id,
637 (gpointer) uri);
640 /* Process channel data */
641 for (node = sipe_xml_child(xml, "chanib");
642 node;
643 node = sipe_xml_twin(node)) {
644 const gchar *uri = sipe_xml_attribute(node, "uri");
646 if (uri) {
647 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
648 uri);
649 gboolean new = (chat_session == NULL);
650 const gchar *attr = sipe_xml_attribute(node, "name");
651 char *self = sip_uri_self(sipe_private);
652 const sipe_xml *aib;
654 if (new) {
655 chat_session = sipe_chat_create_session(SIPE_CHAT_TYPE_GROUPCHAT,
656 sipe_xml_attribute(node,
657 "uri"),
658 attr ? attr : "");
659 g_hash_table_insert(groupchat->uri_to_chat_session,
660 chat_session->id,
661 chat_session);
663 SIPE_DEBUG_INFO("joined room '%s' (%s)",
664 chat_session->title,
665 chat_session->id);
666 chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
667 chat_session,
668 chat_session->title,
669 self);
670 } else {
671 SIPE_DEBUG_INFO("rejoining room '%s' (%s)",
672 chat_session->title,
673 chat_session->id);
674 sipe_backend_chat_rejoin(SIPE_CORE_PUBLIC,
675 chat_session->backend,
676 self,
677 chat_session->title);
679 g_free(self);
681 attr = sipe_xml_attribute(node, "topic");
682 if (attr) {
683 sipe_backend_chat_topic(chat_session->backend,
684 attr);
687 /* Process user map for channel */
688 for (aib = sipe_xml_child(node, "aib");
689 aib;
690 aib = sipe_xml_twin(aib)) {
691 const gchar *value = sipe_xml_attribute(aib, "value");
692 gboolean chanop = is_chanop(aib);
693 gchar **ids = g_strsplit(value, ",", 0);
695 if (ids) {
696 gchar **uid = ids;
698 while (*uid) {
699 const gchar *uri = g_hash_table_lookup(user_ids,
700 *uid);
701 if (uri)
702 add_user(chat_session,
703 uri,
704 FALSE,
705 chanop);
706 uid++;
709 g_strfreev(ids);
715 g_hash_table_destroy(user_ids);
719 static void chatserver_response_part(struct sipe_core_private *sipe_private,
720 SIPE_UNUSED_PARAMETER struct sip_session *session,
721 guint result,
722 const gchar *message,
723 const sipe_xml *xml)
725 if (result != 200) {
726 SIPE_DEBUG_WARNING("chatserver_response_part: failed with %d: %s. Dropping room",
727 result, message);
728 } else {
729 struct sipe_groupchat *groupchat = sipe_private->groupchat;
730 const gchar *uri = sipe_xml_attribute(sipe_xml_child(xml, "chanib"),
731 "uri");
732 struct sipe_chat_session *chat_session;
734 if (uri &&
735 (chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
736 uri))) {
738 SIPE_DEBUG_INFO("leaving room '%s' (%s)",
739 chat_session->title, chat_session->id);
741 g_hash_table_remove(groupchat->uri_to_chat_session,
742 uri);
743 sipe_chat_remove_session(chat_session);
745 } else {
746 SIPE_DEBUG_WARNING("chatserver_response_part: unknown chat room uri '%s'",
747 uri ? uri : "");
752 static void chatserver_notice_join(struct sipe_core_private *sipe_private,
753 SIPE_UNUSED_PARAMETER struct sip_session *session,
754 SIPE_UNUSED_PARAMETER guint result,
755 SIPE_UNUSED_PARAMETER const gchar *message,
756 const sipe_xml *xml)
758 struct sipe_groupchat *groupchat = sipe_private->groupchat;
759 const sipe_xml *uib;
761 for (uib = sipe_xml_child(xml, "uib");
762 uib;
763 uib = sipe_xml_twin(uib)) {
764 const gchar *uri = sipe_xml_attribute(uib, "uri");
766 if (uri) {
767 const sipe_xml *aib;
769 for (aib = sipe_xml_child(uib, "aib");
770 aib;
771 aib = sipe_xml_twin(aib)) {
772 const gchar *domain = sipe_xml_attribute(aib, "domain");
773 const gchar *path = sipe_xml_attribute(aib, "value");
775 if (domain && path) {
776 gchar *room_uri = g_strdup_printf("ma-chan://%s/%s",
777 domain, path);
778 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
779 room_uri);
780 if (chat_session)
781 add_user(chat_session,
782 uri,
783 TRUE,
784 is_chanop(aib));
786 g_free(room_uri);
793 static void chatserver_notice_part(struct sipe_core_private *sipe_private,
794 SIPE_UNUSED_PARAMETER struct sip_session *session,
795 SIPE_UNUSED_PARAMETER guint result,
796 SIPE_UNUSED_PARAMETER const gchar *message,
797 const sipe_xml *xml)
799 struct sipe_groupchat *groupchat = sipe_private->groupchat;
800 const sipe_xml *chanib;
802 for (chanib = sipe_xml_child(xml, "chanib");
803 chanib;
804 chanib = sipe_xml_twin(chanib)) {
805 const gchar *room_uri = sipe_xml_attribute(chanib, "uri");
807 if (room_uri) {
808 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
809 room_uri);
811 if (chat_session) {
812 const sipe_xml *uib;
814 for (uib = sipe_xml_child(chanib, "uib");
815 uib;
816 uib = sipe_xml_twin(uib)) {
817 const gchar *uri = sipe_xml_attribute(uib, "uri");
819 if (uri) {
820 SIPE_DEBUG_INFO("remove_user: %s from room %s (%s)",
821 uri,
822 chat_session->title,
823 chat_session->id);
824 sipe_backend_chat_remove(chat_session->backend,
825 uri);
833 static const struct response {
834 const gchar *key;
835 void (* const handler)(struct sipe_core_private *,
836 struct sip_session *,
837 guint result, const gchar *,
838 const sipe_xml *xml);
839 } response_table[] = {
840 { "rpl:requri", chatserver_response_uri },
841 { "rpl:chansrch", chatserver_response_channel_search },
842 { "rpl:join", chatserver_response_join },
843 { "rpl:bjoin", chatserver_response_join },
844 { "rpl:part", chatserver_response_part },
845 { "ntc:join", chatserver_notice_join },
846 { "ntc:bjoin", chatserver_notice_join },
847 { "ntc:part", chatserver_notice_part },
848 { NULL, NULL }
851 /* Handles rpl:XXX & ntc:YYY */
852 static void chatserver_response(struct sipe_core_private *sipe_private,
853 const sipe_xml *reply,
854 struct sip_session *session)
856 do {
857 const sipe_xml *resp, *data;
858 const gchar *id;
859 gchar *message;
860 guint result = 500;
861 const struct response *r;
863 id = sipe_xml_attribute(reply, "id");
864 if (!id) {
865 SIPE_DEBUG_INFO_NOFORMAT("chatserver_response: no reply ID found!");
866 continue;
869 resp = sipe_xml_child(reply, "resp");
870 if (resp) {
871 result = sipe_xml_int_attribute(resp, "code", 500);
872 message = sipe_xml_data(resp);
873 } else {
874 message = g_strdup("");
877 data = sipe_xml_child(reply, "data");
879 SIPE_DEBUG_INFO("chatserver_response: '%s' result (%d) %s",
880 id, result, message ? message : "");
882 for (r = response_table; r->key; r++) {
883 if (sipe_strcase_equal(id, r->key)) {
884 (*r->handler)(sipe_private, session, result, message, data);
885 break;
888 if (!r->key) {
889 SIPE_DEBUG_INFO_NOFORMAT("chatserver_response: ignoring unknown response");
892 g_free(message);
893 } while ((reply = sipe_xml_twin(reply)) != NULL);
896 static void chatserver_grpchat_message(struct sipe_core_private *sipe_private,
897 const sipe_xml *chatgrp)
899 struct sipe_groupchat *groupchat = sipe_private->groupchat;
900 const gchar *uri = sipe_xml_attribute(chatgrp, "chanUri");
901 const gchar *from = sipe_xml_attribute(chatgrp, "author");
902 gchar *text = sipe_xml_data(sipe_xml_child(chatgrp, "chat"));
903 struct sipe_chat_session *chat_session;
904 gchar *escaped;
906 if (!uri || !from) {
907 SIPE_DEBUG_INFO("chatserver_grpchat_message: message '%s' received without chat room URI or author!",
908 text ? text : "");
909 g_free(text);
910 return;
913 chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
914 uri);
915 if (!chat_session) {
916 SIPE_DEBUG_INFO("chatserver_grpchat_message: message '%s' from '%s' received from unknown chat room '%s'!",
917 text ? text : "", from, uri);
918 g_free(text);
919 return;
922 /* libxml2 decodes all entities, but the backend expects HTML */
923 escaped = g_markup_escape_text(text, -1);
924 g_free(text);
925 sipe_backend_chat_message(SIPE_CORE_PUBLIC, chat_session->backend,
926 from, escaped);
927 g_free(escaped);
930 void process_incoming_info_groupchat(struct sipe_core_private *sipe_private,
931 struct sipmsg *msg,
932 struct sip_session *session)
934 sipe_xml *xml = sipe_xml_parse(msg->body, msg->bodylen);
935 const sipe_xml *node;
937 /* @TODO: is this always correct?*/
938 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
940 if (!xml) return;
942 if (((node = sipe_xml_child(xml, "rpl")) != NULL) ||
943 ((node = sipe_xml_child(xml, "ntc")) != NULL)) {
944 chatserver_response(sipe_private, node, session);
945 } else if ((node = sipe_xml_child(xml, "grpchat")) != NULL) {
946 chatserver_grpchat_message(sipe_private, node);
947 } else {
948 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_info_groupchat: ignoring unknown response");
951 sipe_xml_free(xml);
954 void sipe_groupchat_send(struct sipe_core_private *sipe_private,
955 struct sipe_chat_session *chat_session,
956 const gchar *what)
958 struct sipe_groupchat *groupchat = sipe_private->groupchat;
959 gchar *cmd, *self, *timestamp, *tmp;
960 struct sipe_groupchat_msg *msg;
962 if (!groupchat || !chat_session)
963 return;
965 SIPE_DEBUG_INFO("sipe_groupchat_send: '%s' to %s",
966 what, chat_session->id);
968 self = sip_uri_self(sipe_private);
969 timestamp = sipe_utils_time_to_str(time(NULL));
972 * 'what' is already XML-escaped, e.g.
974 * " -> &quot;
975 * > -> &gt;
976 * < -> &lt;
977 * & -> &amp;
979 * No need to escape them here.
981 * Only exception are line breaks which are encoded as <br>.
982 * Replace them with the correct XML tag <br/>.
984 tmp = replace(what, "<br>", "<br/>");
985 cmd = g_strdup_printf("<grpchat id=\"grpchat\" seqid=\"1\" chanUri=\"%s\" author=\"%s\" ts=\"%s\">"
986 "<chat>%s</chat>"
987 "</grpchat>",
988 chat_session->id, self, timestamp, tmp);
989 g_free(tmp);
990 g_free(timestamp);
991 g_free(self);
992 msg = chatserver_command(sipe_private, cmd);
993 g_free(cmd);
995 msg->session = chat_session;
996 msg->content = g_strdup(what);
999 void sipe_groupchat_leave(struct sipe_core_private *sipe_private,
1000 struct sipe_chat_session *chat_session)
1002 struct sipe_groupchat *groupchat = sipe_private->groupchat;
1003 gchar *cmd;
1005 if (!groupchat || !chat_session)
1006 return;
1008 SIPE_DEBUG_INFO("sipe_groupchat_leave: %s", chat_session->id);
1010 cmd = g_strdup_printf("<cmd id=\"cmd:part\" seqid=\"1\">"
1011 "<data>"
1012 "<chanib uri=\"%s\"/>"
1013 "</data>"
1014 "</cmd>", chat_session->id);
1015 chatserver_command(sipe_private, cmd);
1016 g_free(cmd);
1019 gboolean sipe_core_groupchat_query_rooms(struct sipe_core_public *sipe_public)
1021 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1022 struct sipe_groupchat *groupchat = sipe_private->groupchat;
1024 if (!groupchat || !groupchat->connected)
1025 return FALSE;
1027 chatserver_command(sipe_private,
1028 "<cmd id=\"cmd:chansrch\" seqid=\"1\">"
1029 "<data>"
1030 "<qib qtype=\"BYNAME\" criteria=\"\" extended=\"false\"/>"
1031 "</data>"
1032 "</cmd>");
1034 return TRUE;
1037 void sipe_core_groupchat_join(struct sipe_core_public *sipe_public,
1038 const gchar *uri)
1040 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1041 struct sipe_groupchat *groupchat = sipe_private->groupchat;
1043 if (!g_str_has_prefix(uri, "ma-chan://"))
1044 return;
1046 if (!groupchat) {
1047 /* This happens when a user has set auto-join on a channel */
1048 sipe_groupchat_allocate(sipe_private);
1049 groupchat = sipe_private->groupchat;
1052 if (groupchat->connected) {
1053 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
1054 uri);
1056 /* Already joined? */
1057 if (chat_session) {
1059 /* Yes, update backend session */
1060 SIPE_DEBUG_INFO("sipe_core_groupchat_join: show '%s' (%s)",
1061 chat_session->title,
1062 chat_session->id);
1063 sipe_backend_chat_show(chat_session->backend);
1065 } else {
1066 /* No, send command out directly */
1067 gchar *chanid = generate_chanid_node(uri, 0);
1068 if (chanid) {
1069 gchar *cmd = g_strdup_printf("<cmd id=\"cmd:join\" seqid=\"1\">"
1070 "<data>%s</data>"
1071 "</cmd>",
1072 chanid);
1073 SIPE_DEBUG_INFO("sipe_core_groupchat_join: join %s",
1074 uri);
1075 chatserver_command(sipe_private, cmd);
1076 g_free(cmd);
1077 g_free(chanid);
1080 } else {
1081 /* Add it to the queue but avoid duplicates */
1082 if (!g_slist_find_custom(groupchat->join_queue, uri,
1083 sipe_strcompare)) {
1084 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_groupchat_join: URI queued");
1085 groupchat->join_queue = g_slist_prepend(groupchat->join_queue,
1086 g_strdup(uri));
1091 void sipe_groupchat_rejoin(struct sipe_core_private *sipe_private,
1092 struct sipe_chat_session *chat_session)
1094 struct sipe_groupchat *groupchat = sipe_private->groupchat;
1096 if (!groupchat) {
1097 /* First rejoined channel after reconnect will trigger this */
1098 sipe_groupchat_allocate(sipe_private);
1099 groupchat = sipe_private->groupchat;
1102 /* Remember "old" session, so that we don't recreate it at join */
1103 g_hash_table_insert(groupchat->uri_to_chat_session,
1104 chat_session->id,
1105 chat_session);
1106 sipe_core_groupchat_join(SIPE_CORE_PUBLIC, chat_session->id);
1110 Local Variables:
1111 mode: c
1112 c-file-style: "bsd"
1113 indent-tabs-mode: t
1114 tab-width: 8
1115 End: