Release 1.25.0 -- Buddy Idle Time, RTF
[siplcs.git] / src / core / sipe-chat.c
blob34198b5232f4a9ff226779dcdc28394ac75c6734
1 /**
2 * @file sipe-chat.c
4 * pidgin-sipe
6 * Copyright (C) 2009-2019 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 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
31 #include <glib.h>
33 #include "sipe-common.h"
34 #include "sipmsg.h"
35 #include "sip-transport.h"
36 #include "sipe-backend.h"
37 #include "sipe-chat.h"
38 #include "sipe-conf.h"
39 #include "sipe-core.h"
40 #include "sipe-core-private.h"
41 #include "sipe-dialog.h"
42 #include "sipe-groupchat.h"
43 #include "sipe-im.h"
44 #include "sipe-nls.h"
45 #include "sipe-schedule.h"
46 #include "sipe-session.h"
47 #include "sipe-user.h"
48 #include "sipe-utils.h"
49 #include "sipe-xml.h"
51 /**
52 * Invite @who to chat
54 * @param sipe_private SIPE core private data
55 * @param session SIPE session for chat
56 * @param who URI whom to invite to chat.
58 static void
59 sipe_invite_to_chat(struct sipe_core_private *sipe_private,
60 struct sip_session *session,
61 const gchar *who);
63 static GList *chat_sessions = NULL;
65 struct sipe_chat_session *sipe_chat_create_session(guint type,
66 const gchar *id,
67 const gchar *title)
69 struct sipe_chat_session *session = g_new0(struct sipe_chat_session, 1);
70 if (id)
71 session->id = g_strdup(id);
72 session->title = g_strdup(title);
73 session->type = type;
74 chat_sessions = g_list_prepend(chat_sessions, session);
75 return(session);
78 void sipe_chat_remove_session(struct sipe_chat_session *session)
80 chat_sessions = g_list_remove(chat_sessions, session);
81 sipe_backend_chat_session_destroy(session->backend);
82 g_free(session->title);
83 g_free(session->id);
84 g_free(session->join_url);
85 g_free(session->dial_in_conf_id);
86 g_free(session->organizer);
88 if (session->appshare_ask_ctx) {
89 sipe_user_close_ask(session->appshare_ask_ctx);
92 g_free(session);
95 void sipe_chat_destroy(void)
97 while (chat_sessions) {
98 struct sipe_chat_session *chat_session = chat_sessions->data;
99 SIPE_DEBUG_INFO("sipe_chat_destroy: '%s' (%s)",
100 chat_session->title, chat_session->id);
101 sipe_chat_remove_session(chat_session);
105 const gchar *sipe_core_chat_id(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public,
106 struct sipe_chat_session *chat_session)
108 return(chat_session->id);
111 guint sipe_core_chat_type(struct sipe_chat_session *chat_session)
113 return(chat_session ? chat_session->type : SIPE_CHAT_TYPE_UNKNOWN);
116 void sipe_core_chat_invite(struct sipe_core_public *sipe_public,
117 struct sipe_chat_session *chat_session,
118 const char *name)
120 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
122 SIPE_DEBUG_INFO("sipe_core_chat_create: who '%s'", name);
124 switch (chat_session->type) {
125 case SIPE_CHAT_TYPE_MULTIPARTY:
126 case SIPE_CHAT_TYPE_CONFERENCE:
128 struct sip_session *session = sipe_session_find_chat(sipe_private,
129 chat_session);
131 if (session) {
132 gchar *uri = sip_uri(name);
133 sipe_invite_to_chat(sipe_private, session, uri);
134 g_free(uri);
137 break;
138 case SIPE_CHAT_TYPE_GROUPCHAT:
139 /* @TODO */
140 SIPE_DEBUG_INFO_NOFORMAT("GROUP CHAT: INVITE NOT IMPLEMENTED!");
141 break;
142 default:
143 break;
147 void sipe_core_chat_rejoin(struct sipe_core_public *sipe_public,
148 struct sipe_chat_session *chat_session)
150 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
152 SIPE_DEBUG_INFO("sipe_core_chat_rejoin: '%s'", chat_session->title);
154 switch (chat_session->type) {
155 case SIPE_CHAT_TYPE_MULTIPARTY:
157 struct sip_session *session = sipe_session_add_chat(sipe_private,
158 chat_session,
159 TRUE,
160 NULL);
161 gchar *self = sip_uri_self(sipe_private);
163 sipe_invite_to_chat(sipe_private, session, self);
164 sipe_backend_chat_rejoin(SIPE_CORE_PUBLIC,
165 chat_session->backend,
166 self,
167 chat_session->title);
168 g_free(self);
170 break;
171 case SIPE_CHAT_TYPE_CONFERENCE:
172 sipe_conf_create(sipe_private, chat_session, NULL);
173 break;
174 case SIPE_CHAT_TYPE_GROUPCHAT:
175 sipe_groupchat_rejoin(sipe_private, chat_session);
176 break;
177 default:
178 break;
182 void sipe_core_chat_leave(struct sipe_core_public *sipe_public,
183 struct sipe_chat_session *chat_session)
185 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
187 SIPE_DEBUG_INFO("sipe_core_chat_leave: '%s'", chat_session->title);
189 switch (chat_session->type) {
190 case SIPE_CHAT_TYPE_MULTIPARTY:
191 case SIPE_CHAT_TYPE_CONFERENCE:
193 struct sip_session *session = sipe_session_find_chat(sipe_private,
194 chat_session);
196 if (session) {
197 sipe_session_close(sipe_private, session);
200 break;
201 case SIPE_CHAT_TYPE_GROUPCHAT:
202 sipe_groupchat_leave(sipe_private, chat_session);
203 break;
204 default:
205 break;
209 void sipe_core_chat_send(struct sipe_core_public *sipe_public,
210 struct sipe_chat_session *chat_session,
211 const char *what)
213 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
215 SIPE_DEBUG_INFO("sipe_core_chat_send: '%s' to '%s'",
216 what, chat_session->title);
218 switch (chat_session->type) {
219 case SIPE_CHAT_TYPE_MULTIPARTY:
220 case SIPE_CHAT_TYPE_CONFERENCE:
222 struct sip_session *session = sipe_session_find_chat(sipe_private,
223 chat_session);
225 if (session) {
226 sipe_session_enqueue_message(session,
227 what,
228 NULL);
229 sipe_im_process_queue(sipe_private, session);
232 break;
233 case SIPE_CHAT_TYPE_GROUPCHAT:
234 sipe_groupchat_send(sipe_private, chat_session, what);
235 break;
236 default:
237 break;
241 gchar *
242 sipe_chat_get_name(void)
245 * A non-volatile ID counter.
246 * Should survive connection drop & reconnect.
248 static guint chat_seq = 0;
250 /* Generate next ID */
251 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++chat_seq);
252 SIPE_DEBUG_INFO("sipe_chat_get_name: added new: %s", chat_name);
254 return chat_name;
257 static void
258 sipe_refer(struct sipe_core_private *sipe_private,
259 struct sip_session *session,
260 const gchar *who)
262 gchar *hdr;
263 gchar *contact;
264 struct sip_dialog *dialog = sipe_dialog_find(session,
265 session->chat_session->id);
266 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
268 contact = get_contact(sipe_private);
269 hdr = g_strdup_printf(
270 "Contact: %s\r\n"
271 "Refer-to: <%s>\r\n"
272 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
273 "Require: com.microsoft.rtc-multiparty\r\n",
274 contact,
275 who,
276 sipe_private->username,
277 ourtag ? ";tag=" : "",
278 ourtag ? ourtag : "",
279 sip_transport_epid(sipe_private));
281 sip_transport_request(sipe_private,
282 "REFER",
283 session->chat_session->id,
284 session->chat_session->id,
285 hdr,
286 NULL,
287 dialog,
288 NULL);
290 g_free(hdr);
291 g_free(contact);
294 static gboolean
295 sipe_is_election_finished(struct sip_session *session)
297 gboolean res = TRUE;
299 SIPE_DIALOG_FOREACH {
300 if (dialog->election_vote == 0) {
301 res = FALSE;
302 break;
304 } SIPE_DIALOG_FOREACH_END;
306 if (res) {
307 session->is_voting_in_progress = FALSE;
309 return res;
312 static gboolean
313 process_info_response(struct sipe_core_private *sipe_private,
314 struct sipmsg *msg,
315 struct transaction *trans);
317 static void
318 sipe_send_election_set_rm(struct sipe_core_private *sipe_private,
319 struct sip_dialog *dialog)
321 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
323 gchar *body = g_strdup_printf(
324 "<?xml version=\"1.0\"?>\r\n"
325 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
326 "<SetRM uri=\"sip:%s\"/></action>\r\n",
327 sipe_private->username);
329 sip_transport_info(sipe_private,
330 hdr,
331 body,
332 dialog,
333 process_info_response);
335 g_free(body);
338 void
339 sipe_process_pending_invite_queue(struct sipe_core_private *sipe_private,
340 struct sip_session *session)
342 gchar *invitee;
343 GSList *entry = session->pending_invite_queue;
345 while (entry) {
346 invitee = entry->data;
347 sipe_invite_to_chat(sipe_private, session, invitee);
348 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
349 g_free(invitee);
353 void sipe_chat_set_roster_manager(struct sip_session *session,
354 const gchar *roster_manager)
356 struct sipe_chat_session *chat_session = session->chat_session;
358 g_free(chat_session->id);
359 chat_session->id = NULL;
360 if (roster_manager)
361 chat_session->id = g_strdup(roster_manager);
364 static void
365 sipe_election_result(struct sipe_core_private *sipe_private,
366 void *sess)
368 struct sip_session *session = (struct sip_session *)sess;
369 const gchar *rival = NULL;
371 if (session->chat_session->id) {
372 SIPE_DEBUG_INFO(
373 "sipe_election_result: RM has already been elected in the meantime. It is %s",
374 session->chat_session->id);
375 return;
378 session->is_voting_in_progress = FALSE;
380 SIPE_DIALOG_FOREACH {
381 if (dialog->election_vote < 0) {
382 rival = dialog->with;
383 break;
385 } SIPE_DIALOG_FOREACH_END;
387 if (rival) {
388 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
389 } else {
390 gchar *self = sip_uri_self(sipe_private);
392 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
394 sipe_chat_set_roster_manager(session, self);
395 g_free(self);
397 SIPE_DIALOG_FOREACH {
398 /* send SetRM to each chat participant*/
399 sipe_send_election_set_rm(sipe_private, dialog);
400 } SIPE_DIALOG_FOREACH_END;
402 session->bid = 0;
404 sipe_process_pending_invite_queue(sipe_private, session);
407 static gboolean
408 process_info_response(struct sipe_core_private *sipe_private,
409 struct sipmsg *msg,
410 SIPE_UNUSED_PARAMETER struct transaction *trans)
412 const gchar *contenttype = sipmsg_find_content_type_header(msg);
413 const gchar *callid = sipmsg_find_call_id_header(msg);
414 struct sip_dialog *dialog;
415 struct sip_session *session;
417 session = sipe_session_find_chat_by_callid(sipe_private, callid);
418 if (!session) {
419 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
420 return FALSE;
423 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
424 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
425 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
426 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
428 if (xn_request_rm_response) {
429 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
430 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
432 dialog = sipe_dialog_find(session, with);
433 if (!dialog) {
434 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
435 sipe_xml_free(xn_action);
436 return FALSE;
439 if (allow && !g_ascii_strcasecmp(allow, "true")) {
440 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
441 dialog->election_vote = 1;
442 } else if (allow && !g_ascii_strcasecmp(allow, "false")) {
443 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
444 dialog->election_vote = -1;
447 if (sipe_is_election_finished(session)) {
448 sipe_election_result(sipe_private,
449 session);
452 } else if (xn_set_rm_response) {
455 sipe_xml_free(xn_action);
459 return TRUE;
462 static void
463 sipe_send_election_request_rm(struct sipe_core_private *sipe_private,
464 struct sip_dialog *dialog,
465 int bid)
467 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
469 gchar *body = g_strdup_printf(
470 "<?xml version=\"1.0\"?>\r\n"
471 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
472 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
473 sipe_private->username, bid);
475 sip_transport_info(sipe_private,
476 hdr,
477 body,
478 dialog,
479 process_info_response);
481 g_free(body);
484 static void
485 sipe_election_start(struct sipe_core_private *sipe_private,
486 struct sip_session *session)
488 if (session->is_voting_in_progress) {
489 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
490 return;
491 } else {
492 session->is_voting_in_progress = TRUE;
494 session->bid = rand();
496 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
498 SIPE_DIALOG_FOREACH {
499 /* reset election_vote for each chat participant */
500 dialog->election_vote = 0;
502 /* send RequestRM to each chat participant*/
503 sipe_send_election_request_rm(sipe_private, dialog, session->bid);
504 } SIPE_DIALOG_FOREACH_END;
506 sipe_schedule_seconds(sipe_private,
507 "<+election-result>",
508 session,
510 sipe_election_result,
511 NULL);
514 static void
515 sipe_invite_to_chat(struct sipe_core_private *sipe_private,
516 struct sip_session *session,
517 const gchar *who)
519 /* a conference */
520 if (session->chat_session->type == SIPE_CHAT_TYPE_CONFERENCE)
522 sipe_invite_conf(sipe_private, session, who);
524 else /* a multi-party chat */
526 gchar *self = sip_uri_self(sipe_private);
527 if (session->chat_session->id) {
528 if (sipe_strcase_equal(session->chat_session->id, self)) {
529 sipe_im_invite(sipe_private, session, who, NULL, NULL, NULL, FALSE);
530 } else {
531 sipe_refer(sipe_private, session, who);
533 } else {
534 SIPE_DEBUG_INFO_NOFORMAT("sipe_invite_to_chat: no RM available");
536 session->pending_invite_queue = sipe_utils_slist_insert_unique_sorted(session->pending_invite_queue,
537 g_strdup(who),
538 (GCompareFunc)strcmp,
539 g_free);
540 sipe_election_start(sipe_private, session);
542 g_free(self);
547 Local Variables:
548 mode: c
549 c-file-style: "bsd"
550 indent-tabs-mode: t
551 tab-width: 8
552 End: