2 * @file sipe-groupchat.c
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
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)
41 * <sib domain="<DOMAIN>" infoType="123" />
44 * serverTime="2010-09-14T14:26:17.6206356Z"
46 * messageSizeLimit="512"
47 * storySizeLimit="4096"
48 * rootUri="ma-cat://<DOMAIN>/<GUID>"
49 * dbVersion="3ea3a5a8-ef36-46cf-898f-7a5133931d63"
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">
60 * <pref label="kedzie.GroupChannels"
62 * createdefault="true" />
65 * <cmd id="cmd:setpref" seqid="x">
67 * <pref label="kedzie.GroupChannels"
69 * createdefault="false"
70 * content="<BASE64 text>" />
74 * use this to sync chats in buddy list on multiple clients?
77 * send after cmd:join to trigger 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?
86 * <inv inviteId="1" domain="<DOMAIN>" />
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">
105 * <rpl id="rpl:nodespermcreatechild" seqid="1">
106 * <commandid seqid="1" envid="xxx" />
107 * <resp code="200">SUCCESS_OK</resp>
111 * - file transfer (uses HTTPS PUT/GET via a filestore server)
112 * [no log file examples]
125 #include "sipe-common.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"
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?
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
;
163 GHashTable
*uri_to_chat_session
;
169 struct sipe_groupchat_msg
{
170 GHashTable
*container
;
171 struct sipe_chat_session
*session
;
178 static void sipe_groupchat_msg_free(gpointer data
) {
179 struct sipe_groupchat_msg
*msg
= data
;
180 g_free(msg
->content
);
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
,
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
;
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
;
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
);
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\">"
241 g_hash_table_insert(groupchat
->msgs
, &msg
->envid
, 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
,
257 gboolean domain_found
= !is_empty(parts
[1]);
258 const gchar
*user
= "ocschat";
259 const gchar
*domain
= parts
[domain_found
? 1 : 0];
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]))
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
,
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
);
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
,
309 GROUPCHAT_RETRY_TIMEOUT
,
310 groupchat_init_retry_cb
,
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
);
320 gboolean retry
= FALSE
;
322 if (groupchat
->session
) {
323 /* response to group chat server invite */
324 SIPE_DEBUG_ERROR_NOFORMAT("can't connect to group chat server!");
326 /* group chat server exists, but communication failed */
329 /* response to initial invite */
330 SIPE_DEBUG_INFO_NOFORMAT("no group chat server found.");
333 sipe_session_close(sipe_private
, session
);
335 if (!is_empty(setting
)) {
336 gchar
*msg
= g_strdup_printf(_("Group Chat Proxy setting is incorrect:\n\n\t%s\n\nPlease update your Account."),
338 sipe_backend_notify_error(SIPE_CORE_PUBLIC
,
339 _("Couldn't find Group Chat server!"),
343 /* user specified group chat settings: we should retry */
348 groupchat_init_retry(sipe_private
);
350 SIPE_DEBUG_INFO_NOFORMAT("disabling group chat feature.");
354 static gchar
*generate_chanid_node(const gchar
*uri
, guint key
)
356 /* ma-chan://<domain>/<value> */
357 gchar
**parts
= g_strsplit(uri
, "/", 4);
358 gchar
*chanid
= NULL
;
360 if (parts
[2] && parts
[3]) {
361 chanid
= g_strdup_printf("<chanid key=\"%d\" domain=\"%s\" value=\"%s\"/>",
362 key
, parts
[2], parts
[3]);
364 SIPE_DEBUG_ERROR("generate_chanid_node: mal-formed URI '%s'",
372 static struct sipe_groupchat_msg
*chatserver_command(struct sipe_core_private
*sipe_private
,
375 void sipe_groupchat_invite_response(struct sipe_core_private
*sipe_private
,
376 struct sip_dialog
*dialog
)
378 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
380 SIPE_DEBUG_INFO_NOFORMAT("sipe_groupchat_invite_response");
382 if (!groupchat
->session
) {
383 /* response to initial invite */
384 struct sipe_groupchat_msg
*msg
= generate_xccos_message(groupchat
,
385 "<cmd id=\"cmd:requri\" seqid=\"1\"><data/></cmd>");
386 sip_transport_info(sipe_private
,
387 "Content-Type: text/plain\r\n",
391 sipe_groupchat_msg_remove(msg
);
394 /* response to group chat server invite */
397 SIPE_DEBUG_INFO_NOFORMAT("connection to group chat server established.");
399 groupchat
->connected
= TRUE
;
401 /* Any queued joins? */
402 if (groupchat
->join_queue
) {
403 GString
*cmd
= g_string_new("<cmd id=\"cmd:bjoin\" seqid=\"1\">"
408 /* We used g_slist_prepend() to create the list */
409 groupchat
->join_queue
= entry
= g_slist_reverse(groupchat
->join_queue
);
411 gchar
*chanid
= generate_chanid_node(entry
->data
, i
++);
412 g_string_append(cmd
, chanid
);
416 sipe_groupchat_free_join_queue(groupchat
);
418 g_string_append(cmd
, "</data></cmd>");
419 chatserver_command(sipe_private
, cmd
->str
);
420 g_string_free(cmd
, TRUE
);
423 /* Request outstanding invites from server */
424 invcmd
= g_strdup_printf("<cmd id=\"cmd:getinv\" seqid=\"1\">"
426 "<inv inviteId=\"1\" domain=\"%s\"/>"
428 "</cmd>", groupchat
->domain
);
429 chatserver_command(sipe_private
, invcmd
);
435 static gboolean
chatserver_command_response(struct sipe_core_private
*sipe_private
,
437 struct transaction
*trans
)
439 if (msg
->response
!= 200) {
440 struct sipe_groupchat_msg
*gmsg
= trans
->payload
->data
;
441 struct sipe_chat_session
*chat_session
= gmsg
->session
;
443 SIPE_DEBUG_INFO("chatserver_command_response: failure %d", msg
->response
);
446 gchar
*label
= g_strdup_printf(_("This message was not delivered to chat room '%s'"),
447 chat_session
->title
);
448 gchar
*errmsg
= g_strdup_printf("%s:\n<font color=\"#888888\"></b>%s<b></font>",
449 label
, gmsg
->content
);
451 sipe_backend_notify_message_error(SIPE_CORE_PUBLIC
,
452 chat_session
->backend
,
461 static struct sipe_groupchat_msg
*chatserver_command(struct sipe_core_private
*sipe_private
,
464 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
465 struct sipe_groupchat_msg
*msg
= generate_xccos_message(groupchat
, cmd
);
467 struct sip_dialog
*dialog
= sipe_dialog_find(groupchat
->session
,
468 groupchat
->session
->with
);
470 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
471 struct transaction
*trans
= sip_transport_info(sipe_private
,
472 "Content-Type: text/plain\r\n",
475 chatserver_command_response
);
477 payload
->destroy
= sipe_groupchat_msg_remove
;
479 trans
->payload
= payload
;
484 static void chatserver_response_uri(struct sipe_core_private
*sipe_private
,
485 struct sip_session
*session
,
486 SIPE_UNUSED_PARAMETER guint result
,
487 SIPE_UNUSED_PARAMETER
const gchar
*message
,
490 const sipe_xml
*uib
= sipe_xml_child(xml
, "uib");
491 const gchar
*uri
= sipe_xml_attribute(uib
, "uri");
493 /* drop connection to ocschat@<domain> again */
494 sipe_session_close(sipe_private
, session
);
497 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
499 SIPE_DEBUG_INFO("chatserver_response_uri: '%s'", uri
);
501 groupchat
->session
= session
= sipe_session_find_or_add_im(sipe_private
,
504 session
->is_groupchat
= TRUE
;
505 sipe_im_invite(sipe_private
, session
, uri
, NULL
, NULL
, NULL
, FALSE
);
507 SIPE_DEBUG_WARNING_NOFORMAT("process_incoming_info_groupchat: no server URI found!");
508 groupchat_init_retry(sipe_private
);
512 static void chatserver_response_channel_search(struct sipe_core_private
*sipe_private
,
513 SIPE_UNUSED_PARAMETER
struct sip_session
*session
,
515 const gchar
*message
,
518 struct sipe_core_public
*sipe_public
= SIPE_CORE_PUBLIC
;
521 sipe_backend_notify_error(sipe_public
,
522 _("Error retrieving room list"),
525 const sipe_xml
*chanib
;
527 for (chanib
= sipe_xml_child(xml
, "chanib");
529 chanib
= sipe_xml_twin(chanib
)) {
530 const gchar
*name
= sipe_xml_attribute(chanib
, "name");
531 const gchar
*desc
= sipe_xml_attribute(chanib
, "description");
532 const gchar
*uri
= sipe_xml_attribute(chanib
, "uri");
533 const sipe_xml
*node
;
534 guint user_count
= 0;
538 for (node
= sipe_xml_child(chanib
, "info");
540 node
= sipe_xml_twin(node
)) {
541 const gchar
*id
= sipe_xml_attribute(node
, "id");
546 data
= sipe_xml_data(node
);
548 if (sipe_strcase_equal(id
, "urn:parlano:ma:info:ucnt")) {
549 user_count
= g_ascii_strtoll(data
, NULL
, 10);
550 } else if (sipe_strcase_equal(id
, "urn:parlano:ma:info:visibilty")) {
551 if (sipe_strcase_equal(data
, "private")) {
552 flags
|= SIPE_GROUPCHAT_ROOM_PRIVATE
;
560 for (node
= sipe_xml_child(chanib
, "prop");
562 node
= sipe_xml_twin(node
)) {
563 const gchar
*id
= sipe_xml_attribute(node
, "id");
568 data
= sipe_xml_data(node
);
570 gboolean value
= sipe_strcase_equal(data
, "true");
575 if (sipe_strcase_equal(id
, "urn:parlano:ma:prop:filepost")) {
576 add
= SIPE_GROUPCHAT_ROOM_FILEPOST
;
577 } else if (sipe_strcase_equal(id
, "urn:parlano:ma:prop:invite")) {
578 add
= SIPE_GROUPCHAT_ROOM_INVITE
;
579 } else if (sipe_strcase_equal(id
, "urn:parlano:ma:prop:logged")) {
580 add
= SIPE_GROUPCHAT_ROOM_LOGGED
;
587 SIPE_DEBUG_INFO("group chat channel '%s': '%s' (%s) with %u users, flags 0x%x",
588 name
, desc
, uri
, user_count
, flags
);
589 sipe_backend_groupchat_room_add(sipe_public
,
595 sipe_backend_groupchat_room_terminate(sipe_public
);
598 static gboolean
is_chanop(const sipe_xml
*aib
)
600 return sipe_strequal(sipe_xml_attribute(aib
, "key"),
601 GROUPCHAT_AIB_KEY_CHANOP
);
604 static void add_user(struct sipe_chat_session
*chat_session
,
606 gboolean
new, gboolean chanop
)
608 SIPE_DEBUG_INFO("add_user: %s%s%s to room %s (%s)",
610 chanop
? "chanop " : "",
612 chat_session
->title
, chat_session
->id
);
613 sipe_backend_chat_add(chat_session
->backend
, uri
, new);
615 sipe_backend_chat_operator(chat_session
->backend
, uri
);
618 static void chatserver_response_join(struct sipe_core_private
*sipe_private
,
619 SIPE_UNUSED_PARAMETER
struct sip_session
*session
,
621 const gchar
*message
,
625 sipe_backend_notify_error(SIPE_CORE_PUBLIC
,
626 _("Error joining chat room"),
629 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
630 const sipe_xml
*node
;
631 GHashTable
*user_ids
= g_hash_table_new(g_str_hash
, g_str_equal
);
633 /* Extract user IDs & URIs and generate ID -> URI map */
634 for (node
= sipe_xml_child(xml
, "uib");
636 node
= sipe_xml_twin(node
)) {
637 const gchar
*id
= sipe_xml_attribute(node
, "id");
638 const gchar
*uri
= sipe_xml_attribute(node
, "uri");
640 g_hash_table_insert(user_ids
,
645 /* Process channel data */
646 for (node
= sipe_xml_child(xml
, "chanib");
648 node
= sipe_xml_twin(node
)) {
649 const gchar
*uri
= sipe_xml_attribute(node
, "uri");
652 struct sipe_chat_session
*chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
654 gboolean
new = (chat_session
== NULL
);
655 const gchar
*attr
= sipe_xml_attribute(node
, "name");
656 char *self
= sip_uri_self(sipe_private
);
660 chat_session
= sipe_chat_create_session(SIPE_CHAT_TYPE_GROUPCHAT
,
661 sipe_xml_attribute(node
,
664 g_hash_table_insert(groupchat
->uri_to_chat_session
,
668 SIPE_DEBUG_INFO("joined room '%s' (%s)",
671 chat_session
->backend
= sipe_backend_chat_create(SIPE_CORE_PUBLIC
,
676 SIPE_DEBUG_INFO("rejoining room '%s' (%s)",
679 sipe_backend_chat_rejoin(SIPE_CORE_PUBLIC
,
680 chat_session
->backend
,
682 chat_session
->title
);
686 attr
= sipe_xml_attribute(node
, "topic");
688 sipe_backend_chat_topic(chat_session
->backend
,
692 /* Process user map for channel */
693 for (aib
= sipe_xml_child(node
, "aib");
695 aib
= sipe_xml_twin(aib
)) {
696 const gchar
*value
= sipe_xml_attribute(aib
, "value");
697 gboolean chanop
= is_chanop(aib
);
698 gchar
**ids
= g_strsplit(value
, ",", 0);
704 const gchar
*uri
= g_hash_table_lookup(user_ids
,
707 add_user(chat_session
,
720 g_hash_table_destroy(user_ids
);
724 static void chatserver_response_part(struct sipe_core_private
*sipe_private
,
725 SIPE_UNUSED_PARAMETER
struct sip_session
*session
,
727 const gchar
*message
,
731 SIPE_DEBUG_WARNING("chatserver_response_part: failed with %d: %s. Dropping room",
734 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
735 const gchar
*uri
= sipe_xml_attribute(sipe_xml_child(xml
, "chanib"),
737 struct sipe_chat_session
*chat_session
;
740 (chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
743 SIPE_DEBUG_INFO("leaving room '%s' (%s)",
744 chat_session
->title
, chat_session
->id
);
746 g_hash_table_remove(groupchat
->uri_to_chat_session
,
748 sipe_chat_remove_session(chat_session
);
751 SIPE_DEBUG_WARNING("chatserver_response_part: unknown chat room uri '%s'",
757 static void chatserver_notice_join(struct sipe_core_private
*sipe_private
,
758 SIPE_UNUSED_PARAMETER
struct sip_session
*session
,
759 SIPE_UNUSED_PARAMETER guint result
,
760 SIPE_UNUSED_PARAMETER
const gchar
*message
,
763 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
766 for (uib
= sipe_xml_child(xml
, "uib");
768 uib
= sipe_xml_twin(uib
)) {
769 const gchar
*uri
= sipe_xml_attribute(uib
, "uri");
774 for (aib
= sipe_xml_child(uib
, "aib");
776 aib
= sipe_xml_twin(aib
)) {
777 const gchar
*domain
= sipe_xml_attribute(aib
, "domain");
778 const gchar
*path
= sipe_xml_attribute(aib
, "value");
780 if (domain
&& path
) {
781 gchar
*room_uri
= g_strdup_printf("ma-chan://%s/%s",
783 struct sipe_chat_session
*chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
786 add_user(chat_session
,
798 static void chatserver_notice_part(struct sipe_core_private
*sipe_private
,
799 SIPE_UNUSED_PARAMETER
struct sip_session
*session
,
800 SIPE_UNUSED_PARAMETER guint result
,
801 SIPE_UNUSED_PARAMETER
const gchar
*message
,
804 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
805 const sipe_xml
*chanib
;
807 for (chanib
= sipe_xml_child(xml
, "chanib");
809 chanib
= sipe_xml_twin(chanib
)) {
810 const gchar
*room_uri
= sipe_xml_attribute(chanib
, "uri");
813 struct sipe_chat_session
*chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
819 for (uib
= sipe_xml_child(chanib
, "uib");
821 uib
= sipe_xml_twin(uib
)) {
822 const gchar
*uri
= sipe_xml_attribute(uib
, "uri");
825 SIPE_DEBUG_INFO("remove_user: %s from room %s (%s)",
829 sipe_backend_chat_remove(chat_session
->backend
,
838 static const struct response
{
840 void (* const handler
)(struct sipe_core_private
*,
841 struct sip_session
*,
842 guint result
, const gchar
*,
843 const sipe_xml
*xml
);
844 } response_table
[] = {
845 { "rpl:requri", chatserver_response_uri
},
846 { "rpl:chansrch", chatserver_response_channel_search
},
847 { "rpl:join", chatserver_response_join
},
848 { "rpl:bjoin", chatserver_response_join
},
849 { "rpl:part", chatserver_response_part
},
850 { "ntc:join", chatserver_notice_join
},
851 { "ntc:bjoin", chatserver_notice_join
},
852 { "ntc:part", chatserver_notice_part
},
856 /* Handles rpl:XXX & ntc:YYY */
857 static void chatserver_response(struct sipe_core_private
*sipe_private
,
858 const sipe_xml
*reply
,
859 struct sip_session
*session
)
862 const sipe_xml
*resp
, *data
;
866 const struct response
*r
;
868 id
= sipe_xml_attribute(reply
, "id");
870 SIPE_DEBUG_INFO_NOFORMAT("chatserver_response: no reply ID found!");
874 resp
= sipe_xml_child(reply
, "resp");
876 result
= sipe_xml_int_attribute(resp
, "code", 500);
877 message
= sipe_xml_data(resp
);
879 message
= g_strdup("");
882 data
= sipe_xml_child(reply
, "data");
884 SIPE_DEBUG_INFO("chatserver_response: '%s' result (%d) %s",
885 id
, result
, message
? message
: "");
887 for (r
= response_table
; r
->key
; r
++) {
888 if (sipe_strcase_equal(id
, r
->key
)) {
889 (*r
->handler
)(sipe_private
, session
, result
, message
, data
);
894 SIPE_DEBUG_INFO_NOFORMAT("chatserver_response: ignoring unknown response");
898 } while ((reply
= sipe_xml_twin(reply
)) != NULL
);
901 static void chatserver_grpchat_message(struct sipe_core_private
*sipe_private
,
902 const sipe_xml
*chatgrp
)
904 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
905 const gchar
*uri
= sipe_xml_attribute(chatgrp
, "chanUri");
906 const gchar
*from
= sipe_xml_attribute(chatgrp
, "author");
907 gchar
*text
= sipe_xml_data(sipe_xml_child(chatgrp
, "chat"));
908 struct sipe_chat_session
*chat_session
;
912 SIPE_DEBUG_INFO("chatserver_grpchat_message: message '%s' received without chat room URI or author!",
918 chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
921 SIPE_DEBUG_INFO("chatserver_grpchat_message: message '%s' from '%s' received from unknown chat room '%s'!",
922 text
? text
: "", from
, uri
);
927 /* libxml2 decodes all entities, but the backend expects HTML */
928 escaped
= g_markup_escape_text(text
, -1);
930 sipe_backend_chat_message(SIPE_CORE_PUBLIC
, chat_session
->backend
,
935 void process_incoming_info_groupchat(struct sipe_core_private
*sipe_private
,
937 struct sip_session
*session
)
939 sipe_xml
*xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
940 const sipe_xml
*node
;
942 /* @TODO: is this always correct?*/
943 sip_transport_response(sipe_private
, msg
, 200, "OK", NULL
);
947 if (((node
= sipe_xml_child(xml
, "rpl")) != NULL
) ||
948 ((node
= sipe_xml_child(xml
, "ntc")) != NULL
)) {
949 chatserver_response(sipe_private
, node
, session
);
950 } else if ((node
= sipe_xml_child(xml
, "grpchat")) != NULL
) {
951 chatserver_grpchat_message(sipe_private
, node
);
953 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_info_groupchat: ignoring unknown response");
959 void sipe_groupchat_send(struct sipe_core_private
*sipe_private
,
960 struct sipe_chat_session
*chat_session
,
963 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
964 gchar
*cmd
, *self
, *timestamp
, *tmp
;
965 struct sipe_groupchat_msg
*msg
;
967 if (!groupchat
|| !chat_session
)
970 SIPE_DEBUG_INFO("sipe_groupchat_send: '%s' to %s",
971 what
, chat_session
->id
);
973 self
= sip_uri_self(sipe_private
);
974 timestamp
= sipe_utils_time_to_str(time(NULL
));
977 * 'what' is already XML-escaped, e.g.
984 * No need to escape them here.
986 * Only exception are line breaks which are encoded as <br>.
987 * Replace them with the correct XML tag <br/>.
989 tmp
= replace(what
, "<br>", "<br/>");
990 cmd
= g_strdup_printf("<grpchat id=\"grpchat\" seqid=\"1\" chanUri=\"%s\" author=\"%s\" ts=\"%s\">"
993 chat_session
->id
, self
, timestamp
, tmp
);
997 msg
= chatserver_command(sipe_private
, cmd
);
1000 msg
->session
= chat_session
;
1001 msg
->content
= g_strdup(what
);
1004 void sipe_groupchat_leave(struct sipe_core_private
*sipe_private
,
1005 struct sipe_chat_session
*chat_session
)
1007 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
1010 if (!groupchat
|| !chat_session
)
1013 SIPE_DEBUG_INFO("sipe_groupchat_leave: %s", chat_session
->id
);
1015 cmd
= g_strdup_printf("<cmd id=\"cmd:part\" seqid=\"1\">"
1017 "<chanib uri=\"%s\"/>"
1019 "</cmd>", chat_session
->id
);
1020 chatserver_command(sipe_private
, cmd
);
1024 gboolean
sipe_core_groupchat_query_rooms(struct sipe_core_public
*sipe_public
)
1026 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1027 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
1029 if (!groupchat
|| !groupchat
->connected
)
1032 chatserver_command(sipe_private
,
1033 "<cmd id=\"cmd:chansrch\" seqid=\"1\">"
1035 "<qib qtype=\"BYNAME\" criteria=\"\" extended=\"false\"/>"
1042 void sipe_core_groupchat_join(struct sipe_core_public
*sipe_public
,
1045 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1046 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
1048 if (!g_str_has_prefix(uri
, "ma-chan://"))
1052 /* This happens when a user has set auto-join on a channel */
1053 sipe_groupchat_allocate(sipe_private
);
1054 groupchat
= sipe_private
->groupchat
;
1057 if (groupchat
->connected
) {
1058 struct sipe_chat_session
*chat_session
= g_hash_table_lookup(groupchat
->uri_to_chat_session
,
1061 /* Already joined? */
1064 /* Yes, update backend session */
1065 SIPE_DEBUG_INFO("sipe_core_groupchat_join: show '%s' (%s)",
1066 chat_session
->title
,
1068 sipe_backend_chat_show(chat_session
->backend
);
1071 /* No, send command out directly */
1072 gchar
*chanid
= generate_chanid_node(uri
, 0);
1074 gchar
*cmd
= g_strdup_printf("<cmd id=\"cmd:join\" seqid=\"1\">"
1078 SIPE_DEBUG_INFO("sipe_core_groupchat_join: join %s",
1080 chatserver_command(sipe_private
, cmd
);
1086 /* Add it to the queue but avoid duplicates */
1087 if (!g_slist_find_custom(groupchat
->join_queue
, uri
,
1089 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_groupchat_join: URI queued");
1090 groupchat
->join_queue
= g_slist_prepend(groupchat
->join_queue
,
1096 void sipe_groupchat_rejoin(struct sipe_core_private
*sipe_private
,
1097 struct sipe_chat_session
*chat_session
)
1099 struct sipe_groupchat
*groupchat
= sipe_private
->groupchat
;
1102 /* First rejoined channel after reconnect will trigger this */
1103 sipe_groupchat_allocate(sipe_private
);
1104 groupchat
= sipe_private
->groupchat
;
1107 /* Remember "old" session, so that we don't recreate it at join */
1108 g_hash_table_insert(groupchat
->uri_to_chat_session
,
1111 sipe_core_groupchat_join(SIPE_CORE_PUBLIC
, chat_session
->id
);