coverity: fix two HFA warnings
[siplcs.git] / src / core / sipe.c
blob54b32462230d56bd95c3c195aa542b3d77cc2da3
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #include <time.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <errno.h>
44 #include <string.h>
45 #include <unistd.h>
47 #include <glib.h>
49 #include "sipe-common.h"
51 #include "account.h"
52 #include "blist.h"
53 #include "connection.h"
54 #include "conversation.h"
55 #include "ft.h"
56 #include "notify.h"
57 #include "plugin.h"
58 #include "privacy.h"
59 #include "request.h"
60 #include "savedstatuses.h"
62 #include "core-depurple.h" /* Temporary for the core de-purple transition */
64 #include "http-conn.h"
65 #include "sipmsg.h"
66 #include "sip-csta.h"
67 #include "sip-transport.h"
68 #include "sipe-backend.h"
69 #include "sipe-buddy.h"
70 #include "sipe-cal.h"
71 #include "sipe-chat.h"
72 #include "sipe-conf.h"
73 #include "sipe-core.h"
74 #include "sipe-core-private.h"
75 #include "sipe-dialog.h"
76 #include "sipe-ews.h"
77 #include "sipe-domino.h"
78 #include "sipe-ft.h"
79 #include "sipe-mime.h"
80 #include "sipe-nls.h"
81 #include "sipe-schedule.h"
82 #include "sipe-session.h"
83 #include "sipe-subscriptions.h"
84 #ifdef HAVE_VV
85 #include "sipe-media.h"
86 #endif
87 #include "sipe-utils.h"
88 #include "sipe-xml.h"
89 #include "uuid.h"
90 #include "sipe.h"
92 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
94 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
95 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
97 /* Status identifiers (see also: sipe_status_types()) */
98 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
99 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
100 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
101 /* PURPLE_STATUS_UNAVAILABLE: */
102 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
103 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
104 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
105 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
106 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
107 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
108 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
109 /* PURPLE_STATUS_AWAY: */
110 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
111 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
112 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
113 /** Reuters status (user settable) */
114 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
115 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
116 /* ??? PURPLE_STATUS_MOBILE */
117 /* ??? PURPLE_STATUS_TUNE */
119 /* Status attributes (see also sipe_status_types() */
120 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
122 static struct sipe_activity_map_struct
124 sipe_activity type;
125 const char *token;
126 const char *desc;
127 const char *status_id;
129 } const sipe_activity_map[] =
131 /* This has nothing to do with Availability numbers, like 3500 (online).
132 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
134 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
135 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
136 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
137 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
138 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
139 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
140 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
141 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
142 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
143 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
144 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
145 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
146 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
147 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
148 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
150 /** @param x is sipe_activity */
151 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
153 static sipe_activity
154 sipe_get_activity_by_token(const char *token)
156 int i;
158 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
160 if (sipe_strequal(token, sipe_activity_map[i].token))
161 return sipe_activity_map[i].type;
164 return sipe_activity_map[0].type;
167 static const char *
168 sipe_get_activity_desc_by_token(const char *token)
170 if (!token) return NULL;
172 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
175 static void send_presence_status(struct sipe_core_private *sipe_private,
176 void *unused);
179 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
181 static void
182 send_soap_request_with_cb(struct sipe_core_private *sipe_private,
183 gchar *from0,
184 gchar *body,
185 TransCallback callback,
186 struct transaction_payload *payload)
188 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sipe_private);
189 gchar *contact = get_contact(sipe_private);
190 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
191 "Content-Type: application/SOAP+xml\r\n",contact);
193 struct transaction *trans = sip_transport_service(sipe_private,
194 from,
195 hdr,
196 body,
197 callback);
198 trans->payload = payload;
200 g_free(from);
201 g_free(contact);
202 g_free(hdr);
205 static void send_soap_request(struct sipe_core_private *sipe_private,
206 gchar *body)
208 send_soap_request_with_cb(sipe_private, NULL, body, NULL, NULL);
212 * Returns pointer to URI without sip: prefix if any
214 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
215 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
217 * Doesn't allocate memory
219 static const char *
220 sipe_get_no_sip_uri(const char *sip_uri)
222 const char *prefix = "sip:";
223 if (!sip_uri) return NULL;
225 if (g_str_has_prefix(sip_uri, prefix)) {
226 return (sip_uri+strlen(prefix));
227 } else {
228 return sip_uri;
232 static void
233 sipe_contact_set_acl (struct sipe_core_private *sipe_private,
234 const gchar *who,
235 gchar *rights)
237 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
238 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
239 send_soap_request(sipe_private, body);
240 g_free(body);
243 static void
244 sipe_change_access_level(struct sipe_core_private *sipe_private,
245 const int container_id,
246 const gchar *type,
247 const gchar *value);
249 void
250 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
251 const gchar * who, gboolean allow)
253 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
255 if (allow) {
256 SIPE_DEBUG_INFO("Authorizing contact %s", who);
257 } else {
258 SIPE_DEBUG_INFO("Blocking contact %s", who);
261 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
262 sipe_change_access_level(sipe_private, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
263 } else {
264 sipe_contact_set_acl(sipe_private, who, allow ? "AA" : "BD");
268 static
269 void sipe_auth_user_cb(void * data)
271 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
272 if (!job) return;
274 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, TRUE);
275 g_free(job);
278 static
279 void sipe_deny_user_cb(void * data)
281 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
282 if (!job) return;
284 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, FALSE);
285 g_free(job);
288 /** @applicable: 2005-
290 static void
291 sipe_process_presence_wpending (struct sipe_core_private *sipe_private,
292 struct sipmsg * msg)
294 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
295 sipe_xml *watchers;
296 const sipe_xml *watcher;
297 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
298 if (msg->response != 0 && msg->response != 200) return;
300 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
302 watchers = sipe_xml_parse(msg->body, msg->bodylen);
303 if (!watchers) return;
305 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
306 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
307 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
308 gboolean on_list = g_hash_table_lookup(sipe_private->buddies, remote_user) != NULL;
310 // TODO pull out optional displayName to pass as alias
311 if (remote_user) {
312 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
313 job->who = remote_user;
314 job->sipe_private = sipe_private;
315 purple_account_request_authorization(
316 sip->account,
317 remote_user,
318 _("you"), /* id */
319 alias,
320 NULL, /* message */
321 on_list,
322 sipe_auth_user_cb,
323 sipe_deny_user_cb,
324 (void *) job);
329 sipe_xml_free(watchers);
330 return;
333 static void
334 sipe_group_add(struct sipe_core_private *sipe_private,
335 struct sipe_group * group)
337 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
338 PurpleGroup * purple_group = purple_find_group(group->name);
339 if (!purple_group) {
340 purple_group = purple_group_new(group->name);
341 purple_blist_add_group(purple_group, NULL);
344 if (purple_group) {
345 group->purple_group = purple_group;
346 sip->groups = g_slist_append(sip->groups, group);
347 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
348 } else {
349 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
353 static struct sipe_group *sipe_group_find_by_id(struct sipe_core_private *sipe_private,
354 int id)
356 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
357 struct sipe_group *group;
358 GSList *entry;
359 if (sip == NULL) {
360 return NULL;
363 entry = sip->groups;
364 while (entry) {
365 group = entry->data;
366 if (group->id == id) {
367 return group;
369 entry = entry->next;
371 return NULL;
374 static struct sipe_group *sipe_group_find_by_name(struct sipe_core_private *sipe_private,
375 const gchar * name)
377 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
378 struct sipe_group *group;
379 GSList *entry;
380 if (!sip || !name) {
381 return NULL;
384 entry = sip->groups;
385 while (entry) {
386 group = entry->data;
387 if (sipe_strequal(group->name, name)) {
388 return group;
390 entry = entry->next;
392 return NULL;
395 static void
396 sipe_group_rename(struct sipe_core_private *sipe_private,
397 struct sipe_group *group,
398 gchar *name)
400 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
401 gchar *body;
402 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
403 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
404 send_soap_request(sipe_private, body);
405 g_free(body);
406 g_free(group->name);
407 group->name = g_strdup(name);
411 * Only appends if no such value already stored.
412 * Like Set in Java.
414 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
415 GSList * res = list;
416 if (!g_slist_find_custom(list, data, func)) {
417 res = g_slist_insert_sorted(list, data, func);
419 return res;
422 static int
423 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
424 return group1->id - group2->id;
428 * Returns string like "2 4 7 8" - group ids buddy belong to.
430 static gchar *
431 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
432 int i = 0;
433 gchar *res;
434 //creating array from GList, converting int to gchar*
435 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
436 GSList *entry = buddy->groups;
438 if (!ids_arr) return NULL;
440 while (entry) {
441 struct sipe_group * group = entry->data;
442 ids_arr[i] = g_strdup_printf("%d", group->id);
443 entry = entry->next;
444 i++;
446 ids_arr[i] = NULL;
447 res = g_strjoinv(" ", ids_arr);
448 g_strfreev(ids_arr);
449 return res;
453 * Sends buddy update to server
455 void
456 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
458 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
459 struct sipe_buddy *buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
460 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
462 if (buddy && purple_buddy) {
463 const char *alias = purple_buddy_get_alias(purple_buddy);
464 gchar *groups = sipe_get_buddy_groups_string(buddy);
465 if (groups) {
466 gchar *body;
467 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
469 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
470 alias, groups, "true", buddy->name, sip->contacts_delta++
472 send_soap_request(SIPE_CORE_PRIVATE, body);
473 g_free(groups);
474 g_free(body);
479 static gboolean process_add_group_response(struct sipe_core_private *sipe_private,
480 struct sipmsg *msg,
481 struct transaction *trans)
483 if (msg->response == 200) {
484 struct sipe_group *group;
485 struct group_user_context *ctx = trans->payload->data;
486 sipe_xml *xml;
487 const sipe_xml *node;
488 char *group_id;
489 struct sipe_buddy *buddy;
491 xml = sipe_xml_parse(msg->body, msg->bodylen);
492 if (!xml) {
493 return FALSE;
496 node = sipe_xml_child(xml, "Body/addGroup/groupID");
497 if (!node) {
498 sipe_xml_free(xml);
499 return FALSE;
502 group_id = sipe_xml_data(node);
503 if (!group_id) {
504 sipe_xml_free(xml);
505 return FALSE;
508 group = g_new0(struct sipe_group, 1);
509 group->id = (int)g_ascii_strtod(group_id, NULL);
510 g_free(group_id);
511 group->name = g_strdup(ctx->group_name);
513 sipe_group_add(sipe_private, group);
515 if (ctx->user_name) {
516 buddy = g_hash_table_lookup(sipe_private->buddies, ctx->user_name);
517 if (buddy) {
518 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
521 sipe_core_group_set_user(SIPE_CORE_PUBLIC, ctx->user_name);
524 sipe_xml_free(xml);
525 return TRUE;
527 return FALSE;
530 static void sipe_group_context_destroy(gpointer data)
532 struct group_user_context *ctx = data;
533 g_free(ctx->group_name);
534 g_free(ctx->user_name);
535 g_free(ctx);
538 static void sipe_group_create (struct sipe_core_private *sipe_private,
539 const gchar *name, const gchar * who)
541 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
542 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
543 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
544 const gchar *soap_name = sipe_strequal(name, _("Other Contacts")) ? "~" : name;
545 gchar *body;
546 ctx->group_name = g_strdup(name);
547 ctx->user_name = g_strdup(who);
548 payload->destroy = sipe_group_context_destroy;
549 payload->data = ctx;
551 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, soap_name, sip->contacts_delta++);
552 send_soap_request_with_cb(sipe_private, NULL, body, process_add_group_response, payload);
553 g_free(body);
556 static void
557 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
558 time_t calculate_from);
560 static int
561 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
563 static const char*
564 sipe_get_status_by_availability(int avail,
565 char** activity);
567 static void
568 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
569 const char *status_id,
570 const char *message,
571 time_t do_not_publish[]);
573 static void
574 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
575 struct sipe_buddy *sbuddy,
576 const char *status_id)
578 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
579 time_t cal_avail_since;
580 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
581 int avail;
582 gchar *self_uri;
584 if (!sbuddy) return;
586 if (cal_status < SIPE_CAL_NO_DATA) {
587 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
588 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
591 /* scheduled Cal update call */
592 if (!status_id) {
593 status_id = sbuddy->last_non_cal_status_id;
594 g_free(sbuddy->activity);
595 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
598 if (!status_id) {
599 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
600 sbuddy->name ? sbuddy->name : "" );
601 return;
604 /* adjust to calendar status */
605 if (cal_status != SIPE_CAL_NO_DATA) {
606 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
608 if (cal_status == SIPE_CAL_BUSY
609 && cal_avail_since > sbuddy->user_avail_since
610 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
612 status_id = SIPE_STATUS_ID_BUSY;
613 g_free(sbuddy->activity);
614 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
616 avail = sipe_get_availability_by_status(status_id, NULL);
618 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
619 if (cal_avail_since > sbuddy->activity_since) {
620 if (cal_status == SIPE_CAL_OOF
621 && avail >= 15000) /* 12000 in 2007 */
623 g_free(sbuddy->activity);
624 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
629 /* then set status_id actually */
630 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
631 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
633 /* set our account state to the one in roaming (including calendar info) */
634 self_uri = sip_uri_self(sipe_private);
635 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
636 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
637 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
640 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
641 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
643 g_free(self_uri);
646 static void
647 sipe_got_user_status(struct sipe_core_private *sipe_private,
648 const char* uri,
649 const char *status_id)
651 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
652 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
654 if (!sbuddy) return;
656 /* Check if on 2005 system contact's calendar,
657 * then set/preserve it.
659 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
660 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
661 } else {
662 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
666 static void
667 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
668 struct sipe_buddy *sbuddy,
669 struct sipe_core_private *sipe_private)
671 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
675 * Updates contact's status
676 * based on their calendar information.
678 * Applicability: 2005 systems
680 static void
681 update_calendar_status(struct sipe_core_private *sipe_private,
682 SIPE_UNUSED_PARAMETER void *unused)
684 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
685 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
687 /* repeat scheduling */
688 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
692 * Schedules process of contacts' status update
693 * based on their calendar information.
694 * Should be scheduled to the beginning of every
695 * 15 min interval, like:
696 * 13:00, 13:15, 13:30, 13:45, etc.
698 * Applicability: 2005 systems
700 static void
701 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
702 time_t calculate_from)
704 int interval = 15*60;
705 /** start of the beginning of closest 15 min interval. */
706 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
708 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
709 asctime(localtime(&calculate_from)));
710 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
711 asctime(localtime(&next_start)));
713 sipe_schedule_seconds(sipe_private,
714 "<+2005-cal-status>",
715 NULL,
716 next_start - time(NULL),
717 update_calendar_status,
718 NULL);
722 * Schedules process of self status publish
723 * based on own calendar information.
724 * Should be scheduled to the beginning of every
725 * 15 min interval, like:
726 * 13:00, 13:15, 13:30, 13:45, etc.
728 * Applicability: 2007+ systems
730 static void
731 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
732 time_t calculate_from)
734 int interval = 5*60;
735 /** start of the beginning of closest 5 min interval. */
736 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
738 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
739 asctime(localtime(&calculate_from)));
740 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
741 asctime(localtime(&next_start)));
743 sipe_schedule_seconds(sipe_private,
744 "<+2007-cal-status>",
745 NULL,
746 next_start - time(NULL),
747 publish_calendar_status_self,
748 NULL);
751 static void sipe_subscribe_resource_uri(const char *name,
752 SIPE_UNUSED_PARAMETER gpointer value,
753 gchar **resources_uri)
755 gchar *tmp = *resources_uri;
756 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
757 g_free(tmp);
760 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
762 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
763 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
764 gchar *tmp = *resources_uri;
766 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
768 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
769 g_free(tmp);
773 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
774 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
775 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
776 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
777 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
780 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
781 gchar *resources_uri,
782 gchar *to)
784 gchar *contact = get_contact(sipe_private);
785 gchar *request;
786 gchar *content;
787 gchar *require = "";
788 gchar *accept = "";
789 gchar *autoextend = "";
790 gchar *content_type;
792 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
793 require = ", categoryList";
794 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
795 content_type = "application/msrtc-adrl-categorylist+xml";
796 content = g_strdup_printf(
797 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
798 "<action name=\"subscribe\" id=\"63792024\">\n"
799 "<adhocList>\n%s</adhocList>\n"
800 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
801 "<category name=\"calendarData\"/>\n"
802 "<category name=\"contactCard\"/>\n"
803 "<category name=\"note\"/>\n"
804 "<category name=\"state\"/>\n"
805 "</categoryList>\n"
806 "</action>\n"
807 "</batchSub>", sipe_private->username, resources_uri);
808 } else {
809 autoextend = "Supported: com.microsoft.autoextend\r\n";
810 content_type = "application/adrl+xml";
811 content = g_strdup_printf(
812 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
813 "<create xmlns=\"\">\n%s</create>\n"
814 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
816 g_free(resources_uri);
818 request = g_strdup_printf(
819 "Require: adhoclist%s\r\n"
820 "Supported: eventlist\r\n"
821 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
822 "Supported: ms-piggyback-first-notify\r\n"
823 "%sSupported: ms-benotify\r\n"
824 "Proxy-Require: ms-benotify\r\n"
825 "Event: presence\r\n"
826 "Content-Type: %s\r\n"
827 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
828 g_free(contact);
830 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
832 g_free(content);
833 g_free(to);
834 g_free(request);
837 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
838 SIPE_UNUSED_PARAMETER void *unused)
840 gchar *to = sip_uri_self(sipe_private);
841 gchar *resources_uri = g_strdup("");
842 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
843 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
844 } else {
845 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
848 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
851 struct presence_batched_routed {
852 gchar *host;
853 GSList *buddies;
856 static void sipe_subscribe_presence_batched_routed_free(void *payload)
858 struct presence_batched_routed *data = payload;
859 GSList *buddies = data->buddies;
860 while (buddies) {
861 g_free(buddies->data);
862 buddies = buddies->next;
864 g_slist_free(data->buddies);
865 g_free(data->host);
866 g_free(payload);
869 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
870 void *payload)
872 struct presence_batched_routed *data = payload;
873 GSList *buddies = data->buddies;
874 gchar *resources_uri = g_strdup("");
875 while (buddies) {
876 gchar *tmp = resources_uri;
877 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
878 g_free(tmp);
879 buddies = buddies->next;
881 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
882 g_strdup(data->host));
886 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
887 * The user sends a single SUBSCRIBE request to the subscribed contact.
888 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
892 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
893 void *buddy_name)
895 gchar *to = sip_uri((char *)buddy_name);
896 gchar *tmp = get_contact(sipe_private);
897 gchar *request;
898 gchar *content = NULL;
899 gchar *autoextend = "";
900 gchar *content_type = "";
901 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
902 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
904 if (sbuddy) sbuddy->just_added = FALSE;
906 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
907 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
908 } else {
909 autoextend = "Supported: com.microsoft.autoextend\r\n";
912 request = g_strdup_printf(
913 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
914 "Supported: ms-piggyback-first-notify\r\n"
915 "%s%sSupported: ms-benotify\r\n"
916 "Proxy-Require: ms-benotify\r\n"
917 "Event: presence\r\n"
918 "Contact: %s\r\n", autoextend, content_type, tmp);
920 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
921 content = g_strdup_printf(
922 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
923 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
924 "<resource uri=\"%s\"%s\n"
925 "</adhocList>\n"
926 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
927 "<category name=\"calendarData\"/>\n"
928 "<category name=\"contactCard\"/>\n"
929 "<category name=\"note\"/>\n"
930 "<category name=\"state\"/>\n"
931 "</categoryList>\n"
932 "</action>\n"
933 "</batchSub>", sipe_private->username, to, context);
936 g_free(tmp);
938 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
940 g_free(content);
941 g_free(to);
942 g_free(request);
945 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
947 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
949 if (!purple_status_is_active(status))
950 return;
952 if (account->gc) {
953 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
954 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
956 if (sip) {
957 gchar *action_name;
958 gchar *tmp;
959 time_t now = time(NULL);
960 const char *status_id = purple_status_get_id(status);
961 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
962 sipe_activity activity = sipe_get_activity_by_token(status_id);
963 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
965 /* when other point of presence clears note, but we are keeping
966 * state if OOF note.
968 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
969 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
970 do_not_publish = FALSE;
973 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
974 status_id, (int)sip->do_not_publish[activity], (int)now);
976 sip->do_not_publish[activity] = 0;
977 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
978 status_id, (int)sip->do_not_publish[activity]);
980 if (do_not_publish)
982 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
983 return;
986 g_free(sip->status);
987 sip->status = g_strdup(status_id);
989 /* hack to escape apostrof before comparison */
990 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
992 /* this will preserve OOF flag as well */
993 if (!sipe_strequal(tmp, sip->note)) {
994 sip->is_oof_note = FALSE;
995 g_free(sip->note);
996 sip->note = g_strdup(note);
997 sip->note_since = time(NULL);
999 g_free(tmp);
1001 /* schedule 2 sec to capture idle flag */
1002 action_name = g_strdup_printf("<%s>", "+set-status");
1003 sipe_schedule_seconds(sipe_private,
1004 action_name,
1005 NULL,
1006 SIPE_IDLE_SET_DELAY,
1007 send_presence_status,
1008 NULL);
1009 g_free(action_name);
1014 void
1015 sipe_set_idle(PurpleConnection * gc,
1016 int interval)
1018 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
1020 if (gc) {
1021 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1022 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1024 if (sip) {
1025 sip->idle_switch = time(NULL);
1026 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
1031 void
1032 sipe_group_buddy(PurpleConnection *gc,
1033 const char *who,
1034 const char *old_group_name,
1035 const char *new_group_name)
1037 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1038 struct sipe_buddy * buddy = g_hash_table_lookup(sipe_private->buddies, who);
1039 struct sipe_group * old_group = NULL;
1040 struct sipe_group * new_group;
1042 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
1043 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1045 if(!buddy) { // buddy not in roaming list
1046 return;
1049 if (old_group_name) {
1050 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
1052 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
1054 if (old_group) {
1055 buddy->groups = g_slist_remove(buddy->groups, old_group);
1056 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
1059 if (!new_group) {
1060 sipe_group_create(sipe_private, new_group_name, who);
1061 } else {
1062 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1063 sipe_core_group_set_user(SIPE_CORE_PUBLIC, who);
1067 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1069 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1071 /* libpurple can call us with undefined buddy or group */
1072 if (buddy && group) {
1073 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1075 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1076 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
1077 purple_blist_rename_buddy(buddy, buddy_name);
1078 g_free(buddy_name);
1080 /* Prepend sip: if needed */
1081 if (!g_str_has_prefix(buddy->name, "sip:")) {
1082 gchar *buf = sip_uri_from_name(buddy->name);
1083 purple_blist_rename_buddy(buddy, buf);
1084 g_free(buf);
1087 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
1088 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
1089 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
1090 b->name = g_strdup(buddy->name);
1091 b->just_added = TRUE;
1092 g_hash_table_insert(sipe_private->buddies, b->name, b);
1093 sipe_group_buddy(gc, b->name, NULL, group->name);
1094 /* @TODO should go to callback */
1095 sipe_subscribe_presence_single(sipe_private,
1096 b->name);
1097 } else {
1098 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
1103 static void sipe_free_buddy(struct sipe_buddy *buddy)
1105 #ifndef _WIN32
1107 * We are calling g_hash_table_foreach_steal(). That means that no
1108 * key/value deallocation functions are called. Therefore the glib
1109 * hash code does not touch the key (buddy->name) or value (buddy)
1110 * of the to-be-deleted hash node at all. It follows that we
1112 * - MUST free the memory for the key ourselves and
1113 * - ARE allowed to do it in this function
1115 * Conclusion: glib must be broken on the Windows platform if sipe
1116 * crashes with SIGTRAP when closing. You'll have to live
1117 * with the memory leak until this is fixed.
1119 g_free(buddy->name);
1120 #endif
1121 g_free(buddy->activity);
1122 g_free(buddy->meeting_subject);
1123 g_free(buddy->meeting_location);
1124 g_free(buddy->note);
1126 g_free(buddy->cal_start_time);
1127 g_free(buddy->cal_free_busy_base64);
1128 g_free(buddy->cal_free_busy);
1129 g_free(buddy->last_non_cal_activity);
1131 sipe_cal_free_working_hours(buddy->cal_working_hours);
1133 g_free(buddy->device_name);
1134 g_slist_free(buddy->groups);
1135 g_free(buddy);
1139 * Unassociates buddy from group first.
1140 * Then see if no groups left, removes buddy completely.
1141 * Otherwise updates buddy groups on server.
1143 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1145 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1146 struct sipe_buddy *b;
1147 struct sipe_group *g = NULL;
1149 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1150 if (!buddy) return;
1152 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
1153 if (!b) return;
1155 if (group) {
1156 g = sipe_group_find_by_name(sipe_private, group->name);
1159 if (g) {
1160 b->groups = g_slist_remove(b->groups, g);
1161 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
1164 if (g_slist_length(b->groups) < 1) {
1165 gchar *action_name = sipe_utils_presence_key(buddy->name);
1166 sipe_schedule_cancel(sipe_private, action_name);
1167 g_free(action_name);
1169 g_hash_table_remove(sipe_private->buddies, buddy->name);
1171 if (b->name) {
1172 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1173 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1174 send_soap_request(sipe_private, body);
1175 g_free(body);
1178 sipe_free_buddy(b);
1179 } else {
1180 //updates groups on server
1181 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
1186 void
1187 sipe_rename_group(PurpleConnection *gc,
1188 const char *old_name,
1189 PurpleGroup *group,
1190 SIPE_UNUSED_PARAMETER GList *moved_buddies)
1192 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1193 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, old_name);
1194 if (s_group) {
1195 sipe_group_rename(sipe_private, s_group, group->name);
1196 } else {
1197 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
1201 void
1202 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1204 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1205 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, group->name);
1206 if (s_group) {
1207 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1208 gchar *body;
1209 SIPE_DEBUG_INFO("Deleting group %s", group->name);
1210 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1211 send_soap_request(sipe_private, body);
1212 g_free(body);
1214 sip->groups = g_slist_remove(sip->groups, s_group);
1215 g_free(s_group->name);
1216 g_free(s_group);
1217 } else {
1218 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
1223 * A callback for g_hash_table_foreach
1225 static void
1226 sipe_buddy_subscribe_cb(char *buddy_name,
1227 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1228 struct sipe_core_private *sipe_private)
1230 gchar *action_name = sipe_utils_presence_key(buddy_name);
1231 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
1232 guint time_range = (g_hash_table_size(sipe_private->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1233 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
1235 sipe_schedule_mseconds(sipe_private,
1236 action_name,
1237 g_strdup(buddy_name),
1238 timeout,
1239 sipe_subscribe_presence_single,
1240 g_free);
1241 g_free(action_name);
1245 * Removes entries from purple buddy list
1246 * that does not correspond ones in the roaming contact list.
1248 static void sipe_cleanup_local_blist(struct sipe_core_private *sipe_private) {
1249 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1250 GSList *buddies = purple_find_buddies(sip->account, NULL);
1251 GSList *entry = buddies;
1252 struct sipe_buddy *buddy;
1253 PurpleBuddy *b;
1254 PurpleGroup *g;
1256 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
1257 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sipe_private->buddies));
1258 while (entry) {
1259 b = entry->data;
1260 g = purple_buddy_get_group(b);
1261 buddy = g_hash_table_lookup(sipe_private->buddies, b->name);
1262 if(buddy) {
1263 gboolean in_sipe_groups = FALSE;
1264 GSList *entry2 = buddy->groups;
1265 while (entry2) {
1266 struct sipe_group *group = entry2->data;
1267 if (sipe_strequal(group->name, g->name)) {
1268 in_sipe_groups = TRUE;
1269 break;
1271 entry2 = entry2->next;
1273 if(!in_sipe_groups) {
1274 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
1275 purple_blist_remove_buddy(b);
1277 } else {
1278 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
1279 purple_blist_remove_buddy(b);
1281 entry = entry->next;
1283 g_slist_free(buddies);
1286 static int
1287 sipe_find_access_level(struct sipe_core_private *sipe_private,
1288 const gchar *type,
1289 const gchar *value,
1290 gboolean *is_group_access);
1292 static void
1293 sipe_refresh_blocked_status_cb(char *buddy_name,
1294 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1295 struct sipe_core_private *sipe_private)
1297 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1298 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1299 gboolean blocked = (container_id == 32000);
1300 gboolean blocked_in_blist = !purple_privacy_check(sip->account, buddy_name);
1302 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1303 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1305 if (blocked != blocked_in_blist) {
1306 if (blocked) {
1307 purple_privacy_deny_add(sip->account, buddy_name, TRUE);
1308 } else {
1309 purple_privacy_deny_remove(sip->account, buddy_name, TRUE);
1312 /* stupid workaround to make pidgin re-render screen to reflect our changes */
1314 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, buddy_name);
1315 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
1316 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
1318 SIPE_DEBUG_INFO_NOFORMAT("sipe_refresh_blocked_status_cb: forcefully refreshing screen.");
1319 sipe_got_user_status(sipe_private, buddy_name, purple_status_get_id(pstatus));
1325 static void
1326 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1328 g_hash_table_foreach(sipe_private->buddies,
1329 (GHFunc) sipe_refresh_blocked_status_cb,
1330 sipe_private);
1333 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1334 struct sipmsg *msg)
1336 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1337 int len = msg->bodylen;
1339 const gchar *tmp = sipmsg_find_header(msg, "Event");
1340 const sipe_xml *item;
1341 sipe_xml *isc;
1342 const gchar *contacts_delta;
1343 const sipe_xml *group_node;
1344 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1345 return FALSE;
1348 /* Convert the contact from XML to Purple Buddies */
1349 isc = sipe_xml_parse(msg->body, len);
1350 if (!isc) {
1351 return FALSE;
1354 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
1355 if (contacts_delta) {
1356 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1359 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1361 /* Parse groups */
1362 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1363 struct sipe_group * group = g_new0(struct sipe_group, 1);
1364 const char *name = sipe_xml_attribute(group_node, "name");
1366 if (g_str_has_prefix(name, "~")) {
1367 name = _("Other Contacts");
1369 group->name = g_strdup(name);
1370 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1372 sipe_group_add(sipe_private, group);
1375 // Make sure we have at least one group
1376 if (g_slist_length(sip->groups) == 0) {
1377 sipe_group_create(sipe_private, _("Other Contacts"), NULL);
1380 /* Parse contacts */
1381 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1382 const gchar *uri = sipe_xml_attribute(item, "uri");
1383 const gchar *name = sipe_xml_attribute(item, "name");
1384 gchar *buddy_name;
1385 struct sipe_buddy *buddy = NULL;
1386 gchar *tmp;
1387 gchar **item_groups;
1388 int i = 0;
1390 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1391 tmp = sip_uri_from_name(uri);
1392 buddy_name = g_ascii_strdown(tmp, -1);
1393 g_free(tmp);
1395 /* assign to group Other Contacts if nothing else received */
1396 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1397 if(is_empty(tmp)) {
1398 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1399 g_free(tmp);
1400 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1402 item_groups = g_strsplit(tmp, " ", 0);
1403 g_free(tmp);
1405 while (item_groups[i]) {
1406 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1408 // If couldn't find the right group for this contact, just put them in the first group we have
1409 if (group == NULL && g_slist_length(sip->groups) > 0) {
1410 group = sip->groups->data;
1413 if (group != NULL) {
1414 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1415 if (!b){
1416 b = purple_buddy_new(sip->account, buddy_name, uri);
1417 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1419 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1422 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
1423 if (name != NULL && strlen(name) != 0) {
1424 purple_blist_alias_buddy(b, name);
1426 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1430 if (!buddy) {
1431 buddy = g_new0(struct sipe_buddy, 1);
1432 buddy->name = g_strdup(b->name);
1433 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1435 SIPE_DEBUG_INFO("Added SIPE buddy %s", buddy->name);
1438 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1440 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
1441 } else {
1442 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1443 name);
1446 i++;
1447 } // while, contact groups
1448 g_strfreev(item_groups);
1449 g_free(buddy_name);
1451 } // for, contacts
1453 sipe_cleanup_local_blist(sipe_private);
1455 /* Add self-contact if not there yet. 2005 systems. */
1456 /* This will resemble subscription to roaming_self in 2007 systems */
1457 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1458 gchar *self_uri = sip_uri_self(sipe_private);
1459 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1461 if (!buddy) {
1462 buddy = g_new0(struct sipe_buddy, 1);
1463 buddy->name = g_strdup(self_uri);
1464 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1466 g_free(self_uri);
1469 sipe_xml_free(isc);
1471 /* subscribe to buddies */
1472 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1473 if (sip->batched_support) {
1474 sipe_subscribe_presence_batched(sipe_private, NULL);
1475 } else {
1476 g_hash_table_foreach(sipe_private->buddies,
1477 (GHFunc)sipe_buddy_subscribe_cb,
1478 sipe_private);
1480 sip->subscribed_buddies = TRUE;
1482 /* for 2005 systems schedule contacts' status update
1483 * based on their calendar information
1485 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1486 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1489 return 0;
1493 * Fires on deregistration event initiated by server.
1494 * [MS-SIPREGE] SIP extension.
1497 // 2007 Example
1499 // Content-Type: text/registration-event
1500 // subscription-state: terminated;expires=0
1501 // ms-diagnostics-public: 4141;reason="User disabled"
1503 // deregistered;event=rejected
1505 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1506 struct sipmsg *msg)
1508 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1509 gchar *event = NULL;
1510 gchar *reason = NULL;
1511 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
1512 gchar *warning;
1514 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
1515 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1517 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1518 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1519 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1520 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1521 } else {
1522 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1523 return;
1526 if (diagnostics != NULL) {
1527 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
1528 } else { // for LCS2005
1529 int error_id = 0;
1530 if (event && sipe_strcase_equal(event, "unregistered")) {
1531 error_id = 4140; // [MS-SIPREGE]
1532 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1533 reason = g_strdup(_("you are already signed in at another location"));
1534 } else if (event && sipe_strcase_equal(event, "rejected")) {
1535 error_id = 4141;
1536 reason = g_strdup(_("user disabled")); // [MS-OCER]
1537 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1538 error_id = 4142;
1539 reason = g_strdup(_("user moved")); // [MS-OCER]
1542 g_free(event);
1543 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1544 g_free(reason);
1546 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1547 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1548 warning);
1549 g_free(warning);
1553 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1554 struct sipmsg *msg)
1556 sipe_xml *xn_provision_group_list;
1557 const sipe_xml *node;
1559 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1561 /* provisionGroup */
1562 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1563 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1564 g_free(sipe_private->focus_factory_uri);
1565 sipe_private->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1566 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->focus_factory_uri=%s",
1567 sipe_private->focus_factory_uri ? sipe_private->focus_factory_uri : "");
1568 break;
1571 sipe_xml_free(xn_provision_group_list);
1574 /** for 2005 system */
1575 static void
1576 sipe_process_provisioning(struct sipe_core_private *sipe_private,
1577 struct sipmsg *msg)
1579 sipe_xml *xn_provision;
1580 const sipe_xml *node;
1582 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
1583 if ((node = sipe_xml_child(xn_provision, "user"))) {
1584 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
1585 if ((node = sipe_xml_child(node, "line"))) {
1586 const gchar *line_uri = sipe_xml_attribute(node, "uri");
1587 const gchar *server = sipe_xml_attribute(node, "server");
1588 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
1589 sip_csta_open(sipe_private, line_uri, server);
1592 sipe_xml_free(xn_provision);
1595 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
1596 struct sipmsg *msg)
1598 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1599 const gchar *contacts_delta;
1600 sipe_xml *xml;
1602 xml = sipe_xml_parse(msg->body, msg->bodylen);
1603 if (!xml)
1605 return;
1608 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
1609 if (contacts_delta)
1611 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1614 sipe_xml_free(xml);
1617 /** MS-PRES container */
1618 struct sipe_container {
1619 guint id;
1620 guint version;
1621 GSList *members;
1623 /** MS-PRES container member */
1624 struct sipe_container_member {
1625 /** user, domain, sameEnterprise, federated, publicCloud; everyone */
1626 gchar *type;
1627 gchar *value;
1630 static void
1631 free_container_member(struct sipe_container_member *member)
1633 if (!member) return;
1635 g_free(member->type);
1636 g_free(member->value);
1637 g_free(member);
1640 static void
1641 free_container(struct sipe_container *container)
1643 GSList *entry;
1645 if (!container) return;
1647 entry = container->members;
1648 while (entry) {
1649 void *data = entry->data;
1650 entry = g_slist_remove(entry, data);
1651 free_container_member((struct sipe_container_member *)data);
1653 g_free(container);
1656 static void
1657 sipe_send_container_members_prepare(const guint container_id,
1658 const guint container_version,
1659 const gchar *action,
1660 const gchar *type,
1661 const gchar *value,
1662 char **container_xmls)
1664 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
1665 gchar *body;
1667 if (!container_xmls) return;
1669 body = g_strdup_printf(
1670 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
1671 container_id,
1672 container_version,
1673 action,
1674 type,
1675 value_str);
1676 g_free(value_str);
1678 if ((*container_xmls) == NULL) {
1679 *container_xmls = body;
1680 } else {
1681 char *tmp = *container_xmls;
1683 *container_xmls = g_strconcat(*container_xmls, body, NULL);
1684 g_free(tmp);
1685 g_free(body);
1689 static void
1690 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
1691 char *container_xmls)
1693 gchar *self;
1694 gchar *contact;
1695 gchar *hdr;
1696 gchar *body;
1698 if (!container_xmls) return;
1700 self = sip_uri_self(sipe_private);
1701 body = g_strdup_printf(
1702 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
1703 "%s"
1704 "</setContainerMembers>",
1705 container_xmls);
1707 contact = get_contact(sipe_private);
1708 hdr = g_strdup_printf("Contact: %s\r\n"
1709 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
1710 g_free(contact);
1712 sip_transport_service(sipe_private,
1713 self,
1714 hdr,
1715 body,
1716 NULL);
1718 g_free(hdr);
1719 g_free(body);
1720 g_free(self);
1724 * Finds locally stored MS-PRES container member
1726 static struct sipe_container_member *
1727 sipe_find_container_member(struct sipe_container *container,
1728 const gchar *type,
1729 const gchar *value)
1731 struct sipe_container_member *member;
1732 GSList *entry;
1734 if (container == NULL || type == NULL) {
1735 return NULL;
1738 entry = container->members;
1739 while (entry) {
1740 member = entry->data;
1741 if (sipe_strcase_equal(member->type, type) &&
1742 sipe_strcase_equal(member->value, value))
1744 return member;
1746 entry = entry->next;
1748 return NULL;
1752 * Finds locally stored MS-PRES container by id
1754 static struct sipe_container *
1755 sipe_find_container(struct sipe_core_private *sipe_private,
1756 guint id)
1758 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1759 struct sipe_container *container;
1760 GSList *entry;
1762 if (sip == NULL) {
1763 return NULL;
1766 entry = sip->containers;
1767 while (entry) {
1768 container = entry->data;
1769 if (id == container->id) {
1770 return container;
1772 entry = entry->next;
1774 return NULL;
1777 static GSList *
1778 sipe_get_access_domains(struct sipe_core_private *sipe_private)
1780 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1781 struct sipe_container *container;
1782 struct sipe_container_member *member;
1783 GSList *entry;
1784 GSList *entry2;
1785 GSList *res = NULL;
1787 if (!sip) return NULL;
1789 entry = sip->containers;
1790 while (entry) {
1791 container = entry->data;
1793 entry2 = container->members;
1794 while (entry2) {
1795 member = entry2->data;
1796 if (sipe_strcase_equal(member->type, "domain"))
1798 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1800 entry2 = entry2->next;
1802 entry = entry->next;
1804 return res;
1808 * Returns pointer to domain part in provided Email URL
1810 * @param email an email URL. Example: first.last@hq.company.com
1811 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1813 * Doesn't allocate memory
1815 static const char *
1816 sipe_get_domain(const char *email)
1818 char *tmp;
1820 if (!email) return NULL;
1822 tmp = strstr(email, "@");
1824 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1825 return tmp+1;
1826 } else {
1827 return NULL;
1832 /* @TODO: replace with binary search for faster access? */
1833 /** source: http://support.microsoft.com/kb/897567 */
1834 static const char * const public_domains [] = {
1835 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1836 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1837 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1838 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1839 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1840 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1841 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1842 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1843 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1844 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1845 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1846 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1847 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1848 "yahoo.com",
1849 NULL};
1851 static gboolean
1852 sipe_is_public_domain(const char *domain)
1854 int i = 0;
1855 while (public_domains[i]) {
1856 if (sipe_strcase_equal(public_domains[i], domain)) {
1857 return TRUE;
1859 i++;
1861 return FALSE;
1865 * Access Levels
1866 * 32000 - Blocked
1867 * 400 - Personal
1868 * 300 - Team
1869 * 200 - Company
1870 * 100 - Public
1872 static const char *
1873 sipe_get_access_level_name(int container_id)
1875 switch(container_id) {
1876 case 32000: return _("Blocked");
1877 case 400: return _("Personal");
1878 case 300: return _("Team");
1879 case 200: return _("Company");
1880 case 100: return _("Public");
1882 return _("Unknown");
1885 static const guint containers[] = {32000, 400, 300, 200, 100};
1886 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1889 static int
1890 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1891 const gchar *type,
1892 const gchar *value)
1894 unsigned int i = 0;
1895 const gchar *value_mod = value;
1897 if (!type) return -1;
1899 if (sipe_strequal("user", type)) {
1900 value_mod = sipe_get_no_sip_uri(value);
1903 for (i = 0; i < CONTAINERS_LEN; i++) {
1904 struct sipe_container_member *member;
1905 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1906 if (!container) continue;
1908 member = sipe_find_container_member(container, type, value_mod);
1909 if (member) return containers[i];
1912 return -1;
1915 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1916 static int
1917 sipe_find_access_level(struct sipe_core_private *sipe_private,
1918 const gchar *type,
1919 const gchar *value,
1920 gboolean *is_group_access)
1922 int container_id = -1;
1924 if (sipe_strequal("user", type)) {
1925 const char *domain;
1926 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1928 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1929 if (container_id >= 0) {
1930 if (is_group_access) *is_group_access = FALSE;
1931 return container_id;
1934 domain = sipe_get_domain(no_sip_uri);
1935 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1936 if (container_id >= 0) {
1937 if (is_group_access) *is_group_access = TRUE;
1938 return container_id;
1941 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1942 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1943 if (is_group_access) *is_group_access = TRUE;
1944 return container_id;
1947 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1948 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1949 if (is_group_access) *is_group_access = TRUE;
1950 return container_id;
1953 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1954 if ((container_id >= 0)) {
1955 if (is_group_access) *is_group_access = TRUE;
1956 return container_id;
1958 } else {
1959 container_id = sipe_find_member_access_level(sipe_private, type, value);
1960 if (is_group_access) *is_group_access = FALSE;
1963 return container_id;
1967 * @param container_id a new access level. If -1 then current access level
1968 * is just removed (I.e. the member is removed from all containers).
1969 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1970 * @param value a value for member. E.g. SIP URI for "user" member type.
1972 static void
1973 sipe_change_access_level(struct sipe_core_private *sipe_private,
1974 const int container_id,
1975 const gchar *type,
1976 const gchar *value)
1978 unsigned int i;
1979 int current_container_id = -1;
1980 char *container_xmls = NULL;
1982 /* for each container: find/delete */
1983 for (i = 0; i < CONTAINERS_LEN; i++) {
1984 struct sipe_container_member *member;
1985 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1987 if (!container) continue;
1989 member = sipe_find_container_member(container, type, value);
1990 if (member) {
1991 current_container_id = containers[i];
1992 /* delete/publish current access level */
1993 if (container_id < 0 || container_id != current_container_id) {
1994 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
1995 /* remove member from our cache, to be able to recalculate AL below */
1996 container->members = g_slist_remove(container->members, member);
1997 current_container_id = -1;
2002 /* recalculate AL below */
2003 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
2005 /* assign/publish new access level */
2006 if (container_id != current_container_id && container_id >= 0) {
2007 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
2008 guint version = container ? container->version : 0;
2010 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
2013 if (container_xmls) {
2014 sipe_send_set_container_members(sipe_private, container_xmls);
2016 g_free(container_xmls);
2019 static void
2020 free_publication(struct sipe_publication *publication)
2022 g_free(publication->category);
2023 g_free(publication->cal_event_hash);
2024 g_free(publication->note);
2026 g_free(publication->working_hours_xml_str);
2027 g_free(publication->fb_start_str);
2028 g_free(publication->free_busy_base64);
2030 g_free(publication);
2033 /* key is <category><instance><container> */
2034 static gboolean
2035 sipe_is_our_publication(struct sipe_core_private *sipe_private,
2036 const gchar *key)
2038 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2039 GSList *entry;
2041 /* filling keys for our publications if not yet cached */
2042 if (!sip->our_publication_keys) {
2043 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
2044 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
2045 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
2046 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
2047 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
2048 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
2049 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
2051 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
2052 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
2053 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
2054 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
2055 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
2056 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
2057 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
2058 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
2059 SIPE_DEBUG_INFO("\tNote : %u", 0);
2060 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
2062 /* device */
2063 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2064 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2066 /* state:machineState */
2067 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2068 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2069 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2070 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2072 /* state:userState */
2073 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2074 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2075 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2076 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2078 /* state:calendarState */
2079 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2080 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2081 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2082 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2084 /* state:calendarState OOF */
2085 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2086 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2087 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2088 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2090 /* note */
2091 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2092 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2093 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2094 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2095 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2096 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2098 /* note OOF */
2099 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2100 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2101 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2102 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2103 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2104 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2106 /* calendarData:WorkingHours */
2107 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2108 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2109 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2110 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2111 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2112 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2113 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2114 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2115 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2116 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2117 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2118 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2120 /* calendarData:FreeBusy */
2121 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2122 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2123 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2124 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2125 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2126 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2127 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2128 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2129 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2130 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2131 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2132 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2134 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
2135 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2138 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
2140 entry = sip->our_publication_keys;
2141 while (entry) {
2142 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
2143 if (sipe_strequal(entry->data, key)) {
2144 return TRUE;
2146 entry = entry->next;
2148 return FALSE;
2151 /** Property names to store in blist.xml */
2152 #define ALIAS_PROP "alias"
2153 #define EMAIL_PROP "email"
2154 #define PHONE_PROP "phone"
2155 #define PHONE_DISPLAY_PROP "phone-display"
2156 #define PHONE_MOBILE_PROP "phone-mobile"
2157 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2158 #define PHONE_HOME_PROP "phone-home"
2159 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2160 #define PHONE_OTHER_PROP "phone-other"
2161 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2162 #define PHONE_CUSTOM1_PROP "phone-custom1"
2163 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2164 #define SITE_PROP "site"
2165 #define COMPANY_PROP "company"
2166 #define DEPARTMENT_PROP "department"
2167 #define TITLE_PROP "title"
2168 #define OFFICE_PROP "office"
2169 /** implies work address */
2170 #define ADDRESS_STREET_PROP "address-street"
2171 #define ADDRESS_CITY_PROP "address-city"
2172 #define ADDRESS_STATE_PROP "address-state"
2173 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2174 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
2177 * Tries to figure out user first and last name
2178 * based on Display Name and email properties.
2180 * Allocates memory - must be g_free()'d
2182 * Examples to parse:
2183 * First Last
2184 * First Last - Company Name
2185 * Last, First
2186 * Last, First M.
2187 * Last, First (C)(STP) (Company)
2188 * first.last@company.com (preprocessed as "first last")
2189 * first.last.company.com@reuters.net (preprocessed as "first last company com")
2191 * Unusable examples:
2192 * user@company.com (preprocessed as "user")
2193 * first.m.last@company.com (preprocessed as "first m last")
2194 * user.company.com@reuters.net (preprocessed as "user company com")
2196 static void
2197 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
2198 const char *uri,
2199 char **first_name,
2200 char **last_name)
2202 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2203 PurpleBuddy *p_buddy;
2204 char *display_name;
2205 const char *email;
2206 const char *first, *last;
2207 char *tmp;
2208 char **parts;
2209 gboolean has_comma = FALSE;
2211 if (!sip || !uri) return;
2213 p_buddy = purple_find_buddy(sip->account, uri);
2215 if (!p_buddy) return;
2217 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
2218 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
2220 if (!display_name && !email) return;
2222 /* if no display name, make "first last anything_else" out of email */
2223 if (email && !display_name) {
2224 display_name = g_strndup(email, strstr(email, "@") - email);
2225 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
2226 g_free(tmp);
2229 if (display_name) {
2230 has_comma = (strstr(display_name, ",") != NULL);
2231 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
2232 g_free(tmp);
2233 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
2234 g_free(tmp);
2237 parts = g_strsplit(display_name, " ", 0);
2239 if (!parts[0] || !parts[1]) {
2240 g_free(display_name);
2241 g_strfreev(parts);
2242 return;
2245 if (has_comma) {
2246 last = parts[0];
2247 first = parts[1];
2248 } else {
2249 first = parts[0];
2250 last = parts[1];
2253 if (first_name) {
2254 *first_name = g_strstrip(g_strdup(first));
2257 if (last_name) {
2258 *last_name = g_strstrip(g_strdup(last));
2261 g_free(display_name);
2262 g_strfreev(parts);
2266 * Update user information
2268 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2269 * @param property_name
2270 * @param property_value may be modified to strip white space
2272 static void
2273 sipe_update_user_info(struct sipe_core_private *sipe_private,
2274 const char *uri,
2275 const char *property_name,
2276 char *property_value)
2278 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2279 GSList *buddies, *entry;
2281 if (!property_name || strlen(property_name) == 0) return;
2283 if (property_value)
2284 property_value = g_strstrip(property_value);
2286 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
2287 while (entry) {
2288 const char *prop_str;
2289 const char *server_alias;
2290 PurpleBuddy *p_buddy = entry->data;
2292 /* for Display Name */
2293 if (sipe_strequal(property_name, ALIAS_PROP)) {
2294 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
2295 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2296 purple_blist_alias_buddy(p_buddy, property_value);
2299 server_alias = purple_buddy_get_server_alias(p_buddy);
2300 if (!is_empty(property_value) &&
2301 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2303 purple_blist_server_alias_buddy(p_buddy, property_value);
2306 /* for other properties */
2307 else {
2308 if (!is_empty(property_value)) {
2309 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
2310 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2311 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
2316 entry = entry->next;
2318 g_slist_free(buddies);
2322 * Update user phone
2323 * Suitable for both 2005 and 2007 systems.
2325 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2326 * @param phone_type
2327 * @param phone may be modified to strip white space
2328 * @param phone_display_string may be modified to strip white space
2330 static void
2331 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2332 const char *uri,
2333 const gchar *phone_type,
2334 gchar *phone,
2335 gchar *phone_display_string)
2337 const char *phone_node = PHONE_PROP; /* work phone by default */
2338 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
2340 if(!phone || strlen(phone) == 0) return;
2342 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2343 phone_node = PHONE_MOBILE_PROP;
2344 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
2345 } else if (sipe_strequal(phone_type, "home")) {
2346 phone_node = PHONE_HOME_PROP;
2347 phone_display_node = PHONE_HOME_DISPLAY_PROP;
2348 } else if (sipe_strequal(phone_type, "other")) {
2349 phone_node = PHONE_OTHER_PROP;
2350 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
2351 } else if (sipe_strequal(phone_type, "custom1")) {
2352 phone_node = PHONE_CUSTOM1_PROP;
2353 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
2356 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2357 if (phone_display_string) {
2358 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2362 void
2363 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2365 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2367 /* Do in parallel.
2368 * If failed, the branch will be disabled for subsequent calls.
2369 * Can't rely that user turned the functionality on in account settings.
2371 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2372 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2374 /* schedule repeat */
2375 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2376 "<+update-calendar>",
2377 NULL,
2378 UPDATE_CALENDAR_INTERVAL,
2379 (sipe_schedule_action)sipe_core_update_calendar,
2380 NULL);
2382 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2386 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2387 * by using standard Purple's means of signals and saved statuses.
2389 * Thus all UI elements get updated: Status Button with Note, docklet.
2390 * This is ablolutely important as both our status and note can come
2391 * inbound (roaming) or be updated programmatically (e.g. based on our
2392 * calendar data).
2394 static void
2395 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2396 const char *status_id,
2397 const char *message,
2398 time_t do_not_publish[])
2400 PurpleStatus *status = purple_account_get_active_status(account);
2401 gboolean changed = TRUE;
2403 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2404 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2406 changed = FALSE;
2409 if (purple_savedstatus_is_idleaway()) {
2410 changed = FALSE;
2413 if (changed) {
2414 PurpleSavedStatus *saved_status;
2415 const PurpleStatusType *acct_status_type =
2416 purple_status_type_find_with_id(account->status_types, status_id);
2417 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2418 sipe_activity activity = sipe_get_activity_by_token(status_id);
2420 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2421 if (saved_status) {
2422 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2425 /* If this type+message is unique then create a new transient saved status
2426 * Ref: gtkstatusbox.c
2428 if (!saved_status) {
2429 GList *tmp;
2430 GList *active_accts = purple_accounts_get_all_active();
2432 saved_status = purple_savedstatus_new(NULL, primitive);
2433 purple_savedstatus_set_message(saved_status, message);
2435 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2436 purple_savedstatus_set_substatus(saved_status,
2437 (PurpleAccount *)tmp->data, acct_status_type, message);
2439 g_list_free(active_accts);
2442 do_not_publish[activity] = time(NULL);
2443 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2444 status_id, (int)do_not_publish[activity]);
2446 /* Set the status for each account */
2447 purple_savedstatus_activate(saved_status);
2451 struct hash_table_delete_payload {
2452 GHashTable *hash_table;
2453 guint container;
2456 static void
2457 sipe_remove_category_container_publications_cb(const char *name,
2458 struct sipe_publication *publication,
2459 struct hash_table_delete_payload *payload)
2461 if (publication->container == payload->container) {
2462 g_hash_table_remove(payload->hash_table, name);
2465 static void
2466 sipe_remove_category_container_publications(GHashTable *our_publications,
2467 const char *category,
2468 guint container)
2470 struct hash_table_delete_payload payload;
2471 payload.hash_table = g_hash_table_lookup(our_publications, category);
2473 if (!payload.hash_table) return;
2475 payload.container = container;
2476 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2479 static void
2480 send_publish_category_initial(struct sipe_core_private *sipe_private);
2483 * When we receive some self (BE) NOTIFY with a new subscriber
2484 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2487 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2488 struct sipmsg *msg)
2490 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2491 gchar *contact;
2492 gchar *to;
2493 sipe_xml *xml;
2494 const sipe_xml *node;
2495 const sipe_xml *node2;
2496 char *display_name = NULL;
2497 char *uri;
2498 GSList *category_names = NULL;
2499 int aggreg_avail = 0;
2500 gboolean do_update_status = FALSE;
2501 gboolean has_note_cleaned = FALSE;
2503 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2505 xml = sipe_xml_parse(msg->body, msg->bodylen);
2506 if (!xml) return;
2508 contact = get_contact(sipe_private);
2509 to = sip_uri_self(sipe_private);
2512 /* categories */
2513 /* set list of categories participating in this XML */
2514 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2515 const gchar *name = sipe_xml_attribute(node, "name");
2516 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2518 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2519 category_names ? (int) g_slist_length(category_names) : -1);
2520 /* drop category information */
2521 if (category_names) {
2522 GSList *entry = category_names;
2523 while (entry) {
2524 GHashTable *cat_publications;
2525 const gchar *category = entry->data;
2526 entry = entry->next;
2527 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2528 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2529 if (cat_publications) {
2530 g_hash_table_remove(sip->our_publications, category);
2531 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2535 g_slist_free(category_names);
2536 /* filling our categories reflected in roaming data */
2537 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2538 const char *tmp;
2539 const gchar *name = sipe_xml_attribute(node, "name");
2540 guint container = sipe_xml_int_attribute(node, "container", -1);
2541 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2542 guint version = sipe_xml_int_attribute(node, "version", 0);
2543 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2544 sipe_utils_str_to_time(tmp) : 0;
2545 gchar *key;
2546 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2548 /* Ex. clear note: <category name="note"/> */
2549 if (container == (guint)-1) {
2550 g_free(sip->note);
2551 sip->note = NULL;
2552 do_update_status = TRUE;
2553 continue;
2556 /* Ex. clear note: <category name="note" container="200"/> */
2557 if (instance == (guint)-1) {
2558 if (container == 200) {
2559 g_free(sip->note);
2560 sip->note = NULL;
2561 do_update_status = TRUE;
2563 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2564 sipe_remove_category_container_publications(
2565 sip->our_publications, name, container);
2566 continue;
2569 /* key is <category><instance><container> */
2570 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2571 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2573 /* capture all userState publication for later clean up if required */
2574 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2575 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2577 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2578 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2579 publication->category = g_strdup(name);
2580 publication->instance = instance;
2581 publication->container = container;
2582 publication->version = version;
2584 if (!sip->user_state_publications) {
2585 sip->user_state_publications = g_hash_table_new_full(
2586 g_str_hash, g_str_equal,
2587 g_free, (GDestroyNotify)free_publication);
2589 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2590 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2591 key, version);
2595 if (sipe_is_our_publication(sipe_private, key)) {
2596 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2598 publication->category = g_strdup(name);
2599 publication->instance = instance;
2600 publication->container = container;
2601 publication->version = version;
2603 /* filling publication->availability */
2604 if (sipe_strequal(name, "state")) {
2605 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2606 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2608 if (xn_avail) {
2609 gchar *avail_str = sipe_xml_data(xn_avail);
2610 if (avail_str) {
2611 publication->availability = atoi(avail_str);
2613 g_free(avail_str);
2615 /* for calendarState */
2616 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
2617 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
2618 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
2620 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
2621 if (xn_activity) {
2622 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
2623 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
2625 event->is_meeting = TRUE;
2628 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
2629 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
2631 publication->cal_event_hash = sipe_cal_event_hash(event);
2632 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
2633 publication->cal_event_hash);
2634 sipe_cal_event_free(event);
2637 /* filling publication->note */
2638 if (sipe_strequal(name, "note")) {
2639 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
2641 if (!has_note_cleaned) {
2642 has_note_cleaned = TRUE;
2644 g_free(sip->note);
2645 sip->note = NULL;
2646 sip->note_since = publish_time;
2648 do_update_status = TRUE;
2651 g_free(publication->note);
2652 publication->note = NULL;
2653 if (xn_body) {
2654 char *tmp;
2656 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
2657 g_free(tmp);
2658 if (publish_time >= sip->note_since) {
2659 g_free(sip->note);
2660 sip->note = g_strdup(publication->note);
2661 sip->note_since = publish_time;
2662 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
2664 do_update_status = TRUE;
2669 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
2670 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
2671 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
2672 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
2673 if (xn_free_busy) {
2674 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2675 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
2677 if (xn_working_hours) {
2678 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
2682 if (!cat_publications) {
2683 cat_publications = g_hash_table_new_full(
2684 g_str_hash, g_str_equal,
2685 g_free, (GDestroyNotify)free_publication);
2686 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2687 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
2689 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2690 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
2692 g_free(key);
2694 /* aggregateState (not an our publication) from 2-nd container */
2695 if (sipe_strequal(name, "state") && container == 2) {
2696 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2698 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
2699 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2701 if (xn_avail) {
2702 gchar *avail_str = sipe_xml_data(xn_avail);
2703 if (avail_str) {
2704 aggreg_avail = atoi(avail_str);
2706 g_free(avail_str);
2709 do_update_status = TRUE;
2713 /* userProperties published by server from AD */
2714 if (!sip->csta && sipe_strequal(name, "userProperties")) {
2715 const sipe_xml *line;
2716 /* line, for Remote Call Control (RCC) */
2717 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
2718 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
2719 const gchar *line_type = sipe_xml_attribute(line, "lineType");
2720 gchar *line_uri;
2722 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
2724 line_uri = sipe_xml_data(line);
2725 if (line_uri) {
2726 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
2727 sip_csta_open(sipe_private, line_uri, line_server);
2729 g_free(line_uri);
2731 break;
2735 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
2736 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2738 /* containers */
2739 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
2740 guint id = sipe_xml_int_attribute(node, "id", 0);
2741 struct sipe_container *container = sipe_find_container(sipe_private, id);
2743 if (container) {
2744 sip->containers = g_slist_remove(sip->containers, container);
2745 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
2746 free_container(container);
2748 container = g_new0(struct sipe_container, 1);
2749 container->id = id;
2750 container->version = sipe_xml_int_attribute(node, "version", 0);
2751 sip->containers = g_slist_append(sip->containers, container);
2752 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
2754 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
2755 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2756 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
2757 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
2758 container->members = g_slist_append(container->members, member);
2759 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2760 member->type, member->value ? member->value : "");
2764 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2765 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2766 char *container_xmls = NULL;
2767 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2768 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2770 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2771 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2772 /* initial set-up to let counterparties see your status */
2773 if (sameEnterpriseAL < 0) {
2774 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2775 guint version = container ? container->version : 0;
2776 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2778 if (federatedAL < 0) {
2779 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2780 guint version = container ? container->version : 0;
2781 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2783 sip->access_level_set = TRUE;
2785 if (container_xmls) {
2786 sipe_send_set_container_members(sipe_private, container_xmls);
2788 g_free(container_xmls);
2791 /* Refresh contacts' blocked status */
2792 sipe_refresh_blocked_status(sipe_private);
2794 /* subscribers */
2795 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2796 const char *user;
2797 const char *acknowledged;
2798 gchar *hdr;
2799 gchar *body;
2801 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2802 if (!user) continue;
2803 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2804 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2805 uri = sip_uri_from_name(user);
2807 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
2809 acknowledged= sipe_xml_attribute(node, "acknowledged");
2810 if(sipe_strcase_equal(acknowledged,"false")){
2811 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2812 if (!purple_find_buddy(sip->account, uri)) {
2813 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
2816 hdr = g_strdup_printf(
2817 "Contact: %s\r\n"
2818 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2820 body = g_strdup_printf(
2821 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2822 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2823 "</setSubscribers>", user);
2825 sip_transport_service(sipe_private,
2827 hdr,
2828 body,
2829 NULL);
2830 g_free(body);
2831 g_free(hdr);
2833 g_free(display_name);
2834 g_free(uri);
2837 g_free(contact);
2838 sipe_xml_free(xml);
2840 /* Publish initial state if not yet.
2841 * Assuming this happens on initial responce to subscription to roaming-self
2842 * so we've already updated our roaming data in full.
2843 * Only for 2007+
2845 if (!sip->initial_state_published) {
2846 send_publish_category_initial(sipe_private);
2847 sip->initial_state_published = TRUE;
2848 /* dalayed run */
2849 sipe_schedule_seconds(sipe_private,
2850 "<+update-calendar>",
2851 NULL,
2852 UPDATE_CALENDAR_DELAY,
2853 (sipe_schedule_action)sipe_core_update_calendar,
2854 NULL);
2855 do_update_status = FALSE;
2856 } else if (aggreg_avail) {
2858 g_free(sip->status);
2859 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2860 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2861 } else {
2862 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2866 if (do_update_status) {
2867 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2868 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2871 g_free(to);
2874 /* IM Session (INVITE and MESSAGE methods) */
2876 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2877 static gchar *
2878 get_end_points (struct sipe_core_private *sipe_private,
2879 struct sip_session *session)
2881 gchar *res;
2883 if (session == NULL) {
2884 return NULL;
2887 res = g_strdup_printf("<sip:%s>", sipe_private->username);
2889 SIPE_DIALOG_FOREACH {
2890 gchar *tmp = res;
2891 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2892 g_free(tmp);
2894 if (dialog->theirepid) {
2895 tmp = res;
2896 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2897 g_free(tmp);
2899 } SIPE_DIALOG_FOREACH_END;
2901 return res;
2904 static gboolean
2905 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2906 struct sipmsg *msg,
2907 SIPE_UNUSED_PARAMETER struct transaction *trans)
2909 gboolean ret = TRUE;
2911 if (msg->response != 200) {
2912 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2913 return FALSE;
2916 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2918 return ret;
2922 * Asks UA/proxy about its capabilities.
2924 static void sipe_options_request(struct sipe_core_private *sipe_private,
2925 const char *who)
2927 gchar *to = sip_uri(who);
2928 gchar *contact = get_contact(sipe_private);
2929 gchar *request = g_strdup_printf(
2930 "Accept: application/sdp\r\n"
2931 "Contact: %s\r\n", contact);
2932 g_free(contact);
2934 sip_transport_request(sipe_private,
2935 "OPTIONS",
2938 request,
2939 NULL,
2940 NULL,
2941 process_options_response);
2943 g_free(to);
2944 g_free(request);
2947 static void
2948 sipe_notify_user(struct sipe_core_private *sipe_private,
2949 struct sip_session *session,
2950 PurpleMessageFlags flags,
2951 const gchar *message)
2953 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2954 PurpleConversation *conv;
2956 if (!session->backend_session) {
2957 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
2958 } else {
2959 /* TEMPORARY HACK!! */
2960 conv = (PurpleConversation *) session->backend_session;
2962 purple_conversation_write(conv, NULL, message, flags, time(NULL));
2965 void
2966 sipe_present_info(struct sipe_core_private *sipe_private,
2967 struct sip_session *session,
2968 const gchar *message)
2970 sipe_notify_user(sipe_private, session, PURPLE_MESSAGE_SYSTEM, message);
2973 void
2974 sipe_present_err(struct sipe_core_private *sipe_private,
2975 struct sip_session *session,
2976 const gchar *message)
2978 sipe_notify_user(sipe_private, session, PURPLE_MESSAGE_ERROR, message);
2981 void
2982 sipe_present_message_undelivered_err(struct sipe_core_private *sipe_private,
2983 struct sip_session *session,
2984 int sip_error,
2985 int sip_warning,
2986 const gchar *who,
2987 const gchar *message)
2989 char *msg, *msg_tmp, *msg_tmp2;
2990 const char *label;
2992 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
2993 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2994 g_free(msg_tmp);
2995 /* Service unavailable; Server Internal Error; Server Time-out */
2996 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
2997 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
2998 g_free(msg);
2999 msg = NULL;
3000 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
3001 label = _("This message was not delivered to %s because the service is not available");
3002 } else if (sip_error == 486) { /* Busy Here */
3003 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3004 } else if (sip_error == 415) { /* Unsupported media type */
3005 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
3006 } else {
3007 label = _("This message was not delivered to %s because one or more recipients are offline");
3010 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
3011 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
3012 msg ? ":" : "",
3013 msg ? msg : "");
3014 sipe_present_err(sipe_private, session, msg_tmp);
3015 g_free(msg_tmp2);
3016 g_free(msg_tmp);
3017 g_free(msg);
3021 static gboolean
3022 process_message_response(struct sipe_core_private *sipe_private,
3023 struct sipmsg *msg,
3024 SIPE_UNUSED_PARAMETER struct transaction *trans)
3026 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3027 gboolean ret = TRUE;
3028 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3029 struct sip_session *session = sipe_session_find_im(sipe_private, with);
3030 struct sip_dialog *dialog;
3031 gchar *cseq;
3032 char *key;
3033 struct queued_message *message;
3035 if (!session) {
3036 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
3037 g_free(with);
3038 return FALSE;
3041 dialog = sipe_dialog_find(session, with);
3042 if (!dialog) {
3043 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
3044 g_free(with);
3045 return FALSE;
3048 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3049 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3050 g_free(cseq);
3051 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3053 if (msg->response >= 400) {
3054 PurpleBuddy *pbuddy;
3055 const char *alias = with;
3056 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3057 int warning = -1;
3059 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
3061 if (warn_hdr) {
3062 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3063 if (parts[0]) {
3064 warning = atoi(parts[0]);
3066 g_strfreev(parts);
3069 /* cancel file transfer as rejected by server */
3070 if (msg->response == 606 && /* Not acceptable all. */
3071 warning == 309 && /* Message contents not allowed by policy */
3072 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3074 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
3075 sipe_ft_incoming_cancel(dialog, parsed_body);
3076 sipe_utils_nameval_free(parsed_body);
3079 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3080 alias = purple_buddy_get_alias(pbuddy);
3083 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, (message ? message->body : NULL));
3085 /* drop dangling IM sessions: assume that BYE from remote never reached us */
3086 if (msg->response == 408 || /* Request timeout */
3087 msg->response == 480 || /* Temporarily Unavailable */
3088 msg->response == 481) { /* Call/Transaction Does Not Exist */
3089 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
3090 sip_transport_bye(sipe_private, dialog);
3092 /* We might not get a valid reply to our BYE,
3093 so make sure the dialog is removed for sure. */
3094 sipe_dialog_remove(session, with);
3095 dialog = NULL;
3098 ret = FALSE;
3099 } else {
3100 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3101 if (message_id) {
3102 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
3103 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
3104 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3107 g_hash_table_remove(session->unconfirmed_messages, key);
3108 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
3109 key, g_hash_table_size(session->unconfirmed_messages));
3112 g_free(key);
3113 g_free(with);
3115 if (ret) sipe_im_process_queue(sipe_private, session);
3116 return ret;
3119 static void sipe_send_message(struct sipe_core_private *sipe_private,
3120 struct sip_dialog *dialog,
3121 const char *msg, const char *content_type)
3123 gchar *hdr;
3124 gchar *tmp;
3125 char *msgtext = NULL;
3126 const gchar *msgr = "";
3127 gchar *tmp2 = NULL;
3129 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
3130 char *msgformat;
3131 gchar *msgr_value;
3133 sipe_parse_html(msg, &msgformat, &msgtext);
3134 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
3136 msgr_value = sipmsg_get_msgr_string(msgformat);
3137 g_free(msgformat);
3138 if (msgr_value) {
3139 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
3140 g_free(msgr_value);
3142 } else {
3143 msgtext = g_strdup(msg);
3146 tmp = get_contact(sipe_private);
3147 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3148 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3149 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3150 if (content_type == NULL)
3151 content_type = "text/plain";
3153 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
3154 g_free(tmp);
3155 g_free(tmp2);
3157 sip_transport_request(sipe_private,
3158 "MESSAGE",
3159 dialog->with,
3160 dialog->with,
3161 hdr,
3162 msgtext,
3163 dialog,
3164 process_message_response);
3165 g_free(msgtext);
3166 g_free(hdr);
3170 void
3171 sipe_im_process_queue (struct sipe_core_private *sipe_private,
3172 struct sip_session * session)
3174 GSList *entry2 = session->outgoing_message_queue;
3175 while (entry2) {
3176 struct queued_message *msg = entry2->data;
3178 /* for multiparty chat or conference */
3179 if (session->is_multiparty || session->focus_uri) {
3180 gchar *who = sip_uri_self(sipe_private);
3181 sipe_backend_chat_message(SIPE_CORE_PUBLIC,
3182 session->chat_id,
3183 who,
3184 msg->body);
3185 g_free(who);
3188 SIPE_DIALOG_FOREACH {
3189 char *key;
3190 struct queued_message *message;
3192 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3194 message = g_new0(struct queued_message,1);
3195 message->body = g_strdup(msg->body);
3196 if (msg->content_type != NULL)
3197 message->content_type = g_strdup(msg->content_type);
3199 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3200 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3201 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
3202 key, g_hash_table_size(session->unconfirmed_messages));
3203 g_free(key);
3205 sipe_send_message(sipe_private, dialog, msg->body, msg->content_type);
3206 } SIPE_DIALOG_FOREACH_END;
3208 entry2 = sipe_session_dequeue_message(session);
3212 static void
3213 sipe_refer_notify(struct sipe_core_private *sipe_private,
3214 struct sip_session *session,
3215 const gchar *who,
3216 int status,
3217 const gchar *desc)
3219 gchar *hdr;
3220 gchar *body;
3221 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3223 hdr = g_strdup_printf(
3224 "Event: refer\r\n"
3225 "Subscription-State: %s\r\n"
3226 "Content-Type: message/sipfrag\r\n",
3227 status >= 200 ? "terminated" : "active");
3229 body = g_strdup_printf(
3230 "SIP/2.0 %d %s\r\n",
3231 status, desc);
3233 sip_transport_request(sipe_private,
3234 "NOTIFY",
3235 who,
3236 who,
3237 hdr,
3238 body,
3239 dialog,
3240 NULL);
3242 g_free(hdr);
3243 g_free(body);
3246 static gboolean
3247 process_invite_response(struct sipe_core_private *sipe_private,
3248 struct sipmsg *msg, struct transaction *trans)
3250 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3251 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3252 struct sip_session *session;
3253 struct sip_dialog *dialog;
3254 char *cseq;
3255 char *key;
3256 struct queued_message *message;
3257 struct sipmsg *request_msg = trans->msg;
3259 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
3260 gchar *referred_by;
3262 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
3263 if (!session) {
3264 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
3265 g_free(with);
3266 return FALSE;
3269 dialog = sipe_dialog_find(session, with);
3270 if (!dialog) {
3271 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
3272 g_free(with);
3273 return FALSE;
3276 sipe_dialog_parse(dialog, msg, TRUE);
3278 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3279 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
3280 g_free(cseq);
3281 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3283 if (msg->response != 200) {
3284 PurpleBuddy *pbuddy;
3285 const char *alias = with;
3286 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3287 int warning = -1;
3289 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
3291 if (warn_hdr) {
3292 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3293 if (parts[0]) {
3294 warning = atoi(parts[0]);
3296 g_strfreev(parts);
3299 /* cancel file transfer as rejected by server */
3300 if (msg->response == 606 && /* Not acceptable all. */
3301 warning == 309 && /* Message contents not allowed by policy */
3302 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3304 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
3305 sipe_ft_incoming_cancel(dialog, parsed_body);
3306 sipe_utils_nameval_free(parsed_body);
3309 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3310 alias = purple_buddy_get_alias(pbuddy);
3313 if (message) {
3314 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, message->body);
3315 } else {
3316 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
3317 sipe_present_err(sipe_private, session, tmp_msg);
3318 g_free(tmp_msg);
3321 sipe_dialog_remove(session, with);
3323 g_free(key);
3324 g_free(with);
3325 return FALSE;
3328 dialog->cseq = 0;
3329 sip_transport_ack(sipe_private, dialog);
3330 dialog->outgoing_invite = NULL;
3331 dialog->is_established = TRUE;
3333 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3334 if (referred_by) {
3335 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
3336 g_free(referred_by);
3339 /* add user to chat if it is a multiparty session */
3340 if (session->is_multiparty) {
3341 sipe_backend_chat_add(session->backend_session,
3342 with,
3343 TRUE);
3346 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3347 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
3348 sipe_session_dequeue_message(session);
3351 sipe_im_process_queue(sipe_private, session);
3353 g_hash_table_remove(session->unconfirmed_messages, key);
3354 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
3355 key, g_hash_table_size(session->unconfirmed_messages));
3357 g_free(key);
3358 g_free(with);
3359 return TRUE;
3363 void
3364 sipe_invite(struct sipe_core_private *sipe_private,
3365 struct sip_session *session,
3366 const gchar *who,
3367 const gchar *msg_body,
3368 const gchar *msg_content_type,
3369 const gchar *referred_by,
3370 const gboolean is_triggered)
3372 gchar *hdr;
3373 gchar *to;
3374 gchar *contact;
3375 gchar *body;
3376 gchar *self;
3377 char *ms_text_format = NULL;
3378 gchar *roster_manager;
3379 gchar *end_points;
3380 gchar *referred_by_str;
3381 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3383 if (dialog && dialog->is_established) {
3384 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
3385 return;
3388 if (!dialog) {
3389 dialog = sipe_dialog_add(session);
3390 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3391 dialog->with = g_strdup(who);
3394 if (!(dialog->ourtag)) {
3395 dialog->ourtag = gentag();
3398 to = sip_uri(who);
3400 if (msg_body) {
3401 char *msgtext = NULL;
3402 char *base64_msg;
3403 const gchar *msgr = "";
3404 char *key;
3405 struct queued_message *message;
3406 gchar *tmp = NULL;
3408 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
3409 char *msgformat;
3410 gchar *msgr_value;
3412 sipe_parse_html(msg_body, &msgformat, &msgtext);
3413 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
3415 msgr_value = sipmsg_get_msgr_string(msgformat);
3416 g_free(msgformat);
3417 if (msgr_value) {
3418 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
3419 g_free(msgr_value);
3421 } else {
3422 msgtext = g_strdup(msg_body);
3425 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
3426 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
3427 msg_content_type ? msg_content_type : "text/plain",
3428 msgr,
3429 base64_msg);
3430 g_free(msgtext);
3431 g_free(tmp);
3432 g_free(base64_msg);
3434 message = g_new0(struct queued_message,1);
3435 message->body = g_strdup(msg_body);
3436 if (msg_content_type != NULL)
3437 message->content_type = g_strdup(msg_content_type);
3439 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3440 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3441 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
3442 key, g_hash_table_size(session->unconfirmed_messages));
3443 g_free(key);
3446 contact = get_contact(sipe_private);
3447 end_points = get_end_points(sipe_private, session);
3448 self = sip_uri_self(sipe_private);
3449 roster_manager = g_strdup_printf(
3450 "Roster-Manager: %s\r\n"
3451 "EndPoints: %s\r\n",
3452 self,
3453 end_points);
3454 referred_by_str = referred_by ?
3455 g_strdup_printf(
3456 "Referred-By: %s\r\n",
3457 referred_by)
3458 : g_strdup("");
3459 hdr = g_strdup_printf(
3460 "Supported: ms-sender\r\n"
3461 "%s"
3462 "%s"
3463 "%s"
3464 "%s"
3465 "Contact: %s\r\n%s"
3466 "Content-Type: application/sdp\r\n",
3467 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
3468 referred_by_str,
3469 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3470 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3471 contact,
3472 ms_text_format ? ms_text_format : "");
3473 g_free(ms_text_format);
3474 g_free(self);
3476 body = g_strdup_printf(
3477 "v=0\r\n"
3478 "o=- 0 0 IN IP4 %s\r\n"
3479 "s=session\r\n"
3480 "c=IN IP4 %s\r\n"
3481 "t=0 0\r\n"
3482 "m=%s %d sip null\r\n"
3483 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
3484 sipe_backend_network_ip_address(),
3485 sipe_backend_network_ip_address(),
3486 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
3487 sip_transport_port(sipe_private));
3489 dialog->outgoing_invite = sip_transport_request(sipe_private,
3490 "INVITE",
3493 hdr,
3494 body,
3495 dialog,
3496 process_invite_response);
3498 g_free(to);
3499 g_free(roster_manager);
3500 g_free(end_points);
3501 g_free(referred_by_str);
3502 g_free(body);
3503 g_free(hdr);
3504 g_free(contact);
3507 void
3508 sipe_convo_closed(PurpleConnection * gc, const char *who)
3510 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3512 SIPE_DEBUG_INFO("conversation with %s closed", who);
3513 sipe_session_close(sipe_private,
3514 sipe_session_find_im(sipe_private, who));
3517 void
3518 sipe_chat_leave (PurpleConnection *gc, int id)
3520 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3521 struct sip_session *session = sipe_session_find_chat_by_id(sipe_private,
3522 id);
3524 sipe_session_close(sipe_private, session);
3527 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3528 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3530 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3531 struct sip_session *session;
3532 struct sip_dialog *dialog;
3533 gchar *uri = sip_uri(who);
3535 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
3537 session = sipe_session_find_or_add_im(sipe_private, uri);
3538 dialog = sipe_dialog_find(session, uri);
3540 // Queue the message
3541 sipe_session_enqueue_message(session, what, NULL);
3543 if (dialog && !dialog->outgoing_invite) {
3544 sipe_im_process_queue(sipe_private, session);
3545 } else if (!dialog || !dialog->outgoing_invite) {
3546 // Need to send the INVITE to get the outgoing dialog setup
3547 sipe_invite(sipe_private, session, uri, what, NULL, NULL, FALSE);
3550 g_free(uri);
3551 return 1;
3554 int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
3555 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3557 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3558 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3559 struct sip_session *session;
3561 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
3563 session = sipe_session_find_chat_by_id(sipe_private, id);
3565 // Queue the message
3566 if (session && session->dialogs) {
3567 sipe_session_enqueue_message(session,what,NULL);
3568 sipe_im_process_queue(sipe_private, session);
3569 } else if (sip) {
3570 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
3571 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
3573 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
3574 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
3576 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
3577 struct sip_session *session = sipe_session_add_chat(sipe_private);
3579 session->is_multiparty = FALSE;
3580 session->focus_uri = g_strdup(proto_chat_id);
3581 sipe_session_enqueue_message(session, what, NULL);
3582 sipe_invite_conf_focus(sipe_private, session);
3586 return 1;
3590 * Returns 2005-style activity and Availability.
3592 * @param status Sipe statis id.
3594 static void
3595 sipe_get_act_avail_by_status_2005(const char *status,
3596 int *activity,
3597 int *availability)
3599 int avail = 300; /* online */
3600 int act = 400; /* Available */
3602 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
3603 act = 100;
3604 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
3605 // act = 150;
3606 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
3607 act = 300;
3608 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
3609 act = 400;
3610 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
3611 // act = 500;
3612 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
3613 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
3614 act = 600;
3615 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
3616 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
3617 avail = 0; /* offline */
3618 act = 100;
3619 } else {
3620 act = 400; /* Available */
3623 if (activity) *activity = act;
3624 if (availability) *availability = avail;
3628 * [MS-SIP] 2.2.1
3630 * @param activity 2005 aggregated activity. Ex.: 600
3631 * @param availablity 2005 aggregated availablity. Ex.: 300
3633 static const char *
3634 sipe_get_status_by_act_avail_2005(const int activity,
3635 const int availablity,
3636 char **activity_desc)
3638 const char *status_id = NULL;
3639 const char *act = NULL;
3641 if (activity < 150) {
3642 status_id = SIPE_STATUS_ID_AWAY;
3643 } else if (activity < 200) {
3644 //status_id = SIPE_STATUS_ID_LUNCH;
3645 status_id = SIPE_STATUS_ID_AWAY;
3646 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
3647 } else if (activity < 300) {
3648 //status_id = SIPE_STATUS_ID_IDLE;
3649 status_id = SIPE_STATUS_ID_AWAY;
3650 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3651 } else if (activity < 400) {
3652 status_id = SIPE_STATUS_ID_BRB;
3653 } else if (activity < 500) {
3654 status_id = SIPE_STATUS_ID_AVAILABLE;
3655 } else if (activity < 600) {
3656 //status_id = SIPE_STATUS_ID_ON_PHONE;
3657 status_id = SIPE_STATUS_ID_BUSY;
3658 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
3659 } else if (activity < 700) {
3660 status_id = SIPE_STATUS_ID_BUSY;
3661 } else if (activity < 800) {
3662 status_id = SIPE_STATUS_ID_AWAY;
3663 } else {
3664 status_id = SIPE_STATUS_ID_AVAILABLE;
3667 if (availablity < 100)
3668 status_id = SIPE_STATUS_ID_OFFLINE;
3670 if (activity_desc && act) {
3671 g_free(*activity_desc);
3672 *activity_desc = g_strdup(act);
3675 return status_id;
3679 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
3681 static const char*
3682 sipe_get_status_by_availability(int avail,
3683 char** activity_desc)
3685 const char *status;
3686 const char *act = NULL;
3688 if (avail < 3000) {
3689 status = SIPE_STATUS_ID_OFFLINE;
3690 } else if (avail < 4500) {
3691 status = SIPE_STATUS_ID_AVAILABLE;
3692 } else if (avail < 6000) {
3693 //status = SIPE_STATUS_ID_IDLE;
3694 status = SIPE_STATUS_ID_AVAILABLE;
3695 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3696 } else if (avail < 7500) {
3697 status = SIPE_STATUS_ID_BUSY;
3698 } else if (avail < 9000) {
3699 //status = SIPE_STATUS_ID_BUSYIDLE;
3700 status = SIPE_STATUS_ID_BUSY;
3701 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
3702 } else if (avail < 12000) {
3703 status = SIPE_STATUS_ID_DND;
3704 } else if (avail < 15000) {
3705 status = SIPE_STATUS_ID_BRB;
3706 } else if (avail < 18000) {
3707 status = SIPE_STATUS_ID_AWAY;
3708 } else {
3709 status = SIPE_STATUS_ID_OFFLINE;
3712 if (activity_desc && act) {
3713 g_free(*activity_desc);
3714 *activity_desc = g_strdup(act);
3717 return status;
3721 * Returns 2007-style availability value
3723 * @param sipe_status_id (in)
3724 * @param activity_token (out) Must be g_free()'d after use if consumed.
3726 static int
3727 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
3729 int availability;
3730 sipe_activity activity = SIPE_ACTIVITY_UNSET;
3732 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
3733 availability = 15500;
3734 if (!activity_token || !(*activity_token)) {
3735 activity = SIPE_ACTIVITY_AWAY;
3737 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
3738 availability = 12500;
3739 activity = SIPE_ACTIVITY_BRB;
3740 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
3741 availability = 9500;
3742 activity = SIPE_ACTIVITY_DND;
3743 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
3744 availability = 6500;
3745 if (!activity_token || !(*activity_token)) {
3746 activity = SIPE_ACTIVITY_BUSY;
3748 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
3749 availability = 3500;
3750 activity = SIPE_ACTIVITY_ONLINE;
3751 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
3752 availability = 0;
3753 } else {
3754 // Offline or invisible
3755 availability = 18500;
3756 activity = SIPE_ACTIVITY_OFFLINE;
3759 if (activity_token) {
3760 *activity_token = g_strdup(sipe_activity_map[activity].token);
3762 return availability;
3765 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
3766 const gchar *data, unsigned len)
3768 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3769 const char *uri;
3770 sipe_xml *xn_categories;
3771 const sipe_xml *xn_category;
3772 const char *status = NULL;
3773 gboolean do_update_status = FALSE;
3774 gboolean has_note_cleaned = FALSE;
3775 gboolean has_free_busy_cleaned = FALSE;
3777 xn_categories = sipe_xml_parse(data, len);
3778 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
3780 for (xn_category = sipe_xml_child(xn_categories, "category");
3781 xn_category ;
3782 xn_category = sipe_xml_twin(xn_category) )
3784 const sipe_xml *xn_node;
3785 const char *tmp;
3786 const char *attrVar = sipe_xml_attribute(xn_category, "name");
3787 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
3788 sipe_utils_str_to_time(tmp) : 0;
3790 /* contactCard */
3791 if (sipe_strequal(attrVar, "contactCard"))
3793 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
3795 if (card) {
3796 const sipe_xml *node;
3797 /* identity - Display Name and email */
3798 node = sipe_xml_child(card, "identity");
3799 if (node) {
3800 char* display_name = sipe_xml_data(
3801 sipe_xml_child(node, "name/displayName"));
3802 char* email = sipe_xml_data(
3803 sipe_xml_child(node, "email"));
3805 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
3806 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
3808 g_free(display_name);
3809 g_free(email);
3811 /* company */
3812 node = sipe_xml_child(card, "company");
3813 if (node) {
3814 char* company = sipe_xml_data(node);
3815 sipe_update_user_info(sipe_private, uri, COMPANY_PROP, company);
3816 g_free(company);
3818 /* department */
3819 node = sipe_xml_child(card, "department");
3820 if (node) {
3821 char* department = sipe_xml_data(node);
3822 sipe_update_user_info(sipe_private, uri, DEPARTMENT_PROP, department);
3823 g_free(department);
3825 /* title */
3826 node = sipe_xml_child(card, "title");
3827 if (node) {
3828 char* title = sipe_xml_data(node);
3829 sipe_update_user_info(sipe_private, uri, TITLE_PROP, title);
3830 g_free(title);
3832 /* office */
3833 node = sipe_xml_child(card, "office");
3834 if (node) {
3835 char* office = sipe_xml_data(node);
3836 sipe_update_user_info(sipe_private, uri, OFFICE_PROP, office);
3837 g_free(office);
3839 /* site (url) */
3840 node = sipe_xml_child(card, "url");
3841 if (node) {
3842 char* site = sipe_xml_data(node);
3843 sipe_update_user_info(sipe_private, uri, SITE_PROP, site);
3844 g_free(site);
3846 /* phone */
3847 for (node = sipe_xml_child(card, "phone");
3848 node;
3849 node = sipe_xml_twin(node))
3851 const char *phone_type = sipe_xml_attribute(node, "type");
3852 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3853 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3855 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3857 g_free(phone);
3858 g_free(phone_display_string);
3860 /* address */
3861 for (node = sipe_xml_child(card, "address");
3862 node;
3863 node = sipe_xml_twin(node))
3865 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3866 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3867 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3868 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3869 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3870 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3872 sipe_update_user_info(sipe_private, uri, ADDRESS_STREET_PROP, street);
3873 sipe_update_user_info(sipe_private, uri, ADDRESS_CITY_PROP, city);
3874 sipe_update_user_info(sipe_private, uri, ADDRESS_STATE_PROP, state);
3875 sipe_update_user_info(sipe_private, uri, ADDRESS_ZIPCODE_PROP, zipcode);
3876 sipe_update_user_info(sipe_private, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
3878 g_free(street);
3879 g_free(city);
3880 g_free(state);
3881 g_free(zipcode);
3882 g_free(country_code);
3884 break;
3889 /* note */
3890 else if (sipe_strequal(attrVar, "note"))
3892 if (uri) {
3893 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3895 if (!has_note_cleaned) {
3896 has_note_cleaned = TRUE;
3898 g_free(sbuddy->note);
3899 sbuddy->note = NULL;
3900 sbuddy->is_oof_note = FALSE;
3901 sbuddy->note_since = publish_time;
3903 do_update_status = TRUE;
3905 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3906 /* clean up in case no 'note' element is supplied
3907 * which indicate note removal in client
3909 g_free(sbuddy->note);
3910 sbuddy->note = NULL;
3911 sbuddy->is_oof_note = FALSE;
3912 sbuddy->note_since = publish_time;
3914 xn_node = sipe_xml_child(xn_category, "note/body");
3915 if (xn_node) {
3916 char *tmp;
3917 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3918 g_free(tmp);
3919 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3920 sbuddy->note_since = publish_time;
3922 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3923 uri, sbuddy->note ? sbuddy->note : "");
3925 /* to trigger UI refresh in case no status info is supplied in this update */
3926 do_update_status = TRUE;
3930 /* state */
3931 else if(sipe_strequal(attrVar, "state"))
3933 char *tmp;
3934 int availability;
3935 const sipe_xml *xn_availability;
3936 const sipe_xml *xn_activity;
3937 const sipe_xml *xn_meeting_subject;
3938 const sipe_xml *xn_meeting_location;
3939 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3941 xn_node = sipe_xml_child(xn_category, "state");
3942 if (!xn_node) continue;
3943 xn_availability = sipe_xml_child(xn_node, "availability");
3944 if (!xn_availability) continue;
3945 xn_activity = sipe_xml_child(xn_node, "activity");
3946 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3947 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3949 tmp = sipe_xml_data(xn_availability);
3950 availability = atoi(tmp);
3951 g_free(tmp);
3953 /* activity, meeting_subject, meeting_location */
3954 if (sbuddy) {
3955 char *tmp = NULL;
3957 /* activity */
3958 g_free(sbuddy->activity);
3959 sbuddy->activity = NULL;
3960 if (xn_activity) {
3961 const char *token = sipe_xml_attribute(xn_activity, "token");
3962 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3964 /* from token */
3965 if (!is_empty(token)) {
3966 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3968 /* from custom element */
3969 if (xn_custom) {
3970 char *custom = sipe_xml_data(xn_custom);
3972 if (!is_empty(custom)) {
3973 sbuddy->activity = custom;
3974 custom = NULL;
3976 g_free(custom);
3979 /* meeting_subject */
3980 g_free(sbuddy->meeting_subject);
3981 sbuddy->meeting_subject = NULL;
3982 if (xn_meeting_subject) {
3983 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3985 if (!is_empty(meeting_subject)) {
3986 sbuddy->meeting_subject = meeting_subject;
3987 meeting_subject = NULL;
3989 g_free(meeting_subject);
3991 /* meeting_location */
3992 g_free(sbuddy->meeting_location);
3993 sbuddy->meeting_location = NULL;
3994 if (xn_meeting_location) {
3995 char *meeting_location = sipe_xml_data(xn_meeting_location);
3997 if (!is_empty(meeting_location)) {
3998 sbuddy->meeting_location = meeting_location;
3999 meeting_location = NULL;
4001 g_free(meeting_location);
4004 status = sipe_get_status_by_availability(availability, &tmp);
4005 if (sbuddy->activity && tmp) {
4006 char *tmp2 = sbuddy->activity;
4008 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
4009 g_free(tmp);
4010 g_free(tmp2);
4011 } else if (tmp) {
4012 sbuddy->activity = tmp;
4016 do_update_status = TRUE;
4018 /* calendarData */
4019 else if(sipe_strequal(attrVar, "calendarData"))
4021 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
4022 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
4023 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
4025 if (sbuddy && xn_free_busy) {
4026 if (!has_free_busy_cleaned) {
4027 has_free_busy_cleaned = TRUE;
4029 g_free(sbuddy->cal_start_time);
4030 sbuddy->cal_start_time = NULL;
4032 g_free(sbuddy->cal_free_busy_base64);
4033 sbuddy->cal_free_busy_base64 = NULL;
4035 g_free(sbuddy->cal_free_busy);
4036 sbuddy->cal_free_busy = NULL;
4038 sbuddy->cal_free_busy_published = publish_time;
4041 if (publish_time >= sbuddy->cal_free_busy_published) {
4042 g_free(sbuddy->cal_start_time);
4043 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
4045 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
4046 15 : 0;
4048 g_free(sbuddy->cal_free_busy_base64);
4049 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
4051 g_free(sbuddy->cal_free_busy);
4052 sbuddy->cal_free_busy = NULL;
4054 sbuddy->cal_free_busy_published = publish_time;
4056 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: startTime=%s granularity=%d cal_free_busy_base64=\n%s", sbuddy->cal_start_time, sbuddy->cal_granularity, sbuddy->cal_free_busy_base64);
4060 if (sbuddy && xn_working_hours) {
4061 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
4066 if (do_update_status) {
4067 if (!status) { /* no status category in this update, using contact's current status */
4068 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
4069 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
4070 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
4071 status = purple_status_get_id(pstatus);
4074 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
4075 sipe_got_user_status(sipe_private, uri, status);
4078 sipe_xml_free(xn_categories);
4081 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
4082 GSList *server,
4083 struct sipe_core_private *sipe_private)
4085 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4086 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
4087 payload->host = g_strdup(host);
4088 payload->buddies = server;
4089 sipe_subscribe_presence_batched_routed(sipe_private,
4090 payload);
4091 sipe_subscribe_presence_batched_routed_free(payload);
4094 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
4095 const gchar *data, unsigned len)
4097 sipe_xml *xn_list;
4098 const sipe_xml *xn_resource;
4099 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4100 g_free, NULL);
4101 GSList *server;
4102 gchar *host;
4104 xn_list = sipe_xml_parse(data, len);
4106 for (xn_resource = sipe_xml_child(xn_list, "resource");
4107 xn_resource;
4108 xn_resource = sipe_xml_twin(xn_resource) )
4110 const char *uri, *state;
4111 const sipe_xml *xn_instance;
4113 xn_instance = sipe_xml_child(xn_resource, "instance");
4114 if (!xn_instance) continue;
4116 uri = sipe_xml_attribute(xn_resource, "uri");
4117 state = sipe_xml_attribute(xn_instance, "state");
4118 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
4120 if (strstr(state, "resubscribe")) {
4121 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
4123 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4124 gchar *user = g_strdup(uri);
4125 host = g_strdup(poolFqdn);
4126 server = g_hash_table_lookup(servers, host);
4127 server = g_slist_append(server, user);
4128 g_hash_table_insert(servers, host, server);
4129 } else {
4130 sipe_subscribe_presence_single(sipe_private,
4131 (void *) uri);
4136 /* Send out any deferred poolFqdn subscriptions */
4137 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
4138 g_hash_table_destroy(servers);
4140 sipe_xml_free(xn_list);
4143 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
4144 const gchar *data, unsigned len)
4146 gchar *uri;
4147 gchar *getbasic;
4148 gchar *activity = NULL;
4149 sipe_xml *pidf;
4150 const sipe_xml *basicstatus = NULL, *tuple, *status;
4151 gboolean isonline = FALSE;
4152 const sipe_xml *display_name_node;
4154 pidf = sipe_xml_parse(data, len);
4155 if (!pidf) {
4156 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
4157 return;
4160 if ((tuple = sipe_xml_child(pidf, "tuple")))
4162 if ((status = sipe_xml_child(tuple, "status"))) {
4163 basicstatus = sipe_xml_child(status, "basic");
4167 if (!basicstatus) {
4168 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
4169 sipe_xml_free(pidf);
4170 return;
4173 getbasic = sipe_xml_data(basicstatus);
4174 if (!getbasic) {
4175 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
4176 sipe_xml_free(pidf);
4177 return;
4180 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
4181 if (strstr(getbasic, "open")) {
4182 isonline = TRUE;
4184 g_free(getbasic);
4186 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
4188 display_name_node = sipe_xml_child(pidf, "display-name");
4189 if (display_name_node) {
4190 char * display_name = sipe_xml_data(display_name_node);
4192 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4193 g_free(display_name);
4196 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
4197 if ((status = sipe_xml_child(tuple, "status"))) {
4198 if ((basicstatus = sipe_xml_child(status, "activities"))) {
4199 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
4200 activity = sipe_xml_data(basicstatus);
4201 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
4207 if (isonline) {
4208 const gchar * status_id = NULL;
4209 if (activity) {
4210 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
4211 status_id = SIPE_STATUS_ID_BUSY;
4212 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
4213 status_id = SIPE_STATUS_ID_AWAY;
4217 if (!status_id) {
4218 status_id = SIPE_STATUS_ID_AVAILABLE;
4221 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
4222 sipe_got_user_status(sipe_private, uri, status_id);
4223 } else {
4224 sipe_got_user_status(sipe_private, uri, SIPE_STATUS_ID_OFFLINE);
4227 g_free(activity);
4228 g_free(uri);
4229 sipe_xml_free(pidf);
4232 /** 2005 */
4233 static void
4234 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
4235 const sipe_xml *xn_userinfo)
4237 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4238 const sipe_xml *xn_states;
4240 g_free(sip->user_states);
4241 sip->user_states = NULL;
4242 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
4243 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
4245 /* this is a hack-around to remove added newline after inner element,
4246 * state in this case, where it shouldn't be.
4247 * After several use of sipe_xml_stringify, amount of added newlines
4248 * grows significantly.
4250 if (orig) {
4251 gchar c, *stripped = orig;
4252 while ((c = *orig++)) {
4253 if ((c != '\n') /* && (c != '\r') */) {
4254 *stripped++ = c;
4257 *stripped = '\0';
4261 /* Publish initial state if not yet.
4262 * Assuming this happens on initial responce to self subscription
4263 * so we've already updated our UserInfo.
4265 if (!sip->initial_state_published) {
4266 send_presence_soap(sipe_private, FALSE);
4267 /* dalayed run */
4268 sipe_schedule_seconds(sipe_private,
4269 "<+update-calendar>",
4270 NULL,
4271 UPDATE_CALENDAR_DELAY,
4272 (sipe_schedule_action) sipe_core_update_calendar,
4273 NULL);
4277 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
4278 const gchar *data, unsigned len)
4280 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4281 char *activity = NULL;
4282 const char *epid;
4283 const char *status_id = NULL;
4284 const char *name;
4285 char *uri;
4286 char *self_uri = sip_uri_self(sipe_private);
4287 int avl;
4288 int act;
4289 const char *device_name = NULL;
4290 const char *cal_start_time = NULL;
4291 const char *cal_granularity = NULL;
4292 char *cal_free_busy_base64 = NULL;
4293 struct sipe_buddy *sbuddy;
4294 const sipe_xml *node;
4295 sipe_xml *xn_presentity;
4296 const sipe_xml *xn_availability;
4297 const sipe_xml *xn_activity;
4298 const sipe_xml *xn_display_name;
4299 const sipe_xml *xn_email;
4300 const sipe_xml *xn_phone_number;
4301 const sipe_xml *xn_userinfo;
4302 const sipe_xml *xn_note;
4303 const sipe_xml *xn_oof;
4304 const sipe_xml *xn_state;
4305 const sipe_xml *xn_contact;
4306 char *note;
4307 char *free_activity;
4308 int user_avail;
4309 const char *user_avail_nil;
4310 int res_avail;
4311 time_t user_avail_since = 0;
4312 time_t activity_since = 0;
4314 /* fix for Reuters environment on Linux */
4315 if (data && strstr(data, "encoding=\"utf-16\"")) {
4316 char *tmp_data;
4317 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
4318 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
4319 g_free(tmp_data);
4320 } else {
4321 xn_presentity = sipe_xml_parse(data, len);
4324 xn_availability = sipe_xml_child(xn_presentity, "availability");
4325 xn_activity = sipe_xml_child(xn_presentity, "activity");
4326 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
4327 xn_email = sipe_xml_child(xn_presentity, "email");
4328 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
4329 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
4330 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
4331 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
4332 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
4333 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
4334 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
4335 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
4336 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
4337 note = xn_note ? sipe_xml_data(xn_note) : NULL;
4339 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
4340 user_avail = 0;
4341 user_avail_since = 0;
4344 free_activity = NULL;
4346 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
4347 uri = sip_uri_from_name(name);
4348 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
4349 epid = sipe_xml_attribute(xn_availability, "epid");
4350 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
4352 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
4353 res_avail = sipe_get_availability_by_status(status_id, NULL);
4354 if (user_avail > res_avail) {
4355 res_avail = user_avail;
4356 status_id = sipe_get_status_by_availability(user_avail, NULL);
4359 if (xn_display_name) {
4360 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
4361 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
4362 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
4363 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
4364 char *tel_uri = sip_to_tel_uri(phone_number);
4366 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4367 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
4368 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
4369 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
4371 g_free(tel_uri);
4372 g_free(phone_label);
4373 g_free(phone_number);
4374 g_free(email);
4375 g_free(display_name);
4378 if (xn_contact) {
4379 /* tel */
4380 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
4382 /* Ex.: <tel type="work">tel:+3222220000</tel> */
4383 const char *phone_type = sipe_xml_attribute(node, "type");
4384 char* phone = sipe_xml_data(node);
4386 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
4388 g_free(phone);
4392 /* devicePresence */
4393 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
4394 const sipe_xml *xn_device_name;
4395 const sipe_xml *xn_calendar_info;
4396 const sipe_xml *xn_state;
4397 char *state;
4399 /* deviceName */
4400 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
4401 xn_device_name = sipe_xml_child(node, "deviceName");
4402 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
4405 /* calendarInfo */
4406 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
4407 if (xn_calendar_info) {
4408 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
4410 if (cal_start_time) {
4411 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
4412 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
4414 if (cal_start_time_t_tmp > cal_start_time_t) {
4415 cal_start_time = cal_start_time_tmp;
4416 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4417 g_free(cal_free_busy_base64);
4418 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4420 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time, cal_granularity, cal_free_busy_base64);
4422 } else {
4423 cal_start_time = cal_start_time_tmp;
4424 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4425 g_free(cal_free_busy_base64);
4426 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4428 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time, cal_granularity, cal_free_busy_base64);
4432 /* state */
4433 xn_state = sipe_xml_child(node, "states/state");
4434 if (xn_state) {
4435 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
4436 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
4438 state = sipe_xml_data(xn_state);
4439 if (dev_avail_since > user_avail_since &&
4440 dev_avail >= res_avail)
4442 res_avail = dev_avail;
4443 if (!is_empty(state))
4445 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
4446 g_free(activity);
4447 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
4448 } else if (sipe_strequal(state, "presenting")) {
4449 g_free(activity);
4450 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
4451 } else {
4452 activity = state;
4453 state = NULL;
4455 activity_since = dev_avail_since;
4457 status_id = sipe_get_status_by_availability(res_avail, &activity);
4459 g_free(state);
4463 /* oof */
4464 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
4465 g_free(activity);
4466 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
4467 activity_since = 0;
4470 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
4471 if (sbuddy)
4473 g_free(sbuddy->activity);
4474 sbuddy->activity = activity;
4475 activity = NULL;
4477 sbuddy->activity_since = activity_since;
4479 sbuddy->user_avail = user_avail;
4480 sbuddy->user_avail_since = user_avail_since;
4482 g_free(sbuddy->note);
4483 sbuddy->note = NULL;
4484 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
4486 sbuddy->is_oof_note = (xn_oof != NULL);
4488 g_free(sbuddy->device_name);
4489 sbuddy->device_name = NULL;
4490 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
4492 if (!is_empty(cal_free_busy_base64)) {
4493 g_free(sbuddy->cal_start_time);
4494 sbuddy->cal_start_time = g_strdup(cal_start_time);
4496 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
4498 g_free(sbuddy->cal_free_busy_base64);
4499 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
4500 cal_free_busy_base64 = NULL;
4502 g_free(sbuddy->cal_free_busy);
4503 sbuddy->cal_free_busy = NULL;
4506 sbuddy->last_non_cal_status_id = status_id;
4507 g_free(sbuddy->last_non_cal_activity);
4508 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
4510 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
4511 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
4513 sip->is_oof_note = sbuddy->is_oof_note;
4515 g_free(sip->note);
4516 sip->note = g_strdup(sbuddy->note);
4518 sip->note_since = time(NULL);
4521 g_free(sip->status);
4522 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
4525 g_free(cal_free_busy_base64);
4526 g_free(activity);
4528 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
4529 sipe_got_user_status(sipe_private, uri, status_id);
4531 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
4532 sipe_user_info_has_updated(sipe_private, xn_userinfo);
4535 g_free(note);
4536 sipe_xml_free(xn_presentity);
4537 g_free(uri);
4538 g_free(self_uri);
4541 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
4542 const GSList *fields,
4543 const gchar *body,
4544 gsize length)
4546 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
4548 if (strstr(type,"application/rlmi+xml")) {
4549 process_incoming_notify_rlmi_resub(user_data, body, length);
4550 } else if (strstr(type, "text/xml+msrtc.pidf")) {
4551 process_incoming_notify_msrtc(user_data, body, length);
4552 } else {
4553 process_incoming_notify_rlmi(user_data, body, length);
4557 static void sipe_process_presence(struct sipe_core_private *sipe_private,
4558 struct sipmsg *msg)
4560 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4562 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
4564 if (ctype &&
4565 (strstr(ctype, "application/rlmi+xml") ||
4566 strstr(ctype, "application/msrtc-event-categories+xml")))
4568 if (strstr(ctype, "multipart"))
4570 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
4572 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4574 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
4576 else if(strstr(ctype, "application/rlmi+xml"))
4578 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
4581 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4583 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
4585 else
4587 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
4591 static void sipe_presence_timeout_mime_cb(gpointer user_data,
4592 SIPE_UNUSED_PARAMETER const GSList *fields,
4593 const gchar *body,
4594 gsize length)
4596 GSList **buddies = user_data;
4597 sipe_xml *xml = sipe_xml_parse(body, length);
4599 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
4600 const gchar *uri = sipe_xml_attribute(xml, "uri");
4601 const sipe_xml *xn_category;
4604 * automaton: presence is never expected to change
4606 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
4608 for (xn_category = sipe_xml_child(xml, "category");
4609 xn_category;
4610 xn_category = sipe_xml_twin(xn_category)) {
4611 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
4612 "contactCard")) {
4613 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
4614 if (node) {
4615 char *boolean = sipe_xml_data(node);
4616 if (sipe_strequal(boolean, "true")) {
4617 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
4618 uri);
4619 uri = NULL;
4621 g_free(boolean);
4623 break;
4627 if (uri) {
4628 *buddies = g_slist_append(*buddies, sip_uri(uri));
4632 sipe_xml_free(xml);
4635 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
4636 struct sipmsg *msg, gchar *who,
4637 int timeout)
4639 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4640 gchar *action_name = sipe_utils_presence_key(who);
4642 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
4644 if (ctype &&
4645 strstr(ctype, "multipart") &&
4646 (strstr(ctype, "application/rlmi+xml") ||
4647 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4648 GSList *buddies = NULL;
4650 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
4652 if (buddies) {
4653 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4654 payload->host = g_strdup(who);
4655 payload->buddies = buddies;
4656 sipe_schedule_seconds(sipe_private,
4657 action_name,
4658 payload,
4659 timeout,
4660 sipe_subscribe_presence_batched_routed,
4661 sipe_subscribe_presence_batched_routed_free);
4662 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
4665 } else {
4666 sipe_schedule_seconds(sipe_private,
4667 action_name,
4668 g_strdup(who),
4669 timeout,
4670 sipe_subscribe_presence_single,
4671 g_free);
4672 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
4674 g_free(action_name);
4678 * Dispatcher for all incoming subscription information
4679 * whether it comes from NOTIFY, BENOTIFY requests or
4680 * piggy-backed to subscription's OK responce.
4682 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4683 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4685 void process_incoming_notify(struct sipe_core_private *sipe_private,
4686 struct sipmsg *msg,
4687 gboolean request, gboolean benotify)
4689 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4690 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4691 const gchar *event = sipmsg_find_header(msg, "Event");
4692 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4694 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
4696 /* implicit subscriptions */
4697 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4698 sipe_process_imdn(sipe_private, msg);
4701 if (event) {
4702 /* for one off subscriptions (send with Expire: 0) */
4703 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
4705 sipe_process_provisioning_v2(sipe_private, msg);
4707 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
4709 sipe_process_provisioning(sipe_private, msg);
4711 else if (sipe_strcase_equal(event, "presence"))
4713 sipe_process_presence(sipe_private, msg);
4715 else if (sipe_strcase_equal(event, "registration-notify"))
4717 sipe_process_registration_notify(sipe_private, msg);
4720 if (!subscription_state || strstr(subscription_state, "active"))
4722 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
4724 sipe_process_roaming_contacts(sipe_private, msg);
4726 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
4728 sipe_process_roaming_self(sipe_private, msg);
4730 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
4732 sipe_process_roaming_acl(sipe_private, msg);
4734 else if (sipe_strcase_equal(event, "presence.wpending"))
4736 sipe_process_presence_wpending(sipe_private, msg);
4738 else if (sipe_strcase_equal(event, "conference"))
4740 sipe_process_conference(sipe_private, msg);
4745 /* The server sends status 'terminated' */
4746 if (subscription_state && strstr(subscription_state, "terminated") ) {
4747 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4748 gchar *key = sipe_utils_subscription_key(event, who);
4750 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
4751 g_free(who);
4753 sipe_subscriptions_remove(sipe_private, key);
4754 g_free(key);
4757 if (!request && event) {
4758 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
4759 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4760 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
4762 if (timeout) {
4763 /* 2 min ahead of expiration */
4764 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
4766 if (sipe_strcase_equal(event, "presence.wpending") &&
4767 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4769 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4770 sipe_schedule_seconds(sipe_private,
4771 action_name,
4772 NULL,
4773 timeout,
4774 sipe_subscribe_presence_wpending,
4775 NULL);
4776 g_free(action_name);
4778 else if (sipe_strcase_equal(event, "presence") &&
4779 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4781 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
4782 gchar *action_name = sipe_utils_presence_key(who);
4784 if (sip->batched_support) {
4785 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
4787 else {
4788 sipe_schedule_seconds(sipe_private,
4789 action_name,
4790 g_strdup(who),
4791 timeout,
4792 sipe_subscribe_presence_single,
4793 g_free);
4794 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
4796 g_free(action_name);
4797 g_free(who);
4802 /* The client responses on received a NOTIFY message */
4803 if (request && !benotify)
4805 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
4810 * Whether user manually changed status or
4811 * it was changed automatically due to user
4812 * became inactive/active again
4814 static gboolean
4815 sipe_is_user_state(struct sipe_core_private *sipe_private)
4817 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4818 gboolean res;
4819 time_t now = time(NULL);
4821 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
4822 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
4824 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
4826 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
4827 return res;
4830 static void
4831 send_presence_soap0(struct sipe_core_private *sipe_private,
4832 gboolean do_publish_calendar,
4833 gboolean do_reset_status)
4835 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4836 struct sipe_calendar* cal = sip->cal;
4837 int availability = 0;
4838 int activity = 0;
4839 gchar *body;
4840 gchar *tmp;
4841 gchar *tmp2 = NULL;
4842 gchar *res_note = NULL;
4843 gchar *res_oof = NULL;
4844 const gchar *note_pub = NULL;
4845 gchar *states = NULL;
4846 gchar *calendar_data = NULL;
4847 gchar *epid = get_epid(sipe_private);
4848 time_t now = time(NULL);
4849 gchar *since_time_str = sipe_utils_time_to_str(now);
4850 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4851 const char *user_input;
4852 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4854 if (oof_note && sip->note) {
4855 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4856 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4859 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4861 if (!sip->initial_state_published ||
4862 do_reset_status)
4864 g_free(sip->status);
4865 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4868 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4870 /* Note */
4871 if (pub_oof) {
4872 note_pub = oof_note;
4873 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4874 cal->published = TRUE;
4875 } else if (sip->note) {
4876 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4877 g_free(sip->note);
4878 sip->note = NULL;
4879 sip->is_oof_note = FALSE;
4880 sip->note_since = 0;
4881 } else {
4882 note_pub = sip->note;
4883 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4887 if (note_pub)
4889 /* to protocol internal plain text format */
4890 tmp = sipe_backend_markup_strip_html(note_pub);
4891 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4892 g_free(tmp);
4895 /* User State */
4896 if (!do_reset_status) {
4897 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4899 gchar *activity_token = NULL;
4900 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4902 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4903 avail_2007,
4904 since_time_str,
4905 epid,
4906 activity_token);
4907 g_free(activity_token);
4909 else /* preserve existing publication */
4911 if (sip->user_states) {
4912 states = g_strdup(sip->user_states);
4915 } else {
4916 /* do nothing - then User state will be erased */
4918 sip->initial_state_published = TRUE;
4920 /* CalendarInfo */
4921 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4923 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4924 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4925 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4926 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4927 fb_start_str,
4928 free_busy_base64);
4929 g_free(fb_start_str);
4930 g_free(free_busy_base64);
4933 user_input = (sipe_is_user_state(sipe_private) ||
4934 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4935 "active" : "idle";
4937 /* forming resulting XML */
4938 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4939 sipe_private->username,
4940 availability,
4941 activity,
4942 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4943 res_note ? res_note : "",
4944 res_oof ? res_oof : "",
4945 states ? states : "",
4946 calendar_data ? calendar_data : "",
4947 epid,
4948 since_time_str,
4949 since_time_str,
4950 user_input);
4951 g_free(tmp);
4952 g_free(tmp2);
4953 g_free(res_note);
4954 g_free(states);
4955 g_free(calendar_data);
4957 send_soap_request(sipe_private, body);
4959 g_free(body);
4960 g_free(since_time_str);
4961 g_free(epid);
4964 void
4965 send_presence_soap(struct sipe_core_private *sipe_private,
4966 gboolean do_publish_calendar)
4968 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4972 static gboolean
4973 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4974 struct sipmsg *msg,
4975 struct transaction *trans)
4977 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4979 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4980 sipe_xml *xml;
4981 const sipe_xml *node;
4982 gchar *fault_code;
4983 GHashTable *faults;
4984 int index_our;
4985 gboolean has_device_publication = FALSE;
4987 xml = sipe_xml_parse(msg->body, msg->bodylen);
4989 /* test if version mismatch fault */
4990 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
4991 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
4992 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
4993 g_free(fault_code);
4994 sipe_xml_free(xml);
4995 return TRUE;
4997 g_free(fault_code);
4999 /* accumulating information about faulty versions */
5000 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
5001 for (node = sipe_xml_child(xml, "details/operation");
5002 node;
5003 node = sipe_xml_twin(node))
5005 const gchar *index = sipe_xml_attribute(node, "index");
5006 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
5008 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
5009 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
5011 sipe_xml_free(xml);
5013 /* here we are parsing own request to figure out what publication
5014 * referensed here only by index went wrong
5016 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
5018 /* publication */
5019 for (node = sipe_xml_child(xml, "publications/publication"),
5020 index_our = 1; /* starts with 1 - our first publication */
5021 node;
5022 node = sipe_xml_twin(node), index_our++)
5024 gchar *idx = g_strdup_printf("%d", index_our);
5025 const gchar *curVersion = g_hash_table_lookup(faults, idx);
5026 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
5027 g_free(idx);
5029 if (sipe_strequal("device", categoryName)) {
5030 has_device_publication = TRUE;
5033 if (curVersion) { /* fault exist on this index */
5034 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5035 const gchar *container = sipe_xml_attribute(node, "container");
5036 const gchar *instance = sipe_xml_attribute(node, "instance");
5037 /* key is <category><instance><container> */
5038 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
5039 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
5041 if (category) {
5042 struct sipe_publication *publication =
5043 g_hash_table_lookup(category, key);
5045 SIPE_DEBUG_INFO("key is %s", key);
5047 if (publication) {
5048 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
5049 key, curVersion, publication->version);
5050 /* updating publication's version to the correct one */
5051 publication->version = atoi(curVersion);
5053 } else {
5054 /* We somehow lost this category from our publications... */
5055 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
5056 publication->category = g_strdup(categoryName);
5057 publication->instance = atoi(instance);
5058 publication->container = atoi(container);
5059 publication->version = atoi(curVersion);
5060 category = g_hash_table_new_full(g_str_hash, g_str_equal,
5061 g_free, (GDestroyNotify)free_publication);
5062 g_hash_table_insert(category, g_strdup(key), publication);
5063 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
5064 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
5066 g_free(key);
5069 sipe_xml_free(xml);
5070 g_hash_table_destroy(faults);
5072 /* rebublishing with right versions */
5073 if (has_device_publication) {
5074 send_publish_category_initial(sipe_private);
5075 } else {
5076 send_presence_status(sipe_private, NULL);
5079 return TRUE;
5083 * Returns 'device' XML part for publication.
5084 * Must be g_free'd after use.
5086 static gchar *
5087 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
5089 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5090 gchar *uri;
5091 gchar *doc;
5092 gchar *epid = get_epid(sipe_private);
5093 gchar *uuid = generateUUIDfromEPID(epid);
5094 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
5095 /* key is <category><instance><container> */
5096 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
5097 struct sipe_publication *publication =
5098 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
5100 g_free(key);
5101 g_free(epid);
5103 uri = sip_uri_self(sipe_private);
5104 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
5105 device_instance,
5106 publication ? publication->version : 0,
5107 uuid,
5108 uri,
5109 "00:00:00+01:00", /* @TODO make timezone real*/
5110 g_get_host_name()
5113 g_free(uri);
5114 g_free(uuid);
5116 return doc;
5120 * A service method - use
5121 * - send_publish_get_category_state_machine and
5122 * - send_publish_get_category_state_user instead.
5123 * Must be g_free'd after use.
5125 static gchar *
5126 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
5127 gboolean is_user_state)
5129 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5130 int availability = sipe_get_availability_by_status(sip->status, NULL);
5131 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
5132 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
5133 /* key is <category><instance><container> */
5134 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5135 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5136 struct sipe_publication *publication_2 =
5137 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5138 struct sipe_publication *publication_3 =
5139 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5141 g_free(key_2);
5142 g_free(key_3);
5144 if (publication_2 && (publication_2->availability == availability))
5146 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
5147 return NULL; /* nothing to update */
5150 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
5151 instance,
5152 publication_2 ? publication_2->version : 0,
5153 availability,
5154 instance,
5155 publication_3 ? publication_3->version : 0,
5156 availability);
5160 * Only Busy and OOF calendar event are published.
5161 * Different instances are used for that.
5163 * Must be g_free'd after use.
5165 static gchar *
5166 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
5167 struct sipe_cal_event *event,
5168 const char *uri,
5169 int cal_satus)
5171 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5172 gchar *start_time_str;
5173 int availability = 0;
5174 gchar *res;
5175 gchar *tmp = NULL;
5176 guint instance = (cal_satus == SIPE_CAL_OOF) ?
5177 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
5178 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
5180 /* key is <category><instance><container> */
5181 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5182 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5183 struct sipe_publication *publication_2 =
5184 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5185 struct sipe_publication *publication_3 =
5186 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5188 g_free(key_2);
5189 g_free(key_3);
5191 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
5192 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5193 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
5194 return NULL;
5197 if (event &&
5198 publication_3 &&
5199 (publication_3->availability == availability) &&
5200 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
5202 g_free(tmp);
5203 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5204 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
5205 return NULL; /* nothing to update */
5207 g_free(tmp);
5209 if (event &&
5210 (event->cal_status == SIPE_CAL_BUSY ||
5211 event->cal_status == SIPE_CAL_OOF))
5213 gchar *availability_xml_str = NULL;
5214 gchar *activity_xml_str = NULL;
5216 if (event->cal_status == SIPE_CAL_BUSY) {
5217 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
5220 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
5221 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5222 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
5223 "minAvailability=\"6500\"",
5224 "maxAvailability=\"8999\"");
5225 } else if (event->cal_status == SIPE_CAL_OOF) {
5226 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5227 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
5228 "minAvailability=\"12000\"",
5229 "");
5231 start_time_str = sipe_utils_time_to_str(event->start_time);
5233 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
5234 instance,
5235 publication_2 ? publication_2->version : 0,
5236 uri,
5237 start_time_str,
5238 availability_xml_str ? availability_xml_str : "",
5239 activity_xml_str ? activity_xml_str : "",
5240 event->subject ? event->subject : "",
5241 event->location ? event->location : "",
5243 instance,
5244 publication_3 ? publication_3->version : 0,
5245 uri,
5246 start_time_str,
5247 availability_xml_str ? availability_xml_str : "",
5248 activity_xml_str ? activity_xml_str : "",
5249 event->subject ? event->subject : "",
5250 event->location ? event->location : ""
5252 g_free(start_time_str);
5253 g_free(availability_xml_str);
5254 g_free(activity_xml_str);
5257 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
5259 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
5260 instance,
5261 publication_2 ? publication_2->version : 0,
5263 instance,
5264 publication_3 ? publication_3->version : 0
5268 return res;
5272 * Returns 'machineState' XML part for publication.
5273 * Must be g_free'd after use.
5275 static gchar *
5276 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
5278 return sipe_publish_get_category_state(sipe_private, FALSE);
5282 * Returns 'userState' XML part for publication.
5283 * Must be g_free'd after use.
5285 static gchar *
5286 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
5288 return sipe_publish_get_category_state(sipe_private, TRUE);
5292 * Returns 'note' XML part for publication.
5293 * Must be g_free'd after use.
5295 * Protocol format for Note is plain text.
5297 * @param note a note in Sipe internal HTML format
5298 * @param note_type either personal or OOF
5300 static gchar *
5301 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
5302 const char *note, /* html */
5303 const char *note_type,
5304 time_t note_start,
5305 time_t note_end)
5307 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5308 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
5309 /* key is <category><instance><container> */
5310 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
5311 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
5312 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
5314 struct sipe_publication *publication_note_200 =
5315 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
5316 struct sipe_publication *publication_note_300 =
5317 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
5318 struct sipe_publication *publication_note_400 =
5319 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
5321 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
5322 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
5323 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
5324 char *res, *tmp1, *tmp2, *tmp3;
5325 char *start_time_attr;
5326 char *end_time_attr;
5328 g_free(tmp);
5329 tmp = NULL;
5330 g_free(key_note_200);
5331 g_free(key_note_300);
5332 g_free(key_note_400);
5334 /* we even need to republish empty note */
5335 if (sipe_strequal(n1, n2))
5337 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
5338 g_free(n1);
5339 return NULL; /* nothing to update */
5342 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
5343 g_free(tmp);
5344 tmp = NULL;
5345 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
5346 g_free(tmp);
5348 if (n1) {
5349 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5350 instance,
5351 200,
5352 publication_note_200 ? publication_note_200->version : 0,
5353 note_type,
5354 start_time_attr ? start_time_attr : "",
5355 end_time_attr ? end_time_attr : "",
5356 n1);
5358 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5359 instance,
5360 300,
5361 publication_note_300 ? publication_note_300->version : 0,
5362 note_type,
5363 start_time_attr ? start_time_attr : "",
5364 end_time_attr ? end_time_attr : "",
5365 n1);
5367 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5368 instance,
5369 400,
5370 publication_note_400 ? publication_note_400->version : 0,
5371 note_type,
5372 start_time_attr ? start_time_attr : "",
5373 end_time_attr ? end_time_attr : "",
5374 n1);
5375 } else {
5376 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5377 "note",
5378 instance,
5379 200,
5380 publication_note_200 ? publication_note_200->version : 0,
5381 "static");
5382 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5383 "note",
5384 instance,
5385 300,
5386 publication_note_200 ? publication_note_200->version : 0,
5387 "static");
5388 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5389 "note",
5390 instance,
5391 400,
5392 publication_note_200 ? publication_note_200->version : 0,
5393 "static");
5395 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
5397 g_free(start_time_attr);
5398 g_free(end_time_attr);
5399 g_free(tmp1);
5400 g_free(tmp2);
5401 g_free(tmp3);
5402 g_free(n1);
5404 return res;
5408 * Returns 'calendarData' XML part with WorkingHours for publication.
5409 * Must be g_free'd after use.
5411 static gchar *
5412 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
5414 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5415 struct sipe_calendar* cal = sip->cal;
5417 /* key is <category><instance><container> */
5418 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
5419 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
5420 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
5421 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
5422 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
5423 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
5425 struct sipe_publication *publication_cal_1 =
5426 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5427 struct sipe_publication *publication_cal_100 =
5428 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5429 struct sipe_publication *publication_cal_200 =
5430 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5431 struct sipe_publication *publication_cal_300 =
5432 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5433 struct sipe_publication *publication_cal_400 =
5434 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5435 struct sipe_publication *publication_cal_32000 =
5436 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5438 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
5439 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
5441 g_free(key_cal_1);
5442 g_free(key_cal_100);
5443 g_free(key_cal_200);
5444 g_free(key_cal_300);
5445 g_free(key_cal_400);
5446 g_free(key_cal_32000);
5448 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
5449 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
5450 return NULL;
5453 if (sipe_strequal(n1, n2))
5455 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
5456 return NULL; /* nothing to update */
5459 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
5460 /* 1 */
5461 publication_cal_1 ? publication_cal_1->version : 0,
5462 cal->email,
5463 cal->working_hours_xml_str,
5464 /* 100 - Public */
5465 publication_cal_100 ? publication_cal_100->version : 0,
5466 /* 200 - Company */
5467 publication_cal_200 ? publication_cal_200->version : 0,
5468 cal->email,
5469 cal->working_hours_xml_str,
5470 /* 300 - Team */
5471 publication_cal_300 ? publication_cal_300->version : 0,
5472 cal->email,
5473 cal->working_hours_xml_str,
5474 /* 400 - Personal */
5475 publication_cal_400 ? publication_cal_400->version : 0,
5476 cal->email,
5477 cal->working_hours_xml_str,
5478 /* 32000 - Blocked */
5479 publication_cal_32000 ? publication_cal_32000->version : 0
5484 * Returns 'calendarData' XML part with FreeBusy for publication.
5485 * Must be g_free'd after use.
5487 static gchar *
5488 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
5490 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5491 struct sipe_calendar* cal = sip->cal;
5492 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
5493 char *fb_start_str;
5494 char *free_busy_base64;
5495 const char *st;
5496 const char *fb;
5497 char *res;
5499 /* key is <category><instance><container> */
5500 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
5501 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
5502 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
5503 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
5504 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
5505 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
5507 struct sipe_publication *publication_cal_1 =
5508 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5509 struct sipe_publication *publication_cal_100 =
5510 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5511 struct sipe_publication *publication_cal_200 =
5512 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5513 struct sipe_publication *publication_cal_300 =
5514 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5515 struct sipe_publication *publication_cal_400 =
5516 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5517 struct sipe_publication *publication_cal_32000 =
5518 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5520 g_free(key_cal_1);
5521 g_free(key_cal_100);
5522 g_free(key_cal_200);
5523 g_free(key_cal_300);
5524 g_free(key_cal_400);
5525 g_free(key_cal_32000);
5527 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
5528 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
5529 return NULL;
5532 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
5533 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
5535 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
5536 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
5538 /* we will rebuplish the same data to refresh publication time,
5539 * so if data from multiple sources, most recent will be choosen
5541 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
5543 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
5544 // g_free(fb_start_str);
5545 // g_free(free_busy_base64);
5546 // return NULL; /* nothing to update */
5549 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
5550 /* 1 */
5551 cal_data_instance,
5552 publication_cal_1 ? publication_cal_1->version : 0,
5553 /* 100 - Public */
5554 cal_data_instance,
5555 publication_cal_100 ? publication_cal_100->version : 0,
5556 /* 200 - Company */
5557 cal_data_instance,
5558 publication_cal_200 ? publication_cal_200->version : 0,
5559 cal->email,
5560 fb_start_str,
5561 free_busy_base64,
5562 /* 300 - Team */
5563 cal_data_instance,
5564 publication_cal_300 ? publication_cal_300->version : 0,
5565 cal->email,
5566 fb_start_str,
5567 free_busy_base64,
5568 /* 400 - Personal */
5569 cal_data_instance,
5570 publication_cal_400 ? publication_cal_400->version : 0,
5571 cal->email,
5572 fb_start_str,
5573 free_busy_base64,
5574 /* 32000 - Blocked */
5575 cal_data_instance,
5576 publication_cal_32000 ? publication_cal_32000->version : 0
5579 g_free(fb_start_str);
5580 g_free(free_busy_base64);
5581 return res;
5584 static void send_presence_publish(struct sipe_core_private *sipe_private,
5585 const char *publications)
5587 gchar *uri;
5588 gchar *doc;
5589 gchar *tmp;
5590 gchar *hdr;
5592 uri = sip_uri_self(sipe_private);
5593 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
5594 uri,
5595 publications);
5597 tmp = get_contact(sipe_private);
5598 hdr = g_strdup_printf("Contact: %s\r\n"
5599 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
5601 sip_transport_service(sipe_private,
5602 uri,
5603 hdr,
5604 doc,
5605 process_send_presence_category_publish_response);
5607 g_free(tmp);
5608 g_free(hdr);
5609 g_free(uri);
5610 g_free(doc);
5613 static void
5614 send_publish_category_initial(struct sipe_core_private *sipe_private)
5616 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5617 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
5618 gchar *pub_machine;
5619 gchar *publications;
5621 g_free(sip->status);
5622 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
5624 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
5625 publications = g_strdup_printf("%s%s",
5626 pub_device,
5627 pub_machine ? pub_machine : "");
5628 g_free(pub_device);
5629 g_free(pub_machine);
5631 send_presence_publish(sipe_private, publications);
5632 g_free(publications);
5635 static void
5636 send_presence_category_publish(struct sipe_core_private *sipe_private)
5638 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5639 gchar *pub_state = sipe_is_user_state(sipe_private) ?
5640 sipe_publish_get_category_state_user(sipe_private) :
5641 sipe_publish_get_category_state_machine(sipe_private);
5642 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
5643 sip->note,
5644 sip->is_oof_note ? "OOF" : "personal",
5647 gchar *publications;
5649 if (!pub_state && !pub_note) {
5650 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
5651 return;
5654 publications = g_strdup_printf("%s%s",
5655 pub_state ? pub_state : "",
5656 pub_note ? pub_note : "");
5658 g_free(pub_state);
5659 g_free(pub_note);
5661 send_presence_publish(sipe_private, publications);
5662 g_free(publications);
5666 * Publishes self status
5667 * based on own calendar information.
5669 * For 2007+
5671 void
5672 publish_calendar_status_self(struct sipe_core_private *sipe_private,
5673 SIPE_UNUSED_PARAMETER void *unused)
5675 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5676 struct sipe_cal_event* event = NULL;
5677 gchar *pub_cal_working_hours = NULL;
5678 gchar *pub_cal_free_busy = NULL;
5679 gchar *pub_calendar = NULL;
5680 gchar *pub_calendar2 = NULL;
5681 gchar *pub_oof_note = NULL;
5682 const gchar *oof_note;
5683 time_t oof_start = 0;
5684 time_t oof_end = 0;
5686 if (!sip->cal) {
5687 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
5688 return;
5691 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
5692 if (sip->cal->cal_events) {
5693 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
5696 if (!event) {
5697 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
5698 } else {
5699 char *desc = sipe_cal_event_describe(event);
5700 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
5701 g_free(desc);
5704 /* Logic
5705 if OOF
5706 OOF publish, Busy clean
5707 ilse if Busy
5708 OOF clean, Busy publish
5709 else
5710 OOF clean, Busy clean
5712 if (event && event->cal_status == SIPE_CAL_OOF) {
5713 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
5714 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5715 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
5716 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5717 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
5718 } else {
5719 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5720 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5723 oof_note = sipe_ews_get_oof_note(sip->cal);
5724 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
5725 oof_start = sip->cal->oof_start;
5726 oof_end = sip->cal->oof_end;
5728 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
5730 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
5731 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
5733 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
5734 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
5735 } else {
5736 gchar *publications = g_strdup_printf("%s%s%s%s%s",
5737 pub_cal_working_hours ? pub_cal_working_hours : "",
5738 pub_cal_free_busy ? pub_cal_free_busy : "",
5739 pub_calendar ? pub_calendar : "",
5740 pub_calendar2 ? pub_calendar2 : "",
5741 pub_oof_note ? pub_oof_note : "");
5743 send_presence_publish(sipe_private, publications);
5744 g_free(publications);
5747 g_free(pub_cal_working_hours);
5748 g_free(pub_cal_free_busy);
5749 g_free(pub_calendar);
5750 g_free(pub_calendar2);
5751 g_free(pub_oof_note);
5753 /* repeat scheduling */
5754 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
5757 static void send_presence_status(struct sipe_core_private *sipe_private,
5758 SIPE_UNUSED_PARAMETER void *unused)
5760 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5761 PurpleStatus * status = purple_account_get_active_status(sip->account);
5763 if (!status) return;
5765 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
5766 purple_status_get_id(status) ? purple_status_get_id(status) : "",
5767 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
5769 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5770 send_presence_category_publish(sipe_private);
5771 } else {
5772 send_presence_soap(sipe_private, FALSE);
5776 static guint sipe_ht_hash_nick(const char *nick)
5778 char *lc = g_utf8_strdown(nick, -1);
5779 guint bucket = g_str_hash(lc);
5780 g_free(lc);
5782 return bucket;
5785 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5787 char *nick1_norm = NULL;
5788 char *nick2_norm = NULL;
5789 gboolean equal;
5791 if (nick1 == NULL && nick2 == NULL) return TRUE;
5792 if (nick1 == NULL || nick2 == NULL ||
5793 !g_utf8_validate(nick1, -1, NULL) ||
5794 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
5796 nick1_norm = g_utf8_casefold(nick1, -1);
5797 nick2_norm = g_utf8_casefold(nick2, -1);
5798 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
5799 g_free(nick2_norm);
5800 g_free(nick1_norm);
5802 return equal;
5805 /* temporary function */
5806 void sipe_purple_setup(struct sipe_core_public *sipe_public,
5807 PurpleConnection *gc)
5809 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5810 sip->gc = gc;
5811 sip->account = purple_connection_get_account(gc);
5814 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
5815 const gchar *login_domain,
5816 const gchar *login_account,
5817 const gchar *password,
5818 const gchar *email,
5819 const gchar *email_url,
5820 const gchar **errmsg)
5822 struct sipe_core_private *sipe_private;
5823 struct sipe_account_data *sip;
5824 gchar **user_domain;
5826 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
5828 /* ensure that sign-in name doesn't contain invalid characters */
5829 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
5830 *errmsg = _("SIP Exchange user name contains invalid characters");
5831 return NULL;
5834 /* ensure that sign-in name format is name@domain */
5835 if (!strchr(signin_name, '@') ||
5836 g_str_has_prefix(signin_name, "@") ||
5837 g_str_has_suffix(signin_name, "@")) {
5838 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5839 return NULL;
5842 /* ensure that email format is name@domain (if provided) */
5843 if (!is_empty(email) &&
5844 (!strchr(email, '@') ||
5845 g_str_has_prefix(email, "@") ||
5846 g_str_has_suffix(email, "@")))
5848 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5849 return NULL;
5852 /* ensure that user name doesn't contain spaces */
5853 user_domain = g_strsplit(signin_name, "@", 2);
5854 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5855 if (strchr(user_domain[0], ' ') != NULL) {
5856 g_strfreev(user_domain);
5857 *errmsg = _("SIP Exchange user name contains whitespace");
5858 return NULL;
5861 /* ensure that email_url is in proper format if enabled (if provided).
5862 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5863 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5865 if (!is_empty(email_url)) {
5866 char *tmp = g_ascii_strdown(email_url, -1);
5867 if (!g_str_has_prefix(tmp, "https://"))
5869 g_free(tmp);
5870 g_strfreev(user_domain);
5871 *errmsg = _("Email services URL should be valid if provided\n"
5872 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5873 "Example: https://domino.corp.com/maildatabase.nsf");
5874 return NULL;
5876 g_free(tmp);
5879 sipe_private = g_new0(struct sipe_core_private, 1);
5880 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5881 sip->subscribed_buddies = FALSE;
5882 sip->initial_state_published = FALSE;
5883 sipe_private->username = g_strdup(signin_name);
5884 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5885 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5886 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5887 sip->password = g_strdup(password);
5888 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5889 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5890 g_strfreev(user_domain);
5892 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5893 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5894 g_free, (GDestroyNotify)g_hash_table_destroy);
5895 sipe_subscriptions_init(sipe_private);
5896 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5898 return((struct sipe_core_public *)sipe_private);
5901 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5903 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5905 g_free(sipe_private->epid);
5906 sipe_private->epid = NULL;
5908 sip_transport_disconnect(sipe_private);
5910 sipe_schedule_cancel_all(sipe_private);
5912 if (sip->allow_events) {
5913 GSList *entry = sip->allow_events;
5914 while (entry) {
5915 g_free(entry->data);
5916 entry = entry->next;
5919 g_slist_free(sip->allow_events);
5921 if (sip->containers) {
5922 GSList *entry = sip->containers;
5923 while (entry) {
5924 free_container((struct sipe_container *)entry->data);
5925 entry = entry->next;
5928 g_slist_free(sip->containers);
5930 if (sipe_private->contact)
5931 g_free(sipe_private->contact);
5932 sipe_private->contact = NULL;
5933 if (sip->regcallid)
5934 g_free(sip->regcallid);
5935 sip->regcallid = NULL;
5937 if (sipe_private->focus_factory_uri)
5938 g_free(sipe_private->focus_factory_uri);
5939 sipe_private->focus_factory_uri = NULL;
5941 if (sip->cal) {
5942 sipe_cal_calendar_free(sip->cal);
5944 sip->cal = NULL;
5948 * A callback for g_hash_table_foreach_remove
5950 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5951 SIPE_UNUSED_PARAMETER gpointer user_data)
5953 sipe_free_buddy((struct sipe_buddy *) buddy);
5955 /* We must return TRUE as the key/value have already been deleted */
5956 return(TRUE);
5959 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5961 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5964 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5965 SIPE_UNUSED_PARAMETER void *user_data)
5967 PurpleAccount *acct = purple_connection_get_account(gc);
5968 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5969 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5970 if (conv == NULL)
5971 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5972 purple_conversation_present(conv);
5973 g_free(id);
5976 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5977 SIPE_UNUSED_PARAMETER void *user_data)
5980 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5981 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5984 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
5985 struct sipmsg *msg,
5986 SIPE_UNUSED_PARAMETER struct transaction *trans)
5988 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5989 PurpleNotifySearchResults *results;
5990 PurpleNotifySearchColumn *column;
5991 sipe_xml *searchResults;
5992 const sipe_xml *mrow;
5993 int match_count = 0;
5994 gboolean more = FALSE;
5995 gchar *secondary;
5997 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
5999 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6000 if (!searchResults) {
6001 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
6002 return FALSE;
6005 results = purple_notify_searchresults_new();
6007 if (results == NULL) {
6008 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
6009 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
6011 sipe_xml_free(searchResults);
6012 return FALSE;
6015 column = purple_notify_searchresults_column_new(_("User name"));
6016 purple_notify_searchresults_column_add(results, column);
6018 column = purple_notify_searchresults_column_new(_("Name"));
6019 purple_notify_searchresults_column_add(results, column);
6021 column = purple_notify_searchresults_column_new(_("Company"));
6022 purple_notify_searchresults_column_add(results, column);
6024 column = purple_notify_searchresults_column_new(_("Country"));
6025 purple_notify_searchresults_column_add(results, column);
6027 column = purple_notify_searchresults_column_new(_("Email"));
6028 purple_notify_searchresults_column_add(results, column);
6030 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
6031 GList *row = NULL;
6033 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
6034 row = g_list_append(row, g_strdup(uri_parts[1]));
6035 g_strfreev(uri_parts);
6037 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
6038 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
6039 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
6040 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
6042 purple_notify_searchresults_row_add(results, row);
6043 match_count++;
6046 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
6047 char *data = sipe_xml_data(mrow);
6048 more = (g_strcasecmp(data, "true") == 0);
6049 g_free(data);
6052 secondary = g_strdup_printf(
6053 dngettext(PACKAGE_NAME,
6054 "Found %d contact%s:",
6055 "Found %d contacts%s:", match_count),
6056 match_count, more ? _(" (more matched your query)") : "");
6058 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
6059 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
6060 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
6062 g_free(secondary);
6063 sipe_xml_free(searchResults);
6064 return TRUE;
6067 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6069 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
6070 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
6071 unsigned i = 0;
6073 if (!attrs) return;
6075 do {
6076 PurpleRequestField *field = entries->data;
6077 const char *id = purple_request_field_get_id(field);
6078 const char *value = purple_request_field_string_get_value(field);
6080 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
6082 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
6083 } while ((entries = g_list_next(entries)) != NULL);
6084 attrs[i] = NULL;
6086 if (i > 0) {
6087 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6088 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6089 gchar *query = g_strjoinv(NULL, attrs);
6090 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
6091 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
6092 send_soap_request_with_cb(sipe_private, domain_uri, body,
6093 process_search_contact_response, NULL);
6094 g_free(domain_uri);
6095 g_free(body);
6096 g_free(query);
6099 g_strfreev(attrs);
6102 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
6103 gpointer value,
6104 GString* str)
6106 struct sipe_publication *publication = value;
6108 g_string_append_printf( str,
6109 SIPE_PUB_XML_PUBLICATION_CLEAR,
6110 publication->category,
6111 publication->instance,
6112 publication->container,
6113 publication->version,
6114 "static");
6117 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
6119 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6120 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
6121 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
6123 GString* str = g_string_new(NULL);
6124 gchar *publications;
6126 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
6127 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
6128 return;
6131 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
6132 publications = g_string_free(str, FALSE);
6134 send_presence_publish(sipe_private, publications);
6135 g_free(publications);
6137 else /* 2005 */
6139 send_presence_soap0(sipe_private, FALSE, TRUE);
6143 /** for Access levels menu */
6144 #define INDENT_FMT " %s"
6146 /** Member is directly placed to access level container.
6147 * For example SIP URI of user is in the container.
6149 #define INDENT_MARKED_FMT "* %s"
6151 /** Member is indirectly belong to access level container.
6152 * For example 'sameEnterprise' is in the container and user
6153 * belongs to that same enterprise.
6155 #define INDENT_MARKED_INHERITED_FMT "= %s"
6157 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
6158 const gchar *name,
6159 const gchar *status_name,
6160 gboolean is_online)
6162 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6163 gchar *note = NULL;
6164 gboolean is_oof_note = FALSE;
6165 gchar *activity = NULL;
6166 gchar *calendar = NULL;
6167 gchar *meeting_subject = NULL;
6168 gchar *meeting_location = NULL;
6169 gchar *access_text = NULL;
6170 GSList *info = NULL;
6172 #define SIPE_ADD_BUDDY_INFO(l, t) \
6174 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
6175 sbi->label = (l); \
6176 sbi->text = (t); \
6177 info = g_slist_append(info, sbi); \
6180 if (sipe_public) { //happens on pidgin exit
6181 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
6182 if (sbuddy) {
6183 note = sbuddy->note;
6184 is_oof_note = sbuddy->is_oof_note;
6185 activity = sbuddy->activity;
6186 calendar = sipe_cal_get_description(sbuddy);
6187 meeting_subject = sbuddy->meeting_subject;
6188 meeting_location = sbuddy->meeting_location;
6190 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6191 gboolean is_group_access = FALSE;
6192 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
6193 const char *access_level = sipe_get_access_level_name(container_id);
6194 access_text = is_group_access ?
6195 g_strdup(access_level) :
6196 g_strdup_printf(INDENT_MARKED_FMT, access_level);
6200 //Layout
6201 if (is_online)
6203 gchar *status_str = g_strdup(activity ? activity : status_name);
6205 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
6207 if (is_online && !is_empty(calendar))
6209 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
6210 calendar = NULL;
6212 g_free(calendar);
6213 if (!is_empty(meeting_location))
6215 SIPE_ADD_BUDDY_INFO(_("Meeting in"), g_strdup(meeting_location));
6217 if (!is_empty(meeting_subject))
6219 SIPE_ADD_BUDDY_INFO(_("Meeting about"), g_strdup(meeting_subject));
6221 if (note)
6223 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
6224 SIPE_ADD_BUDDY_INFO(is_oof_note ? _("Out of office note") : _("Note"),
6225 g_strdup_printf("<i>%s</i>", note));
6227 if (access_text) {
6228 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
6231 return(info);
6234 static PurpleBuddy *
6235 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6237 PurpleBuddy *clone;
6238 const gchar *server_alias, *email;
6239 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6241 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6243 purple_blist_add_buddy(clone, NULL, group, NULL);
6245 server_alias = purple_buddy_get_server_alias(buddy);
6246 if (server_alias) {
6247 purple_blist_server_alias_buddy(clone, server_alias);
6250 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6251 if (email) {
6252 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
6255 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6256 //for UI to update;
6257 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6258 return clone;
6261 static void
6262 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6264 PurpleBuddy *buddy, *b;
6265 PurpleConnection *gc;
6266 PurpleGroup * group = purple_find_group(group_name);
6268 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6270 buddy = (PurpleBuddy *)node;
6272 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
6273 gc = purple_account_get_connection(buddy->account);
6275 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6276 if (!b){
6277 purple_blist_add_buddy_clone(group, buddy);
6280 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6283 static void
6284 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6286 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6288 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
6290 /* 2007+ conference */
6291 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
6293 sipe_conf_add(sipe_private, buddy->name);
6295 else /* 2005- multiparty chat */
6297 gchar *self = sip_uri_self(sipe_private);
6298 struct sip_session *session;
6300 session = sipe_session_add_chat(sipe_private);
6301 session->chat_title = sipe_chat_get_name(session->callid);
6302 session->roster_manager = g_strdup(self);
6304 session->backend_session = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
6305 session->chat_id,
6306 session->chat_title,
6307 self,
6308 FALSE);
6309 sipe_backend_chat_add(session->backend_session,
6310 self,
6311 FALSE);
6312 sipe_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
6314 g_free(self);
6319 * For 2007+ conference only.
6321 static void
6322 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
6324 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6325 struct sip_session *session;
6327 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
6328 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
6330 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6332 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
6336 * For 2007+ conference only.
6338 static void
6339 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
6341 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6342 struct sip_session *session;
6344 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
6345 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
6347 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6349 sipe_conf_delete_user(sipe_private, session, buddy->name);
6352 static void
6353 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
6355 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6356 struct sip_session *session;
6358 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
6359 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
6361 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6363 sipe_invite_to_chat(sipe_private, session, buddy->name);
6366 static void
6367 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
6369 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6371 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
6372 if (phone) {
6373 char *tel_uri = sip_to_tel_uri(phone);
6375 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
6376 sip_csta_make_call(sipe_private, tel_uri);
6378 g_free(tel_uri);
6382 static void
6383 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
6385 /** Translators: replace with URL to localized page
6386 * If it doesn't exist copy the original URL */
6387 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
6390 static void
6391 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6393 const gchar *email;
6394 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
6396 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6397 if (email)
6399 char *command_line = g_strdup_printf(
6400 #ifdef _WIN32
6401 "cmd /c start"
6402 #else
6403 "xdg-email"
6404 #endif
6405 " mailto:%s", email);
6406 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
6408 g_spawn_command_line_async(command_line, NULL);
6409 g_free(command_line);
6411 else
6413 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
6417 static void
6418 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
6419 struct sipe_container *container)
6421 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6422 struct sipe_container_member *member;
6424 if (!container || !container->members) return;
6426 member = ((struct sipe_container_member *)container->members->data);
6428 if (!member->type) return;
6430 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
6431 container->id, member->type, member->value ? member->value : "");
6433 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
6436 static GList *
6437 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6438 const char* uri);
6441 * A menu which appear when right-clicking on buddy in contact list.
6443 GList *
6444 sipe_buddy_menu(PurpleBuddy *buddy)
6446 PurpleBlistNode *g_node;
6447 PurpleGroup *group, *gr_parent;
6448 PurpleMenuAction *act;
6449 GList *menu = NULL;
6450 GList *menu_groups = NULL;
6451 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6452 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6453 const char *email;
6454 const char *phone;
6455 const char *phone_disp_str;
6456 gchar *self = sip_uri_self(sipe_private);
6458 SIPE_SESSION_FOREACH {
6459 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->backend_session)
6461 if (sipe_backend_chat_find(session->backend_session, buddy->name))
6463 gboolean conf_op = sipe_backend_chat_is_operator(session->backend_session, self);
6465 if (session->focus_uri
6466 && !sipe_backend_chat_is_operator(session->backend_session, buddy->name) /* Not conf OP */
6467 && conf_op) /* We are a conf OP */
6469 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
6470 act = purple_menu_action_new(label,
6471 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6472 session->chat_title, NULL);
6473 g_free(label);
6474 menu = g_list_prepend(menu, act);
6477 if (session->focus_uri
6478 && conf_op) /* We are a conf OP */
6480 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
6481 act = purple_menu_action_new(label,
6482 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6483 session->chat_title, NULL);
6484 g_free(label);
6485 menu = g_list_prepend(menu, act);
6488 else
6490 if (!session->focus_uri
6491 || (session->focus_uri && !session->locked))
6493 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
6494 act = purple_menu_action_new(label,
6495 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6496 session->chat_title, NULL);
6497 g_free(label);
6498 menu = g_list_prepend(menu, act);
6502 } SIPE_SESSION_FOREACH_END;
6504 act = purple_menu_action_new(_("New chat"),
6505 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6506 NULL, NULL);
6507 menu = g_list_prepend(menu, act);
6509 if (sip->csta && !sip->csta->line_status) {
6510 gchar *tmp = NULL;
6511 /* work phone */
6512 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
6513 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
6514 if (phone) {
6515 gchar *label = g_strdup_printf(_("Work %s"),
6516 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6517 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6518 g_free(tmp);
6519 tmp = NULL;
6520 g_free(label);
6521 menu = g_list_prepend(menu, act);
6524 /* mobile phone */
6525 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
6526 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
6527 if (phone) {
6528 gchar *label = g_strdup_printf(_("Mobile %s"),
6529 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6530 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6531 g_free(tmp);
6532 tmp = NULL;
6533 g_free(label);
6534 menu = g_list_prepend(menu, act);
6537 /* home phone */
6538 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
6539 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
6540 if (phone) {
6541 gchar *label = g_strdup_printf(_("Home %s"),
6542 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6543 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6544 g_free(tmp);
6545 tmp = NULL;
6546 g_free(label);
6547 menu = g_list_prepend(menu, act);
6550 /* other phone */
6551 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
6552 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
6553 if (phone) {
6554 gchar *label = g_strdup_printf(_("Other %s"),
6555 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6556 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6557 g_free(tmp);
6558 tmp = NULL;
6559 g_free(label);
6560 menu = g_list_prepend(menu, act);
6563 /* custom1 phone */
6564 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
6565 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
6566 if (phone) {
6567 gchar *label = g_strdup_printf(_("Custom1 %s"),
6568 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6569 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6570 g_free(tmp);
6571 tmp = NULL;
6572 g_free(label);
6573 menu = g_list_prepend(menu, act);
6577 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6578 if (email) {
6579 act = purple_menu_action_new(_("Send email..."),
6580 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6581 NULL, NULL);
6582 menu = g_list_prepend(menu, act);
6585 /* Access Level */
6586 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6587 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
6589 act = purple_menu_action_new(_("Access level"),
6590 NULL,
6591 NULL, menu_access_levels);
6592 menu = g_list_prepend(menu, act);
6595 /* Copy to */
6596 gr_parent = purple_buddy_get_group(buddy);
6597 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6598 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6599 continue;
6601 group = (PurpleGroup *)g_node;
6602 if (group == gr_parent)
6603 continue;
6605 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6606 continue;
6608 act = purple_menu_action_new(purple_group_get_name(group),
6609 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6610 group->name, NULL);
6611 menu_groups = g_list_prepend(menu_groups, act);
6613 menu_groups = g_list_reverse(menu_groups);
6615 act = purple_menu_action_new(_("Copy to"),
6616 NULL,
6617 NULL, menu_groups);
6618 menu = g_list_prepend(menu, act);
6620 menu = g_list_reverse(menu);
6622 g_free(self);
6623 return menu;
6626 static void
6627 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6629 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6630 const char *domain = purple_request_fields_get_string(fields, "access_domain");
6631 int index = purple_request_fields_get_choice(fields, "container_id");
6632 /* move Blocked first */
6633 int i = (index == 4) ? 0 : index + 1;
6634 int container_id = containers[i];
6636 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
6638 sipe_change_access_level(sipe_private, container_id, "domain", domain);
6641 static void
6642 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
6644 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6645 PurpleAccount *account = sip->account;
6646 PurpleConnection *gc = sip->gc;
6647 PurpleRequestFields *fields;
6648 PurpleRequestFieldGroup *g;
6649 PurpleRequestField *f;
6651 fields = purple_request_fields_new();
6653 g = purple_request_field_group_new(NULL);
6654 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
6655 purple_request_field_set_required(f, TRUE);
6656 purple_request_field_group_add_field(g, f);
6658 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
6659 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
6660 purple_request_field_choice_add(f, _("Team"));
6661 purple_request_field_choice_add(f, _("Company"));
6662 purple_request_field_choice_add(f, _("Public"));
6663 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
6664 purple_request_field_choice_set_default_value(f, 3); /* index */
6665 purple_request_field_set_required(f, TRUE);
6666 purple_request_field_group_add_field(g, f);
6668 purple_request_fields_add_group(fields, g);
6670 purple_request_fields(gc, _("Add new domain"),
6671 _("Add new domain"), NULL, fields,
6672 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
6673 _("Cancel"), NULL,
6674 account, NULL, NULL, gc);
6677 static void
6678 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
6680 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
6683 static GList *
6684 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
6685 const char* member_type,
6686 const char* member_value,
6687 const gboolean extra_menu)
6689 GList *menu_access_levels = NULL;
6690 unsigned int i;
6691 char *menu_name;
6692 PurpleMenuAction *act;
6693 struct sipe_container *container;
6694 struct sipe_container_member *member;
6695 gboolean is_group_access = FALSE;
6696 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
6698 for (i = 1; i <= CONTAINERS_LEN; i++) {
6699 /* to put Blocked level last in menu list.
6700 * Blocked should remaim in the first place in the containers[] array.
6702 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
6703 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
6705 container = g_new0(struct sipe_container, 1);
6706 member = g_new0(struct sipe_container_member, 1);
6707 container->id = containers[j];
6708 container->members = g_slist_append(container->members, member);
6709 member->type = g_strdup(member_type);
6710 member->value = g_strdup(member_value);
6712 /* current container/access level */
6713 if (((int)containers[j]) == container_id) {
6714 menu_name = is_group_access ?
6715 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
6716 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
6717 } else {
6718 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
6721 act = purple_menu_action_new(menu_name,
6722 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6723 container, NULL);
6724 g_free(menu_name);
6725 menu_access_levels = g_list_prepend(menu_access_levels, act);
6728 if (extra_menu && (container_id >= 0)) {
6729 /* separator */
6730 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
6731 menu_access_levels = g_list_prepend(menu_access_levels, act);
6733 if (!is_group_access) {
6734 container = g_new0(struct sipe_container, 1);
6735 member = g_new0(struct sipe_container_member, 1);
6736 container->id = -1;
6737 container->members = g_slist_append(container->members, member);
6738 member->type = g_strdup(member_type);
6739 member->value = g_strdup(member_value);
6741 /* Translators: remove (clear) previously assigned access level */
6742 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
6743 act = purple_menu_action_new(menu_name,
6744 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6745 container, NULL);
6746 g_free(menu_name);
6747 menu_access_levels = g_list_prepend(menu_access_levels, act);
6751 menu_access_levels = g_list_reverse(menu_access_levels);
6752 return menu_access_levels;
6755 static GList *
6756 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
6758 GList *menu_access_groups = NULL;
6759 PurpleMenuAction *act;
6760 GSList *access_domains = NULL;
6761 GSList *entry;
6762 char *menu_name;
6763 char *domain;
6765 act = purple_menu_action_new(_("People in my company"),
6766 NULL,
6767 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
6768 menu_access_groups = g_list_prepend(menu_access_groups, act);
6770 /* this is original name, don't edit */
6771 act = purple_menu_action_new(_("People in domains connected with my company"),
6772 NULL,
6773 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
6774 menu_access_groups = g_list_prepend(menu_access_groups, act);
6776 act = purple_menu_action_new(_("People in public domains"),
6777 NULL,
6778 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
6779 menu_access_groups = g_list_prepend(menu_access_groups, act);
6781 access_domains = sipe_get_access_domains(sipe_private);
6782 entry = access_domains;
6783 while (entry) {
6784 domain = entry->data;
6786 menu_name = g_strdup_printf(_("People at %s"), domain);
6787 act = purple_menu_action_new(menu_name,
6788 NULL,
6789 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6790 menu_access_groups = g_list_prepend(menu_access_groups, act);
6791 g_free(menu_name);
6793 entry = entry->next;
6796 /* separator */
6797 /* People in domains connected with my company */
6798 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6799 menu_access_groups = g_list_prepend(menu_access_groups, act);
6801 act = purple_menu_action_new(_("Add new domain..."),
6802 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6803 NULL, NULL);
6804 menu_access_groups = g_list_prepend(menu_access_groups, act);
6806 menu_access_groups = g_list_reverse(menu_access_groups);
6808 return menu_access_groups;
6811 static GList *
6812 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6813 const char* uri)
6815 GList *menu_access_levels = NULL;
6816 GList *menu_access_groups = NULL;
6817 char *menu_name;
6818 PurpleMenuAction *act;
6820 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6822 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6824 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6825 act = purple_menu_action_new(menu_name,
6826 NULL,
6827 NULL, menu_access_groups);
6828 g_free(menu_name);
6829 menu_access_levels = g_list_append(menu_access_levels, act);
6831 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6832 act = purple_menu_action_new(menu_name,
6833 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6834 NULL, NULL);
6835 g_free(menu_name);
6836 menu_access_levels = g_list_append(menu_access_levels, act);
6838 return menu_access_levels;
6841 static void
6842 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
6844 struct sipe_core_private *sipe_private = PURPLE_CHAT_TO_SIPE_CORE_PRIVATE;
6845 struct sip_session *session;
6847 session = sipe_session_find_chat_by_title(sipe_private,
6848 (gchar *)g_hash_table_lookup(chat->components, "channel"));
6849 sipe_conf_modify_conference_lock(sipe_private, session, locked);
6852 static void
6853 sipe_chat_menu_unlock_cb(PurpleChat *chat)
6855 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
6856 sipe_conf_modify_lock(chat, FALSE);
6859 static void
6860 sipe_chat_menu_lock_cb(PurpleChat *chat)
6862 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
6863 sipe_conf_modify_lock(chat, TRUE);
6866 GList *
6867 sipe_chat_menu(PurpleChat *chat)
6869 PurpleMenuAction *act;
6870 GList *menu = NULL;
6871 struct sipe_core_private *sipe_private = PURPLE_CHAT_TO_SIPE_CORE_PRIVATE;
6872 struct sip_session *session;
6873 gchar *self;
6875 session = sipe_session_find_chat_by_title(sipe_private,
6876 (gchar *)g_hash_table_lookup(chat->components, "channel"));
6877 if (!session) return NULL;
6879 self = sip_uri_self(sipe_private);
6881 if (session->focus_uri &&
6882 sipe_backend_chat_is_operator(session->backend_session, self))
6884 if (session->locked) {
6885 act = purple_menu_action_new(_("Unlock"),
6886 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
6887 NULL, NULL);
6888 menu = g_list_prepend(menu, act);
6889 } else {
6890 act = purple_menu_action_new(_("Lock"),
6891 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
6892 NULL, NULL);
6893 menu = g_list_prepend(menu, act);
6897 menu = g_list_reverse(menu);
6899 g_free(self);
6900 return menu;
6903 static gboolean
6904 process_get_info_response(struct sipe_core_private *sipe_private,
6905 struct sipmsg *msg, struct transaction *trans)
6907 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6908 char *uri = trans->payload->data;
6910 PurpleNotifyUserInfo *info;
6911 PurpleBuddy *pbuddy = NULL;
6912 struct sipe_buddy *sbuddy;
6913 const char *alias = NULL;
6914 char *device_name = NULL;
6915 char *server_alias = NULL;
6916 char *phone_number = NULL;
6917 char *email = NULL;
6918 const char *site;
6919 char *first_name = NULL;
6920 char *last_name = NULL;
6922 if (!sip) return FALSE;
6924 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6926 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6927 alias = purple_buddy_get_local_alias(pbuddy);
6929 //will query buddy UA's capabilities and send answer to log
6930 sipe_options_request(sipe_private, uri);
6932 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6933 if (sbuddy) {
6934 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6937 info = purple_notify_user_info_new();
6939 if (msg->response != 200) {
6940 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6941 } else {
6942 sipe_xml *searchResults;
6943 const sipe_xml *mrow;
6945 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6946 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6947 if (!searchResults) {
6948 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6949 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6950 const char *value;
6951 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6952 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6953 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6955 /* For 2007 system we will take this from ContactCard -
6956 * it has cleaner tel: URIs at least
6958 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6959 char *tel_uri = sip_to_tel_uri(phone_number);
6960 /* trims its parameters, so call first */
6961 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, server_alias);
6962 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
6963 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
6964 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, phone_number);
6965 g_free(tel_uri);
6968 if (server_alias && strlen(server_alias) > 0) {
6969 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6971 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6972 purple_notify_user_info_add_pair(info, _("Job title"), value);
6974 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6975 purple_notify_user_info_add_pair(info, _("Office"), value);
6977 if (phone_number && strlen(phone_number) > 0) {
6978 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
6980 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6981 purple_notify_user_info_add_pair(info, _("Company"), value);
6983 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6984 purple_notify_user_info_add_pair(info, _("City"), value);
6986 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
6987 purple_notify_user_info_add_pair(info, _("State"), value);
6989 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
6990 purple_notify_user_info_add_pair(info, _("Country"), value);
6992 if (email && strlen(email) > 0) {
6993 purple_notify_user_info_add_pair(info, _("Email address"), email);
6997 sipe_xml_free(searchResults);
7000 purple_notify_user_info_add_section_break(info);
7002 if (is_empty(server_alias)) {
7003 g_free(server_alias);
7004 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
7005 if (server_alias) {
7006 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
7010 /* present alias if it differs from server alias */
7011 if (alias && !sipe_strequal(alias, server_alias))
7013 purple_notify_user_info_add_pair(info, _("Alias"), alias);
7016 if (is_empty(email)) {
7017 g_free(email);
7018 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
7019 if (email) {
7020 purple_notify_user_info_add_pair(info, _("Email address"), email);
7024 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
7025 if (site) {
7026 purple_notify_user_info_add_pair(info, _("Site"), site);
7029 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
7030 if (first_name && last_name) {
7031 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
7033 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
7034 g_free(link);
7036 g_free(first_name);
7037 g_free(last_name);
7039 if (device_name) {
7040 purple_notify_user_info_add_pair(info, _("Device"), device_name);
7043 /* show a buddy's user info in a nice dialog box */
7044 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
7045 uri, /* buddy's URI */
7046 info, /* body */
7047 NULL, /* callback called when dialog closed */
7048 NULL); /* userdata for callback */
7050 g_free(phone_number);
7051 g_free(server_alias);
7052 g_free(email);
7053 g_free(device_name);
7055 return TRUE;
7059 * AD search first, LDAP based
7061 void sipe_get_info(PurpleConnection *gc, const char *username)
7063 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
7064 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
7065 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
7066 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
7067 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
7069 payload->destroy = g_free;
7070 payload->data = g_strdup(username);
7072 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
7073 send_soap_request_with_cb(sipe_private, domain_uri, body,
7074 process_get_info_response, payload);
7075 g_free(domain_uri);
7076 g_free(body);
7077 g_free(row);
7081 Local Variables:
7082 mode: c
7083 c-file-style: "bsd"
7084 indent-tabs-mode: t
7085 tab-width: 8
7086 End: