Fix #3198585: Extra line breaks
[siplcs.git] / src / core / sipe-chat.c
blobbe8f64677113908176408a59fa7f8a44687b3f7f
1 /**
2 * @file sipe-chat.c
4 * pidgin-sipe
6 * Copyright (C) 2009-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 #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"
49 #include "sipe.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(enum sipe_chat_type 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);
87 void sipe_chat_destroy(void)
89 while (chat_sessions) {
90 struct sipe_chat_session *chat_session = chat_sessions->data;
91 SIPE_DEBUG_INFO("sipe_chat_destroy: '%s' (%s)",
92 chat_session->title, chat_session->id);
93 sipe_chat_remove_session(chat_session);
97 void sipe_core_chat_invite(struct sipe_core_public *sipe_public,
98 struct sipe_chat_session *chat_session,
99 const char *name)
101 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
103 SIPE_DEBUG_INFO("sipe_core_chat_create: who '%s'", name);
105 switch (chat_session->type) {
106 case SIPE_CHAT_TYPE_MULTIPARTY:
107 case SIPE_CHAT_TYPE_CONFERENCE:
109 struct sip_session *session = sipe_session_find_chat(sipe_private,
110 chat_session);
112 if (session) {
113 gchar *uri = sip_uri(name);
114 sipe_invite_to_chat(sipe_private, session, uri);
115 g_free(uri);
118 break;
119 case SIPE_CHAT_TYPE_GROUPCHAT:
120 /* @TODO */
121 SIPE_DEBUG_INFO_NOFORMAT("GROUP CHAT: INVITE NOT IMPLEMENTED!");
122 break;
123 default:
124 break;
128 void sipe_core_chat_rejoin(struct sipe_core_public *sipe_public,
129 struct sipe_chat_session *chat_session)
131 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
133 SIPE_DEBUG_INFO("sipe_core_chat_rejoin: '%s'", chat_session->title);
135 switch (chat_session->type) {
136 case SIPE_CHAT_TYPE_MULTIPARTY:
138 struct sip_session *session = sipe_session_add_chat(sipe_private,
139 chat_session,
140 TRUE,
141 NULL);
142 gchar *self = sip_uri_self(sipe_private);
144 sipe_invite_to_chat(sipe_private, session, self);
145 sipe_backend_chat_rejoin(SIPE_CORE_PUBLIC,
146 chat_session->backend,
147 self,
148 chat_session->title);
149 g_free(self);
151 break;
152 case SIPE_CHAT_TYPE_CONFERENCE:
153 sipe_conf_create(sipe_private, chat_session, NULL);
154 break;
155 case SIPE_CHAT_TYPE_GROUPCHAT:
156 sipe_groupchat_rejoin(sipe_private, chat_session);
157 break;
158 default:
159 break;
163 void sipe_core_chat_leave(struct sipe_core_public *sipe_public,
164 struct sipe_chat_session *chat_session)
166 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
168 SIPE_DEBUG_INFO("sipe_core_chat_leave: '%s'", chat_session->title);
170 switch (chat_session->type) {
171 case SIPE_CHAT_TYPE_MULTIPARTY:
172 case SIPE_CHAT_TYPE_CONFERENCE:
174 struct sip_session *session = sipe_session_find_chat(sipe_private,
175 chat_session);
177 if (session) {
178 sipe_session_close(sipe_private, session);
181 break;
182 case SIPE_CHAT_TYPE_GROUPCHAT:
183 sipe_groupchat_leave(sipe_private, chat_session);
184 break;
185 default:
186 break;
190 void sipe_core_chat_send(struct sipe_core_public *sipe_public,
191 struct sipe_chat_session *chat_session,
192 const char *what)
194 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
196 SIPE_DEBUG_INFO("sipe_core_chat_send: '%s' to '%s'",
197 what, chat_session->title);
199 switch (chat_session->type) {
200 case SIPE_CHAT_TYPE_MULTIPARTY:
201 case SIPE_CHAT_TYPE_CONFERENCE:
203 struct sip_session *session = sipe_session_find_chat(sipe_private,
204 chat_session);
206 if (session) {
207 sipe_session_enqueue_message(session,
208 what,
209 NULL);
210 sipe_im_process_queue(sipe_private, session);
213 break;
214 case SIPE_CHAT_TYPE_GROUPCHAT:
215 sipe_groupchat_send(sipe_private, chat_session, what);
216 break;
217 default:
218 break;
222 gchar *
223 sipe_chat_get_name(void)
226 * A non-volatile ID counter.
227 * Should survive connection drop & reconnect.
229 static guint chat_seq = 0;
231 /* Generate next ID */
232 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++chat_seq);
233 SIPE_DEBUG_INFO("sipe_chat_get_name: added new: %s", chat_name);
235 return chat_name;
238 static void
239 sipe_refer(struct sipe_core_private *sipe_private,
240 struct sip_session *session,
241 const gchar *who)
243 gchar *hdr;
244 gchar *contact;
245 gchar *epid = get_epid(sipe_private);
246 struct sip_dialog *dialog = sipe_dialog_find(session,
247 session->chat_session->id);
248 const char *ourtag = dialog && dialog->ourtag ? dialog->ourtag : NULL;
250 contact = get_contact(sipe_private);
251 hdr = g_strdup_printf(
252 "Contact: %s\r\n"
253 "Refer-to: <%s>\r\n"
254 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
255 "Require: com.microsoft.rtc-multiparty\r\n",
256 contact,
257 who,
258 sipe_private->username,
259 ourtag ? ";tag=" : "",
260 ourtag ? ourtag : "",
261 epid);
262 g_free(epid);
264 sip_transport_request(sipe_private,
265 "REFER",
266 session->chat_session->id,
267 session->chat_session->id,
268 hdr,
269 NULL,
270 dialog,
271 NULL);
273 g_free(hdr);
274 g_free(contact);
277 static gboolean
278 sipe_is_election_finished(struct sip_session *session)
280 gboolean res = TRUE;
282 SIPE_DIALOG_FOREACH {
283 if (dialog->election_vote == 0) {
284 res = FALSE;
285 break;
287 } SIPE_DIALOG_FOREACH_END;
289 if (res) {
290 session->is_voting_in_progress = FALSE;
292 return res;
295 static gboolean
296 process_info_response(struct sipe_core_private *sipe_private,
297 struct sipmsg *msg,
298 struct transaction *trans);
300 static void
301 sipe_send_election_set_rm(struct sipe_core_private *sipe_private,
302 struct sip_dialog *dialog)
304 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
306 gchar *body = g_strdup_printf(
307 "<?xml version=\"1.0\"?>\r\n"
308 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
309 "<SetRM uri=\"sip:%s\"/></action>\r\n",
310 sipe_private->username);
312 sip_transport_info(sipe_private,
313 hdr,
314 body,
315 dialog,
316 process_info_response);
318 g_free(body);
321 void
322 sipe_process_pending_invite_queue(struct sipe_core_private *sipe_private,
323 struct sip_session *session)
325 gchar *invitee;
326 GSList *entry = session->pending_invite_queue;
328 while (entry) {
329 invitee = entry->data;
330 sipe_invite_to_chat(sipe_private, session, invitee);
331 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
332 g_free(invitee);
336 void sipe_chat_set_roster_manager(struct sip_session *session,
337 const gchar *roster_manager)
339 struct sipe_chat_session *chat_session = session->chat_session;
341 g_free(chat_session->id);
342 chat_session->id = NULL;
343 if (roster_manager)
344 chat_session->id = g_strdup(roster_manager);
347 static void
348 sipe_election_result(struct sipe_core_private *sipe_private,
349 void *sess)
351 struct sip_session *session = (struct sip_session *)sess;
352 const gchar *rival = NULL;
354 if (session->chat_session->id) {
355 SIPE_DEBUG_INFO(
356 "sipe_election_result: RM has already been elected in the meantime. It is %s",
357 session->chat_session->id);
358 return;
361 session->is_voting_in_progress = FALSE;
363 SIPE_DIALOG_FOREACH {
364 if (dialog->election_vote < 0) {
365 rival = dialog->with;
366 break;
368 } SIPE_DIALOG_FOREACH_END;
370 if (rival) {
371 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival);
372 } else {
373 gchar *self = sip_uri_self(sipe_private);
375 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
377 sipe_chat_set_roster_manager(session, self);
378 g_free(self);
380 SIPE_DIALOG_FOREACH {
381 /* send SetRM to each chat participant*/
382 sipe_send_election_set_rm(sipe_private, dialog);
383 } SIPE_DIALOG_FOREACH_END;
385 session->bid = 0;
387 sipe_process_pending_invite_queue(sipe_private, session);
390 static gboolean
391 process_info_response(struct sipe_core_private *sipe_private,
392 struct sipmsg *msg,
393 SIPE_UNUSED_PARAMETER struct transaction *trans)
395 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
396 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
397 struct sip_dialog *dialog;
398 struct sip_session *session;
400 session = sipe_session_find_chat_by_callid(sipe_private, callid);
401 if (!session) {
402 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid);
403 return FALSE;
406 if (msg->response == 200 && g_str_has_prefix(contenttype, "application/x-ms-mim")) {
407 sipe_xml *xn_action = sipe_xml_parse(msg->body, msg->bodylen);
408 const sipe_xml *xn_request_rm_response = sipe_xml_child(xn_action, "RequestRMResponse");
409 const sipe_xml *xn_set_rm_response = sipe_xml_child(xn_action, "SetRMResponse");
411 if (xn_request_rm_response) {
412 const char *with = sipe_xml_attribute(xn_request_rm_response, "uri");
413 const char *allow = sipe_xml_attribute(xn_request_rm_response, "allow");
415 dialog = sipe_dialog_find(session, with);
416 if (!dialog) {
417 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with);
418 sipe_xml_free(xn_action);
419 return FALSE;
422 if (allow && !g_strcasecmp(allow, "true")) {
423 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with);
424 dialog->election_vote = 1;
425 } else if (allow && !g_strcasecmp(allow, "false")) {
426 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with);
427 dialog->election_vote = -1;
430 if (sipe_is_election_finished(session)) {
431 sipe_election_result(sipe_private,
432 session);
435 } else if (xn_set_rm_response) {
438 sipe_xml_free(xn_action);
442 return TRUE;
445 static void
446 sipe_send_election_request_rm(struct sipe_core_private *sipe_private,
447 struct sip_dialog *dialog,
448 int bid)
450 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
452 gchar *body = g_strdup_printf(
453 "<?xml version=\"1.0\"?>\r\n"
454 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
455 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
456 sipe_private->username, bid);
458 sip_transport_info(sipe_private,
459 hdr,
460 body,
461 dialog,
462 process_info_response);
464 g_free(body);
467 static void
468 sipe_election_start(struct sipe_core_private *sipe_private,
469 struct sip_session *session)
471 if (session->is_voting_in_progress) {
472 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
473 return;
474 } else {
475 session->is_voting_in_progress = TRUE;
477 session->bid = rand();
479 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session->bid);
481 SIPE_DIALOG_FOREACH {
482 /* reset election_vote for each chat participant */
483 dialog->election_vote = 0;
485 /* send RequestRM to each chat participant*/
486 sipe_send_election_request_rm(sipe_private, dialog, session->bid);
487 } SIPE_DIALOG_FOREACH_END;
489 sipe_schedule_seconds(sipe_private,
490 "<+election-result>",
491 session,
493 sipe_election_result,
494 NULL);
497 static void
498 sipe_invite_to_chat(struct sipe_core_private *sipe_private,
499 struct sip_session *session,
500 const gchar *who)
502 /* a conference */
503 if (session->chat_session->type == SIPE_CHAT_TYPE_CONFERENCE)
505 sipe_invite_conf(sipe_private, session, who);
507 else /* a multi-party chat */
509 gchar *self = sip_uri_self(sipe_private);
510 if (session->chat_session->id) {
511 if (sipe_strcase_equal(session->chat_session->id, self)) {
512 sipe_im_invite(sipe_private, session, who, NULL, NULL, NULL, FALSE);
513 } else {
514 sipe_refer(sipe_private, session, who);
516 } else {
517 SIPE_DEBUG_INFO_NOFORMAT("sipe_invite_to_chat: no RM available");
519 session->pending_invite_queue = slist_insert_unique_sorted(
520 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
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: