core cleanup: move sipe_core_group_set_user() to sipe-group.c
[siplcs.git] / src / core / sipe.c
blobcad1c6f8fa651b796442e369fc59747fbbbf605a
1 /**
2 * @file sipe.c
4 *****************************************************************************
5 *** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ***
6 *** ***
7 *** THIS MODULE IS DEPECRATED ***
8 *** ***
9 *** DO NOT ADD ANY NEW CODE TO THIS MODULE ***
10 *** ***
11 *** !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ***
12 *****************************************************************************
14 * pidgin-sipe
16 * Copyright (C) 2010-11 SIPE Project <http://sipe.sourceforge.net/>
17 * Copyright (C) 2010 pier11 <pier11@operamail.com>
18 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
19 * Copyright (C) 2009 pier11 <pier11@operamail.com>
20 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
21 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
22 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
24 * ***
25 * Thanks to Google's Summer of Code Program and the helpful mentors
26 * ***
28 * Session-based SIP MESSAGE documentation:
29 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
31 * This program is free software; you can redistribute it and/or modify
32 * it under the terms of the GNU General Public License as published by
33 * the Free Software Foundation; either version 2 of the License, or
34 * (at your option) any later version.
36 * This program is distributed in the hope that it will be useful,
37 * but WITHOUT ANY WARRANTY; without even the implied warranty of
38 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
39 * GNU General Public License for more details.
41 * You should have received a copy of the GNU General Public License
42 * along with this program; if not, write to the Free Software
43 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
46 #ifdef HAVE_CONFIG_H
47 #include "config.h"
48 #endif
50 #include <time.h>
51 #include <stdlib.h>
52 #include <stdio.h>
53 #include <errno.h>
54 #include <string.h>
55 #include <unistd.h>
57 #include <glib.h>
59 #include <libintl.h>
61 #include "sipe-common.h"
63 #include "account.h"
64 #include "blist.h"
65 #include "connection.h"
66 #include "conversation.h"
67 #include "ft.h"
68 #include "notify.h"
69 #include "plugin.h"
70 #include "privacy.h"
71 #include "request.h"
72 #include "savedstatuses.h"
73 #include "version.h"
75 #include "core-depurple.h" /* Temporary for the core de-purple transition */
77 #include "http-conn.h"
78 #include "sipmsg.h"
79 #include "sip-csta.h"
80 #include "sip-soap.h"
81 #include "sip-transport.h"
82 #include "sipe-backend.h"
83 #include "sipe-buddy.h"
84 #include "sipe-cal.h"
85 #include "sipe-chat.h"
86 #include "sipe-conf.h"
87 #include "sipe-core.h"
88 #include "sipe-core-private.h"
89 #include "sipe-group.h"
90 #include "sipe-dialog.h"
91 #include "sipe-ews.h"
92 #ifdef _WIN32
93 #include "sipe-domino.h"
94 #endif
95 #include "sipe-groupchat.h"
96 #include "sipe-im.h"
97 #include "sipe-mime.h"
98 #include "sipe-nls.h"
99 #include "sipe-schedule.h"
100 #include "sipe-session.h"
101 #include "sipe-subscriptions.h"
102 #ifdef HAVE_VV
103 #include "sipe-media.h"
104 #endif
105 #include "sipe-utils.h"
106 #include "sipe-xml.h"
107 #include "uuid.h"
108 #include "sipe.h"
110 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
112 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
113 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
115 /* Status identifiers (see also: sipe_status_types()) */
116 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
117 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
118 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
119 /* PURPLE_STATUS_UNAVAILABLE: */
120 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
121 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
122 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
123 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
124 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
125 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
126 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
127 /* PURPLE_STATUS_AWAY: */
128 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
129 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
130 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
131 /** Reuters status (user settable) */
132 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
133 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
134 /* ??? PURPLE_STATUS_MOBILE */
135 /* ??? PURPLE_STATUS_TUNE */
137 /* Status attributes (see also sipe_status_types() */
138 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
140 static struct sipe_activity_map_struct
142 sipe_activity type;
143 const char *token;
144 const char *desc;
145 const char *status_id;
147 } const sipe_activity_map[] =
149 /* This has nothing to do with Availability numbers, like 3500 (online).
150 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
152 { SIPE_ACTIVITY_UNSET, "unset", NULL , NULL },
153 { SIPE_ACTIVITY_ONLINE, "online", NULL , NULL },
154 { SIPE_ACTIVITY_INACTIVE, SIPE_STATUS_ID_IDLE, N_("Inactive") , NULL },
155 { SIPE_ACTIVITY_BUSY, SIPE_STATUS_ID_BUSY, N_("Busy") , SIPE_STATUS_ID_BUSY },
156 { SIPE_ACTIVITY_BUSYIDLE, SIPE_STATUS_ID_BUSYIDLE, N_("Busy-Idle") , NULL },
157 { SIPE_ACTIVITY_DND, SIPE_STATUS_ID_DND, NULL , SIPE_STATUS_ID_DND },
158 { SIPE_ACTIVITY_BRB, SIPE_STATUS_ID_BRB, N_("Be right back") , SIPE_STATUS_ID_BRB },
159 { SIPE_ACTIVITY_AWAY, "away", NULL , NULL },
160 { SIPE_ACTIVITY_LUNCH, SIPE_STATUS_ID_LUNCH, N_("Out to lunch") , NULL },
161 { SIPE_ACTIVITY_OFFLINE, "offline", NULL , NULL },
162 { SIPE_ACTIVITY_ON_PHONE, SIPE_STATUS_ID_ON_PHONE, N_("In a call") , NULL },
163 { SIPE_ACTIVITY_IN_CONF, SIPE_STATUS_ID_IN_CONF, N_("In a conference") , NULL },
164 { SIPE_ACTIVITY_IN_MEETING, SIPE_STATUS_ID_IN_MEETING, N_("In a meeting") , NULL },
165 { SIPE_ACTIVITY_OOF, "out-of-office", N_("Out of office") , NULL },
166 { SIPE_ACTIVITY_URGENT_ONLY, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL }
168 /** @param x is sipe_activity */
169 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
171 static sipe_activity
172 sipe_get_activity_by_token(const char *token)
174 int i;
176 for (i = 0; i < SIPE_ACTIVITY_NUM_TYPES; i++)
178 if (sipe_strequal(token, sipe_activity_map[i].token))
179 return sipe_activity_map[i].type;
182 return sipe_activity_map[0].type;
185 static const char *
186 sipe_get_activity_desc_by_token(const char *token)
188 if (!token) return NULL;
190 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token));
193 static void send_presence_status(struct sipe_core_private *sipe_private,
194 void *unused);
197 * Returns pointer to URI without sip: prefix if any
199 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
200 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
202 * Doesn't allocate memory
204 static const char *
205 sipe_get_no_sip_uri(const char *sip_uri)
207 const char *prefix = "sip:";
208 if (!sip_uri) return NULL;
210 if (g_str_has_prefix(sip_uri, prefix)) {
211 return (sip_uri+strlen(prefix));
212 } else {
213 return sip_uri;
217 static void
218 sipe_change_access_level(struct sipe_core_private *sipe_private,
219 const int container_id,
220 const gchar *type,
221 const gchar *value);
223 void
224 sipe_core_contact_allow_deny (struct sipe_core_public *sipe_public,
225 const gchar * who, gboolean allow)
227 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
229 if (allow) {
230 SIPE_DEBUG_INFO("Authorizing contact %s", who);
231 } else {
232 SIPE_DEBUG_INFO("Blocking contact %s", who);
235 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
236 sipe_change_access_level(sipe_private, (allow ? -1 : 32000), "user", sipe_get_no_sip_uri(who));
237 } else {
238 sip_soap_ocs2005_setacl(sipe_private, who, allow);
242 static int
243 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token);
245 static const char*
246 sipe_get_status_by_availability(int avail,
247 char** activity);
249 static void
250 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
251 const char *status_id,
252 const char *message,
253 time_t do_not_publish[]);
255 static void
256 sipe_apply_calendar_status(struct sipe_core_private *sipe_private,
257 struct sipe_buddy *sbuddy,
258 const char *status_id)
260 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
261 time_t cal_avail_since;
262 int cal_status = sipe_cal_get_status(sbuddy, time(NULL), &cal_avail_since);
263 int avail;
264 gchar *self_uri;
266 if (!sbuddy) return;
268 if (cal_status < SIPE_CAL_NO_DATA) {
269 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status, sbuddy->name);
270 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since)));
273 /* scheduled Cal update call */
274 if (!status_id) {
275 status_id = sbuddy->last_non_cal_status_id;
276 g_free(sbuddy->activity);
277 sbuddy->activity = g_strdup(sbuddy->last_non_cal_activity);
280 if (!status_id) {
281 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
282 sbuddy->name ? sbuddy->name : "" );
283 return;
286 /* adjust to calendar status */
287 if (cal_status != SIPE_CAL_NO_DATA) {
288 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy->user_avail_since)));
290 if (cal_status == SIPE_CAL_BUSY
291 && cal_avail_since > sbuddy->user_avail_since
292 && 6500 >= sipe_get_availability_by_status(status_id, NULL))
294 status_id = SIPE_STATUS_ID_BUSY;
295 g_free(sbuddy->activity);
296 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING));
298 avail = sipe_get_availability_by_status(status_id, NULL);
300 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy->activity_since)));
301 if (cal_avail_since > sbuddy->activity_since) {
302 if (cal_status == SIPE_CAL_OOF
303 && avail >= 15000) /* 12000 in 2007 */
305 g_free(sbuddy->activity);
306 sbuddy->activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
311 /* then set status_id actually */
312 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id, sbuddy->name ? sbuddy->name : "" );
313 sipe_backend_buddy_set_status(SIPE_CORE_PUBLIC, sbuddy->name, status_id);
315 /* set our account state to the one in roaming (including calendar info) */
316 self_uri = sip_uri_self(sipe_private);
317 if (sip->initial_state_published && sipe_strcase_equal(sbuddy->name, self_uri)) {
318 if (sipe_strequal(status_id, SIPE_STATUS_ID_OFFLINE)) {
319 status_id = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
322 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip->status);
323 sipe_set_purple_account_status_and_note(sip->account, status_id, sip->note, sip->do_not_publish);
325 g_free(self_uri);
328 void
329 sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
330 const gchar* uri,
331 const gchar *status_id)
333 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
334 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
336 if (!sbuddy) return;
338 /* Check if on 2005 system contact's calendar,
339 * then set/preserve it.
341 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
342 sipe_apply_calendar_status(sipe_private, sbuddy, status_id);
343 } else {
344 sipe_backend_buddy_set_status(sipe_public, uri, status_id);
348 static void
349 update_calendar_status_cb(SIPE_UNUSED_PARAMETER char *name,
350 struct sipe_buddy *sbuddy,
351 struct sipe_core_private *sipe_private)
353 sipe_apply_calendar_status(sipe_private, sbuddy, NULL);
357 * Updates contact's status
358 * based on their calendar information.
360 * Applicability: 2005 systems
362 static void
363 update_calendar_status(struct sipe_core_private *sipe_private,
364 SIPE_UNUSED_PARAMETER void *unused)
366 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
367 g_hash_table_foreach(sipe_private->buddies, (GHFunc)update_calendar_status_cb, sipe_private);
369 /* repeat scheduling */
370 sipe_sched_calendar_status_update(sipe_private, time(NULL) + 3*60 /* 3 min */);
374 * Schedules process of contacts' status update
375 * based on their calendar information.
376 * Should be scheduled to the beginning of every
377 * 15 min interval, like:
378 * 13:00, 13:15, 13:30, 13:45, etc.
380 * Applicability: 2005 systems
382 void
383 sipe_sched_calendar_status_update(struct sipe_core_private *sipe_private,
384 time_t calculate_from)
386 int interval = 15*60;
387 /** start of the beginning of closest 15 min interval. */
388 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
390 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
391 asctime(localtime(&calculate_from)));
392 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
393 asctime(localtime(&next_start)));
395 sipe_schedule_seconds(sipe_private,
396 "<+2005-cal-status>",
397 NULL,
398 next_start - time(NULL),
399 update_calendar_status,
400 NULL);
404 * Schedules process of self status publish
405 * based on own calendar information.
406 * Should be scheduled to the beginning of every
407 * 15 min interval, like:
408 * 13:00, 13:15, 13:30, 13:45, etc.
410 * Applicability: 2007+ systems
412 static void
413 sipe_sched_calendar_status_self_publish(struct sipe_core_private *sipe_private,
414 time_t calculate_from)
416 int interval = 5*60;
417 /** start of the beginning of closest 5 min interval. */
418 time_t next_start = ((time_t)((int)((int)calculate_from)/interval + 1)*interval);
420 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
421 asctime(localtime(&calculate_from)));
422 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
423 asctime(localtime(&next_start)));
425 sipe_schedule_seconds(sipe_private,
426 "<+2007-cal-status>",
427 NULL,
428 next_start - time(NULL),
429 publish_calendar_status_self,
430 NULL);
434 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
435 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
436 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
437 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
438 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
441 void sipe_subscribe_presence_batched_to(struct sipe_core_private *sipe_private,
442 gchar *resources_uri,
443 gchar *to)
445 gchar *contact = get_contact(sipe_private);
446 gchar *request;
447 gchar *content;
448 gchar *require = "";
449 gchar *accept = "";
450 gchar *autoextend = "";
451 gchar *content_type;
453 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
454 require = ", categoryList";
455 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
456 content_type = "application/msrtc-adrl-categorylist+xml";
457 content = g_strdup_printf(
458 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
459 "<action name=\"subscribe\" id=\"63792024\">\n"
460 "<adhocList>\n%s</adhocList>\n"
461 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
462 "<category name=\"calendarData\"/>\n"
463 "<category name=\"contactCard\"/>\n"
464 "<category name=\"note\"/>\n"
465 "<category name=\"state\"/>\n"
466 "</categoryList>\n"
467 "</action>\n"
468 "</batchSub>", sipe_private->username, resources_uri);
469 } else {
470 autoextend = "Supported: com.microsoft.autoextend\r\n";
471 content_type = "application/adrl+xml";
472 content = g_strdup_printf(
473 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
474 "<create xmlns=\"\">\n%s</create>\n"
475 "</adhoclist>\n", sipe_private->username, sipe_private->username, resources_uri);
477 g_free(resources_uri);
479 request = g_strdup_printf(
480 "Require: adhoclist%s\r\n"
481 "Supported: eventlist\r\n"
482 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
483 "Supported: ms-piggyback-first-notify\r\n"
484 "%sSupported: ms-benotify\r\n"
485 "Proxy-Require: ms-benotify\r\n"
486 "Event: presence\r\n"
487 "Content-Type: %s\r\n"
488 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
489 g_free(contact);
491 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
493 g_free(content);
494 g_free(to);
495 g_free(request);
498 struct presence_batched_routed {
499 gchar *host;
500 GSList *buddies;
503 static void sipe_subscribe_presence_batched_routed_free(void *payload)
505 struct presence_batched_routed *data = payload;
506 GSList *buddies = data->buddies;
507 while (buddies) {
508 g_free(buddies->data);
509 buddies = buddies->next;
511 g_slist_free(data->buddies);
512 g_free(data->host);
513 g_free(payload);
516 static void sipe_subscribe_presence_batched_routed(struct sipe_core_private *sipe_private,
517 void *payload)
519 struct presence_batched_routed *data = payload;
520 GSList *buddies = data->buddies;
521 gchar *resources_uri = g_strdup("");
522 while (buddies) {
523 gchar *tmp = resources_uri;
524 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
525 g_free(tmp);
526 buddies = buddies->next;
528 sipe_subscribe_presence_batched_to(sipe_private, resources_uri,
529 g_strdup(data->host));
533 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
534 * The user sends a single SUBSCRIBE request to the subscribed contact.
535 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
539 void sipe_subscribe_presence_single(struct sipe_core_private *sipe_private,
540 void *buddy_name)
542 gchar *to = sip_uri((char *)buddy_name);
543 gchar *tmp = get_contact(sipe_private);
544 gchar *request;
545 gchar *content = NULL;
546 gchar *autoextend = "";
547 gchar *content_type = "";
548 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, to);
549 gchar *context = sbuddy && sbuddy->just_added ? "><context/></resource>" : "/>";
551 if (sbuddy) sbuddy->just_added = FALSE;
553 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
554 content_type = "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
555 } else {
556 autoextend = "Supported: com.microsoft.autoextend\r\n";
559 request = g_strdup_printf(
560 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
561 "Supported: ms-piggyback-first-notify\r\n"
562 "%s%sSupported: ms-benotify\r\n"
563 "Proxy-Require: ms-benotify\r\n"
564 "Event: presence\r\n"
565 "Contact: %s\r\n", autoextend, content_type, tmp);
567 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
568 content = g_strdup_printf(
569 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
570 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
571 "<resource uri=\"%s\"%s\n"
572 "</adhocList>\n"
573 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
574 "<category name=\"calendarData\"/>\n"
575 "<category name=\"contactCard\"/>\n"
576 "<category name=\"note\"/>\n"
577 "<category name=\"state\"/>\n"
578 "</categoryList>\n"
579 "</action>\n"
580 "</batchSub>", sipe_private->username, to, context);
583 g_free(tmp);
585 sipe_subscribe_presence_buddy(sipe_private, to, request, content);
587 g_free(content);
588 g_free(to);
589 g_free(request);
592 void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
594 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status));
596 if (!purple_status_is_active(status))
597 return;
599 if (account->gc) {
600 struct sipe_core_private *sipe_private = PURPLE_ACCOUNT_TO_SIPE_CORE_PRIVATE;
601 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
603 if (sip) {
604 gchar *action_name;
605 gchar *tmp;
606 time_t now = time(NULL);
607 const char *status_id = purple_status_get_id(status);
608 const char *note = purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE);
609 sipe_activity activity = sipe_get_activity_by_token(status_id);
610 gboolean do_not_publish = ((now - sip->do_not_publish[activity]) <= 2);
612 /* when other point of presence clears note, but we are keeping
613 * state if OOF note.
615 if (do_not_publish && !note && sip->cal && sip->cal->oof_note) {
616 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
617 do_not_publish = FALSE;
620 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
621 status_id, (int)sip->do_not_publish[activity], (int)now);
623 sip->do_not_publish[activity] = 0;
624 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
625 status_id, (int)sip->do_not_publish[activity]);
627 if (do_not_publish)
629 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
630 return;
633 g_free(sip->status);
634 sip->status = g_strdup(status_id);
636 /* hack to escape apostrof before comparison */
637 tmp = note ? sipe_utils_str_replace(note, "'", "&apos;") : NULL;
639 /* this will preserve OOF flag as well */
640 if (!sipe_strequal(tmp, sip->note)) {
641 sip->is_oof_note = FALSE;
642 g_free(sip->note);
643 sip->note = g_strdup(note);
644 sip->note_since = time(NULL);
646 g_free(tmp);
648 /* schedule 2 sec to capture idle flag */
649 action_name = g_strdup_printf("<%s>", "+set-status");
650 sipe_schedule_seconds(sipe_private,
651 action_name,
652 NULL,
653 SIPE_IDLE_SET_DELAY,
654 send_presence_status,
655 NULL);
656 g_free(action_name);
661 void
662 sipe_set_idle(PurpleConnection * gc,
663 int interval)
665 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval);
667 if (gc) {
668 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
669 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
671 if (sip) {
672 sip->idle_switch = time(NULL);
673 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
678 void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
680 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
682 /* libpurple can call us with undefined buddy or group */
683 if (buddy && group) {
684 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
686 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
687 gchar *buddy_name = g_ascii_strdown(buddy->name, -1);
688 purple_blist_rename_buddy(buddy, buddy_name);
689 g_free(buddy_name);
691 /* Prepend sip: if needed */
692 if (!g_str_has_prefix(buddy->name, "sip:")) {
693 gchar *buf = sip_uri_from_name(buddy->name);
694 purple_blist_rename_buddy(buddy, buf);
695 g_free(buf);
698 if (!g_hash_table_lookup(sipe_private->buddies, buddy->name)) {
699 struct sipe_buddy *b = g_new0(struct sipe_buddy, 1);
700 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy->name);
701 b->name = g_strdup(buddy->name);
702 b->just_added = TRUE;
703 g_hash_table_insert(sipe_private->buddies, b->name, b);
704 /* @TODO should go to callback */
705 sipe_subscribe_presence_single(sipe_private,
706 b->name);
707 } else {
708 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy->name);
711 sipe_core_buddy_group(PURPLE_GC_TO_SIPE_CORE_PUBLIC, buddy->name, NULL, group->name);
715 static void sipe_free_buddy(struct sipe_buddy *buddy)
717 #ifndef _WIN32
719 * We are calling g_hash_table_foreach_steal(). That means that no
720 * key/value deallocation functions are called. Therefore the glib
721 * hash code does not touch the key (buddy->name) or value (buddy)
722 * of the to-be-deleted hash node at all. It follows that we
724 * - MUST free the memory for the key ourselves and
725 * - ARE allowed to do it in this function
727 * Conclusion: glib must be broken on the Windows platform if sipe
728 * crashes with SIGTRAP when closing. You'll have to live
729 * with the memory leak until this is fixed.
731 g_free(buddy->name);
732 #endif
733 g_free(buddy->activity);
734 g_free(buddy->meeting_subject);
735 g_free(buddy->meeting_location);
736 g_free(buddy->note);
738 g_free(buddy->cal_start_time);
739 g_free(buddy->cal_free_busy_base64);
740 g_free(buddy->cal_free_busy);
741 g_free(buddy->last_non_cal_activity);
743 sipe_cal_free_working_hours(buddy->cal_working_hours);
745 g_free(buddy->device_name);
746 g_slist_free(buddy->groups);
747 g_free(buddy);
751 * Unassociates buddy from group first.
752 * Then see if no groups left, removes buddy completely.
753 * Otherwise updates buddy groups on server.
755 void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
757 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
758 struct sipe_buddy *b;
759 struct sipe_group *g = NULL;
761 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy ? buddy->name : "", group ? group->name : "");
762 if (!buddy) return;
764 b = g_hash_table_lookup(sipe_private->buddies, buddy->name);
765 if (!b) return;
767 if (group) {
768 g = sipe_group_find_by_name(sipe_private, group->name);
771 if (g) {
772 b->groups = g_slist_remove(b->groups, g);
773 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy->name, g->name);
776 if (g_slist_length(b->groups) < 1) {
777 gchar *action_name = sipe_utils_presence_key(buddy->name);
778 sipe_schedule_cancel(sipe_private, action_name);
779 g_free(action_name);
781 g_hash_table_remove(sipe_private->buddies, buddy->name);
783 if (b->name) {
784 gchar *request = g_strdup_printf("<m:URI>%s</m:URI>",
785 b->name);
786 sip_soap_request(sipe_private,
787 "deleteContact",
788 request);
789 g_free(request);
792 sipe_free_buddy(b);
793 } else {
794 //updates groups on server
795 sipe_core_group_set_user(SIPE_CORE_PUBLIC, b->name);
800 static int
801 sipe_find_access_level(struct sipe_core_private *sipe_private,
802 const gchar *type,
803 const gchar *value,
804 gboolean *is_group_access);
806 static void
807 sipe_refresh_blocked_status_cb(char *buddy_name,
808 SIPE_UNUSED_PARAMETER struct sipe_buddy *buddy,
809 struct sipe_core_private *sipe_private)
811 int container_id = sipe_find_access_level(sipe_private, "user", buddy_name, NULL);
812 gboolean blocked = (container_id == 32000);
813 gboolean blocked_in_blist = sipe_backend_buddy_is_blocked(SIPE_CORE_PUBLIC, buddy_name);
815 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
816 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
818 if (blocked != blocked_in_blist) {
819 sipe_backend_buddy_set_blocked_status(SIPE_CORE_PUBLIC, buddy_name, blocked);
823 static void
824 sipe_refresh_blocked_status(struct sipe_core_private *sipe_private)
826 g_hash_table_foreach(sipe_private->buddies,
827 (GHFunc) sipe_refresh_blocked_status_cb,
828 sipe_private);
831 /** MS-PRES container */
832 struct sipe_container {
833 guint id;
834 guint version;
835 GSList *members;
837 /** MS-PRES container member */
838 struct sipe_container_member {
839 /** user, domain, sameEnterprise, federated, publicCloud; everyone */
840 gchar *type;
841 gchar *value;
844 static void
845 free_container_member(struct sipe_container_member *member)
847 if (!member) return;
849 g_free(member->type);
850 g_free(member->value);
851 g_free(member);
854 static void
855 free_container(struct sipe_container *container)
857 GSList *entry;
859 if (!container) return;
861 entry = container->members;
862 while (entry) {
863 void *data = entry->data;
864 entry = g_slist_remove(entry, data);
865 free_container_member((struct sipe_container_member *)data);
867 g_free(container);
870 static void
871 sipe_send_container_members_prepare(const guint container_id,
872 const guint container_version,
873 const gchar *action,
874 const gchar *type,
875 const gchar *value,
876 char **container_xmls)
878 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
879 gchar *body;
881 if (!container_xmls) return;
883 body = g_strdup_printf(
884 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
885 container_id,
886 container_version,
887 action,
888 type,
889 value_str);
890 g_free(value_str);
892 if ((*container_xmls) == NULL) {
893 *container_xmls = body;
894 } else {
895 char *tmp = *container_xmls;
897 *container_xmls = g_strconcat(*container_xmls, body, NULL);
898 g_free(tmp);
899 g_free(body);
903 static void
904 sipe_send_set_container_members(struct sipe_core_private *sipe_private,
905 char *container_xmls)
907 gchar *self;
908 gchar *contact;
909 gchar *hdr;
910 gchar *body;
912 if (!container_xmls) return;
914 self = sip_uri_self(sipe_private);
915 body = g_strdup_printf(
916 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
917 "%s"
918 "</setContainerMembers>",
919 container_xmls);
921 contact = get_contact(sipe_private);
922 hdr = g_strdup_printf("Contact: %s\r\n"
923 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
924 g_free(contact);
926 sip_transport_service(sipe_private,
927 self,
928 hdr,
929 body,
930 NULL);
932 g_free(hdr);
933 g_free(body);
934 g_free(self);
938 * Finds locally stored MS-PRES container member
940 static struct sipe_container_member *
941 sipe_find_container_member(struct sipe_container *container,
942 const gchar *type,
943 const gchar *value)
945 struct sipe_container_member *member;
946 GSList *entry;
948 if (container == NULL || type == NULL) {
949 return NULL;
952 entry = container->members;
953 while (entry) {
954 member = entry->data;
955 if (sipe_strcase_equal(member->type, type) &&
956 sipe_strcase_equal(member->value, value))
958 return member;
960 entry = entry->next;
962 return NULL;
966 * Finds locally stored MS-PRES container by id
968 static struct sipe_container *
969 sipe_find_container(struct sipe_core_private *sipe_private,
970 guint id)
972 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
973 struct sipe_container *container;
974 GSList *entry;
976 if (sip == NULL) {
977 return NULL;
980 entry = sip->containers;
981 while (entry) {
982 container = entry->data;
983 if (id == container->id) {
984 return container;
986 entry = entry->next;
988 return NULL;
991 static GSList *
992 sipe_get_access_domains(struct sipe_core_private *sipe_private)
994 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
995 struct sipe_container *container;
996 struct sipe_container_member *member;
997 GSList *entry;
998 GSList *entry2;
999 GSList *res = NULL;
1001 if (!sip) return NULL;
1003 entry = sip->containers;
1004 while (entry) {
1005 container = entry->data;
1007 entry2 = container->members;
1008 while (entry2) {
1009 member = entry2->data;
1010 if (sipe_strcase_equal(member->type, "domain"))
1012 res = slist_insert_unique_sorted(res, g_strdup(member->value), (GCompareFunc)g_ascii_strcasecmp);
1014 entry2 = entry2->next;
1016 entry = entry->next;
1018 return res;
1022 * Returns pointer to domain part in provided Email URL
1024 * @param email an email URL. Example: first.last@hq.company.com
1025 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
1027 * Doesn't allocate memory
1029 static const char *
1030 sipe_get_domain(const char *email)
1032 char *tmp;
1034 if (!email) return NULL;
1036 tmp = strstr(email, "@");
1038 if (tmp && ((tmp+1) < (email + strlen(email)))) {
1039 return tmp+1;
1040 } else {
1041 return NULL;
1046 /* @TODO: replace with binary search for faster access? */
1047 /** source: http://support.microsoft.com/kb/897567 */
1048 static const char * const public_domains [] = {
1049 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
1050 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
1051 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
1052 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
1053 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
1054 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
1055 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
1056 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
1057 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
1058 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
1059 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
1060 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
1061 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
1062 "yahoo.com",
1063 NULL};
1065 static gboolean
1066 sipe_is_public_domain(const char *domain)
1068 int i = 0;
1069 while (public_domains[i]) {
1070 if (sipe_strcase_equal(public_domains[i], domain)) {
1071 return TRUE;
1073 i++;
1075 return FALSE;
1079 * Access Levels
1080 * 32000 - Blocked
1081 * 400 - Personal
1082 * 300 - Team
1083 * 200 - Company
1084 * 100 - Public
1086 static const char *
1087 sipe_get_access_level_name(int container_id)
1089 switch(container_id) {
1090 case 32000: return _("Blocked");
1091 case 400: return _("Personal");
1092 case 300: return _("Team");
1093 case 200: return _("Company");
1094 case 100: return _("Public");
1096 return _("Unknown");
1099 static const guint containers[] = {32000, 400, 300, 200, 100};
1100 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
1103 static int
1104 sipe_find_member_access_level(struct sipe_core_private *sipe_private,
1105 const gchar *type,
1106 const gchar *value)
1108 unsigned int i = 0;
1109 const gchar *value_mod = value;
1111 if (!type) return -1;
1113 if (sipe_strequal("user", type)) {
1114 value_mod = sipe_get_no_sip_uri(value);
1117 for (i = 0; i < CONTAINERS_LEN; i++) {
1118 struct sipe_container_member *member;
1119 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1120 if (!container) continue;
1122 member = sipe_find_container_member(container, type, value_mod);
1123 if (member) return containers[i];
1126 return -1;
1129 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
1130 static int
1131 sipe_find_access_level(struct sipe_core_private *sipe_private,
1132 const gchar *type,
1133 const gchar *value,
1134 gboolean *is_group_access)
1136 int container_id = -1;
1138 if (sipe_strequal("user", type)) {
1139 const char *domain;
1140 const char *no_sip_uri = sipe_get_no_sip_uri(value);
1142 container_id = sipe_find_member_access_level(sipe_private, "user", no_sip_uri);
1143 if (container_id >= 0) {
1144 if (is_group_access) *is_group_access = FALSE;
1145 return container_id;
1148 domain = sipe_get_domain(no_sip_uri);
1149 container_id = sipe_find_member_access_level(sipe_private, "domain", domain);
1150 if (container_id >= 0) {
1151 if (is_group_access) *is_group_access = TRUE;
1152 return container_id;
1155 container_id = sipe_find_member_access_level(sipe_private, "sameEnterprise", NULL);
1156 if ((container_id >= 0) && sipe_strcase_equal(sipe_private->public.sip_domain, domain)) {
1157 if (is_group_access) *is_group_access = TRUE;
1158 return container_id;
1161 container_id = sipe_find_member_access_level(sipe_private, "publicCloud", NULL);
1162 if ((container_id >= 0) && sipe_is_public_domain(domain)) {
1163 if (is_group_access) *is_group_access = TRUE;
1164 return container_id;
1167 container_id = sipe_find_member_access_level(sipe_private, "everyone", NULL);
1168 if ((container_id >= 0)) {
1169 if (is_group_access) *is_group_access = TRUE;
1170 return container_id;
1172 } else {
1173 container_id = sipe_find_member_access_level(sipe_private, type, value);
1174 if (is_group_access) *is_group_access = FALSE;
1177 return container_id;
1181 * @param container_id a new access level. If -1 then current access level
1182 * is just removed (I.e. the member is removed from all containers).
1183 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
1184 * @param value a value for member. E.g. SIP URI for "user" member type.
1186 static void
1187 sipe_change_access_level(struct sipe_core_private *sipe_private,
1188 const int container_id,
1189 const gchar *type,
1190 const gchar *value)
1192 unsigned int i;
1193 int current_container_id = -1;
1194 char *container_xmls = NULL;
1196 /* for each container: find/delete */
1197 for (i = 0; i < CONTAINERS_LEN; i++) {
1198 struct sipe_container_member *member;
1199 struct sipe_container *container = sipe_find_container(sipe_private, containers[i]);
1201 if (!container) continue;
1203 member = sipe_find_container_member(container, type, value);
1204 if (member) {
1205 current_container_id = containers[i];
1206 /* delete/publish current access level */
1207 if (container_id < 0 || container_id != current_container_id) {
1208 sipe_send_container_members_prepare(current_container_id, container->version, "remove", type, value, &container_xmls);
1209 /* remove member from our cache, to be able to recalculate AL below */
1210 container->members = g_slist_remove(container->members, member);
1211 current_container_id = -1;
1216 /* recalculate AL below */
1217 current_container_id = sipe_find_access_level(sipe_private, type, value, NULL);
1219 /* assign/publish new access level */
1220 if (container_id != current_container_id && container_id >= 0) {
1221 struct sipe_container *container = sipe_find_container(sipe_private, container_id);
1222 guint version = container ? container->version : 0;
1224 sipe_send_container_members_prepare(container_id, version, "add", type, value, &container_xmls);
1227 if (container_xmls) {
1228 sipe_send_set_container_members(sipe_private, container_xmls);
1230 g_free(container_xmls);
1233 static void
1234 free_publication(struct sipe_publication *publication)
1236 g_free(publication->category);
1237 g_free(publication->cal_event_hash);
1238 g_free(publication->note);
1240 g_free(publication->working_hours_xml_str);
1241 g_free(publication->fb_start_str);
1242 g_free(publication->free_busy_base64);
1244 g_free(publication);
1247 /* key is <category><instance><container> */
1248 static gboolean
1249 sipe_is_our_publication(struct sipe_core_private *sipe_private,
1250 const gchar *key)
1252 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1253 GSList *entry;
1255 /* filling keys for our publications if not yet cached */
1256 if (!sip->our_publication_keys) {
1257 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
1258 guint machine_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
1259 guint user_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER);
1260 guint calendar_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
1261 guint cal_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF);
1262 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
1263 guint note_oof_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF);
1265 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
1266 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance, device_instance);
1267 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance, machine_instance);
1268 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance, user_instance);
1269 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance, calendar_instance);
1270 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance, cal_oof_instance);
1271 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance, cal_data_instance);
1272 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance, note_oof_instance);
1273 SIPE_DEBUG_INFO("\tNote : %u", 0);
1274 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
1276 /* device */
1277 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1278 g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2));
1280 /* state:machineState */
1281 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1282 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 2));
1283 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1284 g_strdup_printf("<%s><%u><%u>", "state", machine_instance, 3));
1286 /* state:userState */
1287 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1288 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 2));
1289 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1290 g_strdup_printf("<%s><%u><%u>", "state", user_instance, 3));
1292 /* state:calendarState */
1293 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1294 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 2));
1295 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1296 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance, 3));
1298 /* state:calendarState OOF */
1299 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1300 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 2));
1301 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1302 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance, 3));
1304 /* note */
1305 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1306 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
1307 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1308 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
1309 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1310 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
1312 /* note OOF */
1313 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1314 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 200));
1315 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1316 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 300));
1317 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1318 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance, 400));
1320 /* calendarData:WorkingHours */
1321 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1322 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
1323 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1324 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
1325 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1326 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
1327 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1328 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
1329 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1330 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
1331 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1332 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
1334 /* calendarData:FreeBusy */
1335 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1336 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1));
1337 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1338 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100));
1339 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1340 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200));
1341 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1342 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300));
1343 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1344 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400));
1345 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
1346 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000));
1348 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
1349 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
1352 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
1354 entry = sip->our_publication_keys;
1355 while (entry) {
1356 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
1357 if (sipe_strequal(entry->data, key)) {
1358 return TRUE;
1360 entry = entry->next;
1362 return FALSE;
1365 /** Property names to store in blist.xml */
1366 #define ALIAS_PROP "alias"
1367 #define EMAIL_PROP "email"
1368 #define PHONE_PROP "phone"
1369 #define PHONE_DISPLAY_PROP "phone-display"
1370 #define PHONE_MOBILE_PROP "phone-mobile"
1371 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
1372 #define PHONE_HOME_PROP "phone-home"
1373 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
1374 #define PHONE_OTHER_PROP "phone-other"
1375 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
1376 #define PHONE_CUSTOM1_PROP "phone-custom1"
1377 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
1378 #define SITE_PROP "site"
1379 #define COMPANY_PROP "company"
1380 #define DEPARTMENT_PROP "department"
1381 #define TITLE_PROP "title"
1382 #define OFFICE_PROP "office"
1383 /** implies work address */
1384 #define ADDRESS_STREET_PROP "address-street"
1385 #define ADDRESS_CITY_PROP "address-city"
1386 #define ADDRESS_STATE_PROP "address-state"
1387 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
1388 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
1391 * Tries to figure out user first and last name
1392 * based on Display Name and email properties.
1394 * Allocates memory - must be g_free()'d
1396 * Examples to parse:
1397 * First Last
1398 * First Last - Company Name
1399 * Last, First
1400 * Last, First M.
1401 * Last, First (C)(STP) (Company)
1402 * first.last@company.com (preprocessed as "first last")
1403 * first.last.company.com@reuters.net (preprocessed as "first last company com")
1405 * Unusable examples:
1406 * user@company.com (preprocessed as "user")
1407 * first.m.last@company.com (preprocessed as "first m last")
1408 * user.company.com@reuters.net (preprocessed as "user company com")
1410 static void
1411 sipe_get_first_last_names(struct sipe_core_private *sipe_private,
1412 const char *uri,
1413 char **first_name,
1414 char **last_name)
1416 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1417 sipe_backend_buddy p_buddy;
1418 char *display_name;
1419 gchar *email;
1420 const char *first, *last;
1421 char *tmp;
1422 char **parts;
1423 gboolean has_comma = FALSE;
1425 if (!sip || !uri) return;
1427 p_buddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL);
1429 if (!p_buddy) return;
1431 display_name = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
1432 email = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, SIPE_BUDDY_INFO_EMAIL);
1434 if (!display_name && !email) return;
1436 /* if no display name, make "first last anything_else" out of email */
1437 if (email && !display_name) {
1438 display_name = g_strndup(email, strstr(email, "@") - email);
1439 display_name = sipe_utils_str_replace((tmp = display_name), ".", " ");
1440 g_free(tmp);
1443 if (display_name) {
1444 has_comma = (strstr(display_name, ",") != NULL);
1445 display_name = sipe_utils_str_replace((tmp = display_name), ", ", " ");
1446 g_free(tmp);
1447 display_name = sipe_utils_str_replace((tmp = display_name), ",", " ");
1448 g_free(tmp);
1451 parts = g_strsplit(display_name, " ", 0);
1453 if (!parts[0] || !parts[1]) {
1454 g_free(email);
1455 g_free(display_name);
1456 g_strfreev(parts);
1457 return;
1460 if (has_comma) {
1461 last = parts[0];
1462 first = parts[1];
1463 } else {
1464 first = parts[0];
1465 last = parts[1];
1468 if (first_name) {
1469 *first_name = g_strstrip(g_strdup(first));
1472 if (last_name) {
1473 *last_name = g_strstrip(g_strdup(last));
1476 g_free(email);
1477 g_free(display_name);
1478 g_strfreev(parts);
1482 * Update user information
1484 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
1485 * @param property_name
1486 * @param property_value may be modified to strip white space
1488 static void
1489 sipe_update_user_info(struct sipe_core_private *sipe_private,
1490 const char *uri,
1491 sipe_buddy_info_fields propkey,
1492 char *property_value)
1494 GSList *buddies, *entry;
1496 if (property_value)
1497 property_value = g_strstrip(property_value);
1499 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
1500 while (entry) {
1501 gchar *prop_str;
1502 gchar *server_alias;
1503 gchar *alias;
1504 sipe_backend_buddy p_buddy = entry->data;
1506 /* for Display Name */
1507 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
1508 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
1509 if (property_value && sipe_is_bad_alias(uri, alias)) {
1510 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
1511 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
1513 g_free(alias);
1515 server_alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
1516 if (!is_empty(property_value) &&
1517 (!sipe_strequal(property_value, server_alias) || is_empty(server_alias)) )
1519 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
1520 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
1522 g_free(server_alias);
1524 /* for other properties */
1525 else {
1526 if (!is_empty(property_value)) {
1527 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
1528 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
1529 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
1531 g_free(prop_str);
1535 entry = entry->next;
1537 g_slist_free(buddies);
1541 * Update user phone
1542 * Suitable for both 2005 and 2007 systems.
1544 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
1545 * @param phone_type
1546 * @param phone may be modified to strip white space
1547 * @param phone_display_string may be modified to strip white space
1549 static void
1550 sipe_update_user_phone(struct sipe_core_private *sipe_private,
1551 const gchar *uri,
1552 const gchar *phone_type,
1553 gchar *phone,
1554 gchar *phone_display_string)
1556 sipe_buddy_info_fields phone_node = SIPE_BUDDY_INFO_WORK_PHONE; /* work phone by default */
1557 sipe_buddy_info_fields phone_display_node = SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY; /* work phone by default */
1559 if(!phone || strlen(phone) == 0) return;
1561 if ((sipe_strequal(phone_type, "mobile") || sipe_strequal(phone_type, "cell"))) {
1562 phone_node = SIPE_BUDDY_INFO_MOBILE_PHONE;
1563 phone_display_node = SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY;
1564 } else if (sipe_strequal(phone_type, "home")) {
1565 phone_node = SIPE_BUDDY_INFO_HOME_PHONE;
1566 phone_display_node = SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY;
1567 } else if (sipe_strequal(phone_type, "other")) {
1568 phone_node = SIPE_BUDDY_INFO_OTHER_PHONE;
1569 phone_display_node = SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY;
1570 } else if (sipe_strequal(phone_type, "custom1")) {
1571 phone_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE;
1572 phone_display_node = SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY;
1575 sipe_update_user_info(sipe_private, uri, phone_node, phone);
1576 if (phone_display_string) {
1577 sipe_update_user_info(sipe_private, uri, phone_display_node, phone_display_string);
1581 void
1582 sipe_core_update_calendar(struct sipe_core_public *sipe_public)
1584 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: started.");
1586 /* Do in parallel.
1587 * If failed, the branch will be disabled for subsequent calls.
1588 * Can't rely that user turned the functionality on in account settings.
1590 sipe_ews_update_calendar(SIPE_CORE_PRIVATE);
1591 #ifdef _WIN32
1592 /* @TODO: UNIX integration missing */
1593 sipe_domino_update_calendar(SIPE_CORE_PRIVATE);
1594 #endif
1596 /* schedule repeat */
1597 sipe_schedule_seconds(SIPE_CORE_PRIVATE,
1598 "<+update-calendar>",
1599 NULL,
1600 UPDATE_CALENDAR_INTERVAL,
1601 (sipe_schedule_action)sipe_core_update_calendar,
1602 NULL);
1604 SIPE_DEBUG_INFO_NOFORMAT("sipe_core_update_calendar: finished.");
1608 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
1609 * by using standard Purple's means of signals and saved statuses.
1611 * Thus all UI elements get updated: Status Button with Note, docklet.
1612 * This is ablolutely important as both our status and note can come
1613 * inbound (roaming) or be updated programmatically (e.g. based on our
1614 * calendar data).
1616 static void
1617 sipe_set_purple_account_status_and_note(const PurpleAccount *account,
1618 const char *status_id,
1619 const char *message,
1620 time_t do_not_publish[])
1622 PurpleStatus *status = purple_account_get_active_status(account);
1623 gboolean changed = TRUE;
1625 if (g_str_equal(status_id, purple_status_get_id(status)) &&
1626 sipe_strequal(message, purple_status_get_attr_string(status, SIPE_STATUS_ATTR_ID_MESSAGE)))
1628 changed = FALSE;
1631 if (purple_savedstatus_is_idleaway()) {
1632 changed = FALSE;
1635 if (changed) {
1636 PurpleSavedStatus *saved_status;
1637 const PurpleStatusType *acct_status_type =
1638 purple_status_type_find_with_id(account->status_types, status_id);
1639 PurpleStatusPrimitive primitive = purple_status_type_get_primitive(acct_status_type);
1640 sipe_activity activity = sipe_get_activity_by_token(status_id);
1642 saved_status = purple_savedstatus_find_transient_by_type_and_message(primitive, message);
1643 if (saved_status) {
1644 purple_savedstatus_set_substatus(saved_status, account, acct_status_type, message);
1647 /* If this type+message is unique then create a new transient saved status
1648 * Ref: gtkstatusbox.c
1650 if (!saved_status) {
1651 GList *tmp;
1652 GList *active_accts = purple_accounts_get_all_active();
1654 saved_status = purple_savedstatus_new(NULL, primitive);
1655 purple_savedstatus_set_message(saved_status, message);
1657 for (tmp = active_accts; tmp != NULL; tmp = tmp->next) {
1658 purple_savedstatus_set_substatus(saved_status,
1659 (PurpleAccount *)tmp->data, acct_status_type, message);
1661 g_list_free(active_accts);
1664 do_not_publish[activity] = time(NULL);
1665 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
1666 status_id, (int)do_not_publish[activity]);
1668 /* Set the status for each account */
1669 purple_savedstatus_activate(saved_status);
1673 struct hash_table_delete_payload {
1674 GHashTable *hash_table;
1675 guint container;
1678 static void
1679 sipe_remove_category_container_publications_cb(const char *name,
1680 struct sipe_publication *publication,
1681 struct hash_table_delete_payload *payload)
1683 if (publication->container == payload->container) {
1684 g_hash_table_remove(payload->hash_table, name);
1687 static void
1688 sipe_remove_category_container_publications(GHashTable *our_publications,
1689 const char *category,
1690 guint container)
1692 struct hash_table_delete_payload payload;
1693 payload.hash_table = g_hash_table_lookup(our_publications, category);
1695 if (!payload.hash_table) return;
1697 payload.container = container;
1698 g_hash_table_foreach(payload.hash_table, (GHFunc)sipe_remove_category_container_publications_cb, &payload);
1701 static void
1702 send_publish_category_initial(struct sipe_core_private *sipe_private);
1705 * When we receive some self (BE) NOTIFY with a new subscriber
1706 * we sends a setSubscribers request to him [SIP-PRES] 4.8
1709 void sipe_process_roaming_self(struct sipe_core_private *sipe_private,
1710 struct sipmsg *msg)
1712 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
1713 gchar *contact;
1714 gchar *to;
1715 sipe_xml *xml;
1716 const sipe_xml *node;
1717 const sipe_xml *node2;
1718 char *display_name = NULL;
1719 char *uri;
1720 GSList *category_names = NULL;
1721 int aggreg_avail = 0;
1722 gboolean do_update_status = FALSE;
1723 gboolean has_note_cleaned = FALSE;
1724 GHashTable *devices;
1726 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
1728 xml = sipe_xml_parse(msg->body, msg->bodylen);
1729 if (!xml) return;
1731 contact = get_contact(sipe_private);
1732 to = sip_uri_self(sipe_private);
1734 /* categories */
1735 /* set list of categories participating in this XML */
1736 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
1737 const gchar *name = sipe_xml_attribute(node, "name");
1738 category_names = slist_insert_unique_sorted(category_names, (gchar *)name, (GCompareFunc)strcmp);
1740 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
1741 category_names ? (int) g_slist_length(category_names) : -1);
1742 /* drop category information */
1743 if (category_names) {
1744 GSList *entry = category_names;
1745 while (entry) {
1746 GHashTable *cat_publications;
1747 const gchar *category = entry->data;
1748 entry = entry->next;
1749 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category);
1750 cat_publications = g_hash_table_lookup(sip->our_publications, category);
1751 if (cat_publications) {
1752 g_hash_table_remove(sip->our_publications, category);
1753 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category);
1757 g_slist_free(category_names);
1759 /* filling our categories reflected in roaming data */
1760 devices = g_hash_table_new_full(g_str_hash, g_str_equal,
1761 g_free, NULL);
1762 for (node = sipe_xml_child(xml, "categories/category"); node; node = sipe_xml_twin(node)) {
1763 const char *tmp;
1764 const gchar *name = sipe_xml_attribute(node, "name");
1765 guint container = sipe_xml_int_attribute(node, "container", -1);
1766 guint instance = sipe_xml_int_attribute(node, "instance", -1);
1767 guint version = sipe_xml_int_attribute(node, "version", 0);
1768 time_t publish_time = (tmp = sipe_xml_attribute(node, "publishTime")) ?
1769 sipe_utils_str_to_time(tmp) : 0;
1770 gchar *key;
1771 GHashTable *cat_publications = g_hash_table_lookup(sip->our_publications, name);
1773 /* Ex. clear note: <category name="note"/> */
1774 if (container == (guint)-1) {
1775 g_free(sip->note);
1776 sip->note = NULL;
1777 do_update_status = TRUE;
1778 continue;
1781 /* Ex. clear note: <category name="note" container="200"/> */
1782 if (instance == (guint)-1) {
1783 if (container == 200) {
1784 g_free(sip->note);
1785 sip->note = NULL;
1786 do_update_status = TRUE;
1788 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name, container);
1789 sipe_remove_category_container_publications(
1790 sip->our_publications, name, container);
1791 continue;
1794 /* key is <category><instance><container> */
1795 key = g_strdup_printf("<%s><%u><%u>", name, instance, container);
1796 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key, version);
1798 /* capture all userState publication for later clean up if required */
1799 if (sipe_strequal(name, "state") && (container == 2 || container == 3)) {
1800 const sipe_xml *xn_state = sipe_xml_child(node, "state");
1802 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "userState")) {
1803 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
1804 publication->category = g_strdup(name);
1805 publication->instance = instance;
1806 publication->container = container;
1807 publication->version = version;
1809 if (!sip->user_state_publications) {
1810 sip->user_state_publications = g_hash_table_new_full(
1811 g_str_hash, g_str_equal,
1812 g_free, (GDestroyNotify)free_publication);
1814 g_hash_table_insert(sip->user_state_publications, g_strdup(key), publication);
1815 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
1816 key, version);
1820 /* count each client instance only once */
1821 if (sipe_strequal(name, "device"))
1822 g_hash_table_replace(devices, g_strdup_printf("%u", instance), NULL);
1824 if (sipe_is_our_publication(sipe_private, key)) {
1825 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
1827 publication->category = g_strdup(name);
1828 publication->instance = instance;
1829 publication->container = container;
1830 publication->version = version;
1832 /* filling publication->availability */
1833 if (sipe_strequal(name, "state")) {
1834 const sipe_xml *xn_state = sipe_xml_child(node, "state");
1835 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
1837 if (xn_avail) {
1838 gchar *avail_str = sipe_xml_data(xn_avail);
1839 if (avail_str) {
1840 publication->availability = atoi(avail_str);
1842 g_free(avail_str);
1844 /* for calendarState */
1845 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "calendarState")) {
1846 const sipe_xml *xn_activity = sipe_xml_child(xn_state, "activity");
1847 struct sipe_cal_event *event = g_new0(struct sipe_cal_event, 1);
1849 event->start_time = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "startTime"));
1850 if (xn_activity) {
1851 if (sipe_strequal(sipe_xml_attribute(xn_activity, "token"),
1852 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token))
1854 event->is_meeting = TRUE;
1857 event->subject = sipe_xml_data(sipe_xml_child(xn_state, "meetingSubject"));
1858 event->location = sipe_xml_data(sipe_xml_child(xn_state, "meetingLocation"));
1860 publication->cal_event_hash = sipe_cal_event_hash(event);
1861 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
1862 publication->cal_event_hash);
1863 sipe_cal_event_free(event);
1866 /* filling publication->note */
1867 if (sipe_strequal(name, "note")) {
1868 const sipe_xml *xn_body = sipe_xml_child(node, "note/body");
1870 if (!has_note_cleaned) {
1871 has_note_cleaned = TRUE;
1873 g_free(sip->note);
1874 sip->note = NULL;
1875 sip->note_since = publish_time;
1877 do_update_status = TRUE;
1880 g_free(publication->note);
1881 publication->note = NULL;
1882 if (xn_body) {
1883 char *tmp;
1885 publication->note = g_markup_escape_text((tmp = sipe_xml_data(xn_body)), -1);
1886 g_free(tmp);
1887 if (publish_time >= sip->note_since) {
1888 g_free(sip->note);
1889 sip->note = g_strdup(publication->note);
1890 sip->note_since = publish_time;
1891 sip->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_body, "type"), "OOF");
1893 do_update_status = TRUE;
1898 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
1899 if (sipe_strequal(name, "calendarData") && (publication->container == 300)) {
1900 const sipe_xml *xn_free_busy = sipe_xml_child(node, "calendarData/freeBusy");
1901 const sipe_xml *xn_working_hours = sipe_xml_child(node, "calendarData/WorkingHours");
1902 if (xn_free_busy) {
1903 publication->fb_start_str = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
1904 publication->free_busy_base64 = sipe_xml_data(xn_free_busy);
1906 if (xn_working_hours) {
1907 publication->working_hours_xml_str = sipe_xml_stringify(xn_working_hours);
1911 if (!cat_publications) {
1912 cat_publications = g_hash_table_new_full(
1913 g_str_hash, g_str_equal,
1914 g_free, (GDestroyNotify)free_publication);
1915 g_hash_table_insert(sip->our_publications, g_strdup(name), cat_publications);
1916 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name);
1918 g_hash_table_insert(cat_publications, g_strdup(key), publication);
1919 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key, version);
1921 g_free(key);
1923 /* aggregateState (not an our publication) from 2-nd container */
1924 if (sipe_strequal(name, "state") && container == 2) {
1925 const sipe_xml *xn_state = sipe_xml_child(node, "state");
1927 if (xn_state && sipe_strequal(sipe_xml_attribute(xn_state, "type"), "aggregateState")) {
1928 const sipe_xml *xn_avail = sipe_xml_child(xn_state, "availability");
1930 if (xn_avail) {
1931 gchar *avail_str = sipe_xml_data(xn_avail);
1932 if (avail_str) {
1933 aggreg_avail = atoi(avail_str);
1935 g_free(avail_str);
1938 do_update_status = TRUE;
1942 /* userProperties published by server from AD */
1943 if (!sip->csta && sipe_strequal(name, "userProperties")) {
1944 const sipe_xml *line;
1945 /* line, for Remote Call Control (RCC) */
1946 for (line = sipe_xml_child(node, "userProperties/lines/line"); line; line = sipe_xml_twin(line)) {
1947 const gchar *line_server = sipe_xml_attribute(line, "lineServer");
1948 const gchar *line_type = sipe_xml_attribute(line, "lineType");
1949 gchar *line_uri;
1951 if (!line_server || !(sipe_strequal(line_type, "Rcc") || sipe_strequal(line_type, "Dual"))) continue;
1953 line_uri = sipe_xml_data(line);
1954 if (line_uri) {
1955 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri, line_server);
1956 sip_csta_open(sipe_private, line_uri, line_server);
1958 g_free(line_uri);
1960 break;
1964 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
1965 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
1967 /* active clients for user account */
1968 if (g_hash_table_size(devices) > 1) {
1969 SIPE_CORE_PRIVATE_FLAG_SET(MPOP);
1970 SIPE_DEBUG_INFO("sipe_process_roaming_self: multiple clients detected (%d)",
1971 g_hash_table_size(devices));
1972 } else {
1973 SIPE_CORE_PRIVATE_FLAG_UNSET(MPOP);
1974 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self: single client detected");
1976 g_hash_table_destroy(devices);
1978 /* containers */
1979 for (node = sipe_xml_child(xml, "containers/container"); node; node = sipe_xml_twin(node)) {
1980 guint id = sipe_xml_int_attribute(node, "id", 0);
1981 struct sipe_container *container = sipe_find_container(sipe_private, id);
1983 if (container) {
1984 sip->containers = g_slist_remove(sip->containers, container);
1985 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container->id, container->version);
1986 free_container(container);
1988 container = g_new0(struct sipe_container, 1);
1989 container->id = id;
1990 container->version = sipe_xml_int_attribute(node, "version", 0);
1991 sip->containers = g_slist_append(sip->containers, container);
1992 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container->id, container->version);
1994 for (node2 = sipe_xml_child(node, "member"); node2; node2 = sipe_xml_twin(node2)) {
1995 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
1996 member->type = g_strdup(sipe_xml_attribute(node2, "type"));
1997 member->value = g_strdup(sipe_xml_attribute(node2, "value"));
1998 container->members = g_slist_append(container->members, member);
1999 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
2000 member->type, member->value ? member->value : "");
2004 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip->access_level_set ? "TRUE" : "FALSE");
2005 if (!sip->access_level_set && sipe_xml_child(xml, "containers")) {
2006 char *container_xmls = NULL;
2007 int sameEnterpriseAL = sipe_find_access_level(sipe_private, "sameEnterprise", NULL, NULL);
2008 int federatedAL = sipe_find_access_level(sipe_private, "federated", NULL, NULL);
2010 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL);
2011 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL);
2012 /* initial set-up to let counterparties see your status */
2013 if (sameEnterpriseAL < 0) {
2014 struct sipe_container *container = sipe_find_container(sipe_private, 200);
2015 guint version = container ? container->version : 0;
2016 sipe_send_container_members_prepare(200, version, "add", "sameEnterprise", NULL, &container_xmls);
2018 if (federatedAL < 0) {
2019 struct sipe_container *container = sipe_find_container(sipe_private, 100);
2020 guint version = container ? container->version : 0;
2021 sipe_send_container_members_prepare(100, version, "add", "federated", NULL, &container_xmls);
2023 sip->access_level_set = TRUE;
2025 if (container_xmls) {
2026 sipe_send_set_container_members(sipe_private, container_xmls);
2028 g_free(container_xmls);
2031 /* Refresh contacts' blocked status */
2032 sipe_refresh_blocked_status(sipe_private);
2034 /* subscribers */
2035 for (node = sipe_xml_child(xml, "subscribers/subscriber"); node; node = sipe_xml_twin(node)) {
2036 const char *user;
2037 const char *acknowledged;
2038 gchar *hdr;
2039 gchar *body;
2041 user = sipe_xml_attribute(node, "user"); /* without 'sip:' prefix */
2042 if (!user) continue;
2043 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user);
2044 display_name = g_strdup(sipe_xml_attribute(node, "displayName"));
2045 uri = sip_uri_from_name(user);
2047 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2049 acknowledged= sipe_xml_attribute(node, "acknowledged");
2050 if(sipe_strcase_equal(acknowledged,"false")){
2051 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user);
2052 if (!sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL)) {
2053 sipe_backend_buddy_request_add(SIPE_CORE_PUBLIC, uri, display_name);
2056 hdr = g_strdup_printf(
2057 "Contact: %s\r\n"
2058 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2060 body = g_strdup_printf(
2061 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2062 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2063 "</setSubscribers>", user);
2065 sip_transport_service(sipe_private,
2067 hdr,
2068 body,
2069 NULL);
2070 g_free(body);
2071 g_free(hdr);
2073 g_free(display_name);
2074 g_free(uri);
2077 g_free(contact);
2078 sipe_xml_free(xml);
2080 /* Publish initial state if not yet.
2081 * Assuming this happens on initial responce to subscription to roaming-self
2082 * so we've already updated our roaming data in full.
2083 * Only for 2007+
2085 if (!sip->initial_state_published) {
2086 send_publish_category_initial(sipe_private);
2087 sipe_groupchat_init(sipe_private);
2088 sip->initial_state_published = TRUE;
2089 /* dalayed run */
2090 sipe_schedule_seconds(sipe_private,
2091 "<+update-calendar>",
2092 NULL,
2093 UPDATE_CALENDAR_DELAY,
2094 (sipe_schedule_action)sipe_core_update_calendar,
2095 NULL);
2096 do_update_status = FALSE;
2097 } else if (aggreg_avail) {
2099 g_free(sip->status);
2100 if (aggreg_avail && aggreg_avail < 18000) { /* not offline */
2101 sip->status = g_strdup(sipe_get_status_by_availability(aggreg_avail, NULL));
2102 } else {
2103 sip->status = g_strdup(SIPE_STATUS_ID_INVISIBLE); /* not not let offline status switch us off */
2107 if (do_update_status) {
2108 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip->status);
2109 sipe_set_purple_account_status_and_note(sip->account, sip->status, sip->note, sip->do_not_publish);
2112 g_free(to);
2115 /* IM Session (INVITE and MESSAGE methods) */
2117 static gboolean
2118 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2119 struct sipmsg *msg,
2120 SIPE_UNUSED_PARAMETER struct transaction *trans)
2122 gboolean ret = TRUE;
2124 if (msg->response != 200) {
2125 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg->response);
2126 return FALSE;
2129 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg->body ? msg->body : "");
2131 return ret;
2135 * Asks UA/proxy about its capabilities.
2137 static void sipe_options_request(struct sipe_core_private *sipe_private,
2138 const char *who)
2140 gchar *to = sip_uri(who);
2141 gchar *contact = get_contact(sipe_private);
2142 gchar *request = g_strdup_printf(
2143 "Accept: application/sdp\r\n"
2144 "Contact: %s\r\n", contact);
2145 g_free(contact);
2147 sip_transport_request(sipe_private,
2148 "OPTIONS",
2151 request,
2152 NULL,
2153 NULL,
2154 process_options_response);
2156 g_free(to);
2157 g_free(request);
2160 void
2161 sipe_convo_closed(PurpleConnection * gc, const char *who)
2163 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
2165 SIPE_DEBUG_INFO("conversation with %s closed", who);
2166 sipe_session_close(sipe_private,
2167 sipe_session_find_im(sipe_private, who));
2171 * Returns 2005-style activity and Availability.
2173 * @param status Sipe statis id.
2175 static void
2176 sipe_get_act_avail_by_status_2005(const char *status,
2177 int *activity,
2178 int *availability)
2180 int avail = 300; /* online */
2181 int act = 400; /* Available */
2183 if (sipe_strequal(status, SIPE_STATUS_ID_AWAY)) {
2184 act = 100;
2185 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
2186 // act = 150;
2187 } else if (sipe_strequal(status, SIPE_STATUS_ID_BRB)) {
2188 act = 300;
2189 } else if (sipe_strequal(status, SIPE_STATUS_ID_AVAILABLE)) {
2190 act = 400;
2191 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
2192 // act = 500;
2193 } else if (sipe_strequal(status, SIPE_STATUS_ID_BUSY) ||
2194 sipe_strequal(status, SIPE_STATUS_ID_DND)) {
2195 act = 600;
2196 } else if (sipe_strequal(status, SIPE_STATUS_ID_INVISIBLE) ||
2197 sipe_strequal(status, SIPE_STATUS_ID_OFFLINE)) {
2198 avail = 0; /* offline */
2199 act = 100;
2200 } else {
2201 act = 400; /* Available */
2204 if (activity) *activity = act;
2205 if (availability) *availability = avail;
2209 * [MS-SIP] 2.2.1
2211 * @param activity 2005 aggregated activity. Ex.: 600
2212 * @param availablity 2005 aggregated availablity. Ex.: 300
2214 static const char *
2215 sipe_get_status_by_act_avail_2005(const int activity,
2216 const int availablity,
2217 char **activity_desc)
2219 const char *status_id = NULL;
2220 const char *act = NULL;
2222 if (activity < 150) {
2223 status_id = SIPE_STATUS_ID_AWAY;
2224 } else if (activity < 200) {
2225 //status_id = SIPE_STATUS_ID_LUNCH;
2226 status_id = SIPE_STATUS_ID_AWAY;
2227 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH);
2228 } else if (activity < 300) {
2229 //status_id = SIPE_STATUS_ID_IDLE;
2230 status_id = SIPE_STATUS_ID_AWAY;
2231 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
2232 } else if (activity < 400) {
2233 status_id = SIPE_STATUS_ID_BRB;
2234 } else if (activity < 500) {
2235 status_id = SIPE_STATUS_ID_AVAILABLE;
2236 } else if (activity < 600) {
2237 //status_id = SIPE_STATUS_ID_ON_PHONE;
2238 status_id = SIPE_STATUS_ID_BUSY;
2239 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE);
2240 } else if (activity < 700) {
2241 status_id = SIPE_STATUS_ID_BUSY;
2242 } else if (activity < 800) {
2243 status_id = SIPE_STATUS_ID_AWAY;
2244 } else {
2245 status_id = SIPE_STATUS_ID_AVAILABLE;
2248 if (availablity < 100)
2249 status_id = SIPE_STATUS_ID_OFFLINE;
2251 if (activity_desc && act) {
2252 g_free(*activity_desc);
2253 *activity_desc = g_strdup(act);
2256 return status_id;
2260 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
2262 static const char*
2263 sipe_get_status_by_availability(int avail,
2264 char** activity_desc)
2266 const char *status;
2267 const char *act = NULL;
2269 if (avail < 3000) {
2270 status = SIPE_STATUS_ID_OFFLINE;
2271 } else if (avail < 4500) {
2272 status = SIPE_STATUS_ID_AVAILABLE;
2273 } else if (avail < 6000) {
2274 //status = SIPE_STATUS_ID_IDLE;
2275 status = SIPE_STATUS_ID_AVAILABLE;
2276 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE);
2277 } else if (avail < 7500) {
2278 status = SIPE_STATUS_ID_BUSY;
2279 } else if (avail < 9000) {
2280 //status = SIPE_STATUS_ID_BUSYIDLE;
2281 status = SIPE_STATUS_ID_BUSY;
2282 act = SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE);
2283 } else if (avail < 12000) {
2284 status = SIPE_STATUS_ID_DND;
2285 } else if (avail < 15000) {
2286 status = SIPE_STATUS_ID_BRB;
2287 } else if (avail < 18000) {
2288 status = SIPE_STATUS_ID_AWAY;
2289 } else {
2290 status = SIPE_STATUS_ID_OFFLINE;
2293 if (activity_desc && act) {
2294 g_free(*activity_desc);
2295 *activity_desc = g_strdup(act);
2298 return status;
2302 * Returns 2007-style availability value
2304 * @param sipe_status_id (in)
2305 * @param activity_token (out) Must be g_free()'d after use if consumed.
2307 static int
2308 sipe_get_availability_by_status(const char* sipe_status_id, char** activity_token)
2310 int availability;
2311 sipe_activity activity = SIPE_ACTIVITY_UNSET;
2313 if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AWAY)) {
2314 availability = 15500;
2315 if (!activity_token || !(*activity_token)) {
2316 activity = SIPE_ACTIVITY_AWAY;
2318 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BRB)) {
2319 availability = 12500;
2320 activity = SIPE_ACTIVITY_BRB;
2321 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_DND)) {
2322 availability = 9500;
2323 activity = SIPE_ACTIVITY_DND;
2324 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_BUSY)) {
2325 availability = 6500;
2326 if (!activity_token || !(*activity_token)) {
2327 activity = SIPE_ACTIVITY_BUSY;
2329 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_AVAILABLE)) {
2330 availability = 3500;
2331 activity = SIPE_ACTIVITY_ONLINE;
2332 } else if (sipe_strequal(sipe_status_id, SIPE_STATUS_ID_UNKNOWN)) {
2333 availability = 0;
2334 } else {
2335 // Offline or invisible
2336 availability = 18500;
2337 activity = SIPE_ACTIVITY_OFFLINE;
2340 if (activity_token) {
2341 *activity_token = g_strdup(sipe_activity_map[activity].token);
2343 return availability;
2346 void process_incoming_notify_rlmi(struct sipe_core_private *sipe_private,
2347 const gchar *data,
2348 unsigned len)
2350 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2351 const char *uri;
2352 sipe_xml *xn_categories;
2353 const sipe_xml *xn_category;
2354 const char *status = NULL;
2355 gboolean do_update_status = FALSE;
2356 gboolean has_note_cleaned = FALSE;
2357 gboolean has_free_busy_cleaned = FALSE;
2359 xn_categories = sipe_xml_parse(data, len);
2360 uri = sipe_xml_attribute(xn_categories, "uri"); /* with 'sip:' prefix */
2362 for (xn_category = sipe_xml_child(xn_categories, "category");
2363 xn_category ;
2364 xn_category = sipe_xml_twin(xn_category) )
2366 const sipe_xml *xn_node;
2367 const char *tmp;
2368 const char *attrVar = sipe_xml_attribute(xn_category, "name");
2369 time_t publish_time = (tmp = sipe_xml_attribute(xn_category, "publishTime")) ?
2370 sipe_utils_str_to_time(tmp) : 0;
2372 /* contactCard */
2373 if (sipe_strequal(attrVar, "contactCard"))
2375 const sipe_xml *card = sipe_xml_child(xn_category, "contactCard");
2377 if (card) {
2378 const sipe_xml *node;
2379 /* identity - Display Name and email */
2380 node = sipe_xml_child(card, "identity");
2381 if (node) {
2382 char* display_name = sipe_xml_data(
2383 sipe_xml_child(node, "name/displayName"));
2384 char* email = sipe_xml_data(
2385 sipe_xml_child(node, "email"));
2387 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2388 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
2390 g_free(display_name);
2391 g_free(email);
2393 /* company */
2394 node = sipe_xml_child(card, "company");
2395 if (node) {
2396 char* company = sipe_xml_data(node);
2397 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COMPANY, company);
2398 g_free(company);
2400 /* department */
2401 node = sipe_xml_child(card, "department");
2402 if (node) {
2403 char* department = sipe_xml_data(node);
2404 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DEPARTMENT, department);
2405 g_free(department);
2407 /* title */
2408 node = sipe_xml_child(card, "title");
2409 if (node) {
2410 char* title = sipe_xml_data(node);
2411 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_JOB_TITLE, title);
2412 g_free(title);
2414 /* office */
2415 node = sipe_xml_child(card, "office");
2416 if (node) {
2417 char* office = sipe_xml_data(node);
2418 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_OFFICE, office);
2419 g_free(office);
2421 /* site (url) */
2422 node = sipe_xml_child(card, "url");
2423 if (node) {
2424 char* site = sipe_xml_data(node);
2425 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_SITE, site);
2426 g_free(site);
2428 /* phone */
2429 for (node = sipe_xml_child(card, "phone");
2430 node;
2431 node = sipe_xml_twin(node))
2433 const char *phone_type = sipe_xml_attribute(node, "type");
2434 char* phone = sipe_xml_data(sipe_xml_child(node, "uri"));
2435 char* phone_display_string = sipe_xml_data(sipe_xml_child(node, "displayString"));
2437 sipe_update_user_phone(sipe_private, uri, phone_type, phone, phone_display_string);
2439 g_free(phone);
2440 g_free(phone_display_string);
2442 /* address */
2443 for (node = sipe_xml_child(card, "address");
2444 node;
2445 node = sipe_xml_twin(node))
2447 if (sipe_strequal(sipe_xml_attribute(node, "type"), "work")) {
2448 char* street = sipe_xml_data(sipe_xml_child(node, "street"));
2449 char* city = sipe_xml_data(sipe_xml_child(node, "city"));
2450 char* state = sipe_xml_data(sipe_xml_child(node, "state"));
2451 char* zipcode = sipe_xml_data(sipe_xml_child(node, "zipcode"));
2452 char* country_code = sipe_xml_data(sipe_xml_child(node, "countryCode"));
2454 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STREET, street);
2455 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_CITY, city);
2456 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_STATE, state);
2457 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_ZIPCODE, zipcode);
2458 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_COUNTRY, country_code);
2460 g_free(street);
2461 g_free(city);
2462 g_free(state);
2463 g_free(zipcode);
2464 g_free(country_code);
2466 break;
2471 /* note */
2472 else if (sipe_strequal(attrVar, "note"))
2474 if (uri) {
2475 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
2477 if (!has_note_cleaned) {
2478 has_note_cleaned = TRUE;
2480 g_free(sbuddy->note);
2481 sbuddy->note = NULL;
2482 sbuddy->is_oof_note = FALSE;
2483 sbuddy->note_since = publish_time;
2485 do_update_status = TRUE;
2487 if (sbuddy && (publish_time >= sbuddy->note_since)) {
2488 /* clean up in case no 'note' element is supplied
2489 * which indicate note removal in client
2491 g_free(sbuddy->note);
2492 sbuddy->note = NULL;
2493 sbuddy->is_oof_note = FALSE;
2494 sbuddy->note_since = publish_time;
2496 xn_node = sipe_xml_child(xn_category, "note/body");
2497 if (xn_node) {
2498 char *tmp;
2499 sbuddy->note = g_markup_escape_text((tmp = sipe_xml_data(xn_node)), -1);
2500 g_free(tmp);
2501 sbuddy->is_oof_note = sipe_strequal(sipe_xml_attribute(xn_node, "type"), "OOF");
2502 sbuddy->note_since = publish_time;
2504 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
2505 uri, sbuddy->note ? sbuddy->note : "");
2507 /* to trigger UI refresh in case no status info is supplied in this update */
2508 do_update_status = TRUE;
2512 /* state */
2513 else if(sipe_strequal(attrVar, "state"))
2515 char *tmp;
2516 int availability;
2517 const sipe_xml *xn_availability;
2518 const sipe_xml *xn_activity;
2519 const sipe_xml *xn_meeting_subject;
2520 const sipe_xml *xn_meeting_location;
2521 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
2523 xn_node = sipe_xml_child(xn_category, "state");
2524 if (!xn_node) continue;
2525 xn_availability = sipe_xml_child(xn_node, "availability");
2526 if (!xn_availability) continue;
2527 xn_activity = sipe_xml_child(xn_node, "activity");
2528 xn_meeting_subject = sipe_xml_child(xn_node, "meetingSubject");
2529 xn_meeting_location = sipe_xml_child(xn_node, "meetingLocation");
2531 tmp = sipe_xml_data(xn_availability);
2532 availability = atoi(tmp);
2533 g_free(tmp);
2535 /* activity, meeting_subject, meeting_location */
2536 if (sbuddy) {
2537 char *tmp = NULL;
2539 /* activity */
2540 g_free(sbuddy->activity);
2541 sbuddy->activity = NULL;
2542 if (xn_activity) {
2543 const char *token = sipe_xml_attribute(xn_activity, "token");
2544 const sipe_xml *xn_custom = sipe_xml_child(xn_activity, "custom");
2546 /* from token */
2547 if (!is_empty(token)) {
2548 sbuddy->activity = g_strdup(sipe_get_activity_desc_by_token(token));
2550 /* from custom element */
2551 if (xn_custom) {
2552 char *custom = sipe_xml_data(xn_custom);
2554 if (!is_empty(custom)) {
2555 sbuddy->activity = custom;
2556 custom = NULL;
2558 g_free(custom);
2561 /* meeting_subject */
2562 g_free(sbuddy->meeting_subject);
2563 sbuddy->meeting_subject = NULL;
2564 if (xn_meeting_subject) {
2565 char *meeting_subject = sipe_xml_data(xn_meeting_subject);
2567 if (!is_empty(meeting_subject)) {
2568 sbuddy->meeting_subject = meeting_subject;
2569 meeting_subject = NULL;
2571 g_free(meeting_subject);
2573 /* meeting_location */
2574 g_free(sbuddy->meeting_location);
2575 sbuddy->meeting_location = NULL;
2576 if (xn_meeting_location) {
2577 char *meeting_location = sipe_xml_data(xn_meeting_location);
2579 if (!is_empty(meeting_location)) {
2580 sbuddy->meeting_location = meeting_location;
2581 meeting_location = NULL;
2583 g_free(meeting_location);
2586 status = sipe_get_status_by_availability(availability, &tmp);
2587 if (sbuddy->activity && tmp) {
2588 char *tmp2 = sbuddy->activity;
2590 sbuddy->activity = g_strdup_printf("%s, %s", sbuddy->activity, tmp);
2591 g_free(tmp);
2592 g_free(tmp2);
2593 } else if (tmp) {
2594 sbuddy->activity = tmp;
2598 do_update_status = TRUE;
2600 /* calendarData */
2601 else if(sipe_strequal(attrVar, "calendarData"))
2603 struct sipe_buddy *sbuddy = uri ? g_hash_table_lookup(sipe_private->buddies, uri) : NULL;
2604 const sipe_xml *xn_free_busy = sipe_xml_child(xn_category, "calendarData/freeBusy");
2605 const sipe_xml *xn_working_hours = sipe_xml_child(xn_category, "calendarData/WorkingHours");
2607 if (sbuddy && xn_free_busy) {
2608 if (!has_free_busy_cleaned) {
2609 has_free_busy_cleaned = TRUE;
2611 g_free(sbuddy->cal_start_time);
2612 sbuddy->cal_start_time = NULL;
2614 g_free(sbuddy->cal_free_busy_base64);
2615 sbuddy->cal_free_busy_base64 = NULL;
2617 g_free(sbuddy->cal_free_busy);
2618 sbuddy->cal_free_busy = NULL;
2620 sbuddy->cal_free_busy_published = publish_time;
2623 if (publish_time >= sbuddy->cal_free_busy_published) {
2624 g_free(sbuddy->cal_start_time);
2625 sbuddy->cal_start_time = g_strdup(sipe_xml_attribute(xn_free_busy, "startTime"));
2627 sbuddy->cal_granularity = sipe_strcase_equal(sipe_xml_attribute(xn_free_busy, "granularity"), "PT15M") ?
2628 15 : 0;
2630 g_free(sbuddy->cal_free_busy_base64);
2631 sbuddy->cal_free_busy_base64 = sipe_xml_data(xn_free_busy);
2633 g_free(sbuddy->cal_free_busy);
2634 sbuddy->cal_free_busy = NULL;
2636 sbuddy->cal_free_busy_published = publish_time;
2638 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);
2642 if (sbuddy && xn_working_hours) {
2643 sipe_cal_parse_working_hours(xn_working_hours, sbuddy);
2648 if (do_update_status) {
2649 if (!status) { /* no status category in this update, using contact's current status */
2650 PurpleBuddy *pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
2651 const PurplePresence *presence = purple_buddy_get_presence(pbuddy);
2652 const PurpleStatus *pstatus = purple_presence_get_active_status(presence);
2653 status = purple_status_get_id(pstatus);
2656 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status);
2657 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status);
2660 sipe_xml_free(xn_categories);
2663 void sipe_subscribe_poolfqdn_resource_uri(const char *host,
2664 GSList *server,
2665 struct sipe_core_private *sipe_private)
2667 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
2668 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host);
2669 payload->host = g_strdup(host);
2670 payload->buddies = server;
2671 sipe_subscribe_presence_batched_routed(sipe_private,
2672 payload);
2673 sipe_subscribe_presence_batched_routed_free(payload);
2676 void process_incoming_notify_pidf(struct sipe_core_private *sipe_private,
2677 const gchar *data,
2678 unsigned len)
2680 gchar *uri;
2681 gchar *getbasic;
2682 gchar *activity = NULL;
2683 sipe_xml *pidf;
2684 const sipe_xml *basicstatus = NULL, *tuple, *status;
2685 gboolean isonline = FALSE;
2686 const sipe_xml *display_name_node;
2688 pidf = sipe_xml_parse(data, len);
2689 if (!pidf) {
2690 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data);
2691 return;
2694 if ((tuple = sipe_xml_child(pidf, "tuple")))
2696 if ((status = sipe_xml_child(tuple, "status"))) {
2697 basicstatus = sipe_xml_child(status, "basic");
2701 if (!basicstatus) {
2702 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
2703 sipe_xml_free(pidf);
2704 return;
2707 getbasic = sipe_xml_data(basicstatus);
2708 if (!getbasic) {
2709 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
2710 sipe_xml_free(pidf);
2711 return;
2714 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic);
2715 if (strstr(getbasic, "open")) {
2716 isonline = TRUE;
2718 g_free(getbasic);
2720 uri = sip_uri(sipe_xml_attribute(pidf, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
2722 display_name_node = sipe_xml_child(pidf, "display-name");
2723 if (display_name_node) {
2724 char * display_name = sipe_xml_data(display_name_node);
2726 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2727 g_free(display_name);
2730 if ((tuple = sipe_xml_child(pidf, "tuple"))) {
2731 if ((status = sipe_xml_child(tuple, "status"))) {
2732 if ((basicstatus = sipe_xml_child(status, "activities"))) {
2733 if ((basicstatus = sipe_xml_child(basicstatus, "activity"))) {
2734 activity = sipe_xml_data(basicstatus);
2735 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity);
2741 if (isonline) {
2742 const gchar * status_id = NULL;
2743 if (activity) {
2744 if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_BUSY].token)) {
2745 status_id = SIPE_STATUS_ID_BUSY;
2746 } else if (sipe_strequal(activity, sipe_activity_map[SIPE_ACTIVITY_AWAY].token)) {
2747 status_id = SIPE_STATUS_ID_AWAY;
2751 if (!status_id) {
2752 status_id = SIPE_STATUS_ID_AVAILABLE;
2755 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id);
2756 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
2757 } else {
2758 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, SIPE_STATUS_ID_OFFLINE);
2761 g_free(activity);
2762 g_free(uri);
2763 sipe_xml_free(pidf);
2766 /** 2005 */
2767 static void
2768 sipe_user_info_has_updated(struct sipe_core_private *sipe_private,
2769 const sipe_xml *xn_userinfo)
2771 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2772 const sipe_xml *xn_states;
2774 g_free(sip->user_states);
2775 sip->user_states = NULL;
2776 if ((xn_states = sipe_xml_child(xn_userinfo, "states")) != NULL) {
2777 gchar *orig = sip->user_states = sipe_xml_stringify(xn_states);
2779 /* this is a hack-around to remove added newline after inner element,
2780 * state in this case, where it shouldn't be.
2781 * After several use of sipe_xml_stringify, amount of added newlines
2782 * grows significantly.
2784 if (orig) {
2785 gchar c, *stripped = orig;
2786 while ((c = *orig++)) {
2787 if ((c != '\n') /* && (c != '\r') */) {
2788 *stripped++ = c;
2791 *stripped = '\0';
2795 /* Publish initial state if not yet.
2796 * Assuming this happens on initial responce to self subscription
2797 * so we've already updated our UserInfo.
2799 if (!sip->initial_state_published) {
2800 send_presence_soap(sipe_private, FALSE);
2801 /* dalayed run */
2802 sipe_schedule_seconds(sipe_private,
2803 "<+update-calendar>",
2804 NULL,
2805 UPDATE_CALENDAR_DELAY,
2806 (sipe_schedule_action) sipe_core_update_calendar,
2807 NULL);
2811 void process_incoming_notify_msrtc(struct sipe_core_private *sipe_private,
2812 const gchar *data,
2813 unsigned len)
2815 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
2816 char *activity = NULL;
2817 const char *epid;
2818 const char *status_id = NULL;
2819 const char *name;
2820 char *uri;
2821 char *self_uri = sip_uri_self(sipe_private);
2822 int avl;
2823 int act;
2824 const char *device_name = NULL;
2825 const char *cal_start_time = NULL;
2826 const char *cal_granularity = NULL;
2827 char *cal_free_busy_base64 = NULL;
2828 struct sipe_buddy *sbuddy;
2829 const sipe_xml *node;
2830 sipe_xml *xn_presentity;
2831 const sipe_xml *xn_availability;
2832 const sipe_xml *xn_activity;
2833 const sipe_xml *xn_display_name;
2834 const sipe_xml *xn_email;
2835 const sipe_xml *xn_phone_number;
2836 const sipe_xml *xn_userinfo;
2837 const sipe_xml *xn_note;
2838 const sipe_xml *xn_oof;
2839 const sipe_xml *xn_state;
2840 const sipe_xml *xn_contact;
2841 char *note;
2842 int user_avail;
2843 const char *user_avail_nil;
2844 int res_avail;
2845 time_t user_avail_since = 0;
2846 time_t activity_since = 0;
2848 /* fix for Reuters environment on Linux */
2849 if (data && strstr(data, "encoding=\"utf-16\"")) {
2850 char *tmp_data;
2851 tmp_data = replace(data, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
2852 xn_presentity = sipe_xml_parse(tmp_data, strlen(tmp_data));
2853 g_free(tmp_data);
2854 } else {
2855 xn_presentity = sipe_xml_parse(data, len);
2858 xn_availability = sipe_xml_child(xn_presentity, "availability");
2859 xn_activity = sipe_xml_child(xn_presentity, "activity");
2860 xn_display_name = sipe_xml_child(xn_presentity, "displayName");
2861 xn_email = sipe_xml_child(xn_presentity, "email");
2862 xn_phone_number = sipe_xml_child(xn_presentity, "phoneNumber");
2863 xn_userinfo = sipe_xml_child(xn_presentity, "userInfo");
2864 xn_oof = xn_userinfo ? sipe_xml_child(xn_userinfo, "oof") : NULL;
2865 xn_state = xn_userinfo ? sipe_xml_child(xn_userinfo, "states/state"): NULL;
2866 user_avail = xn_state ? sipe_xml_int_attribute(xn_state, "avail", 0) : 0;
2867 user_avail_since = xn_state ? sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since")) : 0;
2868 user_avail_nil = xn_state ? sipe_xml_attribute(xn_state, "nil") : NULL;
2869 xn_contact = xn_userinfo ? sipe_xml_child(xn_userinfo, "contact") : NULL;
2870 xn_note = xn_userinfo ? sipe_xml_child(xn_userinfo, "note") : NULL;
2871 note = xn_note ? sipe_xml_data(xn_note) : NULL;
2873 if (sipe_strequal(user_avail_nil, "true")) { /* null-ed */
2874 user_avail = 0;
2875 user_avail_since = 0;
2878 name = sipe_xml_attribute(xn_presentity, "uri"); /* without 'sip:' prefix */
2879 uri = sip_uri_from_name(name);
2880 avl = sipe_xml_int_attribute(xn_availability, "aggregate", 0);
2881 epid = sipe_xml_attribute(xn_availability, "epid");
2882 act = sipe_xml_int_attribute(xn_activity, "aggregate", 0);
2884 status_id = sipe_get_status_by_act_avail_2005(act, avl, &activity);
2885 res_avail = sipe_get_availability_by_status(status_id, NULL);
2886 if (user_avail > res_avail) {
2887 res_avail = user_avail;
2888 status_id = sipe_get_status_by_availability(user_avail, NULL);
2891 if (xn_display_name) {
2892 char *display_name = g_strdup(sipe_xml_attribute(xn_display_name, "displayName"));
2893 char *email = xn_email ? g_strdup(sipe_xml_attribute(xn_email, "email")) : NULL;
2894 char *phone_label = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "label")) : NULL;
2895 char *phone_number = xn_phone_number ? g_strdup(sipe_xml_attribute(xn_phone_number, "number")) : NULL;
2896 char *tel_uri = sip_to_tel_uri(phone_number);
2898 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, display_name);
2899 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
2900 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
2901 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, !is_empty(phone_label) ? phone_label : phone_number);
2903 g_free(tel_uri);
2904 g_free(phone_label);
2905 g_free(phone_number);
2906 g_free(email);
2907 g_free(display_name);
2910 if (xn_contact) {
2911 /* tel */
2912 for (node = sipe_xml_child(xn_contact, "tel"); node; node = sipe_xml_twin(node))
2914 /* Ex.: <tel type="work">tel:+3222220000</tel> */
2915 const char *phone_type = sipe_xml_attribute(node, "type");
2916 char* phone = sipe_xml_data(node);
2918 sipe_update_user_phone(sipe_private, uri, phone_type, phone, NULL);
2920 g_free(phone);
2924 /* devicePresence */
2925 for (node = sipe_xml_child(xn_presentity, "devices/devicePresence"); node; node = sipe_xml_twin(node)) {
2926 const sipe_xml *xn_device_name;
2927 const sipe_xml *xn_calendar_info;
2928 const sipe_xml *xn_state;
2929 char *state;
2931 /* deviceName */
2932 if (sipe_strequal(sipe_xml_attribute(node, "epid"), epid)) {
2933 xn_device_name = sipe_xml_child(node, "deviceName");
2934 device_name = xn_device_name ? sipe_xml_attribute(xn_device_name, "name") : NULL;
2937 /* calendarInfo */
2938 xn_calendar_info = sipe_xml_child(node, "calendarInfo");
2939 if (xn_calendar_info) {
2940 const char *cal_start_time_tmp = sipe_xml_attribute(xn_calendar_info, "startTime");
2942 if (cal_start_time) {
2943 time_t cal_start_time_t = sipe_utils_str_to_time(cal_start_time);
2944 time_t cal_start_time_t_tmp = sipe_utils_str_to_time(cal_start_time_tmp);
2946 if (cal_start_time_t_tmp > cal_start_time_t) {
2947 cal_start_time = cal_start_time_tmp;
2948 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
2949 g_free(cal_free_busy_base64);
2950 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
2952 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);
2954 } else {
2955 cal_start_time = cal_start_time_tmp;
2956 cal_granularity = sipe_xml_attribute(xn_calendar_info, "granularity");
2957 g_free(cal_free_busy_base64);
2958 cal_free_busy_base64 = sipe_xml_data(xn_calendar_info);
2960 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);
2964 /* state */
2965 xn_state = sipe_xml_child(node, "states/state");
2966 if (xn_state) {
2967 int dev_avail = sipe_xml_int_attribute(xn_state, "avail", 0);
2968 time_t dev_avail_since = sipe_utils_str_to_time(sipe_xml_attribute(xn_state, "since"));
2970 state = sipe_xml_data(xn_state);
2971 if (dev_avail_since > user_avail_since &&
2972 dev_avail >= res_avail)
2974 res_avail = dev_avail;
2975 if (!is_empty(state))
2977 if (sipe_strequal(state, sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].token)) {
2978 g_free(activity);
2979 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE));
2980 } else if (sipe_strequal(state, "presenting")) {
2981 g_free(activity);
2982 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF));
2983 } else {
2984 activity = state;
2985 state = NULL;
2987 activity_since = dev_avail_since;
2989 status_id = sipe_get_status_by_availability(res_avail, &activity);
2991 g_free(state);
2995 /* oof */
2996 if (xn_oof && res_avail >= 15000) { /* 12000 in 2007 */
2997 g_free(activity);
2998 activity = g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF));
2999 activity_since = 0;
3002 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
3003 if (sbuddy)
3005 g_free(sbuddy->activity);
3006 sbuddy->activity = activity;
3007 activity = NULL;
3009 sbuddy->activity_since = activity_since;
3011 sbuddy->user_avail = user_avail;
3012 sbuddy->user_avail_since = user_avail_since;
3014 g_free(sbuddy->note);
3015 sbuddy->note = NULL;
3016 if (!is_empty(note)) { sbuddy->note = g_markup_escape_text(note, -1); }
3018 sbuddy->is_oof_note = (xn_oof != NULL);
3020 g_free(sbuddy->device_name);
3021 sbuddy->device_name = NULL;
3022 if (!is_empty(device_name)) { sbuddy->device_name = g_strdup(device_name); }
3024 if (!is_empty(cal_free_busy_base64)) {
3025 g_free(sbuddy->cal_start_time);
3026 sbuddy->cal_start_time = g_strdup(cal_start_time);
3028 sbuddy->cal_granularity = sipe_strcase_equal(cal_granularity, "PT15M") ? 15 : 0;
3030 g_free(sbuddy->cal_free_busy_base64);
3031 sbuddy->cal_free_busy_base64 = cal_free_busy_base64;
3032 cal_free_busy_base64 = NULL;
3034 g_free(sbuddy->cal_free_busy);
3035 sbuddy->cal_free_busy = NULL;
3038 sbuddy->last_non_cal_status_id = status_id;
3039 g_free(sbuddy->last_non_cal_activity);
3040 sbuddy->last_non_cal_activity = g_strdup(sbuddy->activity);
3042 if (sipe_strcase_equal(sbuddy->name, self_uri)) {
3043 if (!sipe_strequal(sbuddy->note, sip->note)) /* not same */
3045 sip->is_oof_note = sbuddy->is_oof_note;
3047 g_free(sip->note);
3048 sip->note = g_strdup(sbuddy->note);
3050 sip->note_since = time(NULL);
3053 g_free(sip->status);
3054 sip->status = g_strdup(sbuddy->last_non_cal_status_id);
3057 g_free(cal_free_busy_base64);
3058 g_free(activity);
3060 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id);
3061 sipe_core_buddy_got_status(SIPE_CORE_PUBLIC, uri, status_id);
3063 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007) && sipe_strcase_equal(self_uri, uri)) {
3064 sipe_user_info_has_updated(sipe_private, xn_userinfo);
3067 g_free(note);
3068 sipe_xml_free(xn_presentity);
3069 g_free(uri);
3070 g_free(self_uri);
3073 static void sipe_presence_timeout_mime_cb(gpointer user_data,
3074 SIPE_UNUSED_PARAMETER const GSList *fields,
3075 const gchar *body,
3076 gsize length)
3078 GSList **buddies = user_data;
3079 sipe_xml *xml = sipe_xml_parse(body, length);
3081 if (xml && !sipe_strequal(sipe_xml_name(xml), "list")) {
3082 const gchar *uri = sipe_xml_attribute(xml, "uri");
3083 const sipe_xml *xn_category;
3086 * automaton: presence is never expected to change
3088 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
3090 for (xn_category = sipe_xml_child(xml, "category");
3091 xn_category;
3092 xn_category = sipe_xml_twin(xn_category)) {
3093 if (sipe_strequal(sipe_xml_attribute(xn_category, "name"),
3094 "contactCard")) {
3095 const sipe_xml *node = sipe_xml_child(xn_category, "contactCard/automaton");
3096 if (node) {
3097 char *boolean = sipe_xml_data(node);
3098 if (sipe_strequal(boolean, "true")) {
3099 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
3100 uri);
3101 uri = NULL;
3103 g_free(boolean);
3105 break;
3109 if (uri) {
3110 *buddies = g_slist_append(*buddies, sip_uri(uri));
3114 sipe_xml_free(xml);
3117 void sipe_process_presence_timeout(struct sipe_core_private *sipe_private,
3118 struct sipmsg *msg,
3119 const gchar *who,
3120 int timeout)
3122 const char *ctype = sipmsg_find_header(msg, "Content-Type");
3123 gchar *action_name = sipe_utils_presence_key(who);
3125 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype ? ctype : "");
3127 if (ctype &&
3128 strstr(ctype, "multipart") &&
3129 (strstr(ctype, "application/rlmi+xml") ||
3130 strstr(ctype, "application/msrtc-event-categories+xml"))) {
3131 GSList *buddies = NULL;
3133 sipe_mime_parts_foreach(ctype, msg->body, sipe_presence_timeout_mime_cb, &buddies);
3135 if (buddies) {
3136 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
3137 payload->host = g_strdup(who);
3138 payload->buddies = buddies;
3139 sipe_schedule_seconds(sipe_private,
3140 action_name,
3141 payload,
3142 timeout,
3143 sipe_subscribe_presence_batched_routed,
3144 sipe_subscribe_presence_batched_routed_free);
3145 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who, timeout);
3148 } else {
3149 sipe_schedule_seconds(sipe_private,
3150 action_name,
3151 g_strdup(who),
3152 timeout,
3153 sipe_subscribe_presence_single,
3154 g_free);
3155 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who, timeout);
3157 g_free(action_name);
3161 * Whether user manually changed status or
3162 * it was changed automatically due to user
3163 * became inactive/active again
3165 static gboolean
3166 sipe_is_user_state(struct sipe_core_private *sipe_private)
3168 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3169 gboolean res;
3170 time_t now = time(NULL);
3172 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip->idle_switch))));
3173 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now)));
3175 res = ((now - SIPE_IDLE_SET_DELAY * 2) >= sip->idle_switch);
3177 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res ? "USER" : "MACHINE");
3178 return res;
3182 * OCS2005 presence XML messages
3184 * Calendar publication entry
3186 * @param legacy_dn (%s) Ex.: /o=EXCHANGE/ou=BTUK02/cn=Recipients/cn=AHHBTT
3187 * @param fb_start_time_str (%s) Ex.: 2009-12-06T17:15:00Z
3188 * @param free_busy_base64 (%s) Ex.: AAAAAAAAAAAAAAAAA......
3190 #define SIPE_SOAP_SET_PRESENCE_CALENDAR \
3191 "<calendarInfo xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\" mailboxId=\"%s\" startTime=\"%s\" granularity=\"PT15M\">%s</calendarInfo>"
3194 * Note publication entry
3196 * @param note (%s) Ex.: Working from home
3198 #define SIPE_SOAP_SET_PRESENCE_NOTE_XML "<note>%s</note>"
3201 * Note's OOF publication entry
3203 #define SIPE_SOAP_SET_PRESENCE_OOF_XML "<oof></oof>"
3206 * States publication entry for User State
3208 * @param avail (%d) Availability 2007-style. Ex.: 9500
3209 * @param since_time_str (%s) Ex.: 2010-01-13T10:30:05Z
3210 * @param device_id (%s) epid. Ex.: 4c77e6ec72
3211 * @param activity_token (%s) Ex.: do-not-disturb
3213 #define SIPE_SOAP_SET_PRESENCE_STATES \
3214 "<states>"\
3215 "<state avail=\"%d\" since=\"%s\" validWith=\"any-device\" deviceId=\"%s\" set=\"manual\" xsi:type=\"userState\">%s</state>"\
3216 "</states>"
3219 * Presentity publication entry.
3221 * @param uri (%s) SIP URI without 'sip:' prefix. Ex.: fox@atlanta.local
3222 * @param aggr_availability (%d) Ex.: 300
3223 * @param aggr_activity (%d) Ex.: 600
3224 * @param host_name (%s) Uppercased. Ex.: ATLANTA
3225 * @param note_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_NOTE_XML
3226 * @param oof_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_OOF_XML
3227 * @param states_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_STATES
3228 * @param calendar_info_xml_str (%s) XML string as SIPE_SOAP_SET_PRESENCE_CALENDAR
3229 * @param device_id (%s) epid. Ex.: 4c77e6ec72
3230 * @param since_time_str (%s) Ex.: 2010-01-13T10:30:05Z
3231 * @param since_time_str (%s) Ex.: 2010-01-13T10:30:05Z
3232 * @param user_input (%s) active, idle
3234 #define SIPE_SOAP_SET_PRESENCE \
3235 "<s:Envelope" \
3236 " xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"" \
3237 " xmlns:m=\"http://schemas.microsoft.com/winrtc/2002/11/sip\"" \
3238 ">" \
3239 "<s:Body>" \
3240 "<m:setPresence>" \
3241 "<m:presentity xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" m:uri=\"sip:%s\">"\
3242 "<m:availability m:aggregate=\"%d\"/>"\
3243 "<m:activity m:aggregate=\"%d\"/>"\
3244 "<deviceName xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\" name=\"%s\"/>"\
3245 "<rtc:devicedata xmlns:rtc=\"http://schemas.microsoft.com/winrtc/2002/11/sip\" namespace=\"rtcService\">"\
3246 "<![CDATA[<caps><renders_gif/><renders_isf/></caps>]]></rtc:devicedata>"\
3247 "<userInfo xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\">"\
3248 "%s%s" \
3249 "%s" \
3250 "</userInfo>"\
3251 "%s" \
3252 "<device xmlns=\"http://schemas.microsoft.com/2002/09/sip/presence\" deviceId=\"%s\" since=\"%s\" >"\
3253 "<userInput since=\"%s\" >%s</userInput>"\
3254 "</device>"\
3255 "</m:presentity>" \
3256 "</m:setPresence>"\
3257 "</s:Body>" \
3258 "</s:Envelope>"
3260 static void
3261 send_presence_soap0(struct sipe_core_private *sipe_private,
3262 gboolean do_publish_calendar,
3263 gboolean do_reset_status)
3265 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3266 struct sipe_calendar* cal = sip->cal;
3267 int availability = 0;
3268 int activity = 0;
3269 gchar *body;
3270 gchar *tmp;
3271 gchar *tmp2 = NULL;
3272 gchar *res_note = NULL;
3273 gchar *res_oof = NULL;
3274 const gchar *note_pub = NULL;
3275 gchar *states = NULL;
3276 gchar *calendar_data = NULL;
3277 gchar *epid = get_epid(sipe_private);
3278 gchar *from = sip_uri_self(sipe_private);
3279 time_t now = time(NULL);
3280 gchar *since_time_str = sipe_utils_time_to_str(now);
3281 const gchar *oof_note = cal ? sipe_ews_get_oof_note(cal) : NULL;
3282 const char *user_input;
3283 gboolean pub_oof = cal && oof_note && (!sip->note || cal->updated > sip->note_since);
3285 if (oof_note && sip->note) {
3286 SIPE_DEBUG_INFO("cal->oof_start : %s", asctime(localtime(&(cal->oof_start))));
3287 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip->note_since))));
3290 SIPE_DEBUG_INFO("sip->note : %s", sip->note ? sip->note : "");
3292 if (!sip->initial_state_published ||
3293 do_reset_status)
3295 g_free(sip->status);
3296 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
3299 sipe_get_act_avail_by_status_2005(sip->status, &activity, &availability);
3301 /* Note */
3302 if (pub_oof) {
3303 note_pub = oof_note;
3304 res_oof = SIPE_SOAP_SET_PRESENCE_OOF_XML;
3305 cal->published = TRUE;
3306 } else if (sip->note) {
3307 if (sip->is_oof_note && !oof_note) { /* stale OOF note, as it's not present in cal already */
3308 g_free(sip->note);
3309 sip->note = NULL;
3310 sip->is_oof_note = FALSE;
3311 sip->note_since = 0;
3312 } else {
3313 note_pub = sip->note;
3314 res_oof = sip->is_oof_note ? SIPE_SOAP_SET_PRESENCE_OOF_XML : "";
3318 if (note_pub)
3320 /* to protocol internal plain text format */
3321 tmp = sipe_backend_markup_strip_html(note_pub);
3322 res_note = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML, tmp);
3323 g_free(tmp);
3326 /* User State */
3327 if (!do_reset_status) {
3328 if (sipe_is_user_state(sipe_private) && !do_publish_calendar && sip->initial_state_published)
3330 gchar *activity_token = NULL;
3331 int avail_2007 = sipe_get_availability_by_status(sip->status, &activity_token);
3333 states = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES,
3334 avail_2007,
3335 since_time_str,
3336 epid,
3337 activity_token);
3338 g_free(activity_token);
3340 else /* preserve existing publication */
3342 if (sip->user_states) {
3343 states = g_strdup(sip->user_states);
3346 } else {
3347 /* do nothing - then User state will be erased */
3349 sip->initial_state_published = TRUE;
3351 /* CalendarInfo */
3352 if (cal && (!is_empty(cal->legacy_dn) || !is_empty(cal->email)) && cal->fb_start && !is_empty(cal->free_busy))
3354 char *fb_start_str = sipe_utils_time_to_str(cal->fb_start);
3355 char *free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
3356 calendar_data = g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR,
3357 !is_empty(cal->legacy_dn) ? cal->legacy_dn : cal->email,
3358 fb_start_str,
3359 free_busy_base64);
3360 g_free(fb_start_str);
3361 g_free(free_busy_base64);
3364 user_input = (sipe_is_user_state(sipe_private) ||
3365 sipe_strequal(sip->status, SIPE_STATUS_ID_AVAILABLE)) ?
3366 "active" : "idle";
3368 /* forming resulting XML */
3369 body = g_strdup_printf(SIPE_SOAP_SET_PRESENCE,
3370 sipe_private->username,
3371 availability,
3372 activity,
3373 (tmp = g_ascii_strup(g_get_host_name(), -1)),
3374 res_note ? res_note : "",
3375 res_oof ? res_oof : "",
3376 states ? states : "",
3377 calendar_data ? calendar_data : "",
3378 epid,
3379 since_time_str,
3380 since_time_str,
3381 user_input);
3382 g_free(tmp);
3383 g_free(tmp2);
3384 g_free(res_note);
3385 g_free(states);
3386 g_free(calendar_data);
3387 g_free(since_time_str);
3388 g_free(epid);
3390 sip_soap_raw_request_cb(sipe_private, from, body, NULL, NULL);
3392 g_free(body);
3395 void
3396 send_presence_soap(struct sipe_core_private *sipe_private,
3397 gboolean do_publish_calendar)
3399 return send_presence_soap0(sipe_private, do_publish_calendar, FALSE);
3403 static gboolean
3404 process_send_presence_category_publish_response(struct sipe_core_private *sipe_private,
3405 struct sipmsg *msg,
3406 struct transaction *trans)
3408 const gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3410 if (msg->response == 409 && g_str_has_prefix(contenttype, "application/msrtc-fault+xml")) {
3411 sipe_xml *xml;
3412 const sipe_xml *node;
3413 gchar *fault_code;
3414 GHashTable *faults;
3415 int index_our;
3416 gboolean has_device_publication = FALSE;
3418 xml = sipe_xml_parse(msg->body, msg->bodylen);
3420 /* test if version mismatch fault */
3421 fault_code = sipe_xml_data(sipe_xml_child(xml, "Faultcode"));
3422 if (!sipe_strequal(fault_code, "Client.BadCall.WrongDelta")) {
3423 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code);
3424 g_free(fault_code);
3425 sipe_xml_free(xml);
3426 return TRUE;
3428 g_free(fault_code);
3430 /* accumulating information about faulty versions */
3431 faults = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
3432 for (node = sipe_xml_child(xml, "details/operation");
3433 node;
3434 node = sipe_xml_twin(node))
3436 const gchar *index = sipe_xml_attribute(node, "index");
3437 const gchar *curVersion = sipe_xml_attribute(node, "curVersion");
3439 g_hash_table_insert(faults, g_strdup(index), g_strdup(curVersion));
3440 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index, curVersion);
3442 sipe_xml_free(xml);
3444 /* here we are parsing our own request to figure out what publication
3445 * referenced here only by index went wrong
3447 xml = sipe_xml_parse(trans->msg->body, trans->msg->bodylen);
3449 /* publication */
3450 for (node = sipe_xml_child(xml, "publications/publication"),
3451 index_our = 1; /* starts with 1 - our first publication */
3452 node;
3453 node = sipe_xml_twin(node), index_our++)
3455 gchar *idx = g_strdup_printf("%d", index_our);
3456 const gchar *curVersion = g_hash_table_lookup(faults, idx);
3457 const gchar *categoryName = sipe_xml_attribute(node, "categoryName");
3458 g_free(idx);
3460 if (sipe_strequal("device", categoryName)) {
3461 has_device_publication = TRUE;
3464 if (curVersion) { /* fault exist on this index */
3465 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3466 const gchar *container = sipe_xml_attribute(node, "container");
3467 const gchar *instance = sipe_xml_attribute(node, "instance");
3468 /* key is <category><instance><container> */
3469 gchar *key = g_strdup_printf("<%s><%s><%s>", categoryName, instance, container);
3470 GHashTable *category = g_hash_table_lookup(sip->our_publications, categoryName);
3472 if (category) {
3473 struct sipe_publication *publication =
3474 g_hash_table_lookup(category, key);
3476 SIPE_DEBUG_INFO("key is %s", key);
3478 if (publication) {
3479 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
3480 key, curVersion, publication->version);
3481 /* updating publication's version to the correct one */
3482 publication->version = atoi(curVersion);
3484 } else {
3485 /* We somehow lost this category from our publications... */
3486 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
3487 publication->category = g_strdup(categoryName);
3488 publication->instance = atoi(instance);
3489 publication->container = atoi(container);
3490 publication->version = atoi(curVersion);
3491 category = g_hash_table_new_full(g_str_hash, g_str_equal,
3492 g_free, (GDestroyNotify)free_publication);
3493 g_hash_table_insert(category, g_strdup(key), publication);
3494 g_hash_table_insert(sip->our_publications, g_strdup(categoryName), category);
3495 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName, key);
3497 g_free(key);
3500 sipe_xml_free(xml);
3501 g_hash_table_destroy(faults);
3503 /* rebublishing with right versions */
3504 if (has_device_publication) {
3505 send_publish_category_initial(sipe_private);
3506 } else {
3507 send_presence_status(sipe_private, NULL);
3510 return TRUE;
3514 * Publishes 'device' category.
3515 * @param instance (%u) Ex.: 1938468728
3516 * @param version (%u) Ex.: 1
3517 * @param endpointId (%s) Ex.: C707E38E-1E10-5413-94D9-ECAC260A0269
3518 * @param uri (%s) Self URI. Ex.: sip:alice7@boston.local
3519 * @param timezone (%s) Ex.: 00:00:00+01:00
3520 * @param machineName (%s) Ex.: BOSTON-OCS07
3522 #define SIPE_PUB_XML_DEVICE \
3523 "<publication categoryName=\"device\" instance=\"%u\" container=\"2\" version=\"%u\" expireType=\"endpoint\">"\
3524 "<device xmlns=\"http://schemas.microsoft.com/2006/09/sip/device\" endpointId=\"%s\">"\
3525 "<capabilities preferred=\"false\" uri=\"%s\">"\
3526 "<text capture=\"true\" render=\"true\" publish=\"false\"/>"\
3527 "<gifInk capture=\"false\" render=\"true\" publish=\"false\"/>"\
3528 "<isfInk capture=\"false\" render=\"true\" publish=\"false\"/>"\
3529 "</capabilities>"\
3530 "<timezone>%s</timezone>"\
3531 "<machineName>%s</machineName>"\
3532 "</device>"\
3533 "</publication>"
3536 * Returns 'device' XML part for publication.
3537 * Must be g_free'd after use.
3539 static gchar *
3540 sipe_publish_get_category_device(struct sipe_core_private *sipe_private)
3542 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3543 gchar *uri;
3544 gchar *doc;
3545 gchar *uuid = get_uuid(sipe_private);
3546 guint device_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_DEVICE);
3547 /* key is <category><instance><container> */
3548 gchar *key = g_strdup_printf("<%s><%u><%u>", "device", device_instance, 2);
3549 struct sipe_publication *publication =
3550 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "device"), key);
3552 g_free(key);
3554 uri = sip_uri_self(sipe_private);
3555 doc = g_strdup_printf(SIPE_PUB_XML_DEVICE,
3556 device_instance,
3557 publication ? publication->version : 0,
3558 uuid,
3559 uri,
3560 "00:00:00+01:00", /* @TODO make timezone real*/
3561 g_get_host_name()
3564 g_free(uri);
3565 g_free(uuid);
3567 return doc;
3571 * Publishes 'machineState' category.
3572 * @param instance (%u) Ex.: 926460663
3573 * @param version (%u) Ex.: 22
3574 * @param availability (%d) Ex.: 3500
3575 * @param instance (%u) Ex.: 926460663
3576 * @param version (%u) Ex.: 22
3577 * @param availability (%d) Ex.: 3500
3579 #define SIPE_PUB_XML_STATE_MACHINE \
3580 "<publication categoryName=\"state\" instance=\"%u\" container=\"2\" version=\"%u\" expireType=\"endpoint\">"\
3581 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"false\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"machineState\">"\
3582 "<availability>%d</availability>"\
3583 "<endpointLocation/>"\
3584 "</state>"\
3585 "</publication>"\
3586 "<publication categoryName=\"state\" instance=\"%u\" container=\"3\" version=\"%u\" expireType=\"endpoint\">"\
3587 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"false\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"machineState\">"\
3588 "<availability>%d</availability>"\
3589 "<endpointLocation/>"\
3590 "</state>"\
3591 "</publication>"
3594 * Publishes 'userState' category.
3595 * @param instance (%u) User. Ex.: 536870912
3596 * @param version (%u) User Container 2. Ex.: 22
3597 * @param availability (%d) User Container 2. Ex.: 15500
3598 * @param instance (%u) User. Ex.: 536870912
3599 * @param version (%u) User Container 3.Ex.: 22
3600 * @param availability (%d) User Container 3. Ex.: 15500
3602 #define SIPE_PUB_XML_STATE_USER \
3603 "<publication categoryName=\"state\" instance=\"%u\" container=\"2\" version=\"%u\" expireType=\"static\">"\
3604 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"true\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"userState\">"\
3605 "<availability>%d</availability>"\
3606 "<endpointLocation/>"\
3607 "</state>"\
3608 "</publication>"\
3609 "<publication categoryName=\"state\" instance=\"%u\" container=\"3\" version=\"%u\" expireType=\"static\">"\
3610 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"true\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"userState\">"\
3611 "<availability>%d</availability>"\
3612 "<endpointLocation/>"\
3613 "</state>"\
3614 "</publication>"
3617 * A service method - use
3618 * - send_publish_get_category_state_machine and
3619 * - send_publish_get_category_state_user instead.
3620 * Must be g_free'd after use.
3622 static gchar *
3623 sipe_publish_get_category_state(struct sipe_core_private *sipe_private,
3624 gboolean is_user_state)
3626 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3627 int availability = sipe_get_availability_by_status(sip->status, NULL);
3628 guint instance = is_user_state ? sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_USER) :
3629 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_MACHINE);
3630 /* key is <category><instance><container> */
3631 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
3632 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
3633 struct sipe_publication *publication_2 =
3634 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
3635 struct sipe_publication *publication_3 =
3636 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
3638 g_free(key_2);
3639 g_free(key_3);
3641 if (publication_2 && (publication_2->availability == availability))
3643 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
3644 return NULL; /* nothing to update */
3647 return g_strdup_printf( is_user_state ? SIPE_PUB_XML_STATE_USER : SIPE_PUB_XML_STATE_MACHINE,
3648 instance,
3649 publication_2 ? publication_2->version : 0,
3650 availability,
3651 instance,
3652 publication_3 ? publication_3->version : 0,
3653 availability);
3657 * An availability XML entry for SIPE_PUB_XML_STATE_CALENDAR
3658 * @param availability (%d) Ex.: 6500
3660 #define SIPE_PUB_XML_STATE_CALENDAR_AVAIL \
3661 "<availability>%d</availability>"
3663 * An activity XML entry for SIPE_PUB_XML_STATE_CALENDAR
3664 * @param token (%s) Ex.: in-a-meeting
3665 * @param minAvailability_attr (%s) Ex.: minAvailability="6500"
3666 * @param maxAvailability_attr (%s) Ex.: maxAvailability="8999" or none
3668 #define SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY \
3669 "<activity token=\"%s\" %s %s></activity>"
3671 * Publishes 'calendarState' category.
3672 * @param instance (%u) Ex.: 1339299275
3673 * @param version (%u) Ex.: 1
3674 * @param uri (%s) Ex.: john@contoso.com
3675 * @param start_time_str (%s) Ex.: 2008-01-11T19:00:00Z
3676 * @param availability (%s) XML string as SIPE_PUB_XML_STATE_CALENDAR_AVAIL
3677 * @param activity (%s) XML string as SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
3678 * @param meeting_subject (%s) Ex.: Customer Meeting
3679 * @param meeting_location (%s) Ex.: Conf Room 100
3681 * @param instance (%u) Ex.: 1339299275
3682 * @param version (%u) Ex.: 1
3683 * @param uri (%s) Ex.: john@contoso.com
3684 * @param start_time_str (%s) Ex.: 2008-01-11T19:00:00Z
3685 * @param availability (%s) XML string as SIPE_PUB_XML_STATE_CALENDAR_AVAIL
3686 * @param activity (%s) XML string as SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
3687 * @param meeting_subject (%s) Ex.: Customer Meeting
3688 * @param meeting_location (%s) Ex.: Conf Room 100
3690 #define SIPE_PUB_XML_STATE_CALENDAR \
3691 "<publication categoryName=\"state\" instance=\"%u\" container=\"2\" version=\"%u\" expireType=\"endpoint\">"\
3692 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"false\" uri=\"%s\" startTime=\"%s\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"calendarState\">"\
3693 "%s"\
3694 "%s"\
3695 "<endpointLocation/>"\
3696 "<meetingSubject>%s</meetingSubject>"\
3697 "<meetingLocation>%s</meetingLocation>"\
3698 "</state>"\
3699 "</publication>"\
3700 "<publication categoryName=\"state\" instance=\"%u\" container=\"3\" version=\"%u\" expireType=\"endpoint\">"\
3701 "<state xmlns=\"http://schemas.microsoft.com/2006/09/sip/state\" manual=\"false\" uri=\"%s\" startTime=\"%s\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"calendarState\">"\
3702 "%s"\
3703 "%s"\
3704 "<endpointLocation/>"\
3705 "<meetingSubject>%s</meetingSubject>"\
3706 "<meetingLocation>%s</meetingLocation>"\
3707 "</state>"\
3708 "</publication>"
3710 * Publishes to clear 'calendarState' category
3711 * @param instance (%u) Ex.: 1251210982
3712 * @param version (%u) Ex.: 1
3714 #define SIPE_PUB_XML_STATE_CALENDAR_CLEAR \
3715 "<publication categoryName=\"state\" instance=\"%u\" container=\"2\" version=\"%u\" expireType=\"endpoint\" expires=\"0\"/>"\
3716 "<publication categoryName=\"state\" instance=\"%u\" container=\"3\" version=\"%u\" expireType=\"endpoint\" expires=\"0\"/>"
3719 * Publishes to clear any category
3720 * @param category_name (%s) Ex.: state
3721 * @param instance (%u) Ex.: 536870912
3722 * @param container (%u) Ex.: 3
3723 * @param version (%u) Ex.: 1
3724 * @param expireType (%s) Ex.: static
3726 #define SIPE_PUB_XML_PUBLICATION_CLEAR \
3727 "<publication categoryName=\"%s\" instance=\"%u\" container=\"%u\" version=\"%u\" expireType=\"%s\" expires=\"0\"/>"
3730 * Publishes 'note' category.
3731 * @param instance (%u) Ex.: 2135971629; 0 for personal
3732 * @param container (%u) Ex.: 200
3733 * @param version (%u) Ex.: 2
3734 * @param type (%s) Ex.: personal or OOF
3735 * @param startTime_attr (%s) Ex.: startTime="2008-01-11T19:00:00Z"
3736 * @param endTime_attr (%s) Ex.: endTime="2008-01-15T19:00:00Z"
3737 * @param body (%s) Ex.: In the office
3739 #define SIPE_PUB_XML_NOTE \
3740 "<publication categoryName=\"note\" instance=\"%u\" container=\"%u\" version=\"%d\" expireType=\"static\">"\
3741 "<note xmlns=\"http://schemas.microsoft.com/2006/09/sip/note\">"\
3742 "<body type=\"%s\" uri=\"\"%s%s>%s</body>"\
3743 "</note>"\
3744 "</publication>"
3747 * Only Busy and OOF calendar event are published.
3748 * Different instances are used for that.
3750 * Must be g_free'd after use.
3752 static gchar *
3753 sipe_publish_get_category_state_calendar(struct sipe_core_private *sipe_private,
3754 struct sipe_cal_event *event,
3755 const char *uri,
3756 int cal_satus)
3758 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3759 gchar *start_time_str;
3760 int availability = 0;
3761 gchar *res;
3762 gchar *tmp = NULL;
3763 guint instance = (cal_satus == SIPE_CAL_OOF) ?
3764 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR_OOF) :
3765 sipe_get_pub_instance(sipe_private, SIPE_PUB_STATE_CALENDAR);
3767 /* key is <category><instance><container> */
3768 gchar *key_2 = g_strdup_printf("<%s><%u><%u>", "state", instance, 2);
3769 gchar *key_3 = g_strdup_printf("<%s><%u><%u>", "state", instance, 3);
3770 struct sipe_publication *publication_2 =
3771 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_2);
3772 struct sipe_publication *publication_3 =
3773 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "state"), key_3);
3775 g_free(key_2);
3776 g_free(key_3);
3778 if (!publication_3 && !event) { /* was nothing, have nothing, exiting */
3779 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
3780 "Exiting as no publication and no event for cal_satus:%d", cal_satus);
3781 return NULL;
3784 if (event &&
3785 publication_3 &&
3786 (publication_3->availability == availability) &&
3787 sipe_strequal(publication_3->cal_event_hash, (tmp = sipe_cal_event_hash(event))))
3789 g_free(tmp);
3790 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
3791 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus);
3792 return NULL; /* nothing to update */
3794 g_free(tmp);
3796 if (event &&
3797 (event->cal_status == SIPE_CAL_BUSY ||
3798 event->cal_status == SIPE_CAL_OOF))
3800 gchar *availability_xml_str = NULL;
3801 gchar *activity_xml_str = NULL;
3802 gchar *escaped_subject = event->subject ? g_markup_escape_text(event->subject, -1) : NULL;
3803 gchar *escaped_location = event->location ? g_markup_escape_text(event->location, -1) : NULL;
3805 if (event->cal_status == SIPE_CAL_BUSY) {
3806 availability_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL, 6500);
3809 if (event->cal_status == SIPE_CAL_BUSY && event->is_meeting) {
3810 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
3811 sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].token,
3812 "minAvailability=\"6500\"",
3813 "maxAvailability=\"8999\"");
3814 } else if (event->cal_status == SIPE_CAL_OOF) {
3815 activity_xml_str = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY,
3816 sipe_activity_map[SIPE_ACTIVITY_OOF].token,
3817 "minAvailability=\"12000\"",
3818 "");
3820 start_time_str = sipe_utils_time_to_str(event->start_time);
3822 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR,
3823 instance,
3824 publication_2 ? publication_2->version : 0,
3825 uri,
3826 start_time_str,
3827 availability_xml_str ? availability_xml_str : "",
3828 activity_xml_str ? activity_xml_str : "",
3829 escaped_subject ? escaped_subject : "",
3830 escaped_location ? escaped_location : "",
3832 instance,
3833 publication_3 ? publication_3->version : 0,
3834 uri,
3835 start_time_str,
3836 availability_xml_str ? availability_xml_str : "",
3837 activity_xml_str ? activity_xml_str : "",
3838 escaped_subject ? escaped_subject : "",
3839 escaped_location ? escaped_location : ""
3841 g_free(escaped_location);
3842 g_free(escaped_subject);
3843 g_free(start_time_str);
3844 g_free(availability_xml_str);
3845 g_free(activity_xml_str);
3848 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
3850 res = g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR,
3851 instance,
3852 publication_2 ? publication_2->version : 0,
3854 instance,
3855 publication_3 ? publication_3->version : 0
3859 return res;
3863 * Returns 'machineState' XML part for publication.
3864 * Must be g_free'd after use.
3866 static gchar *
3867 sipe_publish_get_category_state_machine(struct sipe_core_private *sipe_private)
3869 return sipe_publish_get_category_state(sipe_private, FALSE);
3873 * Returns 'userState' XML part for publication.
3874 * Must be g_free'd after use.
3876 static gchar *
3877 sipe_publish_get_category_state_user(struct sipe_core_private *sipe_private)
3879 return sipe_publish_get_category_state(sipe_private, TRUE);
3884 * Returns 'note' XML part for publication.
3885 * Must be g_free'd after use.
3887 * Protocol format for Note is plain text.
3889 * @param note a note in Sipe internal HTML format
3890 * @param note_type either personal or OOF
3892 static gchar *
3893 sipe_publish_get_category_note(struct sipe_core_private *sipe_private,
3894 const char *note, /* html */
3895 const char *note_type,
3896 time_t note_start,
3897 time_t note_end)
3899 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
3900 guint instance = sipe_strequal("OOF", note_type) ? sipe_get_pub_instance(sipe_private, SIPE_PUB_NOTE_OOF) : 0;
3901 /* key is <category><instance><container> */
3902 gchar *key_note_200 = g_strdup_printf("<%s><%u><%u>", "note", instance, 200);
3903 gchar *key_note_300 = g_strdup_printf("<%s><%u><%u>", "note", instance, 300);
3904 gchar *key_note_400 = g_strdup_printf("<%s><%u><%u>", "note", instance, 400);
3906 struct sipe_publication *publication_note_200 =
3907 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_200);
3908 struct sipe_publication *publication_note_300 =
3909 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_300);
3910 struct sipe_publication *publication_note_400 =
3911 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "note"), key_note_400);
3913 char *tmp = note ? sipe_backend_markup_strip_html(note) : NULL;
3914 char *n1 = tmp ? g_markup_escape_text(tmp, -1) : NULL;
3915 const char *n2 = publication_note_200 ? publication_note_200->note : NULL;
3916 char *res, *tmp1, *tmp2, *tmp3;
3917 char *start_time_attr;
3918 char *end_time_attr;
3920 g_free(tmp);
3921 tmp = NULL;
3922 g_free(key_note_200);
3923 g_free(key_note_300);
3924 g_free(key_note_400);
3926 /* we even need to republish empty note */
3927 if (sipe_strequal(n1, n2))
3929 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
3930 g_free(n1);
3931 return NULL; /* nothing to update */
3934 start_time_attr = note_start ? g_strdup_printf(" startTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_start))) : NULL;
3935 g_free(tmp);
3936 tmp = NULL;
3937 end_time_attr = note_end ? g_strdup_printf(" endTime=\"%s\"", (tmp = sipe_utils_time_to_str(note_end))) : NULL;
3938 g_free(tmp);
3940 if (n1) {
3941 tmp1 = g_strdup_printf(SIPE_PUB_XML_NOTE,
3942 instance,
3943 200,
3944 publication_note_200 ? publication_note_200->version : 0,
3945 note_type,
3946 start_time_attr ? start_time_attr : "",
3947 end_time_attr ? end_time_attr : "",
3948 n1);
3950 tmp2 = g_strdup_printf(SIPE_PUB_XML_NOTE,
3951 instance,
3952 300,
3953 publication_note_300 ? publication_note_300->version : 0,
3954 note_type,
3955 start_time_attr ? start_time_attr : "",
3956 end_time_attr ? end_time_attr : "",
3957 n1);
3959 tmp3 = g_strdup_printf(SIPE_PUB_XML_NOTE,
3960 instance,
3961 400,
3962 publication_note_400 ? publication_note_400->version : 0,
3963 note_type,
3964 start_time_attr ? start_time_attr : "",
3965 end_time_attr ? end_time_attr : "",
3966 n1);
3967 } else {
3968 tmp1 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
3969 "note",
3970 instance,
3971 200,
3972 publication_note_200 ? publication_note_200->version : 0,
3973 "static");
3974 tmp2 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
3975 "note",
3976 instance,
3977 300,
3978 publication_note_200 ? publication_note_200->version : 0,
3979 "static");
3980 tmp3 = g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR,
3981 "note",
3982 instance,
3983 400,
3984 publication_note_200 ? publication_note_200->version : 0,
3985 "static");
3987 res = g_strconcat(tmp1, tmp2, tmp3, NULL);
3989 g_free(start_time_attr);
3990 g_free(end_time_attr);
3991 g_free(tmp1);
3992 g_free(tmp2);
3993 g_free(tmp3);
3994 g_free(n1);
3996 return res;
4000 * Publishes 'calendarData' category's WorkingHours.
4002 * @param version (%u) Ex.: 1
4003 * @param email (%s) Ex.: alice@cosmo.local
4004 * @param working_hours_xml_str (%s) Ex.: <WorkingHours xmlns=.....
4006 * @param version (%u)
4008 * @param version (%u)
4009 * @param email (%s)
4010 * @param working_hours_xml_str (%s)
4012 * @param version (%u)
4013 * @param email (%s)
4014 * @param working_hours_xml_str (%s)
4016 * @param version (%u)
4017 * @param email (%s)
4018 * @param working_hours_xml_str (%s)
4020 * @param version (%u)
4022 #define SIPE_PUB_XML_WORKING_HOURS \
4023 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"1\" version=\"%d\" expireType=\"static\">"\
4024 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">%s"\
4025 "</calendarData>"\
4026 "</publication>"\
4027 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"100\" version=\"%d\" expireType=\"static\">"\
4028 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\"/>"\
4029 "</publication>"\
4030 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"200\" version=\"%d\" expireType=\"static\">"\
4031 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">%s"\
4032 "</calendarData>"\
4033 "</publication>"\
4034 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"300\" version=\"%d\" expireType=\"static\">"\
4035 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">%s"\
4036 "</calendarData>"\
4037 "</publication>"\
4038 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"400\" version=\"%d\" expireType=\"static\">"\
4039 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">%s"\
4040 "</calendarData>"\
4041 "</publication>"\
4042 "<publication categoryName=\"calendarData\" instance=\"0\" container=\"32000\" version=\"%d\" expireType=\"static\">"\
4043 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\"/>"\
4044 "</publication>"
4047 * Returns 'calendarData' XML part with WorkingHours for publication.
4048 * Must be g_free'd after use.
4050 static gchar *
4051 sipe_publish_get_category_cal_working_hours(struct sipe_core_private *sipe_private)
4053 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4054 struct sipe_calendar* cal = sip->cal;
4056 /* key is <category><instance><container> */
4057 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
4058 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
4059 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
4060 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
4061 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
4062 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
4064 struct sipe_publication *publication_cal_1 =
4065 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4066 struct sipe_publication *publication_cal_100 =
4067 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4068 struct sipe_publication *publication_cal_200 =
4069 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4070 struct sipe_publication *publication_cal_300 =
4071 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4072 struct sipe_publication *publication_cal_400 =
4073 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4074 struct sipe_publication *publication_cal_32000 =
4075 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4077 const char *n1 = cal ? cal->working_hours_xml_str : NULL;
4078 const char *n2 = publication_cal_300 ? publication_cal_300->working_hours_xml_str : NULL;
4080 g_free(key_cal_1);
4081 g_free(key_cal_100);
4082 g_free(key_cal_200);
4083 g_free(key_cal_300);
4084 g_free(key_cal_400);
4085 g_free(key_cal_32000);
4087 if (!cal || is_empty(cal->email) || is_empty(cal->working_hours_xml_str)) {
4088 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
4089 return NULL;
4092 if (sipe_strequal(n1, n2))
4094 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
4095 return NULL; /* nothing to update */
4098 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS,
4099 /* 1 */
4100 publication_cal_1 ? publication_cal_1->version : 0,
4101 cal->email,
4102 cal->working_hours_xml_str,
4103 /* 100 - Public */
4104 publication_cal_100 ? publication_cal_100->version : 0,
4105 /* 200 - Company */
4106 publication_cal_200 ? publication_cal_200->version : 0,
4107 cal->email,
4108 cal->working_hours_xml_str,
4109 /* 300 - Team */
4110 publication_cal_300 ? publication_cal_300->version : 0,
4111 cal->email,
4112 cal->working_hours_xml_str,
4113 /* 400 - Personal */
4114 publication_cal_400 ? publication_cal_400->version : 0,
4115 cal->email,
4116 cal->working_hours_xml_str,
4117 /* 32000 - Blocked */
4118 publication_cal_32000 ? publication_cal_32000->version : 0
4123 * Publishes 'calendarData' category's FreeBusy.
4125 * @param instance (%u) Ex.: 1300372959
4126 * @param version (%u) Ex.: 1
4128 * @param instance (%u) Ex.: 1300372959
4129 * @param version (%u) Ex.: 1
4131 * @param instance (%u) Ex.: 1300372959
4132 * @param version (%u) Ex.: 1
4133 * @param email (%s) Ex.: alice@cosmo.local
4134 * @param fb_start_time_str (%s) Ex.: 2009-12-03T00:00:00Z
4135 * @param free_busy_base64 (%s) Ex.: AAAAAAAAAAAAAAAAAAAAA.....
4137 * @param instance (%u) Ex.: 1300372959
4138 * @param version (%u) Ex.: 1
4139 * @param email (%s) Ex.: alice@cosmo.local
4140 * @param fb_start_time_str (%s) Ex.: 2009-12-03T00:00:00Z
4141 * @param free_busy_base64 (%s) Ex.: AAAAAAAAAAAAAAAAAAAAA.....
4143 * @param instance (%u) Ex.: 1300372959
4144 * @param version (%u) Ex.: 1
4145 * @param email (%s) Ex.: alice@cosmo.local
4146 * @param fb_start_time_str (%s) Ex.: 2009-12-03T00:00:00Z
4147 * @param free_busy_base64 (%s) Ex.: AAAAAAAAAAAAAAAAAAAAA.....
4149 * @param instance (%u) Ex.: 1300372959
4150 * @param version (%u) Ex.: 1
4152 #define SIPE_PUB_XML_FREE_BUSY \
4153 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"1\" version=\"%d\" expireType=\"endpoint\">"\
4154 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\"/>"\
4155 "</publication>"\
4156 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"100\" version=\"%d\" expireType=\"endpoint\">"\
4157 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\"/>"\
4158 "</publication>"\
4159 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"200\" version=\"%d\" expireType=\"endpoint\">"\
4160 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">"\
4161 "<freeBusy startTime=\"%s\" granularity=\"PT15M\" encodingVersion=\"1\">%s</freeBusy>"\
4162 "</calendarData>"\
4163 "</publication>"\
4164 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"300\" version=\"%d\" expireType=\"endpoint\">"\
4165 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">"\
4166 "<freeBusy startTime=\"%s\" granularity=\"PT15M\" encodingVersion=\"1\">%s</freeBusy>"\
4167 "</calendarData>"\
4168 "</publication>"\
4169 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"400\" version=\"%d\" expireType=\"endpoint\">"\
4170 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\" mailboxID=\"%s\">"\
4171 "<freeBusy startTime=\"%s\" granularity=\"PT15M\" encodingVersion=\"1\">%s</freeBusy>"\
4172 "</calendarData>"\
4173 "</publication>"\
4174 "<publication categoryName=\"calendarData\" instance=\"%u\" container=\"32000\" version=\"%d\" expireType=\"endpoint\">"\
4175 "<calendarData xmlns=\"http://schemas.microsoft.com/2006/09/sip/calendarData\"/>"\
4176 "</publication>"
4179 * Returns 'calendarData' XML part with FreeBusy for publication.
4180 * Must be g_free'd after use.
4182 static gchar *
4183 sipe_publish_get_category_cal_free_busy(struct sipe_core_private *sipe_private)
4185 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4186 struct sipe_calendar* cal = sip->cal;
4187 guint cal_data_instance = sipe_get_pub_instance(sipe_private, SIPE_PUB_CALENDAR_DATA);
4188 char *fb_start_str;
4189 char *free_busy_base64;
4190 /* const char *st; */
4191 /* const char *fb; */
4192 char *res;
4194 /* key is <category><instance><container> */
4195 gchar *key_cal_1 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 1);
4196 gchar *key_cal_100 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 100);
4197 gchar *key_cal_200 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 200);
4198 gchar *key_cal_300 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 300);
4199 gchar *key_cal_400 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 400);
4200 gchar *key_cal_32000 = g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance, 32000);
4202 struct sipe_publication *publication_cal_1 =
4203 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_1);
4204 struct sipe_publication *publication_cal_100 =
4205 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_100);
4206 struct sipe_publication *publication_cal_200 =
4207 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_200);
4208 struct sipe_publication *publication_cal_300 =
4209 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_300);
4210 struct sipe_publication *publication_cal_400 =
4211 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_400);
4212 struct sipe_publication *publication_cal_32000 =
4213 g_hash_table_lookup(g_hash_table_lookup(sip->our_publications, "calendarData"), key_cal_32000);
4215 g_free(key_cal_1);
4216 g_free(key_cal_100);
4217 g_free(key_cal_200);
4218 g_free(key_cal_300);
4219 g_free(key_cal_400);
4220 g_free(key_cal_32000);
4222 if (!cal || is_empty(cal->email) || !cal->fb_start || is_empty(cal->free_busy)) {
4223 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
4224 return NULL;
4227 fb_start_str = sipe_utils_time_to_str(cal->fb_start);
4228 free_busy_base64 = sipe_cal_get_freebusy_base64(cal->free_busy);
4230 /* we will rebuplish the same data to refresh publication time,
4231 * so if data from multiple sources, most recent will be choosen
4233 // st = publication_cal_300 ? publication_cal_300->fb_start_str : NULL;
4234 // fb = publication_cal_300 ? publication_cal_300->free_busy_base64 : NULL;
4236 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
4238 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
4239 // g_free(fb_start_str);
4240 // g_free(free_busy_base64);
4241 // return NULL; /* nothing to update */
4244 res = g_strdup_printf(SIPE_PUB_XML_FREE_BUSY,
4245 /* 1 */
4246 cal_data_instance,
4247 publication_cal_1 ? publication_cal_1->version : 0,
4248 /* 100 - Public */
4249 cal_data_instance,
4250 publication_cal_100 ? publication_cal_100->version : 0,
4251 /* 200 - Company */
4252 cal_data_instance,
4253 publication_cal_200 ? publication_cal_200->version : 0,
4254 cal->email,
4255 fb_start_str,
4256 free_busy_base64,
4257 /* 300 - Team */
4258 cal_data_instance,
4259 publication_cal_300 ? publication_cal_300->version : 0,
4260 cal->email,
4261 fb_start_str,
4262 free_busy_base64,
4263 /* 400 - Personal */
4264 cal_data_instance,
4265 publication_cal_400 ? publication_cal_400->version : 0,
4266 cal->email,
4267 fb_start_str,
4268 free_busy_base64,
4269 /* 32000 - Blocked */
4270 cal_data_instance,
4271 publication_cal_32000 ? publication_cal_32000->version : 0
4274 g_free(fb_start_str);
4275 g_free(free_busy_base64);
4276 return res;
4280 * Publishes categories.
4281 * @param uri (%s) Self URI. Ex.: sip:alice7@boston.local
4282 * @param publications (%s) XML publications
4284 #define SIPE_SEND_PRESENCE \
4285 "<publish xmlns=\"http://schemas.microsoft.com/2006/09/sip/rich-presence\">"\
4286 "<publications uri=\"%s\">"\
4287 "%s"\
4288 "</publications>"\
4289 "</publish>"
4291 static void send_presence_publish(struct sipe_core_private *sipe_private,
4292 const char *publications)
4294 gchar *uri;
4295 gchar *doc;
4296 gchar *tmp;
4297 gchar *hdr;
4299 uri = sip_uri_self(sipe_private);
4300 doc = g_strdup_printf(SIPE_SEND_PRESENCE,
4301 uri,
4302 publications);
4304 tmp = get_contact(sipe_private);
4305 hdr = g_strdup_printf("Contact: %s\r\n"
4306 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4308 sip_transport_service(sipe_private,
4309 uri,
4310 hdr,
4311 doc,
4312 process_send_presence_category_publish_response);
4314 g_free(tmp);
4315 g_free(hdr);
4316 g_free(uri);
4317 g_free(doc);
4320 static void
4321 send_publish_category_initial(struct sipe_core_private *sipe_private)
4323 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4324 gchar *pub_device = sipe_publish_get_category_device(sipe_private);
4325 gchar *pub_machine;
4326 gchar *publications;
4328 g_free(sip->status);
4329 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE); /* our initial state */
4331 pub_machine = sipe_publish_get_category_state_machine(sipe_private);
4332 publications = g_strdup_printf("%s%s",
4333 pub_device,
4334 pub_machine ? pub_machine : "");
4335 g_free(pub_device);
4336 g_free(pub_machine);
4338 send_presence_publish(sipe_private, publications);
4339 g_free(publications);
4342 static void
4343 send_presence_category_publish(struct sipe_core_private *sipe_private)
4345 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4346 gchar *pub_state = sipe_is_user_state(sipe_private) ?
4347 sipe_publish_get_category_state_user(sipe_private) :
4348 sipe_publish_get_category_state_machine(sipe_private);
4349 gchar *pub_note = sipe_publish_get_category_note(sipe_private,
4350 sip->note,
4351 sip->is_oof_note ? "OOF" : "personal",
4354 gchar *publications;
4356 if (!pub_state && !pub_note) {
4357 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
4358 return;
4361 publications = g_strdup_printf("%s%s",
4362 pub_state ? pub_state : "",
4363 pub_note ? pub_note : "");
4365 g_free(pub_state);
4366 g_free(pub_note);
4368 send_presence_publish(sipe_private, publications);
4369 g_free(publications);
4373 * Publishes self status
4374 * based on own calendar information.
4376 * For 2007+
4378 void
4379 publish_calendar_status_self(struct sipe_core_private *sipe_private,
4380 SIPE_UNUSED_PARAMETER void *unused)
4382 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4383 struct sipe_cal_event* event = NULL;
4384 gchar *pub_cal_working_hours = NULL;
4385 gchar *pub_cal_free_busy = NULL;
4386 gchar *pub_calendar = NULL;
4387 gchar *pub_calendar2 = NULL;
4388 gchar *pub_oof_note = NULL;
4389 const gchar *oof_note;
4390 time_t oof_start = 0;
4391 time_t oof_end = 0;
4393 if (!sip->cal) {
4394 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
4395 return;
4398 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
4399 if (sip->cal->cal_events) {
4400 event = sipe_cal_get_event(sip->cal->cal_events, time(NULL));
4403 if (!event) {
4404 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
4405 } else {
4406 char *desc = sipe_cal_event_describe(event);
4407 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc ? desc : "");
4408 g_free(desc);
4411 /* Logic
4412 if OOF
4413 OOF publish, Busy clean
4414 ilse if Busy
4415 OOF clean, Busy publish
4416 else
4417 OOF clean, Busy clean
4419 if (event && event->cal_status == SIPE_CAL_OOF) {
4420 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_OOF);
4421 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
4422 } else if (event && event->cal_status == SIPE_CAL_BUSY) {
4423 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
4424 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, event, sip->cal->email, SIPE_CAL_BUSY);
4425 } else {
4426 pub_calendar = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_OOF);
4427 pub_calendar2 = sipe_publish_get_category_state_calendar(sipe_private, NULL, sip->cal->email, SIPE_CAL_BUSY);
4430 oof_note = sipe_ews_get_oof_note(sip->cal);
4431 if (sipe_strequal("Scheduled", sip->cal->oof_state)) {
4432 oof_start = sip->cal->oof_start;
4433 oof_end = sip->cal->oof_end;
4435 pub_oof_note = sipe_publish_get_category_note(sipe_private, oof_note, "OOF", oof_start, oof_end);
4437 pub_cal_working_hours = sipe_publish_get_category_cal_working_hours(sipe_private);
4438 pub_cal_free_busy = sipe_publish_get_category_cal_free_busy(sipe_private);
4440 if (!pub_cal_working_hours && !pub_cal_free_busy && !pub_calendar && !pub_calendar2 && !pub_oof_note) {
4441 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
4442 } else {
4443 gchar *publications = g_strdup_printf("%s%s%s%s%s",
4444 pub_cal_working_hours ? pub_cal_working_hours : "",
4445 pub_cal_free_busy ? pub_cal_free_busy : "",
4446 pub_calendar ? pub_calendar : "",
4447 pub_calendar2 ? pub_calendar2 : "",
4448 pub_oof_note ? pub_oof_note : "");
4450 send_presence_publish(sipe_private, publications);
4451 g_free(publications);
4454 g_free(pub_cal_working_hours);
4455 g_free(pub_cal_free_busy);
4456 g_free(pub_calendar);
4457 g_free(pub_calendar2);
4458 g_free(pub_oof_note);
4460 /* repeat scheduling */
4461 sipe_sched_calendar_status_self_publish(sipe_private, time(NULL));
4464 static void send_presence_status(struct sipe_core_private *sipe_private,
4465 SIPE_UNUSED_PARAMETER void *unused)
4467 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4468 PurpleStatus * status = purple_account_get_active_status(sip->account);
4470 if (!status) return;
4472 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
4473 purple_status_get_id(status) ? purple_status_get_id(status) : "",
4474 sipe_is_user_state(sipe_private) ? "USER" : "MACHINE");
4476 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
4477 send_presence_category_publish(sipe_private);
4478 } else {
4479 send_presence_soap(sipe_private, FALSE);
4483 static guint sipe_ht_hash_nick(const char *nick)
4485 char *lc = g_utf8_strdown(nick, -1);
4486 guint bucket = g_str_hash(lc);
4487 g_free(lc);
4489 return bucket;
4492 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
4494 char *nick1_norm = NULL;
4495 char *nick2_norm = NULL;
4496 gboolean equal;
4498 if (nick1 == NULL && nick2 == NULL) return TRUE;
4499 if (nick1 == NULL || nick2 == NULL ||
4500 !g_utf8_validate(nick1, -1, NULL) ||
4501 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
4503 nick1_norm = g_utf8_casefold(nick1, -1);
4504 nick2_norm = g_utf8_casefold(nick2, -1);
4505 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
4506 g_free(nick2_norm);
4507 g_free(nick1_norm);
4509 return equal;
4512 /* temporary function */
4513 void sipe_purple_setup(struct sipe_core_public *sipe_public,
4514 PurpleConnection *gc)
4516 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
4517 sip->gc = gc;
4518 sip->account = purple_connection_get_account(gc);
4521 struct sipe_core_public *sipe_core_allocate(const gchar *signin_name,
4522 const gchar *login_domain,
4523 const gchar *login_account,
4524 const gchar *password,
4525 const gchar *email,
4526 const gchar *email_url,
4527 const gchar **errmsg)
4529 struct sipe_core_private *sipe_private;
4530 struct sipe_account_data *sip;
4531 gchar **user_domain;
4533 SIPE_DEBUG_INFO("sipe_core_allocate: signin_name '%s'", signin_name);
4535 /* ensure that sign-in name doesn't contain invalid characters */
4536 if (strpbrk(signin_name, "\t\v\r\n") != NULL) {
4537 *errmsg = _("SIP Exchange user name contains invalid characters");
4538 return NULL;
4541 /* ensure that sign-in name format is name@domain */
4542 if (!strchr(signin_name, '@') ||
4543 g_str_has_prefix(signin_name, "@") ||
4544 g_str_has_suffix(signin_name, "@")) {
4545 *errmsg = _("User name should be a valid SIP URI\nExample: user@company.com");
4546 return NULL;
4549 /* ensure that email format is name@domain (if provided) */
4550 if (!is_empty(email) &&
4551 (!strchr(email, '@') ||
4552 g_str_has_prefix(email, "@") ||
4553 g_str_has_suffix(email, "@")))
4555 *errmsg = _("Email address should be valid if provided\nExample: user@company.com");
4556 return NULL;
4559 /* ensure that user name doesn't contain spaces */
4560 user_domain = g_strsplit(signin_name, "@", 2);
4561 SIPE_DEBUG_INFO("sipe_core_allocate: user '%s' domain '%s'", user_domain[0], user_domain[1]);
4562 if (strchr(user_domain[0], ' ') != NULL) {
4563 g_strfreev(user_domain);
4564 *errmsg = _("SIP Exchange user name contains whitespace");
4565 return NULL;
4568 /* ensure that email_url is in proper format if enabled (if provided).
4569 * Example (Exchange): https://server.company.com/EWS/Exchange.asmx
4570 * Example (Domino) : https://[domino_server]/[mail_database_name].nsf
4572 if (!is_empty(email_url)) {
4573 char *tmp = g_ascii_strdown(email_url, -1);
4574 if (!g_str_has_prefix(tmp, "https://"))
4576 g_free(tmp);
4577 g_strfreev(user_domain);
4578 *errmsg = _("Email services URL should be valid if provided\n"
4579 "Example: https://exchange.corp.com/EWS/Exchange.asmx\n"
4580 "Example: https://domino.corp.com/maildatabase.nsf");
4581 return NULL;
4583 g_free(tmp);
4586 sipe_private = g_new0(struct sipe_core_private, 1);
4587 sipe_private->temporary = sip = g_new0(struct sipe_account_data, 1);
4588 sip->subscribed_buddies = FALSE;
4589 sip->initial_state_published = FALSE;
4590 sipe_private->username = g_strdup(signin_name);
4591 sip->email = is_empty(email) ? g_strdup(signin_name) : g_strdup(email);
4592 sip->authdomain = is_empty(login_domain) ? NULL : g_strdup(login_domain);
4593 sip->authuser = is_empty(login_account) ? NULL : g_strdup(login_account);
4594 sip->password = g_strdup(password);
4595 sipe_private->public.sip_name = g_strdup(user_domain[0]);
4596 sipe_private->public.sip_domain = g_strdup(user_domain[1]);
4597 g_strfreev(user_domain);
4599 sipe_private->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
4600 sip->our_publications = g_hash_table_new_full(g_str_hash, g_str_equal,
4601 g_free, (GDestroyNotify)g_hash_table_destroy);
4602 sipe_subscriptions_init(sipe_private);
4603 sip->status = g_strdup(SIPE_STATUS_ID_UNKNOWN);
4605 return((struct sipe_core_public *)sipe_private);
4608 static void
4609 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private);
4611 void sipe_connection_cleanup(struct sipe_core_private *sipe_private)
4613 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4615 g_free(sipe_private->epid);
4616 sipe_private->epid = NULL;
4618 sip_transport_disconnect(sipe_private);
4620 sipe_schedule_cancel_all(sipe_private);
4622 if (sip->allow_events) {
4623 GSList *entry = sip->allow_events;
4624 while (entry) {
4625 g_free(entry->data);
4626 entry = entry->next;
4629 g_slist_free(sip->allow_events);
4631 if (sip->containers) {
4632 GSList *entry = sip->containers;
4633 while (entry) {
4634 free_container((struct sipe_container *)entry->data);
4635 entry = entry->next;
4638 g_slist_free(sip->containers);
4640 /* libpurple memory leak workaround */
4641 sipe_blist_menu_free_containers(sipe_private);
4643 if (sipe_private->contact)
4644 g_free(sipe_private->contact);
4645 sipe_private->contact = NULL;
4646 if (sip->regcallid)
4647 g_free(sip->regcallid);
4648 sip->regcallid = NULL;
4650 if (sipe_private->focus_factory_uri)
4651 g_free(sipe_private->focus_factory_uri);
4652 sipe_private->focus_factory_uri = NULL;
4654 if (sip->cal) {
4655 sipe_cal_calendar_free(sip->cal);
4657 sip->cal = NULL;
4659 sipe_groupchat_free(sipe_private);
4663 * A callback for g_hash_table_foreach_remove
4665 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
4666 SIPE_UNUSED_PARAMETER gpointer user_data)
4668 sipe_free_buddy((struct sipe_buddy *) buddy);
4670 /* We must return TRUE as the key/value have already been deleted */
4671 return(TRUE);
4674 void sipe_buddy_free_all(struct sipe_core_private *sipe_private)
4676 g_hash_table_foreach_steal(sipe_private->buddies, sipe_buddy_remove, NULL);
4679 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
4680 SIPE_UNUSED_PARAMETER void *user_data)
4682 PurpleAccount *acct = purple_connection_get_account(gc);
4683 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
4684 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
4685 if (conv == NULL)
4686 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
4687 purple_conversation_present(conv);
4688 g_free(id);
4691 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
4692 SIPE_UNUSED_PARAMETER void *user_data)
4695 purple_blist_request_add_buddy(purple_connection_get_account(gc),
4696 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
4699 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
4700 struct sipmsg *msg,
4701 SIPE_UNUSED_PARAMETER struct transaction *trans)
4703 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
4704 PurpleNotifySearchResults *results;
4705 PurpleNotifySearchColumn *column;
4706 sipe_xml *searchResults;
4707 const sipe_xml *mrow;
4708 int match_count = 0;
4709 gboolean more = FALSE;
4710 gchar *secondary;
4712 /* valid response? */
4713 if (msg->response != 200) {
4714 SIPE_DEBUG_ERROR("process_search_contact_response: request failed (%d)",
4715 msg->response);
4716 purple_notify_error(sip->gc, NULL,
4717 _("Contact search failed"),
4718 NULL);
4719 return(FALSE);
4722 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
4724 /* valid XML? */
4725 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
4726 if (!searchResults) {
4727 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
4728 purple_notify_error(sip->gc, NULL,
4729 _("Contact search failed"),
4730 NULL);
4731 return FALSE;
4734 /* any matches? */
4735 mrow = sipe_xml_child(searchResults, "Body/Array/row");
4736 if (!mrow) {
4737 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: no matches");
4738 purple_notify_error(sip->gc, NULL,
4739 _("No contacts found"),
4740 NULL);
4742 sipe_xml_free(searchResults);
4743 return(FALSE);
4746 /* OK, we found something - show the results to the user */
4747 results = purple_notify_searchresults_new();
4748 if (!results) {
4749 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: Unable to display the search results.");
4750 purple_notify_error(sip->gc, NULL, _("Unable to display the search results"), NULL);
4752 sipe_xml_free(searchResults);
4753 return FALSE;
4756 column = purple_notify_searchresults_column_new(_("User name"));
4757 purple_notify_searchresults_column_add(results, column);
4759 column = purple_notify_searchresults_column_new(_("Name"));
4760 purple_notify_searchresults_column_add(results, column);
4762 column = purple_notify_searchresults_column_new(_("Company"));
4763 purple_notify_searchresults_column_add(results, column);
4765 column = purple_notify_searchresults_column_new(_("Country"));
4766 purple_notify_searchresults_column_add(results, column);
4768 column = purple_notify_searchresults_column_new(_("Email"));
4769 purple_notify_searchresults_column_add(results, column);
4771 for (/* initialized above */ ; mrow; mrow = sipe_xml_twin(mrow)) {
4772 GList *row = NULL;
4774 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
4775 row = g_list_append(row, g_strdup(uri_parts[1]));
4776 g_strfreev(uri_parts);
4778 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "displayName")));
4779 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "company")));
4780 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "country")));
4781 row = g_list_append(row, g_strdup(sipe_xml_attribute(mrow, "email")));
4783 purple_notify_searchresults_row_add(results, row);
4784 match_count++;
4787 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
4788 char *data = sipe_xml_data(mrow);
4789 more = (g_strcasecmp(data, "true") == 0);
4790 g_free(data);
4793 secondary = g_strdup_printf(
4794 dngettext(PACKAGE_NAME,
4795 "Found %d contact%s:",
4796 "Found %d contacts%s:", match_count),
4797 match_count, more ? _(" (more matched your query)") : "");
4799 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
4800 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
4801 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
4803 g_free(secondary);
4804 sipe_xml_free(searchResults);
4805 return TRUE;
4808 #define SIPE_SOAP_SEARCH_ROW "<m:row m:attrib=\"%s\" m:value=\"%s\"/>"
4810 void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
4812 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
4813 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
4814 unsigned i = 0;
4816 if (!attrs) return;
4818 do {
4819 PurpleRequestField *field = entries->data;
4820 const char *id = purple_request_field_get_id(field);
4821 const char *value = purple_request_field_string_get_value(field);
4823 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id, value ? value : "");
4825 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
4826 } while ((entries = g_list_next(entries)) != NULL);
4827 attrs[i] = NULL;
4829 if (i > 0) {
4830 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
4831 gchar *query = g_strjoinv(NULL, attrs);
4832 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: rows:\n%s", query ? query : "");
4833 sip_soap_directory_search(sipe_private,
4834 100,
4835 query,
4836 process_search_contact_response,
4837 NULL);
4838 g_free(query);
4841 g_strfreev(attrs);
4844 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER const char *name,
4845 gpointer value,
4846 GString* str)
4848 struct sipe_publication *publication = value;
4850 g_string_append_printf( str,
4851 SIPE_PUB_XML_PUBLICATION_CLEAR,
4852 publication->category,
4853 publication->instance,
4854 publication->container,
4855 publication->version,
4856 "static");
4859 void sipe_core_reset_status(struct sipe_core_public *sipe_public)
4861 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
4862 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA;
4863 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) /* 2007+ */
4865 GString* str;
4866 gchar *publications;
4868 if (!sip->user_state_publications || g_hash_table_size(sip->user_state_publications) == 0) {
4869 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
4870 return;
4873 str = g_string_new(NULL);
4874 g_hash_table_foreach(sip->user_state_publications, (GHFunc)sipe_publish_get_cat_state_user_to_clear, str);
4875 publications = g_string_free(str, FALSE);
4877 send_presence_publish(sipe_private, publications);
4878 g_free(publications);
4880 else /* 2005 */
4882 send_presence_soap0(sipe_private, FALSE, TRUE);
4886 /** for Access levels menu */
4887 #define INDENT_FMT " %s"
4889 /** Member is directly placed to access level container.
4890 * For example SIP URI of user is in the container.
4892 #define INDENT_MARKED_FMT "* %s"
4894 /** Member is indirectly belong to access level container.
4895 * For example 'sameEnterprise' is in the container and user
4896 * belongs to that same enterprise.
4898 #define INDENT_MARKED_INHERITED_FMT "= %s"
4900 GSList *sipe_core_buddy_info(struct sipe_core_public *sipe_public,
4901 const gchar *name,
4902 const gchar *status_name,
4903 gboolean is_online)
4905 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
4906 gchar *note = NULL;
4907 gboolean is_oof_note = FALSE;
4908 const gchar *activity = NULL;
4909 gchar *calendar = NULL;
4910 const gchar *meeting_subject = NULL;
4911 const gchar *meeting_location = NULL;
4912 gchar *access_text = NULL;
4913 GSList *info = NULL;
4915 #define SIPE_ADD_BUDDY_INFO_COMMON(l, t) \
4917 struct sipe_buddy_info *sbi = g_malloc(sizeof(struct sipe_buddy_info)); \
4918 sbi->label = (l); \
4919 sbi->text = (t); \
4920 info = g_slist_append(info, sbi); \
4922 #define SIPE_ADD_BUDDY_INFO(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), g_markup_escape_text((t), -1))
4923 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) SIPE_ADD_BUDDY_INFO_COMMON((l), (t))
4925 if (sipe_public) { //happens on pidgin exit
4926 struct sipe_buddy *sbuddy = g_hash_table_lookup(sipe_private->buddies, name);
4927 if (sbuddy) {
4928 note = sbuddy->note;
4929 is_oof_note = sbuddy->is_oof_note;
4930 activity = sbuddy->activity;
4931 calendar = sipe_cal_get_description(sbuddy);
4932 meeting_subject = sbuddy->meeting_subject;
4933 meeting_location = sbuddy->meeting_location;
4935 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
4936 gboolean is_group_access = FALSE;
4937 const int container_id = sipe_find_access_level(sipe_private, "user", sipe_get_no_sip_uri(name), &is_group_access);
4938 const char *access_level = sipe_get_access_level_name(container_id);
4939 access_text = is_group_access ?
4940 g_strdup(access_level) :
4941 g_strdup_printf(INDENT_MARKED_FMT, access_level);
4945 //Layout
4946 if (is_online)
4948 const gchar *status_str = activity ? activity : status_name;
4950 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
4952 if (is_online && !is_empty(calendar))
4954 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
4956 g_free(calendar);
4957 if (!is_empty(meeting_location))
4959 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", name, meeting_location);
4960 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
4962 if (!is_empty(meeting_subject))
4964 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", name, meeting_subject);
4965 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
4967 if (note)
4969 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", name, note);
4970 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
4971 g_strdup_printf("<i>%s</i>", note));
4973 if (access_text) {
4974 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
4975 g_free(access_text);
4978 return(info);
4981 static PurpleBuddy *
4982 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
4984 PurpleBuddy *clone;
4985 const gchar *server_alias, *email;
4986 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
4988 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
4990 purple_blist_add_buddy(clone, NULL, group, NULL);
4992 server_alias = purple_buddy_get_server_alias(buddy);
4993 if (server_alias) {
4994 purple_blist_server_alias_buddy(clone, server_alias);
4997 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
4998 if (email) {
4999 purple_blist_node_set_string(&clone->node, EMAIL_PROP, email);
5002 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5003 //for UI to update;
5004 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5005 return clone;
5008 static void
5009 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5011 PurpleBuddy *buddy, *b;
5012 PurpleConnection *gc;
5013 PurpleGroup * group = purple_find_group(group_name);
5015 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5017 buddy = (PurpleBuddy *)node;
5019 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy->name, group_name);
5020 gc = purple_account_get_connection(buddy->account);
5022 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5023 if (!b){
5024 b = purple_blist_add_buddy_clone(group, buddy);
5027 sipe_add_buddy(gc, b, group);
5030 static void
5031 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
5033 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5035 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy->name);
5037 /* 2007+ conference */
5038 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
5040 sipe_conf_add(sipe_private, buddy->name);
5042 else /* 2005- multiparty chat */
5044 gchar *self = sip_uri_self(sipe_private);
5045 struct sip_session *session;
5047 session = sipe_session_add_chat(sipe_private,
5048 NULL,
5049 TRUE,
5050 self);
5051 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
5052 session->chat_session,
5053 session->chat_session->title,
5054 self);
5055 g_free(self);
5057 sipe_im_invite(sipe_private, session, buddy->name, NULL, NULL, NULL, FALSE);
5062 * For 2007+ conference only.
5064 static void
5065 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy,
5066 struct sipe_chat_session *chat_session)
5068 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5069 struct sip_session *session;
5071 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy->name);
5072 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_session->title);
5074 session = sipe_session_find_chat(sipe_private, chat_session);
5076 sipe_conf_modify_user_role(sipe_private, session, buddy->name);
5080 * For 2007+ conference only.
5082 static void
5083 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy,
5084 struct sipe_chat_session *chat_session)
5086 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5087 struct sip_session *session;
5089 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy->name);
5090 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_session->title);
5092 session = sipe_session_find_chat(sipe_private, chat_session);
5094 sipe_conf_delete_user(sipe_private, session, buddy->name);
5097 static void
5098 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy,
5099 struct sipe_chat_session *chat_session)
5101 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5103 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy->name);
5104 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_session->title);
5106 sipe_core_chat_invite(SIPE_CORE_PUBLIC, chat_session, buddy->name);
5109 static void
5110 sipe_buddy_menu_make_call_cb(PurpleBuddy *buddy, const char *phone)
5112 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5114 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy->name);
5115 if (phone) {
5116 char *tel_uri = sip_to_tel_uri(phone);
5118 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri ? tel_uri : "");
5119 sip_csta_make_call(sipe_private, tel_uri);
5121 g_free(tel_uri);
5125 static void
5126 sipe_buddy_menu_access_level_help_cb(PurpleBuddy *buddy)
5128 /** Translators: replace with URL to localized page
5129 * If it doesn't exist copy the original URL */
5130 purple_notify_uri(buddy->account->gc, _("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
5133 static void
5134 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
5136 const gchar *email;
5137 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy->name);
5139 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5140 if (email)
5142 char *command_line = g_strdup_printf(
5143 #ifdef _WIN32
5144 "cmd /c start"
5145 #else
5146 "xdg-email"
5147 #endif
5148 " mailto:%s", email);
5149 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call email client: %s", command_line);
5151 g_spawn_command_line_async(command_line, NULL);
5152 g_free(command_line);
5154 else
5156 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy->name);
5160 static void
5161 sipe_buddy_menu_access_level_cb(PurpleBuddy *buddy,
5162 struct sipe_container *container)
5164 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5165 struct sipe_container_member *member;
5167 if (!container || !container->members) return;
5169 member = ((struct sipe_container_member *)container->members->data);
5171 if (!member->type) return;
5173 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
5174 container->id, member->type, member->value ? member->value : "");
5176 sipe_change_access_level(sipe_private, container->id, member->type, member->value);
5179 static GList *
5180 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
5181 const char* uri);
5184 * A menu which appear when right-clicking on buddy in contact list.
5186 GList *
5187 sipe_buddy_menu(PurpleBuddy *buddy)
5189 PurpleBlistNode *g_node;
5190 PurpleGroup *gr_parent;
5191 PurpleMenuAction *act;
5192 GList *menu = NULL;
5193 GList *menu_groups = NULL;
5194 struct sipe_core_private *sipe_private = PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE;
5195 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5196 const char *email;
5197 gchar *self = sip_uri_self(sipe_private);
5199 SIPE_SESSION_FOREACH {
5200 if (!sipe_strcase_equal(self, buddy->name) && session->chat_session)
5202 struct sipe_chat_session *chat_session = session->chat_session;
5203 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
5205 if (sipe_backend_chat_find(chat_session->backend, buddy->name))
5207 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
5209 if (is_conf
5210 && !sipe_backend_chat_is_operator(chat_session->backend, buddy->name) /* Not conf OP */
5211 && conf_op) /* We are a conf OP */
5213 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
5214 chat_session->title);
5215 act = purple_menu_action_new(label,
5216 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
5217 chat_session, NULL);
5218 g_free(label);
5219 menu = g_list_prepend(menu, act);
5222 if (is_conf
5223 && conf_op) /* We are a conf OP */
5225 gchar *label = g_strdup_printf(_("Remove from '%s'"),
5226 chat_session->title);
5227 act = purple_menu_action_new(label,
5228 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
5229 chat_session, NULL);
5230 g_free(label);
5231 menu = g_list_prepend(menu, act);
5234 else
5236 if (!is_conf
5237 || (is_conf && !session->locked))
5239 gchar *label = g_strdup_printf(_("Invite to '%s'"),
5240 chat_session->title);
5241 act = purple_menu_action_new(label,
5242 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
5243 chat_session, NULL);
5244 g_free(label);
5245 menu = g_list_prepend(menu, act);
5249 } SIPE_SESSION_FOREACH_END;
5251 act = purple_menu_action_new(_("New chat"),
5252 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
5253 NULL, NULL);
5254 menu = g_list_prepend(menu, act);
5256 if (sip->csta && !sip->csta->line_status) {
5257 const char *phone;
5258 const char *phone_disp_str;
5259 gchar *tmp = NULL;
5260 /* work phone */
5261 phone = purple_blist_node_get_string(&buddy->node, PHONE_PROP);
5262 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_DISPLAY_PROP);
5263 if (phone) {
5264 gchar *label = g_strdup_printf(_("Work %s"),
5265 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5266 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5267 g_free(tmp);
5268 tmp = NULL;
5269 g_free(label);
5270 menu = g_list_prepend(menu, act);
5273 /* mobile phone */
5274 phone = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_PROP);
5275 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_MOBILE_DISPLAY_PROP);
5276 if (phone) {
5277 gchar *label = g_strdup_printf(_("Mobile %s"),
5278 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5279 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5280 g_free(tmp);
5281 tmp = NULL;
5282 g_free(label);
5283 menu = g_list_prepend(menu, act);
5286 /* home phone */
5287 phone = purple_blist_node_get_string(&buddy->node, PHONE_HOME_PROP);
5288 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_HOME_DISPLAY_PROP);
5289 if (phone) {
5290 gchar *label = g_strdup_printf(_("Home %s"),
5291 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5292 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5293 g_free(tmp);
5294 tmp = NULL;
5295 g_free(label);
5296 menu = g_list_prepend(menu, act);
5299 /* other phone */
5300 phone = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_PROP);
5301 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_OTHER_DISPLAY_PROP);
5302 if (phone) {
5303 gchar *label = g_strdup_printf(_("Other %s"),
5304 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5305 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5306 g_free(tmp);
5307 tmp = NULL;
5308 g_free(label);
5309 menu = g_list_prepend(menu, act);
5312 /* custom1 phone */
5313 phone = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_PROP);
5314 phone_disp_str = purple_blist_node_get_string(&buddy->node, PHONE_CUSTOM1_DISPLAY_PROP);
5315 if (phone) {
5316 gchar *label = g_strdup_printf(_("Custom1 %s"),
5317 phone_disp_str ? phone_disp_str : (tmp = sip_tel_uri_denormalize(phone)));
5318 act = purple_menu_action_new(label, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb), (gpointer) phone, NULL);
5319 g_free(tmp);
5320 tmp = NULL;
5321 g_free(label);
5322 menu = g_list_prepend(menu, act);
5326 email = purple_blist_node_get_string(&buddy->node, EMAIL_PROP);
5327 if (email) {
5328 act = purple_menu_action_new(_("Send email..."),
5329 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
5330 NULL, NULL);
5331 menu = g_list_prepend(menu, act);
5334 /* Access Level */
5335 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5336 GList *menu_access_levels = sipe_get_access_control_menu(sipe_private, buddy->name);
5338 act = purple_menu_action_new(_("Access level"),
5339 NULL,
5340 NULL, menu_access_levels);
5341 menu = g_list_prepend(menu, act);
5344 /* Copy to */
5345 gr_parent = purple_buddy_get_group(buddy);
5346 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
5347 PurpleGroup *group;
5349 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
5350 continue;
5352 group = (PurpleGroup *)g_node;
5353 if (group == gr_parent)
5354 continue;
5356 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
5357 continue;
5359 act = purple_menu_action_new(purple_group_get_name(group),
5360 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
5361 group->name, NULL);
5362 menu_groups = g_list_prepend(menu_groups, act);
5364 menu_groups = g_list_reverse(menu_groups);
5366 act = purple_menu_action_new(_("Copy to"),
5367 NULL,
5368 NULL, menu_groups);
5369 menu = g_list_prepend(menu, act);
5371 menu = g_list_reverse(menu);
5373 g_free(self);
5374 return menu;
5377 static void
5378 sipe_ask_access_domain_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5380 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
5381 const char *domain = purple_request_fields_get_string(fields, "access_domain");
5382 int index = purple_request_fields_get_choice(fields, "container_id");
5383 /* move Blocked first */
5384 int i = (index == 4) ? 0 : index + 1;
5385 int container_id = containers[i];
5387 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain ? domain : "", index, container_id);
5389 sipe_change_access_level(sipe_private, container_id, "domain", domain);
5392 static void
5393 sipe_ask_access_domain(struct sipe_core_private *sipe_private)
5395 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5396 PurpleAccount *account = sip->account;
5397 PurpleConnection *gc = sip->gc;
5398 PurpleRequestFields *fields;
5399 PurpleRequestFieldGroup *g;
5400 PurpleRequestField *f;
5402 fields = purple_request_fields_new();
5404 g = purple_request_field_group_new(NULL);
5405 f = purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE);
5406 purple_request_field_set_required(f, TRUE);
5407 purple_request_field_group_add_field(g, f);
5409 f = purple_request_field_choice_new("container_id", _("Access level"), 0);
5410 purple_request_field_choice_add(f, _("Personal")); /* index 0 */
5411 purple_request_field_choice_add(f, _("Team"));
5412 purple_request_field_choice_add(f, _("Company"));
5413 purple_request_field_choice_add(f, _("Public"));
5414 purple_request_field_choice_add(f, _("Blocked")); /* index 4 */
5415 purple_request_field_choice_set_default_value(f, 3); /* index */
5416 purple_request_field_set_required(f, TRUE);
5417 purple_request_field_group_add_field(g, f);
5419 purple_request_fields_add_group(fields, g);
5421 purple_request_fields(gc, _("Add new domain"),
5422 _("Add new domain"), NULL, fields,
5423 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb),
5424 _("Cancel"), NULL,
5425 account, NULL, NULL, gc);
5428 static void
5429 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy *buddy)
5431 sipe_ask_access_domain(PURPLE_BUDDY_TO_SIPE_CORE_PRIVATE);
5435 * Workaround for missing libpurple API to release resources allocated
5436 * during blist_node_menu() callback. See also:
5438 * <http://developer.pidgin.im/ticket/12597>
5440 * We remember all memory blocks in a list and deallocate them when
5442 * - the next time we enter the callback, or
5443 * - the account is disconnected
5445 * That means that after the buddy menu has been closed we have unused
5446 * resources but at least we don't leak them anymore...
5448 static void
5449 sipe_blist_menu_free_containers(struct sipe_core_private *sipe_private)
5451 GSList *entry = sipe_private->blist_menu_containers;
5452 while (entry) {
5453 free_container(entry->data);
5454 entry = entry->next;
5456 g_slist_free(sipe_private->blist_menu_containers);
5457 sipe_private->blist_menu_containers = NULL;
5460 static void
5461 sipe_blist_menu_remember_container(struct sipe_core_private *sipe_private,
5462 struct sipe_container *container)
5464 sipe_private->blist_menu_containers = g_slist_prepend(sipe_private->blist_menu_containers,
5465 container);
5468 static GList *
5469 sipe_get_access_levels_menu(struct sipe_core_private *sipe_private,
5470 const char* member_type,
5471 const char* member_value,
5472 const gboolean extra_menu)
5474 GList *menu_access_levels = NULL;
5475 unsigned int i;
5476 char *menu_name;
5477 PurpleMenuAction *act;
5478 struct sipe_container *container;
5479 struct sipe_container_member *member;
5480 gboolean is_group_access = FALSE;
5481 int container_id = sipe_find_access_level(sipe_private, member_type, member_value, &is_group_access);
5483 for (i = 1; i <= CONTAINERS_LEN; i++) {
5484 /* to put Blocked level last in menu list.
5485 * Blocked should remaim in the first place in the containers[] array.
5487 unsigned int j = (i == CONTAINERS_LEN) ? 0 : i;
5488 const char *acc_level_name = sipe_get_access_level_name(containers[j]);
5490 container = g_new0(struct sipe_container, 1);
5491 member = g_new0(struct sipe_container_member, 1);
5492 container->id = containers[j];
5493 container->members = g_slist_append(container->members, member);
5494 member->type = g_strdup(member_type);
5495 member->value = g_strdup(member_value);
5497 /* libpurple memory leak workaround */
5498 sipe_blist_menu_remember_container(sipe_private, container);
5500 /* current container/access level */
5501 if (((int)containers[j]) == container_id) {
5502 menu_name = is_group_access ?
5503 g_strdup_printf(INDENT_MARKED_INHERITED_FMT, acc_level_name) :
5504 g_strdup_printf(INDENT_MARKED_FMT, acc_level_name);
5505 } else {
5506 menu_name = g_strdup_printf(INDENT_FMT, acc_level_name);
5509 act = purple_menu_action_new(menu_name,
5510 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
5511 container, NULL);
5512 g_free(menu_name);
5513 menu_access_levels = g_list_prepend(menu_access_levels, act);
5516 if (extra_menu && (container_id >= 0)) {
5517 /* separator */
5518 act = purple_menu_action_new(" --------------", NULL, NULL, NULL);
5519 menu_access_levels = g_list_prepend(menu_access_levels, act);
5521 if (!is_group_access) {
5522 container = g_new0(struct sipe_container, 1);
5523 member = g_new0(struct sipe_container_member, 1);
5524 container->id = -1;
5525 container->members = g_slist_append(container->members, member);
5526 member->type = g_strdup(member_type);
5527 member->value = g_strdup(member_value);
5529 /* libpurple memory leak workaround */
5530 sipe_blist_menu_remember_container(sipe_private, container);
5532 /* Translators: remove (clear) previously assigned access level */
5533 menu_name = g_strdup_printf(INDENT_FMT, _("Unspecify"));
5534 act = purple_menu_action_new(menu_name,
5535 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb),
5536 container, NULL);
5537 g_free(menu_name);
5538 menu_access_levels = g_list_prepend(menu_access_levels, act);
5542 menu_access_levels = g_list_reverse(menu_access_levels);
5543 return menu_access_levels;
5546 static GList *
5547 sipe_get_access_groups_menu(struct sipe_core_private *sipe_private)
5549 GList *menu_access_groups = NULL;
5550 PurpleMenuAction *act;
5551 GSList *access_domains = NULL;
5552 GSList *entry;
5553 char *menu_name;
5554 char *domain;
5556 act = purple_menu_action_new(_("People in my company"),
5557 NULL,
5558 NULL, sipe_get_access_levels_menu(sipe_private, "sameEnterprise", NULL, FALSE));
5559 menu_access_groups = g_list_prepend(menu_access_groups, act);
5561 /* this is original name, don't edit */
5562 act = purple_menu_action_new(_("People in domains connected with my company"),
5563 NULL,
5564 NULL, sipe_get_access_levels_menu(sipe_private, "federated", NULL, FALSE));
5565 menu_access_groups = g_list_prepend(menu_access_groups, act);
5567 act = purple_menu_action_new(_("People in public domains"),
5568 NULL,
5569 NULL, sipe_get_access_levels_menu(sipe_private, "publicCloud", NULL, TRUE));
5570 menu_access_groups = g_list_prepend(menu_access_groups, act);
5572 access_domains = sipe_get_access_domains(sipe_private);
5573 entry = access_domains;
5574 while (entry) {
5575 domain = entry->data;
5577 menu_name = g_strdup_printf(_("People at %s"), domain);
5578 act = purple_menu_action_new(menu_name,
5579 NULL,
5580 NULL, sipe_get_access_levels_menu(sipe_private, "domain", g_strdup(domain), TRUE));
5581 menu_access_groups = g_list_prepend(menu_access_groups, act);
5582 g_free(menu_name);
5584 entry = entry->next;
5587 /* separator */
5588 /* People in domains connected with my company */
5589 act = purple_menu_action_new("-------------------------------------------", NULL, NULL, NULL);
5590 menu_access_groups = g_list_prepend(menu_access_groups, act);
5592 act = purple_menu_action_new(_("Add new domain..."),
5593 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb),
5594 NULL, NULL);
5595 menu_access_groups = g_list_prepend(menu_access_groups, act);
5597 menu_access_groups = g_list_reverse(menu_access_groups);
5599 return menu_access_groups;
5602 static GList *
5603 sipe_get_access_control_menu(struct sipe_core_private *sipe_private,
5604 const char* uri)
5606 GList *menu_access_levels = NULL;
5607 GList *menu_access_groups = NULL;
5608 char *menu_name;
5609 PurpleMenuAction *act;
5611 /* libpurple memory leak workaround */
5612 sipe_blist_menu_free_containers(sipe_private);
5614 menu_access_levels = sipe_get_access_levels_menu(sipe_private, "user", sipe_get_no_sip_uri(uri), TRUE);
5616 menu_access_groups = sipe_get_access_groups_menu(sipe_private);
5618 menu_name = g_strdup_printf(INDENT_FMT, _("Access groups"));
5619 act = purple_menu_action_new(menu_name,
5620 NULL,
5621 NULL, menu_access_groups);
5622 g_free(menu_name);
5623 menu_access_levels = g_list_append(menu_access_levels, act);
5625 menu_name = g_strdup_printf(INDENT_FMT, _("Online help..."));
5626 act = purple_menu_action_new(menu_name,
5627 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb),
5628 NULL, NULL);
5629 g_free(menu_name);
5630 menu_access_levels = g_list_append(menu_access_levels, act);
5632 return menu_access_levels;
5635 static gboolean
5636 process_get_info_response(struct sipe_core_private *sipe_private,
5637 struct sipmsg *msg, struct transaction *trans)
5639 struct sipe_account_data *sip = SIPE_ACCOUNT_DATA_PRIVATE;
5640 char *uri = trans->payload->data;
5642 PurpleNotifyUserInfo *info;
5643 PurpleBuddy *pbuddy = NULL;
5644 struct sipe_buddy *sbuddy;
5645 const char *alias = NULL;
5646 char *device_name = NULL;
5647 char *server_alias = NULL;
5648 char *phone_number = NULL;
5649 char *email = NULL;
5650 const char *site;
5651 char *first_name = NULL;
5652 char *last_name = NULL;
5654 if (!sip) return FALSE;
5656 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri, sipe_private->username);
5658 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri);
5659 alias = purple_buddy_get_local_alias(pbuddy);
5661 //will query buddy UA's capabilities and send answer to log
5662 sipe_options_request(sipe_private, uri);
5664 sbuddy = g_hash_table_lookup(sipe_private->buddies, uri);
5665 if (sbuddy) {
5666 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
5669 info = purple_notify_user_info_new();
5671 if (msg->response != 200) {
5672 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
5673 } else {
5674 sipe_xml *searchResults;
5675 const sipe_xml *mrow;
5677 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s", msg->body ? msg->body : "");
5678 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
5679 if (!searchResults) {
5680 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
5681 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
5682 const char *value;
5683 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
5684 email = g_strdup(sipe_xml_attribute(mrow, "email"));
5685 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
5687 /* For 2007 system we will take this from ContactCard -
5688 * it has cleaner tel: URIs at least
5690 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
5691 char *tel_uri = sip_to_tel_uri(phone_number);
5692 /* trims its parameters, so call first */
5693 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
5694 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
5695 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
5696 sipe_update_user_info(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
5697 g_free(tel_uri);
5700 #if PURPLE_VERSION_CHECK(3,0,0)
5701 #define PURPLE_NOTIFY_USER_INFO_ADD_PAIR purple_notify_user_info_add_pair_html
5702 #else
5703 #define PURPLE_NOTIFY_USER_INFO_ADD_PAIR purple_notify_user_info_add_pair
5704 #endif
5706 if (server_alias && strlen(server_alias) > 0) {
5707 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Display name"), server_alias);
5709 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
5710 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Job title"), value);
5712 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
5713 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Office"), value);
5715 if (phone_number && strlen(phone_number) > 0) {
5716 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Business phone"), phone_number);
5718 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
5719 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Company"), value);
5721 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
5722 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("City"), value);
5724 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
5725 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("State"), value);
5727 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
5728 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Country"), value);
5730 if (email && strlen(email) > 0) {
5731 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Email address"), email);
5735 sipe_xml_free(searchResults);
5738 purple_notify_user_info_add_section_break(info);
5740 if (is_empty(server_alias)) {
5741 g_free(server_alias);
5742 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
5743 if (server_alias) {
5744 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Display name"), server_alias);
5748 /* present alias if it differs from server alias */
5749 if (alias && !sipe_strequal(alias, server_alias))
5751 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Alias"), alias);
5754 if (is_empty(email)) {
5755 g_free(email);
5756 email = g_strdup(purple_blist_node_get_string(&pbuddy->node, EMAIL_PROP));
5757 if (email) {
5758 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Email address"), email);
5762 site = purple_blist_node_get_string(&pbuddy->node, SITE_PROP);
5763 if (site) {
5764 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Site"), site);
5767 sipe_get_first_last_names(sipe_private, uri, &first_name, &last_name);
5768 if (first_name && last_name) {
5769 char *link = g_strconcat("http://www.linkedin.com/pub/dir/", first_name, "/", last_name, NULL);
5771 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Find on LinkedIn"), link);
5772 g_free(link);
5774 g_free(first_name);
5775 g_free(last_name);
5777 if (device_name) {
5778 PURPLE_NOTIFY_USER_INFO_ADD_PAIR(info, _("Device"), device_name);
5781 /* show a buddy's user info in a nice dialog box */
5782 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
5783 uri, /* buddy's URI */
5784 info, /* body */
5785 NULL, /* callback called when dialog closed */
5786 NULL); /* userdata for callback */
5788 g_free(phone_number);
5789 g_free(server_alias);
5790 g_free(email);
5791 g_free(device_name);
5793 return TRUE;
5797 * AD search first, LDAP based
5799 void sipe_get_info(PurpleConnection *gc, const char *username)
5801 struct sipe_core_private *sipe_private = PURPLE_GC_TO_SIPE_CORE_PRIVATE;
5802 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
5803 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
5805 payload->destroy = g_free;
5806 payload->data = g_strdup(username);
5808 SIPE_DEBUG_INFO("sipe_get_info: row: %s", row ? row : "");
5809 sip_soap_directory_search(sipe_private,
5811 row,
5812 process_get_info_response,
5813 payload);
5814 g_free(row);
5818 Local Variables:
5819 mode: c
5820 c-file-style: "bsd"
5821 indent-tabs-mode: t
5822 tab-width: 8
5823 End: