audio: don't use GHashTableIter
[siplcs.git] / src / core / sipe.c
blob2a0c663cd8c0d4421b347760c38659f06d5dc590
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);
3091 ret = FALSE;
3092 } else {
3093 const gchar *message_id = sipmsg_find_header(msg, "Message-Id");
3094 if (message_id) {
3095 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message->body));
3096 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
3097 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
3100 g_hash_table_remove(session->unconfirmed_messages, key);
3101 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
3102 key, g_hash_table_size(session->unconfirmed_messages));
3105 g_free(key);
3106 g_free(with);
3108 if (ret) sipe_im_process_queue(sipe_private, session);
3109 return ret;
3112 static void sipe_send_message(struct sipe_core_private *sipe_private,
3113 struct sip_dialog *dialog,
3114 const char *msg, const char *content_type)
3116 gchar *hdr;
3117 gchar *tmp;
3118 char *msgtext = NULL;
3119 const gchar *msgr = "";
3120 gchar *tmp2 = NULL;
3122 if (!g_str_has_prefix(content_type, "text/x-msmsgsinvite")) {
3123 char *msgformat;
3124 gchar *msgr_value;
3126 sipe_parse_html(msg, &msgformat, &msgtext);
3127 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat);
3129 msgr_value = sipmsg_get_msgr_string(msgformat);
3130 g_free(msgformat);
3131 if (msgr_value) {
3132 msgr = tmp2 = g_strdup_printf(";msgr=%s", msgr_value);
3133 g_free(msgr_value);
3135 } else {
3136 msgtext = g_strdup(msg);
3139 tmp = get_contact(sipe_private);
3140 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3141 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3142 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3143 if (content_type == NULL)
3144 content_type = "text/plain";
3146 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp, content_type, msgr);
3147 g_free(tmp);
3148 g_free(tmp2);
3150 sip_transport_request(sipe_private,
3151 "MESSAGE",
3152 dialog->with,
3153 dialog->with,
3154 hdr,
3155 msgtext,
3156 dialog,
3157 process_message_response);
3158 g_free(msgtext);
3159 g_free(hdr);
3163 void
3164 sipe_im_process_queue (struct sipe_core_private *sipe_private,
3165 struct sip_session * session)
3167 GSList *entry2 = session->outgoing_message_queue;
3168 while (entry2) {
3169 struct queued_message *msg = entry2->data;
3171 /* for multiparty chat or conference */
3172 if (session->is_multiparty || session->focus_uri) {
3173 gchar *who = sip_uri_self(sipe_private);
3174 sipe_backend_chat_message(SIPE_CORE_PUBLIC,
3175 session->chat_id,
3176 who,
3177 msg->body);
3178 g_free(who);
3181 SIPE_DIALOG_FOREACH {
3182 char *key;
3183 struct queued_message *message;
3185 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
3187 message = g_new0(struct queued_message,1);
3188 message->body = g_strdup(msg->body);
3189 if (msg->content_type != NULL)
3190 message->content_type = g_strdup(msg->content_type);
3192 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
3193 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3194 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
3195 key, g_hash_table_size(session->unconfirmed_messages));
3196 g_free(key);
3198 sipe_send_message(sipe_private, dialog, msg->body, msg->content_type);
3199 } SIPE_DIALOG_FOREACH_END;
3201 entry2 = sipe_session_dequeue_message(session);
3205 static void
3206 sipe_refer_notify(struct sipe_core_private *sipe_private,
3207 struct sip_session *session,
3208 const gchar *who,
3209 int status,
3210 const gchar *desc)
3212 gchar *hdr;
3213 gchar *body;
3214 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3216 hdr = g_strdup_printf(
3217 "Event: refer\r\n"
3218 "Subscription-State: %s\r\n"
3219 "Content-Type: message/sipfrag\r\n",
3220 status >= 200 ? "terminated" : "active");
3222 body = g_strdup_printf(
3223 "SIP/2.0 %d %s\r\n",
3224 status, desc);
3226 sip_transport_request(sipe_private,
3227 "NOTIFY",
3228 who,
3229 who,
3230 hdr,
3231 body,
3232 dialog,
3233 NULL);
3235 g_free(hdr);
3236 g_free(body);
3239 static gboolean
3240 process_invite_response(struct sipe_core_private *sipe_private,
3241 struct sipmsg *msg, struct transaction *trans)
3243 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3244 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
3245 struct sip_session *session;
3246 struct sip_dialog *dialog;
3247 char *cseq;
3248 char *key;
3249 struct queued_message *message;
3250 struct sipmsg *request_msg = trans->msg;
3252 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
3253 gchar *referred_by;
3255 session = sipe_session_find_chat_or_im(sipe_private, callid, with);
3256 if (!session) {
3257 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
3258 g_free(with);
3259 return FALSE;
3262 dialog = sipe_dialog_find(session, with);
3263 if (!dialog) {
3264 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
3265 g_free(with);
3266 return FALSE;
3269 sipe_dialog_parse(dialog, msg, TRUE);
3271 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
3272 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
3273 g_free(cseq);
3274 message = g_hash_table_lookup(session->unconfirmed_messages, key);
3276 if (msg->response != 200) {
3277 PurpleBuddy *pbuddy;
3278 const char *alias = with;
3279 const char *warn_hdr = sipmsg_find_header(msg, "Warning");
3280 int warning = -1;
3282 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
3284 if (warn_hdr) {
3285 gchar **parts = g_strsplit(warn_hdr, " ", 2);
3286 if (parts[0]) {
3287 warning = atoi(parts[0]);
3289 g_strfreev(parts);
3292 /* cancel file transfer as rejected by server */
3293 if (msg->response == 606 && /* Not acceptable all. */
3294 warning == 309 && /* Message contents not allowed by policy */
3295 message && g_str_has_prefix(message->content_type, "text/x-msmsgsinvite"))
3297 GSList *parsed_body = sipe_ft_parse_msg_body(message->body);
3298 sipe_ft_incoming_cancel(dialog, parsed_body);
3299 sipe_utils_nameval_free(parsed_body);
3302 if ((pbuddy = purple_find_buddy(sip->account, with))) {
3303 alias = purple_buddy_get_alias(pbuddy);
3306 if (message) {
3307 sipe_present_message_undelivered_err(sipe_private, session, msg->response, warning, alias, message->body);
3308 } else {
3309 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
3310 sipe_present_err(sipe_private, session, tmp_msg);
3311 g_free(tmp_msg);
3314 sipe_dialog_remove(session, with);
3316 g_free(key);
3317 g_free(with);
3318 return FALSE;
3321 dialog->cseq = 0;
3322 sip_transport_ack(sipe_private, dialog);
3323 dialog->outgoing_invite = NULL;
3324 dialog->is_established = TRUE;
3326 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3327 if (referred_by) {
3328 sipe_refer_notify(sipe_private, session, referred_by, 200, "OK");
3329 g_free(referred_by);
3332 /* add user to chat if it is a multiparty session */
3333 if (session->is_multiparty) {
3334 sipe_backend_chat_add(session->backend_session,
3335 with,
3336 TRUE);
3339 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3340 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
3341 sipe_session_dequeue_message(session);
3344 sipe_im_process_queue(sipe_private, session);
3346 g_hash_table_remove(session->unconfirmed_messages, key);
3347 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
3348 key, g_hash_table_size(session->unconfirmed_messages));
3350 g_free(key);
3351 g_free(with);
3352 return TRUE;
3356 void
3357 sipe_invite(struct sipe_core_private *sipe_private,
3358 struct sip_session *session,
3359 const gchar *who,
3360 const gchar *msg_body,
3361 const gchar *msg_content_type,
3362 const gchar *referred_by,
3363 const gboolean is_triggered)
3365 gchar *hdr;
3366 gchar *to;
3367 gchar *contact;
3368 gchar *body;
3369 gchar *self;
3370 char *ms_text_format = NULL;
3371 gchar *roster_manager;
3372 gchar *end_points;
3373 gchar *referred_by_str;
3374 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3376 if (dialog && dialog->is_established) {
3377 SIPE_DEBUG_INFO("session with %s already has a dialog open", who);
3378 return;
3381 if (!dialog) {
3382 dialog = sipe_dialog_add(session);
3383 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3384 dialog->with = g_strdup(who);
3387 if (!(dialog->ourtag)) {
3388 dialog->ourtag = gentag();
3391 to = sip_uri(who);
3393 if (msg_body) {
3394 char *msgtext = NULL;
3395 char *base64_msg;
3396 const gchar *msgr = "";
3397 char *key;
3398 struct queued_message *message;
3399 gchar *tmp = NULL;
3401 if (!g_str_has_prefix(msg_content_type, "text/x-msmsgsinvite")) {
3402 char *msgformat;
3403 gchar *msgr_value;
3405 sipe_parse_html(msg_body, &msgformat, &msgtext);
3406 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat);
3408 msgr_value = sipmsg_get_msgr_string(msgformat);
3409 g_free(msgformat);
3410 if (msgr_value) {
3411 msgr = tmp = g_strdup_printf(";msgr=%s", msgr_value);
3412 g_free(msgr_value);
3414 } else {
3415 msgtext = g_strdup(msg_body);
3418 base64_msg = g_base64_encode((guchar*) msgtext, strlen(msgtext));
3419 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT,
3420 msg_content_type ? msg_content_type : "text/plain",
3421 msgr,
3422 base64_msg);
3423 g_free(msgtext);
3424 g_free(tmp);
3425 g_free(base64_msg);
3427 message = g_new0(struct queued_message,1);
3428 message->body = g_strdup(msg_body);
3429 if (msg_content_type != NULL)
3430 message->content_type = g_strdup(msg_content_type);
3432 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3433 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), message);
3434 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
3435 key, g_hash_table_size(session->unconfirmed_messages));
3436 g_free(key);
3439 contact = get_contact(sipe_private);
3440 end_points = get_end_points(sipe_private, session);
3441 self = sip_uri_self(sipe_private);
3442 roster_manager = g_strdup_printf(
3443 "Roster-Manager: %s\r\n"
3444 "EndPoints: %s\r\n",
3445 self,
3446 end_points);
3447 referred_by_str = referred_by ?
3448 g_strdup_printf(
3449 "Referred-By: %s\r\n",
3450 referred_by)
3451 : g_strdup("");
3452 hdr = g_strdup_printf(
3453 "Supported: ms-sender\r\n"
3454 "%s"
3455 "%s"
3456 "%s"
3457 "%s"
3458 "Contact: %s\r\n%s"
3459 "Content-Type: application/sdp\r\n",
3460 sipe_strcase_equal(session->roster_manager, self) ? roster_manager : "",
3461 referred_by_str,
3462 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3463 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3464 contact,
3465 ms_text_format ? ms_text_format : "");
3466 g_free(ms_text_format);
3467 g_free(self);
3469 body = g_strdup_printf(
3470 "v=0\r\n"
3471 "o=- 0 0 IN IP4 %s\r\n"
3472 "s=session\r\n"
3473 "c=IN IP4 %s\r\n"
3474 "t=0 0\r\n"
3475 "m=%s %d sip null\r\n"
3476 "a=accept-types:" SDP_ACCEPT_TYPES "\r\n",
3477 sipe_backend_network_ip_address(),
3478 sipe_backend_network_ip_address(),
3479 SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) ? "message" : "x-ms-message",
3480 sip_transport_port(sipe_private));
3482 dialog->outgoing_invite = sip_transport_request(sipe_private,
3483 "INVITE",
3486 hdr,
3487 body,
3488 dialog,
3489 process_invite_response);
3491 g_free(to);
3492 g_free(roster_manager);
3493 g_free(end_points);
3494 g_free(referred_by_str);
3495 g_free(body);
3496 g_free(hdr);
3497 g_free(contact);
3500 void
3501 sipe_convo_closed(PurpleConnection * gc, const char *who)
3503 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3505 SIPE_DEBUG_INFO("conversation with %s closed", who);
3506 sipe_session_close(sipe_private,
3507 sipe_session_find_im(sipe_private, who));
3510 void
3511 sipe_chat_leave (PurpleConnection *gc, int id)
3513 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3514 struct sip_session *session = sipe_session_find_chat_by_id(sipe_private,
3515 id);
3517 sipe_session_close(sipe_private, session);
3520 int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3521 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3523 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3524 struct sip_session *session;
3525 struct sip_dialog *dialog;
3526 gchar *uri = sip_uri(who);
3528 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what);
3530 session = sipe_session_find_or_add_im(sipe_private, uri);
3531 dialog = sipe_dialog_find(session, uri);
3533 // Queue the message
3534 sipe_session_enqueue_message(session, what, NULL);
3536 if (dialog && !dialog->outgoing_invite) {
3537 sipe_im_process_queue(sipe_private, session);
3538 } else if (!dialog || !dialog->outgoing_invite) {
3539 // Need to send the INVITE to get the outgoing dialog setup
3540 sipe_invite(sipe_private, session, uri, what, NULL, NULL, FALSE);
3543 g_free(uri);
3544 return 1;
3547 int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
3548 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3550 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
3551 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3552 struct sip_session *session;
3554 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what);
3556 session = sipe_session_find_chat_by_id(sipe_private, id);
3558 // Queue the message
3559 if (session && session->dialogs) {
3560 sipe_session_enqueue_message(session,what,NULL);
3561 sipe_im_process_queue(sipe_private, session);
3562 } else if (sip) {
3563 gchar *chat_name = purple_find_chat(sip->gc, id)->name;
3564 const gchar *proto_chat_id = sipe_chat_find_name(chat_name);
3566 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name ? chat_name : "NULL");
3567 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id ? proto_chat_id : "NULL");
3569 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
3570 struct sip_session *session = sipe_session_add_chat(sipe_private);
3572 session->is_multiparty = FALSE;
3573 session->focus_uri = g_strdup(proto_chat_id);
3574 sipe_session_enqueue_message(session, what, NULL);
3575 sipe_invite_conf_focus(sipe_private, session);
3579 return 1;
3583 * Returns 2005-style activity and Availability.
3585 * @param status Sipe statis id.
3587 static void
3588 sipe_get_act_avail_by_status_2005(const char *status,
3589 int *activity,
3590 int *availability)
3592 int avail = 300; /* online */
3593 int act = 400; /* Available */
3595 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
3596 act = 100;
3597 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
3598 // act = 150;
3599 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
3600 act = 300;
3601 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
3602 act = 400;
3603 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
3604 // act = 500;
3605 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
3606 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
3607 act = 600;
3608 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
3609 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
3610 avail = 0; /* offline */
3611 act = 100;
3612 } else {
3613 act = 400; /* Available */
3616 if (activity) *activity = act;
3617 if (availability) *availability = avail;
3621 * [MS-SIP] 2.2.1
3623 * @param activity 2005 aggregated activity. Ex.: 600
3624 * @param availablity 2005 aggregated availablity. Ex.: 300
3626 static const char *
3627 sipe_get_status_by_act_avail_2005(const int activity,
3628 const int availablity,
3629 char **activity_desc)
3631 const char *status_id = NULL;
3632 const char *act = NULL;
3634 if (activity < 150) {
3635 status_id = SIPE_STATUS_ID_AWAY;
3636 } else if (activity < 200) {
3637 //status_id = SIPE_STATUS_ID_LUNCH;
3638 status_id = SIPE_STATUS_ID_AWAY;
3639 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
3640 } else if (activity < 300) {
3641 //status_id = SIPE_STATUS_ID_IDLE;
3642 status_id = SIPE_STATUS_ID_AWAY;
3643 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3644 } else if (activity < 400) {
3645 status_id = SIPE_STATUS_ID_BRB;
3646 } else if (activity < 500) {
3647 status_id = SIPE_STATUS_ID_AVAILABLE;
3648 } else if (activity < 600) {
3649 //status_id = SIPE_STATUS_ID_ON_PHONE;
3650 status_id = SIPE_STATUS_ID_BUSY;
3651 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
3652 } else if (activity < 700) {
3653 status_id = SIPE_STATUS_ID_BUSY;
3654 } else if (activity < 800) {
3655 status_id = SIPE_STATUS_ID_AWAY;
3656 } else {
3657 status_id = SIPE_STATUS_ID_AVAILABLE;
3660 if (availablity < 100)
3661 status_id = SIPE_STATUS_ID_OFFLINE;
3663 if (activity_desc && act) {
3664 g_free(*activity_desc);
3665 *activity_desc = g_strdup(act);
3668 return status_id;
3672 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
3674 static const char*
3675 sipe_get_status_by_availability(int avail,
3676 char** activity_desc)
3678 const char *status;
3679 const char *act = NULL;
3681 if (avail < 3000) {
3682 status = SIPE_STATUS_ID_OFFLINE;
3683 } else if (avail < 4500) {
3684 status = SIPE_STATUS_ID_AVAILABLE;
3685 } else if (avail < 6000) {
3686 //status = SIPE_STATUS_ID_IDLE;
3687 status = SIPE_STATUS_ID_AVAILABLE;
3688 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
3689 } else if (avail < 7500) {
3690 status = SIPE_STATUS_ID_BUSY;
3691 } else if (avail < 9000) {
3692 //status = SIPE_STATUS_ID_BUSYIDLE;
3693 status = SIPE_STATUS_ID_BUSY;
3694 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
3695 } else if (avail < 12000) {
3696 status = SIPE_STATUS_ID_DND;
3697 } else if (avail < 15000) {
3698 status = SIPE_STATUS_ID_BRB;
3699 } else if (avail < 18000) {
3700 status = SIPE_STATUS_ID_AWAY;
3701 } else {
3702 status = SIPE_STATUS_ID_OFFLINE;
3705 if (activity_desc && act) {
3706 g_free(*activity_desc);
3707 *activity_desc = g_strdup(act);
3710 return status;
3714 * Returns 2007-style availability value
3716 * @param sipe_status_id (in)
3717 * @param activity_token (out) Must be g_free()'d after use if consumed.
3719 static int
3720 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
3722 int availability;
3723 sipe_activity activity = SIPE_ACTIVITY_UNSET;
3725 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
3726 availability = 15500;
3727 if (!activity_token || !(*activity_token)) {
3728 activity = SIPE_ACTIVITY_AWAY;
3730 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
3731 availability = 12500;
3732 activity = SIPE_ACTIVITY_BRB;
3733 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
3734 availability = 9500;
3735 activity = SIPE_ACTIVITY_DND;
3736 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
3737 availability = 6500;
3738 if (!activity_token || !(*activity_token)) {
3739 activity = SIPE_ACTIVITY_BUSY;
3741 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
3742 availability = 3500;
3743 activity = SIPE_ACTIVITY_ONLINE;
3744 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
3745 availability = 0;
3746 } else {
3747 // Offline or invisible
3748 availability = 18500;
3749 activity = SIPE_ACTIVITY_OFFLINE;
3752 if (activity_token) {
3753 *activity_token = g_strdup(sipe_activity_map[activity].token);
3755 return availability;
3758 static void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
3759 const gchar *data, unsigned len)
3761 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3762 const char *uri;
3763 sipe_xml *xn_categories;
3764 const sipe_xml *xn_category;
3765 const char *status = NULL;
3766 gboolean do_update_status = FALSE;
3767 gboolean has_note_cleaned = FALSE;
3768 gboolean has_free_busy_cleaned = FALSE;
3770 xn_categories = sipe_xml_parse(data, len);
3771 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
3773 for (xn_category = sipe_xml_child(xn_categories, "category");
3774 xn_category ;
3775 xn_category = sipe_xml_twin(xn_category) )
3777 const sipe_xml *xn_node;
3778 const char *tmp;
3779 const char *attrVar = sipe_xml_attribute(xn_category, "name");
3780 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
3781 sipe_utils_str_to_time(tmp) : 0;
3783 /* contactCard */
3784 if (sipe_strequal(attrVar, "contactCard"))
3786 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
3788 if (card) {
3789 const sipe_xml *node;
3790 /* identity - Display Name and email */
3791 node = sipe_xml_child(card, "identity");
3792 if (node) {
3793 char* display_name = sipe_xml_data(
3794 sipe_xml_child(node, "name/displayName"));
3795 char* email = sipe_xml_data(
3796 sipe_xml_child(node, "email"));
3798 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
3799 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
3801 g_free(display_name);
3802 g_free(email);
3804 /* company */
3805 node = sipe_xml_child(card, "company");
3806 if (node) {
3807 char* company = sipe_xml_data(node);
3808 sipe_update_user_info(sipe_private, uri, COMPANY_PROP, company);
3809 g_free(company);
3811 /* department */
3812 node = sipe_xml_child(card, "department");
3813 if (node) {
3814 char* department = sipe_xml_data(node);
3815 sipe_update_user_info(sipe_private, uri, DEPARTMENT_PROP, department);
3816 g_free(department);
3818 /* title */
3819 node = sipe_xml_child(card, "title");
3820 if (node) {
3821 char* title = sipe_xml_data(node);
3822 sipe_update_user_info(sipe_private, uri, TITLE_PROP, title);
3823 g_free(title);
3825 /* office */
3826 node = sipe_xml_child(card, "office");
3827 if (node) {
3828 char* office = sipe_xml_data(node);
3829 sipe_update_user_info(sipe_private, uri, OFFICE_PROP, office);
3830 g_free(office);
3832 /* site (url) */
3833 node = sipe_xml_child(card, "url");
3834 if (node) {
3835 char* site = sipe_xml_data(node);
3836 sipe_update_user_info(sipe_private, uri, SITE_PROP, site);
3837 g_free(site);
3839 /* phone */
3840 for (node = sipe_xml_child(card, "phone");
3841 node;
3842 node = sipe_xml_twin(node))
3844 const char *phone_type = sipe_xml_attribute(node, "type");
3845 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
3846 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
3848 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
3850 g_free(phone);
3851 g_free(phone_display_string);
3853 /* address */
3854 for (node = sipe_xml_child(card, "address");
3855 node;
3856 node = sipe_xml_twin(node))
3858 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
3859 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
3860 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
3861 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
3862 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
3863 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
3865 sipe_update_user_info(sipe_private, uri, ADDRESS_STREET_PROP, street);
3866 sipe_update_user_info(sipe_private, uri, ADDRESS_CITY_PROP, city);
3867 sipe_update_user_info(sipe_private, uri, ADDRESS_STATE_PROP, state);
3868 sipe_update_user_info(sipe_private, uri, ADDRESS_ZIPCODE_PROP, zipcode);
3869 sipe_update_user_info(sipe_private, uri, ADDRESS_COUNTRYCODE_PROP, country_code);
3871 g_free(street);
3872 g_free(city);
3873 g_free(state);
3874 g_free(zipcode);
3875 g_free(country_code);
3877 break;
3882 /* note */
3883 else if (sipe_strequal(attrVar, "note"))
3885 if (uri) {
3886 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3888 if (!has_note_cleaned) {
3889 has_note_cleaned = TRUE;
3891 g_free(sbuddy->note);
3892 sbuddy->note = NULL;
3893 sbuddy->is_oof_note = FALSE;
3894 sbuddy->note_since = publish_time;
3896 do_update_status = TRUE;
3898 if (sbuddy && (publish_time >= sbuddy->note_since)) {
3899 /* clean up in case no 'note' element is supplied
3900 * which indicate note removal in client
3902 g_free(sbuddy->note);
3903 sbuddy->note = NULL;
3904 sbuddy->is_oof_note = FALSE;
3905 sbuddy->note_since = publish_time;
3907 xn_node = sipe_xml_child(xn_category, "note/body");
3908 if (xn_node) {
3909 char *tmp;
3910 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
3911 g_free(tmp);
3912 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
3913 sbuddy->note_since = publish_time;
3915 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
3916 uri, sbuddy->note ? sbuddy->note : "");
3918 /* to trigger UI refresh in case no status info is supplied in this update */
3919 do_update_status = TRUE;
3923 /* state */
3924 else if(sipe_strequal(attrVar, "state"))
3926 char *tmp;
3927 int availability;
3928 const sipe_xml *xn_availability;
3929 const sipe_xml *xn_activity;
3930 const sipe_xml *xn_meeting_subject;
3931 const sipe_xml *xn_meeting_location;
3932 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
3934 xn_node = sipe_xml_child(xn_category, "state");
3935 if (!xn_node) continue;
3936 xn_availability = sipe_xml_child(xn_node, "availability");
3937 if (!xn_availability) continue;
3938 xn_activity = sipe_xml_child(xn_node, "activity");
3939 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
3940 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
3942 tmp = sipe_xml_data(xn_availability);
3943 availability = atoi(tmp);
3944 g_free(tmp);
3946 /* activity, meeting_subject, meeting_location */
3947 if (sbuddy) {
3948 char *tmp = NULL;
3950 /* activity */
3951 g_free(sbuddy->activity);
3952 sbuddy->activity = NULL;
3953 if (xn_activity) {
3954 const char *token = sipe_xml_attribute(xn_activity, "token");
3955 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
3957 /* from token */
3958 if (!is_empty(token)) {
3959 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
3961 /* from custom element */
3962 if (xn_custom) {
3963 char *custom = sipe_xml_data(xn_custom);
3965 if (!is_empty(custom)) {
3966 sbuddy->activity = custom;
3967 custom = NULL;
3969 g_free(custom);
3972 /* meeting_subject */
3973 g_free(sbuddy->meeting_subject);
3974 sbuddy->meeting_subject = NULL;
3975 if (xn_meeting_subject) {
3976 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
3978 if (!is_empty(meeting_subject)) {
3979 sbuddy->meeting_subject = meeting_subject;
3980 meeting_subject = NULL;
3982 g_free(meeting_subject);
3984 /* meeting_location */
3985 g_free(sbuddy->meeting_location);
3986 sbuddy->meeting_location = NULL;
3987 if (xn_meeting_location) {
3988 char *meeting_location = sipe_xml_data(xn_meeting_location);
3990 if (!is_empty(meeting_location)) {
3991 sbuddy->meeting_location = meeting_location;
3992 meeting_location = NULL;
3994 g_free(meeting_location);
3997 status = sipe_get_status_by_availability(availability, &tmp);
3998 if (sbuddy->activity && tmp) {
3999 char *tmp2 = sbuddy->activity;
4001 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
4002 g_free(tmp);
4003 g_free(tmp2);
4004 } else if (tmp) {
4005 sbuddy->activity = tmp;
4009 do_update_status = TRUE;
4011 /* calendarData */
4012 else if(sipe_strequal(attrVar, "calendarData"))
4014 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
4015 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
4016 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
4018 if (sbuddy && xn_free_busy) {
4019 if (!has_free_busy_cleaned) {
4020 has_free_busy_cleaned = TRUE;
4022 g_free(sbuddy->cal_start_time);
4023 sbuddy->cal_start_time = NULL;
4025 g_free(sbuddy->cal_free_busy_base64);
4026 sbuddy->cal_free_busy_base64 = NULL;
4028 g_free(sbuddy->cal_free_busy);
4029 sbuddy->cal_free_busy = NULL;
4031 sbuddy->cal_free_busy_published = publish_time;
4034 if (publish_time >= sbuddy->cal_free_busy_published) {
4035 g_free(sbuddy->cal_start_time);
4036 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
4038 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
4039 15 : 0;
4041 g_free(sbuddy->cal_free_busy_base64);
4042 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
4044 g_free(sbuddy->cal_free_busy);
4045 sbuddy->cal_free_busy = NULL;
4047 sbuddy->cal_free_busy_published = publish_time;
4049 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);
4053 if (sbuddy && xn_working_hours) {
4054 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
4059 if (do_update_status) {
4060 if (!status) { /* no status category in this update, using contact's current status */
4061 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
4062 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
4063 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
4064 status = purple_status_get_id(pstatus);
4067 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
4068 sipe_got_user_status(sipe_private, uri, status);
4071 sipe_xml_free(xn_categories);
4074 static void sipe_subscribe_poolfqdn_resource_uri(const char *host,
4075 GSList *server,
4076 struct sipe_core_private *sipe_private)
4078 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4079 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
4080 payload->host = g_strdup(host);
4081 payload->buddies = server;
4082 sipe_subscribe_presence_batched_routed(sipe_private,
4083 payload);
4084 sipe_subscribe_presence_batched_routed_free(payload);
4087 static void process_incoming_notify_rlmi_resub(struct sipe_core_private *sipe_private,
4088 const gchar *data, unsigned len)
4090 sipe_xml *xn_list;
4091 const sipe_xml *xn_resource;
4092 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4093 g_free, NULL);
4094 GSList *server;
4095 gchar *host;
4097 xn_list = sipe_xml_parse(data, len);
4099 for (xn_resource = sipe_xml_child(xn_list, "resource");
4100 xn_resource;
4101 xn_resource = sipe_xml_twin(xn_resource) )
4103 const char *uri, *state;
4104 const sipe_xml *xn_instance;
4106 xn_instance = sipe_xml_child(xn_resource, "instance");
4107 if (!xn_instance) continue;
4109 uri = sipe_xml_attribute(xn_resource, "uri");
4110 state = sipe_xml_attribute(xn_instance, "state");
4111 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri, state);
4113 if (strstr(state, "resubscribe")) {
4114 const char *poolFqdn = sipe_xml_attribute(xn_instance, "poolFqdn");
4116 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4117 gchar *user = g_strdup(uri);
4118 host = g_strdup(poolFqdn);
4119 server = g_hash_table_lookup(servers, host);
4120 server = g_slist_append(server, user);
4121 g_hash_table_insert(servers, host, server);
4122 } else {
4123 sipe_subscribe_presence_single(sipe_private,
4124 (void *) uri);
4129 /* Send out any deferred poolFqdn subscriptions */
4130 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sipe_private);
4131 g_hash_table_destroy(servers);
4133 sipe_xml_free(xn_list);
4136 static void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
4137 const gchar *data, unsigned len)
4139 gchar *uri;
4140 gchar *getbasic;
4141 gchar *activity = NULL;
4142 sipe_xml *pidf;
4143 const sipe_xml *basicstatus = NULL, *tuple, *status;
4144 gboolean isonline = FALSE;
4145 const sipe_xml *display_name_node;
4147 pidf = sipe_xml_parse(data, len);
4148 if (!pidf) {
4149 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
4150 return;
4153 if ((tuple = sipe_xml_child(pidf, "tuple")))
4155 if ((status = sipe_xml_child(tuple, "status"))) {
4156 basicstatus = sipe_xml_child(status, "basic");
4160 if (!basicstatus) {
4161 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
4162 sipe_xml_free(pidf);
4163 return;
4166 getbasic = sipe_xml_data(basicstatus);
4167 if (!getbasic) {
4168 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
4169 sipe_xml_free(pidf);
4170 return;
4173 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
4174 if (strstr(getbasic, "open")) {
4175 isonline = TRUE;
4177 g_free(getbasic);
4179 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
4181 display_name_node = sipe_xml_child(pidf, "display-name");
4182 if (display_name_node) {
4183 char * display_name = sipe_xml_data(display_name_node);
4185 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4186 g_free(display_name);
4189 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
4190 if ((status = sipe_xml_child(tuple, "status"))) {
4191 if ((basicstatus = sipe_xml_child(status, "activities"))) {
4192 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
4193 activity = sipe_xml_data(basicstatus);
4194 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
4200 if (isonline) {
4201 const gchar * status_id = NULL;
4202 if (activity) {
4203 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
4204 status_id = SIPE_STATUS_ID_BUSY;
4205 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
4206 status_id = SIPE_STATUS_ID_AWAY;
4210 if (!status_id) {
4211 status_id = SIPE_STATUS_ID_AVAILABLE;
4214 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
4215 sipe_got_user_status(sipe_private, uri, status_id);
4216 } else {
4217 sipe_got_user_status(sipe_private, uri, SIPE_STATUS_ID_OFFLINE);
4220 g_free(activity);
4221 g_free(uri);
4222 sipe_xml_free(pidf);
4225 /** 2005 */
4226 static void
4227 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
4228 const sipe_xml *xn_userinfo)
4230 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4231 const sipe_xml *xn_states;
4233 g_free(sip->user_states);
4234 sip->user_states = NULL;
4235 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
4236 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
4238 /* this is a hack-around to remove added newline after inner element,
4239 * state in this case, where it shouldn't be.
4240 * After several use of sipe_xml_stringify, amount of added newlines
4241 * grows significantly.
4243 if (orig) {
4244 gchar c, *stripped = orig;
4245 while ((c = *orig++)) {
4246 if ((c != '\n') /* && (c != '\r') */) {
4247 *stripped++ = c;
4250 *stripped = '\0';
4254 /* Publish initial state if not yet.
4255 * Assuming this happens on initial responce to self subscription
4256 * so we've already updated our UserInfo.
4258 if (!sip->initial_state_published) {
4259 send_presence_soap(sipe_private, FALSE);
4260 /* dalayed run */
4261 sipe_schedule_seconds(sipe_private,
4262 "<+update-calendar>",
4263 NULL,
4264 UPDATE_CALENDAR_DELAY,
4265 (sipe_schedule_action) sipe_core_update_calendar,
4266 NULL);
4270 static void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
4271 const gchar *data, unsigned len)
4273 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4274 char *activity = NULL;
4275 const char *epid;
4276 const char *status_id = NULL;
4277 const char *name;
4278 char *uri;
4279 char *self_uri = sip_uri_self(sipe_private);
4280 int avl;
4281 int act;
4282 const char *device_name = NULL;
4283 const char *cal_start_time = NULL;
4284 const char *cal_granularity = NULL;
4285 char *cal_free_busy_base64 = NULL;
4286 struct sipe_buddy *sbuddy;
4287 const sipe_xml *node;
4288 sipe_xml *xn_presentity;
4289 const sipe_xml *xn_availability;
4290 const sipe_xml *xn_activity;
4291 const sipe_xml *xn_display_name;
4292 const sipe_xml *xn_email;
4293 const sipe_xml *xn_phone_number;
4294 const sipe_xml *xn_userinfo;
4295 const sipe_xml *xn_note;
4296 const sipe_xml *xn_oof;
4297 const sipe_xml *xn_state;
4298 const sipe_xml *xn_contact;
4299 char *note;
4300 char *free_activity;
4301 int user_avail;
4302 const char *user_avail_nil;
4303 int res_avail;
4304 time_t user_avail_since = 0;
4305 time_t activity_since = 0;
4307 /* fix for Reuters environment on Linux */
4308 if (data && strstr(data, "encoding=\"utf-16\"")) {
4309 char *tmp_data;
4310 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
4311 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
4312 g_free(tmp_data);
4313 } else {
4314 xn_presentity = sipe_xml_parse(data, len);
4317 xn_availability = sipe_xml_child(xn_presentity, "availability");
4318 xn_activity = sipe_xml_child(xn_presentity, "activity");
4319 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
4320 xn_email = sipe_xml_child(xn_presentity, "email");
4321 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
4322 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
4323 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
4324 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
4325 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
4326 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
4327 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
4328 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
4329 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
4330 note = xn_note ? sipe_xml_data(xn_note) : NULL;
4332 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
4333 user_avail = 0;
4334 user_avail_since = 0;
4337 free_activity = NULL;
4339 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
4340 uri = sip_uri_from_name(name);
4341 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
4342 epid = sipe_xml_attribute(xn_availability, "epid");
4343 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
4345 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
4346 res_avail = sipe_get_availability_by_status(status_id, NULL);
4347 if (user_avail > res_avail) {
4348 res_avail = user_avail;
4349 status_id = sipe_get_status_by_availability(user_avail, NULL);
4352 if (xn_display_name) {
4353 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
4354 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
4355 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
4356 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
4357 char *tel_uri = sip_to_tel_uri(phone_number);
4359 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, display_name);
4360 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
4361 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
4362 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, !is_empty(phone_label) ? phone_label : phone_number);
4364 g_free(tel_uri);
4365 g_free(phone_label);
4366 g_free(phone_number);
4367 g_free(email);
4368 g_free(display_name);
4371 if (xn_contact) {
4372 /* tel */
4373 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
4375 /* Ex.: <tel type="work">tel:+3222220000</tel> */
4376 const char *phone_type = sipe_xml_attribute(node, "type");
4377 char* phone = sipe_xml_data(node);
4379 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
4381 g_free(phone);
4385 /* devicePresence */
4386 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
4387 const sipe_xml *xn_device_name;
4388 const sipe_xml *xn_calendar_info;
4389 const sipe_xml *xn_state;
4390 char *state;
4392 /* deviceName */
4393 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
4394 xn_device_name = sipe_xml_child(node, "deviceName");
4395 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
4398 /* calendarInfo */
4399 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
4400 if (xn_calendar_info) {
4401 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
4403 if (cal_start_time) {
4404 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
4405 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
4407 if (cal_start_time_t_tmp > cal_start_time_t) {
4408 cal_start_time = cal_start_time_tmp;
4409 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4410 g_free(cal_free_busy_base64);
4411 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4413 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);
4415 } else {
4416 cal_start_time = cal_start_time_tmp;
4417 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
4418 g_free(cal_free_busy_base64);
4419 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
4421 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);
4425 /* state */
4426 xn_state = sipe_xml_child(node, "states/state");
4427 if (xn_state) {
4428 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
4429 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
4431 state = sipe_xml_data(xn_state);
4432 if (dev_avail_since > user_avail_since &&
4433 dev_avail >= res_avail)
4435 res_avail = dev_avail;
4436 if (!is_empty(state))
4438 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
4439 g_free(activity);
4440 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
4441 } else if (sipe_strequal(state, "presenting")) {
4442 g_free(activity);
4443 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
4444 } else {
4445 activity = state;
4446 state = NULL;
4448 activity_since = dev_avail_since;
4450 status_id = sipe_get_status_by_availability(res_avail, &activity);
4452 g_free(state);
4456 /* oof */
4457 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
4458 g_free(activity);
4459 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
4460 activity_since = 0;
4463 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
4464 if (sbuddy)
4466 g_free(sbuddy->activity);
4467 sbuddy->activity = activity;
4468 activity = NULL;
4470 sbuddy->activity_since = activity_since;
4472 sbuddy->user_avail = user_avail;
4473 sbuddy->user_avail_since = user_avail_since;
4475 g_free(sbuddy->note);
4476 sbuddy->note = NULL;
4477 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
4479 sbuddy->is_oof_note = (xn_oof != NULL);
4481 g_free(sbuddy->device_name);
4482 sbuddy->device_name = NULL;
4483 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
4485 if (!is_empty(cal_free_busy_base64)) {
4486 g_free(sbuddy->cal_start_time);
4487 sbuddy->cal_start_time = g_strdup(cal_start_time);
4489 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
4491 g_free(sbuddy->cal_free_busy_base64);
4492 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
4493 cal_free_busy_base64 = NULL;
4495 g_free(sbuddy->cal_free_busy);
4496 sbuddy->cal_free_busy = NULL;
4499 sbuddy->last_non_cal_status_id = status_id;
4500 g_free(sbuddy->last_non_cal_activity);
4501 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
4503 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
4504 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
4506 sip->is_oof_note = sbuddy->is_oof_note;
4508 g_free(sip->note);
4509 sip->note = g_strdup(sbuddy->note);
4511 sip->note_since = time(NULL);
4514 g_free(sip->status);
4515 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
4518 g_free(cal_free_busy_base64);
4519 g_free(activity);
4521 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
4522 sipe_got_user_status(sipe_private, uri, status_id);
4524 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
4525 sipe_user_info_has_updated(sipe_private, xn_userinfo);
4528 g_free(note);
4529 sipe_xml_free(xn_presentity);
4530 g_free(uri);
4531 g_free(self_uri);
4534 static void sipe_presence_mime_cb(gpointer user_data, /* sipe_core_private */
4535 const GSList *fields,
4536 const gchar *body,
4537 gsize length)
4539 const gchar *type = sipe_utils_nameval_find(fields, "Content-Type");
4541 if (strstr(type,"application/rlmi+xml")) {
4542 process_incoming_notify_rlmi_resub(user_data, body, length);
4543 } else if (strstr(type, "text/xml+msrtc.pidf")) {
4544 process_incoming_notify_msrtc(user_data, body, length);
4545 } else {
4546 process_incoming_notify_rlmi(user_data, body, length);
4550 static void sipe_process_presence(struct sipe_core_private *sipe_private,
4551 struct sipmsg *msg)
4553 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4555 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype ? ctype : "");
4557 if (ctype &&
4558 (strstr(ctype, "application/rlmi+xml") ||
4559 strstr(ctype, "application/msrtc-event-categories+xml")))
4561 if (strstr(ctype, "multipart"))
4563 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_mime_cb, sipe_private);
4565 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4567 process_incoming_notify_rlmi(sipe_private, msg->body, msg->bodylen);
4569 else if(strstr(ctype, "application/rlmi+xml"))
4571 process_incoming_notify_rlmi_resub(sipe_private, msg->body, msg->bodylen);
4574 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4576 process_incoming_notify_msrtc(sipe_private, msg->body, msg->bodylen);
4578 else
4580 process_incoming_notify_pidf(sipe_private, msg->body, msg->bodylen);
4584 static void sipe_presence_timeout_mime_cb(gpointer user_data,
4585 SIPE_UNUSED_PARAMETER const GSList *fields,
4586 const gchar *body,
4587 gsize length)
4589 GSList **buddies = user_data;
4590 sipe_xml *xml = sipe_xml_parse(body, length);
4592 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
4593 const gchar *uri = sipe_xml_attribute(xml, "uri");
4594 const sipe_xml *xn_category;
4597 * automaton: presence is never expected to change
4599 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
4601 for (xn_category = sipe_xml_child(xml, "category");
4602 xn_category;
4603 xn_category = sipe_xml_twin(xn_category)) {
4604 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
4605 "contactCard")) {
4606 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
4607 if (node) {
4608 char *boolean = sipe_xml_data(node);
4609 if (sipe_strequal(boolean, "true")) {
4610 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
4611 uri);
4612 uri = NULL;
4614 g_free(boolean);
4616 break;
4620 if (uri) {
4621 *buddies = g_slist_append(*buddies, sip_uri(uri));
4625 sipe_xml_free(xml);
4628 static void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
4629 struct sipmsg *msg, gchar *who,
4630 int timeout)
4632 const char *ctype = sipmsg_find_header(msg, "Content-Type");
4633 gchar *action_name = sipe_utils_presence_key(who);
4635 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
4637 if (ctype &&
4638 strstr(ctype, "multipart") &&
4639 (strstr(ctype, "application/rlmi+xml") ||
4640 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4641 GSList *buddies = NULL;
4643 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
4645 if (buddies) {
4646 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4647 payload->host = g_strdup(who);
4648 payload->buddies = buddies;
4649 sipe_schedule_seconds(sipe_private,
4650 action_name,
4651 payload,
4652 timeout,
4653 sipe_subscribe_presence_batched_routed,
4654 sipe_subscribe_presence_batched_routed_free);
4655 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
4658 } else {
4659 sipe_schedule_seconds(sipe_private,
4660 action_name,
4661 g_strdup(who),
4662 timeout,
4663 sipe_subscribe_presence_single,
4664 g_free);
4665 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
4667 g_free(action_name);
4671 * Dispatcher for all incoming subscription information
4672 * whether it comes from NOTIFY, BENOTIFY requests or
4673 * piggy-backed to subscription's OK responce.
4675 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4676 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4678 void process_incoming_notify(struct sipe_core_private *sipe_private,
4679 struct sipmsg *msg,
4680 gboolean request, gboolean benotify)
4682 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4683 const gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4684 const gchar *event = sipmsg_find_header(msg, "Event");
4685 const gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4687 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state ? subscription_state : "");
4689 /* implicit subscriptions */
4690 if (content_type && g_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4691 sipe_process_imdn(sipe_private, msg);
4694 if (event) {
4695 /* for one off subscriptions (send with Expire: 0) */
4696 if (sipe_strcase_equal(event, "vnd-microsoft-provisioning-v2"))
4698 sipe_process_provisioning_v2(sipe_private, msg);
4700 else if (sipe_strcase_equal(event, "vnd-microsoft-provisioning"))
4702 sipe_process_provisioning(sipe_private, msg);
4704 else if (sipe_strcase_equal(event, "presence"))
4706 sipe_process_presence(sipe_private, msg);
4708 else if (sipe_strcase_equal(event, "registration-notify"))
4710 sipe_process_registration_notify(sipe_private, msg);
4713 if (!subscription_state || strstr(subscription_state, "active"))
4715 if (sipe_strcase_equal(event, "vnd-microsoft-roaming-contacts"))
4717 sipe_process_roaming_contacts(sipe_private, msg);
4719 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-self"))
4721 sipe_process_roaming_self(sipe_private, msg);
4723 else if (sipe_strcase_equal(event, "vnd-microsoft-roaming-ACL"))
4725 sipe_process_roaming_acl(sipe_private, msg);
4727 else if (sipe_strcase_equal(event, "presence.wpending"))
4729 sipe_process_presence_wpending(sipe_private, msg);
4731 else if (sipe_strcase_equal(event, "conference"))
4733 sipe_process_conference(sipe_private, msg);
4738 /* The server sends status 'terminated' */
4739 if (subscription_state && strstr(subscription_state, "terminated") ) {
4740 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4741 gchar *key = sipe_utils_subscription_key(event, who);
4743 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who);
4744 g_free(who);
4746 sipe_subscriptions_remove(sipe_private, key);
4747 g_free(key);
4750 if (!request && event) {
4751 const gchar *expires_header = sipmsg_find_header(msg, "Expires");
4752 int timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4753 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout);
4755 if (timeout) {
4756 /* 2 min ahead of expiration */
4757 timeout = (timeout - 120) > 120 ? (timeout - 120) : timeout;
4759 if (sipe_strcase_equal(event, "presence.wpending") &&
4760 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4762 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4763 sipe_schedule_seconds(sipe_private,
4764 action_name,
4765 NULL,
4766 timeout,
4767 sipe_subscribe_presence_wpending,
4768 NULL);
4769 g_free(action_name);
4771 else if (sipe_strcase_equal(event, "presence") &&
4772 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4774 gchar *who = parse_from(sipmsg_find_header(msg, "To"));
4775 gchar *action_name = sipe_utils_presence_key(who);
4777 if (sip->batched_support) {
4778 sipe_process_presence_timeout(sipe_private, msg, who, timeout);
4780 else {
4781 sipe_schedule_seconds(sipe_private,
4782 action_name,
4783 g_strdup(who),
4784 timeout,
4785 sipe_subscribe_presence_single,
4786 g_free);
4787 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who, timeout);
4789 g_free(action_name);
4790 g_free(who);
4795 /* The client responses on received a NOTIFY message */
4796 if (request && !benotify)
4798 sip_transport_response(sipe_private, msg, 200, "OK", NULL);
4803 * Whether user manually changed status or
4804 * it was changed automatically due to user
4805 * became inactive/active again
4807 static gboolean
4808 sipe_is_user_state(struct sipe_core_private *sipe_private)
4810 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4811 gboolean res;
4812 time_t now = time(NULL);
4814 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
4815 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
4817 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
4819 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
4820 return res;
4823 static void
4824 send_presence_soap0(struct sipe_core_private *sipe_private,
4825 gboolean do_publish_calendar,
4826 gboolean do_reset_status)
4828 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4829 struct sipe_calendar* cal = sip->cal;
4830 int availability = 0;
4831 int activity = 0;
4832 gchar *body;
4833 gchar *tmp;
4834 gchar *tmp2 = NULL;
4835 gchar *res_note = NULL;
4836 gchar *res_oof = NULL;
4837 const gchar *note_pub = NULL;
4838 gchar *states = NULL;
4839 gchar *calendar_data = NULL;
4840 gchar *epid = get_epid(sipe_private);
4841 time_t now = time(NULL);
4842 gchar *since_time_str = sipe_utils_time_to_str(now);
4843 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
4844 const char *user_input;
4845 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
4847 if (oof_note && sip->note) {
4848 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
4849 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
4852 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
4854 if (!sip->initial_state_published ||
4855 do_reset_status)
4857 g_free(sip->status);
4858 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
4861 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
4863 /* Note */
4864 if (pub_oof) {
4865 note_pub = oof_note;
4866 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
4867 cal->published = TRUE;
4868 } else if (sip->note) {
4869 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
4870 g_free(sip->note);
4871 sip->note = NULL;
4872 sip->is_oof_note = FALSE;
4873 sip->note_since = 0;
4874 } else {
4875 note_pub = sip->note;
4876 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
4880 if (note_pub)
4882 /* to protocol internal plain text format */
4883 tmp = sipe_backend_markup_strip_html(note_pub);
4884 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
4885 g_free(tmp);
4888 /* User State */
4889 if (!do_reset_status) {
4890 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
4892 gchar *activity_token = NULL;
4893 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
4895 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
4896 avail_2007,
4897 since_time_str,
4898 epid,
4899 activity_token);
4900 g_free(activity_token);
4902 else /* preserve existing publication */
4904 if (sip->user_states) {
4905 states = g_strdup(sip->user_states);
4908 } else {
4909 /* do nothing - then User state will be erased */
4911 sip->initial_state_published = TRUE;
4913 /* CalendarInfo */
4914 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
4916 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4917 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4918 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
4919 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
4920 fb_start_str,
4921 free_busy_base64);
4922 g_free(fb_start_str);
4923 g_free(free_busy_base64);
4926 user_input = !sipe_is_user_state(sipe_private) && sip->status != SIPE_STATUS_ID_AVAILABLE ? "idle" : "active";
4928 /* forming resulting XML */
4929 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
4930 sipe_private->username,
4931 availability,
4932 activity,
4933 (tmp = g_ascii_strup(g_get_host_name(), -1)),
4934 res_note ? res_note : "",
4935 res_oof ? res_oof : "",
4936 states ? states : "",
4937 calendar_data ? calendar_data : "",
4938 epid,
4939 since_time_str,
4940 since_time_str,
4941 user_input);
4942 g_free(tmp);
4943 g_free(tmp2);
4944 g_free(res_note);
4945 g_free(states);
4946 g_free(calendar_data);
4948 send_soap_request(sipe_private, body);
4950 g_free(body);
4951 g_free(since_time_str);
4952 g_free(epid);
4955 void
4956 send_presence_soap(struct sipe_core_private *sipe_private,
4957 gboolean do_publish_calendar)
4959 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
4963 static gboolean
4964 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
4965 struct sipmsg *msg,
4966 struct transaction *trans)
4968 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
4970 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
4971 sipe_xml *xml;
4972 const sipe_xml *node;
4973 gchar *fault_code;
4974 GHashTable *faults;
4975 int index_our;
4976 gboolean has_device_publication = FALSE;
4978 xml = sipe_xml_parse(msg->body, msg->bodylen);
4980 /* test if version mismatch fault */
4981 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
4982 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
4983 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
4984 g_free(fault_code);
4985 sipe_xml_free(xml);
4986 return TRUE;
4988 g_free(fault_code);
4990 /* accumulating information about faulty versions */
4991 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
4992 for (node = sipe_xml_child(xml, "details/operation");
4993 node;
4994 node = sipe_xml_twin(node))
4996 const gchar *index = sipe_xml_attribute(node, "index");
4997 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
4999 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
5000 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
5002 sipe_xml_free(xml);
5004 /* here we are parsing own request to figure out what publication
5005 * referensed here only by index went wrong
5007 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
5009 /* publication */
5010 for (node = sipe_xml_child(xml, "publications/publication"),
5011 index_our = 1; /* starts with 1 - our first publication */
5012 node;
5013 node = sipe_xml_twin(node), index_our++)
5015 gchar *idx = g_strdup_printf("%d", index_our);
5016 const gchar *curVersion = g_hash_table_lookup(faults, idx);
5017 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
5018 g_free(idx);
5020 if (sipe_strequal("device", categoryName)) {
5021 has_device_publication = TRUE;
5024 if (curVersion) { /* fault exist on this index */
5025 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5026 const gchar *container = sipe_xml_attribute(node, "container");
5027 const gchar *instance = sipe_xml_attribute(node, "instance");
5028 /* key is <category><instance><container> */
5029 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
5030 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
5032 if (category) {
5033 struct sipe_publication *publication =
5034 g_hash_table_lookup(category, key);
5036 SIPE_DEBUG_INFO("key is %s", key);
5038 if (publication) {
5039 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
5040 key, curVersion, publication->version);
5041 /* updating publication's version to the correct one */
5042 publication->version = atoi(curVersion);
5044 } else {
5045 /* We somehow lost this category from our publications... */
5046 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
5047 publication->category = g_strdup(categoryName);
5048 publication->instance = atoi(instance);
5049 publication->container = atoi(container);
5050 publication->version = atoi(curVersion);
5051 category = g_hash_table_new_full(g_str_hash, g_str_equal,
5052 g_free, (GDestroyNotify)free_publication);
5053 g_hash_table_insert(category, g_strdup(key), publication);
5054 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
5055 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
5057 g_free(key);
5060 sipe_xml_free(xml);
5061 g_hash_table_destroy(faults);
5063 /* rebublishing with right versions */
5064 if (has_device_publication) {
5065 send_publish_category_initial(sipe_private);
5066 } else {
5067 send_presence_status(sipe_private, NULL);
5070 return TRUE;
5074 * Returns 'device' XML part for publication.
5075 * Must be g_free'd after use.
5077 static gchar *
5078 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
5080 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5081 gchar *uri;
5082 gchar *doc;
5083 gchar *epid = get_epid(sipe_private);
5084 gchar *uuid = generateUUIDfromEPID(epid);
5085 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
5086 /* key is <category><instance><container> */
5087 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
5088 struct sipe_publication *publication =
5089 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
5091 g_free(key);
5092 g_free(epid);
5094 uri = sip_uri_self(sipe_private);
5095 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
5096 device_instance,
5097 publication ? publication->version : 0,
5098 uuid,
5099 uri,
5100 "00:00:00+01:00", /* @TODO make timezone real*/
5101 g_get_host_name()
5104 g_free(uri);
5105 g_free(uuid);
5107 return doc;
5111 * A service method - use
5112 * - send_publish_get_category_state_machine and
5113 * - send_publish_get_category_state_user instead.
5114 * Must be g_free'd after use.
5116 static gchar *
5117 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
5118 gboolean is_user_state)
5120 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5121 int availability = sipe_get_availability_by_status(sip->status, NULL);
5122 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
5123 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
5124 /* key is <category><instance><container> */
5125 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5126 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5127 struct sipe_publication *publication_2 =
5128 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5129 struct sipe_publication *publication_3 =
5130 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5132 g_free(key_2);
5133 g_free(key_3);
5135 if (publication_2 && (publication_2->availability == availability))
5137 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
5138 return NULL; /* nothing to update */
5141 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
5142 instance,
5143 publication_2 ? publication_2->version : 0,
5144 availability,
5145 instance,
5146 publication_3 ? publication_3->version : 0,
5147 availability);
5151 * Only Busy and OOF calendar event are published.
5152 * Different instances are used for that.
5154 * Must be g_free'd after use.
5156 static gchar *
5157 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
5158 struct sipe_cal_event *event,
5159 const char *uri,
5160 int cal_satus)
5162 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5163 gchar *start_time_str;
5164 int availability = 0;
5165 gchar *res;
5166 gchar *tmp = NULL;
5167 guint instance = (cal_satus == SIPE_CAL_OOF) ?
5168 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
5169 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
5171 /* key is <category><instance><container> */
5172 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
5173 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
5174 struct sipe_publication *publication_2 =
5175 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
5176 struct sipe_publication *publication_3 =
5177 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
5179 g_free(key_2);
5180 g_free(key_3);
5182 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
5183 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5184 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
5185 return NULL;
5188 if (event &&
5189 publication_3 &&
5190 (publication_3->availability == availability) &&
5191 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
5193 g_free(tmp);
5194 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
5195 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
5196 return NULL; /* nothing to update */
5198 g_free(tmp);
5200 if (event &&
5201 (event->cal_status == SIPE_CAL_BUSY ||
5202 event->cal_status == SIPE_CAL_OOF))
5204 gchar *availability_xml_str = NULL;
5205 gchar *activity_xml_str = NULL;
5207 if (event->cal_status == SIPE_CAL_BUSY) {
5208 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
5211 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
5212 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5213 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
5214 "minAvailability=\"6500\"",
5215 "maxAvailability=\"8999\"");
5216 } else if (event->cal_status == SIPE_CAL_OOF) {
5217 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
5218 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
5219 "minAvailability=\"12000\"",
5220 "");
5222 start_time_str = sipe_utils_time_to_str(event->start_time);
5224 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
5225 instance,
5226 publication_2 ? publication_2->version : 0,
5227 uri,
5228 start_time_str,
5229 availability_xml_str ? availability_xml_str : "",
5230 activity_xml_str ? activity_xml_str : "",
5231 event->subject ? event->subject : "",
5232 event->location ? event->location : "",
5234 instance,
5235 publication_3 ? publication_3->version : 0,
5236 uri,
5237 start_time_str,
5238 availability_xml_str ? availability_xml_str : "",
5239 activity_xml_str ? activity_xml_str : "",
5240 event->subject ? event->subject : "",
5241 event->location ? event->location : ""
5243 g_free(start_time_str);
5244 g_free(availability_xml_str);
5245 g_free(activity_xml_str);
5248 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
5250 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
5251 instance,
5252 publication_2 ? publication_2->version : 0,
5254 instance,
5255 publication_3 ? publication_3->version : 0
5259 return res;
5263 * Returns 'machineState' XML part for publication.
5264 * Must be g_free'd after use.
5266 static gchar *
5267 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
5269 return sipe_publish_get_category_state(sipe_private, FALSE);
5273 * Returns 'userState' XML part for publication.
5274 * Must be g_free'd after use.
5276 static gchar *
5277 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
5279 return sipe_publish_get_category_state(sipe_private, TRUE);
5283 * Returns 'note' XML part for publication.
5284 * Must be g_free'd after use.
5286 * Protocol format for Note is plain text.
5288 * @param note a note in Sipe internal HTML format
5289 * @param note_type either personal or OOF
5291 static gchar *
5292 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
5293 const char *note, /* html */
5294 const char *note_type,
5295 time_t note_start,
5296 time_t note_end)
5298 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5299 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
5300 /* key is <category><instance><container> */
5301 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
5302 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
5303 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
5305 struct sipe_publication *publication_note_200 =
5306 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
5307 struct sipe_publication *publication_note_300 =
5308 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
5309 struct sipe_publication *publication_note_400 =
5310 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
5312 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
5313 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
5314 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
5315 char *res, *tmp1, *tmp2, *tmp3;
5316 char *start_time_attr;
5317 char *end_time_attr;
5319 g_free(tmp);
5320 tmp = NULL;
5321 g_free(key_note_200);
5322 g_free(key_note_300);
5323 g_free(key_note_400);
5325 /* we even need to republish empty note */
5326 if (sipe_strequal(n1, n2))
5328 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
5329 g_free(n1);
5330 return NULL; /* nothing to update */
5333 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
5334 g_free(tmp);
5335 tmp = NULL;
5336 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
5337 g_free(tmp);
5339 if (n1) {
5340 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5341 instance,
5342 200,
5343 publication_note_200 ? publication_note_200->version : 0,
5344 note_type,
5345 start_time_attr ? start_time_attr : "",
5346 end_time_attr ? end_time_attr : "",
5347 n1);
5349 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5350 instance,
5351 300,
5352 publication_note_300 ? publication_note_300->version : 0,
5353 note_type,
5354 start_time_attr ? start_time_attr : "",
5355 end_time_attr ? end_time_attr : "",
5356 n1);
5358 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
5359 instance,
5360 400,
5361 publication_note_400 ? publication_note_400->version : 0,
5362 note_type,
5363 start_time_attr ? start_time_attr : "",
5364 end_time_attr ? end_time_attr : "",
5365 n1);
5366 } else {
5367 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5368 "note",
5369 instance,
5370 200,
5371 publication_note_200 ? publication_note_200->version : 0,
5372 "static");
5373 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5374 "note",
5375 instance,
5376 300,
5377 publication_note_200 ? publication_note_200->version : 0,
5378 "static");
5379 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
5380 "note",
5381 instance,
5382 400,
5383 publication_note_200 ? publication_note_200->version : 0,
5384 "static");
5386 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
5388 g_free(start_time_attr);
5389 g_free(end_time_attr);
5390 g_free(tmp1);
5391 g_free(tmp2);
5392 g_free(tmp3);
5393 g_free(n1);
5395 return res;
5399 * Returns 'calendarData' XML part with WorkingHours for publication.
5400 * Must be g_free'd after use.
5402 static gchar *
5403 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
5405 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5406 struct sipe_calendar* cal = sip->cal;
5408 /* key is <category><instance><container> */
5409 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
5410 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
5411 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
5412 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
5413 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
5414 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
5416 struct sipe_publication *publication_cal_1 =
5417 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5418 struct sipe_publication *publication_cal_100 =
5419 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5420 struct sipe_publication *publication_cal_200 =
5421 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5422 struct sipe_publication *publication_cal_300 =
5423 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5424 struct sipe_publication *publication_cal_400 =
5425 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5426 struct sipe_publication *publication_cal_32000 =
5427 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5429 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
5430 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
5432 g_free(key_cal_1);
5433 g_free(key_cal_100);
5434 g_free(key_cal_200);
5435 g_free(key_cal_300);
5436 g_free(key_cal_400);
5437 g_free(key_cal_32000);
5439 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
5440 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
5441 return NULL;
5444 if (sipe_strequal(n1, n2))
5446 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
5447 return NULL; /* nothing to update */
5450 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
5451 /* 1 */
5452 publication_cal_1 ? publication_cal_1->version : 0,
5453 cal->email,
5454 cal->working_hours_xml_str,
5455 /* 100 - Public */
5456 publication_cal_100 ? publication_cal_100->version : 0,
5457 /* 200 - Company */
5458 publication_cal_200 ? publication_cal_200->version : 0,
5459 cal->email,
5460 cal->working_hours_xml_str,
5461 /* 300 - Team */
5462 publication_cal_300 ? publication_cal_300->version : 0,
5463 cal->email,
5464 cal->working_hours_xml_str,
5465 /* 400 - Personal */
5466 publication_cal_400 ? publication_cal_400->version : 0,
5467 cal->email,
5468 cal->working_hours_xml_str,
5469 /* 32000 - Blocked */
5470 publication_cal_32000 ? publication_cal_32000->version : 0
5475 * Returns 'calendarData' XML part with FreeBusy for publication.
5476 * Must be g_free'd after use.
5478 static gchar *
5479 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
5481 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5482 struct sipe_calendar* cal = sip->cal;
5483 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
5484 char *fb_start_str;
5485 char *free_busy_base64;
5486 const char *st;
5487 const char *fb;
5488 char *res;
5490 /* key is <category><instance><container> */
5491 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
5492 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
5493 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
5494 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
5495 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
5496 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
5498 struct sipe_publication *publication_cal_1 =
5499 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
5500 struct sipe_publication *publication_cal_100 =
5501 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
5502 struct sipe_publication *publication_cal_200 =
5503 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
5504 struct sipe_publication *publication_cal_300 =
5505 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
5506 struct sipe_publication *publication_cal_400 =
5507 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
5508 struct sipe_publication *publication_cal_32000 =
5509 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
5511 g_free(key_cal_1);
5512 g_free(key_cal_100);
5513 g_free(key_cal_200);
5514 g_free(key_cal_300);
5515 g_free(key_cal_400);
5516 g_free(key_cal_32000);
5518 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
5519 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
5520 return NULL;
5523 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
5524 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
5526 st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
5527 fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
5529 /* we will rebuplish the same data to refresh publication time,
5530 * so if data from multiple sources, most recent will be choosen
5532 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
5534 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
5535 // g_free(fb_start_str);
5536 // g_free(free_busy_base64);
5537 // return NULL; /* nothing to update */
5540 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
5541 /* 1 */
5542 cal_data_instance,
5543 publication_cal_1 ? publication_cal_1->version : 0,
5544 /* 100 - Public */
5545 cal_data_instance,
5546 publication_cal_100 ? publication_cal_100->version : 0,
5547 /* 200 - Company */
5548 cal_data_instance,
5549 publication_cal_200 ? publication_cal_200->version : 0,
5550 cal->email,
5551 fb_start_str,
5552 free_busy_base64,
5553 /* 300 - Team */
5554 cal_data_instance,
5555 publication_cal_300 ? publication_cal_300->version : 0,
5556 cal->email,
5557 fb_start_str,
5558 free_busy_base64,
5559 /* 400 - Personal */
5560 cal_data_instance,
5561 publication_cal_400 ? publication_cal_400->version : 0,
5562 cal->email,
5563 fb_start_str,
5564 free_busy_base64,
5565 /* 32000 - Blocked */
5566 cal_data_instance,
5567 publication_cal_32000 ? publication_cal_32000->version : 0
5570 g_free(fb_start_str);
5571 g_free(free_busy_base64);
5572 return res;
5575 static void send_presence_publish(struct sipe_core_private *sipe_private,
5576 const char *publications)
5578 gchar *uri;
5579 gchar *doc;
5580 gchar *tmp;
5581 gchar *hdr;
5583 uri = sip_uri_self(sipe_private);
5584 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
5585 uri,
5586 publications);
5588 tmp = get_contact(sipe_private);
5589 hdr = g_strdup_printf("Contact: %s\r\n"
5590 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
5592 sip_transport_service(sipe_private,
5593 uri,
5594 hdr,
5595 doc,
5596 process_send_presence_category_publish_response);
5598 g_free(tmp);
5599 g_free(hdr);
5600 g_free(uri);
5601 g_free(doc);
5604 static void
5605 send_publish_category_initial(struct sipe_core_private *sipe_private)
5607 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5608 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
5609 gchar *pub_machine;
5610 gchar *publications;
5612 g_free(sip->status);
5613 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
5615 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
5616 publications = g_strdup_printf("%s%s",
5617 pub_device,
5618 pub_machine ? pub_machine : "");
5619 g_free(pub_device);
5620 g_free(pub_machine);
5622 send_presence_publish(sipe_private, publications);
5623 g_free(publications);
5626 static void
5627 send_presence_category_publish(struct sipe_core_private *sipe_private)
5629 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5630 gchar *pub_state = sipe_is_user_state(sipe_private) ?
5631 sipe_publish_get_category_state_user(sipe_private) :
5632 sipe_publish_get_category_state_machine(sipe_private);
5633 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
5634 sip->note,
5635 sip->is_oof_note ? "OOF" : "personal",
5638 gchar *publications;
5640 if (!pub_state && !pub_note) {
5641 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
5642 return;
5645 publications = g_strdup_printf("%s%s",
5646 pub_state ? pub_state : "",
5647 pub_note ? pub_note : "");
5649 g_free(pub_state);
5650 g_free(pub_note);
5652 send_presence_publish(sipe_private, publications);
5653 g_free(publications);
5657 * Publishes self status
5658 * based on own calendar information.
5660 * For 2007+
5662 void
5663 publish_calendar_status_self(struct sipe_core_private *sipe_private,
5664 SIPE_UNUSED_PARAMETER void *unused)
5666 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5667 struct sipe_cal_event* event = NULL;
5668 gchar *pub_cal_working_hours = NULL;
5669 gchar *pub_cal_free_busy = NULL;
5670 gchar *pub_calendar = NULL;
5671 gchar *pub_calendar2 = NULL;
5672 gchar *pub_oof_note = NULL;
5673 const gchar *oof_note;
5674 time_t oof_start = 0;
5675 time_t oof_end = 0;
5677 if (!sip->cal) {
5678 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
5679 return;
5682 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
5683 if (sip->cal->cal_events) {
5684 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
5687 if (!event) {
5688 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
5689 } else {
5690 char *desc = sipe_cal_event_describe(event);
5691 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
5692 g_free(desc);
5695 /* Logic
5696 if OOF
5697 OOF publish, Busy clean
5698 ilse if Busy
5699 OOF clean, Busy publish
5700 else
5701 OOF clean, Busy clean
5703 if (event && event->cal_status == SIPE_CAL_OOF) {
5704 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
5705 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5706 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
5707 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5708 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
5709 } else {
5710 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
5711 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
5714 oof_note = sipe_ews_get_oof_note(sip->cal);
5715 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
5716 oof_start = sip->cal->oof_start;
5717 oof_end = sip->cal->oof_end;
5719 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
5721 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
5722 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
5724 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
5725 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
5726 } else {
5727 gchar *publications = g_strdup_printf("%s%s%s%s%s",
5728 pub_cal_working_hours ? pub_cal_working_hours : "",
5729 pub_cal_free_busy ? pub_cal_free_busy : "",
5730 pub_calendar ? pub_calendar : "",
5731 pub_calendar2 ? pub_calendar2 : "",
5732 pub_oof_note ? pub_oof_note : "");
5734 send_presence_publish(sipe_private, publications);
5735 g_free(publications);
5738 g_free(pub_cal_working_hours);
5739 g_free(pub_cal_free_busy);
5740 g_free(pub_calendar);
5741 g_free(pub_calendar2);
5742 g_free(pub_oof_note);
5744 /* repeat scheduling */
5745 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
5748 static void send_presence_status(struct sipe_core_private *sipe_private,
5749 SIPE_UNUSED_PARAMETER void *unused)
5751 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5752 PurpleStatus * status = purple_account_get_active_status(sip->account);
5754 if (!status) return;
5756 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
5757 purple_status_get_id(status) ? purple_status_get_id(status) : "",
5758 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
5760 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5761 send_presence_category_publish(sipe_private);
5762 } else {
5763 send_presence_soap(sipe_private, FALSE);
5767 static guint sipe_ht_hash_nick(const char *nick)
5769 char *lc = g_utf8_strdown(nick, -1);
5770 guint bucket = g_str_hash(lc);
5771 g_free(lc);
5773 return bucket;
5776 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5778 char *nick1_norm = NULL;
5779 char *nick2_norm = NULL;
5780 gboolean equal;
5782 if (nick1 == NULL && nick2 == NULL) return TRUE;
5783 if (nick1 == NULL || nick2 == NULL ||
5784 !g_utf8_validate(nick1, -1, NULL) ||
5785 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
5787 nick1_norm = g_utf8_casefold(nick1, -1);
5788 nick2_norm = g_utf8_casefold(nick2, -1);
5789 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
5790 g_free(nick2_norm);
5791 g_free(nick1_norm);
5793 return equal;
5796 /* temporary function */
5797 void sipe_purple_setup(struct sipe_core_public *sipe_public,
5798 PurpleConnection *gc)
5800 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
5801 sip->gc = gc;
5802 sip->account = purple_connection_get_account(gc);
5805 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
5806 const gchar *login_domain,
5807 const gchar *login_account,
5808 const gchar *password,
5809 const gchar *email,
5810 const gchar *email_url,
5811 const gchar **errmsg)
5813 struct sipe_core_private *sipe_private;
5814 struct sipe_account_data *sip;
5815 gchar **user_domain;
5817 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
5819 /* ensure that sign-in name doesn't contain invalid characters */
5820 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
5821 *errmsg = _("SIP Exchange user name contains invalid characters");
5822 return NULL;
5825 /* ensure that sign-in name format is name@domain */
5826 if (!strchr(signin_name, '@') ||
5827 g_str_has_prefix(signin_name, "@") ||
5828 g_str_has_suffix(signin_name, "@")) {
5829 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
5830 return NULL;
5833 /* ensure that email format is name@domain (if provided) */
5834 if (!is_empty(email) &&
5835 (!strchr(email, '@') ||
5836 g_str_has_prefix(email, "@") ||
5837 g_str_has_suffix(email, "@")))
5839 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
5840 return NULL;
5843 /* ensure that user name doesn't contain spaces */
5844 user_domain = g_strsplit(signin_name, "@", 2);
5845 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
5846 if (strchr(user_domain[0], ' ') != NULL) {
5847 g_strfreev(user_domain);
5848 *errmsg = _("SIP Exchange user name contains whitespace");
5849 return NULL;
5852 /* ensure that email_url is in proper format if enabled (if provided).
5853 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
5854 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
5856 if (!is_empty(email_url)) {
5857 char *tmp = g_ascii_strdown(email_url, -1);
5858 if (!g_str_has_prefix(tmp, "https://"))
5860 g_free(tmp);
5861 g_strfreev(user_domain);
5862 *errmsg = _("Email services URL should be valid if provided\n"
5863 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
5864 "Example: https://domino.corp.com/maildatabase.nsf");
5865 return NULL;
5867 g_free(tmp);
5870 sipe_private = g_new0(struct sipe_core_private, 1);
5871 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
5872 sip->subscribed_buddies = FALSE;
5873 sip->initial_state_published = FALSE;
5874 sipe_private->username = g_strdup(signin_name);
5875 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
5876 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
5877 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
5878 sip->password = g_strdup(password);
5879 sipe_private->public.sip_name = g_strdup(user_domain[0]);
5880 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
5881 g_strfreev(user_domain);
5883 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5884 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
5885 g_free, (GDestroyNotify)g_hash_table_destroy);
5886 sipe_subscriptions_init(sipe_private);
5887 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
5889 return((struct sipe_core_public *)sipe_private);
5892 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
5894 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5896 g_free(sipe_private->epid);
5897 sipe_private->epid = NULL;
5899 sip_transport_disconnect(sipe_private);
5901 sipe_schedule_cancel_all(sipe_private);
5903 if (sip->allow_events) {
5904 GSList *entry = sip->allow_events;
5905 while (entry) {
5906 g_free(entry->data);
5907 entry = entry->next;
5910 g_slist_free(sip->allow_events);
5912 if (sip->containers) {
5913 GSList *entry = sip->containers;
5914 while (entry) {
5915 free_container((struct sipe_container *)entry->data);
5916 entry = entry->next;
5919 g_slist_free(sip->containers);
5921 if (sipe_private->contact)
5922 g_free(sipe_private->contact);
5923 sipe_private->contact = NULL;
5924 if (sip->regcallid)
5925 g_free(sip->regcallid);
5926 sip->regcallid = NULL;
5928 if (sipe_private->focus_factory_uri)
5929 g_free(sipe_private->focus_factory_uri);
5930 sipe_private->focus_factory_uri = NULL;
5932 if (sip->cal) {
5933 sipe_cal_calendar_free(sip->cal);
5935 sip->cal = NULL;
5939 * A callback for g_hash_table_foreach_remove
5941 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5942 SIPE_UNUSED_PARAMETER gpointer user_data)
5944 sipe_free_buddy((struct sipe_buddy *) buddy);
5946 /* We must return TRUE as the key/value have already been deleted */
5947 return(TRUE);
5950 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
5952 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
5955 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5956 SIPE_UNUSED_PARAMETER void *user_data)
5958 PurpleAccount *acct = purple_connection_get_account(gc);
5959 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5960 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5961 if (conv == NULL)
5962 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5963 purple_conversation_present(conv);
5964 g_free(id);
5967 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5968 SIPE_UNUSED_PARAMETER void *user_data)
5971 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5972 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5975 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
5976 struct sipmsg *msg,
5977 SIPE_UNUSED_PARAMETER struct transaction *trans)
5979 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5980 PurpleNotifySearchResults *results;
5981 PurpleNotifySearchColumn *column;
5982 sipe_xml *searchResults;
5983 const sipe_xml *mrow;
5984 int match_count = 0;
5985 gboolean more = FALSE;
5986 gchar *secondary;
5988 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
5990 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
5991 if (!searchResults) {
5992 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
5993 return FALSE;
5996 results = purple_notify_searchresults_new();
5998 if (results == NULL) {
5999 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
6000 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
6002 sipe_xml_free(searchResults);
6003 return FALSE;
6006 column = purple_notify_searchresults_column_new(_("User name"));
6007 purple_notify_searchresults_column_add(results, column);
6009 column = purple_notify_searchresults_column_new(_("Name"));
6010 purple_notify_searchresults_column_add(results, column);
6012 column = purple_notify_searchresults_column_new(_("Company"));
6013 purple_notify_searchresults_column_add(results, column);
6015 column = purple_notify_searchresults_column_new(_("Country"));
6016 purple_notify_searchresults_column_add(results, column);
6018 column = purple_notify_searchresults_column_new(_("Email"));
6019 purple_notify_searchresults_column_add(results, column);
6021 for (mrow = sipe_xml_child(searchResults, "Body/Array/row"); mrow; mrow = sipe_xml_twin(mrow)) {
6022 GList *row = NULL;
6024 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
6025 row = g_list_append(row, g_strdup(uri_parts[1]));
6026 g_strfreev(uri_parts);
6028 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
6029 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
6030 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
6031 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
6033 purple_notify_searchresults_row_add(results, row);
6034 match_count++;
6037 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
6038 char *data = sipe_xml_data(mrow);
6039 more = (g_strcasecmp(data, "true") == 0);
6040 g_free(data);
6043 secondary = g_strdup_printf(
6044 dngettext(PACKAGE_NAME,
6045 "Found %d contact%s:",
6046 "Found %d contacts%s:", match_count),
6047 match_count, more ? _(" (more matched your query)") : "");
6049 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
6050 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
6051 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
6053 g_free(secondary);
6054 sipe_xml_free(searchResults);
6055 return TRUE;
6058 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6060 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
6061 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
6062 unsigned i = 0;
6064 if (!attrs) return;
6066 do {
6067 PurpleRequestField *field = entries->data;
6068 const char *id = purple_request_field_get_id(field);
6069 const char *value = purple_request_field_string_get_value(field);
6071 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
6073 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
6074 } while ((entries = g_list_next(entries)) != NULL);
6075 attrs[i] = NULL;
6077 if (i > 0) {
6078 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6079 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
6080 gchar *query = g_strjoinv(NULL, attrs);
6081 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
6082 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body ? body : "");
6083 send_soap_request_with_cb(sipe_private, domain_uri, body,
6084 process_search_contact_response, NULL);
6085 g_free(domain_uri);
6086 g_free(body);
6087 g_free(query);
6090 g_strfreev(attrs);
6093 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
6094 gpointer value,
6095 GString* str)
6097 struct sipe_publication *publication = value;
6099 g_string_append_printf( str,
6100 SIPE_PUB_XML_PUBLICATION_CLEAR,
6101 publication->category,
6102 publication->instance,
6103 publication->container,
6104 publication->version,
6105 "static");
6108 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
6110 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6111 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
6112 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
6114 GString* str = g_string_new(NULL);
6115 gchar *publications;
6117 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
6118 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
6119 return;
6122 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
6123 publications = g_string_free(str, FALSE);
6125 send_presence_publish(sipe_private, publications);
6126 g_free(publications);
6128 else /* 2005 */
6130 send_presence_soap0(sipe_private, FALSE, TRUE);
6134 /** for Access levels menu */
6135 #define INDENT_FMT " %s"
6137 /** Member is directly placed to access level container.
6138 * For example SIP URI of user is in the container.
6140 #define INDENT_MARKED_FMT "* %s"
6142 /** Member is indirectly belong to access level container.
6143 * For example 'sameEnterprise' is in the container and user
6144 * belongs to that same enterprise.
6146 #define INDENT_MARKED_INHERITED_FMT "= %s"
6148 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
6149 const gchar *name,
6150 const gchar *status_name,
6151 gboolean is_online)
6153 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
6154 gchar *note = NULL;
6155 gboolean is_oof_note = FALSE;
6156 gchar *activity = NULL;
6157 gchar *calendar = NULL;
6158 gchar *meeting_subject = NULL;
6159 gchar *meeting_location = NULL;
6160 gchar *access_text = NULL;
6161 GSList *info = NULL;
6163 #define SIPE_ADD_BUDDY_INFO(l, t) \
6165 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
6166 sbi->label = (l); \
6167 sbi->text = (t); \
6168 info = g_slist_append(info, sbi); \
6171 if (sipe_public) { //happens on pidgin exit
6172 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
6173 if (sbuddy) {
6174 note = sbuddy->note;
6175 is_oof_note = sbuddy->is_oof_note;
6176 activity = sbuddy->activity;
6177 calendar = sipe_cal_get_description(sbuddy);
6178 meeting_subject = sbuddy->meeting_subject;
6179 meeting_location = sbuddy->meeting_location;
6181 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6182 gboolean is_group_access = FALSE;
6183 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
6184 const char *access_level = sipe_get_access_level_name(container_id);
6185 access_text = is_group_access ?
6186 g_strdup(access_level) :
6187 g_strdup_printf(INDENT_MARKED_FMT, access_level);
6191 //Layout
6192 if (is_online)
6194 gchar *status_str = g_strdup(activity ? activity : status_name);
6196 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
6198 if (is_online && !is_empty(calendar))
6200 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
6201 calendar = NULL;
6203 g_free(calendar);
6204 if (!is_empty(meeting_location))
6206 SIPE_ADD_BUDDY_INFO(_("Meeting in"), g_strdup(meeting_location));
6208 if (!is_empty(meeting_subject))
6210 SIPE_ADD_BUDDY_INFO(_("Meeting about"), g_strdup(meeting_subject));
6212 if (note)
6214 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
6215 SIPE_ADD_BUDDY_INFO(is_oof_note ? _("Out of office note") : _("Note"),
6216 g_strdup_printf("<i>%s</i>", note));
6218 if (access_text) {
6219 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
6222 return(info);
6225 static PurpleBuddy *
6226 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6228 PurpleBuddy *clone;
6229 const gchar *server_alias, *email;
6230 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6232 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6234 purple_blist_add_buddy(clone, NULL, group, NULL);
6236 server_alias = purple_buddy_get_server_alias(buddy);
6237 if (server_alias) {
6238 purple_blist_server_alias_buddy(clone, server_alias);
6241 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6242 if (email) {
6243 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
6246 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6247 //for UI to update;
6248 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6249 return clone;
6252 static void
6253 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6255 PurpleBuddy *buddy, *b;
6256 PurpleConnection *gc;
6257 PurpleGroup * group = purple_find_group(group_name);
6259 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6261 buddy = (PurpleBuddy *)node;
6263 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
6264 gc = purple_account_get_connection(buddy->account);
6266 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6267 if (!b){
6268 purple_blist_add_buddy_clone(group, buddy);
6271 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6274 static void
6275 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6277 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6279 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
6281 /* 2007+ conference */
6282 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
6284 sipe_conf_add(sipe_private, buddy->name);
6286 else /* 2005- multiparty chat */
6288 gchar *self = sip_uri_self(sipe_private);
6289 struct sip_session *session;
6291 session = sipe_session_add_chat(sipe_private);
6292 session->chat_title = sipe_chat_get_name(session->callid);
6293 session->roster_manager = g_strdup(self);
6295 session->backend_session = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
6296 session->chat_id,
6297 session->chat_title,
6298 self,
6299 FALSE);
6300 sipe_backend_chat_add(session->backend_session,
6301 self,
6302 FALSE);
6303 sipe_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
6305 g_free(self);
6310 * For 2007+ conference only.
6312 static void
6313 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_title)
6315 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6316 struct sip_session *session;
6318 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
6319 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title);
6321 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6323 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
6327 * For 2007+ conference only.
6329 static void
6330 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_title)
6332 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6333 struct sip_session *session;
6335 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
6336 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title);
6338 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6340 sipe_conf_delete_user(sipe_private, session, buddy->name);
6343 static void
6344 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, char *chat_title)
6346 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6347 struct sip_session *session;
6349 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
6350 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title);
6352 session = sipe_session_find_chat_by_title(sipe_private, chat_title);
6354 sipe_invite_to_chat(sipe_private, session, buddy->name);
6357 static void
6358 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
6360 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6362 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
6363 if (phone) {
6364 char *tel_uri = sip_to_tel_uri(phone);
6366 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
6367 sip_csta_make_call(sipe_private, tel_uri);
6369 g_free(tel_uri);
6373 static void
6374 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
6376 /** Translators: replace with URL to localized page
6377 * If it doesn't exist copy the original URL */
6378 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
6381 static void
6382 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6384 const gchar *email;
6385 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
6387 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6388 if (email)
6390 char *command_line = g_strdup_printf(
6391 #ifdef _WIN32
6392 "cmd /c start"
6393 #else
6394 "xdg-email"
6395 #endif
6396 " mailto:%s", email);
6397 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
6399 g_spawn_command_line_async(command_line, NULL);
6400 g_free(command_line);
6402 else
6404 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
6408 static void
6409 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
6410 struct sipe_container *container)
6412 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6413 struct sipe_container_member *member;
6415 if (!container || !container->members) return;
6417 member = ((struct sipe_container_member *)container->members->data);
6419 if (!member->type) return;
6421 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
6422 container->id, member->type, member->value ? member->value : "");
6424 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
6427 static GList *
6428 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6429 const char* uri);
6432 * A menu which appear when right-clicking on buddy in contact list.
6434 GList *
6435 sipe_buddy_menu(PurpleBuddy *buddy)
6437 PurpleBlistNode *g_node;
6438 PurpleGroup *group, *gr_parent;
6439 PurpleMenuAction *act;
6440 GList *menu = NULL;
6441 GList *menu_groups = NULL;
6442 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
6443 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6444 const char *email;
6445 const char *phone;
6446 const char *phone_disp_str;
6447 gchar *self = sip_uri_self(sipe_private);
6449 SIPE_SESSION_FOREACH {
6450 if (!sipe_strcase_equal(self, buddy->name) && session->chat_title && session->backend_session)
6452 if (sipe_backend_chat_find(session->backend_session, buddy->name))
6454 gboolean conf_op = sipe_backend_chat_is_operator(session->backend_session, self);
6456 if (session->focus_uri
6457 && !sipe_backend_chat_is_operator(session->backend_session, buddy->name) /* Not conf OP */
6458 && conf_op) /* We are a conf OP */
6460 gchar *label = g_strdup_printf(_("Make leader of '%s'"), session->chat_title);
6461 act = purple_menu_action_new(label,
6462 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6463 session->chat_title, NULL);
6464 g_free(label);
6465 menu = g_list_prepend(menu, act);
6468 if (session->focus_uri
6469 && conf_op) /* We are a conf OP */
6471 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_title);
6472 act = purple_menu_action_new(label,
6473 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6474 session->chat_title, NULL);
6475 g_free(label);
6476 menu = g_list_prepend(menu, act);
6479 else
6481 if (!session->focus_uri
6482 || (session->focus_uri && !session->locked))
6484 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_title);
6485 act = purple_menu_action_new(label,
6486 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6487 session->chat_title, NULL);
6488 g_free(label);
6489 menu = g_list_prepend(menu, act);
6493 } SIPE_SESSION_FOREACH_END;
6495 act = purple_menu_action_new(_("New chat"),
6496 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6497 NULL, NULL);
6498 menu = g_list_prepend(menu, act);
6500 if (sip->csta && !sip->csta->line_status) {
6501 gchar *tmp = NULL;
6502 /* work phone */
6503 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
6504 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
6505 if (phone) {
6506 gchar *label = g_strdup_printf(_("Work %s"),
6507 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6508 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6509 g_free(tmp);
6510 tmp = NULL;
6511 g_free(label);
6512 menu = g_list_prepend(menu, act);
6515 /* mobile phone */
6516 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
6517 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
6518 if (phone) {
6519 gchar *label = g_strdup_printf(_("Mobile %s"),
6520 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6521 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6522 g_free(tmp);
6523 tmp = NULL;
6524 g_free(label);
6525 menu = g_list_prepend(menu, act);
6528 /* home phone */
6529 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
6530 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
6531 if (phone) {
6532 gchar *label = g_strdup_printf(_("Home %s"),
6533 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6534 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6535 g_free(tmp);
6536 tmp = NULL;
6537 g_free(label);
6538 menu = g_list_prepend(menu, act);
6541 /* other phone */
6542 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
6543 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
6544 if (phone) {
6545 gchar *label = g_strdup_printf(_("Other %s"),
6546 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6547 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6548 g_free(tmp);
6549 tmp = NULL;
6550 g_free(label);
6551 menu = g_list_prepend(menu, act);
6554 /* custom1 phone */
6555 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
6556 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
6557 if (phone) {
6558 gchar *label = g_strdup_printf(_("Custom1 %s"),
6559 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
6560 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
6561 g_free(tmp);
6562 tmp = NULL;
6563 g_free(label);
6564 menu = g_list_prepend(menu, act);
6568 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
6569 if (email) {
6570 act = purple_menu_action_new(_("Send email..."),
6571 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6572 NULL, NULL);
6573 menu = g_list_prepend(menu, act);
6576 /* Access Level */
6577 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6578 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
6580 act = purple_menu_action_new(_("Access level"),
6581 NULL,
6582 NULL, menu_access_levels);
6583 menu = g_list_prepend(menu, act);
6586 /* Copy to */
6587 gr_parent = purple_buddy_get_group(buddy);
6588 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6589 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6590 continue;
6592 group = (PurpleGroup *)g_node;
6593 if (group == gr_parent)
6594 continue;
6596 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6597 continue;
6599 act = purple_menu_action_new(purple_group_get_name(group),
6600 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6601 group->name, NULL);
6602 menu_groups = g_list_prepend(menu_groups, act);
6604 menu_groups = g_list_reverse(menu_groups);
6606 act = purple_menu_action_new(_("Copy to"),
6607 NULL,
6608 NULL, menu_groups);
6609 menu = g_list_prepend(menu, act);
6611 menu = g_list_reverse(menu);
6613 g_free(self);
6614 return menu;
6617 static void
6618 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
6620 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
6621 const char *domain = purple_request_fields_get_string(fields, "access_domain");
6622 int index = purple_request_fields_get_choice(fields, "container_id");
6623 /* move Blocked first */
6624 int i = (index == 4) ? 0 : index + 1;
6625 int container_id = containers[i];
6627 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
6629 sipe_change_access_level(sipe_private, container_id, "domain", domain);
6632 static void
6633 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
6635 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6636 PurpleAccount *account = sip->account;
6637 PurpleConnection *gc = sip->gc;
6638 PurpleRequestFields *fields;
6639 PurpleRequestFieldGroup *g;
6640 PurpleRequestField *f;
6642 fields = purple_request_fields_new();
6644 g = purple_request_field_group_new(NULL);
6645 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
6646 purple_request_field_set_required(f, TRUE);
6647 purple_request_field_group_add_field(g, f);
6649 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
6650 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
6651 purple_request_field_choice_add(f, _("Team"));
6652 purple_request_field_choice_add(f, _("Company"));
6653 purple_request_field_choice_add(f, _("Public"));
6654 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
6655 purple_request_field_choice_set_default_value(f, 3); /* index */
6656 purple_request_field_set_required(f, TRUE);
6657 purple_request_field_group_add_field(g, f);
6659 purple_request_fields_add_group(fields, g);
6661 purple_request_fields(gc, _("Add new domain"),
6662 _("Add new domain"), NULL, fields,
6663 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
6664 _("Cancel"), NULL,
6665 account, NULL, NULL, gc);
6668 static void
6669 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
6671 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
6674 static GList *
6675 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
6676 const char* member_type,
6677 const char* member_value,
6678 const gboolean extra_menu)
6680 GList *menu_access_levels = NULL;
6681 unsigned int i;
6682 char *menu_name;
6683 PurpleMenuAction *act;
6684 struct sipe_container *container;
6685 struct sipe_container_member *member;
6686 gboolean is_group_access = FALSE;
6687 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
6689 for (i = 1; i <= CONTAINERS_LEN; i++) {
6690 /* to put Blocked level last in menu list.
6691 * Blocked should remaim in the first place in the containers[] array.
6693 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
6694 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
6696 container = g_new0(struct sipe_container, 1);
6697 member = g_new0(struct sipe_container_member, 1);
6698 container->id = containers[j];
6699 container->members = g_slist_append(container->members, member);
6700 member->type = g_strdup(member_type);
6701 member->value = g_strdup(member_value);
6703 /* current container/access level */
6704 if (((int)containers[j]) == container_id) {
6705 menu_name = is_group_access ?
6706 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
6707 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
6708 } else {
6709 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
6712 act = purple_menu_action_new(menu_name,
6713 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6714 container, NULL);
6715 g_free(menu_name);
6716 menu_access_levels = g_list_prepend(menu_access_levels, act);
6719 if (extra_menu && (container_id >= 0)) {
6720 /* separator */
6721 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
6722 menu_access_levels = g_list_prepend(menu_access_levels, act);
6724 if (!is_group_access) {
6725 container = g_new0(struct sipe_container, 1);
6726 member = g_new0(struct sipe_container_member, 1);
6727 container->id = -1;
6728 container->members = g_slist_append(container->members, member);
6729 member->type = g_strdup(member_type);
6730 member->value = g_strdup(member_value);
6732 /* Translators: remove (clear) previously assigned access level */
6733 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
6734 act = purple_menu_action_new(menu_name,
6735 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
6736 container, NULL);
6737 g_free(menu_name);
6738 menu_access_levels = g_list_prepend(menu_access_levels, act);
6742 menu_access_levels = g_list_reverse(menu_access_levels);
6743 return menu_access_levels;
6746 static GList *
6747 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
6749 GList *menu_access_groups = NULL;
6750 PurpleMenuAction *act;
6751 GSList *access_domains = NULL;
6752 GSList *entry;
6753 char *menu_name;
6754 char *domain;
6756 act = purple_menu_action_new(_("People in my company"),
6757 NULL,
6758 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
6759 menu_access_groups = g_list_prepend(menu_access_groups, act);
6761 /* this is original name, don't edit */
6762 act = purple_menu_action_new(_("People in domains connected with my company"),
6763 NULL,
6764 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
6765 menu_access_groups = g_list_prepend(menu_access_groups, act);
6767 act = purple_menu_action_new(_("People in public domains"),
6768 NULL,
6769 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
6770 menu_access_groups = g_list_prepend(menu_access_groups, act);
6772 access_domains = sipe_get_access_domains(sipe_private);
6773 entry = access_domains;
6774 while (entry) {
6775 domain = entry->data;
6777 menu_name = g_strdup_printf(_("People at %s"), domain);
6778 act = purple_menu_action_new(menu_name,
6779 NULL,
6780 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
6781 menu_access_groups = g_list_prepend(menu_access_groups, act);
6782 g_free(menu_name);
6784 entry = entry->next;
6787 /* separator */
6788 /* People in domains connected with my company */
6789 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
6790 menu_access_groups = g_list_prepend(menu_access_groups, act);
6792 act = purple_menu_action_new(_("Add new domain..."),
6793 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
6794 NULL, NULL);
6795 menu_access_groups = g_list_prepend(menu_access_groups, act);
6797 menu_access_groups = g_list_reverse(menu_access_groups);
6799 return menu_access_groups;
6802 static GList *
6803 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
6804 const char* uri)
6806 GList *menu_access_levels = NULL;
6807 GList *menu_access_groups = NULL;
6808 char *menu_name;
6809 PurpleMenuAction *act;
6811 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
6813 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
6815 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
6816 act = purple_menu_action_new(menu_name,
6817 NULL,
6818 NULL, menu_access_groups);
6819 g_free(menu_name);
6820 menu_access_levels = g_list_append(menu_access_levels, act);
6822 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
6823 act = purple_menu_action_new(menu_name,
6824 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
6825 NULL, NULL);
6826 g_free(menu_name);
6827 menu_access_levels = g_list_append(menu_access_levels, act);
6829 return menu_access_levels;
6832 static void
6833 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
6835 struct sipe_core_private *sipe_private = PURPLE_CHAT_TO_SIPE_CORE_PRIVATE;
6836 struct sip_session *session;
6838 session = sipe_session_find_chat_by_title(sipe_private,
6839 (gchar *)g_hash_table_lookup(chat->components, "channel"));
6840 sipe_conf_modify_conference_lock(sipe_private, session, locked);
6843 static void
6844 sipe_chat_menu_unlock_cb(PurpleChat *chat)
6846 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
6847 sipe_conf_modify_lock(chat, FALSE);
6850 static void
6851 sipe_chat_menu_lock_cb(PurpleChat *chat)
6853 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
6854 sipe_conf_modify_lock(chat, TRUE);
6857 GList *
6858 sipe_chat_menu(PurpleChat *chat)
6860 PurpleMenuAction *act;
6861 GList *menu = NULL;
6862 struct sipe_core_private *sipe_private = PURPLE_CHAT_TO_SIPE_CORE_PRIVATE;
6863 struct sip_session *session;
6864 gchar *self;
6866 session = sipe_session_find_chat_by_title(sipe_private,
6867 (gchar *)g_hash_table_lookup(chat->components, "channel"));
6868 if (!session) return NULL;
6870 self = sip_uri_self(sipe_private);
6872 if (session->focus_uri &&
6873 sipe_backend_chat_is_operator(session->backend_session, self))
6875 if (session->locked) {
6876 act = purple_menu_action_new(_("Unlock"),
6877 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
6878 NULL, NULL);
6879 menu = g_list_prepend(menu, act);
6880 } else {
6881 act = purple_menu_action_new(_("Lock"),
6882 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
6883 NULL, NULL);
6884 menu = g_list_prepend(menu, act);
6888 menu = g_list_reverse(menu);
6890 g_free(self);
6891 return menu;
6894 static gboolean
6895 process_get_info_response(struct sipe_core_private *sipe_private,
6896 struct sipmsg *msg, struct transaction *trans)
6898 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
6899 char *uri = trans->payload->data;
6901 PurpleNotifyUserInfo *info;
6902 PurpleBuddy *pbuddy = NULL;
6903 struct sipe_buddy *sbuddy;
6904 const char *alias = NULL;
6905 char *device_name = NULL;
6906 char *server_alias = NULL;
6907 char *phone_number = NULL;
6908 char *email = NULL;
6909 const char *site;
6910 char *first_name = NULL;
6911 char *last_name = NULL;
6913 if (!sip) return FALSE;
6915 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
6917 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
6918 alias = purple_buddy_get_local_alias(pbuddy);
6920 //will query buddy UA's capabilities and send answer to log
6921 sipe_options_request(sipe_private, uri);
6923 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
6924 if (sbuddy) {
6925 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6928 info = purple_notify_user_info_new();
6930 if (msg->response != 200) {
6931 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
6932 } else {
6933 sipe_xml *searchResults;
6934 const sipe_xml *mrow;
6936 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
6937 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
6938 if (!searchResults) {
6939 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
6940 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
6941 const char *value;
6942 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
6943 email = g_strdup(sipe_xml_attribute(mrow, "email"));
6944 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
6946 /* For 2007 system we will take this from ContactCard -
6947 * it has cleaner tel: URIs at least
6949 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
6950 char *tel_uri = sip_to_tel_uri(phone_number);
6951 /* trims its parameters, so call first */
6952 sipe_update_user_info(sipe_private, uri, ALIAS_PROP, server_alias);
6953 sipe_update_user_info(sipe_private, uri, EMAIL_PROP, email);
6954 sipe_update_user_info(sipe_private, uri, PHONE_PROP, tel_uri);
6955 sipe_update_user_info(sipe_private, uri, PHONE_DISPLAY_PROP, phone_number);
6956 g_free(tel_uri);
6959 if (server_alias && strlen(server_alias) > 0) {
6960 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
6962 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
6963 purple_notify_user_info_add_pair(info, _("Job title"), value);
6965 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
6966 purple_notify_user_info_add_pair(info, _("Office"), value);
6968 if (phone_number && strlen(phone_number) > 0) {
6969 purple_notify_user_info_add_pair(info, _("Business phone"), phone_number);
6971 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
6972 purple_notify_user_info_add_pair(info, _("Company"), value);
6974 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
6975 purple_notify_user_info_add_pair(info, _("City"), value);
6977 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
6978 purple_notify_user_info_add_pair(info, _("State"), value);
6980 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
6981 purple_notify_user_info_add_pair(info, _("Country"), value);
6983 if (email && strlen(email) > 0) {
6984 purple_notify_user_info_add_pair(info, _("Email address"), email);
6988 sipe_xml_free(searchResults);
6991 purple_notify_user_info_add_section_break(info);
6993 if (is_empty(server_alias)) {
6994 g_free(server_alias);
6995 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6996 if (server_alias) {
6997 purple_notify_user_info_add_pair(info, _("Display name"), server_alias);
7001 /* present alias if it differs from server alias */
7002 if (alias && !sipe_strequal(alias, server_alias))
7004 purple_notify_user_info_add_pair(info, _("Alias"), alias);
7007 if (is_empty(email)) {
7008 g_free(email);
7009 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
7010 if (email) {
7011 purple_notify_user_info_add_pair(info, _("Email address"), email);
7015 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
7016 if (site) {
7017 purple_notify_user_info_add_pair(info, _("Site"), site);
7020 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
7021 if (first_name && last_name) {
7022 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
7024 purple_notify_user_info_add_pair(info, _("Find on LinkedIn"), link);
7025 g_free(link);
7027 g_free(first_name);
7028 g_free(last_name);
7030 if (device_name) {
7031 purple_notify_user_info_add_pair(info, _("Device"), device_name);
7034 /* show a buddy's user info in a nice dialog box */
7035 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
7036 uri, /* buddy's URI */
7037 info, /* body */
7038 NULL, /* callback called when dialog closed */
7039 NULL); /* userdata for callback */
7041 g_free(phone_number);
7042 g_free(server_alias);
7043 g_free(email);
7044 g_free(device_name);
7046 return TRUE;
7050 * AD search first, LDAP based
7052 void sipe_get_info(PurpleConnection *gc, const char *username)
7054 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
7055 gchar *domain_uri = sip_uri_from_name(sipe_private->public.sip_domain);
7056 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
7057 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
7058 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
7060 payload->destroy = g_free;
7061 payload->data = g_strdup(username);
7063 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body ? body : "");
7064 send_soap_request_with_cb(sipe_private, domain_uri, body,
7065 process_get_info_response, payload);
7066 g_free(domain_uri);
7067 g_free(body);
7068 g_free(row);
7072 Local Variables:
7073 mode: c
7074 c-file-style: "bsd"
7075 indent-tabs-mode: t
7076 tab-width: 8
7077 End: