Release 1.25.0 -- Buddy Idle Time, RTF
[siplcs.git] / src / core / sipe-im.c
blobe427ae13aabc9864b736f94dc2b2440e151ac723
1 /**
2 * @file sipe-im.c
4 * pidgin-sipe
6 * Copyright (C) 2011-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>
30 #include <glib.h>
32 #include "sipe-common.h"
33 #include "sipmsg.h"
34 #include "sip-transport.h"
35 #include "sipe-backend.h"
36 #include "sipe-buddy.h"
37 #include "sipe-chat.h"
38 #include "sipe-core.h"
39 #include "sipe-core-private.h"
40 #include "sipe-dialog.h"
41 #include "sipe-ft.h"
42 #include "sipe-groupchat.h"
43 #include "sipe-im.h"
44 #include "sipe-incoming.h"
45 #include "sipe-nls.h"
46 #include "sipe-session.h"
47 #include "sipe-user.h"
48 #include "sipe-utils.h"
49 #include "sipe-xml.h"
52 * Hash key template for unconfirmed messages
54 * Call-ID Recipient URI (or empty)
55 * | |
56 * | SIP method | CSeq
57 * | | | | */
58 #define UNCONFIRMED_KEY_TEMPLATE(method, cseq) "<%s><" method "><%s><" cseq
60 /* key must be g_free()'d */
61 static gchar *get_unconfirmed_message_key(const gchar *callid,
62 unsigned int cseq,
63 const gchar *with)
65 return(g_strdup_printf(UNCONFIRMED_KEY_TEMPLATE("%s", "%d>"),
66 callid,
67 with ? "MESSAGE" : "INVITE",
68 with ? with : "",
69 cseq));
72 static void insert_unconfirmed_message(struct sip_session *session,
73 struct sip_dialog *dialog,
74 const gchar *with,
75 const gchar *body,
76 const gchar *content_type)
78 gchar *key = get_unconfirmed_message_key(dialog->callid, dialog->cseq + 1, with);
79 struct queued_message *message = g_new0(struct queued_message, 1);
81 message->body = g_strdup(body);
82 if (content_type != NULL)
83 message->content_type = g_strdup(content_type);
84 message->cseq = dialog->cseq + 1;
86 g_hash_table_insert(session->unconfirmed_messages, key, message);
87 SIPE_DEBUG_INFO("insert_unconfirmed_message: added %s to list (count=%d)",
88 key, g_hash_table_size(session->unconfirmed_messages));
91 static gboolean remove_unconfirmed_message(struct sip_session *session,
92 const gchar *key)
94 gboolean found = g_hash_table_remove(session->unconfirmed_messages, key);
95 if (found) {
96 SIPE_DEBUG_INFO("remove_unconfirmed_message: removed %s from list (count=%d)",
97 key, g_hash_table_size(session->unconfirmed_messages));
98 } else {
99 SIPE_DEBUG_INFO("remove_unconfirmed_message: key %s not found",
100 key);
102 return(found);
105 static void sipe_refer_notify(struct sipe_core_private *sipe_private,
106 struct sip_session *session,
107 const gchar *who,
108 int status,
109 const gchar *desc)
111 gchar *hdr;
112 gchar *body;
113 struct sip_dialog *dialog = sipe_dialog_find(session, who);
115 hdr = g_strdup_printf(
116 "Event: refer\r\n"
117 "Subscription-State: %s\r\n"
118 "Content-Type: message/sipfrag\r\n",
119 status >= 200 ? "terminated" : "active");
121 body = g_strdup_printf(
122 "SIP/2.0 %d %s\r\n",
123 status, desc);
125 sip_transport_request(sipe_private,
126 "NOTIFY",
127 who,
128 who,
129 hdr,
130 body,
131 dialog,
132 NULL);
134 g_free(hdr);
135 g_free(body);
138 static gboolean process_invite_response(struct sipe_core_private *sipe_private,
139 struct sipmsg *msg,
140 struct transaction *trans)
142 gchar *with = sipmsg_parse_to_address(msg);
143 struct sip_session *session;
144 struct sip_dialog *dialog;
145 gchar *key;
146 struct queued_message *message;
147 struct sipmsg *request_msg = trans->msg;
149 const gchar *callid = sipmsg_find_call_id_header(msg);
150 gchar *referred_by;
152 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
153 if (!session) {
154 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
155 g_free(with);
156 return FALSE;
159 dialog = sipe_dialog_find(session, with);
160 if (!dialog) {
161 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
162 g_free(with);
163 return FALSE;
166 sipe_dialog_parse(dialog, msg, TRUE);
168 key = get_unconfirmed_message_key(dialog->callid, sipmsg_parse_cseq(msg), NULL);
169 message = g_hash_table_lookup(session->unconfirmed_messages, key);
171 if (msg->response != 200) {
172 gchar *alias = sipe_buddy_get_alias(sipe_private, with);
173 int warning = sipmsg_parse_warning(msg, NULL);
175 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
177 /* cancel file transfer as rejected by server */
178 if (msg->response == 606 && /* Not acceptable all. */
179 warning == 309 && /* Message contents not allowed by policy */
180 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
182 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
183 sipe_ft_incoming_cancel(dialog, parsed_body);
184 sipe_utils_nameval_free(parsed_body);
187 if (message) {
188 /* generate error for each unprocessed message */
189 GSList *entry = session->outgoing_message_queue;
190 while (entry) {
191 struct queued_message *queued = entry->data;
192 sipe_user_present_message_undelivered(sipe_private, session, msg->response, warning, alias ? alias : with, queued->body);
193 entry = sipe_session_dequeue_message(session);
195 } else {
196 /* generate one error and remove all unprocessed messages */
197 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias ? alias : with);
198 sipe_user_present_error(sipe_private, session, tmp_msg);
199 g_free(tmp_msg);
200 while (sipe_session_dequeue_message(session));
202 g_free(alias);
204 remove_unconfirmed_message(session, key);
205 /* message is no longer valid */
206 g_free(key);
208 sipe_dialog_remove(session, with);
209 g_free(with);
211 if (session->is_groupchat) {
212 sipe_groupchat_invite_failed(sipe_private, session);
213 /* session is no longer valid */
216 return FALSE;
219 dialog->cseq = 0;
220 sip_transport_ack(sipe_private, dialog);
221 dialog->outgoing_invite = NULL;
222 dialog->is_established = TRUE;
224 referred_by = sipmsg_parse_address_from_header(request_msg, "Referred-By");
225 if (referred_by) {
226 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
227 g_free(referred_by);
230 /* add user to chat if it is a multiparty session */
231 if (session->chat_session &&
232 (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY)) {
233 sipe_backend_chat_add(session->chat_session->backend,
234 with,
235 TRUE);
238 if (session->is_groupchat) {
239 sipe_groupchat_invite_response(sipe_private, dialog, msg);
242 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
243 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
244 sipe_session_dequeue_message(session);
247 sipe_im_process_queue(sipe_private, session);
249 remove_unconfirmed_message(session, key);
251 g_free(key);
252 g_free(with);
253 return TRUE;
256 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
257 static gchar *get_end_points(struct sipe_core_private *sipe_private,
258 struct sip_session *session)
260 gchar *res;
262 if (session == NULL) {
263 return NULL;
266 res = g_strdup_printf("<sip:%s>", sipe_private->username);
268 SIPE_DIALOG_FOREACH {
269 gchar *tmp = res;
270 res = g_strdup_printf("%s, <%s>", res, dialog->with);
271 g_free(tmp);
273 if (dialog->theirepid) {
274 tmp = res;
275 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
276 g_free(tmp);
278 } SIPE_DIALOG_FOREACH_END;
280 return res;
283 void
284 sipe_im_invite(struct sipe_core_private *sipe_private,
285 struct sip_session *session,
286 const gchar *who,
287 const gchar *msg_body,
288 const gchar *content_type,
289 const gchar *referred_by,
290 const gboolean is_triggered)
292 gchar *hdr;
293 gchar *to;
294 gchar *contact;
295 gchar *body;
296 gchar *self;
297 char *ms_text_format = NULL;
298 char *ms_conversation_id = NULL;
299 gchar *roster_manager;
300 gchar *end_points;
301 gchar *referred_by_str;
302 gboolean is_multiparty =
303 session->chat_session &&
304 (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY);
305 struct sip_dialog *dialog = sipe_dialog_find(session, who);
307 if (dialog && dialog->is_established) {
308 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
309 return;
312 if (!dialog) {
313 dialog = sipe_dialog_add(session);
314 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
315 dialog->with = g_strdup(who);
318 if (!(dialog->ourtag)) {
319 dialog->ourtag = gentag();
322 to = sip_uri(who);
324 if (msg_body) {
325 char *msgtext = NULL;
326 char *base64_msg;
327 const gchar *msgr = "";
328 gchar *tmp = NULL;
330 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
331 char *msgformat = NULL;
332 gchar *msgr_value;
334 sipe_parse_html(msg_body, &msgformat, &msgtext);
335 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
337 msgr_value = sipmsg_get_msgr_string(msgformat);
338 g_free(msgformat);
339 if (msgr_value) {
340 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
341 g_free(msgr_value);
344 /* When Sipe reconnects after a crash, we are not able
345 * to send messages to contacts with which we had open
346 * conversations when the crash occured. Server sends
347 * error response with reason="This client has an IM
348 * session with the same conversation ID"
350 * Setting random Ms-Conversation-ID prevents this problem
351 * so we can continue the conversation. */
352 ms_conversation_id = g_strdup_printf("Ms-Conversation-ID: %u\r\n",
353 rand() % 1000000000);
354 } else {
355 msgtext = g_strdup(msg_body);
358 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
359 ms_text_format = g_strdup_printf("ms-text-format: %s; charset=UTF-8%s;ms-body=%s\r\n",
360 content_type ? content_type : "text/plain",
361 msgr,
362 base64_msg);
363 g_free(msgtext);
364 g_free(tmp);
365 g_free(base64_msg);
367 insert_unconfirmed_message(session, dialog, NULL,
368 msg_body, content_type);
371 contact = get_contact(sipe_private);
372 end_points = get_end_points(sipe_private, session);
373 self = sip_uri_self(sipe_private);
374 roster_manager = g_strdup_printf(
375 "Roster-Manager: %s\r\n"
376 "EndPoints: %s\r\n",
377 self,
378 end_points);
379 referred_by_str = referred_by ?
380 g_strdup_printf(
381 "Referred-By: %s\r\n",
382 referred_by)
383 : g_strdup("");
384 hdr = g_strdup_printf(
385 "Supported: ms-sender\r\n"
386 "%s"
387 "%s"
388 "%s"
389 "%s"
390 "Contact: %s\r\n%s"
391 "%s"
392 "Content-Type: application/sdp\r\n",
393 is_multiparty && sipe_strcase_equal(session->chat_session->id, self) ? roster_manager : "",
394 referred_by_str,
395 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
396 is_triggered || is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
397 contact,
398 ms_text_format ? ms_text_format : "",
399 ms_conversation_id ? ms_conversation_id : "");
400 g_free(ms_text_format);
401 g_free(ms_conversation_id);
402 g_free(self);
404 body = g_strdup_printf(
405 "v=0\r\n"
406 "o=- 0 0 IN %s %s\r\n"
407 "s=session\r\n"
408 "c=IN %s %s\r\n"
409 "t=0 0\r\n"
410 "m=%s %d sip null\r\n"
411 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
412 sip_transport_sdp_address_marker(sipe_private),
413 sip_transport_ip_address(sipe_private),
414 sip_transport_sdp_address_marker(sipe_private),
415 sip_transport_ip_address(sipe_private),
416 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
417 sip_transport_port(sipe_private));
419 dialog->outgoing_invite = sip_transport_request(sipe_private,
420 "INVITE",
423 hdr,
424 body,
425 dialog,
426 process_invite_response);
428 g_free(to);
429 g_free(roster_manager);
430 g_free(end_points);
431 g_free(referred_by_str);
432 g_free(body);
433 g_free(hdr);
434 g_free(contact);
437 static gboolean
438 process_message_response(struct sipe_core_private *sipe_private,
439 struct sipmsg *msg,
440 SIPE_UNUSED_PARAMETER struct transaction *trans)
442 gboolean ret = TRUE;
443 gchar *with = sipmsg_parse_to_address(msg);
444 const gchar *callid = sipmsg_find_call_id_header(msg);
445 struct sip_session *session = sipe_session_find_chat_or_im(sipe_private, callid, with);
446 struct sip_dialog *dialog;
447 gchar *key;
448 struct queued_message *message;
450 if (!session) {
451 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
452 g_free(with);
453 return FALSE;
456 dialog = sipe_dialog_find(session, with);
457 if (!dialog) {
458 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
459 g_free(with);
460 return FALSE;
463 key = get_unconfirmed_message_key(sipmsg_find_call_id_header(msg), sipmsg_parse_cseq(msg), with);
464 message = g_hash_table_lookup(session->unconfirmed_messages, key);
466 if (msg->response >= 400) {
467 int warning = sipmsg_parse_warning(msg, NULL);
469 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
471 /* cancel file transfer as rejected by server */
472 if (msg->response == 606 && /* Not acceptable all. */
473 warning == 309 && /* Message contents not allowed by policy */
474 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
476 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
477 sipe_ft_incoming_cancel(dialog, parsed_body);
478 sipe_utils_nameval_free(parsed_body);
481 /* drop dangling IM sessions: assume that BYE from remote never reached us */
482 if (msg->response == 408 || /* Request timeout */
483 msg->response == 480 || /* Temporarily Unavailable */
484 msg->response == 481) { /* Call/Transaction Does Not Exist */
485 sipe_im_cancel_dangling(sipe_private, session, dialog, with,
486 sipe_im_cancel_unconfirmed);
487 /* dialog is no longer valid */
488 } else {
489 gchar *alias = sipe_buddy_get_alias(sipe_private, with);
490 sipe_user_present_message_undelivered(sipe_private, session,
491 msg->response, warning,
492 alias ? alias : with,
493 message ? message->body : NULL);
494 remove_unconfirmed_message(session, key);
495 /* message is no longer valid */
496 g_free(alias);
499 ret = FALSE;
500 } else {
501 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
502 if (message_id) {
503 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
504 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
505 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
507 remove_unconfirmed_message(session, key);
510 g_free(key);
511 g_free(with);
513 if (ret) sipe_im_process_queue(sipe_private, session);
514 return ret;
517 #ifndef ENABLE_OCS2005_MESSAGE_HACK
519 * Hack to circumvent problems reported in the bug report
521 * #3267073 - False "could not be delivered" errors
523 * The logs provided by the reporters indicate that OCS2005 clients DO NOT
524 * acknowledge our SIP MESSAGEs. Therefore the message timeout is triggered
525 * and messages are reported to the user as not delivered.
527 * Either this is a bug in the OCS2005 client or we do something wrong in our
528 * SIP MESSAGEs. This hack removes the message timeout and is provided for
529 * users who need to communicate with a still existing OCS2005 user base.
531 * Do not enable it by default!
533 static gboolean
534 process_message_timeout(struct sipe_core_private *sipe_private,
535 struct sipmsg *msg,
536 SIPE_UNUSED_PARAMETER struct transaction *trans)
538 gchar *with = sipmsg_parse_to_address(msg);
539 const gchar *callid = sipmsg_find_call_id_header(msg);
540 struct sip_session *session = sipe_session_find_chat_or_im(sipe_private, callid, with);
541 gchar *key;
542 gboolean found;
544 if (!session) {
545 SIPE_DEBUG_INFO_NOFORMAT("process_message_timeout: unable to find IM session");
546 g_free(with);
547 return TRUE;
550 /* Remove timed-out message from unconfirmed list */
551 key = get_unconfirmed_message_key(sipmsg_find_call_id_header(msg), sipmsg_parse_cseq(msg), with);
552 found = remove_unconfirmed_message(session, key);
553 g_free(key);
555 if (found) {
556 gchar *alias = sipe_buddy_get_alias(sipe_private, with);
557 sipe_user_present_message_undelivered(sipe_private, session, -1, -1,
558 alias ? alias : with,
559 msg->body);
560 g_free(alias);
563 g_free(with);
564 return TRUE;
566 #endif
568 static void sipe_im_send_message(struct sipe_core_private *sipe_private,
569 struct sip_dialog *dialog,
570 const gchar *msg_body,
571 const gchar *content_type)
573 gchar *hdr;
574 gchar *tmp;
575 char *msgtext = NULL;
576 const gchar *msgr = "";
577 gchar *tmp2 = NULL;
579 if (content_type == NULL)
580 content_type = "text/plain";
582 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
583 char *msgformat = NULL;
584 gchar *msgr_value;
586 sipe_parse_html(msg_body, &msgformat, &msgtext);
587 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
589 msgr_value = sipmsg_get_msgr_string(msgformat);
590 g_free(msgformat);
591 if (msgr_value) {
592 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
593 g_free(msgr_value);
595 } else {
596 msgtext = g_strdup(msg_body);
599 tmp = get_contact(sipe_private);
600 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
601 //hdr = g_strdup("Content-Type: text/rtf\r\n");
602 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
604 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
605 g_free(tmp);
606 g_free(tmp2);
608 #ifdef ENABLE_OCS2005_MESSAGE_HACK
609 sip_transport_request(
610 #else
611 sip_transport_request_timeout(
612 #endif
613 sipe_private,
614 "MESSAGE",
615 dialog->with,
616 dialog->with,
617 hdr,
618 msgtext,
619 dialog,
620 process_message_response
621 #ifndef ENABLE_OCS2005_MESSAGE_HACK
624 process_message_timeout
625 #endif
627 g_free(msgtext);
628 g_free(hdr);
631 void sipe_im_process_queue(struct sipe_core_private *sipe_private,
632 struct sip_session *session)
634 GSList *entry2 = session->outgoing_message_queue;
635 while (entry2) {
636 struct queued_message *msg = entry2->data;
638 /* for multiparty chat or conference */
639 if (session->chat_session) {
640 gchar *who = sip_uri_self(sipe_private);
641 sipe_backend_chat_message(SIPE_CORE_PUBLIC,
642 session->chat_session->backend,
643 who,
645 msg->body);
646 g_free(who);
649 SIPE_DIALOG_FOREACH {
650 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
652 insert_unconfirmed_message(session, dialog, dialog->with,
653 msg->body, msg->content_type);
655 sipe_im_send_message(sipe_private, dialog, msg->body, msg->content_type);
656 } SIPE_DIALOG_FOREACH_END;
658 entry2 = sipe_session_dequeue_message(session);
662 struct unconfirmed_callback_data {
663 const gchar *prefix;
664 GSList *list;
667 struct unconfirmed_message {
668 const gchar *key;
669 const struct queued_message *msg;
672 static gint compare_cseq(gconstpointer a,
673 gconstpointer b)
675 return(((struct unconfirmed_message *) a)->msg->cseq -
676 ((struct unconfirmed_message *) b)->msg->cseq);
679 static void unconfirmed_message_callback(gpointer key,
680 gpointer value,
681 gpointer user_data)
683 const gchar *message_key = key;
684 struct unconfirmed_callback_data *data = user_data;
686 SIPE_DEBUG_INFO("unconfirmed_message_callback: key %s", message_key);
688 /* Put messages with the same prefix on a list sorted by CSeq */
689 if (g_str_has_prefix(message_key, data->prefix)) {
690 struct unconfirmed_message *msg = g_malloc(sizeof(struct unconfirmed_message));
691 msg->key = message_key;
692 msg->msg = value;
693 data->list = g_slist_insert_sorted(data->list, msg,
694 compare_cseq);
698 static void foreach_unconfirmed_message(struct sipe_core_private *sipe_private,
699 struct sip_session *session,
700 const gchar *callid,
701 const gchar *with,
702 unconfirmed_callback callback,
703 const gchar *callback_data)
705 gchar *prefix = g_strdup_printf(UNCONFIRMED_KEY_TEMPLATE("MESSAGE", ""),
706 callid, with);
707 struct unconfirmed_callback_data data = { prefix, NULL };
709 SIPE_DEBUG_INFO("foreach_unconfirmed_message: prefix %s", prefix);
711 /* Generate list of matching unconfirmed messages */
712 g_hash_table_foreach(session->unconfirmed_messages,
713 unconfirmed_message_callback,
714 &data);
715 g_free(prefix);
717 /* Process list unconfirmed messages */
718 if (data.list) {
719 GSList *entry;
721 while ((entry = data.list) != NULL) {
722 struct unconfirmed_message *unconfirmed = entry->data;
723 data.list = g_slist_remove(data.list, unconfirmed);
725 SIPE_DEBUG_INFO("foreach_unconfirmed_message: %s", unconfirmed->key);
726 (*callback)(sipe_private, session, unconfirmed->msg->body, callback_data);
728 g_hash_table_remove(session->unconfirmed_messages, unconfirmed->key);
729 g_free(unconfirmed);
734 static void cancel_callback(struct sipe_core_private *sipe_private,
735 struct sip_session *session,
736 const gchar *body,
737 const gchar *with)
739 sipe_user_present_message_undelivered(sipe_private, session,
740 -1, -1, with, body);
743 void sipe_im_cancel_unconfirmed(struct sipe_core_private *sipe_private,
744 struct sip_session *session,
745 const gchar *callid,
746 const gchar *with)
748 gchar *alias = sipe_buddy_get_alias(sipe_private, with);
750 SIPE_DEBUG_INFO("sipe_im_cancel_unconfirmed: with %s callid '%s'",
751 with, callid);
753 foreach_unconfirmed_message(sipe_private, session, callid, with,
754 cancel_callback, alias ? alias : with);
755 g_free(alias);
758 static void reenqueue_callback(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
759 struct sip_session *session,
760 const gchar *body,
761 SIPE_UNUSED_PARAMETER const gchar *with)
763 sipe_session_enqueue_message(session, body, NULL);
766 void sipe_im_reenqueue_unconfirmed(struct sipe_core_private *sipe_private,
767 struct sip_session *session,
768 const gchar *callid,
769 const gchar *with)
771 /* Remember original list, start with an empty list */
772 GSList *first = session->outgoing_message_queue;
773 session->outgoing_message_queue = NULL;
775 SIPE_DEBUG_INFO("sipe_im_reenqueue_unconfirmed: with %s callid '%s'",
776 with, callid);
778 /* Enqueue unconfirmed messages */
779 foreach_unconfirmed_message(sipe_private, session, callid, with,
780 reenqueue_callback, NULL);
782 /* Append or restore original list */
783 if (session->outgoing_message_queue) {
784 GSList *last = g_slist_last(session->outgoing_message_queue);
785 last->next = first;
786 } else {
787 session->outgoing_message_queue = first;
791 void sipe_core_im_send(struct sipe_core_public *sipe_public,
792 const gchar *who,
793 const gchar *what)
795 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
796 struct sip_session *session;
797 struct sip_dialog *dialog;
798 gchar *uri = sip_uri(who);
800 SIPE_DEBUG_INFO("sipe_core_im_send: '%s'", what);
802 session = sipe_session_find_or_add_im(sipe_private, uri);
803 dialog = sipe_dialog_find(session, uri);
805 /* Queue the message */
806 sipe_session_enqueue_message(session, what, NULL);
808 if (dialog && !dialog->outgoing_invite) {
809 if (dialog->delayed_invite)
810 sipe_incoming_cancel_delayed_invite(sipe_private,
811 dialog);
812 sipe_im_process_queue(sipe_private, session);
813 } else if (!dialog || !dialog->outgoing_invite) {
814 /* Need to send the INVITE to get the outgoing dialog setup */
815 sipe_im_invite(sipe_private, session, uri, what, NULL, NULL, FALSE);
818 g_free(uri);
821 void sipe_core_im_close(struct sipe_core_public *sipe_public,
822 const gchar *who)
824 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
826 SIPE_DEBUG_INFO("sipe_core_im_close: conversation with %s closed", who);
827 sipe_session_close(sipe_private,
828 sipe_session_find_im(sipe_private, who));
831 void sipe_im_cancel_dangling(struct sipe_core_private *sipe_private,
832 struct sip_session *session,
833 struct sip_dialog *dialog,
834 const gchar *with,
835 unconfirmed_callback callback)
837 SIPE_DEBUG_INFO_NOFORMAT("sipe_im_cancel_dangling: assuming dangling IM session, dropping it.");
838 sip_transport_bye(sipe_private, dialog);
840 (*callback)(sipe_private, session, dialog->callid, with);
842 /* We might not get a valid reply to our BYE,
843 so make sure the dialog is removed for sure. */
844 sipe_dialog_remove(session, with);
845 /* dialog is no longer valid */
848 void sipe_im_topic(struct sipe_core_private *sipe_private,
849 struct sip_session *session,
850 const gchar *topic)
852 g_free(session->subject);
853 session->subject = g_strdup(topic);
854 sipe_backend_im_topic(SIPE_CORE_PUBLIC, session->with, topic);
857 void process_incoming_info_conversation(struct sipe_core_private *sipe_private,
858 struct sipmsg *msg)
860 sipe_xml *xml = sipe_xml_parse(msg->body, msg->bodylen);
861 const gchar *from = NULL;
862 gchar *subject = NULL;
865 if (!xml)
866 return;
868 if (sipe_strequal(sipe_xml_name(xml), "ConversationInfo")) {
869 const sipe_xml *node = sipe_xml_child(xml, "From");
870 if (node)
871 from = sipe_xml_attribute(node, "uri");
873 node = sipe_xml_child(xml, "Subject");
874 if (node)
875 subject = sipe_xml_data(node);
878 if (from && subject) {
879 struct sip_session *session;
880 session = sipe_session_find_im(sipe_private, from);
882 if (session)
883 sipe_im_topic(sipe_private, session, subject);
886 g_free(subject);
887 sipe_xml_free(xml);
889 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
893 Local Variables:
894 mode: c
895 c-file-style: "bsd"
896 indent-tabs-mode: t
897 tab-width: 8
898 End: