presence: fix idle check for OCS2005 case
[siplcs.git] / src / core / sipe.c
blob5864d589b0a19b925e74f57934fae65b2ae05a2a
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * ***
15 * Thanks to Google's Summer of Code Program and the helpful mentors
16 * ***
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #ifdef HAVE_CONFIG_H
37 #include "config.h"
38 #endif
40 #include <time.h>
41 #include <stdlib.h>
42 #include <stdio.h>
43 #include <errno.h>
44 #include <string.h>
45 #include <unistd.h>
47 #include <glib.h>
49 #include "sipe-common.h"
51 #include "account.h"
52 #include "blist.h"
53 #include "connection.h"
54 #include "conversation.h"
55 #include "ft.h"
56 #include "notify.h"
57 #include "plugin.h"
58 #include "privacy.h"
59 #include "request.h"
60 #include "savedstatuses.h"
62 #include "core-depurple.h" /* Temporary for the core de-purple transition */
64 #include "http-conn.h"
65 #include "sipmsg.h"
66 #include "sip-csta.h"
67 #include "sip-transport.h"
68 #include "sipe-backend.h"
69 #include "sipe-buddy.h"
70 #include "sipe-cal.h"
71 #include "sipe-chat.h"
72 #include "sipe-conf.h"
73 #include "sipe-core.h"
74 #include "sipe-core-private.h"
75 #include "sipe-dialog.h"
76 #include "sipe-ews.h"
77 #include "sipe-domino.h"
78 #include "sipe-ft.h"
79 #include "sipe-mime.h"
80 #include "sipe-nls.h"
81 #include "sipe-schedule.h"
82 #include "sipe-session.h"
83 #include "sipe-subscriptions.h"
84 #include "sipe-media.h"
85 #include "sipe-utils.h"
86 #include "sipe-xml.h"
87 #include "uuid.h"
88 #include "sipe.h"
90 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
92 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
93 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
95 /* Status identifiers (see also: sipe_status_types()) */
96 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
97 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
98 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
99 /* PURPLE_STATUS_UNAVAILABLE: */
100 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
101 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
102 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
103 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
104 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
105 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
106 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
107 /* PURPLE_STATUS_AWAY: */
108 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
109 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
110 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
111 /** Reuters status (user settable) */
112 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
113 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
114 /* ??? PURPLE_STATUS_MOBILE */
115 /* ??? PURPLE_STATUS_TUNE */
117 /* Status attributes (see also sipe_status_types() */
118 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
120 static struct sipe_activity_map_struct
122 sipe_activity type;
123 const char *token;
124 const char *desc;
125 const char *status_id;
127 } const sipe_activity_map[] =
129 /* This has nothing to do with Availability numbers, like 3500 (online).
130 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
132 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
133 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
134 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
135 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
136 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
137 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
138 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
139 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
140 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
141 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
142 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
143 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
144 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
145 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
146 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
148 /** @param x is sipe_activity */
149 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
151 static sipe_activity
152 sipe_get_activity_by_token(const char *token)
154 int i;
156 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
158 if (sipe_strequal(token, sipe_activity_map[i].token))
159 return sipe_activity_map[i].type;
162 return sipe_activity_map[0].type;
165 static const char *
166 sipe_get_activity_desc_by_token(const char *token)
168 if (!token) return NULL;
170 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
173 static void send_presence_status(struct sipe_core_private *sipe_private,
174 void *unused);
177 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
179 static void
180 send_soap_request_with_cb(struct sipe_core_private *sipe_private,
181 gchar *from0,
182 gchar *body,
183 TransCallback callback,
184 struct transaction_payload *payload)
186 gchar *from = from0 ? g_strdup(from0) : sip_uri_self(sipe_private);
187 gchar *contact = get_contact(sipe_private);
188 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
189 "Content-Type: application/SOAP+xml\r\n",contact);
191 struct transaction *trans = sip_transport_service(sipe_private,
192 from,
193 hdr,
194 body,
195 callback);
196 trans->payload = payload;
198 g_free(from);
199 g_free(contact);
200 g_free(hdr);
203 static void send_soap_request(struct sipe_core_private *sipe_private,
204 gchar *body)
206 send_soap_request_with_cb(sipe_private, NULL, body, NULL, NULL);
210 * Returns pointer to URI without sip: prefix if any
212 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
213 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
215 * Doesn't allocate memory
217 static const char *
218 sipe_get_no_sip_uri(const char *sip_uri)
220 const char *prefix = "sip:";
221 if (!sip_uri) return NULL;
223 if (g_str_has_prefix(sip_uri, prefix)) {
224 return (sip_uri+strlen(prefix));
225 } else {
226 return sip_uri;
230 static void
231 sipe_contact_set_acl (struct sipe_core_private *sipe_private,
232 const gchar *who,
233 gchar *rights)
235 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
236 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
237 send_soap_request(sipe_private, body);
238 g_free(body);
241 static void
242 sipe_change_access_level(struct sipe_core_private *sipe_private,
243 const int container_id,
244 const gchar *type,
245 const gchar *value);
247 void
248 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
249 const gchar * who, gboolean allow)
251 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
253 if (allow) {
254 SIPE_DEBUG_INFO("Authorizing contact %s", who);
255 } else {
256 SIPE_DEBUG_INFO("Blocking contact %s", who);
259 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
260 sipe_change_access_level(sipe_private, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
261 } else {
262 sipe_contact_set_acl(sipe_private, who, allow ? "AA" : "BD");
266 static
267 void sipe_auth_user_cb(void * data)
269 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
270 if (!job) return;
272 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, TRUE);
273 g_free(job);
276 static
277 void sipe_deny_user_cb(void * data)
279 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
280 if (!job) return;
282 sipe_core_contact_allow_deny((struct sipe_core_public *)job->sipe_private, job->who, FALSE);
283 g_free(job);
286 /** @applicable: 2005-
288 static void
289 sipe_process_presence_wpending (struct sipe_core_private *sipe_private,
290 struct sipmsg * msg)
292 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
293 sipe_xml *watchers;
294 const sipe_xml *watcher;
295 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
296 if (msg->response != 0 && msg->response != 200) return;
298 if (msg->bodylen == 0 || msg->body == NULL || sipe_strequal(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
300 watchers = sipe_xml_parse(msg->body, msg->bodylen);
301 if (!watchers) return;
303 for (watcher = sipe_xml_child(watchers, "watcher"); watcher; watcher = sipe_xml_twin(watcher)) {
304 gchar * remote_user = g_strdup(sipe_xml_attribute(watcher, "uri"));
305 gchar * alias = g_strdup(sipe_xml_attribute(watcher, "displayName"));
306 gboolean on_list = g_hash_table_lookup(sipe_private->buddies, remote_user) != NULL;
308 // TODO pull out optional displayName to pass as alias
309 if (remote_user) {
310 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
311 job->who = remote_user;
312 job->sipe_private = sipe_private;
313 purple_account_request_authorization(
314 sip->account,
315 remote_user,
316 _("you"), /* id */
317 alias,
318 NULL, /* message */
319 on_list,
320 sipe_auth_user_cb,
321 sipe_deny_user_cb,
322 (void *) job);
327 sipe_xml_free(watchers);
328 return;
331 static void
332 sipe_group_add(struct sipe_core_private *sipe_private,
333 struct sipe_group * group)
335 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
336 PurpleGroup * purple_group = purple_find_group(group->name);
337 if (!purple_group) {
338 purple_group = purple_group_new(group->name);
339 purple_blist_add_group(purple_group, NULL);
342 if (purple_group) {
343 group->purple_group = purple_group;
344 sip->groups = g_slist_append(sip->groups, group);
345 SIPE_DEBUG_INFO("added group %s (id %d)", group->name, group->id);
346 } else {
347 SIPE_DEBUG_INFO("did not add group %s", group->name ? group->name : "");
351 static struct sipe_group *sipe_group_find_by_id(struct sipe_core_private *sipe_private,
352 int id)
354 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
355 struct sipe_group *group;
356 GSList *entry;
357 if (sip == NULL) {
358 return NULL;
361 entry = sip->groups;
362 while (entry) {
363 group = entry->data;
364 if (group->id == id) {
365 return group;
367 entry = entry->next;
369 return NULL;
372 static struct sipe_group *sipe_group_find_by_name(struct sipe_core_private *sipe_private,
373 const gchar * name)
375 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
376 struct sipe_group *group;
377 GSList *entry;
378 if (!sip || !name) {
379 return NULL;
382 entry = sip->groups;
383 while (entry) {
384 group = entry->data;
385 if (sipe_strequal(group->name, name)) {
386 return group;
388 entry = entry->next;
390 return NULL;
393 static void
394 sipe_group_rename(struct sipe_core_private *sipe_private,
395 struct sipe_group *group,
396 gchar *name)
398 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
399 gchar *body;
400 SIPE_DEBUG_INFO("Renaming group %s to %s", group->name, name);
401 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
402 send_soap_request(sipe_private, body);
403 g_free(body);
404 g_free(group->name);
405 group->name = g_strdup(name);
409 * Only appends if no such value already stored.
410 * Like Set in Java.
412 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
413 GSList * res = list;
414 if (!g_slist_find_custom(list, data, func)) {
415 res = g_slist_insert_sorted(list, data, func);
417 return res;
420 static int
421 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
422 return group1->id - group2->id;
426 * Returns string like "2 4 7 8" - group ids buddy belong to.
428 static gchar *
429 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
430 int i = 0;
431 gchar *res;
432 //creating array from GList, converting int to gchar*
433 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
434 GSList *entry = buddy->groups;
436 if (!ids_arr) return NULL;
438 while (entry) {
439 struct sipe_group * group = entry->data;
440 ids_arr[i] = g_strdup_printf("%d", group->id);
441 entry = entry->next;
442 i++;
444 ids_arr[i] = NULL;
445 res = g_strjoinv(" ", ids_arr);
446 g_strfreev(ids_arr);
447 return res;
451 * Sends buddy update to server
453 void
454 sipe_core_group_set_user(struct sipe_core_public *sipe_public, const gchar * who)
456 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
457 struct sipe_buddy *buddy = g_hash_table_lookup(SIPE_CORE_PRIVATE->buddies, who);
458 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
460 if (buddy && purple_buddy) {
461 const char *alias = purple_buddy_get_alias(purple_buddy);
462 gchar *groups = sipe_get_buddy_groups_string(buddy);
463 if (groups) {
464 gchar *body;
465 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who, alias, groups);
467 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
468 alias, groups, "true", buddy->name, sip->contacts_delta++
470 send_soap_request(SIPE_CORE_PRIVATE, body);
471 g_free(groups);
472 g_free(body);
477 static gboolean process_add_group_response(struct sipe_core_private *sipe_private,
478 struct sipmsg *msg,
479 struct transaction *trans)
481 if (msg->response == 200) {
482 struct sipe_group *group;
483 struct group_user_context *ctx = trans->payload->data;
484 sipe_xml *xml;
485 const sipe_xml *node;
486 char *group_id;
487 struct sipe_buddy *buddy;
489 xml = sipe_xml_parse(msg->body, msg->bodylen);
490 if (!xml) {
491 return FALSE;
494 node = sipe_xml_child(xml, "Body/addGroup/groupID");
495 if (!node) {
496 sipe_xml_free(xml);
497 return FALSE;
500 group_id = sipe_xml_data(node);
501 if (!group_id) {
502 sipe_xml_free(xml);
503 return FALSE;
506 group = g_new0(struct sipe_group, 1);
507 group->id = (int)g_ascii_strtod(group_id, NULL);
508 g_free(group_id);
509 group->name = g_strdup(ctx->group_name);
511 sipe_group_add(sipe_private, group);
513 if (ctx->user_name) {
514 buddy = g_hash_table_lookup(sipe_private->buddies, ctx->user_name);
515 if (buddy) {
516 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
519 sipe_core_group_set_user(SIPE_CORE_PUBLIC, ctx->user_name);
522 sipe_xml_free(xml);
523 return TRUE;
525 return FALSE;
528 static void sipe_group_context_destroy(gpointer data)
530 struct group_user_context *ctx = data;
531 g_free(ctx->group_name);
532 g_free(ctx->user_name);
533 g_free(ctx);
536 static void sipe_group_create (struct sipe_core_private *sipe_private,
537 const gchar *name, const gchar * who)
539 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
540 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
541 struct group_user_context *ctx = g_new0(struct group_user_context, 1);
542 const gchar *soap_name = sipe_strequal(name, _("Other Contacts")) ? "~" : name;
543 gchar *body;
544 ctx->group_name = g_strdup(name);
545 ctx->user_name = g_strdup(who);
546 payload->destroy = sipe_group_context_destroy;
547 payload->data = ctx;
549 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, soap_name, sip->contacts_delta++);
550 send_soap_request_with_cb(sipe_private, NULL, body, process_add_group_response, payload);
551 g_free(body);
554 static void
555 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
556 time_t calculate_from);
558 static int
559 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
561 static const char*
562 sipe_get_status_by_availability(int avail,
563 char** activity);
565 static void
566 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
567 const char *status_id,
568 const char *message,
569 time_t do_not_publish[]);
571 static void
572 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
573 struct sipe_buddy *sbuddy,
574 const char *status_id)
576 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
577 time_t cal_avail_since;
578 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
579 int avail;
580 gchar *self_uri;
582 if (!sbuddy) return;
584 if (cal_status < SIPE_CAL_NO_DATA) {
585 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
586 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
589 /* scheduled Cal update call */
590 if (!status_id) {
591 status_id = sbuddy->last_non_cal_status_id;
592 g_free(sbuddy->activity);
593 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
596 if (!status_id) {
597 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
598 sbuddy->name ? sbuddy->name : "" );
599 return;
602 /* adjust to calendar status */
603 if (cal_status != SIPE_CAL_NO_DATA) {
604 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
606 if (cal_status == SIPE_CAL_BUSY
607 && cal_avail_since > sbuddy->user_avail_since
608 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
610 status_id = SIPE_STATUS_ID_BUSY;
611 g_free(sbuddy->activity);
612 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
614 avail = sipe_get_availability_by_status(status_id, NULL);
616 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
617 if (cal_avail_since > sbuddy->activity_since) {
618 if (cal_status == SIPE_CAL_OOF
619 && avail >= 15000) /* 12000 in 2007 */
621 g_free(sbuddy->activity);
622 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
627 /* then set status_id actually */
628 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
629 purple_prpl_got_user_status(sip->account, sbuddy->name, status_id, NULL);
631 /* set our account state to the one in roaming (including calendar info) */
632 self_uri = sip_uri_self(sipe_private);
633 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
634 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
635 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
638 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
639 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
641 g_free(self_uri);
644 static void
645 sipe_got_user_status(struct sipe_core_private *sipe_private,
646 const char* uri,
647 const char *status_id)
649 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
650 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
652 if (!sbuddy) return;
654 /* Check if on 2005 system contact's calendar,
655 * then set/preserve it.
657 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
658 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
659 } else {
660 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
664 static void
665 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
666 struct sipe_buddy *sbuddy,
667 struct sipe_core_private *sipe_private)
669 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
673 * Updates contact's status
674 * based on their calendar information.
676 * Applicability: 2005 systems
678 static void
679 update_calendar_status(struct sipe_core_private *sipe_private,
680 SIPE_UNUSED_PARAMETER void *unused)
682 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
683 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
685 /* repeat scheduling */
686 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
690 * Schedules process of contacts' status update
691 * based on their calendar information.
692 * Should be scheduled to the beginning of every
693 * 15 min interval, like:
694 * 13:00, 13:15, 13:30, 13:45, etc.
696 * Applicability: 2005 systems
698 static void
699 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
700 time_t calculate_from)
702 int interval = 15*60;
703 /** start of the beginning of closest 15 min interval. */
704 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
706 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
707 asctime(localtime(&calculate_from)));
708 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
709 asctime(localtime(&next_start)));
711 sipe_schedule_seconds(sipe_private,
712 "<+2005-cal-status>",
713 NULL,
714 next_start - time(NULL),
715 update_calendar_status,
716 NULL);
720 * Schedules process of self status publish
721 * based on own calendar information.
722 * Should be scheduled to the beginning of every
723 * 15 min interval, like:
724 * 13:00, 13:15, 13:30, 13:45, etc.
726 * Applicability: 2007+ systems
728 static void
729 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
730 time_t calculate_from)
732 int interval = 5*60;
733 /** start of the beginning of closest 5 min interval. */
734 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
736 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
737 asctime(localtime(&calculate_from)));
738 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
739 asctime(localtime(&next_start)));
741 sipe_schedule_seconds(sipe_private,
742 "<+2007-cal-status>",
743 NULL,
744 next_start - time(NULL),
745 publish_calendar_status_self,
746 NULL);
749 static void sipe_subscribe_resource_uri(const char *name,
750 SIPE_UNUSED_PARAMETER gpointer value,
751 gchar **resources_uri)
753 gchar *tmp = *resources_uri;
754 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
755 g_free(tmp);
758 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
760 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
761 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
762 gchar *tmp = *resources_uri;
764 if (sbuddy) sbuddy->just_added = FALSE; /* should be enought to include context one time */
766 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp, name, context);
767 g_free(tmp);
771 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
772 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
773 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
774 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
775 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
778 static void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
779 gchar *resources_uri,
780 gchar *to)
782 gchar *contact = get_contact(sipe_private);
783 gchar *request;
784 gchar *content;
785 gchar *require = "";
786 gchar *accept = "";
787 gchar *autoextend = "";
788 gchar *content_type;
790 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
791 require = ", categoryList";
792 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
793 content_type = "application/msrtc-adrl-categorylist+xml";
794 content = g_strdup_printf(
795 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
796 "<action name=\"subscribe\" id=\"63792024\">\n"
797 "<adhocList>\n%s</adhocList>\n"
798 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
799 "<category name=\"calendarData\"/>\n"
800 "<category name=\"contactCard\"/>\n"
801 "<category name=\"note\"/>\n"
802 "<category name=\"state\"/>\n"
803 "</categoryList>\n"
804 "</action>\n"
805 "</batchSub>", sipe_private->username, resources_uri);
806 } else {
807 autoextend = "Supported: com.microsoft.autoextend\r\n";
808 content_type = "application/adrl+xml";
809 content = g_strdup_printf(
810 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
811 "<create xmlns=\"\">\n%s</create>\n"
812 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
814 g_free(resources_uri);
816 request = g_strdup_printf(
817 "Require: adhoclist%s\r\n"
818 "Supported: eventlist\r\n"
819 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
820 "Supported: ms-piggyback-first-notify\r\n"
821 "%sSupported: ms-benotify\r\n"
822 "Proxy-Require: ms-benotify\r\n"
823 "Event: presence\r\n"
824 "Content-Type: %s\r\n"
825 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
826 g_free(contact);
828 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
830 g_free(content);
831 g_free(to);
832 g_free(request);
835 static void sipe_subscribe_presence_batched(struct sipe_core_private *sipe_private,
836 SIPE_UNUSED_PARAMETER void *unused)
838 gchar *to = sip_uri_self(sipe_private);
839 gchar *resources_uri = g_strdup("");
840 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
841 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
842 } else {
843 g_hash_table_foreach(sipe_private->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
846 sipe_subscribe_presence_batched_to(sipe_private, resources_uri, to);
849 struct presence_batched_routed {
850 gchar *host;
851 GSList *buddies;
854 static void sipe_subscribe_presence_batched_routed_free(void *payload)
856 struct presence_batched_routed *data = payload;
857 GSList *buddies = data->buddies;
858 while (buddies) {
859 g_free(buddies->data);
860 buddies = buddies->next;
862 g_slist_free(data->buddies);
863 g_free(data->host);
864 g_free(payload);
867 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
868 void *payload)
870 struct presence_batched_routed *data = payload;
871 GSList *buddies = data->buddies;
872 gchar *resources_uri = g_strdup("");
873 while (buddies) {
874 gchar *tmp = resources_uri;
875 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
876 g_free(tmp);
877 buddies = buddies->next;
879 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
880 g_strdup(data->host));
884 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
885 * The user sends a single SUBSCRIBE request to the subscribed contact.
886 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
890 static void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
891 void *buddy_name)
893 gchar *to = sip_uri((char *)buddy_name);
894 gchar *tmp = get_contact(sipe_private);
895 gchar *request;
896 gchar *content = NULL;
897 gchar *autoextend = "";
898 gchar *content_type = "";
899 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
900 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
902 if (sbuddy) sbuddy->just_added = FALSE;
904 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
905 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
906 } else {
907 autoextend = "Supported: com.microsoft.autoextend\r\n";
910 request = g_strdup_printf(
911 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
912 "Supported: ms-piggyback-first-notify\r\n"
913 "%s%sSupported: ms-benotify\r\n"
914 "Proxy-Require: ms-benotify\r\n"
915 "Event: presence\r\n"
916 "Contact: %s\r\n", autoextend, content_type, tmp);
918 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
919 content = g_strdup_printf(
920 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
921 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
922 "<resource uri=\"%s\"%s\n"
923 "</adhocList>\n"
924 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
925 "<category name=\"calendarData\"/>\n"
926 "<category name=\"contactCard\"/>\n"
927 "<category name=\"note\"/>\n"
928 "<category name=\"state\"/>\n"
929 "</categoryList>\n"
930 "</action>\n"
931 "</batchSub>", sipe_private->username, to, context);
934 g_free(tmp);
936 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
938 g_free(content);
939 g_free(to);
940 g_free(request);
943 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
945 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
947 if (!purple_status_is_active(status))
948 return;
950 if (account->gc) {
951 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
952 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
954 if (sip) {
955 gchar *action_name;
956 gchar *tmp;
957 time_t now = time(NULL);
958 const char *status_id = purple_status_get_id(status);
959 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
960 sipe_activity activity = sipe_get_activity_by_token(status_id);
961 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
963 /* when other point of presence clears note, but we are keeping
964 * state if OOF note.
966 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
967 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
968 do_not_publish = FALSE;
971 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
972 status_id, (int)sip->do_not_publish[activity], (int)now);
974 sip->do_not_publish[activity] = 0;
975 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
976 status_id, (int)sip->do_not_publish[activity]);
978 if (do_not_publish)
980 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
981 return;
984 g_free(sip->status);
985 sip->status = g_strdup(status_id);
987 /* hack to escape apostrof before comparison */
988 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
990 /* this will preserve OOF flag as well */
991 if (!sipe_strequal(tmp, sip->note)) {
992 sip->is_oof_note = FALSE;
993 g_free(sip->note);
994 sip->note = g_strdup(note);
995 sip->note_since = time(NULL);
997 g_free(tmp);
999 /* schedule 2 sec to capture idle flag */
1000 action_name = g_strdup_printf("<%s>", "+set-status");
1001 sipe_schedule_seconds(sipe_private,
1002 action_name,
1003 NULL,
1004 SIPE_IDLE_SET_DELAY,
1005 send_presence_status,
1006 NULL);
1007 g_free(action_name);
1012 void
1013 sipe_set_idle(PurpleConnection * gc,
1014 int interval)
1016 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
1018 if (gc) {
1019 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1020 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1022 if (sip) {
1023 sip->idle_switch = time(NULL);
1024 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
1029 void
1030 sipe_group_buddy(PurpleConnection *gc,
1031 const char *who,
1032 const char *old_group_name,
1033 const char *new_group_name)
1035 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1036 struct sipe_buddy * buddy = g_hash_table_lookup(sipe_private->buddies, who);
1037 struct sipe_group * old_group = NULL;
1038 struct sipe_group * new_group;
1040 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
1041 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1043 if(!buddy) { // buddy not in roaming list
1044 return;
1047 if (old_group_name) {
1048 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
1050 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
1052 if (old_group) {
1053 buddy->groups = g_slist_remove(buddy->groups, old_group);
1054 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who, old_group_name);
1057 if (!new_group) {
1058 sipe_group_create(sipe_private, new_group_name, who);
1059 } else {
1060 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1061 sipe_core_group_set_user(SIPE_CORE_PUBLIC, who);
1065 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1067 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1069 /* libpurple can call us with undefined buddy or group */
1070 if (buddy && group) {
1071 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1073 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1074 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
1075 purple_blist_rename_buddy(buddy, buddy_name);
1076 g_free(buddy_name);
1078 /* Prepend sip: if needed */
1079 if (!g_str_has_prefix(buddy->name, "sip:")) {
1080 gchar *buf = sip_uri_from_name(buddy->name);
1081 purple_blist_rename_buddy(buddy, buf);
1082 g_free(buf);
1085 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
1086 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
1087 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
1088 b->name = g_strdup(buddy->name);
1089 b->just_added = TRUE;
1090 g_hash_table_insert(sipe_private->buddies, b->name, b);
1091 sipe_group_buddy(gc, b->name, NULL, group->name);
1092 /* @TODO should go to callback */
1093 sipe_subscribe_presence_single(sipe_private,
1094 b->name);
1095 } else {
1096 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
1101 static void sipe_free_buddy(struct sipe_buddy *buddy)
1103 #ifndef _WIN32
1105 * We are calling g_hash_table_foreach_steal(). That means that no
1106 * key/value deallocation functions are called. Therefore the glib
1107 * hash code does not touch the key (buddy->name) or value (buddy)
1108 * of the to-be-deleted hash node at all. It follows that we
1110 * - MUST free the memory for the key ourselves and
1111 * - ARE allowed to do it in this function
1113 * Conclusion: glib must be broken on the Windows platform if sipe
1114 * crashes with SIGTRAP when closing. You'll have to live
1115 * with the memory leak until this is fixed.
1117 g_free(buddy->name);
1118 #endif
1119 g_free(buddy->activity);
1120 g_free(buddy->meeting_subject);
1121 g_free(buddy->meeting_location);
1122 g_free(buddy->note);
1124 g_free(buddy->cal_start_time);
1125 g_free(buddy->cal_free_busy_base64);
1126 g_free(buddy->cal_free_busy);
1127 g_free(buddy->last_non_cal_activity);
1129 sipe_cal_free_working_hours(buddy->cal_working_hours);
1131 g_free(buddy->device_name);
1132 g_slist_free(buddy->groups);
1133 g_free(buddy);
1137 * Unassociates buddy from group first.
1138 * Then see if no groups left, removes buddy completely.
1139 * Otherwise updates buddy groups on server.
1141 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1143 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1144 struct sipe_buddy *b;
1145 struct sipe_group *g = NULL;
1147 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
1148 if (!buddy) return;
1150 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
1151 if (!b) return;
1153 if (group) {
1154 g = sipe_group_find_by_name(sipe_private, group->name);
1157 if (g) {
1158 b->groups = g_slist_remove(b->groups, g);
1159 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
1162 if (g_slist_length(b->groups) < 1) {
1163 gchar *action_name = sipe_utils_presence_key(buddy->name);
1164 sipe_schedule_cancel(sipe_private, action_name);
1165 g_free(action_name);
1167 g_hash_table_remove(sipe_private->buddies, buddy->name);
1169 if (b->name) {
1170 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1171 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1172 send_soap_request(sipe_private, body);
1173 g_free(body);
1176 sipe_free_buddy(b);
1177 } else {
1178 //updates groups on server
1179 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
1184 void
1185 sipe_rename_group(PurpleConnection *gc,
1186 const char *old_name,
1187 PurpleGroup *group,
1188 SIPE_UNUSED_PARAMETER GList *moved_buddies)
1190 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1191 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, old_name);
1192 if (s_group) {
1193 sipe_group_rename(sipe_private, s_group, group->name);
1194 } else {
1195 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name);
1199 void
1200 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1202 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
1203 struct sipe_group * s_group = sipe_group_find_by_name(sipe_private, group->name);
1204 if (s_group) {
1205 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1206 gchar *body;
1207 SIPE_DEBUG_INFO("Deleting group %s", group->name);
1208 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1209 send_soap_request(sipe_private, body);
1210 g_free(body);
1212 sip->groups = g_slist_remove(sip->groups, s_group);
1213 g_free(s_group->name);
1214 g_free(s_group);
1215 } else {
1216 SIPE_DEBUG_INFO("Cannot find group %s to delete", group->name);
1221 * A callback for g_hash_table_foreach
1223 static void
1224 sipe_buddy_subscribe_cb(char *buddy_name,
1225 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1226 struct sipe_core_private *sipe_private)
1228 gchar *action_name = sipe_utils_presence_key(buddy_name);
1229 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
1230 guint time_range = (g_hash_table_size(sipe_private->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1231 guint timeout = ((guint) rand()) / (RAND_MAX / time_range) + 1; /* random period within the range but never 0! */
1233 sipe_schedule_mseconds(sipe_private,
1234 action_name,
1235 g_strdup(buddy_name),
1236 timeout,
1237 sipe_subscribe_presence_single,
1238 g_free);
1239 g_free(action_name);
1243 * Removes entries from purple buddy list
1244 * that does not correspond ones in the roaming contact list.
1246 static void sipe_cleanup_local_blist(struct sipe_core_private *sipe_private) {
1247 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1248 GSList *buddies = purple_find_buddies(sip->account, NULL);
1249 GSList *entry = buddies;
1250 struct sipe_buddy *buddy;
1251 PurpleBuddy *b;
1252 PurpleGroup *g;
1254 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies));
1255 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sipe_private->buddies));
1256 while (entry) {
1257 b = entry->data;
1258 g = purple_buddy_get_group(b);
1259 buddy = g_hash_table_lookup(sipe_private->buddies, b->name);
1260 if(buddy) {
1261 gboolean in_sipe_groups = FALSE;
1262 GSList *entry2 = buddy->groups;
1263 while (entry2) {
1264 struct sipe_group *group = entry2->data;
1265 if (sipe_strequal(group->name, g->name)) {
1266 in_sipe_groups = TRUE;
1267 break;
1269 entry2 = entry2->next;
1271 if(!in_sipe_groups) {
1272 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b->name, g->name);
1273 purple_blist_remove_buddy(b);
1275 } else {
1276 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b->name, g->name);
1277 purple_blist_remove_buddy(b);
1279 entry = entry->next;
1281 g_slist_free(buddies);
1284 static int
1285 sipe_find_access_level(struct sipe_core_private *sipe_private,
1286 const gchar *type,
1287 const gchar *value,
1288 gboolean *is_group_access);
1290 static void
1291 sipe_refresh_blocked_status_cb(char *buddy_name,
1292 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
1293 struct sipe_core_private *sipe_private)
1295 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1296 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
1297 gboolean blocked = (container_id == 32000);
1298 gboolean blocked_in_blist = !purple_privacy_check(sip->account, buddy_name);
1300 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
1301 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
1303 if (blocked != blocked_in_blist) {
1304 if (blocked) {
1305 purple_privacy_deny_add(sip->account, buddy_name, TRUE);
1306 } else {
1307 purple_privacy_deny_remove(sip->account, buddy_name, TRUE);
1310 /* stupid workaround to make pidgin re-render screen to reflect our changes */
1312 PurpleBuddy *pbuddy = purple_find_buddy(sip->account, buddy_name);
1313 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
1314 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
1316 SIPE_DEBUG_INFO_NOFORMAT("sipe_refresh_blocked_status_cb: forcefully refreshing screen.");
1317 sipe_got_user_status(sipe_private, buddy_name, purple_status_get_id(pstatus));
1323 static void
1324 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
1326 g_hash_table_foreach(sipe_private->buddies,
1327 (GHFunc) sipe_refresh_blocked_status_cb,
1328 sipe_private);
1331 static gboolean sipe_process_roaming_contacts(struct sipe_core_private *sipe_private,
1332 struct sipmsg *msg)
1334 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1335 int len = msg->bodylen;
1337 const gchar *tmp = sipmsg_find_header(msg, "Event");
1338 const sipe_xml *item;
1339 sipe_xml *isc;
1340 const gchar *contacts_delta;
1341 const sipe_xml *group_node;
1342 if (!g_str_has_prefix(tmp, "vnd-microsoft-roaming-contacts")) {
1343 return FALSE;
1346 /* Convert the contact from XML to Purple Buddies */
1347 isc = sipe_xml_parse(msg->body, len);
1348 if (!isc) {
1349 return FALSE;
1352 contacts_delta = sipe_xml_attribute(isc, "deltaNum");
1353 if (contacts_delta) {
1354 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1357 if (sipe_strequal(sipe_xml_name(isc), "contactList")) {
1359 /* Parse groups */
1360 for (group_node = sipe_xml_child(isc, "group"); group_node; group_node = sipe_xml_twin(group_node)) {
1361 struct sipe_group * group = g_new0(struct sipe_group, 1);
1362 const char *name = sipe_xml_attribute(group_node, "name");
1364 if (g_str_has_prefix(name, "~")) {
1365 name = _("Other Contacts");
1367 group->name = g_strdup(name);
1368 group->id = (int)g_ascii_strtod(sipe_xml_attribute(group_node, "id"), NULL);
1370 sipe_group_add(sipe_private, group);
1373 // Make sure we have at least one group
1374 if (g_slist_length(sip->groups) == 0) {
1375 sipe_group_create(sipe_private, _("Other Contacts"), NULL);
1378 /* Parse contacts */
1379 for (item = sipe_xml_child(isc, "contact"); item; item = sipe_xml_twin(item)) {
1380 const gchar *uri = sipe_xml_attribute(item, "uri");
1381 const gchar *name = sipe_xml_attribute(item, "name");
1382 gchar *buddy_name;
1383 struct sipe_buddy *buddy = NULL;
1384 gchar *tmp;
1385 gchar **item_groups;
1386 int i = 0;
1388 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
1389 tmp = sip_uri_from_name(uri);
1390 buddy_name = g_ascii_strdown(tmp, -1);
1391 g_free(tmp);
1393 /* assign to group Other Contacts if nothing else received */
1394 tmp = g_strdup(sipe_xml_attribute(item, "groups"));
1395 if(is_empty(tmp)) {
1396 struct sipe_group *group = sipe_group_find_by_name(sipe_private, _("Other Contacts"));
1397 g_free(tmp);
1398 tmp = group ? g_strdup_printf("%d", group->id) : g_strdup("1");
1400 item_groups = g_strsplit(tmp, " ", 0);
1401 g_free(tmp);
1403 while (item_groups[i]) {
1404 struct sipe_group *group = sipe_group_find_by_id(sipe_private, g_ascii_strtod(item_groups[i], NULL));
1406 // If couldn't find the right group for this contact, just put them in the first group we have
1407 if (group == NULL && g_slist_length(sip->groups) > 0) {
1408 group = sip->groups->data;
1411 if (group != NULL) {
1412 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1413 if (!b){
1414 b = purple_buddy_new(sip->account, buddy_name, uri);
1415 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1417 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name, uri);
1420 if (sipe_strcase_equal(uri, purple_buddy_get_alias(b))) {
1421 if (name != NULL && strlen(name) != 0) {
1422 purple_blist_alias_buddy(b, name);
1424 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name, name);
1428 if (!buddy) {
1429 buddy = g_new0(struct sipe_buddy, 1);
1430 buddy->name = g_strdup(b->name);
1431 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1433 SIPE_DEBUG_INFO("Added SIPE buddy %s", buddy->name);
1436 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1438 SIPE_DEBUG_INFO("Added buddy %s to group %s", b->name, group->name);
1439 } else {
1440 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
1441 name);
1444 i++;
1445 } // while, contact groups
1446 g_strfreev(item_groups);
1447 g_free(buddy_name);
1449 } // for, contacts
1451 sipe_cleanup_local_blist(sipe_private);
1453 /* Add self-contact if not there yet. 2005 systems. */
1454 /* This will resemble subscription to roaming_self in 2007 systems */
1455 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1456 gchar *self_uri = sip_uri_self(sipe_private);
1457 struct sipe_buddy *buddy = g_hash_table_lookup(sipe_private->buddies, self_uri);
1459 if (!buddy) {
1460 buddy = g_new0(struct sipe_buddy, 1);
1461 buddy->name = g_strdup(self_uri);
1462 g_hash_table_insert(sipe_private->buddies, buddy->name, buddy);
1464 g_free(self_uri);
1467 sipe_xml_free(isc);
1469 /* subscribe to buddies */
1470 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1471 if (sip->batched_support) {
1472 sipe_subscribe_presence_batched(sipe_private, NULL);
1473 } else {
1474 g_hash_table_foreach(sipe_private->buddies,
1475 (GHFunc)sipe_buddy_subscribe_cb,
1476 sipe_private);
1478 sip->subscribed_buddies = TRUE;
1480 /* for 2005 systems schedule contacts' status update
1481 * based on their calendar information
1483 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1484 sipe_sched_calendar_status_update(sipe_private, time(NULL));
1487 return 0;
1491 * Fires on deregistration event initiated by server.
1492 * [MS-SIPREGE] SIP extension.
1495 // 2007 Example
1497 // Content-Type: text/registration-event
1498 // subscription-state: terminated;expires=0
1499 // ms-diagnostics-public: 4141;reason="User disabled"
1501 // deregistered;event=rejected
1503 static void sipe_process_registration_notify(struct sipe_core_private *sipe_private,
1504 struct sipmsg *msg)
1506 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
1507 gchar *event = NULL;
1508 gchar *reason = NULL;
1509 const gchar *diagnostics = sipmsg_find_header(msg, "ms-diagnostics");
1510 gchar *warning;
1512 diagnostics = diagnostics ? diagnostics : sipmsg_find_header(msg, "ms-diagnostics-public");
1513 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
1515 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
1516 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
1517 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
1518 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
1519 } else {
1520 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
1521 return;
1524 if (diagnostics != NULL) {
1525 reason = sipmsg_find_part_of_header(diagnostics, "reason=\"", "\"", NULL);
1526 } else { // for LCS2005
1527 int error_id = 0;
1528 if (event && sipe_strcase_equal(event, "unregistered")) {
1529 error_id = 4140; // [MS-SIPREGE]
1530 //reason = g_strdup(_("User logged out")); // [MS-OCER]
1531 reason = g_strdup(_("you are already signed in at another location"));
1532 } else if (event && sipe_strcase_equal(event, "rejected")) {
1533 error_id = 4141;
1534 reason = g_strdup(_("user disabled")); // [MS-OCER]
1535 } else if (event && sipe_strcase_equal(event, "deactivated")) {
1536 error_id = 4142;
1537 reason = g_strdup(_("user moved")); // [MS-OCER]
1540 g_free(event);
1541 warning = g_strdup_printf(_("You have been rejected by the server: %s"), reason ? reason : _("no reason given"));
1542 g_free(reason);
1544 sipe_backend_connection_error(SIPE_CORE_PUBLIC,
1545 SIPE_CONNECTION_ERROR_INVALID_USERNAME,
1546 warning);
1547 g_free(warning);
1551 static void sipe_process_provisioning_v2(struct sipe_core_private *sipe_private,
1552 struct sipmsg *msg)
1554 sipe_xml *xn_provision_group_list;
1555 const sipe_xml *node;
1557 xn_provision_group_list = sipe_xml_parse(msg->body, msg->bodylen);
1559 /* provisionGroup */
1560 for (node = sipe_xml_child(xn_provision_group_list, "provisionGroup"); node; node = sipe_xml_twin(node)) {
1561 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node, "name"))) {
1562 g_free(sipe_private->focus_factory_uri);
1563 sipe_private->focus_factory_uri = sipe_xml_data(sipe_xml_child(node, "focusFactoryUri"));
1564 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sipe_private->focus_factory_uri=%s",
1565 sipe_private->focus_factory_uri ? sipe_private->focus_factory_uri : "");
1566 break;
1569 sipe_xml_free(xn_provision_group_list);
1572 /** for 2005 system */
1573 static void
1574 sipe_process_provisioning(struct sipe_core_private *sipe_private,
1575 struct sipmsg *msg)
1577 sipe_xml *xn_provision;
1578 const sipe_xml *node;
1580 xn_provision = sipe_xml_parse(msg->body, msg->bodylen);
1581 if ((node = sipe_xml_child(xn_provision, "user"))) {
1582 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node, "uri"));
1583 if ((node = sipe_xml_child(node, "line"))) {
1584 const gchar *line_uri = sipe_xml_attribute(node, "uri");
1585 const gchar *server = sipe_xml_attribute(node, "server");
1586 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri, server);
1587 sip_csta_open(sipe_private, line_uri, server);
1590 sipe_xml_free(xn_provision);
1593 static void sipe_process_roaming_acl(struct sipe_core_private *sipe_private,
1594 struct sipmsg *msg)
1596 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1597 const gchar *contacts_delta;
1598 sipe_xml *xml;
1600 xml = sipe_xml_parse(msg->body, msg->bodylen);
1601 if (!xml)
1603 return;
1606 contacts_delta = sipe_xml_attribute(xml, "deltaNum");
1607 if (contacts_delta)
1609 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1612 sipe_xml_free(xml);
1615 /** MS-PRES container */
1616 struct sipe_container {
1617 guint id;
1618 guint version;
1619 GSList *members;
1621 /** MS-PRES container member */
1622 struct sipe_container_member {
1623 /** user, domain, sameEnterprise, federated, publicCloud; everyone */
1624 gchar *type;
1625 gchar *value;
1628 static void
1629 free_container_member(struct sipe_container_member *member)
1631 if (!member) return;
1633 g_free(member->type);
1634 g_free(member->value);
1635 g_free(member);
1638 static void
1639 free_container(struct sipe_container *container)
1641 GSList *entry;
1643 if (!container) return;
1645 entry = container->members;
1646 while (entry) {
1647 void *data = entry->data;
1648 entry = g_slist_remove(entry, data);
1649 free_container_member((struct sipe_container_member *)data);
1651 g_free(container);
1654 static void
1655 sipe_send_container_members_prepare(const guint container_id,
1656 const guint container_version,
1657 const gchar *action,
1658 const gchar *type,
1659 const gchar *value,
1660 char **container_xmls)
1662 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
1663 gchar *body;
1665 if (!container_xmls) return;
1667 body = g_strdup_printf(
1668 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
1669 container_id,
1670 container_version,
1671 action,
1672 type,
1673 value_str);
1674 g_free(value_str);
1676 if ((*container_xmls) == NULL) {
1677 *container_xmls = body;
1678 } else {
1679 char *tmp = *container_xmls;
1681 *container_xmls = g_strconcat(*container_xmls, body, NULL);
1682 g_free(tmp);
1683 g_free(body);
1687 static void
1688 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
1689 char *container_xmls)
1691 gchar *self;
1692 gchar *contact;
1693 gchar *hdr;
1694 gchar *body;
1696 if (!container_xmls) return;
1698 self = sip_uri_self(sipe_private);
1699 body = g_strdup_printf(
1700 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
1701 "%s"
1702 "</setContainerMembers>",
1703 container_xmls);
1705 contact = get_contact(sipe_private);
1706 hdr = g_strdup_printf("Contact: %s\r\n"
1707 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
1708 g_free(contact);
1710 sip_transport_service(sipe_private,
1711 self,
1712 hdr,
1713 body,
1714 NULL);
1716 g_free(hdr);
1717 g_free(body);
1718 g_free(self);
1722 * Finds locally stored MS-PRES container member
1724 static struct sipe_container_member *
1725 sipe_find_container_member(struct sipe_container *container,
1726 const gchar *type,
1727 const gchar *value)
1729 struct sipe_container_member *member;
1730 GSList *entry;
1732 if (container == NULL || type == NULL) {
1733 return NULL;
1736 entry = container->members;
1737 while (entry) {
1738 member = entry->data;
1739 if (sipe_strcase_equal(member->type, type) &&
1740 sipe_strcase_equal(member->value, value))
1742 return member;
1744 entry = entry->next;
1746 return NULL;
1750 * Finds locally stored MS-PRES container by id
1752 static struct sipe_container *
1753 sipe_find_container(struct sipe_core_private *sipe_private,
1754 guint id)
1756 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1757 struct sipe_container *container;
1758 GSList *entry;
1760 if (sip == NULL) {
1761 return NULL;
1764 entry = sip->containers;
1765 while (entry) {
1766 container = entry->data;
1767 if (id == container->id) {
1768 return container;
1770 entry = entry->next;
1772 return NULL;
1775 static GSList *
1776 sipe_get_access_domains(struct sipe_core_private *sipe_private)
1778 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1779 struct sipe_container *container;
1780 struct sipe_container_member *member;
1781 GSList *entry;
1782 GSList *entry2;
1783 GSList *res = NULL;
1785 if (!sip) return NULL;
1787 entry = sip->containers;
1788 while (entry) {
1789 container = entry->data;
1791 entry2 = container->members;
1792 while (entry2) {
1793 member = entry2->data;
1794 if (sipe_strcase_equal(member->type, "domain"))
1796 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1798 entry2 = entry2->next;
1800 entry = entry->next;
1802 return res;
1806 * Returns pointer to domain part in provided Email URL
1808 * @param email an email URL. Example: first.last@hq.company.com
1809 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1811 * Doesn't allocate memory
1813 static const char *
1814 sipe_get_domain(const char *email)
1816 char *tmp;
1818 if (!email) return NULL;
1820 tmp = strstr(email, "@");
1822 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1823 return tmp+1;
1824 } else {
1825 return NULL;
1830 /* @TODO: replace with binary search for faster access? */
1831 /** source: http://support.microsoft.com/kb/897567 */
1832 static const char * const public_domains [] = {
1833 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1834 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1835 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1836 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1837 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1838 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1839 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1840 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1841 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1842 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1843 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1844 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1845 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1846 "yahoo.com",
1847 NULL};
1849 static gboolean
1850 sipe_is_public_domain(const char *domain)
1852 int i = 0;
1853 while (public_domains[i]) {
1854 if (sipe_strcase_equal(public_domains[i], domain)) {
1855 return TRUE;
1857 i++;
1859 return FALSE;
1863 * Access Levels
1864 * 32000 - Blocked
1865 * 400 - Personal
1866 * 300 - Team
1867 * 200 - Company
1868 * 100 - Public
1870 static const char *
1871 sipe_get_access_level_name(int container_id)
1873 switch(container_id) {
1874 case 32000: return _("Blocked");
1875 case 400: return _("Personal");
1876 case 300: return _("Team");
1877 case 200: return _("Company");
1878 case 100: return _("Public");
1880 return _("Unknown");
1883 static const guint containers[] = {32000, 400, 300, 200, 100};
1884 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1887 static int
1888 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1889 const gchar *type,
1890 const gchar *value)
1892 unsigned int i = 0;
1893 const gchar *value_mod = value;
1895 if (!type) return -1;
1897 if (sipe_strequal("user", type)) {
1898 value_mod = sipe_get_no_sip_uri(value);
1901 for (i = 0; i < CONTAINERS_LEN; i++) {
1902 struct sipe_container_member *member;
1903 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1904 if (!container) continue;
1906 member = sipe_find_container_member(container, type, value_mod);
1907 if (member) return containers[i];
1910 return -1;
1913 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1914 static int
1915 sipe_find_access_level(struct sipe_core_private *sipe_private,
1916 const gchar *type,
1917 const gchar *value,
1918 gboolean *is_group_access)
1920 int container_id = -1;
1922 if (sipe_strequal("user", type)) {
1923 const char *domain;
1924 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1926 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1927 if (container_id >= 0) {
1928 if (is_group_access) *is_group_access = FALSE;
1929 return container_id;
1932 domain = sipe_get_domain(no_sip_uri);
1933 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1934 if (container_id >= 0) {
1935 if (is_group_access) *is_group_access = TRUE;
1936 return container_id;
1939 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1940 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1941 if (is_group_access) *is_group_access = TRUE;
1942 return container_id;
1945 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1946 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1947 if (is_group_access) *is_group_access = TRUE;
1948 return container_id;
1951 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1952 if ((container_id >= 0)) {
1953 if (is_group_access) *is_group_access = TRUE;
1954 return container_id;
1956 } else {
1957 container_id = sipe_find_member_access_level(sipe_private, type, value);
1958 if (is_group_access) *is_group_access = FALSE;
1961 return container_id;
1965 * @param container_id a new access level. If -1 then current access level
1966 * is just removed (I.e. the member is removed from all containers).
1967 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1968 * @param value a value for member. E.g. SIP URI for "user" member type.
1970 static void
1971 sipe_change_access_level(struct sipe_core_private *sipe_private,
1972 const int container_id,
1973 const gchar *type,
1974 const gchar *value)
1976 unsigned int i;
1977 int current_container_id = -1;
1978 char *container_xmls = NULL;
1980 /* for each container: find/delete */
1981 for (i = 0; i < CONTAINERS_LEN; i++) {
1982 struct sipe_container_member *member;
1983 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1985 if (!container) continue;
1987 member = sipe_find_container_member(container, type, value);
1988 if (member) {
1989 current_container_id = containers[i];
1990 /* delete/publish current access level */
1991 if (container_id < 0 || container_id != current_container_id) {
1992 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
1993 /* remove member from our cache, to be able to recalculate AL below */
1994 container->members = g_slist_remove(container->members, member);
1995 current_container_id = -1;
2000 /* recalculate AL below */
2001 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
2003 /* assign/publish new access level */
2004 if (container_id != current_container_id && container_id >= 0) {
2005 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
2006 guint version = container ? container->version : 0;
2008 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
2011 if (container_xmls) {
2012 sipe_send_set_container_members(sipe_private, container_xmls);
2014 g_free(container_xmls);
2017 static void
2018 free_publication(struct sipe_publication *publication)
2020 g_free(publication->category);
2021 g_free(publication->cal_event_hash);
2022 g_free(publication->note);
2024 g_free(publication->working_hours_xml_str);
2025 g_free(publication->fb_start_str);
2026 g_free(publication->free_busy_base64);
2028 g_free(publication);
2031 /* key is <category><instance><container> */
2032 static gboolean
2033 sipe_is_our_publication(struct sipe_core_private *sipe_private,
2034 const gchar *key)
2036 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2037 GSList *entry;
2039 /* filling keys for our publications if not yet cached */
2040 if (!sip->our_publication_keys) {
2041 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
2042 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
2043 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
2044 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
2045 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
2046 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
2047 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
2049 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
2050 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
2051 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
2052 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
2053 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
2054 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
2055 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
2056 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
2057 SIPE_DEBUG_INFO("\tNote : %u", 0);
2058 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
2060 /* device */
2061 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2062 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
2064 /* state:machineState */
2065 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2066 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
2067 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2068 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
2070 /* state:userState */
2071 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2072 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
2073 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2074 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
2076 /* state:calendarState */
2077 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2078 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
2079 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2080 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
2082 /* state:calendarState OOF */
2083 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2084 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
2085 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2086 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
2088 /* note */
2089 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2090 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2091 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2092 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2093 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2094 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2096 /* note OOF */
2097 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2098 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
2099 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2100 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
2101 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2102 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
2104 /* calendarData:WorkingHours */
2105 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2106 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2107 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2108 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2109 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2110 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2111 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2112 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2113 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2114 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2115 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2116 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2118 /* calendarData:FreeBusy */
2119 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2120 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
2121 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2122 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
2123 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2124 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
2125 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2126 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
2127 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2128 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
2129 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2130 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
2132 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
2133 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2136 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
2138 entry = sip->our_publication_keys;
2139 while (entry) {
2140 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
2141 if (sipe_strequal(entry->data, key)) {
2142 return TRUE;
2144 entry = entry->next;
2146 return FALSE;
2149 /** Property names to store in blist.xml */
2150 #define ALIAS_PROP "alias"
2151 #define EMAIL_PROP "email"
2152 #define PHONE_PROP "phone"
2153 #define PHONE_DISPLAY_PROP "phone-display"
2154 #define PHONE_MOBILE_PROP "phone-mobile"
2155 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2156 #define PHONE_HOME_PROP "phone-home"
2157 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2158 #define PHONE_OTHER_PROP "phone-other"
2159 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2160 #define PHONE_CUSTOM1_PROP "phone-custom1"
2161 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2162 #define SITE_PROP "site"
2163 #define COMPANY_PROP "company"
2164 #define DEPARTMENT_PROP "department"
2165 #define TITLE_PROP "title"
2166 #define OFFICE_PROP "office"
2167 /** implies work address */
2168 #define ADDRESS_STREET_PROP "address-street"
2169 #define ADDRESS_CITY_PROP "address-city"
2170 #define ADDRESS_STATE_PROP "address-state"
2171 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2172 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
2175 * Tries to figure out user first and last name
2176 * based on Display Name and email properties.
2178 * Allocates memory - must be g_free()'d
2180 * Examples to parse:
2181 * First Last
2182 * First Last - Company Name
2183 * Last, First
2184 * Last, First M.
2185 * Last, First (C)(STP) (Company)
2186 * first.last@company.com (preprocessed as "first last")
2187 * first.last.company.com@reuters.net (preprocessed as "first last company com")
2189 * Unusable examples:
2190 * user@company.com (preprocessed as "user")
2191 * first.m.last@company.com (preprocessed as "first m last")
2192 * user.company.com@reuters.net (preprocessed as "user company com")
2194 static void
2195 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
2196 const char *uri,
2197 char **first_name,
2198 char **last_name)
2200 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2201 PurpleBuddy *p_buddy;
2202 char *display_name;
2203 const char *email;
2204 const char *first, *last;
2205 char *tmp;
2206 char **parts;
2207 gboolean has_comma = FALSE;
2209 if (!sip || !uri) return;
2211 p_buddy = purple_find_buddy(sip->account, uri);
2213 if (!p_buddy) return;
2215 display_name = g_strdup(purple_buddy_get_alias(p_buddy));
2216 email = purple_blist_node_get_string(&p_buddy->node, EMAIL_PROP);
2218 if (!display_name && !email) return;
2220 /* if no display name, make "first last anything_else" out of email */
2221 if (email && !display_name) {
2222 display_name = g_strndup(email, strstr(email, "@") - email);
2223 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
2224 g_free(tmp);
2227 if (display_name) {
2228 has_comma = (strstr(display_name, ",") != NULL);
2229 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
2230 g_free(tmp);
2231 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
2232 g_free(tmp);
2235 parts = g_strsplit(display_name, " ", 0);
2237 if (!parts[0] || !parts[1]) {
2238 g_free(display_name);
2239 g_strfreev(parts);
2240 return;
2243 if (has_comma) {
2244 last = parts[0];
2245 first = parts[1];
2246 } else {
2247 first = parts[0];
2248 last = parts[1];
2251 if (first_name) {
2252 *first_name = g_strstrip(g_strdup(first));
2255 if (last_name) {
2256 *last_name = g_strstrip(g_strdup(last));
2259 g_free(display_name);
2260 g_strfreev(parts);
2264 * Update user information
2266 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2267 * @param property_name
2268 * @param property_value may be modified to strip white space
2270 static void
2271 sipe_update_user_info(struct sipe_core_private *sipe_private,
2272 const char *uri,
2273 const char *property_name,
2274 char *property_value)
2276 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2277 GSList *buddies, *entry;
2279 if (!property_name || strlen(property_name) == 0) return;
2281 if (property_value)
2282 property_value = g_strstrip(property_value);
2284 entry = buddies = purple_find_buddies(sip->account, uri); /* all buddies in different groups */
2285 while (entry) {
2286 const char *prop_str;
2287 const char *server_alias;
2288 PurpleBuddy *p_buddy = entry->data;
2290 /* for Display Name */
2291 if (sipe_strequal(property_name, ALIAS_PROP)) {
2292 if (property_value && sipe_is_bad_alias(uri, purple_buddy_get_alias(p_buddy))) {
2293 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
2294 purple_blist_alias_buddy(p_buddy, property_value);
2297 server_alias = purple_buddy_get_server_alias(p_buddy);
2298 if (!is_empty(property_value) &&
2299 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
2301 purple_blist_server_alias_buddy(p_buddy, property_value);
2304 /* for other properties */
2305 else {
2306 if (!is_empty(property_value)) {
2307 prop_str = purple_blist_node_get_string(&p_buddy->node, property_name);
2308 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
2309 purple_blist_node_set_string(&p_buddy->node, property_name, property_value);
2314 entry = entry->next;
2316 g_slist_free(buddies);
2320 * Update user phone
2321 * Suitable for both 2005 and 2007 systems.
2323 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
2324 * @param phone_type
2325 * @param phone may be modified to strip white space
2326 * @param phone_display_string may be modified to strip white space
2328 static void
2329 sipe_update_user_phone(struct sipe_core_private *sipe_private,
2330 const char *uri,
2331 const gchar *phone_type,
2332 gchar *phone,
2333 gchar *phone_display_string)
2335 const char *phone_node = PHONE_PROP; /* work phone by default */
2336 const char *phone_display_node = PHONE_DISPLAY_PROP; /* work phone by default */
2338 if(!phone || strlen(phone) == 0) return;
2340 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
2341 phone_node = PHONE_MOBILE_PROP;
2342 phone_display_node = PHONE_MOBILE_DISPLAY_PROP;
2343 } else if (sipe_strequal(phone_type, "home")) {
2344 phone_node = PHONE_HOME_PROP;
2345 phone_display_node = PHONE_HOME_DISPLAY_PROP;
2346 } else if (sipe_strequal(phone_type, "other")) {
2347 phone_node = PHONE_OTHER_PROP;
2348 phone_display_node = PHONE_OTHER_DISPLAY_PROP;
2349 } else if (sipe_strequal(phone_type, "custom1")) {
2350 phone_node = PHONE_CUSTOM1_PROP;
2351 phone_display_node = PHONE_CUSTOM1_DISPLAY_PROP;
2354 sipe_update_user_info(sipe_private, uri, phone_node, phone);
2355 if (phone_display_string) {
2356 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
2360 void
2361 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
2363 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
2365 /* Do in parallel.
2366 * If failed, the branch will be disabled for subsequent calls.
2367 * Can't rely that user turned the functionality on in account settings.
2369 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
2370 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
2372 /* schedule repeat */
2373 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
2374 "<+update-calendar>",
2375 NULL,
2376 UPDATE_CALENDAR_INTERVAL,
2377 (sipe_schedule_action)sipe_core_update_calendar,
2378 NULL);
2380 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
2384 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
2385 * by using standard Purple's means of signals and saved statuses.
2387 * Thus all UI elements get updated: Status Button with Note, docklet.
2388 * This is ablolutely important as both our status and note can come
2389 * inbound (roaming) or be updated programmatically (e.g. based on our
2390 * calendar data).
2392 static void
2393 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
2394 const char *status_id,
2395 const char *message,
2396 time_t do_not_publish[])
2398 PurpleStatus *status = purple_account_get_active_status(account);
2399 gboolean changed = TRUE;
2401 if (g_str_equal(status_id, purple_status_get_id(status)) &&
2402 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
2404 changed = FALSE;
2407 if (purple_savedstatus_is_idleaway()) {
2408 changed = FALSE;
2411 if (changed) {
2412 PurpleSavedStatus *saved_status;
2413 const PurpleStatusType *acct_status_type =
2414 purple_status_type_find_with_id(account->status_types, status_id);
2415 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
2416 sipe_activity activity = sipe_get_activity_by_token(status_id);
2418 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
2419 if (saved_status) {
2420 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
2423 /* If this type+message is unique then create a new transient saved status
2424 * Ref: gtkstatusbox.c
2426 if (!saved_status) {
2427 GList *tmp;
2428 GList *active_accts = purple_accounts_get_all_active();
2430 saved_status = purple_savedstatus_new(NULL, primitive);
2431 purple_savedstatus_set_message(saved_status, message);
2433 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
2434 purple_savedstatus_set_substatus(saved_status,
2435 (PurpleAccount *)tmp->data, acct_status_type, message);
2437 g_list_free(active_accts);
2440 do_not_publish[activity] = time(NULL);
2441 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
2442 status_id, (int)do_not_publish[activity]);
2444 /* Set the status for each account */
2445 purple_savedstatus_activate(saved_status);
2449 struct hash_table_delete_payload {
2450 GHashTable *hash_table;
2451 guint container;
2454 static void
2455 sipe_remove_category_container_publications_cb(const char *name,
2456 struct sipe_publication *publication,
2457 struct hash_table_delete_payload *payload)
2459 if (publication->container == payload->container) {
2460 g_hash_table_remove(payload->hash_table, name);
2463 static void
2464 sipe_remove_category_container_publications(GHashTable *our_publications,
2465 const char *category,
2466 guint container)
2468 struct hash_table_delete_payload payload;
2469 payload.hash_table = g_hash_table_lookup(our_publications, category);
2471 if (!payload.hash_table) return;
2473 payload.container = container;
2474 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
2477 static void
2478 send_publish_category_initial(struct sipe_core_private *sipe_private);
2481 * When we receive some self (BE) NOTIFY with a new subscriber
2482 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2485 static void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
2486 struct sipmsg *msg)
2488 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2489 gchar *contact;
2490 gchar *to;
2491 sipe_xml *xml;
2492 const sipe_xml *node;
2493 const sipe_xml *node2;
2494 char *display_name = NULL;
2495 char *uri;
2496 GSList *category_names = NULL;
2497 int aggreg_avail = 0;
2498 gboolean do_update_status = FALSE;
2499 gboolean has_note_cleaned = FALSE;
2501 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
2503 xml = sipe_xml_parse(msg->body, msg->bodylen);
2504 if (!xml) return;
2506 contact = get_contact(sipe_private);
2507 to = sip_uri_self(sipe_private);
2510 /* categories */
2511 /* set list of categories participating in this XML */
2512 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2513 const gchar *name = sipe_xml_attribute(node, "name");
2514 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
2516 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
2517 category_names ? (int) g_slist_length(category_names) : -1);
2518 /* drop category information */
2519 if (category_names) {
2520 GSList *entry = category_names;
2521 while (entry) {
2522 GHashTable *cat_publications;
2523 const gchar *category = entry->data;
2524 entry = entry->next;
2525 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
2526 cat_publications = g_hash_table_lookup(sip->our_publications, category);
2527 if (cat_publications) {
2528 g_hash_table_remove(sip->our_publications, category);
2529 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
2533 g_slist_free(category_names);
2534 /* filling our categories reflected in roaming data */
2535 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
2536 const char *tmp;
2537 const gchar *name = sipe_xml_attribute(node, "name");
2538 guint container = sipe_xml_int_attribute(node, "container", -1);
2539 guint instance = sipe_xml_int_attribute(node, "instance", -1);
2540 guint version = sipe_xml_int_attribute(node, "version", 0);
2541 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
2542 sipe_utils_str_to_time(tmp) : 0;
2543 gchar *key;
2544 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
2546 /* Ex. clear note: <category name="note"/> */
2547 if (container == (guint)-1) {
2548 g_free(sip->note);
2549 sip->note = NULL;
2550 do_update_status = TRUE;
2551 continue;
2554 /* Ex. clear note: <category name="note" container="200"/> */
2555 if (instance == (guint)-1) {
2556 if (container == 200) {
2557 g_free(sip->note);
2558 sip->note = NULL;
2559 do_update_status = TRUE;
2561 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
2562 sipe_remove_category_container_publications(
2563 sip->our_publications, name, container);
2564 continue;
2567 /* key is <category><instance><container> */
2568 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
2569 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
2571 /* capture all userState publication for later clean up if required */
2572 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
2573 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2575 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
2576 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2577 publication->category = g_strdup(name);
2578 publication->instance = instance;
2579 publication->container = container;
2580 publication->version = version;
2582 if (!sip->user_state_publications) {
2583 sip->user_state_publications = g_hash_table_new_full(
2584 g_str_hash, g_str_equal,
2585 g_free, (GDestroyNotify)free_publication);
2587 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
2588 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
2589 key, version);
2593 if (sipe_is_our_publication(sipe_private, key)) {
2594 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2596 publication->category = g_strdup(name);
2597 publication->instance = instance;
2598 publication->container = container;
2599 publication->version = version;
2601 /* filling publication->availability */
2602 if (sipe_strequal(name, "state")) {
2603 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2604 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2606 if (xn_avail) {
2607 gchar *avail_str = sipe_xml_data(xn_avail);
2608 if (avail_str) {
2609 publication->availability = atoi(avail_str);
2611 g_free(avail_str);
2613 /* for calendarState */
2614 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
2615 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
2616 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
2618 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
2619 if (xn_activity) {
2620 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
2621 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
2623 event->is_meeting = TRUE;
2626 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
2627 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
2629 publication->cal_event_hash = sipe_cal_event_hash(event);
2630 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
2631 publication->cal_event_hash);
2632 sipe_cal_event_free(event);
2635 /* filling publication->note */
2636 if (sipe_strequal(name, "note")) {
2637 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
2639 if (!has_note_cleaned) {
2640 has_note_cleaned = TRUE;
2642 g_free(sip->note);
2643 sip->note = NULL;
2644 sip->note_since = publish_time;
2646 do_update_status = TRUE;
2649 g_free(publication->note);
2650 publication->note = NULL;
2651 if (xn_body) {
2652 char *tmp;
2654 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
2655 g_free(tmp);
2656 if (publish_time >= sip->note_since) {
2657 g_free(sip->note);
2658 sip->note = g_strdup(publication->note);
2659 sip->note_since = publish_time;
2660 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
2662 do_update_status = TRUE;
2667 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
2668 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
2669 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
2670 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
2671 if (xn_free_busy) {
2672 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2673 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
2675 if (xn_working_hours) {
2676 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
2680 if (!cat_publications) {
2681 cat_publications = g_hash_table_new_full(
2682 g_str_hash, g_str_equal,
2683 g_free, (GDestroyNotify)free_publication);
2684 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
2685 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
2687 g_hash_table_insert(cat_publications, g_strdup(key), publication);
2688 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
2690 g_free(key);
2692 /* aggregateState (not an our publication) from 2-nd container */
2693 if (sipe_strequal(name, "state") && container == 2) {
2694 const sipe_xml *xn_state = sipe_xml_child(node, "state");
2696 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
2697 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
2699 if (xn_avail) {
2700 gchar *avail_str = sipe_xml_data(xn_avail);
2701 if (avail_str) {
2702 aggreg_avail = atoi(avail_str);
2704 g_free(avail_str);
2707 do_update_status = TRUE;
2711 /* userProperties published by server from AD */
2712 if (!sip->csta && sipe_strequal(name, "userProperties")) {
2713 const sipe_xml *line;
2714 /* line, for Remote Call Control (RCC) */
2715 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
2716 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
2717 const gchar *line_type = sipe_xml_attribute(line, "lineType");
2718 gchar *line_uri;
2720 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
2722 line_uri = sipe_xml_data(line);
2723 if (line_uri) {
2724 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
2725 sip_csta_open(sipe_private, line_uri, line_server);
2727 g_free(line_uri);
2729 break;
2733 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
2734 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2736 /* containers */
2737 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
2738 guint id = sipe_xml_int_attribute(node, "id", 0);
2739 struct sipe_container *container = sipe_find_container(sipe_private, id);
2741 if (container) {
2742 sip->containers = g_slist_remove(sip->containers, container);
2743 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
2744 free_container(container);
2746 container = g_new0(struct sipe_container, 1);
2747 container->id = id;
2748 container->version = sipe_xml_int_attribute(node, "version", 0);
2749 sip->containers = g_slist_append(sip->containers, container);
2750 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
2752 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
2753 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2754 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
2755 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
2756 container->members = g_slist_append(container->members, member);
2757 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2758 member->type, member->value ? member->value : "");
2762 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2763 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2764 char *container_xmls = NULL;
2765 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2766 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2768 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2769 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2770 /* initial set-up to let counterparties see your status */
2771 if (sameEnterpriseAL < 0) {
2772 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2773 guint version = container ? container->version : 0;
2774 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2776 if (federatedAL < 0) {
2777 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2778 guint version = container ? container->version : 0;
2779 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2781 sip->access_level_set = TRUE;
2783 if (container_xmls) {
2784 sipe_send_set_container_members(sipe_private, container_xmls);
2786 g_free(container_xmls);
2789 /* Refresh contacts' blocked status */
2790 sipe_refresh_blocked_status(sipe_private);
2792 /* subscribers */
2793 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2794 const char *user;
2795 const char *acknowledged;
2796 gchar *hdr;
2797 gchar *body;
2799 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2800 if (!user) continue;
2801 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2802 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2803 uri = sip_uri_from_name(user);
2805 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
2807 acknowledged= sipe_xml_attribute(node, "acknowledged");
2808 if(sipe_strcase_equal(acknowledged,"false")){
2809 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2810 if (!purple_find_buddy(sip->account, uri)) {
2811 purple_account_request_add(sip->account, uri, _("you"), display_name, NULL);
2814 hdr = g_strdup_printf(
2815 "Contact: %s\r\n"
2816 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2818 body = g_strdup_printf(
2819 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2820 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2821 "</setSubscribers>", user);
2823 sip_transport_service(sipe_private,
2825 hdr,
2826 body,
2827 NULL);
2828 g_free(body);
2829 g_free(hdr);
2831 g_free(display_name);
2832 g_free(uri);
2835 g_free(contact);
2836 sipe_xml_free(xml);
2838 /* Publish initial state if not yet.
2839 * Assuming this happens on initial responce to subscription to roaming-self
2840 * so we've already updated our roaming data in full.
2841 * Only for 2007+
2843 if (!sip->initial_state_published) {
2844 send_publish_category_initial(sipe_private);
2845 sip->initial_state_published = TRUE;
2846 /* dalayed run */
2847 sipe_schedule_seconds(sipe_private,
2848 "<+update-calendar>",
2849 NULL,
2850 UPDATE_CALENDAR_DELAY,
2851 (sipe_schedule_action)sipe_core_update_calendar,
2852 NULL);
2853 do_update_status = FALSE;
2854 } else if (aggreg_avail) {
2856 g_free(sip->status);
2857 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2858 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2859 } else {
2860 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2864 if (do_update_status) {
2865 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2866 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2869 g_free(to);
2872 /* IM Session (INVITE and MESSAGE methods) */
2874 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2875 static gchar *
2876 get_end_points (struct sipe_core_private *sipe_private,
2877 struct sip_session *session)
2879 gchar *res;
2881 if (session == NULL) {
2882 return NULL;
2885 res = g_strdup_printf("<sip:%s>", sipe_private->username);
2887 SIPE_DIALOG_FOREACH {
2888 gchar *tmp = res;
2889 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2890 g_free(tmp);
2892 if (dialog->theirepid) {
2893 tmp = res;
2894 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2895 g_free(tmp);
2897 } SIPE_DIALOG_FOREACH_END;
2899 return res;
2902 static gboolean
2903 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2904 struct sipmsg *msg,
2905 SIPE_UNUSED_PARAMETER struct transaction *trans)
2907 gboolean ret = TRUE;
2909 if (msg->response != 200) {
2910 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2911 return FALSE;
2914 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2916 return ret;
2920 * Asks UA/proxy about its capabilities.
2922 static void sipe_options_request(struct sipe_core_private *sipe_private,
2923 const char *who)
2925 gchar *to = sip_uri(who);
2926 gchar *contact = get_contact(sipe_private);
2927 gchar *request = g_strdup_printf(
2928 "Accept: application/sdp\r\n"
2929 "Contact: %s\r\n", contact);
2930 g_free(contact);
2932 sip_transport_request(sipe_private,
2933 "OPTIONS",
2936 request,
2937 NULL,
2938 NULL,
2939 process_options_response);
2941 g_free(to);
2942 g_free(request);
2945 static void
2946 sipe_notify_user(struct sipe_core_private *sipe_private,
2947 struct sip_session *session,
2948 PurpleMessageFlags flags,
2949 const gchar *message)
2951 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2952 PurpleConversation *conv;
2954 if (!session->backend_session) {
2955 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
2956 } else {
2957 /* TEMPORARY HACK!! */
2958 conv = (PurpleConversation *) session->backend_session;
2960 purple_conversation_write(conv, NULL, message, flags, time(NULL));
2963 void
2964 sipe_present_info(struct sipe_core_private *sipe_private,
2965 struct sip_session *session,
2966 const gchar *message)
2968 sipe_notify_user(sipe_private, session, PURPLE_MESSAGE_SYSTEM, message);
2971 void
2972 sipe_present_err(struct sipe_core_private *sipe_private,
2973 struct sip_session *session,
2974 const gchar *message)
2976 sipe_notify_user(sipe_private, session, PURPLE_MESSAGE_ERROR, message);
2979 void
2980 sipe_present_message_undelivered_err(struct sipe_core_private *sipe_private,
2981 struct sip_session *session,
2982 int sip_error,
2983 int sip_warning,
2984 const gchar *who,
2985 const gchar *message)
2987 char *msg, *msg_tmp, *msg_tmp2;
2988 const char *label;
2990 msg_tmp = message ? sipe_backend_markup_strip_html(message) : NULL;
2991 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2992 g_free(msg_tmp);
2993 /* Service unavailable; Server Internal Error; Server Time-out */
2994 if (sip_error == 606 && sip_warning == 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
2995 label = _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
2996 g_free(msg);
2997 msg = NULL;
2998 } else if (sip_error == 503 || sip_error == 500 || sip_error == 504) {
2999 label = _("This message was not delivered to %s because the service is not available");
3000 } else if (sip_error == 486) { /* Busy Here */
3001 label = _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3002 } else if (sip_error == 415) { /* Unsupported media type */
3003 label = _("This message was not delivered to %s because one or more recipients don't support this type of message");
3004 } else {
3005 label = _("This message was not delivered to %s because one or more recipients are offline");
3008 msg_tmp = g_strdup_printf( "%s%s\n%s" ,
3009 msg_tmp2 = g_strdup_printf(label, who ? who : ""),
3010 msg ? ":" : "",
3011 msg ? msg : "");
3012 sipe_present_err(sipe_private, session, msg_tmp);
3013 g_free(msg_tmp2);
3014 g_free(msg_tmp);
3015 g_free(msg);
3019 static gboolean
3020 process_message_response(struct sipe_core_private *sipe_private,
3021 struct sipmsg *msg,
3022 SIPE_UNUSED_PARAMETER struct transaction *trans)
3024 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3025 gboolean ret = TRUE;
3026 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3027 struct sip_session *session = sipe_session_find_im(sipe_private, with);
3028 struct sip_dialog *dialog;
3029 gchar *cseq;
3030 char *key;
3031 struct queued_message *message;
3033 if (!session) {
3034 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
3035 g_free(with);
3036 return FALSE;
3039 dialog = sipe_dialog_find(session, with);
3040 if (!dialog) {
3041 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
3042 g_free(with);
3043 return FALSE;
3046 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3047 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
3048 g_free(cseq);
3049 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3051 if (msg->response >= 400) {
3052 PurpleBuddy *pbuddy;
3053 const char *alias = with;
3054 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3055 int warning = -1;
3057 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
3059 if (warn_hdr) {
3060 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3061 if (parts[0]) {
3062 warning = atoi(parts[0]);
3064 g_strfreev(parts);
3067 /* cancel file transfer as rejected by server */
3068 if (msg->response == 606 && /* Not acceptable all. */
3069 warning == 309 && /* Message contents not allowed by policy */
3070 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3072 GSList *parsed_body = sipe_ft_parse_msg_body(msg->body);
3073 sipe_ft_incoming_cancel(dialog, parsed_body);
3074 sipe_utils_nameval_free(parsed_body);
3077 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3078 alias = purple_buddy_get_alias(pbuddy);
3081 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, (message ? message->body : NULL));
3083 /* drop dangling IM sessions: assume that BYE from remote never reached us */
3084 if (msg->response == 408 || /* Request timeout */
3085 msg->response == 480 || /* Temporarily Unavailable */
3086 msg->response == 481) { /* Call/Transaction Does Not Exist */
3087 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
3088 sip_transport_bye(sipe_private, dialog);
3090 /* We might not get a valid reply to our BYE,
3091 so make sure the dialog is removed for sure. */
3092 sipe_dialog_remove(session, with);
3093 dialog = NULL;
3096 ret = FALSE;
3097 } else {
3098 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3099 if (message_id) {
3100 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
3101 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
3102 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3105 g_hash_table_remove(session->unconfirmed_messages, key);
3106 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
3107 key, g_hash_table_size(session->unconfirmed_messages));
3110 g_free(key);
3111 g_free(with);
3113 if (ret) sipe_im_process_queue(sipe_private, session);
3114 return ret;
3117 static void sipe_send_message(struct sipe_core_private *sipe_private,
3118 struct sip_dialog *dialog,
3119 const char *msg, const char *content_type)
3121 gchar *hdr;
3122 gchar *tmp;
3123 char *msgtext = NULL;
3124 const gchar *msgr = "";
3125 gchar *tmp2 = NULL;
3127 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
3128 char *msgformat;
3129 gchar *msgr_value;
3131 sipe_parse_html(msg, &msgformat, &msgtext);
3132 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
3134 msgr_value = sipmsg_get_msgr_string(msgformat);
3135 g_free(msgformat);
3136 if (msgr_value) {
3137 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
3138 g_free(msgr_value);
3140 } else {
3141 msgtext = g_strdup(msg);
3144 tmp = get_contact(sipe_private);
3145 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3146 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3147 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3148 if (content_type == NULL)
3149 content_type = "text/plain";
3151 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
3152 g_free(tmp);
3153 g_free(tmp2);
3155 sip_transport_request(sipe_private,
3156 "MESSAGE",
3157 dialog->with,
3158 dialog->with,
3159 hdr,
3160 msgtext,
3161 dialog,
3162 process_message_response);
3163 g_free(msgtext);
3164 g_free(hdr);
3168 void
3169 sipe_im_process_queue (struct sipe_core_private *sipe_private,
3170 struct sip_session * session)
3172 GSList *entry2 = session->outgoing_message_queue;
3173 while (entry2) {
3174 struct queued_message *msg = entry2->data;
3176 /* for multiparty chat or conference */
3177 if (session->is_multiparty || session->focus_uri) {
3178 gchar *who = sip_uri_self(sipe_private);
3179 sipe_backend_chat_message(SIPE_CORE_PUBLIC,
3180 session->chat_id,
3181 who,
3182 msg->body);
3183 g_free(who);
3186 SIPE_DIALOG_FOREACH {
3187 char *key;
3188 struct queued_message *message;
3190 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3192 message = g_new0(struct queued_message,1);
3193 message->body = g_strdup(msg->body);
3194 if (msg->content_type != NULL)
3195 message->content_type = g_strdup(msg->content_type);
3197 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3198 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3199 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
3200 key, g_hash_table_size(session->unconfirmed_messages));
3201 g_free(key);
3203 sipe_send_message(sipe_private, dialog, msg->body, msg->content_type);
3204 } SIPE_DIALOG_FOREACH_END;
3206 entry2 = sipe_session_dequeue_message(session);
3210 static void
3211 sipe_refer_notify(struct sipe_core_private *sipe_private,
3212 struct sip_session *session,
3213 const gchar *who,
3214 int status,
3215 const gchar *desc)
3217 gchar *hdr;
3218 gchar *body;
3219 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3221 hdr = g_strdup_printf(
3222 "Event: refer\r\n"
3223 "Subscription-State: %s\r\n"
3224 "Content-Type: message/sipfrag\r\n",
3225 status >= 200 ? "terminated" : "active");
3227 body = g_strdup_printf(
3228 "SIP/2.0 %d %s\r\n",
3229 status, desc);
3231 sip_transport_request(sipe_private,
3232 "NOTIFY",
3233 who,
3234 who,
3235 hdr,
3236 body,
3237 dialog,
3238 NULL);
3240 g_free(hdr);
3241 g_free(body);
3244 static gboolean
3245 process_invite_response(struct sipe_core_private *sipe_private,
3246 struct sipmsg *msg, struct transaction *trans)
3248 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3249 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3250 struct sip_session *session;
3251 struct sip_dialog *dialog;
3252 char *cseq;
3253 char *key;
3254 struct queued_message *message;
3255 struct sipmsg *request_msg = trans->msg;
3257 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
3258 gchar *referred_by;
3260 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
3261 if (!session) {
3262 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
3263 g_free(with);
3264 return FALSE;
3267 dialog = sipe_dialog_find(session, with);
3268 if (!dialog) {
3269 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
3270 g_free(with);
3271 return FALSE;
3274 sipe_dialog_parse(dialog, msg, TRUE);
3276 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3277 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
3278 g_free(cseq);
3279 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3281 if (msg->response != 200) {
3282 PurpleBuddy *pbuddy;
3283 const char *alias = with;
3284 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3285 int warning = -1;
3287 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
3289 if (warn_hdr) {
3290 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3291 if (parts[0]) {
3292 warning = atoi(parts[0]);
3294 g_strfreev(parts);
3297 /* cancel file transfer as rejected by server */
3298 if (msg->response == 606 && /* Not acceptable all. */
3299 warning == 309 && /* Message contents not allowed by policy */
3300 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3302 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
3303 sipe_ft_incoming_cancel(dialog, parsed_body);
3304 sipe_utils_nameval_free(parsed_body);
3307 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3308 alias = purple_buddy_get_alias(pbuddy);
3311 if (message) {
3312 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, message->body);
3313 } else {
3314 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
3315 sipe_present_err(sipe_private, session, tmp_msg);
3316 g_free(tmp_msg);
3319 sipe_dialog_remove(session, with);
3321 g_free(key);
3322 g_free(with);
3323 return FALSE;
3326 dialog->cseq = 0;
3327 sip_transport_ack(sipe_private, dialog);
3328 dialog->outgoing_invite = NULL;
3329 dialog->is_established = TRUE;
3331 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3332 if (referred_by) {
3333 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
3334 g_free(referred_by);
3337 /* add user to chat if it is a multiparty session */
3338 if (session->is_multiparty) {
3339 sipe_backend_chat_add(session->backend_session,
3340 with,
3341 TRUE);
3344 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3345 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
3346 sipe_session_dequeue_message(session);
3349 sipe_im_process_queue(sipe_private, session);
3351 g_hash_table_remove(session->unconfirmed_messages, key);
3352 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
3353 key, g_hash_table_size(session->unconfirmed_messages));
3355 g_free(key);
3356 g_free(with);
3357 return TRUE;
3361 void
3362 sipe_invite(struct sipe_core_private *sipe_private,
3363 struct sip_session *session,
3364 const gchar *who,
3365 const gchar *msg_body,
3366 const gchar *msg_content_type,
3367 const gchar *referred_by,
3368 const gboolean is_triggered)
3370 gchar *hdr;
3371 gchar *to;
3372 gchar *contact;
3373 gchar *body;
3374 gchar *self;
3375 char *ms_text_format = NULL;
3376 gchar *roster_manager;
3377 gchar *end_points;
3378 gchar *referred_by_str;
3379 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3381 if (dialog && dialog->is_established) {
3382 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
3383 return;
3386 if (!dialog) {
3387 dialog = sipe_dialog_add(session);
3388 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3389 dialog->with = g_strdup(who);
3392 if (!(dialog->ourtag)) {
3393 dialog->ourtag = gentag();
3396 to = sip_uri(who);
3398 if (msg_body) {
3399 char *msgtext = NULL;
3400 char *base64_msg;
3401 const gchar *msgr = "";
3402 char *key;
3403 struct queued_message *message;
3404 gchar *tmp = NULL;
3406 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
3407 char *msgformat;
3408 gchar *msgr_value;
3410 sipe_parse_html(msg_body, &msgformat, &msgtext);
3411 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
3413 msgr_value = sipmsg_get_msgr_string(msgformat);
3414 g_free(msgformat);
3415 if (msgr_value) {
3416 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
3417 g_free(msgr_value);
3419 } else {
3420 msgtext = g_strdup(msg_body);
3423 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
3424 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
3425 msg_content_type ? msg_content_type : "text/plain",
3426 msgr,
3427 base64_msg);
3428 g_free(msgtext);
3429 g_free(tmp);
3430 g_free(base64_msg);
3432 message = g_new0(struct queued_message,1);
3433 message->body = g_strdup(msg_body);
3434 if (msg_content_type != NULL)
3435 message->content_type = g_strdup(msg_content_type);
3437 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3438 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3439 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
3440 key, g_hash_table_size(session->unconfirmed_messages));
3441 g_free(key);
3444 contact = get_contact(sipe_private);
3445 end_points = get_end_points(sipe_private, session);
3446 self = sip_uri_self(sipe_private);
3447 roster_manager = g_strdup_printf(
3448 "Roster-Manager: %s\r\n"
3449 "EndPoints: %s\r\n",
3450 self,
3451 end_points);
3452 referred_by_str = referred_by ?
3453 g_strdup_printf(
3454 "Referred-By: %s\r\n",
3455 referred_by)
3456 : g_strdup("");
3457 hdr = g_strdup_printf(
3458 "Supported: ms-sender\r\n"
3459 "%s"
3460 "%s"
3461 "%s"
3462 "%s"
3463 "Contact: %s\r\n%s"
3464 "Content-Type: application/sdp\r\n",
3465 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
3466 referred_by_str,
3467 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3468 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3469 contact,
3470 ms_text_format ? ms_text_format : "");
3471 g_free(ms_text_format);
3472 g_free(self);
3474 body = g_strdup_printf(
3475 "v=0\r\n"
3476 "o=- 0 0 IN IP4 %s\r\n"
3477 "s=session\r\n"
3478 "c=IN IP4 %s\r\n"
3479 "t=0 0\r\n"
3480 "m=%s %d sip null\r\n"
3481 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
3482 sipe_backend_network_ip_address(),
3483 sipe_backend_network_ip_address(),
3484 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
3485 sip_transport_port(sipe_private));
3487 dialog->outgoing_invite = sip_transport_request(sipe_private,
3488 "INVITE",
3491 hdr,
3492 body,
3493 dialog,
3494 process_invite_response);
3496 g_free(to);
3497 g_free(roster_manager);
3498 g_free(end_points);
3499 g_free(referred_by_str);
3500 g_free(body);
3501 g_free(hdr);
3502 g_free(contact);
3505 void
3506 sipe_convo_closed(PurpleConnection * gc, const char *who)
3508 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3510 SIPE_DEBUG_INFO("conversation with %s closed", who);
3511 sipe_session_close(sipe_private,
3512 sipe_session_find_im(sipe_private, who));
3515 void
3516 sipe_chat_leave (PurpleConnection *gc, int id)
3518 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3519 struct sip_session *session = sipe_session_find_chat_by_id(sipe_private,
3520 id);
3522 sipe_session_close(sipe_private, session);
3525 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3526 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3528 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3529 struct sip_session *session;
3530 struct sip_dialog *dialog;
3531 gchar *uri = sip_uri(who);
3533 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
3535 session = sipe_session_find_or_add_im(sipe_private, uri);
3536 dialog = sipe_dialog_find(session, uri);
3538 // Queue the message
3539 sipe_session_enqueue_message(session, what, NULL);
3541 if (dialog && !dialog->outgoing_invite) {
3542 sipe_im_process_queue(sipe_private, session);
3543 } else if (!dialog || !dialog->outgoing_invite) {
3544 // Need to send the INVITE to get the outgoing dialog setup
3545 sipe_invite(sipe_private, session, uri, what, NULL, NULL, FALSE);
3548 g_free(uri);
3549 return 1;
3552 int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
3553 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3555 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3556 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3557 struct sip_session *session;
3559 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
3561 session = sipe_session_find_chat_by_id(sipe_private, id);
3563 // Queue the message
3564 if (session && session->dialogs) {
3565 sipe_session_enqueue_message(session,what,NULL);
3566 sipe_im_process_queue(sipe_private, session);
3567 } else if (sip) {
3568 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
3569 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
3571 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
3572 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
3574 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
3575 struct sip_session *session = sipe_session_add_chat(sipe_private);
3577 session->is_multiparty = FALSE;
3578 session->focus_uri = g_strdup(proto_chat_id);
3579 sipe_session_enqueue_message(session, what, NULL);
3580 sipe_invite_conf_focus(sipe_private, session);
3584 return 1;
3588 * Returns 2005-style activity and Availability.
3590 * @param status Sipe statis id.
3592 static void
3593 sipe_get_act_avail_by_status_2005(const char *status,
3594 int *activity,
3595 int *availability)
3597 int avail = 300; /* online */
3598 int act = 400; /* Available */
3600 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
3601 act = 100;
3602 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
3603 // act = 150;
3604 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
3605 act = 300;
3606 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
3607 act = 400;
3608 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
3609 // act = 500;
3610 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
3611 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
3612 act = 600;
3613 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
3614 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
3615 avail = 0; /* offline */
3616 act = 100;
3617 } else {
3618 act = 400; /* Available */
3621 if (activity) *activity = act;
3622 if (availability) *availability = avail;
3626 * [MS-SIP] 2.2.1
3628 * @param activity 2005 aggregated activity. Ex.: 600
3629 * @param availablity 2005 aggregated availablity. Ex.: 300
3631 static const char *
3632 sipe_get_status_by_act_avail_2005(const int activity,
3633 const int availablity,
3634 char **activity_desc)
3636 const char *status_id = NULL;
3637 const char *act = NULL;
3639 if (activity < 150) {
3640 status_id = SIPE_STATUS_ID_AWAY;
3641 } else if (activity < 200) {
3642 //status_id = SIPE_STATUS_ID_LUNCH;
3643 status_id = SIPE_STATUS_ID_AWAY;
3644 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
3645 } else if (activity < 300) {
3646 //status_id = SIPE_STATUS_ID_IDLE;
3647 status_id = SIPE_STATUS_ID_AWAY;
3648 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3649 } else if (activity < 400) {
3650 status_id = SIPE_STATUS_ID_BRB;
3651 } else if (activity < 500) {
3652 status_id = SIPE_STATUS_ID_AVAILABLE;
3653 } else if (activity < 600) {
3654 //status_id = SIPE_STATUS_ID_ON_PHONE;
3655 status_id = SIPE_STATUS_ID_BUSY;
3656 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
3657 } else if (activity < 700) {
3658 status_id = SIPE_STATUS_ID_BUSY;
3659 } else if (activity < 800) {
3660 status_id = SIPE_STATUS_ID_AWAY;
3661 } else {
3662 status_id = SIPE_STATUS_ID_AVAILABLE;
3665 if (availablity < 100)
3666 status_id = SIPE_STATUS_ID_OFFLINE;
3668 if (activity_desc && act) {
3669 g_free(*activity_desc);
3670 *activity_desc = g_strdup(act);
3673 return status_id;
3677 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
3679 static const char*
3680 sipe_get_status_by_availability(int avail,
3681 char** activity_desc)
3683 const char *status;
3684 const char *act = NULL;
3686 if (avail < 3000) {
3687 status = SIPE_STATUS_ID_OFFLINE;
3688 } else if (avail < 4500) {
3689 status = SIPE_STATUS_ID_AVAILABLE;
3690 } else if (avail < 6000) {
3691 //status = SIPE_STATUS_ID_IDLE;
3692 status = SIPE_STATUS_ID_AVAILABLE;
3693 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3694 } else if (avail < 7500) {
3695 status = SIPE_STATUS_ID_BUSY;
3696 } else if (avail < 9000) {
3697 //status = SIPE_STATUS_ID_BUSYIDLE;
3698 status = SIPE_STATUS_ID_BUSY;
3699 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
3700 } else if (avail < 12000) {
3701 status = SIPE_STATUS_ID_DND;
3702 } else if (avail < 15000) {
3703 status = SIPE_STATUS_ID_BRB;
3704 } else if (avail < 18000) {
3705 status = SIPE_STATUS_ID_AWAY;
3706 } else {
3707 status = SIPE_STATUS_ID_OFFLINE;
3710 if (activity_desc && act) {
3711 g_free(*activity_desc);
3712 *activity_desc = g_strdup(act);
3715 return status;
3719 * Returns 2007-style availability value
3721 * @param sipe_status_id (in)
3722 * @param activity_token (out) Must be g_free()'d after use if consumed.
3724 static int
3725 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
3727 int availability;
3728 sipe_activity activity = SIPE_ACTIVITY_UNSET;
3730 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
3731 availability = 15500;
3732 if (!activity_token || !(*activity_token)) {
3733 activity = SIPE_ACTIVITY_AWAY;
3735 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
3736 availability = 12500;
3737 activity = SIPE_ACTIVITY_BRB;
3738 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
3739 availability = 9500;
3740 activity = SIPE_ACTIVITY_DND;
3741 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
3742 availability = 6500;
3743 if (!activity_token || !(*activity_token)) {
3744 activity = SIPE_ACTIVITY_BUSY;
3746 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
3747 availability = 3500;
3748 activity = SIPE_ACTIVITY_ONLINE;
3749 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
3750 availability = 0;
3751 } else {
3752 // Offline or invisible
3753 availability = 18500;
3754 activity = SIPE_ACTIVITY_OFFLINE;
3757 if (activity_token) {
3758 *activity_token = g_strdup(sipe_activity_map[activity].token);
3760 return availability;
3763 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
3764 const gchar *data, unsigned len)
3766 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3767 const char *uri;
3768 sipe_xml *xn_categories;
3769 const sipe_xml *xn_category;
3770 const char *status = NULL;
3771 gboolean do_update_status = FALSE;
3772 gboolean has_note_cleaned = FALSE;
3773 gboolean has_free_busy_cleaned = FALSE;
3775 xn_categories = sipe_xml_parse(data, len);
3776 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
3778 for (xn_category = sipe_xml_child(xn_categories, "category");
3779 xn_category ;
3780 xn_category = sipe_xml_twin(xn_category) )
3782 const sipe_xml *xn_node;
3783 const char *tmp;
3784 const char *attrVar = sipe_xml_attribute(xn_category, "name");
3785 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
3786 sipe_utils_str_to_time(tmp) : 0;
3788 /* contactCard */
3789 if (sipe_strequal(attrVar, "contactCard"))
3791 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
3793 if (card) {
3794 const sipe_xml *node;
3795 /* identity - Display Name and email */
3796 node = sipe_xml_child(card, "identity");
3797 if (node) {
3798 char* display_name = sipe_xml_data(
3799 sipe_xml_child(node, "name/displayName"));
3800 char* email = sipe_xml_data(
3801 sipe_xml_child(node, "email"));
3803 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
3804 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
3806 g_free(display_name);
3807 g_free(email);
3809 /* company */
3810 node = sipe_xml_child(card, "company");
3811 if (node) {
3812 char* company = sipe_xml_data(node);
3813 sipe_update_user_info(sipe_private, uri, COMPANY_PROP, company);
3814 g_free(company);
3816 /* department */
3817 node = sipe_xml_child(card, "department");
3818 if (node) {
3819 char* department = sipe_xml_data(node);
3820 sipe_update_user_info(sipe_private, uri, DEPARTMENT_PROP, department);
3821 g_free(department);
3823 /* title */
3824 node = sipe_xml_child(card, "title");
3825 if (node) {
3826 char* title = sipe_xml_data(node);
3827 sipe_update_user_info(sipe_private, uri, TITLE_PROP, title);
3828 g_free(title);
3830 /* office */
3831 node = sipe_xml_child(card, "office");
3832 if (node) {
3833 char* office = sipe_xml_data(node);
3834 sipe_update_user_info(sipe_private, uri, OFFICE_PROP, office);
3835 g_free(office);
3837 /* site (url) */
3838 node = sipe_xml_child(card, "url");
3839 if (node) {
3840 char* site = sipe_xml_data(node);
3841 sipe_update_user_info(sipe_private, uri, SITE_PROP, site);
3842 g_free(site);
3844 /* phone */
3845 for (node = sipe_xml_child(card, "phone");
3846 node;
3847 node = sipe_xml_twin(node))
3849 const char *phone_type = sipe_xml_attribute(node, "type");
3850 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3851 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3853 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3855 g_free(phone);
3856 g_free(phone_display_string);
3858 /* address */
3859 for (node = sipe_xml_child(card, "address");
3860 node;
3861 node = sipe_xml_twin(node))
3863 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3864 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3865 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3866 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3867 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3868 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3870 sipe_update_user_info(sipe_private, uri, ADDRESS_STREET_PROP, street);
3871 sipe_update_user_info(sipe_private, uri, ADDRESS_CITY_PROP, city);
3872 sipe_update_user_info(sipe_private, uri, ADDRESS_STATE_PROP, state);
3873 sipe_update_user_info(sipe_private, uri, ADDRESS_ZIPCODE_PROP, zipcode);
3874 sipe_update_user_info(sipe_private, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
3876 g_free(street);
3877 g_free(city);
3878 g_free(state);
3879 g_free(zipcode);
3880 g_free(country_code);
3882 break;
3887 /* note */
3888 else if (sipe_strequal(attrVar, "note"))
3890 if (uri) {
3891 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3893 if (!has_note_cleaned) {
3894 has_note_cleaned = TRUE;
3896 g_free(sbuddy->note);
3897 sbuddy->note = NULL;
3898 sbuddy->is_oof_note = FALSE;
3899 sbuddy->note_since = publish_time;
3901 do_update_status = TRUE;
3903 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3904 /* clean up in case no 'note' element is supplied
3905 * which indicate note removal in client
3907 g_free(sbuddy->note);
3908 sbuddy->note = NULL;
3909 sbuddy->is_oof_note = FALSE;
3910 sbuddy->note_since = publish_time;
3912 xn_node = sipe_xml_child(xn_category, "note/body");
3913 if (xn_node) {
3914 char *tmp;
3915 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3916 g_free(tmp);
3917 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3918 sbuddy->note_since = publish_time;
3920 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3921 uri, sbuddy->note ? sbuddy->note : "");
3923 /* to trigger UI refresh in case no status info is supplied in this update */
3924 do_update_status = TRUE;
3928 /* state */
3929 else if(sipe_strequal(attrVar, "state"))
3931 char *tmp;
3932 int availability;
3933 const sipe_xml *xn_availability;
3934 const sipe_xml *xn_activity;
3935 const sipe_xml *xn_meeting_subject;
3936 const sipe_xml *xn_meeting_location;
3937 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3939 xn_node = sipe_xml_child(xn_category, "state");
3940 if (!xn_node) continue;
3941 xn_availability = sipe_xml_child(xn_node, "availability");
3942 if (!xn_availability) continue;
3943 xn_activity = sipe_xml_child(xn_node, "activity");
3944 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3945 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3947 tmp = sipe_xml_data(xn_availability);
3948 availability = atoi(tmp);
3949 g_free(tmp);
3951 /* activity, meeting_subject, meeting_location */
3952 if (sbuddy) {
3953 char *tmp = NULL;
3955 /* activity */
3956 g_free(sbuddy->activity);
3957 sbuddy->activity = NULL;
3958 if (xn_activity) {
3959 const char *token = sipe_xml_attribute(xn_activity, "token");
3960 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3962 /* from token */
3963 if (!is_empty(token)) {
3964 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3966 /* from custom element */
3967 if (xn_custom) {
3968 char *custom = sipe_xml_data(xn_custom);
3970 if (!is_empty(custom)) {
3971 sbuddy->activity = custom;
3972 custom = NULL;
3974 g_free(custom);
3977 /* meeting_subject */
3978 g_free(sbuddy->meeting_subject);
3979 sbuddy->meeting_subject = NULL;
3980 if (xn_meeting_subject) {
3981 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3983 if (!is_empty(meeting_subject)) {
3984 sbuddy->meeting_subject = meeting_subject;
3985 meeting_subject = NULL;
3987 g_free(meeting_subject);
3989 /* meeting_location */
3990 g_free(sbuddy->meeting_location);
3991 sbuddy->meeting_location = NULL;
3992 if (xn_meeting_location) {
3993 char *meeting_location = sipe_xml_data(xn_meeting_location);
3995 if (!is_empty(meeting_location)) {
3996 sbuddy->meeting_location = meeting_location;
3997 meeting_location = NULL;
3999 g_free(meeting_location);
4002 status = sipe_get_status_by_availability(availability, &tmp);
4003 if (sbuddy->activity && tmp) {
4004 char *tmp2 = sbuddy->activity;
4006 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
4007 g_free(tmp);
4008 g_free(tmp2);
4009 } else if (tmp) {
4010 sbuddy->activity = tmp;
4014 do_update_status = TRUE;
4016 /* calendarData */
4017 else if(sipe_strequal(attrVar, "calendarData"))
4019 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
4020 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
4021 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
4023 if (sbuddy && xn_free_busy) {
4024 if (!has_free_busy_cleaned) {
4025 has_free_busy_cleaned = TRUE;
4027 g_free(sbuddy->cal_start_time);
4028 sbuddy->cal_start_time = NULL;
4030 g_free(sbuddy->cal_free_busy_base64);
4031 sbuddy->cal_free_busy_base64 = NULL;
4033 g_free(sbuddy->cal_free_busy);
4034 sbuddy->cal_free_busy = NULL;
4036 sbuddy->cal_free_busy_published = publish_time;
4039 if (publish_time >= sbuddy->cal_free_busy_published) {
4040 g_free(sbuddy->cal_start_time);
4041 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
4043 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
4044 15 : 0;
4046 g_free(sbuddy->cal_free_busy_base64);
4047 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
4049 g_free(sbuddy->cal_free_busy);
4050 sbuddy->cal_free_busy = NULL;
4052 sbuddy->cal_free_busy_published = publish_time;
4054 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);
4058 if (sbuddy && xn_working_hours) {
4059 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
4064 if (do_update_status) {
4065 if (!status) { /* no status category in this update, using contact's current status */
4066 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
4067 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
4068 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
4069 status = purple_status_get_id(pstatus);
4072 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
4073 sipe_got_user_status(sipe_private, uri, status);
4076 sipe_xml_free(xn_categories);
4079 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
4080 GSList *server,
4081 struct sipe_core_private *sipe_private)
4083 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4084 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
4085 payload->host = g_strdup(host);
4086 payload->buddies = server;
4087 sipe_subscribe_presence_batched_routed(sipe_private,
4088 payload);
4089 sipe_subscribe_presence_batched_routed_free(payload);
4092 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
4093 const gchar *data, unsigned len)
4095 sipe_xml *xn_list;
4096 const sipe_xml *xn_resource;
4097 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4098 g_free, NULL);
4099 GSList *server;
4100 gchar *host;
4102 xn_list = sipe_xml_parse(data, len);
4104 for (xn_resource = sipe_xml_child(xn_list, "resource");
4105 xn_resource;
4106 xn_resource = sipe_xml_twin(xn_resource) )
4108 const char *uri, *state;
4109 const sipe_xml *xn_instance;
4111 xn_instance = sipe_xml_child(xn_resource, "instance");
4112 if (!xn_instance) continue;
4114 uri = sipe_xml_attribute(xn_resource, "uri");
4115 state = sipe_xml_attribute(xn_instance, "state");
4116 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
4118 if (strstr(state, "resubscribe")) {
4119 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
4121 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4122 gchar *user = g_strdup(uri);
4123 host = g_strdup(poolFqdn);
4124 server = g_hash_table_lookup(servers, host);
4125 server = g_slist_append(server, user);
4126 g_hash_table_insert(servers, host, server);
4127 } else {
4128 sipe_subscribe_presence_single(sipe_private,
4129 (void *) uri);
4134 /* Send out any deferred poolFqdn subscriptions */
4135 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
4136 g_hash_table_destroy(servers);
4138 sipe_xml_free(xn_list);
4141 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
4142 const gchar *data, unsigned len)
4144 gchar *uri;
4145 gchar *getbasic;
4146 gchar *activity = NULL;
4147 sipe_xml *pidf;
4148 const sipe_xml *basicstatus = NULL, *tuple, *status;
4149 gboolean isonline = FALSE;
4150 const sipe_xml *display_name_node;
4152 pidf = sipe_xml_parse(data, len);
4153 if (!pidf) {
4154 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
4155 return;
4158 if ((tuple = sipe_xml_child(pidf, "tuple")))
4160 if ((status = sipe_xml_child(tuple, "status"))) {
4161 basicstatus = sipe_xml_child(status, "basic");
4165 if (!basicstatus) {
4166 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
4167 sipe_xml_free(pidf);
4168 return;
4171 getbasic = sipe_xml_data(basicstatus);
4172 if (!getbasic) {
4173 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
4174 sipe_xml_free(pidf);
4175 return;
4178 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
4179 if (strstr(getbasic, "open")) {
4180 isonline = TRUE;
4182 g_free(getbasic);
4184 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
4186 display_name_node = sipe_xml_child(pidf, "display-name");
4187 if (display_name_node) {
4188 char * display_name = sipe_xml_data(display_name_node);
4190 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4191 g_free(display_name);
4194 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
4195 if ((status = sipe_xml_child(tuple, "status"))) {
4196 if ((basicstatus = sipe_xml_child(status, "activities"))) {
4197 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
4198 activity = sipe_xml_data(basicstatus);
4199 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
4205 if (isonline) {
4206 const gchar * status_id = NULL;
4207 if (activity) {
4208 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
4209 status_id = SIPE_STATUS_ID_BUSY;
4210 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
4211 status_id = SIPE_STATUS_ID_AWAY;
4215 if (!status_id) {
4216 status_id = SIPE_STATUS_ID_AVAILABLE;
4219 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
4220 sipe_got_user_status(sipe_private, uri, status_id);
4221 } else {
4222 sipe_got_user_status(sipe_private, uri, SIPE_STATUS_ID_OFFLINE);
4225 g_free(activity);
4226 g_free(uri);
4227 sipe_xml_free(pidf);
4230 /** 2005 */
4231 static void
4232 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
4233 const sipe_xml *xn_userinfo)
4235 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4236 const sipe_xml *xn_states;
4238 g_free(sip->user_states);
4239 sip->user_states = NULL;
4240 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
4241 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
4243 /* this is a hack-around to remove added newline after inner element,
4244 * state in this case, where it shouldn't be.
4245 * After several use of sipe_xml_stringify, amount of added newlines
4246 * grows significantly.
4248 if (orig) {
4249 gchar c, *stripped = orig;
4250 while ((c = *orig++)) {
4251 if ((c != '\n') /* && (c != '\r') */) {
4252 *stripped++ = c;
4255 *stripped = '\0';
4259 /* Publish initial state if not yet.
4260 * Assuming this happens on initial responce to self subscription
4261 * so we've already updated our UserInfo.
4263 if (!sip->initial_state_published) {
4264 send_presence_soap(sipe_private, FALSE);
4265 /* dalayed run */
4266 sipe_schedule_seconds(sipe_private,
4267 "<+update-calendar>",
4268 NULL,
4269 UPDATE_CALENDAR_DELAY,
4270 (sipe_schedule_action) sipe_core_update_calendar,
4271 NULL);
4275 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
4276 const gchar *data, unsigned len)
4278 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4279 char *activity = NULL;
4280 const char *epid;
4281 const char *status_id = NULL;
4282 const char *name;
4283 char *uri;
4284 char *self_uri = sip_uri_self(sipe_private);
4285 int avl;
4286 int act;
4287 const char *device_name = NULL;
4288 const char *cal_start_time = NULL;
4289 const char *cal_granularity = NULL;
4290 char *cal_free_busy_base64 = NULL;
4291 struct sipe_buddy *sbuddy;
4292 const sipe_xml *node;
4293 sipe_xml *xn_presentity;
4294 const sipe_xml *xn_availability;
4295 const sipe_xml *xn_activity;
4296 const sipe_xml *xn_display_name;
4297 const sipe_xml *xn_email;
4298 const sipe_xml *xn_phone_number;
4299 const sipe_xml *xn_userinfo;
4300 const sipe_xml *xn_note;
4301 const sipe_xml *xn_oof;
4302 const sipe_xml *xn_state;
4303 const sipe_xml *xn_contact;
4304 char *note;
4305 char *free_activity;
4306 int user_avail;
4307 const char *user_avail_nil;
4308 int res_avail;
4309 time_t user_avail_since = 0;
4310 time_t activity_since = 0;
4312 /* fix for Reuters environment on Linux */
4313 if (data && strstr(data, "encoding=\"utf-16\"")) {
4314 char *tmp_data;
4315 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
4316 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
4317 g_free(tmp_data);
4318 } else {
4319 xn_presentity = sipe_xml_parse(data, len);
4322 xn_availability = sipe_xml_child(xn_presentity, "availability");
4323 xn_activity = sipe_xml_child(xn_presentity, "activity");
4324 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
4325 xn_email = sipe_xml_child(xn_presentity, "email");
4326 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
4327 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
4328 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
4329 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
4330 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
4331 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
4332 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
4333 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
4334 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
4335 note = xn_note ? sipe_xml_data(xn_note) : NULL;
4337 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
4338 user_avail = 0;
4339 user_avail_since = 0;
4342 free_activity = NULL;
4344 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
4345 uri = sip_uri_from_name(name);
4346 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
4347 epid = sipe_xml_attribute(xn_availability, "epid");
4348 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
4350 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
4351 res_avail = sipe_get_availability_by_status(status_id, NULL);
4352 if (user_avail > res_avail) {
4353 res_avail = user_avail;
4354 status_id = sipe_get_status_by_availability(user_avail, NULL);
4357 if (xn_display_name) {
4358 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
4359 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
4360 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
4361 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
4362 char *tel_uri = sip_to_tel_uri(phone_number);
4364 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4365 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
4366 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
4367 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
4369 g_free(tel_uri);
4370 g_free(phone_label);
4371 g_free(phone_number);
4372 g_free(email);
4373 g_free(display_name);
4376 if (xn_contact) {
4377 /* tel */
4378 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
4380 /* Ex.: <tel type="work">tel:+3222220000</tel> */
4381 const char *phone_type = sipe_xml_attribute(node, "type");
4382 char* phone = sipe_xml_data(node);
4384 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
4386 g_free(phone);
4390 /* devicePresence */
4391 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
4392 const sipe_xml *xn_device_name;
4393 const sipe_xml *xn_calendar_info;
4394 const sipe_xml *xn_state;
4395 char *state;
4397 /* deviceName */
4398 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
4399 xn_device_name = sipe_xml_child(node, "deviceName");
4400 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
4403 /* calendarInfo */
4404 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
4405 if (xn_calendar_info) {
4406 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
4408 if (cal_start_time) {
4409 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
4410 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
4412 if (cal_start_time_t_tmp > cal_start_time_t) {
4413 cal_start_time = cal_start_time_tmp;
4414 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4415 g_free(cal_free_busy_base64);
4416 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4418 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);
4420 } else {
4421 cal_start_time = cal_start_time_tmp;
4422 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4423 g_free(cal_free_busy_base64);
4424 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4426 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);
4430 /* state */
4431 xn_state = sipe_xml_child(node, "states/state");
4432 if (xn_state) {
4433 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
4434 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
4436 state = sipe_xml_data(xn_state);
4437 if (dev_avail_since > user_avail_since &&
4438 dev_avail >= res_avail)
4440 res_avail = dev_avail;
4441 if (!is_empty(state))
4443 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
4444 g_free(activity);
4445 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
4446 } else if (sipe_strequal(state, "presenting")) {
4447 g_free(activity);
4448 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
4449 } else {
4450 activity = state;
4451 state = NULL;
4453 activity_since = dev_avail_since;
4455 status_id = sipe_get_status_by_availability(res_avail, &activity);
4457 g_free(state);
4461 /* oof */
4462 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
4463 g_free(activity);
4464 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
4465 activity_since = 0;
4468 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
4469 if (sbuddy)
4471 g_free(sbuddy->activity);
4472 sbuddy->activity = activity;
4473 activity = NULL;
4475 sbuddy->activity_since = activity_since;
4477 sbuddy->user_avail = user_avail;
4478 sbuddy->user_avail_since = user_avail_since;
4480 g_free(sbuddy->note);
4481 sbuddy->note = NULL;
4482 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
4484 sbuddy->is_oof_note = (xn_oof != NULL);
4486 g_free(sbuddy->device_name);
4487 sbuddy->device_name = NULL;
4488 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
4490 if (!is_empty(cal_free_busy_base64)) {
4491 g_free(sbuddy->cal_start_time);
4492 sbuddy->cal_start_time = g_strdup(cal_start_time);
4494 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
4496 g_free(sbuddy->cal_free_busy_base64);
4497 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
4498 cal_free_busy_base64 = NULL;
4500 g_free(sbuddy->cal_free_busy);
4501 sbuddy->cal_free_busy = NULL;
4504 sbuddy->last_non_cal_status_id = status_id;
4505 g_free(sbuddy->last_non_cal_activity);
4506 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
4508 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
4509 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
4511 sip->is_oof_note = sbuddy->is_oof_note;
4513 g_free(sip->note);
4514 sip->note = g_strdup(sbuddy->note);
4516 sip->note_since = time(NULL);
4519 g_free(sip->status);
4520 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
4523 g_free(cal_free_busy_base64);
4524 g_free(activity);
4526 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
4527 sipe_got_user_status(sipe_private, uri, status_id);
4529 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
4530 sipe_user_info_has_updated(sipe_private, xn_userinfo);
4533 g_free(note);
4534 sipe_xml_free(xn_presentity);
4535 g_free(uri);
4536 g_free(self_uri);
4539 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
4540 const GSList *fields,
4541 const gchar *body,
4542 gsize length)
4544 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
4546 if (strstr(type,"application/rlmi+xml")) {
4547 process_incoming_notify_rlmi_resub(user_data, body, length);
4548 } else if (strstr(type, "text/xml+msrtc.pidf")) {
4549 process_incoming_notify_msrtc(user_data, body, length);
4550 } else {
4551 process_incoming_notify_rlmi(user_data, body, length);
4555 static void sipe_process_presence(struct sipe_core_private *sipe_private,
4556 struct sipmsg *msg)
4558 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4560 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
4562 if (ctype &&
4563 (strstr(ctype, "application/rlmi+xml") ||
4564 strstr(ctype, "application/msrtc-event-categories+xml")))
4566 if (strstr(ctype, "multipart"))
4568 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
4570 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4572 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
4574 else if(strstr(ctype, "application/rlmi+xml"))
4576 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
4579 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4581 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
4583 else
4585 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
4589 static void sipe_presence_timeout_mime_cb(gpointer user_data,
4590 SIPE_UNUSED_PARAMETER const GSList *fields,
4591 const gchar *body,
4592 gsize length)
4594 GSList **buddies = user_data;
4595 sipe_xml *xml = sipe_xml_parse(body, length);
4597 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
4598 const gchar *uri = sipe_xml_attribute(xml, "uri");
4599 const sipe_xml *xn_category;
4602 * automaton: presence is never expected to change
4604 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
4606 for (xn_category = sipe_xml_child(xml, "category");
4607 xn_category;
4608 xn_category = sipe_xml_twin(xn_category)) {
4609 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
4610 "contactCard")) {
4611 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
4612 if (node) {
4613 char *boolean = sipe_xml_data(node);
4614 if (sipe_strequal(boolean, "true")) {
4615 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
4616 uri);
4617 uri = NULL;
4619 g_free(boolean);
4621 break;
4625 if (uri) {
4626 *buddies = g_slist_append(*buddies, sip_uri(uri));
4630 sipe_xml_free(xml);
4633 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
4634 struct sipmsg *msg, gchar *who,
4635 int timeout)
4637 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4638 gchar *action_name = sipe_utils_presence_key(who);
4640 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
4642 if (ctype &&
4643 strstr(ctype, "multipart") &&
4644 (strstr(ctype, "application/rlmi+xml") ||
4645 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4646 GSList *buddies = NULL;
4648 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
4650 if (buddies) {
4651 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4652 payload->host = g_strdup(who);
4653 payload->buddies = buddies;
4654 sipe_schedule_seconds(sipe_private,
4655 action_name,
4656 payload,
4657 timeout,
4658 sipe_subscribe_presence_batched_routed,
4659 sipe_subscribe_presence_batched_routed_free);
4660 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
4663 } else {
4664 sipe_schedule_seconds(sipe_private,
4665 action_name,
4666 g_strdup(who),
4667 timeout,
4668 sipe_subscribe_presence_single,
4669 g_free);
4670 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
4672 g_free(action_name);
4676 * Dispatcher for all incoming subscription information
4677 * whether it comes from NOTIFY, BENOTIFY requests or
4678 * piggy-backed to subscription's OK responce.
4680 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4681 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4683 void process_incoming_notify(struct sipe_core_private *sipe_private,
4684 struct sipmsg *msg,
4685 gboolean request, gboolean benotify)
4687 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4688 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4689 const gchar *event = sipmsg_find_header(msg, "Event");
4690 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4692 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
4694 /* implicit subscriptions */
4695 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4696 sipe_process_imdn(sipe_private, msg);
4699 if (event) {
4700 /* for one off subscriptions (send with Expire: 0) */
4701 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
4703 sipe_process_provisioning_v2(sipe_private, msg);
4705 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
4707 sipe_process_provisioning(sipe_private, msg);
4709 else if (sipe_strcase_equal(event, "presence"))
4711 sipe_process_presence(sipe_private, msg);
4713 else if (sipe_strcase_equal(event, "registration-notify"))
4715 sipe_process_registration_notify(sipe_private, msg);
4718 if (!subscription_state || strstr(subscription_state, "active"))
4720 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
4722 sipe_process_roaming_contacts(sipe_private, msg);
4724 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
4726 sipe_process_roaming_self(sipe_private, msg);
4728 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
4730 sipe_process_roaming_acl(sipe_private, msg);
4732 else if (sipe_strcase_equal(event, "presence.wpending"))
4734 sipe_process_presence_wpending(sipe_private, msg);
4736 else if (sipe_strcase_equal(event, "conference"))
4738 sipe_process_conference(sipe_private, msg);
4743 /* The server sends status 'terminated' */
4744 if (subscription_state && strstr(subscription_state, "terminated") ) {
4745 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4746 gchar *key = sipe_utils_subscription_key(event, who);
4748 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
4749 g_free(who);
4751 sipe_subscriptions_remove(sipe_private, key);
4752 g_free(key);
4755 if (!request && event) {
4756 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
4757 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4758 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
4760 if (timeout) {
4761 /* 2 min ahead of expiration */
4762 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
4764 if (sipe_strcase_equal(event, "presence.wpending") &&
4765 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4767 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4768 sipe_schedule_seconds(sipe_private,
4769 action_name,
4770 NULL,
4771 timeout,
4772 sipe_subscribe_presence_wpending,
4773 NULL);
4774 g_free(action_name);
4776 else if (sipe_strcase_equal(event, "presence") &&
4777 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4779 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
4780 gchar *action_name = sipe_utils_presence_key(who);
4782 if (sip->batched_support) {
4783 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
4785 else {
4786 sipe_schedule_seconds(sipe_private,
4787 action_name,
4788 g_strdup(who),
4789 timeout,
4790 sipe_subscribe_presence_single,
4791 g_free);
4792 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
4794 g_free(action_name);
4795 g_free(who);
4800 /* The client responses on received a NOTIFY message */
4801 if (request && !benotify)
4803 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
4808 * Whether user manually changed status or
4809 * it was changed automatically due to user
4810 * became inactive/active again
4812 static gboolean
4813 sipe_is_user_state(struct sipe_core_private *sipe_private)
4815 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4816 gboolean res;
4817 time_t now = time(NULL);
4819 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
4820 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
4822 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
4824 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
4825 return res;
4828 static void
4829 send_presence_soap0(struct sipe_core_private *sipe_private,
4830 gboolean do_publish_calendar,
4831 gboolean do_reset_status)
4833 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4834 struct sipe_calendar* cal = sip->cal;
4835 int availability = 0;
4836 int activity = 0;
4837 gchar *body;
4838 gchar *tmp;
4839 gchar *tmp2 = NULL;
4840 gchar *res_note = NULL;
4841 gchar *res_oof = NULL;
4842 const gchar *note_pub = NULL;
4843 gchar *states = NULL;
4844 gchar *calendar_data = NULL;
4845 gchar *epid = get_epid(sipe_private);
4846 time_t now = time(NULL);
4847 gchar *since_time_str = sipe_utils_time_to_str(now);
4848 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4849 const char *user_input;
4850 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4852 if (oof_note && sip->note) {
4853 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4854 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4857 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4859 if (!sip->initial_state_published ||
4860 do_reset_status)
4862 g_free(sip->status);
4863 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4866 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4868 /* Note */
4869 if (pub_oof) {
4870 note_pub = oof_note;
4871 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4872 cal->published = TRUE;
4873 } else if (sip->note) {
4874 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4875 g_free(sip->note);
4876 sip->note = NULL;
4877 sip->is_oof_note = FALSE;
4878 sip->note_since = 0;
4879 } else {
4880 note_pub = sip->note;
4881 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4885 if (note_pub)
4887 /* to protocol internal plain text format */
4888 tmp = sipe_backend_markup_strip_html(note_pub);
4889 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4890 g_free(tmp);
4893 /* User State */
4894 if (!do_reset_status) {
4895 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4897 gchar *activity_token = NULL;
4898 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4900 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4901 avail_2007,
4902 since_time_str,
4903 epid,
4904 activity_token);
4905 g_free(activity_token);
4907 else /* preserve existing publication */
4909 if (sip->user_states) {
4910 states = g_strdup(sip->user_states);
4913 } else {
4914 /* do nothing - then User state will be erased */
4916 sip->initial_state_published = TRUE;
4918 /* CalendarInfo */
4919 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4921 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4922 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4923 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4924 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4925 fb_start_str,
4926 free_busy_base64);
4927 g_free(fb_start_str);
4928 g_free(free_busy_base64);
4931 user_input = (sipe_is_user_state(sipe_private) ||
4932 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
4933 "active" : "idle";
4935 /* forming resulting XML */
4936 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4937 sipe_private->username,
4938 availability,
4939 activity,
4940 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4941 res_note ? res_note : "",
4942 res_oof ? res_oof : "",
4943 states ? states : "",
4944 calendar_data ? calendar_data : "",
4945 epid,
4946 since_time_str,
4947 since_time_str,
4948 user_input);
4949 g_free(tmp);
4950 g_free(tmp2);
4951 g_free(res_note);
4952 g_free(states);
4953 g_free(calendar_data);
4955 send_soap_request(sipe_private, body);
4957 g_free(body);
4958 g_free(since_time_str);
4959 g_free(epid);
4962 void
4963 send_presence_soap(struct sipe_core_private *sipe_private,
4964 gboolean do_publish_calendar)
4966 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4970 static gboolean
4971 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4972 struct sipmsg *msg,
4973 struct transaction *trans)
4975 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4977 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4978 sipe_xml *xml;
4979 const sipe_xml *node;
4980 gchar *fault_code;
4981 GHashTable *faults;
4982 int index_our;
4983 gboolean has_device_publication = FALSE;
4985 xml = sipe_xml_parse(msg->body, msg->bodylen);
4987 /* test if version mismatch fault */
4988 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
4989 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
4990 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
4991 g_free(fault_code);
4992 sipe_xml_free(xml);
4993 return TRUE;
4995 g_free(fault_code);
4997 /* accumulating information about faulty versions */
4998 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
4999 for (node = sipe_xml_child(xml, "details/operation");
5000 node;
5001 node = sipe_xml_twin(node))
5003 const gchar *index = sipe_xml_attribute(node, "index");
5004 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
5006 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
5007 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
5009 sipe_xml_free(xml);
5011 /* here we are parsing own request to figure out what publication
5012 * referensed here only by index went wrong
5014 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
5016 /* publication */
5017 for (node = sipe_xml_child(xml, "publications/publication"),
5018 index_our = 1; /* starts with 1 - our first publication */
5019 node;
5020 node = sipe_xml_twin(node), index_our++)
5022 gchar *idx = g_strdup_printf("%d", index_our);
5023 const gchar *curVersion = g_hash_table_lookup(faults, idx);
5024 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
5025 g_free(idx);
5027 if (sipe_strequal("device", categoryName)) {
5028 has_device_publication = TRUE;
5031 if (curVersion) { /* fault exist on this index */
5032 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5033 const gchar *container = sipe_xml_attribute(node, "container");
5034 const gchar *instance = sipe_xml_attribute(node, "instance");
5035 /* key is <category><instance><container> */
5036 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
5037 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
5039 if (category) {
5040 struct sipe_publication *publication =
5041 g_hash_table_lookup(category, key);
5043 SIPE_DEBUG_INFO("key is %s", key);
5045 if (publication) {
5046 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
5047 key, curVersion, publication->version);
5048 /* updating publication's version to the correct one */
5049 publication->version = atoi(curVersion);
5051 } else {
5052 /* We somehow lost this category from our publications... */
5053 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
5054 publication->category = g_strdup(categoryName);
5055 publication->instance = atoi(instance);
5056 publication->container = atoi(container);
5057 publication->version = atoi(curVersion);
5058 category = g_hash_table_new_full(g_str_hash, g_str_equal,
5059 g_free, (GDestroyNotify)free_publication);
5060 g_hash_table_insert(category, g_strdup(key), publication);
5061 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
5062 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
5064 g_free(key);
5067 sipe_xml_free(xml);
5068 g_hash_table_destroy(faults);
5070 /* rebublishing with right versions */
5071 if (has_device_publication) {
5072 send_publish_category_initial(sipe_private);
5073 } else {
5074 send_presence_status(sipe_private, NULL);
5077 return TRUE;
5081 * Returns 'device' XML part for publication.
5082 * Must be g_free'd after use.
5084 static gchar *
5085 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
5087 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5088 gchar *uri;
5089 gchar *doc;
5090 gchar *epid = get_epid(sipe_private);
5091 gchar *uuid = generateUUIDfromEPID(epid);
5092 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
5093 /* key is <category><instance><container> */
5094 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
5095 struct sipe_publication *publication =
5096 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
5098 g_free(key);
5099 g_free(epid);
5101 uri = sip_uri_self(sipe_private);
5102 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
5103 device_instance,
5104 publication ? publication->version : 0,
5105 uuid,
5106 uri,
5107 "00:00:00+01:00", /* @TODO make timezone real*/
5108 g_get_host_name()
5111 g_free(uri);
5112 g_free(uuid);
5114 return doc;
5118 * A service method - use
5119 * - send_publish_get_category_state_machine and
5120 * - send_publish_get_category_state_user instead.
5121 * Must be g_free'd after use.
5123 static gchar *
5124 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
5125 gboolean is_user_state)
5127 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5128 int availability = sipe_get_availability_by_status(sip->status, NULL);
5129 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
5130 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
5131 /* key is <category><instance><container> */
5132 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5133 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5134 struct sipe_publication *publication_2 =
5135 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5136 struct sipe_publication *publication_3 =
5137 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5139 g_free(key_2);
5140 g_free(key_3);
5142 if (publication_2 && (publication_2->availability == availability))
5144 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
5145 return NULL; /* nothing to update */
5148 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
5149 instance,
5150 publication_2 ? publication_2->version : 0,
5151 availability,
5152 instance,
5153 publication_3 ? publication_3->version : 0,
5154 availability);
5158 * Only Busy and OOF calendar event are published.
5159 * Different instances are used for that.
5161 * Must be g_free'd after use.
5163 static gchar *
5164 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
5165 struct sipe_cal_event *event,
5166 const char *uri,
5167 int cal_satus)
5169 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5170 gchar *start_time_str;
5171 int availability = 0;
5172 gchar *res;
5173 gchar *tmp = NULL;
5174 guint instance = (cal_satus == SIPE_CAL_OOF) ?
5175 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
5176 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
5178 /* key is <category><instance><container> */
5179 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5180 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5181 struct sipe_publication *publication_2 =
5182 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5183 struct sipe_publication *publication_3 =
5184 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5186 g_free(key_2);
5187 g_free(key_3);
5189 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
5190 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5191 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
5192 return NULL;
5195 if (event &&
5196 publication_3 &&
5197 (publication_3->availability == availability) &&
5198 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
5200 g_free(tmp);
5201 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5202 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
5203 return NULL; /* nothing to update */
5205 g_free(tmp);
5207 if (event &&
5208 (event->cal_status == SIPE_CAL_BUSY ||
5209 event->cal_status == SIPE_CAL_OOF))
5211 gchar *availability_xml_str = NULL;
5212 gchar *activity_xml_str = NULL;
5214 if (event->cal_status == SIPE_CAL_BUSY) {
5215 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
5218 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
5219 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5220 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
5221 "minAvailability=\"6500\"",
5222 "maxAvailability=\"8999\"");
5223 } else if (event->cal_status == SIPE_CAL_OOF) {
5224 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5225 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
5226 "minAvailability=\"12000\"",
5227 "");
5229 start_time_str = sipe_utils_time_to_str(event->start_time);
5231 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
5232 instance,
5233 publication_2 ? publication_2->version : 0,
5234 uri,
5235 start_time_str,
5236 availability_xml_str ? availability_xml_str : "",
5237 activity_xml_str ? activity_xml_str : "",
5238 event->subject ? event->subject : "",
5239 event->location ? event->location : "",
5241 instance,
5242 publication_3 ? publication_3->version : 0,
5243 uri,
5244 start_time_str,
5245 availability_xml_str ? availability_xml_str : "",
5246 activity_xml_str ? activity_xml_str : "",
5247 event->subject ? event->subject : "",
5248 event->location ? event->location : ""
5250 g_free(start_time_str);
5251 g_free(availability_xml_str);
5252 g_free(activity_xml_str);
5255 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
5257 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
5258 instance,
5259 publication_2 ? publication_2->version : 0,
5261 instance,
5262 publication_3 ? publication_3->version : 0
5266 return res;
5270 * Returns 'machineState' XML part for publication.
5271 * Must be g_free'd after use.
5273 static gchar *
5274 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
5276 return sipe_publish_get_category_state(sipe_private, FALSE);
5280 * Returns 'userState' XML part for publication.
5281 * Must be g_free'd after use.
5283 static gchar *
5284 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
5286 return sipe_publish_get_category_state(sipe_private, TRUE);
5290 * Returns 'note' XML part for publication.
5291 * Must be g_free'd after use.
5293 * Protocol format for Note is plain text.
5295 * @param note a note in Sipe internal HTML format
5296 * @param note_type either personal or OOF
5298 static gchar *
5299 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
5300 const char *note, /* html */
5301 const char *note_type,
5302 time_t note_start,
5303 time_t note_end)
5305 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5306 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
5307 /* key is <category><instance><container> */
5308 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
5309 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
5310 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
5312 struct sipe_publication *publication_note_200 =
5313 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
5314 struct sipe_publication *publication_note_300 =
5315 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
5316 struct sipe_publication *publication_note_400 =
5317 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
5319 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
5320 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
5321 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
5322 char *res, *tmp1, *tmp2, *tmp3;
5323 char *start_time_attr;
5324 char *end_time_attr;
5326 g_free(tmp);
5327 tmp = NULL;
5328 g_free(key_note_200);
5329 g_free(key_note_300);
5330 g_free(key_note_400);
5332 /* we even need to republish empty note */
5333 if (sipe_strequal(n1, n2))
5335 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
5336 g_free(n1);
5337 return NULL; /* nothing to update */
5340 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
5341 g_free(tmp);
5342 tmp = NULL;
5343 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
5344 g_free(tmp);
5346 if (n1) {
5347 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5348 instance,
5349 200,
5350 publication_note_200 ? publication_note_200->version : 0,
5351 note_type,
5352 start_time_attr ? start_time_attr : "",
5353 end_time_attr ? end_time_attr : "",
5354 n1);
5356 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5357 instance,
5358 300,
5359 publication_note_300 ? publication_note_300->version : 0,
5360 note_type,
5361 start_time_attr ? start_time_attr : "",
5362 end_time_attr ? end_time_attr : "",
5363 n1);
5365 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5366 instance,
5367 400,
5368 publication_note_400 ? publication_note_400->version : 0,
5369 note_type,
5370 start_time_attr ? start_time_attr : "",
5371 end_time_attr ? end_time_attr : "",
5372 n1);
5373 } else {
5374 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5375 "note",
5376 instance,
5377 200,
5378 publication_note_200 ? publication_note_200->version : 0,
5379 "static");
5380 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5381 "note",
5382 instance,
5383 300,
5384 publication_note_200 ? publication_note_200->version : 0,
5385 "static");
5386 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5387 "note",
5388 instance,
5389 400,
5390 publication_note_200 ? publication_note_200->version : 0,
5391 "static");
5393 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
5395 g_free(start_time_attr);
5396 g_free(end_time_attr);
5397 g_free(tmp1);
5398 g_free(tmp2);
5399 g_free(tmp3);
5400 g_free(n1);
5402 return res;
5406 * Returns 'calendarData' XML part with WorkingHours for publication.
5407 * Must be g_free'd after use.
5409 static gchar *
5410 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
5412 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5413 struct sipe_calendar* cal = sip->cal;
5415 /* key is <category><instance><container> */
5416 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
5417 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
5418 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
5419 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
5420 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
5421 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
5423 struct sipe_publication *publication_cal_1 =
5424 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5425 struct sipe_publication *publication_cal_100 =
5426 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5427 struct sipe_publication *publication_cal_200 =
5428 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5429 struct sipe_publication *publication_cal_300 =
5430 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5431 struct sipe_publication *publication_cal_400 =
5432 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5433 struct sipe_publication *publication_cal_32000 =
5434 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5436 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
5437 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
5439 g_free(key_cal_1);
5440 g_free(key_cal_100);
5441 g_free(key_cal_200);
5442 g_free(key_cal_300);
5443 g_free(key_cal_400);
5444 g_free(key_cal_32000);
5446 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
5447 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
5448 return NULL;
5451 if (sipe_strequal(n1, n2))
5453 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
5454 return NULL; /* nothing to update */
5457 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
5458 /* 1 */
5459 publication_cal_1 ? publication_cal_1->version : 0,
5460 cal->email,
5461 cal->working_hours_xml_str,
5462 /* 100 - Public */
5463 publication_cal_100 ? publication_cal_100->version : 0,
5464 /* 200 - Company */
5465 publication_cal_200 ? publication_cal_200->version : 0,
5466 cal->email,
5467 cal->working_hours_xml_str,
5468 /* 300 - Team */
5469 publication_cal_300 ? publication_cal_300->version : 0,
5470 cal->email,
5471 cal->working_hours_xml_str,
5472 /* 400 - Personal */
5473 publication_cal_400 ? publication_cal_400->version : 0,
5474 cal->email,
5475 cal->working_hours_xml_str,
5476 /* 32000 - Blocked */
5477 publication_cal_32000 ? publication_cal_32000->version : 0
5482 * Returns 'calendarData' XML part with FreeBusy for publication.
5483 * Must be g_free'd after use.
5485 static gchar *
5486 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
5488 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5489 struct sipe_calendar* cal = sip->cal;
5490 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
5491 char *fb_start_str;
5492 char *free_busy_base64;
5493 const char *st;
5494 const char *fb;
5495 char *res;
5497 /* key is <category><instance><container> */
5498 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
5499 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
5500 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
5501 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
5502 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
5503 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
5505 struct sipe_publication *publication_cal_1 =
5506 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5507 struct sipe_publication *publication_cal_100 =
5508 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5509 struct sipe_publication *publication_cal_200 =
5510 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5511 struct sipe_publication *publication_cal_300 =
5512 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5513 struct sipe_publication *publication_cal_400 =
5514 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5515 struct sipe_publication *publication_cal_32000 =
5516 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5518 g_free(key_cal_1);
5519 g_free(key_cal_100);
5520 g_free(key_cal_200);
5521 g_free(key_cal_300);
5522 g_free(key_cal_400);
5523 g_free(key_cal_32000);
5525 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
5526 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
5527 return NULL;
5530 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
5531 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
5533 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
5534 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
5536 /* we will rebuplish the same data to refresh publication time,
5537 * so if data from multiple sources, most recent will be choosen
5539 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
5541 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
5542 // g_free(fb_start_str);
5543 // g_free(free_busy_base64);
5544 // return NULL; /* nothing to update */
5547 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
5548 /* 1 */
5549 cal_data_instance,
5550 publication_cal_1 ? publication_cal_1->version : 0,
5551 /* 100 - Public */
5552 cal_data_instance,
5553 publication_cal_100 ? publication_cal_100->version : 0,
5554 /* 200 - Company */
5555 cal_data_instance,
5556 publication_cal_200 ? publication_cal_200->version : 0,
5557 cal->email,
5558 fb_start_str,
5559 free_busy_base64,
5560 /* 300 - Team */
5561 cal_data_instance,
5562 publication_cal_300 ? publication_cal_300->version : 0,
5563 cal->email,
5564 fb_start_str,
5565 free_busy_base64,
5566 /* 400 - Personal */
5567 cal_data_instance,
5568 publication_cal_400 ? publication_cal_400->version : 0,
5569 cal->email,
5570 fb_start_str,
5571 free_busy_base64,
5572 /* 32000 - Blocked */
5573 cal_data_instance,
5574 publication_cal_32000 ? publication_cal_32000->version : 0
5577 g_free(fb_start_str);
5578 g_free(free_busy_base64);
5579 return res;
5582 static void send_presence_publish(struct sipe_core_private *sipe_private,
5583 const char *publications)
5585 gchar *uri;
5586 gchar *doc;
5587 gchar *tmp;
5588 gchar *hdr;
5590 uri = sip_uri_self(sipe_private);
5591 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
5592 uri,
5593 publications);
5595 tmp = get_contact(sipe_private);
5596 hdr = g_strdup_printf("Contact: %s\r\n"
5597 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
5599 sip_transport_service(sipe_private,
5600 uri,
5601 hdr,
5602 doc,
5603 process_send_presence_category_publish_response);
5605 g_free(tmp);
5606 g_free(hdr);
5607 g_free(uri);
5608 g_free(doc);
5611 static void
5612 send_publish_category_initial(struct sipe_core_private *sipe_private)
5614 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5615 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
5616 gchar *pub_machine;
5617 gchar *publications;
5619 g_free(sip->status);
5620 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
5622 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
5623 publications = g_strdup_printf("%s%s",
5624 pub_device,
5625 pub_machine ? pub_machine : "");
5626 g_free(pub_device);
5627 g_free(pub_machine);
5629 send_presence_publish(sipe_private, publications);
5630 g_free(publications);
5633 static void
5634 send_presence_category_publish(struct sipe_core_private *sipe_private)
5636 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5637 gchar *pub_state = sipe_is_user_state(sipe_private) ?
5638 sipe_publish_get_category_state_user(sipe_private) :
5639 sipe_publish_get_category_state_machine(sipe_private);
5640 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
5641 sip->note,
5642 sip->is_oof_note ? "OOF" : "personal",
5645 gchar *publications;
5647 if (!pub_state && !pub_note) {
5648 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
5649 return;
5652 publications = g_strdup_printf("%s%s",
5653 pub_state ? pub_state : "",
5654 pub_note ? pub_note : "");
5656 g_free(pub_state);
5657 g_free(pub_note);
5659 send_presence_publish(sipe_private, publications);
5660 g_free(publications);
5664 * Publishes self status
5665 * based on own calendar information.
5667 * For 2007+
5669 void
5670 publish_calendar_status_self(struct sipe_core_private *sipe_private,
5671 SIPE_UNUSED_PARAMETER void *unused)
5673 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5674 struct sipe_cal_event* event = NULL;
5675 gchar *pub_cal_working_hours = NULL;
5676 gchar *pub_cal_free_busy = NULL;
5677 gchar *pub_calendar = NULL;
5678 gchar *pub_calendar2 = NULL;
5679 gchar *pub_oof_note = NULL;
5680 const gchar *oof_note;
5681 time_t oof_start = 0;
5682 time_t oof_end = 0;
5684 if (!sip->cal) {
5685 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
5686 return;
5689 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
5690 if (sip->cal->cal_events) {
5691 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
5694 if (!event) {
5695 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
5696 } else {
5697 char *desc = sipe_cal_event_describe(event);
5698 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
5699 g_free(desc);
5702 /* Logic
5703 if OOF
5704 OOF publish, Busy clean
5705 ilse if Busy
5706 OOF clean, Busy publish
5707 else
5708 OOF clean, Busy clean
5710 if (event && event->cal_status == SIPE_CAL_OOF) {
5711 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
5712 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5713 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
5714 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5715 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
5716 } else {
5717 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5718 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5721 oof_note = sipe_ews_get_oof_note(sip->cal);
5722 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
5723 oof_start = sip->cal->oof_start;
5724 oof_end = sip->cal->oof_end;
5726 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
5728 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
5729 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
5731 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
5732 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
5733 } else {
5734 gchar *publications = g_strdup_printf("%s%s%s%s%s",
5735 pub_cal_working_hours ? pub_cal_working_hours : "",
5736 pub_cal_free_busy ? pub_cal_free_busy : "",
5737 pub_calendar ? pub_calendar : "",
5738 pub_calendar2 ? pub_calendar2 : "",
5739 pub_oof_note ? pub_oof_note : "");
5741 send_presence_publish(sipe_private, publications);
5742 g_free(publications);
5745 g_free(pub_cal_working_hours);
5746 g_free(pub_cal_free_busy);
5747 g_free(pub_calendar);
5748 g_free(pub_calendar2);
5749 g_free(pub_oof_note);
5751 /* repeat scheduling */
5752 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
5755 static void send_presence_status(struct sipe_core_private *sipe_private,
5756 SIPE_UNUSED_PARAMETER void *unused)
5758 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5759 PurpleStatus * status = purple_account_get_active_status(sip->account);
5761 if (!status) return;
5763 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
5764 purple_status_get_id(status) ? purple_status_get_id(status) : "",
5765 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
5767 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5768 send_presence_category_publish(sipe_private);
5769 } else {
5770 send_presence_soap(sipe_private, FALSE);
5774 static guint sipe_ht_hash_nick(const char *nick)
5776 char *lc = g_utf8_strdown(nick, -1);
5777 guint bucket = g_str_hash(lc);
5778 g_free(lc);
5780 return bucket;
5783 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5785 char *nick1_norm = NULL;
5786 char *nick2_norm = NULL;
5787 gboolean equal;
5789 if (nick1 == NULL && nick2 == NULL) return TRUE;
5790 if (nick1 == NULL || nick2 == NULL ||
5791 !g_utf8_validate(nick1, -1, NULL) ||
5792 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
5794 nick1_norm = g_utf8_casefold(nick1, -1);
5795 nick2_norm = g_utf8_casefold(nick2, -1);
5796 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
5797 g_free(nick2_norm);
5798 g_free(nick1_norm);
5800 return equal;
5803 /* temporary function */
5804 void sipe_purple_setup(struct sipe_core_public *sipe_public,
5805 PurpleConnection *gc)
5807 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5808 sip->gc = gc;
5809 sip->account = purple_connection_get_account(gc);
5812 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
5813 const gchar *login_domain,
5814 const gchar *login_account,
5815 const gchar *password,
5816 const gchar *email,
5817 const gchar *email_url,
5818 const gchar **errmsg)
5820 struct sipe_core_private *sipe_private;
5821 struct sipe_account_data *sip;
5822 gchar **user_domain;
5824 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
5826 /* ensure that sign-in name doesn't contain invalid characters */
5827 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
5828 *errmsg = _("SIP Exchange user name contains invalid characters");
5829 return NULL;
5832 /* ensure that sign-in name format is name@domain */
5833 if (!strchr(signin_name, '@') ||
5834 g_str_has_prefix(signin_name, "@") ||
5835 g_str_has_suffix(signin_name, "@")) {
5836 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5837 return NULL;
5840 /* ensure that email format is name@domain (if provided) */
5841 if (!is_empty(email) &&
5842 (!strchr(email, '@') ||
5843 g_str_has_prefix(email, "@") ||
5844 g_str_has_suffix(email, "@")))
5846 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5847 return NULL;
5850 /* ensure that user name doesn't contain spaces */
5851 user_domain = g_strsplit(signin_name, "@", 2);
5852 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5853 if (strchr(user_domain[0], ' ') != NULL) {
5854 g_strfreev(user_domain);
5855 *errmsg = _("SIP Exchange user name contains whitespace");
5856 return NULL;
5859 /* ensure that email_url is in proper format if enabled (if provided).
5860 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5861 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5863 if (!is_empty(email_url)) {
5864 char *tmp = g_ascii_strdown(email_url, -1);
5865 if (!g_str_has_prefix(tmp, "https://"))
5867 g_free(tmp);
5868 g_strfreev(user_domain);
5869 *errmsg = _("Email services URL should be valid if provided\n"
5870 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5871 "Example: https://domino.corp.com/maildatabase.nsf");
5872 return NULL;
5874 g_free(tmp);
5877 sipe_private = g_new0(struct sipe_core_private, 1);
5878 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5879 sip->subscribed_buddies = FALSE;
5880 sip->initial_state_published = FALSE;
5881 sipe_private->username = g_strdup(signin_name);
5882 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5883 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5884 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5885 sip->password = g_strdup(password);
5886 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5887 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5888 g_strfreev(user_domain);
5890 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5891 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5892 g_free, (GDestroyNotify)g_hash_table_destroy);
5893 sipe_subscriptions_init(sipe_private);
5894 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5896 return((struct sipe_core_public *)sipe_private);
5899 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5901 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5903 g_free(sipe_private->epid);
5904 sipe_private->epid = NULL;
5906 sip_transport_disconnect(sipe_private);
5908 sipe_schedule_cancel_all(sipe_private);
5910 if (sip->allow_events) {
5911 GSList *entry = sip->allow_events;
5912 while (entry) {
5913 g_free(entry->data);
5914 entry = entry->next;
5917 g_slist_free(sip->allow_events);
5919 if (sip->containers) {
5920 GSList *entry = sip->containers;
5921 while (entry) {
5922 free_container((struct sipe_container *)entry->data);
5923 entry = entry->next;
5926 g_slist_free(sip->containers);
5928 if (sipe_private->contact)
5929 g_free(sipe_private->contact);
5930 sipe_private->contact = NULL;
5931 if (sip->regcallid)
5932 g_free(sip->regcallid);
5933 sip->regcallid = NULL;
5935 if (sipe_private->focus_factory_uri)
5936 g_free(sipe_private->focus_factory_uri);
5937 sipe_private->focus_factory_uri = NULL;
5939 if (sip->cal) {
5940 sipe_cal_calendar_free(sip->cal);
5942 sip->cal = NULL;
5946 * A callback for g_hash_table_foreach_remove
5948 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5949 SIPE_UNUSED_PARAMETER gpointer user_data)
5951 sipe_free_buddy((struct sipe_buddy *) buddy);
5953 /* We must return TRUE as the key/value have already been deleted */
5954 return(TRUE);
5957 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5959 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5962 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5963 SIPE_UNUSED_PARAMETER void *user_data)
5965 PurpleAccount *acct = purple_connection_get_account(gc);
5966 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5967 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5968 if (conv == NULL)
5969 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5970 purple_conversation_present(conv);
5971 g_free(id);
5974 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5975 SIPE_UNUSED_PARAMETER void *user_data)
5978 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5979 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5982 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
5983 struct sipmsg *msg,
5984 SIPE_UNUSED_PARAMETER struct transaction *trans)
5986 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5987 PurpleNotifySearchResults *results;
5988 PurpleNotifySearchColumn *column;
5989 sipe_xml *searchResults;
5990 const sipe_xml *mrow;
5991 int match_count = 0;
5992 gboolean more = FALSE;
5993 gchar *secondary;
5995 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
5997 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
5998 if (!searchResults) {
5999 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
6000 return FALSE;
6003 results = purple_notify_searchresults_new();
6005 if (results == NULL) {
6006 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
6007 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
6009 sipe_xml_free(searchResults);
6010 return FALSE;
6013 column = purple_notify_searchresults_column_new(_("User name"));
6014 purple_notify_searchresults_column_add(results, column);
6016 column = purple_notify_searchresults_column_new(_("Name"));
6017 purple_notify_searchresults_column_add(results, column);
6019 column = purple_notify_searchresults_column_new(_("Company"));
6020 purple_notify_searchresults_column_add(results, column);
6022 column = purple_notify_searchresults_column_new(_("Country"));
6023 purple_notify_searchresults_column_add(results, column);
6025 column = purple_notify_searchresults_column_new(_("Email"));
6026 purple_notify_searchresults_column_add(results, column);
6028 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
6029 GList *row = NULL;
6031 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
6032 row = g_list_append(row, g_strdup(uri_parts[1]));
6033 g_strfreev(uri_parts);
6035 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
6036 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
6037 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
6038 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
6040 purple_notify_searchresults_row_add(results, row);
6041 match_count++;
6044 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
6045 char *data = sipe_xml_data(mrow);
6046 more = (g_strcasecmp(data, "true") == 0);
6047 g_free(data);
6050 secondary = g_strdup_printf(
6051 dngettext(PACKAGE_NAME,
6052 "Found %d contact%s:",
6053 "Found %d contacts%s:", match_count),
6054 match_count, more ? _(" (more matched your query)") : "");
6056 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
6057 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
6058 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
6060 g_free(secondary);
6061 sipe_xml_free(searchResults);
6062 return TRUE;
6065 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6067 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
6068 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
6069 unsigned i = 0;
6071 if (!attrs) return;
6073 do {
6074 PurpleRequestField *field = entries->data;
6075 const char *id = purple_request_field_get_id(field);
6076 const char *value = purple_request_field_string_get_value(field);
6078 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
6080 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
6081 } while ((entries = g_list_next(entries)) != NULL);
6082 attrs[i] = NULL;
6084 if (i > 0) {
6085 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6086 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6087 gchar *query = g_strjoinv(NULL, attrs);
6088 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
6089 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
6090 send_soap_request_with_cb(sipe_private, domain_uri, body,
6091 process_search_contact_response, NULL);
6092 g_free(domain_uri);
6093 g_free(body);
6094 g_free(query);
6097 g_strfreev(attrs);
6100 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
6101 gpointer value,
6102 GString* str)
6104 struct sipe_publication *publication = value;
6106 g_string_append_printf( str,
6107 SIPE_PUB_XML_PUBLICATION_CLEAR,
6108 publication->category,
6109 publication->instance,
6110 publication->container,
6111 publication->version,
6112 "static");
6115 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
6117 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6118 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
6119 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
6121 GString* str = g_string_new(NULL);
6122 gchar *publications;
6124 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
6125 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
6126 return;
6129 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
6130 publications = g_string_free(str, FALSE);
6132 send_presence_publish(sipe_private, publications);
6133 g_free(publications);
6135 else /* 2005 */
6137 send_presence_soap0(sipe_private, FALSE, TRUE);
6141 /** for Access levels menu */
6142 #define INDENT_FMT " %s"
6144 /** Member is directly placed to access level container.
6145 * For example SIP URI of user is in the container.
6147 #define INDENT_MARKED_FMT "* %s"
6149 /** Member is indirectly belong to access level container.
6150 * For example 'sameEnterprise' is in the container and user
6151 * belongs to that same enterprise.
6153 #define INDENT_MARKED_INHERITED_FMT "= %s"
6155 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
6156 const gchar *name,
6157 const gchar *status_name,
6158 gboolean is_online)
6160 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6161 gchar *note = NULL;
6162 gboolean is_oof_note = FALSE;
6163 gchar *activity = NULL;
6164 gchar *calendar = NULL;
6165 gchar *meeting_subject = NULL;
6166 gchar *meeting_location = NULL;
6167 gchar *access_text = NULL;
6168 GSList *info = NULL;
6170 #define SIPE_ADD_BUDDY_INFO(l, t) \
6172 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
6173 sbi->label = (l); \
6174 sbi->text = (t); \
6175 info = g_slist_append(info, sbi); \
6178 if (sipe_public) { //happens on pidgin exit
6179 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
6180 if (sbuddy) {
6181 note = sbuddy->note;
6182 is_oof_note = sbuddy->is_oof_note;
6183 activity = sbuddy->activity;
6184 calendar = sipe_cal_get_description(sbuddy);
6185 meeting_subject = sbuddy->meeting_subject;
6186 meeting_location = sbuddy->meeting_location;
6188 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6189 gboolean is_group_access = FALSE;
6190 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
6191 const char *access_level = sipe_get_access_level_name(container_id);
6192 access_text = is_group_access ?
6193 g_strdup(access_level) :
6194 g_strdup_printf(INDENT_MARKED_FMT, access_level);
6198 //Layout
6199 if (is_online)
6201 gchar *status_str = g_strdup(activity ? activity : status_name);
6203 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
6205 if (is_online && !is_empty(calendar))
6207 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
6208 calendar = NULL;
6210 g_free(calendar);
6211 if (!is_empty(meeting_location))
6213 SIPE_ADD_BUDDY_INFO(_("Meeting in"), g_strdup(meeting_location));
6215 if (!is_empty(meeting_subject))
6217 SIPE_ADD_BUDDY_INFO(_("Meeting about"), g_strdup(meeting_subject));
6219 if (note)
6221 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
6222 SIPE_ADD_BUDDY_INFO(is_oof_note ? _("Out of office note") : _("Note"),
6223 g_strdup_printf("<i>%s</i>", note));
6225 if (access_text) {
6226 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
6229 return(info);
6232 static PurpleBuddy *
6233 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6235 PurpleBuddy *clone;
6236 const gchar *server_alias, *email;
6237 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6239 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6241 purple_blist_add_buddy(clone, NULL, group, NULL);
6243 server_alias = purple_buddy_get_server_alias(buddy);
6244 if (server_alias) {
6245 purple_blist_server_alias_buddy(clone, server_alias);
6248 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6249 if (email) {
6250 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
6253 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6254 //for UI to update;
6255 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6256 return clone;
6259 static void
6260 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6262 PurpleBuddy *buddy, *b;
6263 PurpleConnection *gc;
6264 PurpleGroup * group = purple_find_group(group_name);
6266 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6268 buddy = (PurpleBuddy *)node;
6270 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
6271 gc = purple_account_get_connection(buddy->account);
6273 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6274 if (!b){
6275 purple_blist_add_buddy_clone(group, buddy);
6278 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6281 static void
6282 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6284 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6286 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
6288 /* 2007+ conference */
6289 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
6291 sipe_conf_add(sipe_private, buddy->name);
6293 else /* 2005- multiparty chat */
6295 gchar *self = sip_uri_self(sipe_private);
6296 struct sip_session *session;
6298 session = sipe_session_add_chat(sipe_private);
6299 session->chat_title = sipe_chat_get_name(session->callid);
6300 session->roster_manager = g_strdup(self);
6302 session->backend_session = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
6303 session->chat_id,
6304 session->chat_title,
6305 self,
6306 FALSE);
6307 sipe_backend_chat_add(session->backend_session,
6308 self,
6309 FALSE);
6310 sipe_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
6312 g_free(self);
6317 * For 2007+ conference only.
6319 static void
6320 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
6322 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6323 struct sip_session *session;
6325 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
6326 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
6328 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6330 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
6334 * For 2007+ conference only.
6336 static void
6337 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
6339 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6340 struct sip_session *session;
6342 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
6343 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
6345 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6347 sipe_conf_delete_user(sipe_private, session, buddy->name);
6350 static void
6351 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
6353 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6354 struct sip_session *session;
6356 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
6357 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
6359 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6361 sipe_invite_to_chat(sipe_private, session, buddy->name);
6364 static void
6365 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
6367 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6369 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
6370 if (phone) {
6371 char *tel_uri = sip_to_tel_uri(phone);
6373 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
6374 sip_csta_make_call(sipe_private, tel_uri);
6376 g_free(tel_uri);
6380 static void
6381 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
6383 /** Translators: replace with URL to localized page
6384 * If it doesn't exist copy the original URL */
6385 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
6388 static void
6389 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6391 const gchar *email;
6392 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
6394 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6395 if (email)
6397 char *command_line = g_strdup_printf(
6398 #ifdef _WIN32
6399 "cmd /c start"
6400 #else
6401 "xdg-email"
6402 #endif
6403 " mailto:%s", email);
6404 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
6406 g_spawn_command_line_async(command_line, NULL);
6407 g_free(command_line);
6409 else
6411 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
6415 static void
6416 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
6417 struct sipe_container *container)
6419 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6420 struct sipe_container_member *member;
6422 if (!container || !container->members) return;
6424 member = ((struct sipe_container_member *)container->members->data);
6426 if (!member->type) return;
6428 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
6429 container->id, member->type, member->value ? member->value : "");
6431 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
6434 static GList *
6435 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6436 const char* uri);
6439 * A menu which appear when right-clicking on buddy in contact list.
6441 GList *
6442 sipe_buddy_menu(PurpleBuddy *buddy)
6444 PurpleBlistNode *g_node;
6445 PurpleGroup *group, *gr_parent;
6446 PurpleMenuAction *act;
6447 GList *menu = NULL;
6448 GList *menu_groups = NULL;
6449 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6450 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6451 const char *email;
6452 const char *phone;
6453 const char *phone_disp_str;
6454 gchar *self = sip_uri_self(sipe_private);
6456 SIPE_SESSION_FOREACH {
6457 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->backend_session)
6459 if (sipe_backend_chat_find(session->backend_session, buddy->name))
6461 gboolean conf_op = sipe_backend_chat_is_operator(session->backend_session, self);
6463 if (session->focus_uri
6464 && !sipe_backend_chat_is_operator(session->backend_session, buddy->name) /* Not conf OP */
6465 && conf_op) /* We are a conf OP */
6467 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
6468 act = purple_menu_action_new(label,
6469 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6470 session->chat_title, NULL);
6471 g_free(label);
6472 menu = g_list_prepend(menu, act);
6475 if (session->focus_uri
6476 && conf_op) /* We are a conf OP */
6478 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
6479 act = purple_menu_action_new(label,
6480 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6481 session->chat_title, NULL);
6482 g_free(label);
6483 menu = g_list_prepend(menu, act);
6486 else
6488 if (!session->focus_uri
6489 || (session->focus_uri && !session->locked))
6491 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
6492 act = purple_menu_action_new(label,
6493 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6494 session->chat_title, NULL);
6495 g_free(label);
6496 menu = g_list_prepend(menu, act);
6500 } SIPE_SESSION_FOREACH_END;
6502 act = purple_menu_action_new(_("New chat"),
6503 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6504 NULL, NULL);
6505 menu = g_list_prepend(menu, act);
6507 if (sip->csta && !sip->csta->line_status) {
6508 gchar *tmp = NULL;
6509 /* work phone */
6510 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
6511 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
6512 if (phone) {
6513 gchar *label = g_strdup_printf(_("Work %s"),
6514 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6515 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6516 g_free(tmp);
6517 tmp = NULL;
6518 g_free(label);
6519 menu = g_list_prepend(menu, act);
6522 /* mobile phone */
6523 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
6524 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
6525 if (phone) {
6526 gchar *label = g_strdup_printf(_("Mobile %s"),
6527 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6528 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6529 g_free(tmp);
6530 tmp = NULL;
6531 g_free(label);
6532 menu = g_list_prepend(menu, act);
6535 /* home phone */
6536 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
6537 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
6538 if (phone) {
6539 gchar *label = g_strdup_printf(_("Home %s"),
6540 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6541 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6542 g_free(tmp);
6543 tmp = NULL;
6544 g_free(label);
6545 menu = g_list_prepend(menu, act);
6548 /* other phone */
6549 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
6550 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
6551 if (phone) {
6552 gchar *label = g_strdup_printf(_("Other %s"),
6553 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6554 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6555 g_free(tmp);
6556 tmp = NULL;
6557 g_free(label);
6558 menu = g_list_prepend(menu, act);
6561 /* custom1 phone */
6562 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
6563 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
6564 if (phone) {
6565 gchar *label = g_strdup_printf(_("Custom1 %s"),
6566 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6567 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6568 g_free(tmp);
6569 tmp = NULL;
6570 g_free(label);
6571 menu = g_list_prepend(menu, act);
6575 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6576 if (email) {
6577 act = purple_menu_action_new(_("Send email..."),
6578 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6579 NULL, NULL);
6580 menu = g_list_prepend(menu, act);
6583 /* Access Level */
6584 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6585 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
6587 act = purple_menu_action_new(_("Access level"),
6588 NULL,
6589 NULL, menu_access_levels);
6590 menu = g_list_prepend(menu, act);
6593 /* Copy to */
6594 gr_parent = purple_buddy_get_group(buddy);
6595 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6596 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6597 continue;
6599 group = (PurpleGroup *)g_node;
6600 if (group == gr_parent)
6601 continue;
6603 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6604 continue;
6606 act = purple_menu_action_new(purple_group_get_name(group),
6607 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6608 group->name, NULL);
6609 menu_groups = g_list_prepend(menu_groups, act);
6611 menu_groups = g_list_reverse(menu_groups);
6613 act = purple_menu_action_new(_("Copy to"),
6614 NULL,
6615 NULL, menu_groups);
6616 menu = g_list_prepend(menu, act);
6618 menu = g_list_reverse(menu);
6620 g_free(self);
6621 return menu;
6624 static void
6625 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6627 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6628 const char *domain = purple_request_fields_get_string(fields, "access_domain");
6629 int index = purple_request_fields_get_choice(fields, "container_id");
6630 /* move Blocked first */
6631 int i = (index == 4) ? 0 : index + 1;
6632 int container_id = containers[i];
6634 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
6636 sipe_change_access_level(sipe_private, container_id, "domain", domain);
6639 static void
6640 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
6642 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6643 PurpleAccount *account = sip->account;
6644 PurpleConnection *gc = sip->gc;
6645 PurpleRequestFields *fields;
6646 PurpleRequestFieldGroup *g;
6647 PurpleRequestField *f;
6649 fields = purple_request_fields_new();
6651 g = purple_request_field_group_new(NULL);
6652 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
6653 purple_request_field_set_required(f, TRUE);
6654 purple_request_field_group_add_field(g, f);
6656 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
6657 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
6658 purple_request_field_choice_add(f, _("Team"));
6659 purple_request_field_choice_add(f, _("Company"));
6660 purple_request_field_choice_add(f, _("Public"));
6661 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
6662 purple_request_field_choice_set_default_value(f, 3); /* index */
6663 purple_request_field_set_required(f, TRUE);
6664 purple_request_field_group_add_field(g, f);
6666 purple_request_fields_add_group(fields, g);
6668 purple_request_fields(gc, _("Add new domain"),
6669 _("Add new domain"), NULL, fields,
6670 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
6671 _("Cancel"), NULL,
6672 account, NULL, NULL, gc);
6675 static void
6676 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
6678 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
6681 static GList *
6682 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
6683 const char* member_type,
6684 const char* member_value,
6685 const gboolean extra_menu)
6687 GList *menu_access_levels = NULL;
6688 unsigned int i;
6689 char *menu_name;
6690 PurpleMenuAction *act;
6691 struct sipe_container *container;
6692 struct sipe_container_member *member;
6693 gboolean is_group_access = FALSE;
6694 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
6696 for (i = 1; i <= CONTAINERS_LEN; i++) {
6697 /* to put Blocked level last in menu list.
6698 * Blocked should remaim in the first place in the containers[] array.
6700 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
6701 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
6703 container = g_new0(struct sipe_container, 1);
6704 member = g_new0(struct sipe_container_member, 1);
6705 container->id = containers[j];
6706 container->members = g_slist_append(container->members, member);
6707 member->type = g_strdup(member_type);
6708 member->value = g_strdup(member_value);
6710 /* current container/access level */
6711 if (((int)containers[j]) == container_id) {
6712 menu_name = is_group_access ?
6713 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
6714 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
6715 } else {
6716 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
6719 act = purple_menu_action_new(menu_name,
6720 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6721 container, NULL);
6722 g_free(menu_name);
6723 menu_access_levels = g_list_prepend(menu_access_levels, act);
6726 if (extra_menu && (container_id >= 0)) {
6727 /* separator */
6728 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
6729 menu_access_levels = g_list_prepend(menu_access_levels, act);
6731 if (!is_group_access) {
6732 container = g_new0(struct sipe_container, 1);
6733 member = g_new0(struct sipe_container_member, 1);
6734 container->id = -1;
6735 container->members = g_slist_append(container->members, member);
6736 member->type = g_strdup(member_type);
6737 member->value = g_strdup(member_value);
6739 /* Translators: remove (clear) previously assigned access level */
6740 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
6741 act = purple_menu_action_new(menu_name,
6742 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6743 container, NULL);
6744 g_free(menu_name);
6745 menu_access_levels = g_list_prepend(menu_access_levels, act);
6749 menu_access_levels = g_list_reverse(menu_access_levels);
6750 return menu_access_levels;
6753 static GList *
6754 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
6756 GList *menu_access_groups = NULL;
6757 PurpleMenuAction *act;
6758 GSList *access_domains = NULL;
6759 GSList *entry;
6760 char *menu_name;
6761 char *domain;
6763 act = purple_menu_action_new(_("People in my company"),
6764 NULL,
6765 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
6766 menu_access_groups = g_list_prepend(menu_access_groups, act);
6768 /* this is original name, don't edit */
6769 act = purple_menu_action_new(_("People in domains connected with my company"),
6770 NULL,
6771 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
6772 menu_access_groups = g_list_prepend(menu_access_groups, act);
6774 act = purple_menu_action_new(_("People in public domains"),
6775 NULL,
6776 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
6777 menu_access_groups = g_list_prepend(menu_access_groups, act);
6779 access_domains = sipe_get_access_domains(sipe_private);
6780 entry = access_domains;
6781 while (entry) {
6782 domain = entry->data;
6784 menu_name = g_strdup_printf(_("People at %s"), domain);
6785 act = purple_menu_action_new(menu_name,
6786 NULL,
6787 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6788 menu_access_groups = g_list_prepend(menu_access_groups, act);
6789 g_free(menu_name);
6791 entry = entry->next;
6794 /* separator */
6795 /* People in domains connected with my company */
6796 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6797 menu_access_groups = g_list_prepend(menu_access_groups, act);
6799 act = purple_menu_action_new(_("Add new domain..."),
6800 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6801 NULL, NULL);
6802 menu_access_groups = g_list_prepend(menu_access_groups, act);
6804 menu_access_groups = g_list_reverse(menu_access_groups);
6806 return menu_access_groups;
6809 static GList *
6810 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6811 const char* uri)
6813 GList *menu_access_levels = NULL;
6814 GList *menu_access_groups = NULL;
6815 char *menu_name;
6816 PurpleMenuAction *act;
6818 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6820 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6822 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6823 act = purple_menu_action_new(menu_name,
6824 NULL,
6825 NULL, menu_access_groups);
6826 g_free(menu_name);
6827 menu_access_levels = g_list_append(menu_access_levels, act);
6829 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6830 act = purple_menu_action_new(menu_name,
6831 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6832 NULL, NULL);
6833 g_free(menu_name);
6834 menu_access_levels = g_list_append(menu_access_levels, act);
6836 return menu_access_levels;
6839 static void
6840 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
6842 struct sipe_core_private *sipe_private = PURPLE_CHAT_TO_SIPE_CORE_PRIVATE;
6843 struct sip_session *session;
6845 session = sipe_session_find_chat_by_title(sipe_private,
6846 (gchar *)g_hash_table_lookup(chat->components, "channel"));
6847 sipe_conf_modify_conference_lock(sipe_private, session, locked);
6850 static void
6851 sipe_chat_menu_unlock_cb(PurpleChat *chat)
6853 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
6854 sipe_conf_modify_lock(chat, FALSE);
6857 static void
6858 sipe_chat_menu_lock_cb(PurpleChat *chat)
6860 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
6861 sipe_conf_modify_lock(chat, TRUE);
6864 GList *
6865 sipe_chat_menu(PurpleChat *chat)
6867 PurpleMenuAction *act;
6868 GList *menu = NULL;
6869 struct sipe_core_private *sipe_private = PURPLE_CHAT_TO_SIPE_CORE_PRIVATE;
6870 struct sip_session *session;
6871 gchar *self;
6873 session = sipe_session_find_chat_by_title(sipe_private,
6874 (gchar *)g_hash_table_lookup(chat->components, "channel"));
6875 if (!session) return NULL;
6877 self = sip_uri_self(sipe_private);
6879 if (session->focus_uri &&
6880 sipe_backend_chat_is_operator(session->backend_session, self))
6882 if (session->locked) {
6883 act = purple_menu_action_new(_("Unlock"),
6884 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
6885 NULL, NULL);
6886 menu = g_list_prepend(menu, act);
6887 } else {
6888 act = purple_menu_action_new(_("Lock"),
6889 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
6890 NULL, NULL);
6891 menu = g_list_prepend(menu, act);
6895 menu = g_list_reverse(menu);
6897 g_free(self);
6898 return menu;
6901 static gboolean
6902 process_get_info_response(struct sipe_core_private *sipe_private,
6903 struct sipmsg *msg, struct transaction *trans)
6905 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6906 char *uri = trans->payload->data;
6908 PurpleNotifyUserInfo *info;
6909 PurpleBuddy *pbuddy = NULL;
6910 struct sipe_buddy *sbuddy;
6911 const char *alias = NULL;
6912 char *device_name = NULL;
6913 char *server_alias = NULL;
6914 char *phone_number = NULL;
6915 char *email = NULL;
6916 const char *site;
6917 char *first_name = NULL;
6918 char *last_name = NULL;
6920 if (!sip) return FALSE;
6922 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6924 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6925 alias = purple_buddy_get_local_alias(pbuddy);
6927 //will query buddy UA's capabilities and send answer to log
6928 sipe_options_request(sipe_private, uri);
6930 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6931 if (sbuddy) {
6932 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6935 info = purple_notify_user_info_new();
6937 if (msg->response != 200) {
6938 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6939 } else {
6940 sipe_xml *searchResults;
6941 const sipe_xml *mrow;
6943 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6944 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6945 if (!searchResults) {
6946 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6947 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6948 const char *value;
6949 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6950 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6951 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6953 /* For 2007 system we will take this from ContactCard -
6954 * it has cleaner tel: URIs at least
6956 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6957 char *tel_uri = sip_to_tel_uri(phone_number);
6958 /* trims its parameters, so call first */
6959 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, server_alias);
6960 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
6961 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
6962 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, phone_number);
6963 g_free(tel_uri);
6966 if (server_alias && strlen(server_alias) > 0) {
6967 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6969 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6970 purple_notify_user_info_add_pair(info, _("Job title"), value);
6972 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6973 purple_notify_user_info_add_pair(info, _("Office"), value);
6975 if (phone_number && strlen(phone_number) > 0) {
6976 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
6978 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6979 purple_notify_user_info_add_pair(info, _("Company"), value);
6981 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6982 purple_notify_user_info_add_pair(info, _("City"), value);
6984 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
6985 purple_notify_user_info_add_pair(info, _("State"), value);
6987 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
6988 purple_notify_user_info_add_pair(info, _("Country"), value);
6990 if (email && strlen(email) > 0) {
6991 purple_notify_user_info_add_pair(info, _("Email address"), email);
6995 sipe_xml_free(searchResults);
6998 purple_notify_user_info_add_section_break(info);
7000 if (is_empty(server_alias)) {
7001 g_free(server_alias);
7002 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
7003 if (server_alias) {
7004 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
7008 /* present alias if it differs from server alias */
7009 if (alias && !sipe_strequal(alias, server_alias))
7011 purple_notify_user_info_add_pair(info, _("Alias"), alias);
7014 if (is_empty(email)) {
7015 g_free(email);
7016 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
7017 if (email) {
7018 purple_notify_user_info_add_pair(info, _("Email address"), email);
7022 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
7023 if (site) {
7024 purple_notify_user_info_add_pair(info, _("Site"), site);
7027 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
7028 if (first_name && last_name) {
7029 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
7031 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
7032 g_free(link);
7034 g_free(first_name);
7035 g_free(last_name);
7037 if (device_name) {
7038 purple_notify_user_info_add_pair(info, _("Device"), device_name);
7041 /* show a buddy's user info in a nice dialog box */
7042 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
7043 uri, /* buddy's URI */
7044 info, /* body */
7045 NULL, /* callback called when dialog closed */
7046 NULL); /* userdata for callback */
7048 g_free(phone_number);
7049 g_free(server_alias);
7050 g_free(email);
7051 g_free(device_name);
7053 return TRUE;
7057 * AD search first, LDAP based
7059 void sipe_get_info(PurpleConnection *gc, const char *username)
7061 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
7062 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
7063 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
7064 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
7065 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
7067 payload->destroy = g_free;
7068 payload->data = g_strdup(username);
7070 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
7071 send_soap_request_with_cb(sipe_private, domain_uri, body,
7072 process_get_info_response, payload);
7073 g_free(domain_uri);
7074 g_free(body);
7075 g_free(row);
7079 Local Variables:
7080 mode: c
7081 c-file-style: "bsd"
7082 indent-tabs-mode: t
7083 tab-width: 8
7084 End: