Fix #3198585: Extra line breaks
[siplcs.git] / src / core / sipe-groupchat.c
blobf79550c3745b4a4a4285058bf4cd994a19ff53ca
1 /**
2 * @file sipe-groupchat.c
4 * pidgin-sipe
6 * Copyright (C) 2010-11 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 GSList *entry = groupchat->join_queue;
207 while (entry) {
208 g_free(entry->data);
209 entry = entry->next;
211 g_slist_free(groupchat->join_queue);
212 groupchat->join_queue = NULL;
215 void sipe_groupchat_free(struct sipe_core_private *sipe_private)
217 struct sipe_groupchat *groupchat = sipe_private->groupchat;
218 if (groupchat) {
219 sipe_groupchat_free_join_queue(groupchat);
220 g_hash_table_destroy(groupchat->msgs);
221 g_hash_table_destroy(groupchat->uri_to_chat_session);
222 g_free(groupchat->domain);
223 g_free(groupchat);
224 sipe_private->groupchat = NULL;
228 static struct sipe_groupchat_msg *generate_xccos_message(struct sipe_groupchat *groupchat,
229 const gchar *content)
231 struct sipe_groupchat_msg *msg = g_new0(struct sipe_groupchat_msg, 1);
233 msg->container = groupchat->msgs;
234 msg->envid = groupchat->envid++;
235 msg->xccos = g_strdup_printf("<xccos ver=\"1\" envid=\"%u\" xmlns=\"urn:parlano:xml:ns:xccos\">"
236 "%s"
237 "</xccos>",
238 msg->envid,
239 content);
241 g_hash_table_insert(groupchat->msgs, &msg->envid, msg);
243 return(msg);
247 * Create short-lived dialog with ocschat@<domain> (or user specified value)
248 * This initiates the Group Chat feature
250 void sipe_groupchat_init(struct sipe_core_private *sipe_private)
252 const gchar *setting = sipe_backend_setting(SIPE_CORE_PUBLIC,
253 SIPE_SETTING_GROUPCHAT_USER);
254 gboolean user_set = !is_empty(setting);
255 gchar **parts = g_strsplit(user_set ? setting : sipe_private->username,
256 "@", 2);
257 gboolean domain_found = !is_empty(parts[1]);
258 const gchar *user = "ocschat";
259 const gchar *domain = parts[domain_found ? 1 : 0];
260 gchar *chat_uri;
261 struct sip_session *session;
262 struct sipe_groupchat *groupchat;
264 /* User specified valid 'user@company.com' */
265 if (user_set && domain_found && !is_empty(parts[0]))
266 user = parts[0];
268 SIPE_DEBUG_INFO("sipe_groupchat_init: username '%s' setting '%s' split '%s'/'%s' GC user %s@%s",
269 sipe_private->username, setting ? setting : "(null)", parts[0],
270 parts[1] ? parts[1] : "(null)", user, domain);
272 if (!sipe_private->groupchat)
273 sipe_groupchat_allocate(sipe_private);
274 groupchat = sipe_private->groupchat;
276 chat_uri = g_strdup_printf("sip:%s@%s", user, domain);
277 session = sipe_session_find_or_add_im(sipe_private,
278 chat_uri);
279 session->is_groupchat = TRUE;
280 sipe_im_invite(sipe_private, session, chat_uri,
281 NULL, NULL, NULL, FALSE);
283 g_free(groupchat->domain);
284 groupchat->domain = g_strdup(domain);
286 g_free(chat_uri);
287 g_strfreev(parts);
290 /* sipe_schedule_action */
291 static void groupchat_init_retry_cb(struct sipe_core_private *sipe_private,
292 SIPE_UNUSED_PARAMETER gpointer data)
294 sipe_groupchat_init(sipe_private);
297 static void groupchat_init_retry(struct sipe_core_private *sipe_private)
299 struct sipe_groupchat *groupchat = sipe_private->groupchat;
301 SIPE_DEBUG_INFO_NOFORMAT("groupchat_init_retry: trying again later...");
303 groupchat->session = NULL;
304 groupchat->connected = FALSE;
306 sipe_schedule_seconds(sipe_private,
307 "<+grouchat-retry>",
308 NULL,
309 GROUPCHAT_RETRY_TIMEOUT,
310 groupchat_init_retry_cb,
311 NULL);
314 void sipe_groupchat_invite_failed(struct sipe_core_private *sipe_private,
315 struct sip_session *session)
317 struct sipe_groupchat *groupchat = sipe_private->groupchat;
318 const gchar *setting = sipe_backend_setting(SIPE_CORE_PUBLIC,
319 SIPE_SETTING_GROUPCHAT_USER);
321 if (groupchat->session) {
322 /* response to group chat server invite */
323 SIPE_DEBUG_ERROR_NOFORMAT("can't connect to group chat server!");
324 } else {
325 /* response to initial invite */
326 SIPE_DEBUG_INFO_NOFORMAT("no group chat server found.");
329 sipe_session_close(sipe_private, session);
330 groupchat_init_retry(sipe_private);
332 if (!is_empty(setting)) {
333 gchar *msg = g_strdup_printf(_("Group Chat Proxy setting is incorrect:\n\n\t%s\n\nPlease update your Account."),
334 setting);
335 sipe_backend_notify_error(_("Couldn't find Group Chat server!"), msg);
336 g_free(msg);
340 static gchar *generate_chanid_node(const gchar *uri, guint key)
342 /* ma-chan://<domain>/<value> */
343 gchar **parts = g_strsplit(uri, "/", 4);
344 gchar *chanid = NULL;
346 if (parts[2] && parts[3]) {
347 chanid = g_strdup_printf("<chanid key=\"%d\" domain=\"%s\" value=\"%s\"/>",
348 key, parts[2], parts[3]);
349 } else {
350 SIPE_DEBUG_ERROR("generate_chanid_node: mal-formed URI '%s'",
351 uri);
353 g_strfreev(parts);
355 return chanid;
358 static struct sipe_groupchat_msg *chatserver_command(struct sipe_core_private *sipe_private,
359 const gchar *cmd);
361 void sipe_groupchat_invite_response(struct sipe_core_private *sipe_private,
362 struct sip_dialog *dialog)
364 struct sipe_groupchat *groupchat = sipe_private->groupchat;
366 SIPE_DEBUG_INFO_NOFORMAT("sipe_groupchat_invite_response");
368 if (!groupchat->session) {
369 /* response to initial invite */
370 struct sipe_groupchat_msg *msg = generate_xccos_message(groupchat,
371 "<cmd id=\"cmd:requri\" seqid=\"1\"><data/></cmd>");
372 sip_transport_info(sipe_private,
373 "Content-Type: text/plain\r\n",
374 msg->xccos,
375 dialog,
376 NULL);
377 sipe_groupchat_msg_remove(msg);
379 } else {
380 /* response to group chat server invite */
381 gchar *invcmd;
383 SIPE_DEBUG_INFO_NOFORMAT("connection to group chat server established.");
385 groupchat->connected = TRUE;
387 /* Any queued joins? */
388 if (groupchat->join_queue) {
389 GString *cmd = g_string_new("<cmd id=\"cmd:bjoin\" seqid=\"1\">"
390 "<data>");
391 GSList *entry;
392 guint i = 0;
394 /* We used g_slist_prepend() to create the list */
395 groupchat->join_queue = entry = g_slist_reverse(groupchat->join_queue);
396 while (entry) {
397 gchar *chanid = generate_chanid_node(entry->data, i++);
398 g_string_append(cmd, chanid);
399 g_free(chanid);
400 entry = entry->next;
402 sipe_groupchat_free_join_queue(groupchat);
404 g_string_append(cmd, "</data></cmd>");
405 chatserver_command(sipe_private, cmd->str);
406 g_string_free(cmd, TRUE);
409 /* Request outstanding invites from server */
410 invcmd = g_strdup_printf("<cmd id=\"cmd:getinv\" seqid=\"1\">"
411 "<data>"
412 "<inv inviteId=\"1\" domain=\"%s\"/>"
413 "</data>"
414 "</cmd>", groupchat->domain);
415 chatserver_command(sipe_private, invcmd);
416 g_free(invcmd);
420 /* TransCallback */
421 static gboolean chatserver_command_response(struct sipe_core_private *sipe_private,
422 struct sipmsg *msg,
423 struct transaction *trans)
425 if (msg->response != 200) {
426 struct sipe_groupchat_msg *gmsg = trans->payload->data;
427 struct sipe_chat_session *chat_session = gmsg->session;
429 SIPE_DEBUG_INFO("chatserver_command_response: failure %d", msg->response);
431 if (chat_session) {
432 gchar *label = g_strdup_printf(_("This message was not delivered to chat room '%s'"),
433 chat_session->title);
434 gchar *errmsg = g_strdup_printf("%s:\n<font color=\"#888888\"></b>%s<b></font>",
435 label, gmsg->content);
436 g_free(label);
437 sipe_backend_notify_message_error(SIPE_CORE_PUBLIC,
438 chat_session->backend,
439 NULL,
440 errmsg);
441 g_free(errmsg);
444 return TRUE;
447 static struct sipe_groupchat_msg *chatserver_command(struct sipe_core_private *sipe_private,
448 const gchar *cmd)
450 struct sipe_groupchat *groupchat = sipe_private->groupchat;
451 struct sipe_groupchat_msg *msg = generate_xccos_message(groupchat, cmd);
453 struct sip_dialog *dialog = sipe_dialog_find(groupchat->session,
454 groupchat->session->with);
456 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
457 struct transaction *trans = sip_transport_info(sipe_private,
458 "Content-Type: text/plain\r\n",
459 msg->xccos,
460 dialog,
461 chatserver_command_response);
463 payload->destroy = sipe_groupchat_msg_remove;
464 payload->data = msg;
465 trans->payload = payload;
467 return(msg);
470 static void chatserver_response_uri(struct sipe_core_private *sipe_private,
471 struct sip_session *session,
472 SIPE_UNUSED_PARAMETER guint result,
473 SIPE_UNUSED_PARAMETER const gchar *message,
474 const sipe_xml *xml)
476 const sipe_xml *uib = sipe_xml_child(xml, "uib");
477 const gchar *uri = sipe_xml_attribute(uib, "uri");
479 /* drop connection to ocschat@<domain> again */
480 sipe_session_close(sipe_private, session);
482 if (uri) {
483 struct sipe_groupchat *groupchat = sipe_private->groupchat;
485 SIPE_DEBUG_INFO("chatserver_response_uri: '%s'", uri);
487 groupchat->session = session = sipe_session_find_or_add_im(sipe_private,
488 uri);
490 session->is_groupchat = TRUE;
491 sipe_im_invite(sipe_private, session, uri, NULL, NULL, NULL, FALSE);
492 } else {
493 SIPE_DEBUG_WARNING_NOFORMAT("process_incoming_info_groupchat: no server URI found!");
494 groupchat_init_retry(sipe_private);
498 static void chatserver_response_channel_search(struct sipe_core_private *sipe_private,
499 SIPE_UNUSED_PARAMETER struct sip_session *session,
500 guint result,
501 const gchar *message,
502 const sipe_xml *xml)
504 struct sipe_core_public *sipe_public = SIPE_CORE_PUBLIC;
506 if (result != 200) {
507 sipe_backend_notify_error(_("Error retrieving room list"),
508 message);
509 } else {
510 const sipe_xml *chanib;
512 for (chanib = sipe_xml_child(xml, "chanib");
513 chanib;
514 chanib = sipe_xml_twin(chanib)) {
515 const gchar *name = sipe_xml_attribute(chanib, "name");
516 const gchar *desc = sipe_xml_attribute(chanib, "description");
517 const gchar *uri = sipe_xml_attribute(chanib, "uri");
518 const sipe_xml *node;
519 guint user_count = 0;
520 guint32 flags = 0;
522 /* information */
523 for (node = sipe_xml_child(chanib, "info");
524 node;
525 node = sipe_xml_twin(node)) {
526 const gchar *id = sipe_xml_attribute(node, "id");
527 gchar *data;
529 if (!id) continue;
531 data = sipe_xml_data(node);
532 if (data) {
533 if (sipe_strcase_equal(id, "urn:parlano:ma:info:ucnt")) {
534 user_count = g_ascii_strtoll(data, NULL, 10);
535 } else if (sipe_strcase_equal(id, "urn:parlano:ma:info:visibilty")) {
536 if (sipe_strcase_equal(data, "private")) {
537 flags |= SIPE_GROUPCHAT_ROOM_PRIVATE;
540 g_free(data);
544 /* properties */
545 for (node = sipe_xml_child(chanib, "prop");
546 node;
547 node = sipe_xml_twin(node)) {
548 const gchar *id = sipe_xml_attribute(node, "id");
549 gchar *data;
551 if (!id) continue;
553 data = sipe_xml_data(node);
554 if (data) {
555 gboolean value = sipe_strcase_equal(data, "true");
556 g_free(data);
558 if (value) {
559 guint32 add = 0;
560 if (sipe_strcase_equal(id, "urn:parlano:ma:prop:filepost")) {
561 add = SIPE_GROUPCHAT_ROOM_FILEPOST;
562 } else if (sipe_strcase_equal(id, "urn:parlano:ma:prop:invite")) {
563 add = SIPE_GROUPCHAT_ROOM_INVITE;
564 } else if (sipe_strcase_equal(id, "urn:parlano:ma:prop:logged")) {
565 add = SIPE_GROUPCHAT_ROOM_LOGGED;
567 flags |= add;
572 SIPE_DEBUG_INFO("group chat channel '%s': '%s' (%s) with %u users, flags 0x%x",
573 name, desc, uri, user_count, flags);
574 sipe_backend_groupchat_room_add(sipe_public,
575 uri, name, desc,
576 user_count, flags);
580 sipe_backend_groupchat_room_terminate(sipe_public);
583 static gboolean is_chanop(const sipe_xml *aib)
585 return sipe_strequal(sipe_xml_attribute(aib, "key"),
586 GROUPCHAT_AIB_KEY_CHANOP);
589 static void add_user(struct sipe_chat_session *chat_session,
590 const gchar *uri,
591 gboolean new, gboolean chanop)
593 SIPE_DEBUG_INFO("add_user: %s%s%s to room %s (%s)",
594 new ? "new " : "",
595 chanop ? "chanop " : "",
596 uri,
597 chat_session->title, chat_session->id);
598 sipe_backend_chat_add(chat_session->backend, uri, new);
599 if (chanop)
600 sipe_backend_chat_operator(chat_session->backend, uri);
603 static void chatserver_response_join(struct sipe_core_private *sipe_private,
604 SIPE_UNUSED_PARAMETER struct sip_session *session,
605 guint result,
606 const gchar *message,
607 const sipe_xml *xml)
609 if (result != 200) {
610 sipe_backend_notify_error(_("Error joining chat room"),
611 message);
612 } else {
613 struct sipe_groupchat *groupchat = sipe_private->groupchat;
614 const sipe_xml *node;
615 GHashTable *user_ids = g_hash_table_new(g_str_hash, g_str_equal);
617 /* Extract user IDs & URIs and generate ID -> URI map */
618 for (node = sipe_xml_child(xml, "uib");
619 node;
620 node = sipe_xml_twin(node)) {
621 const gchar *id = sipe_xml_attribute(node, "id");
622 const gchar *uri = sipe_xml_attribute(node, "uri");
623 if (id && uri)
624 g_hash_table_insert(user_ids,
625 (gpointer) id,
626 (gpointer) uri);
629 /* Process channel data */
630 for (node = sipe_xml_child(xml, "chanib");
631 node;
632 node = sipe_xml_twin(node)) {
633 const gchar *uri = sipe_xml_attribute(node, "uri");
635 if (uri) {
636 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
637 uri);
638 gboolean new = (chat_session == NULL);
639 const gchar *attr = sipe_xml_attribute(node, "name");
640 char *self = sip_uri_self(sipe_private);
641 const sipe_xml *aib;
643 if (new) {
644 chat_session = sipe_chat_create_session(SIPE_CHAT_TYPE_GROUPCHAT,
645 sipe_xml_attribute(node,
646 "uri"),
647 attr ? attr : "");
648 g_hash_table_insert(groupchat->uri_to_chat_session,
649 chat_session->id,
650 chat_session);
652 SIPE_DEBUG_INFO("joined room '%s' (%s)",
653 chat_session->title,
654 chat_session->id);
655 chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
656 chat_session,
657 chat_session->title,
658 self);
659 } else {
660 SIPE_DEBUG_INFO("rejoining room '%s' (%s)",
661 chat_session->title,
662 chat_session->id);
663 sipe_backend_chat_rejoin(SIPE_CORE_PUBLIC,
664 chat_session->backend,
665 self,
666 chat_session->title);
668 g_free(self);
670 attr = sipe_xml_attribute(node, "topic");
671 if (attr) {
672 sipe_backend_chat_topic(chat_session->backend,
673 attr);
676 /* Process user map for channel */
677 for (aib = sipe_xml_child(node, "aib");
678 aib;
679 aib = sipe_xml_twin(aib)) {
680 const gchar *value = sipe_xml_attribute(aib, "value");
681 gboolean chanop = is_chanop(aib);
682 gchar **ids = g_strsplit(value, ",", 0);
684 if (ids) {
685 gchar **uid = ids;
687 while (*uid) {
688 const gchar *uri = g_hash_table_lookup(user_ids,
689 *uid);
690 if (uri)
691 add_user(chat_session,
692 uri,
693 FALSE,
694 chanop);
695 uid++;
698 g_strfreev(ids);
704 g_hash_table_destroy(user_ids);
708 static void chatserver_response_part(struct sipe_core_private *sipe_private,
709 SIPE_UNUSED_PARAMETER struct sip_session *session,
710 guint result,
711 const gchar *message,
712 const sipe_xml *xml)
714 if (result != 200) {
715 SIPE_DEBUG_WARNING("chatserver_response_part: failed with %d: %s. Dropping room",
716 result, message);
717 } else {
718 struct sipe_groupchat *groupchat = sipe_private->groupchat;
719 const gchar *uri = sipe_xml_attribute(sipe_xml_child(xml, "chanib"),
720 "uri");
721 struct sipe_chat_session *chat_session;
723 if (uri &&
724 (chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
725 uri))) {
727 SIPE_DEBUG_INFO("leaving room '%s' (%s)",
728 chat_session->title, chat_session->id);
730 g_hash_table_remove(groupchat->uri_to_chat_session,
731 uri);
732 sipe_chat_remove_session(chat_session);
734 } else {
735 SIPE_DEBUG_WARNING("chatserver_response_part: unknown chat room uri '%s'",
736 uri ? uri : "");
741 static void chatserver_notice_join(struct sipe_core_private *sipe_private,
742 SIPE_UNUSED_PARAMETER struct sip_session *session,
743 SIPE_UNUSED_PARAMETER guint result,
744 SIPE_UNUSED_PARAMETER const gchar *message,
745 const sipe_xml *xml)
747 struct sipe_groupchat *groupchat = sipe_private->groupchat;
748 const sipe_xml *uib;
750 for (uib = sipe_xml_child(xml, "uib");
751 uib;
752 uib = sipe_xml_twin(uib)) {
753 const gchar *uri = sipe_xml_attribute(uib, "uri");
755 if (uri) {
756 const sipe_xml *aib;
758 for (aib = sipe_xml_child(uib, "aib");
759 aib;
760 aib = sipe_xml_twin(aib)) {
761 const gchar *domain = sipe_xml_attribute(aib, "domain");
762 const gchar *path = sipe_xml_attribute(aib, "value");
764 if (domain && path) {
765 gchar *room_uri = g_strdup_printf("ma-chan://%s/%s",
766 domain, path);
767 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
768 room_uri);
769 if (chat_session)
770 add_user(chat_session,
771 uri,
772 TRUE,
773 is_chanop(aib));
775 g_free(room_uri);
782 static void chatserver_notice_part(struct sipe_core_private *sipe_private,
783 SIPE_UNUSED_PARAMETER struct sip_session *session,
784 SIPE_UNUSED_PARAMETER guint result,
785 SIPE_UNUSED_PARAMETER const gchar *message,
786 const sipe_xml *xml)
788 struct sipe_groupchat *groupchat = sipe_private->groupchat;
789 const sipe_xml *chanib;
791 for (chanib = sipe_xml_child(xml, "chanib");
792 chanib;
793 chanib = sipe_xml_twin(chanib)) {
794 const gchar *room_uri = sipe_xml_attribute(chanib, "uri");
796 if (room_uri) {
797 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
798 room_uri);
800 if (chat_session) {
801 const sipe_xml *uib;
803 for (uib = sipe_xml_child(chanib, "uib");
804 uib;
805 uib = sipe_xml_twin(uib)) {
806 const gchar *uri = sipe_xml_attribute(uib, "uri");
808 if (uri) {
809 SIPE_DEBUG_INFO("remove_user: %s from room %s (%s)",
810 uri,
811 chat_session->title,
812 chat_session->id);
813 sipe_backend_chat_remove(chat_session->backend,
814 uri);
822 static const struct response {
823 const gchar *key;
824 void (* const handler)(struct sipe_core_private *,
825 struct sip_session *,
826 guint result, const gchar *,
827 const sipe_xml *xml);
828 } response_table[] = {
829 { "rpl:requri", chatserver_response_uri },
830 { "rpl:chansrch", chatserver_response_channel_search },
831 { "rpl:join", chatserver_response_join },
832 { "rpl:bjoin", chatserver_response_join },
833 { "rpl:part", chatserver_response_part },
834 { "ntc:join", chatserver_notice_join },
835 { "ntc:bjoin", chatserver_notice_join },
836 { "ntc:part", chatserver_notice_part },
837 { NULL, NULL }
840 /* Handles rpl:XXX & ntc:YYY */
841 static void chatserver_response(struct sipe_core_private *sipe_private,
842 const sipe_xml *reply,
843 struct sip_session *session)
845 do {
846 const sipe_xml *resp, *data;
847 const gchar *id;
848 gchar *message;
849 guint result = 500;
850 const struct response *r;
852 id = sipe_xml_attribute(reply, "id");
853 if (!id) {
854 SIPE_DEBUG_INFO_NOFORMAT("chatserver_response: no reply ID found!");
855 continue;
858 resp = sipe_xml_child(reply, "resp");
859 if (resp) {
860 result = sipe_xml_int_attribute(resp, "code", 500);
861 message = sipe_xml_data(resp);
862 } else {
863 message = g_strdup("");
866 data = sipe_xml_child(reply, "data");
868 SIPE_DEBUG_INFO("chatserver_response: '%s' result (%d) %s",
869 id, result, message ? message : "");
871 for (r = response_table; r->key; r++) {
872 if (sipe_strcase_equal(id, r->key)) {
873 (*r->handler)(sipe_private, session, result, message, data);
874 break;
877 if (!r->key) {
878 SIPE_DEBUG_INFO_NOFORMAT("chatserver_response: ignoring unknown response");
881 g_free(message);
882 } while ((reply = sipe_xml_twin(reply)) != NULL);
885 static void chatserver_grpchat_message(struct sipe_core_private *sipe_private,
886 const sipe_xml *chatgrp)
888 struct sipe_groupchat *groupchat = sipe_private->groupchat;
889 const gchar *uri = sipe_xml_attribute(chatgrp, "chanUri");
890 const gchar *from = sipe_xml_attribute(chatgrp, "author");
891 gchar *text = sipe_xml_data(sipe_xml_child(chatgrp, "chat"));
892 struct sipe_chat_session *chat_session;
893 gchar *escaped;
895 if (!uri || !from) {
896 SIPE_DEBUG_INFO("chatserver_grpchat_message: message '%s' received without chat room URI or author!",
897 text ? text : "");
898 g_free(text);
899 return;
902 chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
903 uri);
904 if (!chat_session) {
905 SIPE_DEBUG_INFO("chatserver_grpchat_message: message '%s' from '%s' received from unknown chat room '%s'!",
906 text ? text : "", from, uri);
907 g_free(text);
908 return;
911 /* libxml2 decodes all entities, but the backend expects HTML */
912 escaped = g_markup_escape_text(text, -1);
913 g_free(text);
914 sipe_backend_chat_message(SIPE_CORE_PUBLIC, chat_session->backend,
915 from, escaped);
916 g_free(escaped);
919 void process_incoming_info_groupchat(struct sipe_core_private *sipe_private,
920 struct sipmsg *msg,
921 struct sip_session *session)
923 sipe_xml *xml = sipe_xml_parse(msg->body, msg->bodylen);
924 const sipe_xml *node;
926 /* @TODO: is this always correct?*/
927 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
929 if (!xml) return;
931 if (((node = sipe_xml_child(xml, "rpl")) != NULL) ||
932 ((node = sipe_xml_child(xml, "ntc")) != NULL)) {
933 chatserver_response(sipe_private, node, session);
934 } else if ((node = sipe_xml_child(xml, "grpchat")) != NULL) {
935 chatserver_grpchat_message(sipe_private, node);
936 } else {
937 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_info_groupchat: ignoring unknown response");
940 sipe_xml_free(xml);
943 void sipe_groupchat_send(struct sipe_core_private *sipe_private,
944 struct sipe_chat_session *chat_session,
945 const gchar *what)
947 struct sipe_groupchat *groupchat = sipe_private->groupchat;
948 gchar *cmd, *self, *timestamp;
949 struct sipe_groupchat_msg *msg;
951 if (!groupchat || !chat_session)
952 return;
954 SIPE_DEBUG_INFO("sipe_groupchat_send: '%s' to %s",
955 what, chat_session->id);
957 self = sip_uri_self(sipe_private);
958 timestamp = sipe_utils_time_to_str(time(NULL));
961 * 'what' is already XML-escaped, e.g.
963 * " -> &quot;
964 * > -> &gt;
965 * < -> &lt;
966 * & -> &amp;
968 * No need to escape them here.
970 cmd = g_strdup_printf("<grpchat id=\"grpchat\" seqid=\"1\" chanUri=\"%s\" author=\"%s\" ts=\"%s\">"
971 "<chat>%s</chat>"
972 "</grpchat>",
973 chat_session->id, self, timestamp, what);
974 g_free(timestamp);
975 g_free(self);
976 msg = chatserver_command(sipe_private, cmd);
977 g_free(cmd);
979 msg->session = chat_session;
980 msg->content = g_strdup(what);
983 void sipe_groupchat_leave(struct sipe_core_private *sipe_private,
984 struct sipe_chat_session *chat_session)
986 struct sipe_groupchat *groupchat = sipe_private->groupchat;
987 gchar *cmd;
989 if (!groupchat || !chat_session)
990 return;
992 SIPE_DEBUG_INFO("sipe_groupchat_leave: %s", chat_session->id);
994 cmd = g_strdup_printf("<cmd id=\"cmd:part\" seqid=\"1\">"
995 "<data>"
996 "<chanib uri=\"%s\"/>"
997 "</data>"
998 "</cmd>", chat_session->id);
999 chatserver_command(sipe_private, cmd);
1000 g_free(cmd);
1003 gboolean sipe_core_groupchat_query_rooms(struct sipe_core_public *sipe_public)
1005 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1006 struct sipe_groupchat *groupchat = sipe_private->groupchat;
1008 if (!groupchat || !groupchat->connected)
1009 return FALSE;
1011 chatserver_command(sipe_private,
1012 "<cmd id=\"cmd:chansrch\" seqid=\"1\">"
1013 "<data>"
1014 "<qib qtype=\"BYNAME\" criteria=\"\" extended=\"false\"/>"
1015 "</data>"
1016 "</cmd>");
1018 return TRUE;
1021 void sipe_core_groupchat_join(struct sipe_core_public *sipe_public,
1022 const gchar *uri)
1024 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1025 struct sipe_groupchat *groupchat = sipe_private->groupchat;
1027 if (!g_str_has_prefix(uri, "ma-chan://"))
1028 return;
1030 if (!groupchat) {
1031 /* This happens when a user has set auto-join on a channel */
1032 sipe_groupchat_allocate(sipe_private);
1033 groupchat = sipe_private->groupchat;
1036 if (groupchat->connected) {
1037 struct sipe_chat_session *chat_session = g_hash_table_lookup(groupchat->uri_to_chat_session,
1038 uri);
1040 /* Already joined? */
1041 if (chat_session) {
1043 /* Yes, update backend session */
1044 SIPE_DEBUG_INFO("sipe_core_groupchat_join: show '%s' (%s)",
1045 chat_session->title,
1046 chat_session->id);
1047 sipe_backend_chat_show(chat_session->backend);
1049 } else {
1050 /* No, send command out directly */
1051 gchar *chanid = generate_chanid_node(uri, 0);
1052 if (chanid) {
1053 gchar *cmd = g_strdup_printf("<cmd id=\"cmd:join\" seqid=\"1\">"
1054 "<data>%s</data>"
1055 "</cmd>",
1056 chanid);
1057 SIPE_DEBUG_INFO("sipe_core_groupchat_join: join %s",
1058 uri);
1059 chatserver_command(sipe_private, cmd);
1060 g_free(cmd);
1061 g_free(chanid);
1064 } else {
1065 /* Add it to the queue but avoid duplicates */
1066 if (!g_slist_find_custom(groupchat->join_queue, uri,
1067 sipe_strcompare)) {
1068 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_groupchat_join: URI queued");
1069 groupchat->join_queue = g_slist_prepend(groupchat->join_queue,
1070 g_strdup(uri));
1075 void sipe_groupchat_rejoin(struct sipe_core_private *sipe_private,
1076 struct sipe_chat_session *chat_session)
1078 struct sipe_groupchat *groupchat = sipe_private->groupchat;
1080 if (!groupchat) {
1081 /* First rejoined channel after reconnect will trigger this */
1082 sipe_groupchat_allocate(sipe_private);
1083 groupchat = sipe_private->groupchat;
1086 /* Remember "old" session, so that we don't recreate it at join */
1087 g_hash_table_insert(groupchat->uri_to_chat_session,
1088 chat_session->id,
1089 chat_session);
1090 sipe_core_groupchat_join(SIPE_CORE_PUBLIC, chat_session->id);
1094 Local Variables:
1095 mode: c
1096 c-file-style: "bsd"
1097 indent-tabs-mode: t
1098 tab-width: 8
1099 End: