Merge branch 'mob' into private-chatid-rework
[siplcs.git] / src / core / sipe.c
blob16709e848c1134670d0dea966bb7e693442baf7a
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-groupchat.h"
80 #include "sipe-mime.h"
81 #include "sipe-nls.h"
82 #include "sipe-schedule.h"
83 #include "sipe-session.h"
84 #include "sipe-subscriptions.h"
85 #ifdef HAVE_VV
86 #include "sipe-media.h"
87 #endif
88 #include "sipe-utils.h"
89 #include "sipe-xml.h"
90 #include "uuid.h"
91 #include "sipe.h"
93 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
95 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
96 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
98 /* Status identifiers (see also: sipe_status_types()) */
99 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
100 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
101 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
102 /* PURPLE_STATUS_UNAVAILABLE: */
103 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
104 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
105 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
106 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
107 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
108 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
109 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
110 /* PURPLE_STATUS_AWAY: */
111 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
112 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
113 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
114 /** Reuters status (user settable) */
115 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
116 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
117 /* ??? PURPLE_STATUS_MOBILE */
118 /* ??? PURPLE_STATUS_TUNE */
120 /* Status attributes (see also sipe_status_types() */
121 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
123 static struct sipe_activity_map_struct
125 sipe_activity type;
126 const char *token;
127 const char *desc;
128 const char *status_id;
130 } const sipe_activity_map[] =
132 /* This has nothing to do with Availability numbers, like 3500 (online).
133 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
135 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
136 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
137 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
138 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
139 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
140 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
141 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
142 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
143 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
144 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
145 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
146 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
147 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
148 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
149 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
151 /** @param x is sipe_activity */
152 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
154 static sipe_activity
155 sipe_get_activity_by_token(const char *token)
157 int i;
159 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
161 if (sipe_strequal(token, sipe_activity_map[i].token))
162 return sipe_activity_map[i].type;
165 return sipe_activity_map[0].type;
168 static const char *
169 sipe_get_activity_desc_by_token(const char *token)
171 if (!token) return NULL;
173 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
176 static void send_presence_status(struct sipe_core_private *sipe_private,
177 void *unused);
180 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
182 static void
183 send_soap_request_with_cb(struct sipe_core_private *sipe_private,
184 gchar *from0,
185 gchar *body,
186 TransCallback callback,
187 struct transaction_payload *payload)
189 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sipe_private);
190 gchar *contact = get_contact(sipe_private);
191 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
192 "Content-Type: application/SOAP+xml\r\n",contact);
194 struct transaction *trans = sip_transport_service(sipe_private,
195 from,
196 hdr,
197 body,
198 callback);
199 trans->payload = payload;
201 g_free(from);
202 g_free(contact);
203 g_free(hdr);
206 static void send_soap_request(struct sipe_core_private *sipe_private,
207 gchar *body)
209 send_soap_request_with_cb(sipe_private, NULL, body, NULL, NULL);
213 * Returns pointer to URI without sip: prefix if any
215 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
216 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
218 * Doesn't allocate memory
220 static const char *
221 sipe_get_no_sip_uri(const char *sip_uri)
223 const char *prefix = "sip:";
224 if (!sip_uri) return NULL;
226 if (g_str_has_prefix(sip_uri, prefix)) {
227 return (sip_uri+strlen(prefix));
228 } else {
229 return sip_uri;
233 static void
234 sipe_contact_set_acl (struct sipe_core_private *sipe_private,
235 const gchar *who,
236 gchar *rights)
238 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
239 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
240 send_soap_request(sipe_private, body);
241 g_free(body);
244 static void
245 sipe_change_access_level(struct sipe_core_private *sipe_private,
246 const int container_id,
247 const gchar *type,
248 const gchar *value);
250 void
251 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
252 const gchar * who, gboolean allow)
254 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
256 if (allow) {
257 SIPE_DEBUG_INFO("Authorizing contact %s", who);
258 } else {
259 SIPE_DEBUG_INFO("Blocking contact %s", who);
262 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
263 sipe_change_access_level(sipe_private, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
264 } else {
265 sipe_contact_set_acl(sipe_private, who, allow ? "AA" : "BD");
269 static
270 void sipe_auth_user_cb(void * data)
272 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
273 if (!job) return;
275 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, TRUE);
276 g_free(job);
279 static
280 void sipe_deny_user_cb(void * data)
282 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
283 if (!job) return;
285 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, FALSE);
286 g_free(job);
289 /** @applicable: 2005-
291 static void
292 sipe_process_presence_wpending (struct sipe_core_private *sipe_private,
293 struct sipmsg * msg)
295 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
296 sipe_xml *watchers;
297 const sipe_xml *watcher;
298 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
299 if (msg->response != 0 && msg->response != 200) return;
301 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
303 watchers = sipe_xml_parse(msg->body, msg->bodylen);
304 if (!watchers) return;
306 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
307 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
308 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
309 gboolean on_list = g_hash_table_lookup(sipe_private->buddies, remote_user) != NULL;
311 // TODO pull out optional displayName to pass as alias
312 if (remote_user) {
313 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
314 job->who = remote_user;
315 job->sipe_private = sipe_private;
316 purple_account_request_authorization(
317 sip->account,
318 remote_user,
319 _("you"), /* id */
320 alias,
321 NULL, /* message */
322 on_list,
323 sipe_auth_user_cb,
324 sipe_deny_user_cb,
325 (void *) job);
330 sipe_xml_free(watchers);
331 return;
334 static void
335 sipe_group_add(struct sipe_core_private *sipe_private,
336 struct sipe_group * group)
338 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
339 PurpleGroup * purple_group = purple_find_group(group->name);
340 if (!purple_group) {
341 purple_group = purple_group_new(group->name);
342 purple_blist_add_group(purple_group, NULL);
345 if (purple_group) {
346 group->purple_group = purple_group;
347 sip->groups = g_slist_append(sip->groups, group);
348 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
349 } else {
350 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
354 static struct sipe_group *sipe_group_find_by_id(struct sipe_core_private *sipe_private,
355 int id)
357 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
358 struct sipe_group *group;
359 GSList *entry;
360 if (sip == NULL) {
361 return NULL;
364 entry = sip->groups;
365 while (entry) {
366 group = entry->data;
367 if (group->id == id) {
368 return group;
370 entry = entry->next;
372 return NULL;
375 static struct sipe_group *sipe_group_find_by_name(struct sipe_core_private *sipe_private,
376 const gchar * name)
378 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
379 struct sipe_group *group;
380 GSList *entry;
381 if (!sip || !name) {
382 return NULL;
385 entry = sip->groups;
386 while (entry) {
387 group = entry->data;
388 if (sipe_strequal(group->name, name)) {
389 return group;
391 entry = entry->next;
393 return NULL;
396 static void
397 sipe_group_rename(struct sipe_core_private *sipe_private,
398 struct sipe_group *group,
399 gchar *name)
401 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
402 gchar *body;
403 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
404 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
405 send_soap_request(sipe_private, body);
406 g_free(body);
407 g_free(group->name);
408 group->name = g_strdup(name);
412 * Only appends if no such value already stored.
413 * Like Set in Java.
415 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
416 GSList * res = list;
417 if (!g_slist_find_custom(list, data, func)) {
418 res = g_slist_insert_sorted(list, data, func);
420 return res;
423 static int
424 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
425 return group1->id - group2->id;
429 * Returns string like "2 4 7 8" - group ids buddy belong to.
431 static gchar *
432 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
433 int i = 0;
434 gchar *res;
435 //creating array from GList, converting int to gchar*
436 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
437 GSList *entry = buddy->groups;
439 if (!ids_arr) return NULL;
441 while (entry) {
442 struct sipe_group * group = entry->data;
443 ids_arr[i] = g_strdup_printf("%d", group->id);
444 entry = entry->next;
445 i++;
447 ids_arr[i] = NULL;
448 res = g_strjoinv(" ", ids_arr);
449 g_strfreev(ids_arr);
450 return res;
454 * Sends buddy update to server
456 void
457 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
459 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
460 struct sipe_buddy *buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
461 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
463 if (buddy && purple_buddy) {
464 const char *alias = purple_buddy_get_alias(purple_buddy);
465 gchar *groups = sipe_get_buddy_groups_string(buddy);
466 if (groups) {
467 gchar *body;
468 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
470 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
471 alias, groups, "true", buddy->name, sip->contacts_delta++
473 send_soap_request(SIPE_CORE_PRIVATE, body);
474 g_free(groups);
475 g_free(body);
480 static gboolean process_add_group_response(struct sipe_core_private *sipe_private,
481 struct sipmsg *msg,
482 struct transaction *trans)
484 if (msg->response == 200) {
485 struct sipe_group *group;
486 struct group_user_context *ctx = trans->payload->data;
487 sipe_xml *xml;
488 const sipe_xml *node;
489 char *group_id;
490 struct sipe_buddy *buddy;
492 xml = sipe_xml_parse(msg->body, msg->bodylen);
493 if (!xml) {
494 return FALSE;
497 node = sipe_xml_child(xml, "Body/addGroup/groupID");
498 if (!node) {
499 sipe_xml_free(xml);
500 return FALSE;
503 group_id = sipe_xml_data(node);
504 if (!group_id) {
505 sipe_xml_free(xml);
506 return FALSE;
509 group = g_new0(struct sipe_group, 1);
510 group->id = (int)g_ascii_strtod(group_id, NULL);
511 g_free(group_id);
512 group->name = g_strdup(ctx->group_name);
514 sipe_group_add(sipe_private, group);
516 if (ctx->user_name) {
517 buddy = g_hash_table_lookup(sipe_private->buddies, ctx->user_name);
518 if (buddy) {
519 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
522 sipe_core_group_set_user(SIPE_CORE_PUBLIC, ctx->user_name);
525 sipe_xml_free(xml);
526 return TRUE;
528 return FALSE;
531 static void sipe_group_context_destroy(gpointer data)
533 struct group_user_context *ctx = data;
534 g_free(ctx->group_name);
535 g_free(ctx->user_name);
536 g_free(ctx);
539 static void sipe_group_create (struct sipe_core_private *sipe_private,
540 const gchar *name, const gchar * who)
542 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
543 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
544 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
545 const gchar *soap_name = sipe_strequal(name, _("Other Contacts")) ? "~" : name;
546 gchar *body;
547 ctx->group_name = g_strdup(name);
548 ctx->user_name = g_strdup(who);
549 payload->destroy = sipe_group_context_destroy;
550 payload->data = ctx;
552 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, soap_name, sip->contacts_delta++);
553 send_soap_request_with_cb(sipe_private, NULL, body, process_add_group_response, payload);
554 g_free(body);
557 static void
558 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
559 time_t calculate_from);
561 static int
562 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
564 static const char*
565 sipe_get_status_by_availability(int avail,
566 char** activity);
568 static void
569 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
570 const char *status_id,
571 const char *message,
572 time_t do_not_publish[]);
574 static void
575 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
576 struct sipe_buddy *sbuddy,
577 const char *status_id)
579 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
580 time_t cal_avail_since;
581 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
582 int avail;
583 gchar *self_uri;
585 if (!sbuddy) return;
587 if (cal_status < SIPE_CAL_NO_DATA) {
588 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
589 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
592 /* scheduled Cal update call */
593 if (!status_id) {
594 status_id = sbuddy->last_non_cal_status_id;
595 g_free(sbuddy->activity);
596 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
599 if (!status_id) {
600 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
601 sbuddy->name ? sbuddy->name : "" );
602 return;
605 /* adjust to calendar status */
606 if (cal_status != SIPE_CAL_NO_DATA) {
607 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
609 if (cal_status == SIPE_CAL_BUSY
610 && cal_avail_since > sbuddy->user_avail_since
611 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
613 status_id = SIPE_STATUS_ID_BUSY;
614 g_free(sbuddy->activity);
615 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
617 avail = sipe_get_availability_by_status(status_id, NULL);
619 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
620 if (cal_avail_since > sbuddy->activity_since) {
621 if (cal_status == SIPE_CAL_OOF
622 && avail >= 15000) /* 12000 in 2007 */
624 g_free(sbuddy->activity);
625 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
630 /* then set status_id actually */
631 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
632 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
634 /* set our account state to the one in roaming (including calendar info) */
635 self_uri = sip_uri_self(sipe_private);
636 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
637 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
638 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
641 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
642 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
644 g_free(self_uri);
647 static void
648 sipe_got_user_status(struct sipe_core_private *sipe_private,
649 const char* uri,
650 const char *status_id)
652 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
653 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
655 if (!sbuddy) return;
657 /* Check if on 2005 system contact's calendar,
658 * then set/preserve it.
660 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
661 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
662 } else {
663 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
667 static void
668 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
669 struct sipe_buddy *sbuddy,
670 struct sipe_core_private *sipe_private)
672 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
676 * Updates contact's status
677 * based on their calendar information.
679 * Applicability: 2005 systems
681 static void
682 update_calendar_status(struct sipe_core_private *sipe_private,
683 SIPE_UNUSED_PARAMETER void *unused)
685 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
686 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
688 /* repeat scheduling */
689 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
693 * Schedules process of contacts' status update
694 * based on their calendar information.
695 * Should be scheduled to the beginning of every
696 * 15 min interval, like:
697 * 13:00, 13:15, 13:30, 13:45, etc.
699 * Applicability: 2005 systems
701 static void
702 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
703 time_t calculate_from)
705 int interval = 15*60;
706 /** start of the beginning of closest 15 min interval. */
707 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
709 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
710 asctime(localtime(&calculate_from)));
711 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
712 asctime(localtime(&next_start)));
714 sipe_schedule_seconds(sipe_private,
715 "<+2005-cal-status>",
716 NULL,
717 next_start - time(NULL),
718 update_calendar_status,
719 NULL);
723 * Schedules process of self status publish
724 * based on own calendar information.
725 * Should be scheduled to the beginning of every
726 * 15 min interval, like:
727 * 13:00, 13:15, 13:30, 13:45, etc.
729 * Applicability: 2007+ systems
731 static void
732 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
733 time_t calculate_from)
735 int interval = 5*60;
736 /** start of the beginning of closest 5 min interval. */
737 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
739 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
740 asctime(localtime(&calculate_from)));
741 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
742 asctime(localtime(&next_start)));
744 sipe_schedule_seconds(sipe_private,
745 "<+2007-cal-status>",
746 NULL,
747 next_start - time(NULL),
748 publish_calendar_status_self,
749 NULL);
752 static void sipe_subscribe_resource_uri(const char *name,
753 SIPE_UNUSED_PARAMETER gpointer value,
754 gchar **resources_uri)
756 gchar *tmp = *resources_uri;
757 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
758 g_free(tmp);
761 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
763 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
764 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
765 gchar *tmp = *resources_uri;
767 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
769 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
770 g_free(tmp);
774 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
775 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
776 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
777 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
778 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
781 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
782 gchar *resources_uri,
783 gchar *to)
785 gchar *contact = get_contact(sipe_private);
786 gchar *request;
787 gchar *content;
788 gchar *require = "";
789 gchar *accept = "";
790 gchar *autoextend = "";
791 gchar *content_type;
793 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
794 require = ", categoryList";
795 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
796 content_type = "application/msrtc-adrl-categorylist+xml";
797 content = g_strdup_printf(
798 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
799 "<action name=\"subscribe\" id=\"63792024\">\n"
800 "<adhocList>\n%s</adhocList>\n"
801 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
802 "<category name=\"calendarData\"/>\n"
803 "<category name=\"contactCard\"/>\n"
804 "<category name=\"note\"/>\n"
805 "<category name=\"state\"/>\n"
806 "</categoryList>\n"
807 "</action>\n"
808 "</batchSub>", sipe_private->username, resources_uri);
809 } else {
810 autoextend = "Supported: com.microsoft.autoextend\r\n";
811 content_type = "application/adrl+xml";
812 content = g_strdup_printf(
813 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
814 "<create xmlns=\"\">\n%s</create>\n"
815 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
817 g_free(resources_uri);
819 request = g_strdup_printf(
820 "Require: adhoclist%s\r\n"
821 "Supported: eventlist\r\n"
822 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
823 "Supported: ms-piggyback-first-notify\r\n"
824 "%sSupported: ms-benotify\r\n"
825 "Proxy-Require: ms-benotify\r\n"
826 "Event: presence\r\n"
827 "Content-Type: %s\r\n"
828 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
829 g_free(contact);
831 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
833 g_free(content);
834 g_free(to);
835 g_free(request);
838 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
839 SIPE_UNUSED_PARAMETER void *unused)
841 gchar *to = sip_uri_self(sipe_private);
842 gchar *resources_uri = g_strdup("");
843 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
844 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
845 } else {
846 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
849 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
852 struct presence_batched_routed {
853 gchar *host;
854 GSList *buddies;
857 static void sipe_subscribe_presence_batched_routed_free(void *payload)
859 struct presence_batched_routed *data = payload;
860 GSList *buddies = data->buddies;
861 while (buddies) {
862 g_free(buddies->data);
863 buddies = buddies->next;
865 g_slist_free(data->buddies);
866 g_free(data->host);
867 g_free(payload);
870 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
871 void *payload)
873 struct presence_batched_routed *data = payload;
874 GSList *buddies = data->buddies;
875 gchar *resources_uri = g_strdup("");
876 while (buddies) {
877 gchar *tmp = resources_uri;
878 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
879 g_free(tmp);
880 buddies = buddies->next;
882 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
883 g_strdup(data->host));
887 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
888 * The user sends a single SUBSCRIBE request to the subscribed contact.
889 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
893 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
894 void *buddy_name)
896 gchar *to = sip_uri((char *)buddy_name);
897 gchar *tmp = get_contact(sipe_private);
898 gchar *request;
899 gchar *content = NULL;
900 gchar *autoextend = "";
901 gchar *content_type = "";
902 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
903 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
905 if (sbuddy) sbuddy->just_added = FALSE;
907 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
908 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
909 } else {
910 autoextend = "Supported: com.microsoft.autoextend\r\n";
913 request = g_strdup_printf(
914 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
915 "Supported: ms-piggyback-first-notify\r\n"
916 "%s%sSupported: ms-benotify\r\n"
917 "Proxy-Require: ms-benotify\r\n"
918 "Event: presence\r\n"
919 "Contact: %s\r\n", autoextend, content_type, tmp);
921 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
922 content = g_strdup_printf(
923 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
924 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
925 "<resource uri=\"%s\"%s\n"
926 "</adhocList>\n"
927 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
928 "<category name=\"calendarData\"/>\n"
929 "<category name=\"contactCard\"/>\n"
930 "<category name=\"note\"/>\n"
931 "<category name=\"state\"/>\n"
932 "</categoryList>\n"
933 "</action>\n"
934 "</batchSub>", sipe_private->username, to, context);
937 g_free(tmp);
939 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
941 g_free(content);
942 g_free(to);
943 g_free(request);
946 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
948 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
950 if (!purple_status_is_active(status))
951 return;
953 if (account->gc) {
954 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
955 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
957 if (sip) {
958 gchar *action_name;
959 gchar *tmp;
960 time_t now = time(NULL);
961 const char *status_id = purple_status_get_id(status);
962 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
963 sipe_activity activity = sipe_get_activity_by_token(status_id);
964 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
966 /* when other point of presence clears note, but we are keeping
967 * state if OOF note.
969 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
970 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
971 do_not_publish = FALSE;
974 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
975 status_id, (int)sip->do_not_publish[activity], (int)now);
977 sip->do_not_publish[activity] = 0;
978 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
979 status_id, (int)sip->do_not_publish[activity]);
981 if (do_not_publish)
983 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
984 return;
987 g_free(sip->status);
988 sip->status = g_strdup(status_id);
990 /* hack to escape apostrof before comparison */
991 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
993 /* this will preserve OOF flag as well */
994 if (!sipe_strequal(tmp, sip->note)) {
995 sip->is_oof_note = FALSE;
996 g_free(sip->note);
997 sip->note = g_strdup(note);
998 sip->note_since = time(NULL);
1000 g_free(tmp);
1002 /* schedule 2 sec to capture idle flag */
1003 action_name = g_strdup_printf("<%s>", "+set-status");
1004 sipe_schedule_seconds(sipe_private,
1005 action_name,
1006 NULL,
1007 SIPE_IDLE_SET_DELAY,
1008 send_presence_status,
1009 NULL);
1010 g_free(action_name);
1015 void
1016 sipe_set_idle(PurpleConnection * gc,
1017 int interval)
1019 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
1021 if (gc) {
1022 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1023 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1025 if (sip) {
1026 sip->idle_switch = time(NULL);
1027 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
1032 void
1033 sipe_group_buddy(PurpleConnection *gc,
1034 const char *who,
1035 const char *old_group_name,
1036 const char *new_group_name)
1038 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1039 struct sipe_buddy * buddy = g_hash_table_lookup(sipe_private->buddies, who);
1040 struct sipe_group * old_group = NULL;
1041 struct sipe_group * new_group;
1043 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
1044 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1046 if(!buddy) { // buddy not in roaming list
1047 return;
1050 if (old_group_name) {
1051 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
1053 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
1055 if (old_group) {
1056 buddy->groups = g_slist_remove(buddy->groups, old_group);
1057 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
1060 if (!new_group) {
1061 sipe_group_create(sipe_private, new_group_name, who);
1062 } else {
1063 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1064 sipe_core_group_set_user(SIPE_CORE_PUBLIC, who);
1068 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1070 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1072 /* libpurple can call us with undefined buddy or group */
1073 if (buddy && group) {
1074 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1076 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1077 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
1078 purple_blist_rename_buddy(buddy, buddy_name);
1079 g_free(buddy_name);
1081 /* Prepend sip: if needed */
1082 if (!g_str_has_prefix(buddy->name, "sip:")) {
1083 gchar *buf = sip_uri_from_name(buddy->name);
1084 purple_blist_rename_buddy(buddy, buf);
1085 g_free(buf);
1088 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
1089 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
1090 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
1091 b->name = g_strdup(buddy->name);
1092 b->just_added = TRUE;
1093 g_hash_table_insert(sipe_private->buddies, b->name, b);
1094 sipe_group_buddy(gc, b->name, NULL, group->name);
1095 /* @TODO should go to callback */
1096 sipe_subscribe_presence_single(sipe_private,
1097 b->name);
1098 } else {
1099 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
1104 static void sipe_free_buddy(struct sipe_buddy *buddy)
1106 #ifndef _WIN32
1108 * We are calling g_hash_table_foreach_steal(). That means that no
1109 * key/value deallocation functions are called. Therefore the glib
1110 * hash code does not touch the key (buddy->name) or value (buddy)
1111 * of the to-be-deleted hash node at all. It follows that we
1113 * - MUST free the memory for the key ourselves and
1114 * - ARE allowed to do it in this function
1116 * Conclusion: glib must be broken on the Windows platform if sipe
1117 * crashes with SIGTRAP when closing. You'll have to live
1118 * with the memory leak until this is fixed.
1120 g_free(buddy->name);
1121 #endif
1122 g_free(buddy->activity);
1123 g_free(buddy->meeting_subject);
1124 g_free(buddy->meeting_location);
1125 g_free(buddy->note);
1127 g_free(buddy->cal_start_time);
1128 g_free(buddy->cal_free_busy_base64);
1129 g_free(buddy->cal_free_busy);
1130 g_free(buddy->last_non_cal_activity);
1132 sipe_cal_free_working_hours(buddy->cal_working_hours);
1134 g_free(buddy->device_name);
1135 g_slist_free(buddy->groups);
1136 g_free(buddy);
1140 * Unassociates buddy from group first.
1141 * Then see if no groups left, removes buddy completely.
1142 * Otherwise updates buddy groups on server.
1144 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1146 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1147 struct sipe_buddy *b;
1148 struct sipe_group *g = NULL;
1150 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1151 if (!buddy) return;
1153 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
1154 if (!b) return;
1156 if (group) {
1157 g = sipe_group_find_by_name(sipe_private, group->name);
1160 if (g) {
1161 b->groups = g_slist_remove(b->groups, g);
1162 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
1165 if (g_slist_length(b->groups) < 1) {
1166 gchar *action_name = sipe_utils_presence_key(buddy->name);
1167 sipe_schedule_cancel(sipe_private, action_name);
1168 g_free(action_name);
1170 g_hash_table_remove(sipe_private->buddies, buddy->name);
1172 if (b->name) {
1173 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1174 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1175 send_soap_request(sipe_private, body);
1176 g_free(body);
1179 sipe_free_buddy(b);
1180 } else {
1181 //updates groups on server
1182 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
1187 void
1188 sipe_rename_group(PurpleConnection *gc,
1189 const char *old_name,
1190 PurpleGroup *group,
1191 SIPE_UNUSED_PARAMETER GList *moved_buddies)
1193 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1194 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, old_name);
1195 if (s_group) {
1196 sipe_group_rename(sipe_private, s_group, group->name);
1197 } else {
1198 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
1202 void
1203 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1205 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1206 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, group->name);
1207 if (s_group) {
1208 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1209 gchar *body;
1210 SIPE_DEBUG_INFO("Deleting group %s", group->name);
1211 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1212 send_soap_request(sipe_private, body);
1213 g_free(body);
1215 sip->groups = g_slist_remove(sip->groups, s_group);
1216 g_free(s_group->name);
1217 g_free(s_group);
1218 } else {
1219 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
1224 * A callback for g_hash_table_foreach
1226 static void
1227 sipe_buddy_subscribe_cb(char *buddy_name,
1228 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1229 struct sipe_core_private *sipe_private)
1231 gchar *action_name = sipe_utils_presence_key(buddy_name);
1232 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
1233 guint time_range = (g_hash_table_size(sipe_private->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1234 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
1236 sipe_schedule_mseconds(sipe_private,
1237 action_name,
1238 g_strdup(buddy_name),
1239 timeout,
1240 sipe_subscribe_presence_single,
1241 g_free);
1242 g_free(action_name);
1246 * Removes entries from purple buddy list
1247 * that does not correspond ones in the roaming contact list.
1249 static void sipe_cleanup_local_blist(struct sipe_core_private *sipe_private) {
1250 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1251 GSList *buddies = purple_find_buddies(sip->account, NULL);
1252 GSList *entry = buddies;
1253 struct sipe_buddy *buddy;
1254 PurpleBuddy *b;
1255 PurpleGroup *g;
1257 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
1258 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sipe_private->buddies));
1259 while (entry) {
1260 b = entry->data;
1261 g = purple_buddy_get_group(b);
1262 buddy = g_hash_table_lookup(sipe_private->buddies, b->name);
1263 if(buddy) {
1264 gboolean in_sipe_groups = FALSE;
1265 GSList *entry2 = buddy->groups;
1266 while (entry2) {
1267 struct sipe_group *group = entry2->data;
1268 if (sipe_strequal(group->name, g->name)) {
1269 in_sipe_groups = TRUE;
1270 break;
1272 entry2 = entry2->next;
1274 if(!in_sipe_groups) {
1275 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
1276 purple_blist_remove_buddy(b);
1278 } else {
1279 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
1280 purple_blist_remove_buddy(b);
1282 entry = entry->next;
1284 g_slist_free(buddies);
1287 static int
1288 sipe_find_access_level(struct sipe_core_private *sipe_private,
1289 const gchar *type,
1290 const gchar *value,
1291 gboolean *is_group_access);
1293 static void
1294 sipe_refresh_blocked_status_cb(char *buddy_name,
1295 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1296 struct sipe_core_private *sipe_private)
1298 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1299 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1300 gboolean blocked = (container_id == 32000);
1301 gboolean blocked_in_blist = !purple_privacy_check(sip->account, buddy_name);
1303 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1304 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1306 if (blocked != blocked_in_blist) {
1307 if (blocked) {
1308 purple_privacy_deny_add(sip->account, buddy_name, TRUE);
1309 } else {
1310 purple_privacy_deny_remove(sip->account, buddy_name, TRUE);
1313 /* stupid workaround to make pidgin re-render screen to reflect our changes */
1315 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, buddy_name);
1316 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
1317 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
1319 SIPE_DEBUG_INFO_NOFORMAT("sipe_refresh_blocked_status_cb: forcefully refreshing screen.");
1320 sipe_got_user_status(sipe_private, buddy_name, purple_status_get_id(pstatus));
1326 static void
1327 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1329 g_hash_table_foreach(sipe_private->buddies,
1330 (GHFunc) sipe_refresh_blocked_status_cb,
1331 sipe_private);
1334 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1335 struct sipmsg *msg)
1337 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1338 int len = msg->bodylen;
1340 const gchar *tmp = sipmsg_find_header(msg, "Event");
1341 const sipe_xml *item;
1342 sipe_xml *isc;
1343 const gchar *contacts_delta;
1344 const sipe_xml *group_node;
1345 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1346 return FALSE;
1349 /* Convert the contact from XML to Purple Buddies */
1350 isc = sipe_xml_parse(msg->body, len);
1351 if (!isc) {
1352 return FALSE;
1355 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
1356 if (contacts_delta) {
1357 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1360 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1362 /* Parse groups */
1363 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1364 struct sipe_group * group = g_new0(struct sipe_group, 1);
1365 const char *name = sipe_xml_attribute(group_node, "name");
1367 if (g_str_has_prefix(name, "~")) {
1368 name = _("Other Contacts");
1370 group->name = g_strdup(name);
1371 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1373 sipe_group_add(sipe_private, group);
1376 // Make sure we have at least one group
1377 if (g_slist_length(sip->groups) == 0) {
1378 sipe_group_create(sipe_private, _("Other Contacts"), NULL);
1381 /* Parse contacts */
1382 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1383 const gchar *uri = sipe_xml_attribute(item, "uri");
1384 const gchar *name = sipe_xml_attribute(item, "name");
1385 gchar *buddy_name;
1386 struct sipe_buddy *buddy = NULL;
1387 gchar *tmp;
1388 gchar **item_groups;
1389 int i = 0;
1391 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1392 tmp = sip_uri_from_name(uri);
1393 buddy_name = g_ascii_strdown(tmp, -1);
1394 g_free(tmp);
1396 /* assign to group Other Contacts if nothing else received */
1397 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1398 if(is_empty(tmp)) {
1399 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1400 g_free(tmp);
1401 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1403 item_groups = g_strsplit(tmp, " ", 0);
1404 g_free(tmp);
1406 while (item_groups[i]) {
1407 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1409 // If couldn't find the right group for this contact, just put them in the first group we have
1410 if (group == NULL && g_slist_length(sip->groups) > 0) {
1411 group = sip->groups->data;
1414 if (group != NULL) {
1415 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1416 if (!b){
1417 b = purple_buddy_new(sip->account, buddy_name, uri);
1418 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1420 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1423 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
1424 if (name != NULL && strlen(name) != 0) {
1425 purple_blist_alias_buddy(b, name);
1427 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1431 if (!buddy) {
1432 buddy = g_new0(struct sipe_buddy, 1);
1433 buddy->name = g_strdup(b->name);
1434 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1436 SIPE_DEBUG_INFO("Added SIPE buddy %s", buddy->name);
1439 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1441 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
1442 } else {
1443 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1444 name);
1447 i++;
1448 } // while, contact groups
1449 g_strfreev(item_groups);
1450 g_free(buddy_name);
1452 } // for, contacts
1454 sipe_cleanup_local_blist(sipe_private);
1456 /* Add self-contact if not there yet. 2005 systems. */
1457 /* This will resemble subscription to roaming_self in 2007 systems */
1458 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1459 gchar *self_uri = sip_uri_self(sipe_private);
1460 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1462 if (!buddy) {
1463 buddy = g_new0(struct sipe_buddy, 1);
1464 buddy->name = g_strdup(self_uri);
1465 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1467 g_free(self_uri);
1470 sipe_xml_free(isc);
1472 /* subscribe to buddies */
1473 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1474 if (sip->batched_support) {
1475 sipe_subscribe_presence_batched(sipe_private, NULL);
1476 } else {
1477 g_hash_table_foreach(sipe_private->buddies,
1478 (GHFunc)sipe_buddy_subscribe_cb,
1479 sipe_private);
1481 sip->subscribed_buddies = TRUE;
1483 /* for 2005 systems schedule contacts' status update
1484 * based on their calendar information
1486 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1487 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1490 return 0;
1494 * Fires on deregistration event initiated by server.
1495 * [MS-SIPREGE] SIP extension.
1498 // 2007 Example
1500 // Content-Type: text/registration-event
1501 // subscription-state: terminated;expires=0
1502 // ms-diagnostics-public: 4141;reason="User disabled"
1504 // deregistered;event=rejected
1506 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1507 struct sipmsg *msg)
1509 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1510 gchar *event = NULL;
1511 gchar *reason = NULL;
1512 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
1513 gchar *warning;
1515 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
1516 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1518 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1519 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1520 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1521 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1522 } else {
1523 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1524 return;
1527 if (diagnostics != NULL) {
1528 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
1529 } else { // for LCS2005
1530 int error_id = 0;
1531 if (event && sipe_strcase_equal(event, "unregistered")) {
1532 error_id = 4140; // [MS-SIPREGE]
1533 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1534 reason = g_strdup(_("you are already signed in at another location"));
1535 } else if (event && sipe_strcase_equal(event, "rejected")) {
1536 error_id = 4141;
1537 reason = g_strdup(_("user disabled")); // [MS-OCER]
1538 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1539 error_id = 4142;
1540 reason = g_strdup(_("user moved")); // [MS-OCER]
1543 g_free(event);
1544 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1545 g_free(reason);
1547 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1548 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1549 warning);
1550 g_free(warning);
1554 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1555 struct sipmsg *msg)
1557 sipe_xml *xn_provision_group_list;
1558 const sipe_xml *node;
1560 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1562 /* provisionGroup */
1563 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1564 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1565 g_free(sipe_private->focus_factory_uri);
1566 sipe_private->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1567 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->focus_factory_uri=%s",
1568 sipe_private->focus_factory_uri ? sipe_private->focus_factory_uri : "");
1570 #ifdef HAVE_VV
1571 g_free(sipe_private->mras_uri);
1572 sipe_private->mras_uri = g_strstrip(sipe_xml_data(sipe_xml_child(node, "mrasUri")));
1573 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->mras_uri=%s",
1574 sipe_private->mras_uri ? sipe_private->mras_uri : "");
1576 if (sipe_private->mras_uri)
1577 sipe_media_get_av_edge_credentials(sipe_private);
1578 #endif
1579 break;
1582 sipe_xml_free(xn_provision_group_list);
1585 /** for 2005 system */
1586 static void
1587 sipe_process_provisioning(struct sipe_core_private *sipe_private,
1588 struct sipmsg *msg)
1590 sipe_xml *xn_provision;
1591 const sipe_xml *node;
1593 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
1594 if ((node = sipe_xml_child(xn_provision, "user"))) {
1595 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
1596 if ((node = sipe_xml_child(node, "line"))) {
1597 const gchar *line_uri = sipe_xml_attribute(node, "uri");
1598 const gchar *server = sipe_xml_attribute(node, "server");
1599 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
1600 sip_csta_open(sipe_private, line_uri, server);
1603 sipe_xml_free(xn_provision);
1606 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
1607 struct sipmsg *msg)
1609 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1610 const gchar *contacts_delta;
1611 sipe_xml *xml;
1613 xml = sipe_xml_parse(msg->body, msg->bodylen);
1614 if (!xml)
1616 return;
1619 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
1620 if (contacts_delta)
1622 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1625 sipe_xml_free(xml);
1628 /** MS-PRES container */
1629 struct sipe_container {
1630 guint id;
1631 guint version;
1632 GSList *members;
1634 /** MS-PRES container member */
1635 struct sipe_container_member {
1636 /** user, domain, sameEnterprise, federated, publicCloud; everyone */
1637 gchar *type;
1638 gchar *value;
1641 static void
1642 free_container_member(struct sipe_container_member *member)
1644 if (!member) return;
1646 g_free(member->type);
1647 g_free(member->value);
1648 g_free(member);
1651 static void
1652 free_container(struct sipe_container *container)
1654 GSList *entry;
1656 if (!container) return;
1658 entry = container->members;
1659 while (entry) {
1660 void *data = entry->data;
1661 entry = g_slist_remove(entry, data);
1662 free_container_member((struct sipe_container_member *)data);
1664 g_free(container);
1667 static void
1668 sipe_send_container_members_prepare(const guint container_id,
1669 const guint container_version,
1670 const gchar *action,
1671 const gchar *type,
1672 const gchar *value,
1673 char **container_xmls)
1675 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
1676 gchar *body;
1678 if (!container_xmls) return;
1680 body = g_strdup_printf(
1681 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
1682 container_id,
1683 container_version,
1684 action,
1685 type,
1686 value_str);
1687 g_free(value_str);
1689 if ((*container_xmls) == NULL) {
1690 *container_xmls = body;
1691 } else {
1692 char *tmp = *container_xmls;
1694 *container_xmls = g_strconcat(*container_xmls, body, NULL);
1695 g_free(tmp);
1696 g_free(body);
1700 static void
1701 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
1702 char *container_xmls)
1704 gchar *self;
1705 gchar *contact;
1706 gchar *hdr;
1707 gchar *body;
1709 if (!container_xmls) return;
1711 self = sip_uri_self(sipe_private);
1712 body = g_strdup_printf(
1713 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
1714 "%s"
1715 "</setContainerMembers>",
1716 container_xmls);
1718 contact = get_contact(sipe_private);
1719 hdr = g_strdup_printf("Contact: %s\r\n"
1720 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
1721 g_free(contact);
1723 sip_transport_service(sipe_private,
1724 self,
1725 hdr,
1726 body,
1727 NULL);
1729 g_free(hdr);
1730 g_free(body);
1731 g_free(self);
1735 * Finds locally stored MS-PRES container member
1737 static struct sipe_container_member *
1738 sipe_find_container_member(struct sipe_container *container,
1739 const gchar *type,
1740 const gchar *value)
1742 struct sipe_container_member *member;
1743 GSList *entry;
1745 if (container == NULL || type == NULL) {
1746 return NULL;
1749 entry = container->members;
1750 while (entry) {
1751 member = entry->data;
1752 if (sipe_strcase_equal(member->type, type) &&
1753 sipe_strcase_equal(member->value, value))
1755 return member;
1757 entry = entry->next;
1759 return NULL;
1763 * Finds locally stored MS-PRES container by id
1765 static struct sipe_container *
1766 sipe_find_container(struct sipe_core_private *sipe_private,
1767 guint id)
1769 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1770 struct sipe_container *container;
1771 GSList *entry;
1773 if (sip == NULL) {
1774 return NULL;
1777 entry = sip->containers;
1778 while (entry) {
1779 container = entry->data;
1780 if (id == container->id) {
1781 return container;
1783 entry = entry->next;
1785 return NULL;
1788 static GSList *
1789 sipe_get_access_domains(struct sipe_core_private *sipe_private)
1791 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1792 struct sipe_container *container;
1793 struct sipe_container_member *member;
1794 GSList *entry;
1795 GSList *entry2;
1796 GSList *res = NULL;
1798 if (!sip) return NULL;
1800 entry = sip->containers;
1801 while (entry) {
1802 container = entry->data;
1804 entry2 = container->members;
1805 while (entry2) {
1806 member = entry2->data;
1807 if (sipe_strcase_equal(member->type, "domain"))
1809 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1811 entry2 = entry2->next;
1813 entry = entry->next;
1815 return res;
1819 * Returns pointer to domain part in provided Email URL
1821 * @param email an email URL. Example: first.last@hq.company.com
1822 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1824 * Doesn't allocate memory
1826 static const char *
1827 sipe_get_domain(const char *email)
1829 char *tmp;
1831 if (!email) return NULL;
1833 tmp = strstr(email, "@");
1835 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1836 return tmp+1;
1837 } else {
1838 return NULL;
1843 /* @TODO: replace with binary search for faster access? */
1844 /** source: http://support.microsoft.com/kb/897567 */
1845 static const char * const public_domains [] = {
1846 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1847 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1848 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1849 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1850 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1851 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1852 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1853 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1854 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1855 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1856 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1857 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1858 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1859 "yahoo.com",
1860 NULL};
1862 static gboolean
1863 sipe_is_public_domain(const char *domain)
1865 int i = 0;
1866 while (public_domains[i]) {
1867 if (sipe_strcase_equal(public_domains[i], domain)) {
1868 return TRUE;
1870 i++;
1872 return FALSE;
1876 * Access Levels
1877 * 32000 - Blocked
1878 * 400 - Personal
1879 * 300 - Team
1880 * 200 - Company
1881 * 100 - Public
1883 static const char *
1884 sipe_get_access_level_name(int container_id)
1886 switch(container_id) {
1887 case 32000: return _("Blocked");
1888 case 400: return _("Personal");
1889 case 300: return _("Team");
1890 case 200: return _("Company");
1891 case 100: return _("Public");
1893 return _("Unknown");
1896 static const guint containers[] = {32000, 400, 300, 200, 100};
1897 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1900 static int
1901 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1902 const gchar *type,
1903 const gchar *value)
1905 unsigned int i = 0;
1906 const gchar *value_mod = value;
1908 if (!type) return -1;
1910 if (sipe_strequal("user", type)) {
1911 value_mod = sipe_get_no_sip_uri(value);
1914 for (i = 0; i < CONTAINERS_LEN; i++) {
1915 struct sipe_container_member *member;
1916 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1917 if (!container) continue;
1919 member = sipe_find_container_member(container, type, value_mod);
1920 if (member) return containers[i];
1923 return -1;
1926 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1927 static int
1928 sipe_find_access_level(struct sipe_core_private *sipe_private,
1929 const gchar *type,
1930 const gchar *value,
1931 gboolean *is_group_access)
1933 int container_id = -1;
1935 if (sipe_strequal("user", type)) {
1936 const char *domain;
1937 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1939 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1940 if (container_id >= 0) {
1941 if (is_group_access) *is_group_access = FALSE;
1942 return container_id;
1945 domain = sipe_get_domain(no_sip_uri);
1946 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1947 if (container_id >= 0) {
1948 if (is_group_access) *is_group_access = TRUE;
1949 return container_id;
1952 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1953 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1954 if (is_group_access) *is_group_access = TRUE;
1955 return container_id;
1958 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1959 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1960 if (is_group_access) *is_group_access = TRUE;
1961 return container_id;
1964 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1965 if ((container_id >= 0)) {
1966 if (is_group_access) *is_group_access = TRUE;
1967 return container_id;
1969 } else {
1970 container_id = sipe_find_member_access_level(sipe_private, type, value);
1971 if (is_group_access) *is_group_access = FALSE;
1974 return container_id;
1978 * @param container_id a new access level. If -1 then current access level
1979 * is just removed (I.e. the member is removed from all containers).
1980 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1981 * @param value a value for member. E.g. SIP URI for "user" member type.
1983 static void
1984 sipe_change_access_level(struct sipe_core_private *sipe_private,
1985 const int container_id,
1986 const gchar *type,
1987 const gchar *value)
1989 unsigned int i;
1990 int current_container_id = -1;
1991 char *container_xmls = NULL;
1993 /* for each container: find/delete */
1994 for (i = 0; i < CONTAINERS_LEN; i++) {
1995 struct sipe_container_member *member;
1996 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1998 if (!container) continue;
2000 member = sipe_find_container_member(container, type, value);
2001 if (member) {
2002 current_container_id = containers[i];
2003 /* delete/publish current access level */
2004 if (container_id < 0 || container_id != current_container_id) {
2005 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
2006 /* remove member from our cache, to be able to recalculate AL below */
2007 container->members = g_slist_remove(container->members, member);
2008 current_container_id = -1;
2013 /* recalculate AL below */
2014 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
2016 /* assign/publish new access level */
2017 if (container_id != current_container_id && container_id >= 0) {
2018 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
2019 guint version = container ? container->version : 0;
2021 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
2024 if (container_xmls) {
2025 sipe_send_set_container_members(sipe_private, container_xmls);
2027 g_free(container_xmls);
2030 static void
2031 free_publication(struct sipe_publication *publication)
2033 g_free(publication->category);
2034 g_free(publication->cal_event_hash);
2035 g_free(publication->note);
2037 g_free(publication->working_hours_xml_str);
2038 g_free(publication->fb_start_str);
2039 g_free(publication->free_busy_base64);
2041 g_free(publication);
2044 /* key is <category><instance><container> */
2045 static gboolean
2046 sipe_is_our_publication(struct sipe_core_private *sipe_private,
2047 const gchar *key)
2049 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2050 GSList *entry;
2052 /* filling keys for our publications if not yet cached */
2053 if (!sip->our_publication_keys) {
2054 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
2055 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
2056 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
2057 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
2058 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
2059 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
2060 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
2062 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
2063 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
2064 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
2065 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
2066 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
2067 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
2068 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
2069 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
2070 SIPE_DEBUG_INFO("\tNote : %u", 0);
2071 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
2073 /* device */
2074 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2075 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2077 /* state:machineState */
2078 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2079 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2080 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2081 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2083 /* state:userState */
2084 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2085 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2086 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2087 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2089 /* state:calendarState */
2090 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2091 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2092 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2093 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2095 /* state:calendarState OOF */
2096 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2097 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2098 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2099 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2101 /* note */
2102 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2103 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2104 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2105 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2106 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2107 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2109 /* note OOF */
2110 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2111 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2112 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2113 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2114 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2115 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2117 /* calendarData:WorkingHours */
2118 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2119 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2120 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2121 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2122 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2123 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2124 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2125 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2126 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2127 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2128 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2129 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2131 /* calendarData:FreeBusy */
2132 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2133 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2134 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2135 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2136 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2137 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2138 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2139 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2140 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2141 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2142 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2143 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2145 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
2146 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2149 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
2151 entry = sip->our_publication_keys;
2152 while (entry) {
2153 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
2154 if (sipe_strequal(entry->data, key)) {
2155 return TRUE;
2157 entry = entry->next;
2159 return FALSE;
2162 /** Property names to store in blist.xml */
2163 #define ALIAS_PROP "alias"
2164 #define EMAIL_PROP "email"
2165 #define PHONE_PROP "phone"
2166 #define PHONE_DISPLAY_PROP "phone-display"
2167 #define PHONE_MOBILE_PROP "phone-mobile"
2168 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2169 #define PHONE_HOME_PROP "phone-home"
2170 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2171 #define PHONE_OTHER_PROP "phone-other"
2172 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2173 #define PHONE_CUSTOM1_PROP "phone-custom1"
2174 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2175 #define SITE_PROP "site"
2176 #define COMPANY_PROP "company"
2177 #define DEPARTMENT_PROP "department"
2178 #define TITLE_PROP "title"
2179 #define OFFICE_PROP "office"
2180 /** implies work address */
2181 #define ADDRESS_STREET_PROP "address-street"
2182 #define ADDRESS_CITY_PROP "address-city"
2183 #define ADDRESS_STATE_PROP "address-state"
2184 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2185 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
2188 * Tries to figure out user first and last name
2189 * based on Display Name and email properties.
2191 * Allocates memory - must be g_free()'d
2193 * Examples to parse:
2194 * First Last
2195 * First Last - Company Name
2196 * Last, First
2197 * Last, First M.
2198 * Last, First (C)(STP) (Company)
2199 * first.last@company.com (preprocessed as "first last")
2200 * first.last.company.com@reuters.net (preprocessed as "first last company com")
2202 * Unusable examples:
2203 * user@company.com (preprocessed as "user")
2204 * first.m.last@company.com (preprocessed as "first m last")
2205 * user.company.com@reuters.net (preprocessed as "user company com")
2207 static void
2208 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
2209 const char *uri,
2210 char **first_name,
2211 char **last_name)
2213 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2214 PurpleBuddy *p_buddy;
2215 char *display_name;
2216 const char *email;
2217 const char *first, *last;
2218 char *tmp;
2219 char **parts;
2220 gboolean has_comma = FALSE;
2222 if (!sip || !uri) return;
2224 p_buddy = purple_find_buddy(sip->account, uri);
2226 if (!p_buddy) return;
2228 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
2229 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
2231 if (!display_name && !email) return;
2233 /* if no display name, make "first last anything_else" out of email */
2234 if (email && !display_name) {
2235 display_name = g_strndup(email, strstr(email, "@") - email);
2236 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
2237 g_free(tmp);
2240 if (display_name) {
2241 has_comma = (strstr(display_name, ",") != NULL);
2242 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
2243 g_free(tmp);
2244 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
2245 g_free(tmp);
2248 parts = g_strsplit(display_name, " ", 0);
2250 if (!parts[0] || !parts[1]) {
2251 g_free(display_name);
2252 g_strfreev(parts);
2253 return;
2256 if (has_comma) {
2257 last = parts[0];
2258 first = parts[1];
2259 } else {
2260 first = parts[0];
2261 last = parts[1];
2264 if (first_name) {
2265 *first_name = g_strstrip(g_strdup(first));
2268 if (last_name) {
2269 *last_name = g_strstrip(g_strdup(last));
2272 g_free(display_name);
2273 g_strfreev(parts);
2277 * Update user information
2279 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2280 * @param property_name
2281 * @param property_value may be modified to strip white space
2283 static void
2284 sipe_update_user_info(struct sipe_core_private *sipe_private,
2285 const char *uri,
2286 const char *property_name,
2287 char *property_value)
2289 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2290 GSList *buddies, *entry;
2292 if (!property_name || strlen(property_name) == 0) return;
2294 if (property_value)
2295 property_value = g_strstrip(property_value);
2297 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
2298 while (entry) {
2299 const char *prop_str;
2300 const char *server_alias;
2301 PurpleBuddy *p_buddy = entry->data;
2303 /* for Display Name */
2304 if (sipe_strequal(property_name, ALIAS_PROP)) {
2305 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
2306 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2307 purple_blist_alias_buddy(p_buddy, property_value);
2310 server_alias = purple_buddy_get_server_alias(p_buddy);
2311 if (!is_empty(property_value) &&
2312 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2314 purple_blist_server_alias_buddy(p_buddy, property_value);
2317 /* for other properties */
2318 else {
2319 if (!is_empty(property_value)) {
2320 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
2321 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2322 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
2327 entry = entry->next;
2329 g_slist_free(buddies);
2333 * Update user phone
2334 * Suitable for both 2005 and 2007 systems.
2336 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2337 * @param phone_type
2338 * @param phone may be modified to strip white space
2339 * @param phone_display_string may be modified to strip white space
2341 static void
2342 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2343 const char *uri,
2344 const gchar *phone_type,
2345 gchar *phone,
2346 gchar *phone_display_string)
2348 const char *phone_node = PHONE_PROP; /* work phone by default */
2349 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
2351 if(!phone || strlen(phone) == 0) return;
2353 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2354 phone_node = PHONE_MOBILE_PROP;
2355 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
2356 } else if (sipe_strequal(phone_type, "home")) {
2357 phone_node = PHONE_HOME_PROP;
2358 phone_display_node = PHONE_HOME_DISPLAY_PROP;
2359 } else if (sipe_strequal(phone_type, "other")) {
2360 phone_node = PHONE_OTHER_PROP;
2361 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
2362 } else if (sipe_strequal(phone_type, "custom1")) {
2363 phone_node = PHONE_CUSTOM1_PROP;
2364 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
2367 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2368 if (phone_display_string) {
2369 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2373 void
2374 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2376 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2378 /* Do in parallel.
2379 * If failed, the branch will be disabled for subsequent calls.
2380 * Can't rely that user turned the functionality on in account settings.
2382 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2383 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2385 /* schedule repeat */
2386 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2387 "<+update-calendar>",
2388 NULL,
2389 UPDATE_CALENDAR_INTERVAL,
2390 (sipe_schedule_action)sipe_core_update_calendar,
2391 NULL);
2393 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2397 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2398 * by using standard Purple's means of signals and saved statuses.
2400 * Thus all UI elements get updated: Status Button with Note, docklet.
2401 * This is ablolutely important as both our status and note can come
2402 * inbound (roaming) or be updated programmatically (e.g. based on our
2403 * calendar data).
2405 static void
2406 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2407 const char *status_id,
2408 const char *message,
2409 time_t do_not_publish[])
2411 PurpleStatus *status = purple_account_get_active_status(account);
2412 gboolean changed = TRUE;
2414 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2415 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2417 changed = FALSE;
2420 if (purple_savedstatus_is_idleaway()) {
2421 changed = FALSE;
2424 if (changed) {
2425 PurpleSavedStatus *saved_status;
2426 const PurpleStatusType *acct_status_type =
2427 purple_status_type_find_with_id(account->status_types, status_id);
2428 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2429 sipe_activity activity = sipe_get_activity_by_token(status_id);
2431 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2432 if (saved_status) {
2433 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2436 /* If this type+message is unique then create a new transient saved status
2437 * Ref: gtkstatusbox.c
2439 if (!saved_status) {
2440 GList *tmp;
2441 GList *active_accts = purple_accounts_get_all_active();
2443 saved_status = purple_savedstatus_new(NULL, primitive);
2444 purple_savedstatus_set_message(saved_status, message);
2446 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2447 purple_savedstatus_set_substatus(saved_status,
2448 (PurpleAccount *)tmp->data, acct_status_type, message);
2450 g_list_free(active_accts);
2453 do_not_publish[activity] = time(NULL);
2454 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2455 status_id, (int)do_not_publish[activity]);
2457 /* Set the status for each account */
2458 purple_savedstatus_activate(saved_status);
2462 struct hash_table_delete_payload {
2463 GHashTable *hash_table;
2464 guint container;
2467 static void
2468 sipe_remove_category_container_publications_cb(const char *name,
2469 struct sipe_publication *publication,
2470 struct hash_table_delete_payload *payload)
2472 if (publication->container == payload->container) {
2473 g_hash_table_remove(payload->hash_table, name);
2476 static void
2477 sipe_remove_category_container_publications(GHashTable *our_publications,
2478 const char *category,
2479 guint container)
2481 struct hash_table_delete_payload payload;
2482 payload.hash_table = g_hash_table_lookup(our_publications, category);
2484 if (!payload.hash_table) return;
2486 payload.container = container;
2487 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2490 static void
2491 send_publish_category_initial(struct sipe_core_private *sipe_private);
2494 * When we receive some self (BE) NOTIFY with a new subscriber
2495 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2498 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2499 struct sipmsg *msg)
2501 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2502 gchar *contact;
2503 gchar *to;
2504 sipe_xml *xml;
2505 const sipe_xml *node;
2506 const sipe_xml *node2;
2507 char *display_name = NULL;
2508 char *uri;
2509 GSList *category_names = NULL;
2510 int aggreg_avail = 0;
2511 gboolean do_update_status = FALSE;
2512 gboolean has_note_cleaned = FALSE;
2514 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2516 xml = sipe_xml_parse(msg->body, msg->bodylen);
2517 if (!xml) return;
2519 contact = get_contact(sipe_private);
2520 to = sip_uri_self(sipe_private);
2523 /* categories */
2524 /* set list of categories participating in this XML */
2525 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2526 const gchar *name = sipe_xml_attribute(node, "name");
2527 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2529 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2530 category_names ? (int) g_slist_length(category_names) : -1);
2531 /* drop category information */
2532 if (category_names) {
2533 GSList *entry = category_names;
2534 while (entry) {
2535 GHashTable *cat_publications;
2536 const gchar *category = entry->data;
2537 entry = entry->next;
2538 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2539 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2540 if (cat_publications) {
2541 g_hash_table_remove(sip->our_publications, category);
2542 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2546 g_slist_free(category_names);
2547 /* filling our categories reflected in roaming data */
2548 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2549 const char *tmp;
2550 const gchar *name = sipe_xml_attribute(node, "name");
2551 guint container = sipe_xml_int_attribute(node, "container", -1);
2552 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2553 guint version = sipe_xml_int_attribute(node, "version", 0);
2554 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2555 sipe_utils_str_to_time(tmp) : 0;
2556 gchar *key;
2557 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2559 /* Ex. clear note: <category name="note"/> */
2560 if (container == (guint)-1) {
2561 g_free(sip->note);
2562 sip->note = NULL;
2563 do_update_status = TRUE;
2564 continue;
2567 /* Ex. clear note: <category name="note" container="200"/> */
2568 if (instance == (guint)-1) {
2569 if (container == 200) {
2570 g_free(sip->note);
2571 sip->note = NULL;
2572 do_update_status = TRUE;
2574 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2575 sipe_remove_category_container_publications(
2576 sip->our_publications, name, container);
2577 continue;
2580 /* key is <category><instance><container> */
2581 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2582 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2584 /* capture all userState publication for later clean up if required */
2585 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2586 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2588 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2589 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2590 publication->category = g_strdup(name);
2591 publication->instance = instance;
2592 publication->container = container;
2593 publication->version = version;
2595 if (!sip->user_state_publications) {
2596 sip->user_state_publications = g_hash_table_new_full(
2597 g_str_hash, g_str_equal,
2598 g_free, (GDestroyNotify)free_publication);
2600 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2601 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2602 key, version);
2606 if (sipe_is_our_publication(sipe_private, key)) {
2607 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2609 publication->category = g_strdup(name);
2610 publication->instance = instance;
2611 publication->container = container;
2612 publication->version = version;
2614 /* filling publication->availability */
2615 if (sipe_strequal(name, "state")) {
2616 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2617 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2619 if (xn_avail) {
2620 gchar *avail_str = sipe_xml_data(xn_avail);
2621 if (avail_str) {
2622 publication->availability = atoi(avail_str);
2624 g_free(avail_str);
2626 /* for calendarState */
2627 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
2628 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
2629 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
2631 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
2632 if (xn_activity) {
2633 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
2634 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
2636 event->is_meeting = TRUE;
2639 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
2640 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
2642 publication->cal_event_hash = sipe_cal_event_hash(event);
2643 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
2644 publication->cal_event_hash);
2645 sipe_cal_event_free(event);
2648 /* filling publication->note */
2649 if (sipe_strequal(name, "note")) {
2650 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
2652 if (!has_note_cleaned) {
2653 has_note_cleaned = TRUE;
2655 g_free(sip->note);
2656 sip->note = NULL;
2657 sip->note_since = publish_time;
2659 do_update_status = TRUE;
2662 g_free(publication->note);
2663 publication->note = NULL;
2664 if (xn_body) {
2665 char *tmp;
2667 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
2668 g_free(tmp);
2669 if (publish_time >= sip->note_since) {
2670 g_free(sip->note);
2671 sip->note = g_strdup(publication->note);
2672 sip->note_since = publish_time;
2673 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
2675 do_update_status = TRUE;
2680 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
2681 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
2682 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
2683 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
2684 if (xn_free_busy) {
2685 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2686 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
2688 if (xn_working_hours) {
2689 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
2693 if (!cat_publications) {
2694 cat_publications = g_hash_table_new_full(
2695 g_str_hash, g_str_equal,
2696 g_free, (GDestroyNotify)free_publication);
2697 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2698 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
2700 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2701 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
2703 g_free(key);
2705 /* aggregateState (not an our publication) from 2-nd container */
2706 if (sipe_strequal(name, "state") && container == 2) {
2707 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2709 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
2710 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2712 if (xn_avail) {
2713 gchar *avail_str = sipe_xml_data(xn_avail);
2714 if (avail_str) {
2715 aggreg_avail = atoi(avail_str);
2717 g_free(avail_str);
2720 do_update_status = TRUE;
2724 /* userProperties published by server from AD */
2725 if (!sip->csta && sipe_strequal(name, "userProperties")) {
2726 const sipe_xml *line;
2727 /* line, for Remote Call Control (RCC) */
2728 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
2729 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
2730 const gchar *line_type = sipe_xml_attribute(line, "lineType");
2731 gchar *line_uri;
2733 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
2735 line_uri = sipe_xml_data(line);
2736 if (line_uri) {
2737 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
2738 sip_csta_open(sipe_private, line_uri, line_server);
2740 g_free(line_uri);
2742 break;
2746 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
2747 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2749 /* containers */
2750 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
2751 guint id = sipe_xml_int_attribute(node, "id", 0);
2752 struct sipe_container *container = sipe_find_container(sipe_private, id);
2754 if (container) {
2755 sip->containers = g_slist_remove(sip->containers, container);
2756 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
2757 free_container(container);
2759 container = g_new0(struct sipe_container, 1);
2760 container->id = id;
2761 container->version = sipe_xml_int_attribute(node, "version", 0);
2762 sip->containers = g_slist_append(sip->containers, container);
2763 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
2765 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
2766 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2767 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
2768 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
2769 container->members = g_slist_append(container->members, member);
2770 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2771 member->type, member->value ? member->value : "");
2775 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2776 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2777 char *container_xmls = NULL;
2778 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2779 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2781 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2782 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2783 /* initial set-up to let counterparties see your status */
2784 if (sameEnterpriseAL < 0) {
2785 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2786 guint version = container ? container->version : 0;
2787 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2789 if (federatedAL < 0) {
2790 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2791 guint version = container ? container->version : 0;
2792 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2794 sip->access_level_set = TRUE;
2796 if (container_xmls) {
2797 sipe_send_set_container_members(sipe_private, container_xmls);
2799 g_free(container_xmls);
2802 /* Refresh contacts' blocked status */
2803 sipe_refresh_blocked_status(sipe_private);
2805 /* subscribers */
2806 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2807 const char *user;
2808 const char *acknowledged;
2809 gchar *hdr;
2810 gchar *body;
2812 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2813 if (!user) continue;
2814 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2815 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2816 uri = sip_uri_from_name(user);
2818 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
2820 acknowledged= sipe_xml_attribute(node, "acknowledged");
2821 if(sipe_strcase_equal(acknowledged,"false")){
2822 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2823 if (!purple_find_buddy(sip->account, uri)) {
2824 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
2827 hdr = g_strdup_printf(
2828 "Contact: %s\r\n"
2829 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2831 body = g_strdup_printf(
2832 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2833 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2834 "</setSubscribers>", user);
2836 sip_transport_service(sipe_private,
2838 hdr,
2839 body,
2840 NULL);
2841 g_free(body);
2842 g_free(hdr);
2844 g_free(display_name);
2845 g_free(uri);
2848 g_free(contact);
2849 sipe_xml_free(xml);
2851 /* Publish initial state if not yet.
2852 * Assuming this happens on initial responce to subscription to roaming-self
2853 * so we've already updated our roaming data in full.
2854 * Only for 2007+
2856 if (!sip->initial_state_published) {
2857 send_publish_category_initial(sipe_private);
2858 sipe_groupchat_init(sipe_private);
2859 sip->initial_state_published = TRUE;
2860 /* dalayed run */
2861 sipe_schedule_seconds(sipe_private,
2862 "<+update-calendar>",
2863 NULL,
2864 UPDATE_CALENDAR_DELAY,
2865 (sipe_schedule_action)sipe_core_update_calendar,
2866 NULL);
2867 do_update_status = FALSE;
2868 } else if (aggreg_avail) {
2870 g_free(sip->status);
2871 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2872 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2873 } else {
2874 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2878 if (do_update_status) {
2879 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2880 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2883 g_free(to);
2886 /* IM Session (INVITE and MESSAGE methods) */
2888 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2889 static gchar *
2890 get_end_points (struct sipe_core_private *sipe_private,
2891 struct sip_session *session)
2893 gchar *res;
2895 if (session == NULL) {
2896 return NULL;
2899 res = g_strdup_printf("<sip:%s>", sipe_private->username);
2901 SIPE_DIALOG_FOREACH {
2902 gchar *tmp = res;
2903 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2904 g_free(tmp);
2906 if (dialog->theirepid) {
2907 tmp = res;
2908 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2909 g_free(tmp);
2911 } SIPE_DIALOG_FOREACH_END;
2913 return res;
2916 static gboolean
2917 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2918 struct sipmsg *msg,
2919 SIPE_UNUSED_PARAMETER struct transaction *trans)
2921 gboolean ret = TRUE;
2923 if (msg->response != 200) {
2924 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2925 return FALSE;
2928 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2930 return ret;
2934 * Asks UA/proxy about its capabilities.
2936 static void sipe_options_request(struct sipe_core_private *sipe_private,
2937 const char *who)
2939 gchar *to = sip_uri(who);
2940 gchar *contact = get_contact(sipe_private);
2941 gchar *request = g_strdup_printf(
2942 "Accept: application/sdp\r\n"
2943 "Contact: %s\r\n", contact);
2944 g_free(contact);
2946 sip_transport_request(sipe_private,
2947 "OPTIONS",
2950 request,
2951 NULL,
2952 NULL,
2953 process_options_response);
2955 g_free(to);
2956 g_free(request);
2959 void
2960 sipe_present_info(struct sipe_core_private *sipe_private,
2961 struct sip_session *session,
2962 const gchar *message)
2964 sipe_backend_notify_message_info(SIPE_CORE_PUBLIC,
2965 session->chat_session ? session->chat_session->backend : NULL,
2966 session->with,
2967 message);
2970 void
2971 sipe_present_err(struct sipe_core_private *sipe_private,
2972 struct sip_session *session,
2973 const gchar *message)
2975 sipe_backend_notify_message_error(SIPE_CORE_PUBLIC,
2976 session->chat_session ? session->chat_session->backend : NULL,
2977 session->with,
2978 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->chat_session) {
3180 gchar *who = sip_uri_self(sipe_private);
3181 sipe_backend_chat_message(SIPE_CORE_PUBLIC,
3182 session->chat_session->backend,
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 if (session->is_groupchat) {
3324 sipe_groupchat_invite_failed(sipe_private, session);
3327 g_free(key);
3328 g_free(with);
3329 return FALSE;
3332 dialog->cseq = 0;
3333 sip_transport_ack(sipe_private, dialog);
3334 dialog->outgoing_invite = NULL;
3335 dialog->is_established = TRUE;
3337 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3338 if (referred_by) {
3339 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
3340 g_free(referred_by);
3343 /* add user to chat if it is a multiparty session */
3344 if (session->chat_session &&
3345 (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY)) {
3346 sipe_backend_chat_add(session->chat_session->backend,
3347 with,
3348 TRUE);
3351 if (session->is_groupchat) {
3352 sipe_groupchat_invite_response(sipe_private, dialog);
3355 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3356 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
3357 sipe_session_dequeue_message(session);
3360 sipe_im_process_queue(sipe_private, session);
3362 g_hash_table_remove(session->unconfirmed_messages, key);
3363 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
3364 key, g_hash_table_size(session->unconfirmed_messages));
3366 g_free(key);
3367 g_free(with);
3368 return TRUE;
3372 void
3373 sipe_invite(struct sipe_core_private *sipe_private,
3374 struct sip_session *session,
3375 const gchar *who,
3376 const gchar *msg_body,
3377 const gchar *msg_content_type,
3378 const gchar *referred_by,
3379 const gboolean is_triggered)
3381 gchar *hdr;
3382 gchar *to;
3383 gchar *contact;
3384 gchar *body;
3385 gchar *self;
3386 char *ms_text_format = NULL;
3387 gchar *roster_manager;
3388 gchar *end_points;
3389 gchar *referred_by_str;
3390 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3392 if (dialog && dialog->is_established) {
3393 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
3394 return;
3397 if (!dialog) {
3398 dialog = sipe_dialog_add(session);
3399 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3400 dialog->with = g_strdup(who);
3403 if (!(dialog->ourtag)) {
3404 dialog->ourtag = gentag();
3407 to = sip_uri(who);
3409 if (msg_body) {
3410 char *msgtext = NULL;
3411 char *base64_msg;
3412 const gchar *msgr = "";
3413 char *key;
3414 struct queued_message *message;
3415 gchar *tmp = NULL;
3417 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
3418 char *msgformat;
3419 gchar *msgr_value;
3421 sipe_parse_html(msg_body, &msgformat, &msgtext);
3422 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
3424 msgr_value = sipmsg_get_msgr_string(msgformat);
3425 g_free(msgformat);
3426 if (msgr_value) {
3427 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
3428 g_free(msgr_value);
3430 } else {
3431 msgtext = g_strdup(msg_body);
3434 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
3435 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
3436 msg_content_type ? msg_content_type : "text/plain",
3437 msgr,
3438 base64_msg);
3439 g_free(msgtext);
3440 g_free(tmp);
3441 g_free(base64_msg);
3443 message = g_new0(struct queued_message,1);
3444 message->body = g_strdup(msg_body);
3445 if (msg_content_type != NULL)
3446 message->content_type = g_strdup(msg_content_type);
3448 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3449 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3450 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
3451 key, g_hash_table_size(session->unconfirmed_messages));
3452 g_free(key);
3455 contact = get_contact(sipe_private);
3456 end_points = get_end_points(sipe_private, session);
3457 self = sip_uri_self(sipe_private);
3458 roster_manager = g_strdup_printf(
3459 "Roster-Manager: %s\r\n"
3460 "EndPoints: %s\r\n",
3461 self,
3462 end_points);
3463 referred_by_str = referred_by ?
3464 g_strdup_printf(
3465 "Referred-By: %s\r\n",
3466 referred_by)
3467 : g_strdup("");
3468 hdr = g_strdup_printf(
3469 "Supported: ms-sender\r\n"
3470 "%s"
3471 "%s"
3472 "%s"
3473 "%s"
3474 "Contact: %s\r\n%s"
3475 "Content-Type: application/sdp\r\n",
3476 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
3477 referred_by_str,
3478 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3479 is_triggered || (session->chat_session && (session->chat_session->type == SIPE_CHAT_TYPE_MULTIPARTY)) ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3480 contact,
3481 ms_text_format ? ms_text_format : "");
3482 g_free(ms_text_format);
3483 g_free(self);
3485 body = g_strdup_printf(
3486 "v=0\r\n"
3487 "o=- 0 0 IN IP4 %s\r\n"
3488 "s=session\r\n"
3489 "c=IN IP4 %s\r\n"
3490 "t=0 0\r\n"
3491 "m=%s %d sip null\r\n"
3492 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
3493 sipe_backend_network_ip_address(),
3494 sipe_backend_network_ip_address(),
3495 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
3496 sip_transport_port(sipe_private));
3498 dialog->outgoing_invite = sip_transport_request(sipe_private,
3499 "INVITE",
3502 hdr,
3503 body,
3504 dialog,
3505 process_invite_response);
3507 g_free(to);
3508 g_free(roster_manager);
3509 g_free(end_points);
3510 g_free(referred_by_str);
3511 g_free(body);
3512 g_free(hdr);
3513 g_free(contact);
3516 void
3517 sipe_convo_closed(PurpleConnection * gc, const char *who)
3519 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3521 SIPE_DEBUG_INFO("conversation with %s closed", who);
3522 sipe_session_close(sipe_private,
3523 sipe_session_find_im(sipe_private, who));
3526 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3527 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3529 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3530 struct sip_session *session;
3531 struct sip_dialog *dialog;
3532 gchar *uri = sip_uri(who);
3534 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
3536 session = sipe_session_find_or_add_im(sipe_private, uri);
3537 dialog = sipe_dialog_find(session, uri);
3539 // Queue the message
3540 sipe_session_enqueue_message(session, what, NULL);
3542 if (dialog && !dialog->outgoing_invite) {
3543 sipe_im_process_queue(sipe_private, session);
3544 } else if (!dialog || !dialog->outgoing_invite) {
3545 // Need to send the INVITE to get the outgoing dialog setup
3546 sipe_invite(sipe_private, session, uri, what, NULL, NULL, FALSE);
3549 g_free(uri);
3550 return 1;
3554 * Returns 2005-style activity and Availability.
3556 * @param status Sipe statis id.
3558 static void
3559 sipe_get_act_avail_by_status_2005(const char *status,
3560 int *activity,
3561 int *availability)
3563 int avail = 300; /* online */
3564 int act = 400; /* Available */
3566 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
3567 act = 100;
3568 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
3569 // act = 150;
3570 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
3571 act = 300;
3572 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
3573 act = 400;
3574 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
3575 // act = 500;
3576 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
3577 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
3578 act = 600;
3579 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
3580 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
3581 avail = 0; /* offline */
3582 act = 100;
3583 } else {
3584 act = 400; /* Available */
3587 if (activity) *activity = act;
3588 if (availability) *availability = avail;
3592 * [MS-SIP] 2.2.1
3594 * @param activity 2005 aggregated activity. Ex.: 600
3595 * @param availablity 2005 aggregated availablity. Ex.: 300
3597 static const char *
3598 sipe_get_status_by_act_avail_2005(const int activity,
3599 const int availablity,
3600 char **activity_desc)
3602 const char *status_id = NULL;
3603 const char *act = NULL;
3605 if (activity < 150) {
3606 status_id = SIPE_STATUS_ID_AWAY;
3607 } else if (activity < 200) {
3608 //status_id = SIPE_STATUS_ID_LUNCH;
3609 status_id = SIPE_STATUS_ID_AWAY;
3610 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
3611 } else if (activity < 300) {
3612 //status_id = SIPE_STATUS_ID_IDLE;
3613 status_id = SIPE_STATUS_ID_AWAY;
3614 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3615 } else if (activity < 400) {
3616 status_id = SIPE_STATUS_ID_BRB;
3617 } else if (activity < 500) {
3618 status_id = SIPE_STATUS_ID_AVAILABLE;
3619 } else if (activity < 600) {
3620 //status_id = SIPE_STATUS_ID_ON_PHONE;
3621 status_id = SIPE_STATUS_ID_BUSY;
3622 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
3623 } else if (activity < 700) {
3624 status_id = SIPE_STATUS_ID_BUSY;
3625 } else if (activity < 800) {
3626 status_id = SIPE_STATUS_ID_AWAY;
3627 } else {
3628 status_id = SIPE_STATUS_ID_AVAILABLE;
3631 if (availablity < 100)
3632 status_id = SIPE_STATUS_ID_OFFLINE;
3634 if (activity_desc && act) {
3635 g_free(*activity_desc);
3636 *activity_desc = g_strdup(act);
3639 return status_id;
3643 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
3645 static const char*
3646 sipe_get_status_by_availability(int avail,
3647 char** activity_desc)
3649 const char *status;
3650 const char *act = NULL;
3652 if (avail < 3000) {
3653 status = SIPE_STATUS_ID_OFFLINE;
3654 } else if (avail < 4500) {
3655 status = SIPE_STATUS_ID_AVAILABLE;
3656 } else if (avail < 6000) {
3657 //status = SIPE_STATUS_ID_IDLE;
3658 status = SIPE_STATUS_ID_AVAILABLE;
3659 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3660 } else if (avail < 7500) {
3661 status = SIPE_STATUS_ID_BUSY;
3662 } else if (avail < 9000) {
3663 //status = SIPE_STATUS_ID_BUSYIDLE;
3664 status = SIPE_STATUS_ID_BUSY;
3665 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
3666 } else if (avail < 12000) {
3667 status = SIPE_STATUS_ID_DND;
3668 } else if (avail < 15000) {
3669 status = SIPE_STATUS_ID_BRB;
3670 } else if (avail < 18000) {
3671 status = SIPE_STATUS_ID_AWAY;
3672 } else {
3673 status = SIPE_STATUS_ID_OFFLINE;
3676 if (activity_desc && act) {
3677 g_free(*activity_desc);
3678 *activity_desc = g_strdup(act);
3681 return status;
3685 * Returns 2007-style availability value
3687 * @param sipe_status_id (in)
3688 * @param activity_token (out) Must be g_free()'d after use if consumed.
3690 static int
3691 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
3693 int availability;
3694 sipe_activity activity = SIPE_ACTIVITY_UNSET;
3696 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
3697 availability = 15500;
3698 if (!activity_token || !(*activity_token)) {
3699 activity = SIPE_ACTIVITY_AWAY;
3701 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
3702 availability = 12500;
3703 activity = SIPE_ACTIVITY_BRB;
3704 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
3705 availability = 9500;
3706 activity = SIPE_ACTIVITY_DND;
3707 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
3708 availability = 6500;
3709 if (!activity_token || !(*activity_token)) {
3710 activity = SIPE_ACTIVITY_BUSY;
3712 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
3713 availability = 3500;
3714 activity = SIPE_ACTIVITY_ONLINE;
3715 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
3716 availability = 0;
3717 } else {
3718 // Offline or invisible
3719 availability = 18500;
3720 activity = SIPE_ACTIVITY_OFFLINE;
3723 if (activity_token) {
3724 *activity_token = g_strdup(sipe_activity_map[activity].token);
3726 return availability;
3729 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
3730 const gchar *data, unsigned len)
3732 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3733 const char *uri;
3734 sipe_xml *xn_categories;
3735 const sipe_xml *xn_category;
3736 const char *status = NULL;
3737 gboolean do_update_status = FALSE;
3738 gboolean has_note_cleaned = FALSE;
3739 gboolean has_free_busy_cleaned = FALSE;
3741 xn_categories = sipe_xml_parse(data, len);
3742 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
3744 for (xn_category = sipe_xml_child(xn_categories, "category");
3745 xn_category ;
3746 xn_category = sipe_xml_twin(xn_category) )
3748 const sipe_xml *xn_node;
3749 const char *tmp;
3750 const char *attrVar = sipe_xml_attribute(xn_category, "name");
3751 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
3752 sipe_utils_str_to_time(tmp) : 0;
3754 /* contactCard */
3755 if (sipe_strequal(attrVar, "contactCard"))
3757 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
3759 if (card) {
3760 const sipe_xml *node;
3761 /* identity - Display Name and email */
3762 node = sipe_xml_child(card, "identity");
3763 if (node) {
3764 char* display_name = sipe_xml_data(
3765 sipe_xml_child(node, "name/displayName"));
3766 char* email = sipe_xml_data(
3767 sipe_xml_child(node, "email"));
3769 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
3770 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
3772 g_free(display_name);
3773 g_free(email);
3775 /* company */
3776 node = sipe_xml_child(card, "company");
3777 if (node) {
3778 char* company = sipe_xml_data(node);
3779 sipe_update_user_info(sipe_private, uri, COMPANY_PROP, company);
3780 g_free(company);
3782 /* department */
3783 node = sipe_xml_child(card, "department");
3784 if (node) {
3785 char* department = sipe_xml_data(node);
3786 sipe_update_user_info(sipe_private, uri, DEPARTMENT_PROP, department);
3787 g_free(department);
3789 /* title */
3790 node = sipe_xml_child(card, "title");
3791 if (node) {
3792 char* title = sipe_xml_data(node);
3793 sipe_update_user_info(sipe_private, uri, TITLE_PROP, title);
3794 g_free(title);
3796 /* office */
3797 node = sipe_xml_child(card, "office");
3798 if (node) {
3799 char* office = sipe_xml_data(node);
3800 sipe_update_user_info(sipe_private, uri, OFFICE_PROP, office);
3801 g_free(office);
3803 /* site (url) */
3804 node = sipe_xml_child(card, "url");
3805 if (node) {
3806 char* site = sipe_xml_data(node);
3807 sipe_update_user_info(sipe_private, uri, SITE_PROP, site);
3808 g_free(site);
3810 /* phone */
3811 for (node = sipe_xml_child(card, "phone");
3812 node;
3813 node = sipe_xml_twin(node))
3815 const char *phone_type = sipe_xml_attribute(node, "type");
3816 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3817 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3819 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3821 g_free(phone);
3822 g_free(phone_display_string);
3824 /* address */
3825 for (node = sipe_xml_child(card, "address");
3826 node;
3827 node = sipe_xml_twin(node))
3829 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3830 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3831 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3832 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3833 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3834 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3836 sipe_update_user_info(sipe_private, uri, ADDRESS_STREET_PROP, street);
3837 sipe_update_user_info(sipe_private, uri, ADDRESS_CITY_PROP, city);
3838 sipe_update_user_info(sipe_private, uri, ADDRESS_STATE_PROP, state);
3839 sipe_update_user_info(sipe_private, uri, ADDRESS_ZIPCODE_PROP, zipcode);
3840 sipe_update_user_info(sipe_private, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
3842 g_free(street);
3843 g_free(city);
3844 g_free(state);
3845 g_free(zipcode);
3846 g_free(country_code);
3848 break;
3853 /* note */
3854 else if (sipe_strequal(attrVar, "note"))
3856 if (uri) {
3857 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3859 if (!has_note_cleaned) {
3860 has_note_cleaned = TRUE;
3862 g_free(sbuddy->note);
3863 sbuddy->note = NULL;
3864 sbuddy->is_oof_note = FALSE;
3865 sbuddy->note_since = publish_time;
3867 do_update_status = TRUE;
3869 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3870 /* clean up in case no 'note' element is supplied
3871 * which indicate note removal in client
3873 g_free(sbuddy->note);
3874 sbuddy->note = NULL;
3875 sbuddy->is_oof_note = FALSE;
3876 sbuddy->note_since = publish_time;
3878 xn_node = sipe_xml_child(xn_category, "note/body");
3879 if (xn_node) {
3880 char *tmp;
3881 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3882 g_free(tmp);
3883 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3884 sbuddy->note_since = publish_time;
3886 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3887 uri, sbuddy->note ? sbuddy->note : "");
3889 /* to trigger UI refresh in case no status info is supplied in this update */
3890 do_update_status = TRUE;
3894 /* state */
3895 else if(sipe_strequal(attrVar, "state"))
3897 char *tmp;
3898 int availability;
3899 const sipe_xml *xn_availability;
3900 const sipe_xml *xn_activity;
3901 const sipe_xml *xn_meeting_subject;
3902 const sipe_xml *xn_meeting_location;
3903 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3905 xn_node = sipe_xml_child(xn_category, "state");
3906 if (!xn_node) continue;
3907 xn_availability = sipe_xml_child(xn_node, "availability");
3908 if (!xn_availability) continue;
3909 xn_activity = sipe_xml_child(xn_node, "activity");
3910 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3911 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3913 tmp = sipe_xml_data(xn_availability);
3914 availability = atoi(tmp);
3915 g_free(tmp);
3917 /* activity, meeting_subject, meeting_location */
3918 if (sbuddy) {
3919 char *tmp = NULL;
3921 /* activity */
3922 g_free(sbuddy->activity);
3923 sbuddy->activity = NULL;
3924 if (xn_activity) {
3925 const char *token = sipe_xml_attribute(xn_activity, "token");
3926 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3928 /* from token */
3929 if (!is_empty(token)) {
3930 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3932 /* from custom element */
3933 if (xn_custom) {
3934 char *custom = sipe_xml_data(xn_custom);
3936 if (!is_empty(custom)) {
3937 sbuddy->activity = custom;
3938 custom = NULL;
3940 g_free(custom);
3943 /* meeting_subject */
3944 g_free(sbuddy->meeting_subject);
3945 sbuddy->meeting_subject = NULL;
3946 if (xn_meeting_subject) {
3947 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3949 if (!is_empty(meeting_subject)) {
3950 sbuddy->meeting_subject = meeting_subject;
3951 meeting_subject = NULL;
3953 g_free(meeting_subject);
3955 /* meeting_location */
3956 g_free(sbuddy->meeting_location);
3957 sbuddy->meeting_location = NULL;
3958 if (xn_meeting_location) {
3959 char *meeting_location = sipe_xml_data(xn_meeting_location);
3961 if (!is_empty(meeting_location)) {
3962 sbuddy->meeting_location = meeting_location;
3963 meeting_location = NULL;
3965 g_free(meeting_location);
3968 status = sipe_get_status_by_availability(availability, &tmp);
3969 if (sbuddy->activity && tmp) {
3970 char *tmp2 = sbuddy->activity;
3972 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
3973 g_free(tmp);
3974 g_free(tmp2);
3975 } else if (tmp) {
3976 sbuddy->activity = tmp;
3980 do_update_status = TRUE;
3982 /* calendarData */
3983 else if(sipe_strequal(attrVar, "calendarData"))
3985 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3986 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
3987 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
3989 if (sbuddy && xn_free_busy) {
3990 if (!has_free_busy_cleaned) {
3991 has_free_busy_cleaned = TRUE;
3993 g_free(sbuddy->cal_start_time);
3994 sbuddy->cal_start_time = NULL;
3996 g_free(sbuddy->cal_free_busy_base64);
3997 sbuddy->cal_free_busy_base64 = NULL;
3999 g_free(sbuddy->cal_free_busy);
4000 sbuddy->cal_free_busy = NULL;
4002 sbuddy->cal_free_busy_published = publish_time;
4005 if (publish_time >= sbuddy->cal_free_busy_published) {
4006 g_free(sbuddy->cal_start_time);
4007 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
4009 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
4010 15 : 0;
4012 g_free(sbuddy->cal_free_busy_base64);
4013 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
4015 g_free(sbuddy->cal_free_busy);
4016 sbuddy->cal_free_busy = NULL;
4018 sbuddy->cal_free_busy_published = publish_time;
4020 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);
4024 if (sbuddy && xn_working_hours) {
4025 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
4030 if (do_update_status) {
4031 if (!status) { /* no status category in this update, using contact's current status */
4032 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
4033 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
4034 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
4035 status = purple_status_get_id(pstatus);
4038 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
4039 sipe_got_user_status(sipe_private, uri, status);
4042 sipe_xml_free(xn_categories);
4045 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
4046 GSList *server,
4047 struct sipe_core_private *sipe_private)
4049 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4050 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
4051 payload->host = g_strdup(host);
4052 payload->buddies = server;
4053 sipe_subscribe_presence_batched_routed(sipe_private,
4054 payload);
4055 sipe_subscribe_presence_batched_routed_free(payload);
4058 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
4059 const gchar *data, unsigned len)
4061 sipe_xml *xn_list;
4062 const sipe_xml *xn_resource;
4063 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4064 g_free, NULL);
4065 GSList *server;
4066 gchar *host;
4068 xn_list = sipe_xml_parse(data, len);
4070 for (xn_resource = sipe_xml_child(xn_list, "resource");
4071 xn_resource;
4072 xn_resource = sipe_xml_twin(xn_resource) )
4074 const char *uri, *state;
4075 const sipe_xml *xn_instance;
4077 xn_instance = sipe_xml_child(xn_resource, "instance");
4078 if (!xn_instance) continue;
4080 uri = sipe_xml_attribute(xn_resource, "uri");
4081 state = sipe_xml_attribute(xn_instance, "state");
4082 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
4084 if (strstr(state, "resubscribe")) {
4085 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
4087 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4088 gchar *user = g_strdup(uri);
4089 host = g_strdup(poolFqdn);
4090 server = g_hash_table_lookup(servers, host);
4091 server = g_slist_append(server, user);
4092 g_hash_table_insert(servers, host, server);
4093 } else {
4094 sipe_subscribe_presence_single(sipe_private,
4095 (void *) uri);
4100 /* Send out any deferred poolFqdn subscriptions */
4101 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
4102 g_hash_table_destroy(servers);
4104 sipe_xml_free(xn_list);
4107 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
4108 const gchar *data, unsigned len)
4110 gchar *uri;
4111 gchar *getbasic;
4112 gchar *activity = NULL;
4113 sipe_xml *pidf;
4114 const sipe_xml *basicstatus = NULL, *tuple, *status;
4115 gboolean isonline = FALSE;
4116 const sipe_xml *display_name_node;
4118 pidf = sipe_xml_parse(data, len);
4119 if (!pidf) {
4120 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
4121 return;
4124 if ((tuple = sipe_xml_child(pidf, "tuple")))
4126 if ((status = sipe_xml_child(tuple, "status"))) {
4127 basicstatus = sipe_xml_child(status, "basic");
4131 if (!basicstatus) {
4132 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
4133 sipe_xml_free(pidf);
4134 return;
4137 getbasic = sipe_xml_data(basicstatus);
4138 if (!getbasic) {
4139 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
4140 sipe_xml_free(pidf);
4141 return;
4144 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
4145 if (strstr(getbasic, "open")) {
4146 isonline = TRUE;
4148 g_free(getbasic);
4150 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
4152 display_name_node = sipe_xml_child(pidf, "display-name");
4153 if (display_name_node) {
4154 char * display_name = sipe_xml_data(display_name_node);
4156 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4157 g_free(display_name);
4160 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
4161 if ((status = sipe_xml_child(tuple, "status"))) {
4162 if ((basicstatus = sipe_xml_child(status, "activities"))) {
4163 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
4164 activity = sipe_xml_data(basicstatus);
4165 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
4171 if (isonline) {
4172 const gchar * status_id = NULL;
4173 if (activity) {
4174 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
4175 status_id = SIPE_STATUS_ID_BUSY;
4176 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
4177 status_id = SIPE_STATUS_ID_AWAY;
4181 if (!status_id) {
4182 status_id = SIPE_STATUS_ID_AVAILABLE;
4185 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
4186 sipe_got_user_status(sipe_private, uri, status_id);
4187 } else {
4188 sipe_got_user_status(sipe_private, uri, SIPE_STATUS_ID_OFFLINE);
4191 g_free(activity);
4192 g_free(uri);
4193 sipe_xml_free(pidf);
4196 /** 2005 */
4197 static void
4198 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
4199 const sipe_xml *xn_userinfo)
4201 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4202 const sipe_xml *xn_states;
4204 g_free(sip->user_states);
4205 sip->user_states = NULL;
4206 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
4207 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
4209 /* this is a hack-around to remove added newline after inner element,
4210 * state in this case, where it shouldn't be.
4211 * After several use of sipe_xml_stringify, amount of added newlines
4212 * grows significantly.
4214 if (orig) {
4215 gchar c, *stripped = orig;
4216 while ((c = *orig++)) {
4217 if ((c != '\n') /* && (c != '\r') */) {
4218 *stripped++ = c;
4221 *stripped = '\0';
4225 /* Publish initial state if not yet.
4226 * Assuming this happens on initial responce to self subscription
4227 * so we've already updated our UserInfo.
4229 if (!sip->initial_state_published) {
4230 send_presence_soap(sipe_private, FALSE);
4231 /* dalayed run */
4232 sipe_schedule_seconds(sipe_private,
4233 "<+update-calendar>",
4234 NULL,
4235 UPDATE_CALENDAR_DELAY,
4236 (sipe_schedule_action) sipe_core_update_calendar,
4237 NULL);
4241 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
4242 const gchar *data, unsigned len)
4244 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4245 char *activity = NULL;
4246 const char *epid;
4247 const char *status_id = NULL;
4248 const char *name;
4249 char *uri;
4250 char *self_uri = sip_uri_self(sipe_private);
4251 int avl;
4252 int act;
4253 const char *device_name = NULL;
4254 const char *cal_start_time = NULL;
4255 const char *cal_granularity = NULL;
4256 char *cal_free_busy_base64 = NULL;
4257 struct sipe_buddy *sbuddy;
4258 const sipe_xml *node;
4259 sipe_xml *xn_presentity;
4260 const sipe_xml *xn_availability;
4261 const sipe_xml *xn_activity;
4262 const sipe_xml *xn_display_name;
4263 const sipe_xml *xn_email;
4264 const sipe_xml *xn_phone_number;
4265 const sipe_xml *xn_userinfo;
4266 const sipe_xml *xn_note;
4267 const sipe_xml *xn_oof;
4268 const sipe_xml *xn_state;
4269 const sipe_xml *xn_contact;
4270 char *note;
4271 char *free_activity;
4272 int user_avail;
4273 const char *user_avail_nil;
4274 int res_avail;
4275 time_t user_avail_since = 0;
4276 time_t activity_since = 0;
4278 /* fix for Reuters environment on Linux */
4279 if (data && strstr(data, "encoding=\"utf-16\"")) {
4280 char *tmp_data;
4281 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
4282 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
4283 g_free(tmp_data);
4284 } else {
4285 xn_presentity = sipe_xml_parse(data, len);
4288 xn_availability = sipe_xml_child(xn_presentity, "availability");
4289 xn_activity = sipe_xml_child(xn_presentity, "activity");
4290 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
4291 xn_email = sipe_xml_child(xn_presentity, "email");
4292 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
4293 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
4294 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
4295 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
4296 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
4297 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
4298 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
4299 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
4300 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
4301 note = xn_note ? sipe_xml_data(xn_note) : NULL;
4303 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
4304 user_avail = 0;
4305 user_avail_since = 0;
4308 free_activity = NULL;
4310 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
4311 uri = sip_uri_from_name(name);
4312 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
4313 epid = sipe_xml_attribute(xn_availability, "epid");
4314 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
4316 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
4317 res_avail = sipe_get_availability_by_status(status_id, NULL);
4318 if (user_avail > res_avail) {
4319 res_avail = user_avail;
4320 status_id = sipe_get_status_by_availability(user_avail, NULL);
4323 if (xn_display_name) {
4324 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
4325 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
4326 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
4327 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
4328 char *tel_uri = sip_to_tel_uri(phone_number);
4330 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4331 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
4332 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
4333 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
4335 g_free(tel_uri);
4336 g_free(phone_label);
4337 g_free(phone_number);
4338 g_free(email);
4339 g_free(display_name);
4342 if (xn_contact) {
4343 /* tel */
4344 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
4346 /* Ex.: <tel type="work">tel:+3222220000</tel> */
4347 const char *phone_type = sipe_xml_attribute(node, "type");
4348 char* phone = sipe_xml_data(node);
4350 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
4352 g_free(phone);
4356 /* devicePresence */
4357 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
4358 const sipe_xml *xn_device_name;
4359 const sipe_xml *xn_calendar_info;
4360 const sipe_xml *xn_state;
4361 char *state;
4363 /* deviceName */
4364 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
4365 xn_device_name = sipe_xml_child(node, "deviceName");
4366 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
4369 /* calendarInfo */
4370 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
4371 if (xn_calendar_info) {
4372 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
4374 if (cal_start_time) {
4375 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
4376 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
4378 if (cal_start_time_t_tmp > cal_start_time_t) {
4379 cal_start_time = cal_start_time_tmp;
4380 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4381 g_free(cal_free_busy_base64);
4382 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4384 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);
4386 } else {
4387 cal_start_time = cal_start_time_tmp;
4388 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4389 g_free(cal_free_busy_base64);
4390 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4392 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);
4396 /* state */
4397 xn_state = sipe_xml_child(node, "states/state");
4398 if (xn_state) {
4399 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
4400 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
4402 state = sipe_xml_data(xn_state);
4403 if (dev_avail_since > user_avail_since &&
4404 dev_avail >= res_avail)
4406 res_avail = dev_avail;
4407 if (!is_empty(state))
4409 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
4410 g_free(activity);
4411 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
4412 } else if (sipe_strequal(state, "presenting")) {
4413 g_free(activity);
4414 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
4415 } else {
4416 activity = state;
4417 state = NULL;
4419 activity_since = dev_avail_since;
4421 status_id = sipe_get_status_by_availability(res_avail, &activity);
4423 g_free(state);
4427 /* oof */
4428 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
4429 g_free(activity);
4430 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
4431 activity_since = 0;
4434 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
4435 if (sbuddy)
4437 g_free(sbuddy->activity);
4438 sbuddy->activity = activity;
4439 activity = NULL;
4441 sbuddy->activity_since = activity_since;
4443 sbuddy->user_avail = user_avail;
4444 sbuddy->user_avail_since = user_avail_since;
4446 g_free(sbuddy->note);
4447 sbuddy->note = NULL;
4448 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
4450 sbuddy->is_oof_note = (xn_oof != NULL);
4452 g_free(sbuddy->device_name);
4453 sbuddy->device_name = NULL;
4454 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
4456 if (!is_empty(cal_free_busy_base64)) {
4457 g_free(sbuddy->cal_start_time);
4458 sbuddy->cal_start_time = g_strdup(cal_start_time);
4460 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
4462 g_free(sbuddy->cal_free_busy_base64);
4463 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
4464 cal_free_busy_base64 = NULL;
4466 g_free(sbuddy->cal_free_busy);
4467 sbuddy->cal_free_busy = NULL;
4470 sbuddy->last_non_cal_status_id = status_id;
4471 g_free(sbuddy->last_non_cal_activity);
4472 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
4474 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
4475 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
4477 sip->is_oof_note = sbuddy->is_oof_note;
4479 g_free(sip->note);
4480 sip->note = g_strdup(sbuddy->note);
4482 sip->note_since = time(NULL);
4485 g_free(sip->status);
4486 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
4489 g_free(cal_free_busy_base64);
4490 g_free(activity);
4492 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
4493 sipe_got_user_status(sipe_private, uri, status_id);
4495 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
4496 sipe_user_info_has_updated(sipe_private, xn_userinfo);
4499 g_free(note);
4500 sipe_xml_free(xn_presentity);
4501 g_free(uri);
4502 g_free(self_uri);
4505 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
4506 const GSList *fields,
4507 const gchar *body,
4508 gsize length)
4510 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
4512 if (strstr(type,"application/rlmi+xml")) {
4513 process_incoming_notify_rlmi_resub(user_data, body, length);
4514 } else if (strstr(type, "text/xml+msrtc.pidf")) {
4515 process_incoming_notify_msrtc(user_data, body, length);
4516 } else {
4517 process_incoming_notify_rlmi(user_data, body, length);
4521 static void sipe_process_presence(struct sipe_core_private *sipe_private,
4522 struct sipmsg *msg)
4524 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4526 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
4528 if (ctype &&
4529 (strstr(ctype, "application/rlmi+xml") ||
4530 strstr(ctype, "application/msrtc-event-categories+xml")))
4532 if (strstr(ctype, "multipart"))
4534 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
4536 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4538 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
4540 else if(strstr(ctype, "application/rlmi+xml"))
4542 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
4545 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4547 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
4549 else
4551 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
4555 static void sipe_presence_timeout_mime_cb(gpointer user_data,
4556 SIPE_UNUSED_PARAMETER const GSList *fields,
4557 const gchar *body,
4558 gsize length)
4560 GSList **buddies = user_data;
4561 sipe_xml *xml = sipe_xml_parse(body, length);
4563 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
4564 const gchar *uri = sipe_xml_attribute(xml, "uri");
4565 const sipe_xml *xn_category;
4568 * automaton: presence is never expected to change
4570 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
4572 for (xn_category = sipe_xml_child(xml, "category");
4573 xn_category;
4574 xn_category = sipe_xml_twin(xn_category)) {
4575 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
4576 "contactCard")) {
4577 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
4578 if (node) {
4579 char *boolean = sipe_xml_data(node);
4580 if (sipe_strequal(boolean, "true")) {
4581 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
4582 uri);
4583 uri = NULL;
4585 g_free(boolean);
4587 break;
4591 if (uri) {
4592 *buddies = g_slist_append(*buddies, sip_uri(uri));
4596 sipe_xml_free(xml);
4599 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
4600 struct sipmsg *msg, gchar *who,
4601 int timeout)
4603 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4604 gchar *action_name = sipe_utils_presence_key(who);
4606 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
4608 if (ctype &&
4609 strstr(ctype, "multipart") &&
4610 (strstr(ctype, "application/rlmi+xml") ||
4611 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4612 GSList *buddies = NULL;
4614 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
4616 if (buddies) {
4617 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4618 payload->host = g_strdup(who);
4619 payload->buddies = buddies;
4620 sipe_schedule_seconds(sipe_private,
4621 action_name,
4622 payload,
4623 timeout,
4624 sipe_subscribe_presence_batched_routed,
4625 sipe_subscribe_presence_batched_routed_free);
4626 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
4629 } else {
4630 sipe_schedule_seconds(sipe_private,
4631 action_name,
4632 g_strdup(who),
4633 timeout,
4634 sipe_subscribe_presence_single,
4635 g_free);
4636 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
4638 g_free(action_name);
4642 * Dispatcher for all incoming subscription information
4643 * whether it comes from NOTIFY, BENOTIFY requests or
4644 * piggy-backed to subscription's OK responce.
4646 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4647 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4649 void process_incoming_notify(struct sipe_core_private *sipe_private,
4650 struct sipmsg *msg,
4651 gboolean request, gboolean benotify)
4653 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4654 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4655 const gchar *event = sipmsg_find_header(msg, "Event");
4656 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4658 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
4660 /* implicit subscriptions */
4661 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4662 sipe_process_imdn(sipe_private, msg);
4665 if (event) {
4666 /* for one off subscriptions (send with Expire: 0) */
4667 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
4669 sipe_process_provisioning_v2(sipe_private, msg);
4671 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
4673 sipe_process_provisioning(sipe_private, msg);
4675 else if (sipe_strcase_equal(event, "presence"))
4677 sipe_process_presence(sipe_private, msg);
4679 else if (sipe_strcase_equal(event, "registration-notify"))
4681 sipe_process_registration_notify(sipe_private, msg);
4684 if (!subscription_state || strstr(subscription_state, "active"))
4686 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
4688 sipe_process_roaming_contacts(sipe_private, msg);
4690 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
4692 sipe_process_roaming_self(sipe_private, msg);
4694 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
4696 sipe_process_roaming_acl(sipe_private, msg);
4698 else if (sipe_strcase_equal(event, "presence.wpending"))
4700 sipe_process_presence_wpending(sipe_private, msg);
4702 else if (sipe_strcase_equal(event, "conference"))
4704 sipe_process_conference(sipe_private, msg);
4709 /* The server sends status 'terminated' */
4710 if (subscription_state && strstr(subscription_state, "terminated") ) {
4711 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4712 gchar *key = sipe_utils_subscription_key(event, who);
4714 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
4715 g_free(who);
4717 sipe_subscriptions_remove(sipe_private, key);
4718 g_free(key);
4721 if (!request && event) {
4722 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
4723 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4724 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
4726 if (timeout) {
4727 /* 2 min ahead of expiration */
4728 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
4730 if (sipe_strcase_equal(event, "presence.wpending") &&
4731 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4733 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4734 sipe_schedule_seconds(sipe_private,
4735 action_name,
4736 NULL,
4737 timeout,
4738 sipe_subscribe_presence_wpending,
4739 NULL);
4740 g_free(action_name);
4742 else if (sipe_strcase_equal(event, "presence") &&
4743 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4745 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
4746 gchar *action_name = sipe_utils_presence_key(who);
4748 if (sip->batched_support) {
4749 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
4751 else {
4752 sipe_schedule_seconds(sipe_private,
4753 action_name,
4754 g_strdup(who),
4755 timeout,
4756 sipe_subscribe_presence_single,
4757 g_free);
4758 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
4760 g_free(action_name);
4761 g_free(who);
4766 /* The client responses on received a NOTIFY message */
4767 if (request && !benotify)
4769 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
4774 * Whether user manually changed status or
4775 * it was changed automatically due to user
4776 * became inactive/active again
4778 static gboolean
4779 sipe_is_user_state(struct sipe_core_private *sipe_private)
4781 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4782 gboolean res;
4783 time_t now = time(NULL);
4785 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
4786 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
4788 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
4790 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
4791 return res;
4794 static void
4795 send_presence_soap0(struct sipe_core_private *sipe_private,
4796 gboolean do_publish_calendar,
4797 gboolean do_reset_status)
4799 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4800 struct sipe_calendar* cal = sip->cal;
4801 int availability = 0;
4802 int activity = 0;
4803 gchar *body;
4804 gchar *tmp;
4805 gchar *tmp2 = NULL;
4806 gchar *res_note = NULL;
4807 gchar *res_oof = NULL;
4808 const gchar *note_pub = NULL;
4809 gchar *states = NULL;
4810 gchar *calendar_data = NULL;
4811 gchar *epid = get_epid(sipe_private);
4812 time_t now = time(NULL);
4813 gchar *since_time_str = sipe_utils_time_to_str(now);
4814 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4815 const char *user_input;
4816 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4818 if (oof_note && sip->note) {
4819 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4820 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4823 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4825 if (!sip->initial_state_published ||
4826 do_reset_status)
4828 g_free(sip->status);
4829 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4832 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4834 /* Note */
4835 if (pub_oof) {
4836 note_pub = oof_note;
4837 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4838 cal->published = TRUE;
4839 } else if (sip->note) {
4840 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4841 g_free(sip->note);
4842 sip->note = NULL;
4843 sip->is_oof_note = FALSE;
4844 sip->note_since = 0;
4845 } else {
4846 note_pub = sip->note;
4847 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4851 if (note_pub)
4853 /* to protocol internal plain text format */
4854 tmp = sipe_backend_markup_strip_html(note_pub);
4855 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4856 g_free(tmp);
4859 /* User State */
4860 if (!do_reset_status) {
4861 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4863 gchar *activity_token = NULL;
4864 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4866 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4867 avail_2007,
4868 since_time_str,
4869 epid,
4870 activity_token);
4871 g_free(activity_token);
4873 else /* preserve existing publication */
4875 if (sip->user_states) {
4876 states = g_strdup(sip->user_states);
4879 } else {
4880 /* do nothing - then User state will be erased */
4882 sip->initial_state_published = TRUE;
4884 /* CalendarInfo */
4885 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4887 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4888 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4889 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4890 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4891 fb_start_str,
4892 free_busy_base64);
4893 g_free(fb_start_str);
4894 g_free(free_busy_base64);
4897 user_input = (sipe_is_user_state(sipe_private) ||
4898 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4899 "active" : "idle";
4901 /* forming resulting XML */
4902 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4903 sipe_private->username,
4904 availability,
4905 activity,
4906 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4907 res_note ? res_note : "",
4908 res_oof ? res_oof : "",
4909 states ? states : "",
4910 calendar_data ? calendar_data : "",
4911 epid,
4912 since_time_str,
4913 since_time_str,
4914 user_input);
4915 g_free(tmp);
4916 g_free(tmp2);
4917 g_free(res_note);
4918 g_free(states);
4919 g_free(calendar_data);
4921 send_soap_request(sipe_private, body);
4923 g_free(body);
4924 g_free(since_time_str);
4925 g_free(epid);
4928 void
4929 send_presence_soap(struct sipe_core_private *sipe_private,
4930 gboolean do_publish_calendar)
4932 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4936 static gboolean
4937 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4938 struct sipmsg *msg,
4939 struct transaction *trans)
4941 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4943 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4944 sipe_xml *xml;
4945 const sipe_xml *node;
4946 gchar *fault_code;
4947 GHashTable *faults;
4948 int index_our;
4949 gboolean has_device_publication = FALSE;
4951 xml = sipe_xml_parse(msg->body, msg->bodylen);
4953 /* test if version mismatch fault */
4954 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
4955 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
4956 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
4957 g_free(fault_code);
4958 sipe_xml_free(xml);
4959 return TRUE;
4961 g_free(fault_code);
4963 /* accumulating information about faulty versions */
4964 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
4965 for (node = sipe_xml_child(xml, "details/operation");
4966 node;
4967 node = sipe_xml_twin(node))
4969 const gchar *index = sipe_xml_attribute(node, "index");
4970 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
4972 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
4973 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
4975 sipe_xml_free(xml);
4977 /* here we are parsing own request to figure out what publication
4978 * referensed here only by index went wrong
4980 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
4982 /* publication */
4983 for (node = sipe_xml_child(xml, "publications/publication"),
4984 index_our = 1; /* starts with 1 - our first publication */
4985 node;
4986 node = sipe_xml_twin(node), index_our++)
4988 gchar *idx = g_strdup_printf("%d", index_our);
4989 const gchar *curVersion = g_hash_table_lookup(faults, idx);
4990 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
4991 g_free(idx);
4993 if (sipe_strequal("device", categoryName)) {
4994 has_device_publication = TRUE;
4997 if (curVersion) { /* fault exist on this index */
4998 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4999 const gchar *container = sipe_xml_attribute(node, "container");
5000 const gchar *instance = sipe_xml_attribute(node, "instance");
5001 /* key is <category><instance><container> */
5002 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
5003 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
5005 if (category) {
5006 struct sipe_publication *publication =
5007 g_hash_table_lookup(category, key);
5009 SIPE_DEBUG_INFO("key is %s", key);
5011 if (publication) {
5012 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
5013 key, curVersion, publication->version);
5014 /* updating publication's version to the correct one */
5015 publication->version = atoi(curVersion);
5017 } else {
5018 /* We somehow lost this category from our publications... */
5019 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
5020 publication->category = g_strdup(categoryName);
5021 publication->instance = atoi(instance);
5022 publication->container = atoi(container);
5023 publication->version = atoi(curVersion);
5024 category = g_hash_table_new_full(g_str_hash, g_str_equal,
5025 g_free, (GDestroyNotify)free_publication);
5026 g_hash_table_insert(category, g_strdup(key), publication);
5027 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
5028 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
5030 g_free(key);
5033 sipe_xml_free(xml);
5034 g_hash_table_destroy(faults);
5036 /* rebublishing with right versions */
5037 if (has_device_publication) {
5038 send_publish_category_initial(sipe_private);
5039 } else {
5040 send_presence_status(sipe_private, NULL);
5043 return TRUE;
5047 * Returns 'device' XML part for publication.
5048 * Must be g_free'd after use.
5050 static gchar *
5051 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
5053 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5054 gchar *uri;
5055 gchar *doc;
5056 gchar *epid = get_epid(sipe_private);
5057 gchar *uuid = generateUUIDfromEPID(epid);
5058 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
5059 /* key is <category><instance><container> */
5060 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
5061 struct sipe_publication *publication =
5062 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
5064 g_free(key);
5065 g_free(epid);
5067 uri = sip_uri_self(sipe_private);
5068 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
5069 device_instance,
5070 publication ? publication->version : 0,
5071 uuid,
5072 uri,
5073 "00:00:00+01:00", /* @TODO make timezone real*/
5074 g_get_host_name()
5077 g_free(uri);
5078 g_free(uuid);
5080 return doc;
5084 * A service method - use
5085 * - send_publish_get_category_state_machine and
5086 * - send_publish_get_category_state_user instead.
5087 * Must be g_free'd after use.
5089 static gchar *
5090 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
5091 gboolean is_user_state)
5093 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5094 int availability = sipe_get_availability_by_status(sip->status, NULL);
5095 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
5096 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
5097 /* key is <category><instance><container> */
5098 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5099 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5100 struct sipe_publication *publication_2 =
5101 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5102 struct sipe_publication *publication_3 =
5103 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5105 g_free(key_2);
5106 g_free(key_3);
5108 if (publication_2 && (publication_2->availability == availability))
5110 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
5111 return NULL; /* nothing to update */
5114 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
5115 instance,
5116 publication_2 ? publication_2->version : 0,
5117 availability,
5118 instance,
5119 publication_3 ? publication_3->version : 0,
5120 availability);
5124 * Only Busy and OOF calendar event are published.
5125 * Different instances are used for that.
5127 * Must be g_free'd after use.
5129 static gchar *
5130 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
5131 struct sipe_cal_event *event,
5132 const char *uri,
5133 int cal_satus)
5135 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5136 gchar *start_time_str;
5137 int availability = 0;
5138 gchar *res;
5139 gchar *tmp = NULL;
5140 guint instance = (cal_satus == SIPE_CAL_OOF) ?
5141 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
5142 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
5144 /* key is <category><instance><container> */
5145 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5146 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5147 struct sipe_publication *publication_2 =
5148 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5149 struct sipe_publication *publication_3 =
5150 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5152 g_free(key_2);
5153 g_free(key_3);
5155 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
5156 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5157 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
5158 return NULL;
5161 if (event &&
5162 publication_3 &&
5163 (publication_3->availability == availability) &&
5164 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
5166 g_free(tmp);
5167 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5168 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
5169 return NULL; /* nothing to update */
5171 g_free(tmp);
5173 if (event &&
5174 (event->cal_status == SIPE_CAL_BUSY ||
5175 event->cal_status == SIPE_CAL_OOF))
5177 gchar *availability_xml_str = NULL;
5178 gchar *activity_xml_str = NULL;
5180 if (event->cal_status == SIPE_CAL_BUSY) {
5181 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
5184 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
5185 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5186 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
5187 "minAvailability=\"6500\"",
5188 "maxAvailability=\"8999\"");
5189 } else if (event->cal_status == SIPE_CAL_OOF) {
5190 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5191 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
5192 "minAvailability=\"12000\"",
5193 "");
5195 start_time_str = sipe_utils_time_to_str(event->start_time);
5197 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
5198 instance,
5199 publication_2 ? publication_2->version : 0,
5200 uri,
5201 start_time_str,
5202 availability_xml_str ? availability_xml_str : "",
5203 activity_xml_str ? activity_xml_str : "",
5204 event->subject ? event->subject : "",
5205 event->location ? event->location : "",
5207 instance,
5208 publication_3 ? publication_3->version : 0,
5209 uri,
5210 start_time_str,
5211 availability_xml_str ? availability_xml_str : "",
5212 activity_xml_str ? activity_xml_str : "",
5213 event->subject ? event->subject : "",
5214 event->location ? event->location : ""
5216 g_free(start_time_str);
5217 g_free(availability_xml_str);
5218 g_free(activity_xml_str);
5221 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
5223 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
5224 instance,
5225 publication_2 ? publication_2->version : 0,
5227 instance,
5228 publication_3 ? publication_3->version : 0
5232 return res;
5236 * Returns 'machineState' XML part for publication.
5237 * Must be g_free'd after use.
5239 static gchar *
5240 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
5242 return sipe_publish_get_category_state(sipe_private, FALSE);
5246 * Returns 'userState' XML part for publication.
5247 * Must be g_free'd after use.
5249 static gchar *
5250 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
5252 return sipe_publish_get_category_state(sipe_private, TRUE);
5256 * Returns 'note' XML part for publication.
5257 * Must be g_free'd after use.
5259 * Protocol format for Note is plain text.
5261 * @param note a note in Sipe internal HTML format
5262 * @param note_type either personal or OOF
5264 static gchar *
5265 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
5266 const char *note, /* html */
5267 const char *note_type,
5268 time_t note_start,
5269 time_t note_end)
5271 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5272 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
5273 /* key is <category><instance><container> */
5274 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
5275 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
5276 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
5278 struct sipe_publication *publication_note_200 =
5279 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
5280 struct sipe_publication *publication_note_300 =
5281 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
5282 struct sipe_publication *publication_note_400 =
5283 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
5285 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
5286 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
5287 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
5288 char *res, *tmp1, *tmp2, *tmp3;
5289 char *start_time_attr;
5290 char *end_time_attr;
5292 g_free(tmp);
5293 tmp = NULL;
5294 g_free(key_note_200);
5295 g_free(key_note_300);
5296 g_free(key_note_400);
5298 /* we even need to republish empty note */
5299 if (sipe_strequal(n1, n2))
5301 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
5302 g_free(n1);
5303 return NULL; /* nothing to update */
5306 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
5307 g_free(tmp);
5308 tmp = NULL;
5309 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
5310 g_free(tmp);
5312 if (n1) {
5313 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5314 instance,
5315 200,
5316 publication_note_200 ? publication_note_200->version : 0,
5317 note_type,
5318 start_time_attr ? start_time_attr : "",
5319 end_time_attr ? end_time_attr : "",
5320 n1);
5322 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5323 instance,
5324 300,
5325 publication_note_300 ? publication_note_300->version : 0,
5326 note_type,
5327 start_time_attr ? start_time_attr : "",
5328 end_time_attr ? end_time_attr : "",
5329 n1);
5331 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5332 instance,
5333 400,
5334 publication_note_400 ? publication_note_400->version : 0,
5335 note_type,
5336 start_time_attr ? start_time_attr : "",
5337 end_time_attr ? end_time_attr : "",
5338 n1);
5339 } else {
5340 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5341 "note",
5342 instance,
5343 200,
5344 publication_note_200 ? publication_note_200->version : 0,
5345 "static");
5346 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5347 "note",
5348 instance,
5349 300,
5350 publication_note_200 ? publication_note_200->version : 0,
5351 "static");
5352 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5353 "note",
5354 instance,
5355 400,
5356 publication_note_200 ? publication_note_200->version : 0,
5357 "static");
5359 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
5361 g_free(start_time_attr);
5362 g_free(end_time_attr);
5363 g_free(tmp1);
5364 g_free(tmp2);
5365 g_free(tmp3);
5366 g_free(n1);
5368 return res;
5372 * Returns 'calendarData' XML part with WorkingHours for publication.
5373 * Must be g_free'd after use.
5375 static gchar *
5376 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
5378 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5379 struct sipe_calendar* cal = sip->cal;
5381 /* key is <category><instance><container> */
5382 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
5383 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
5384 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
5385 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
5386 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
5387 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
5389 struct sipe_publication *publication_cal_1 =
5390 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5391 struct sipe_publication *publication_cal_100 =
5392 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5393 struct sipe_publication *publication_cal_200 =
5394 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5395 struct sipe_publication *publication_cal_300 =
5396 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5397 struct sipe_publication *publication_cal_400 =
5398 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5399 struct sipe_publication *publication_cal_32000 =
5400 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5402 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
5403 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
5405 g_free(key_cal_1);
5406 g_free(key_cal_100);
5407 g_free(key_cal_200);
5408 g_free(key_cal_300);
5409 g_free(key_cal_400);
5410 g_free(key_cal_32000);
5412 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
5413 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
5414 return NULL;
5417 if (sipe_strequal(n1, n2))
5419 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
5420 return NULL; /* nothing to update */
5423 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
5424 /* 1 */
5425 publication_cal_1 ? publication_cal_1->version : 0,
5426 cal->email,
5427 cal->working_hours_xml_str,
5428 /* 100 - Public */
5429 publication_cal_100 ? publication_cal_100->version : 0,
5430 /* 200 - Company */
5431 publication_cal_200 ? publication_cal_200->version : 0,
5432 cal->email,
5433 cal->working_hours_xml_str,
5434 /* 300 - Team */
5435 publication_cal_300 ? publication_cal_300->version : 0,
5436 cal->email,
5437 cal->working_hours_xml_str,
5438 /* 400 - Personal */
5439 publication_cal_400 ? publication_cal_400->version : 0,
5440 cal->email,
5441 cal->working_hours_xml_str,
5442 /* 32000 - Blocked */
5443 publication_cal_32000 ? publication_cal_32000->version : 0
5448 * Returns 'calendarData' XML part with FreeBusy for publication.
5449 * Must be g_free'd after use.
5451 static gchar *
5452 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
5454 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5455 struct sipe_calendar* cal = sip->cal;
5456 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
5457 char *fb_start_str;
5458 char *free_busy_base64;
5459 const char *st;
5460 const char *fb;
5461 char *res;
5463 /* key is <category><instance><container> */
5464 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
5465 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
5466 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
5467 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
5468 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
5469 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
5471 struct sipe_publication *publication_cal_1 =
5472 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5473 struct sipe_publication *publication_cal_100 =
5474 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5475 struct sipe_publication *publication_cal_200 =
5476 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5477 struct sipe_publication *publication_cal_300 =
5478 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5479 struct sipe_publication *publication_cal_400 =
5480 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5481 struct sipe_publication *publication_cal_32000 =
5482 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5484 g_free(key_cal_1);
5485 g_free(key_cal_100);
5486 g_free(key_cal_200);
5487 g_free(key_cal_300);
5488 g_free(key_cal_400);
5489 g_free(key_cal_32000);
5491 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
5492 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
5493 return NULL;
5496 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
5497 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
5499 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
5500 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
5502 /* we will rebuplish the same data to refresh publication time,
5503 * so if data from multiple sources, most recent will be choosen
5505 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
5507 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
5508 // g_free(fb_start_str);
5509 // g_free(free_busy_base64);
5510 // return NULL; /* nothing to update */
5513 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
5514 /* 1 */
5515 cal_data_instance,
5516 publication_cal_1 ? publication_cal_1->version : 0,
5517 /* 100 - Public */
5518 cal_data_instance,
5519 publication_cal_100 ? publication_cal_100->version : 0,
5520 /* 200 - Company */
5521 cal_data_instance,
5522 publication_cal_200 ? publication_cal_200->version : 0,
5523 cal->email,
5524 fb_start_str,
5525 free_busy_base64,
5526 /* 300 - Team */
5527 cal_data_instance,
5528 publication_cal_300 ? publication_cal_300->version : 0,
5529 cal->email,
5530 fb_start_str,
5531 free_busy_base64,
5532 /* 400 - Personal */
5533 cal_data_instance,
5534 publication_cal_400 ? publication_cal_400->version : 0,
5535 cal->email,
5536 fb_start_str,
5537 free_busy_base64,
5538 /* 32000 - Blocked */
5539 cal_data_instance,
5540 publication_cal_32000 ? publication_cal_32000->version : 0
5543 g_free(fb_start_str);
5544 g_free(free_busy_base64);
5545 return res;
5548 static void send_presence_publish(struct sipe_core_private *sipe_private,
5549 const char *publications)
5551 gchar *uri;
5552 gchar *doc;
5553 gchar *tmp;
5554 gchar *hdr;
5556 uri = sip_uri_self(sipe_private);
5557 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
5558 uri,
5559 publications);
5561 tmp = get_contact(sipe_private);
5562 hdr = g_strdup_printf("Contact: %s\r\n"
5563 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
5565 sip_transport_service(sipe_private,
5566 uri,
5567 hdr,
5568 doc,
5569 process_send_presence_category_publish_response);
5571 g_free(tmp);
5572 g_free(hdr);
5573 g_free(uri);
5574 g_free(doc);
5577 static void
5578 send_publish_category_initial(struct sipe_core_private *sipe_private)
5580 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5581 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
5582 gchar *pub_machine;
5583 gchar *publications;
5585 g_free(sip->status);
5586 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
5588 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
5589 publications = g_strdup_printf("%s%s",
5590 pub_device,
5591 pub_machine ? pub_machine : "");
5592 g_free(pub_device);
5593 g_free(pub_machine);
5595 send_presence_publish(sipe_private, publications);
5596 g_free(publications);
5599 static void
5600 send_presence_category_publish(struct sipe_core_private *sipe_private)
5602 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5603 gchar *pub_state = sipe_is_user_state(sipe_private) ?
5604 sipe_publish_get_category_state_user(sipe_private) :
5605 sipe_publish_get_category_state_machine(sipe_private);
5606 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
5607 sip->note,
5608 sip->is_oof_note ? "OOF" : "personal",
5611 gchar *publications;
5613 if (!pub_state && !pub_note) {
5614 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
5615 return;
5618 publications = g_strdup_printf("%s%s",
5619 pub_state ? pub_state : "",
5620 pub_note ? pub_note : "");
5622 g_free(pub_state);
5623 g_free(pub_note);
5625 send_presence_publish(sipe_private, publications);
5626 g_free(publications);
5630 * Publishes self status
5631 * based on own calendar information.
5633 * For 2007+
5635 void
5636 publish_calendar_status_self(struct sipe_core_private *sipe_private,
5637 SIPE_UNUSED_PARAMETER void *unused)
5639 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5640 struct sipe_cal_event* event = NULL;
5641 gchar *pub_cal_working_hours = NULL;
5642 gchar *pub_cal_free_busy = NULL;
5643 gchar *pub_calendar = NULL;
5644 gchar *pub_calendar2 = NULL;
5645 gchar *pub_oof_note = NULL;
5646 const gchar *oof_note;
5647 time_t oof_start = 0;
5648 time_t oof_end = 0;
5650 if (!sip->cal) {
5651 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
5652 return;
5655 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
5656 if (sip->cal->cal_events) {
5657 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
5660 if (!event) {
5661 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
5662 } else {
5663 char *desc = sipe_cal_event_describe(event);
5664 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
5665 g_free(desc);
5668 /* Logic
5669 if OOF
5670 OOF publish, Busy clean
5671 ilse if Busy
5672 OOF clean, Busy publish
5673 else
5674 OOF clean, Busy clean
5676 if (event && event->cal_status == SIPE_CAL_OOF) {
5677 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
5678 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5679 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
5680 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5681 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
5682 } else {
5683 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5684 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5687 oof_note = sipe_ews_get_oof_note(sip->cal);
5688 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
5689 oof_start = sip->cal->oof_start;
5690 oof_end = sip->cal->oof_end;
5692 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
5694 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
5695 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
5697 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
5698 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
5699 } else {
5700 gchar *publications = g_strdup_printf("%s%s%s%s%s",
5701 pub_cal_working_hours ? pub_cal_working_hours : "",
5702 pub_cal_free_busy ? pub_cal_free_busy : "",
5703 pub_calendar ? pub_calendar : "",
5704 pub_calendar2 ? pub_calendar2 : "",
5705 pub_oof_note ? pub_oof_note : "");
5707 send_presence_publish(sipe_private, publications);
5708 g_free(publications);
5711 g_free(pub_cal_working_hours);
5712 g_free(pub_cal_free_busy);
5713 g_free(pub_calendar);
5714 g_free(pub_calendar2);
5715 g_free(pub_oof_note);
5717 /* repeat scheduling */
5718 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
5721 static void send_presence_status(struct sipe_core_private *sipe_private,
5722 SIPE_UNUSED_PARAMETER void *unused)
5724 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5725 PurpleStatus * status = purple_account_get_active_status(sip->account);
5727 if (!status) return;
5729 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
5730 purple_status_get_id(status) ? purple_status_get_id(status) : "",
5731 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
5733 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5734 send_presence_category_publish(sipe_private);
5735 } else {
5736 send_presence_soap(sipe_private, FALSE);
5740 static guint sipe_ht_hash_nick(const char *nick)
5742 char *lc = g_utf8_strdown(nick, -1);
5743 guint bucket = g_str_hash(lc);
5744 g_free(lc);
5746 return bucket;
5749 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5751 char *nick1_norm = NULL;
5752 char *nick2_norm = NULL;
5753 gboolean equal;
5755 if (nick1 == NULL && nick2 == NULL) return TRUE;
5756 if (nick1 == NULL || nick2 == NULL ||
5757 !g_utf8_validate(nick1, -1, NULL) ||
5758 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
5760 nick1_norm = g_utf8_casefold(nick1, -1);
5761 nick2_norm = g_utf8_casefold(nick2, -1);
5762 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
5763 g_free(nick2_norm);
5764 g_free(nick1_norm);
5766 return equal;
5769 /* temporary function */
5770 void sipe_purple_setup(struct sipe_core_public *sipe_public,
5771 PurpleConnection *gc)
5773 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5774 sip->gc = gc;
5775 sip->account = purple_connection_get_account(gc);
5778 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
5779 const gchar *login_domain,
5780 const gchar *login_account,
5781 const gchar *password,
5782 const gchar *email,
5783 const gchar *email_url,
5784 const gchar **errmsg)
5786 struct sipe_core_private *sipe_private;
5787 struct sipe_account_data *sip;
5788 gchar **user_domain;
5790 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
5792 /* ensure that sign-in name doesn't contain invalid characters */
5793 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
5794 *errmsg = _("SIP Exchange user name contains invalid characters");
5795 return NULL;
5798 /* ensure that sign-in name format is name@domain */
5799 if (!strchr(signin_name, '@') ||
5800 g_str_has_prefix(signin_name, "@") ||
5801 g_str_has_suffix(signin_name, "@")) {
5802 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5803 return NULL;
5806 /* ensure that email format is name@domain (if provided) */
5807 if (!is_empty(email) &&
5808 (!strchr(email, '@') ||
5809 g_str_has_prefix(email, "@") ||
5810 g_str_has_suffix(email, "@")))
5812 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5813 return NULL;
5816 /* ensure that user name doesn't contain spaces */
5817 user_domain = g_strsplit(signin_name, "@", 2);
5818 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5819 if (strchr(user_domain[0], ' ') != NULL) {
5820 g_strfreev(user_domain);
5821 *errmsg = _("SIP Exchange user name contains whitespace");
5822 return NULL;
5825 /* ensure that email_url is in proper format if enabled (if provided).
5826 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5827 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5829 if (!is_empty(email_url)) {
5830 char *tmp = g_ascii_strdown(email_url, -1);
5831 if (!g_str_has_prefix(tmp, "https://"))
5833 g_free(tmp);
5834 g_strfreev(user_domain);
5835 *errmsg = _("Email services URL should be valid if provided\n"
5836 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5837 "Example: https://domino.corp.com/maildatabase.nsf");
5838 return NULL;
5840 g_free(tmp);
5843 sipe_private = g_new0(struct sipe_core_private, 1);
5844 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5845 sip->subscribed_buddies = FALSE;
5846 sip->initial_state_published = FALSE;
5847 sipe_private->username = g_strdup(signin_name);
5848 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5849 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5850 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5851 sip->password = g_strdup(password);
5852 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5853 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5854 g_strfreev(user_domain);
5856 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5857 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5858 g_free, (GDestroyNotify)g_hash_table_destroy);
5859 sipe_subscriptions_init(sipe_private);
5860 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5862 return((struct sipe_core_public *)sipe_private);
5865 static void
5866 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private);
5868 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5870 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5872 g_free(sipe_private->epid);
5873 sipe_private->epid = NULL;
5875 sip_transport_disconnect(sipe_private);
5877 sipe_schedule_cancel_all(sipe_private);
5879 if (sip->allow_events) {
5880 GSList *entry = sip->allow_events;
5881 while (entry) {
5882 g_free(entry->data);
5883 entry = entry->next;
5886 g_slist_free(sip->allow_events);
5888 if (sip->containers) {
5889 GSList *entry = sip->containers;
5890 while (entry) {
5891 free_container((struct sipe_container *)entry->data);
5892 entry = entry->next;
5895 g_slist_free(sip->containers);
5897 /* libpurple memory leak workaround */
5898 sipe_blist_menu_free_containers(sipe_private);
5900 if (sipe_private->contact)
5901 g_free(sipe_private->contact);
5902 sipe_private->contact = NULL;
5903 if (sip->regcallid)
5904 g_free(sip->regcallid);
5905 sip->regcallid = NULL;
5907 if (sipe_private->focus_factory_uri)
5908 g_free(sipe_private->focus_factory_uri);
5909 sipe_private->focus_factory_uri = NULL;
5911 if (sip->cal) {
5912 sipe_cal_calendar_free(sip->cal);
5914 sip->cal = NULL;
5916 sipe_groupchat_free(sipe_private);
5920 * A callback for g_hash_table_foreach_remove
5922 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5923 SIPE_UNUSED_PARAMETER gpointer user_data)
5925 sipe_free_buddy((struct sipe_buddy *) buddy);
5927 /* We must return TRUE as the key/value have already been deleted */
5928 return(TRUE);
5931 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5933 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5936 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5937 SIPE_UNUSED_PARAMETER void *user_data)
5939 PurpleAccount *acct = purple_connection_get_account(gc);
5940 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5941 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5942 if (conv == NULL)
5943 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5944 purple_conversation_present(conv);
5945 g_free(id);
5948 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5949 SIPE_UNUSED_PARAMETER void *user_data)
5952 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5953 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5956 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
5957 struct sipmsg *msg,
5958 SIPE_UNUSED_PARAMETER struct transaction *trans)
5960 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5961 PurpleNotifySearchResults *results;
5962 PurpleNotifySearchColumn *column;
5963 sipe_xml *searchResults;
5964 const sipe_xml *mrow;
5965 int match_count = 0;
5966 gboolean more = FALSE;
5967 gchar *secondary;
5969 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
5971 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
5972 if (!searchResults) {
5973 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
5974 return FALSE;
5977 results = purple_notify_searchresults_new();
5979 if (results == NULL) {
5980 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
5981 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
5983 sipe_xml_free(searchResults);
5984 return FALSE;
5987 column = purple_notify_searchresults_column_new(_("User name"));
5988 purple_notify_searchresults_column_add(results, column);
5990 column = purple_notify_searchresults_column_new(_("Name"));
5991 purple_notify_searchresults_column_add(results, column);
5993 column = purple_notify_searchresults_column_new(_("Company"));
5994 purple_notify_searchresults_column_add(results, column);
5996 column = purple_notify_searchresults_column_new(_("Country"));
5997 purple_notify_searchresults_column_add(results, column);
5999 column = purple_notify_searchresults_column_new(_("Email"));
6000 purple_notify_searchresults_column_add(results, column);
6002 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
6003 GList *row = NULL;
6005 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
6006 row = g_list_append(row, g_strdup(uri_parts[1]));
6007 g_strfreev(uri_parts);
6009 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
6010 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
6011 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
6012 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
6014 purple_notify_searchresults_row_add(results, row);
6015 match_count++;
6018 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
6019 char *data = sipe_xml_data(mrow);
6020 more = (g_strcasecmp(data, "true") == 0);
6021 g_free(data);
6024 secondary = g_strdup_printf(
6025 dngettext(PACKAGE_NAME,
6026 "Found %d contact%s:",
6027 "Found %d contacts%s:", match_count),
6028 match_count, more ? _(" (more matched your query)") : "");
6030 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
6031 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
6032 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
6034 g_free(secondary);
6035 sipe_xml_free(searchResults);
6036 return TRUE;
6039 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6041 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
6042 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
6043 unsigned i = 0;
6045 if (!attrs) return;
6047 do {
6048 PurpleRequestField *field = entries->data;
6049 const char *id = purple_request_field_get_id(field);
6050 const char *value = purple_request_field_string_get_value(field);
6052 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
6054 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
6055 } while ((entries = g_list_next(entries)) != NULL);
6056 attrs[i] = NULL;
6058 if (i > 0) {
6059 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6060 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6061 gchar *query = g_strjoinv(NULL, attrs);
6062 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
6063 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
6064 send_soap_request_with_cb(sipe_private, domain_uri, body,
6065 process_search_contact_response, NULL);
6066 g_free(domain_uri);
6067 g_free(body);
6068 g_free(query);
6071 g_strfreev(attrs);
6074 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
6075 gpointer value,
6076 GString* str)
6078 struct sipe_publication *publication = value;
6080 g_string_append_printf( str,
6081 SIPE_PUB_XML_PUBLICATION_CLEAR,
6082 publication->category,
6083 publication->instance,
6084 publication->container,
6085 publication->version,
6086 "static");
6089 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
6091 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6092 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
6093 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
6095 GString* str = g_string_new(NULL);
6096 gchar *publications;
6098 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
6099 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
6100 return;
6103 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
6104 publications = g_string_free(str, FALSE);
6106 send_presence_publish(sipe_private, publications);
6107 g_free(publications);
6109 else /* 2005 */
6111 send_presence_soap0(sipe_private, FALSE, TRUE);
6115 /** for Access levels menu */
6116 #define INDENT_FMT " %s"
6118 /** Member is directly placed to access level container.
6119 * For example SIP URI of user is in the container.
6121 #define INDENT_MARKED_FMT "* %s"
6123 /** Member is indirectly belong to access level container.
6124 * For example 'sameEnterprise' is in the container and user
6125 * belongs to that same enterprise.
6127 #define INDENT_MARKED_INHERITED_FMT "= %s"
6129 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
6130 const gchar *name,
6131 const gchar *status_name,
6132 gboolean is_online)
6134 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6135 gchar *note = NULL;
6136 gboolean is_oof_note = FALSE;
6137 gchar *activity = NULL;
6138 gchar *calendar = NULL;
6139 gchar *meeting_subject = NULL;
6140 gchar *meeting_location = NULL;
6141 gchar *access_text = NULL;
6142 GSList *info = NULL;
6144 #define SIPE_ADD_BUDDY_INFO(l, t) \
6146 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
6147 sbi->label = (l); \
6148 sbi->text = (t); \
6149 info = g_slist_append(info, sbi); \
6152 if (sipe_public) { //happens on pidgin exit
6153 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
6154 if (sbuddy) {
6155 note = sbuddy->note;
6156 is_oof_note = sbuddy->is_oof_note;
6157 activity = sbuddy->activity;
6158 calendar = sipe_cal_get_description(sbuddy);
6159 meeting_subject = sbuddy->meeting_subject;
6160 meeting_location = sbuddy->meeting_location;
6162 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6163 gboolean is_group_access = FALSE;
6164 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
6165 const char *access_level = sipe_get_access_level_name(container_id);
6166 access_text = is_group_access ?
6167 g_strdup(access_level) :
6168 g_strdup_printf(INDENT_MARKED_FMT, access_level);
6172 //Layout
6173 if (is_online)
6175 gchar *status_str = g_strdup(activity ? activity : status_name);
6177 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
6179 if (is_online && !is_empty(calendar))
6181 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
6182 calendar = NULL;
6184 g_free(calendar);
6185 if (!is_empty(meeting_location))
6187 SIPE_ADD_BUDDY_INFO(_("Meeting in"), g_strdup(meeting_location));
6189 if (!is_empty(meeting_subject))
6191 SIPE_ADD_BUDDY_INFO(_("Meeting about"), g_strdup(meeting_subject));
6193 if (note)
6195 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
6196 SIPE_ADD_BUDDY_INFO(is_oof_note ? _("Out of office note") : _("Note"),
6197 g_strdup_printf("<i>%s</i>", note));
6199 if (access_text) {
6200 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
6203 return(info);
6206 static PurpleBuddy *
6207 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6209 PurpleBuddy *clone;
6210 const gchar *server_alias, *email;
6211 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6213 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6215 purple_blist_add_buddy(clone, NULL, group, NULL);
6217 server_alias = purple_buddy_get_server_alias(buddy);
6218 if (server_alias) {
6219 purple_blist_server_alias_buddy(clone, server_alias);
6222 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6223 if (email) {
6224 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
6227 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6228 //for UI to update;
6229 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6230 return clone;
6233 static void
6234 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6236 PurpleBuddy *buddy, *b;
6237 PurpleConnection *gc;
6238 PurpleGroup * group = purple_find_group(group_name);
6240 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6242 buddy = (PurpleBuddy *)node;
6244 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
6245 gc = purple_account_get_connection(buddy->account);
6247 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6248 if (!b){
6249 purple_blist_add_buddy_clone(group, buddy);
6252 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6255 static void
6256 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6258 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6260 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
6262 /* 2007+ conference */
6263 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
6265 sipe_conf_add(sipe_private, buddy->name);
6267 else /* 2005- multiparty chat */
6269 gchar *self = sip_uri_self(sipe_private);
6270 struct sip_session *session;
6272 session = sipe_session_add_chat(sipe_private,
6273 TRUE,
6274 NULL);
6275 session->roster_manager = self;
6277 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
6278 session->chat_session,
6279 session->chat_session->title,
6280 self);
6282 sipe_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
6287 * For 2007+ conference only.
6289 static void
6290 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy,
6291 struct sipe_chat_session *chat_session)
6293 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6294 struct sip_session *session;
6296 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
6297 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_session->title);
6299 session = sipe_session_find_chat(sipe_private, chat_session);
6301 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
6305 * For 2007+ conference only.
6307 static void
6308 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy,
6309 struct sipe_chat_session *chat_session)
6311 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6312 struct sip_session *session;
6314 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
6315 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_session->title);
6317 session = sipe_session_find_chat(sipe_private, chat_session);
6319 sipe_conf_delete_user(sipe_private, session, buddy->name);
6322 static void
6323 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy,
6324 struct sipe_chat_session *chat_session)
6326 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6327 struct sip_session *session;
6329 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
6330 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_session->title);
6332 session = sipe_session_find_chat(sipe_private, chat_session);
6334 sipe_invite_to_chat(sipe_private, session, buddy->name);
6337 static void
6338 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
6340 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6342 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
6343 if (phone) {
6344 char *tel_uri = sip_to_tel_uri(phone);
6346 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
6347 sip_csta_make_call(sipe_private, tel_uri);
6349 g_free(tel_uri);
6353 static void
6354 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
6356 /** Translators: replace with URL to localized page
6357 * If it doesn't exist copy the original URL */
6358 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
6361 static void
6362 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6364 const gchar *email;
6365 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
6367 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6368 if (email)
6370 char *command_line = g_strdup_printf(
6371 #ifdef _WIN32
6372 "cmd /c start"
6373 #else
6374 "xdg-email"
6375 #endif
6376 " mailto:%s", email);
6377 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
6379 g_spawn_command_line_async(command_line, NULL);
6380 g_free(command_line);
6382 else
6384 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
6388 static void
6389 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
6390 struct sipe_container *container)
6392 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6393 struct sipe_container_member *member;
6395 if (!container || !container->members) return;
6397 member = ((struct sipe_container_member *)container->members->data);
6399 if (!member->type) return;
6401 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
6402 container->id, member->type, member->value ? member->value : "");
6404 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
6407 static GList *
6408 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6409 const char* uri);
6412 * A menu which appear when right-clicking on buddy in contact list.
6414 GList *
6415 sipe_buddy_menu(PurpleBuddy *buddy)
6417 PurpleBlistNode *g_node;
6418 PurpleGroup *gr_parent;
6419 PurpleMenuAction *act;
6420 GList *menu = NULL;
6421 GList *menu_groups = NULL;
6422 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6423 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6424 const char *email;
6425 gchar *self = sip_uri_self(sipe_private);
6427 SIPE_SESSION_FOREACH {
6428 if (!sipe_strcase_equal(self, buddy->name) && session->chat_session)
6430 struct sipe_chat_session *chat_session = session->chat_session;
6431 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
6433 if (sipe_backend_chat_find(chat_session->backend, buddy->name))
6435 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
6437 if (is_conf
6438 && !sipe_backend_chat_is_operator(chat_session->backend, buddy->name) /* Not conf OP */
6439 && conf_op) /* We are a conf OP */
6441 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
6442 chat_session->title);
6443 act = purple_menu_action_new(label,
6444 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6445 chat_session, NULL);
6446 g_free(label);
6447 menu = g_list_prepend(menu, act);
6450 if (is_conf
6451 && conf_op) /* We are a conf OP */
6453 gchar *label = g_strdup_printf(_("Remove from '%s'"),
6454 chat_session->title);
6455 act = purple_menu_action_new(label,
6456 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6457 chat_session, NULL);
6458 g_free(label);
6459 menu = g_list_prepend(menu, act);
6462 else
6464 if (!is_conf
6465 || (is_conf && !session->locked))
6467 gchar *label = g_strdup_printf(_("Invite to '%s'"),
6468 chat_session->title);
6469 act = purple_menu_action_new(label,
6470 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6471 chat_session, NULL);
6472 g_free(label);
6473 menu = g_list_prepend(menu, act);
6477 } SIPE_SESSION_FOREACH_END;
6479 act = purple_menu_action_new(_("New chat"),
6480 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6481 NULL, NULL);
6482 menu = g_list_prepend(menu, act);
6484 if (sip->csta && !sip->csta->line_status) {
6485 const char *phone;
6486 const char *phone_disp_str;
6487 gchar *tmp = NULL;
6488 /* work phone */
6489 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
6490 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
6491 if (phone) {
6492 gchar *label = g_strdup_printf(_("Work %s"),
6493 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6494 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6495 g_free(tmp);
6496 tmp = NULL;
6497 g_free(label);
6498 menu = g_list_prepend(menu, act);
6501 /* mobile phone */
6502 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
6503 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
6504 if (phone) {
6505 gchar *label = g_strdup_printf(_("Mobile %s"),
6506 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6507 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6508 g_free(tmp);
6509 tmp = NULL;
6510 g_free(label);
6511 menu = g_list_prepend(menu, act);
6514 /* home phone */
6515 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
6516 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
6517 if (phone) {
6518 gchar *label = g_strdup_printf(_("Home %s"),
6519 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6520 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6521 g_free(tmp);
6522 tmp = NULL;
6523 g_free(label);
6524 menu = g_list_prepend(menu, act);
6527 /* other phone */
6528 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
6529 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
6530 if (phone) {
6531 gchar *label = g_strdup_printf(_("Other %s"),
6532 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6533 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6534 g_free(tmp);
6535 tmp = NULL;
6536 g_free(label);
6537 menu = g_list_prepend(menu, act);
6540 /* custom1 phone */
6541 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
6542 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
6543 if (phone) {
6544 gchar *label = g_strdup_printf(_("Custom1 %s"),
6545 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6546 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6547 g_free(tmp);
6548 tmp = NULL;
6549 g_free(label);
6550 menu = g_list_prepend(menu, act);
6554 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6555 if (email) {
6556 act = purple_menu_action_new(_("Send email..."),
6557 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6558 NULL, NULL);
6559 menu = g_list_prepend(menu, act);
6562 /* Access Level */
6563 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6564 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
6566 act = purple_menu_action_new(_("Access level"),
6567 NULL,
6568 NULL, menu_access_levels);
6569 menu = g_list_prepend(menu, act);
6572 /* Copy to */
6573 gr_parent = purple_buddy_get_group(buddy);
6574 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6575 PurpleGroup *group;
6577 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6578 continue;
6580 group = (PurpleGroup *)g_node;
6581 if (group == gr_parent)
6582 continue;
6584 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6585 continue;
6587 act = purple_menu_action_new(purple_group_get_name(group),
6588 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6589 group->name, NULL);
6590 menu_groups = g_list_prepend(menu_groups, act);
6592 menu_groups = g_list_reverse(menu_groups);
6594 act = purple_menu_action_new(_("Copy to"),
6595 NULL,
6596 NULL, menu_groups);
6597 menu = g_list_prepend(menu, act);
6599 menu = g_list_reverse(menu);
6601 g_free(self);
6602 return menu;
6605 static void
6606 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6608 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6609 const char *domain = purple_request_fields_get_string(fields, "access_domain");
6610 int index = purple_request_fields_get_choice(fields, "container_id");
6611 /* move Blocked first */
6612 int i = (index == 4) ? 0 : index + 1;
6613 int container_id = containers[i];
6615 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
6617 sipe_change_access_level(sipe_private, container_id, "domain", domain);
6620 static void
6621 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
6623 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6624 PurpleAccount *account = sip->account;
6625 PurpleConnection *gc = sip->gc;
6626 PurpleRequestFields *fields;
6627 PurpleRequestFieldGroup *g;
6628 PurpleRequestField *f;
6630 fields = purple_request_fields_new();
6632 g = purple_request_field_group_new(NULL);
6633 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
6634 purple_request_field_set_required(f, TRUE);
6635 purple_request_field_group_add_field(g, f);
6637 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
6638 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
6639 purple_request_field_choice_add(f, _("Team"));
6640 purple_request_field_choice_add(f, _("Company"));
6641 purple_request_field_choice_add(f, _("Public"));
6642 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
6643 purple_request_field_choice_set_default_value(f, 3); /* index */
6644 purple_request_field_set_required(f, TRUE);
6645 purple_request_field_group_add_field(g, f);
6647 purple_request_fields_add_group(fields, g);
6649 purple_request_fields(gc, _("Add new domain"),
6650 _("Add new domain"), NULL, fields,
6651 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
6652 _("Cancel"), NULL,
6653 account, NULL, NULL, gc);
6656 static void
6657 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
6659 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
6663 * Workaround for missing libpurple API to release resources allocated
6664 * during blist_node_menu() callback. See also:
6666 * <http://developer.pidgin.im/ticket/12597>
6668 * We remember all memory blocks in a list and deallocate them when
6670 * - the next time we enter the callback, or
6671 * - the account is disconnected
6673 * That means that after the buddy menu has been closed we have unused
6674 * resources but at least we don't leak them anymore...
6676 static void
6677 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private)
6679 GSList *entry = sipe_private->blist_menu_containers;
6680 while (entry) {
6681 free_container(entry->data);
6682 entry = entry->next;
6684 g_slist_free(sipe_private->blist_menu_containers);
6685 sipe_private->blist_menu_containers = NULL;
6688 static void
6689 sipe_blist_menu_remember_container(struct sipe_core_private *sipe_private,
6690 struct sipe_container *container)
6692 sipe_private->blist_menu_containers = g_slist_prepend(sipe_private->blist_menu_containers,
6693 container);
6696 static GList *
6697 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
6698 const char* member_type,
6699 const char* member_value,
6700 const gboolean extra_menu)
6702 GList *menu_access_levels = NULL;
6703 unsigned int i;
6704 char *menu_name;
6705 PurpleMenuAction *act;
6706 struct sipe_container *container;
6707 struct sipe_container_member *member;
6708 gboolean is_group_access = FALSE;
6709 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
6711 for (i = 1; i <= CONTAINERS_LEN; i++) {
6712 /* to put Blocked level last in menu list.
6713 * Blocked should remaim in the first place in the containers[] array.
6715 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
6716 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
6718 container = g_new0(struct sipe_container, 1);
6719 member = g_new0(struct sipe_container_member, 1);
6720 container->id = containers[j];
6721 container->members = g_slist_append(container->members, member);
6722 member->type = g_strdup(member_type);
6723 member->value = g_strdup(member_value);
6725 /* libpurple memory leak workaround */
6726 sipe_blist_menu_remember_container(sipe_private, container);
6728 /* current container/access level */
6729 if (((int)containers[j]) == container_id) {
6730 menu_name = is_group_access ?
6731 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
6732 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
6733 } else {
6734 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
6737 act = purple_menu_action_new(menu_name,
6738 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6739 container, NULL);
6740 g_free(menu_name);
6741 menu_access_levels = g_list_prepend(menu_access_levels, act);
6744 if (extra_menu && (container_id >= 0)) {
6745 /* separator */
6746 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
6747 menu_access_levels = g_list_prepend(menu_access_levels, act);
6749 if (!is_group_access) {
6750 container = g_new0(struct sipe_container, 1);
6751 member = g_new0(struct sipe_container_member, 1);
6752 container->id = -1;
6753 container->members = g_slist_append(container->members, member);
6754 member->type = g_strdup(member_type);
6755 member->value = g_strdup(member_value);
6757 /* libpurple memory leak workaround */
6758 sipe_blist_menu_remember_container(sipe_private, container);
6760 /* Translators: remove (clear) previously assigned access level */
6761 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
6762 act = purple_menu_action_new(menu_name,
6763 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6764 container, NULL);
6765 g_free(menu_name);
6766 menu_access_levels = g_list_prepend(menu_access_levels, act);
6770 menu_access_levels = g_list_reverse(menu_access_levels);
6771 return menu_access_levels;
6774 static GList *
6775 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
6777 GList *menu_access_groups = NULL;
6778 PurpleMenuAction *act;
6779 GSList *access_domains = NULL;
6780 GSList *entry;
6781 char *menu_name;
6782 char *domain;
6784 act = purple_menu_action_new(_("People in my company"),
6785 NULL,
6786 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
6787 menu_access_groups = g_list_prepend(menu_access_groups, act);
6789 /* this is original name, don't edit */
6790 act = purple_menu_action_new(_("People in domains connected with my company"),
6791 NULL,
6792 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
6793 menu_access_groups = g_list_prepend(menu_access_groups, act);
6795 act = purple_menu_action_new(_("People in public domains"),
6796 NULL,
6797 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
6798 menu_access_groups = g_list_prepend(menu_access_groups, act);
6800 access_domains = sipe_get_access_domains(sipe_private);
6801 entry = access_domains;
6802 while (entry) {
6803 domain = entry->data;
6805 menu_name = g_strdup_printf(_("People at %s"), domain);
6806 act = purple_menu_action_new(menu_name,
6807 NULL,
6808 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6809 menu_access_groups = g_list_prepend(menu_access_groups, act);
6810 g_free(menu_name);
6812 entry = entry->next;
6815 /* separator */
6816 /* People in domains connected with my company */
6817 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6818 menu_access_groups = g_list_prepend(menu_access_groups, act);
6820 act = purple_menu_action_new(_("Add new domain..."),
6821 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6822 NULL, NULL);
6823 menu_access_groups = g_list_prepend(menu_access_groups, act);
6825 menu_access_groups = g_list_reverse(menu_access_groups);
6827 return menu_access_groups;
6830 static GList *
6831 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6832 const char* uri)
6834 GList *menu_access_levels = NULL;
6835 GList *menu_access_groups = NULL;
6836 char *menu_name;
6837 PurpleMenuAction *act;
6839 /* libpurple memory leak workaround */
6840 sipe_blist_menu_free_containers(sipe_private);
6842 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6844 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6846 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6847 act = purple_menu_action_new(menu_name,
6848 NULL,
6849 NULL, menu_access_groups);
6850 g_free(menu_name);
6851 menu_access_levels = g_list_append(menu_access_levels, act);
6853 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6854 act = purple_menu_action_new(menu_name,
6855 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6856 NULL, NULL);
6857 g_free(menu_name);
6858 menu_access_levels = g_list_append(menu_access_levels, act);
6860 return menu_access_levels;
6863 static gboolean
6864 process_get_info_response(struct sipe_core_private *sipe_private,
6865 struct sipmsg *msg, struct transaction *trans)
6867 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6868 char *uri = trans->payload->data;
6870 PurpleNotifyUserInfo *info;
6871 PurpleBuddy *pbuddy = NULL;
6872 struct sipe_buddy *sbuddy;
6873 const char *alias = NULL;
6874 char *device_name = NULL;
6875 char *server_alias = NULL;
6876 char *phone_number = NULL;
6877 char *email = NULL;
6878 const char *site;
6879 char *first_name = NULL;
6880 char *last_name = NULL;
6882 if (!sip) return FALSE;
6884 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6886 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6887 alias = purple_buddy_get_local_alias(pbuddy);
6889 //will query buddy UA's capabilities and send answer to log
6890 sipe_options_request(sipe_private, uri);
6892 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6893 if (sbuddy) {
6894 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6897 info = purple_notify_user_info_new();
6899 if (msg->response != 200) {
6900 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6901 } else {
6902 sipe_xml *searchResults;
6903 const sipe_xml *mrow;
6905 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6906 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6907 if (!searchResults) {
6908 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6909 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6910 const char *value;
6911 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6912 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6913 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6915 /* For 2007 system we will take this from ContactCard -
6916 * it has cleaner tel: URIs at least
6918 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6919 char *tel_uri = sip_to_tel_uri(phone_number);
6920 /* trims its parameters, so call first */
6921 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, server_alias);
6922 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
6923 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
6924 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, phone_number);
6925 g_free(tel_uri);
6928 if (server_alias && strlen(server_alias) > 0) {
6929 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6931 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6932 purple_notify_user_info_add_pair(info, _("Job title"), value);
6934 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6935 purple_notify_user_info_add_pair(info, _("Office"), value);
6937 if (phone_number && strlen(phone_number) > 0) {
6938 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
6940 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6941 purple_notify_user_info_add_pair(info, _("Company"), value);
6943 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6944 purple_notify_user_info_add_pair(info, _("City"), value);
6946 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
6947 purple_notify_user_info_add_pair(info, _("State"), value);
6949 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
6950 purple_notify_user_info_add_pair(info, _("Country"), value);
6952 if (email && strlen(email) > 0) {
6953 purple_notify_user_info_add_pair(info, _("Email address"), email);
6957 sipe_xml_free(searchResults);
6960 purple_notify_user_info_add_section_break(info);
6962 if (is_empty(server_alias)) {
6963 g_free(server_alias);
6964 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6965 if (server_alias) {
6966 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6970 /* present alias if it differs from server alias */
6971 if (alias && !sipe_strequal(alias, server_alias))
6973 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6976 if (is_empty(email)) {
6977 g_free(email);
6978 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
6979 if (email) {
6980 purple_notify_user_info_add_pair(info, _("Email address"), email);
6984 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
6985 if (site) {
6986 purple_notify_user_info_add_pair(info, _("Site"), site);
6989 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
6990 if (first_name && last_name) {
6991 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
6993 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
6994 g_free(link);
6996 g_free(first_name);
6997 g_free(last_name);
6999 if (device_name) {
7000 purple_notify_user_info_add_pair(info, _("Device"), device_name);
7003 /* show a buddy's user info in a nice dialog box */
7004 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
7005 uri, /* buddy's URI */
7006 info, /* body */
7007 NULL, /* callback called when dialog closed */
7008 NULL); /* userdata for callback */
7010 g_free(phone_number);
7011 g_free(server_alias);
7012 g_free(email);
7013 g_free(device_name);
7015 return TRUE;
7019 * AD search first, LDAP based
7021 void sipe_get_info(PurpleConnection *gc, const char *username)
7023 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
7024 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
7025 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
7026 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
7027 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
7029 payload->destroy = g_free;
7030 payload->data = g_strdup(username);
7032 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
7033 send_soap_request_with_cb(sipe_private, domain_uri, body,
7034 process_get_info_response, payload);
7035 g_free(domain_uri);
7036 g_free(body);
7037 g_free(row);
7041 Local Variables:
7042 mode: c
7043 c-file-style: "bsd"
7044 indent-tabs-mode: t
7045 tab-width: 8
7046 End: