utils: fix memory leak in sorted GSList insert
[siplcs.git] / src / core / sipe-chat.c
blobd02bc314ab760db13d919a1ac0e523e491f3640b
1 /**
2 * @file sipe-chat.c
4 * pidgin-sipe
6 * Copyright (C) 2009-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 #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-utils.h"
48 #include "sipe-xml.h"
50 /**
51 * Invite @who to chat
53 * @param sipe_private SIPE core private data
54 * @param session SIPE session for chat
55 * @param who URI whom to invite to chat.
57 static void
58 sipe_invite_to_chat(struct sipe_core_private *sipe_private,
59 struct sip_session *session,
60 const gchar *who);
62 static GList *chat_sessions = NULL;
64 struct sipe_chat_session *sipe_chat_create_session(enum sipe_chat_type type,
65 const gchar *id,
66 const gchar *title)
68 struct sipe_chat_session *session = g_new0(struct sipe_chat_session, 1);
69 if (id)
70 session->id = g_strdup(id);
71 session->title = g_strdup(title);
72 session->type = type;
73 chat_sessions = g_list_prepend(chat_sessions, session);
74 return(session);
77 void sipe_chat_remove_session(struct sipe_chat_session *session)
79 chat_sessions = g_list_remove(chat_sessions, session);
80 sipe_backend_chat_session_destroy(session->backend);
81 g_free(session->title);
82 g_free(session->id);
83 g_free(session);
86 void sipe_chat_destroy(void)
88 while (chat_sessions) {
89 struct sipe_chat_session *chat_session = chat_sessions->data;
90 SIPE_DEBUG_INFO("sipe_chat_destroy: '%s' (%s)",
91 chat_session->title, chat_session->id);
92 sipe_chat_remove_session(chat_session);
96 void sipe_core_chat_invite(struct sipe_core_public *sipe_public,
97 struct sipe_chat_session *chat_session,
98 const char *name)
100 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
102 SIPE_DEBUG_INFO("sipe_core_chat_create: who '%s'", name);
104 switch (chat_session->type) {
105 case SIPE_CHAT_TYPE_MULTIPARTY:
106 case SIPE_CHAT_TYPE_CONFERENCE:
108 struct sip_session *session = sipe_session_find_chat(sipe_private,
109 chat_session);
111 if (session) {
112 gchar *uri = sip_uri(name);
113 sipe_invite_to_chat(sipe_private, session, uri);
114 g_free(uri);
117 break;
118 case SIPE_CHAT_TYPE_GROUPCHAT:
119 /* @TODO */
120 SIPE_DEBUG_INFO_NOFORMAT("GROUP CHAT: INVITE NOT IMPLEMENTED!");
121 break;
122 default:
123 break;
127 void sipe_core_chat_rejoin(struct sipe_core_public *sipe_public,
128 struct sipe_chat_session *chat_session)
130 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
132 SIPE_DEBUG_INFO("sipe_core_chat_rejoin: '%s'", chat_session->title);
134 switch (chat_session->type) {
135 case SIPE_CHAT_TYPE_MULTIPARTY:
137 struct sip_session *session = sipe_session_add_chat(sipe_private,
138 chat_session,
139 TRUE,
140 NULL);
141 gchar *self = sip_uri_self(sipe_private);
143 sipe_invite_to_chat(sipe_private, session, self);
144 sipe_backend_chat_rejoin(SIPE_CORE_PUBLIC,
145 chat_session->backend,
146 self,
147 chat_session->title);
148 g_free(self);
150 break;
151 case SIPE_CHAT_TYPE_CONFERENCE:
152 sipe_conf_create(sipe_private, chat_session, NULL);
153 break;
154 case SIPE_CHAT_TYPE_GROUPCHAT:
155 sipe_groupchat_rejoin(sipe_private, chat_session);
156 break;
157 default:
158 break;
162 void sipe_core_chat_leave(struct sipe_core_public *sipe_public,
163 struct sipe_chat_session *chat_session)
165 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
167 SIPE_DEBUG_INFO("sipe_core_chat_leave: '%s'", chat_session->title);
169 switch (chat_session->type) {
170 case SIPE_CHAT_TYPE_MULTIPARTY:
171 case SIPE_CHAT_TYPE_CONFERENCE:
173 struct sip_session *session = sipe_session_find_chat(sipe_private,
174 chat_session);
176 if (session) {
177 sipe_session_close(sipe_private, session);
180 break;
181 case SIPE_CHAT_TYPE_GROUPCHAT:
182 sipe_groupchat_leave(sipe_private, chat_session);
183 break;
184 default:
185 break;
189 void sipe_core_chat_send(struct sipe_core_public *sipe_public,
190 struct sipe_chat_session *chat_session,
191 const char *what)
193 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
195 SIPE_DEBUG_INFO("sipe_core_chat_send: '%s' to '%s'",
196 what, chat_session->title);
198 switch (chat_session->type) {
199 case SIPE_CHAT_TYPE_MULTIPARTY:
200 case SIPE_CHAT_TYPE_CONFERENCE:
202 struct sip_session *session = sipe_session_find_chat(sipe_private,
203 chat_session);
205 if (session) {
206 sipe_session_enqueue_message(session,
207 what,
208 NULL);
209 sipe_im_process_queue(sipe_private, session);
212 break;
213 case SIPE_CHAT_TYPE_GROUPCHAT:
214 sipe_groupchat_send(sipe_private, chat_session, what);
215 break;
216 default:
217 break;
221 gchar *
222 sipe_chat_get_name(void)
225 * A non-volatile ID counter.
226 * Should survive connection drop & reconnect.
228 static guint chat_seq = 0;
230 /* Generate next ID */
231 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++chat_seq);
232 SIPE_DEBUG_INFO("sipe_chat_get_name: added new: %s", chat_name);
234 return chat_name;
237 static void
238 sipe_refer(struct sipe_core_private *sipe_private,
239 struct sip_session *session,
240 const gchar *who)
242 gchar *hdr;
243 gchar *contact;
244 gchar *epid = get_epid(sipe_private);
245 struct sip_dialog *dialog = sipe_dialog_find(session,
246 session->chat_session->id);
247 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
249 contact = get_contact(sipe_private);
250 hdr = g_strdup_printf(
251 "Contact: %s\r\n"
252 "Refer-to: <%s>\r\n"
253 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
254 "Require: com.microsoft.rtc-multiparty\r\n",
255 contact,
256 who,
257 sipe_private->username,
258 ourtag ? ";tag=" : "",
259 ourtag ? ourtag : "",
260 epid);
261 g_free(epid);
263 sip_transport_request(sipe_private,
264 "REFER",
265 session->chat_session->id,
266 session->chat_session->id,
267 hdr,
268 NULL,
269 dialog,
270 NULL);
272 g_free(hdr);
273 g_free(contact);
276 static gboolean
277 sipe_is_election_finished(struct sip_session *session)
279 gboolean res = TRUE;
281 SIPE_DIALOG_FOREACH {
282 if (dialog->election_vote == 0) {
283 res = FALSE;
284 break;
286 } SIPE_DIALOG_FOREACH_END;
288 if (res) {
289 session->is_voting_in_progress = FALSE;
291 return res;
294 static gboolean
295 process_info_response(struct sipe_core_private *sipe_private,
296 struct sipmsg *msg,
297 struct transaction *trans);
299 static void
300 sipe_send_election_set_rm(struct sipe_core_private *sipe_private,
301 struct sip_dialog *dialog)
303 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
305 gchar *body = g_strdup_printf(
306 "<?xml version=\"1.0\"?>\r\n"
307 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
308 "<SetRM uri=\"sip:%s\"/></action>\r\n",
309 sipe_private->username);
311 sip_transport_info(sipe_private,
312 hdr,
313 body,
314 dialog,
315 process_info_response);
317 g_free(body);
320 void
321 sipe_process_pending_invite_queue(struct sipe_core_private *sipe_private,
322 struct sip_session *session)
324 gchar *invitee;
325 GSList *entry = session->pending_invite_queue;
327 while (entry) {
328 invitee = entry->data;
329 sipe_invite_to_chat(sipe_private, session, invitee);
330 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
331 g_free(invitee);
335 void sipe_chat_set_roster_manager(struct sip_session *session,
336 const gchar *roster_manager)
338 struct sipe_chat_session *chat_session = session->chat_session;
340 g_free(chat_session->id);
341 chat_session->id = NULL;
342 if (roster_manager)
343 chat_session->id = g_strdup(roster_manager);
346 static void
347 sipe_election_result(struct sipe_core_private *sipe_private,
348 void *sess)
350 struct sip_session *session = (struct sip_session *)sess;
351 const gchar *rival = NULL;
353 if (session->chat_session->id) {
354 SIPE_DEBUG_INFO(
355 "sipe_election_result: RM has already been elected in the meantime. It is %s",
356 session->chat_session->id);
357 return;
360 session->is_voting_in_progress = FALSE;
362 SIPE_DIALOG_FOREACH {
363 if (dialog->election_vote < 0) {
364 rival = dialog->with;
365 break;
367 } SIPE_DIALOG_FOREACH_END;
369 if (rival) {
370 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
371 } else {
372 gchar *self = sip_uri_self(sipe_private);
374 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
376 sipe_chat_set_roster_manager(session, self);
377 g_free(self);
379 SIPE_DIALOG_FOREACH {
380 /* send SetRM to each chat participant*/
381 sipe_send_election_set_rm(sipe_private, dialog);
382 } SIPE_DIALOG_FOREACH_END;
384 session->bid = 0;
386 sipe_process_pending_invite_queue(sipe_private, session);
389 static gboolean
390 process_info_response(struct sipe_core_private *sipe_private,
391 struct sipmsg *msg,
392 SIPE_UNUSED_PARAMETER struct transaction *trans)
394 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
395 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
396 struct sip_dialog *dialog;
397 struct sip_session *session;
399 session = sipe_session_find_chat_by_callid(sipe_private, callid);
400 if (!session) {
401 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
402 return FALSE;
405 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
406 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
407 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
408 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
410 if (xn_request_rm_response) {
411 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
412 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
414 dialog = sipe_dialog_find(session, with);
415 if (!dialog) {
416 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
417 sipe_xml_free(xn_action);
418 return FALSE;
421 if (allow && !g_ascii_strcasecmp(allow, "true")) {
422 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
423 dialog->election_vote = 1;
424 } else if (allow && !g_ascii_strcasecmp(allow, "false")) {
425 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
426 dialog->election_vote = -1;
429 if (sipe_is_election_finished(session)) {
430 sipe_election_result(sipe_private,
431 session);
434 } else if (xn_set_rm_response) {
437 sipe_xml_free(xn_action);
441 return TRUE;
444 static void
445 sipe_send_election_request_rm(struct sipe_core_private *sipe_private,
446 struct sip_dialog *dialog,
447 int bid)
449 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
451 gchar *body = g_strdup_printf(
452 "<?xml version=\"1.0\"?>\r\n"
453 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
454 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
455 sipe_private->username, bid);
457 sip_transport_info(sipe_private,
458 hdr,
459 body,
460 dialog,
461 process_info_response);
463 g_free(body);
466 static void
467 sipe_election_start(struct sipe_core_private *sipe_private,
468 struct sip_session *session)
470 if (session->is_voting_in_progress) {
471 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
472 return;
473 } else {
474 session->is_voting_in_progress = TRUE;
476 session->bid = rand();
478 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
480 SIPE_DIALOG_FOREACH {
481 /* reset election_vote for each chat participant */
482 dialog->election_vote = 0;
484 /* send RequestRM to each chat participant*/
485 sipe_send_election_request_rm(sipe_private, dialog, session->bid);
486 } SIPE_DIALOG_FOREACH_END;
488 sipe_schedule_seconds(sipe_private,
489 "<+election-result>",
490 session,
492 sipe_election_result,
493 NULL);
496 static void
497 sipe_invite_to_chat(struct sipe_core_private *sipe_private,
498 struct sip_session *session,
499 const gchar *who)
501 /* a conference */
502 if (session->chat_session->type == SIPE_CHAT_TYPE_CONFERENCE)
504 sipe_invite_conf(sipe_private, session, who);
506 else /* a multi-party chat */
508 gchar *self = sip_uri_self(sipe_private);
509 if (session->chat_session->id) {
510 if (sipe_strcase_equal(session->chat_session->id, self)) {
511 sipe_im_invite(sipe_private, session, who, NULL, NULL, NULL, FALSE);
512 } else {
513 sipe_refer(sipe_private, session, who);
515 } else {
516 SIPE_DEBUG_INFO_NOFORMAT("sipe_invite_to_chat: no RM available");
518 session->pending_invite_queue = sipe_utils_slist_insert_unique_sorted(session->pending_invite_queue,
519 g_strdup(who),
520 (GCompareFunc)strcmp,
521 g_free);
522 sipe_election_start(sipe_private, session);
524 g_free(self);
529 Local Variables:
530 mode: c
531 c-file-style: "bsd"
532 indent-tabs-mode: t
533 tab-width: 8
534 End: