ucs: add AddNewImContactToGroup operation
[siplcs.git] / src / core / sipe-buddy.c
blob5f4297d3c395eeda3f5f7320eb71e547d475af22
1 /**
2 * @file sipe-buddy.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2013 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
31 #include <glib.h>
33 #include "sipe-common.h"
34 #include "sipmsg.h"
35 #include "sip-csta.h"
36 #include "sip-soap.h"
37 #include "sip-transport.h"
38 #include "sipe-backend.h"
39 #include "sipe-buddy.h"
40 #include "sipe-cal.h"
41 #include "sipe-chat.h"
42 #include "sipe-conf.h"
43 #include "sipe-core.h"
44 #include "sipe-core-private.h"
45 #include "sipe-group.h"
46 #include "sipe-http.h"
47 #include "sipe-im.h"
48 #include "sipe-nls.h"
49 #include "sipe-ocs2005.h"
50 #include "sipe-ocs2007.h"
51 #include "sipe-schedule.h"
52 #include "sipe-session.h"
53 #include "sipe-status.h"
54 #include "sipe-subscriptions.h"
55 #include "sipe-svc.h"
56 #include "sipe-ucs.h"
57 #include "sipe-utils.h"
58 #include "sipe-webticket.h"
59 #include "sipe-xml.h"
61 struct sipe_buddies {
62 GHashTable *uri;
63 GHashTable *exchange_key;
65 /* Pending photo download HTTP requests */
66 GSList *pending_photo_requests;
69 struct buddy_group_data {
70 const struct sipe_group *group;
71 gboolean is_obsolete;
74 struct photo_response_data {
75 gchar *who;
76 gchar *photo_hash;
77 struct sipe_http_request *request;
80 static void buddy_fetch_photo(struct sipe_core_private *sipe_private,
81 const gchar *uri);
82 static void photo_response_data_free(struct photo_response_data *data);
84 void sipe_buddy_add_keys(struct sipe_core_private *sipe_private,
85 struct sipe_buddy *buddy,
86 const gchar *exchange_key,
87 const gchar *change_key)
89 if (exchange_key) {
90 buddy->exchange_key = g_strdup(exchange_key);
91 g_hash_table_insert(sipe_private->buddies->exchange_key,
92 buddy->exchange_key,
93 buddy);
95 if (change_key)
96 buddy->change_key = g_strdup(change_key);
99 struct sipe_buddy *sipe_buddy_add(struct sipe_core_private *sipe_private,
100 const gchar *uri,
101 const gchar *exchange_key,
102 const gchar *change_key)
104 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
105 gchar *normalized_uri = g_ascii_strdown(uri, -1);
106 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
107 normalized_uri);
109 if (!buddy) {
110 buddy = g_new0(struct sipe_buddy, 1);
111 buddy->name = normalized_uri;
112 g_hash_table_insert(sipe_private->buddies->uri,
113 buddy->name,
114 buddy);
116 sipe_buddy_add_keys(sipe_private,
117 buddy,
118 exchange_key,
119 change_key);
121 SIPE_DEBUG_INFO("sipe_buddy_add: Added buddy %s", normalized_uri);
123 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
124 buddy->just_added = TRUE;
125 sipe_subscribe_presence_single_cb(sipe_private,
126 buddy->name);
129 buddy_fetch_photo(sipe_private, normalized_uri);
131 normalized_uri = NULL; /* buddy takes ownership */
132 } else {
133 SIPE_DEBUG_INFO("sipe_buddy_add: Buddy %s already exists", normalized_uri);
134 buddy->is_obsolete = FALSE;
136 g_free(normalized_uri);
138 return(buddy);
141 static gboolean is_buddy_in_group(struct sipe_buddy *buddy,
142 const gchar *name)
144 if (buddy) {
145 GSList *entry = buddy->groups;
147 while (entry) {
148 struct buddy_group_data *bgd = entry->data;
149 if (sipe_strequal(bgd->group->name, name)) {
150 bgd->is_obsolete = FALSE;
151 return(TRUE);
153 entry = entry->next;
157 return(FALSE);
160 void sipe_buddy_add_to_group(struct sipe_core_private *sipe_private,
161 struct sipe_buddy *buddy,
162 struct sipe_group *group,
163 const gchar *alias)
165 const gchar *uri = buddy->name;
166 const gchar *group_name = group->name;
167 sipe_backend_buddy bb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
168 uri,
169 group_name);
171 if (!bb) {
172 bb = sipe_backend_buddy_add(SIPE_CORE_PUBLIC,
173 uri,
174 alias,
175 group_name);
176 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: created backend buddy '%s' with alias '%s'",
177 uri, alias ? alias : "<NONE>");
181 if (!is_empty(alias)) {
182 gchar *old_alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC,
183 bb);
185 if (sipe_strcase_equal(sipe_get_no_sip_uri(uri),
186 old_alias)) {
187 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC,
189 alias);
190 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: replaced alias for buddy '%s': old '%s' new '%s'",
191 uri, old_alias, alias);
193 g_free(old_alias);
196 if (!is_buddy_in_group(buddy, group_name)) {
197 sipe_buddy_insert_group(buddy, group);
198 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: added buddy %s to group %s",
199 uri, group_name);
203 static gint buddy_group_compare(gconstpointer a, gconstpointer b)
205 return(((const struct buddy_group_data *)a)->group->id -
206 ((const struct buddy_group_data *)b)->group->id);
209 void sipe_buddy_insert_group(struct sipe_buddy *buddy,
210 struct sipe_group *group)
212 struct buddy_group_data *bgd = g_new0(struct buddy_group_data, 1);
214 bgd->group = group;
216 buddy->groups = sipe_utils_slist_insert_unique_sorted(buddy->groups,
217 bgd,
218 buddy_group_compare,
219 NULL);
222 static void buddy_group_free(gpointer data)
224 g_free(data);
227 static void buddy_group_remove(struct sipe_buddy *buddy,
228 struct buddy_group_data *bgd)
230 buddy->groups = g_slist_remove(buddy->groups, bgd);
231 buddy_group_free(bgd);
234 static void sipe_buddy_remove_group(struct sipe_buddy *buddy,
235 const struct sipe_group *group)
237 GSList *entry = buddy->groups;
238 struct buddy_group_data *bgd = NULL;
240 while (entry) {
241 bgd = entry->data;
242 if (bgd->group == group)
243 break;
244 entry = entry->next;
247 buddy_group_remove(buddy, bgd);
250 void sipe_buddy_update_groups(struct sipe_core_private *sipe_private,
251 struct sipe_buddy *buddy,
252 GSList *new_groups)
254 const gchar *uri = buddy->name;
255 GSList *entry = buddy->groups;
257 while (entry) {
258 struct buddy_group_data *bgd = entry->data;
259 const struct sipe_group *group = bgd->group;
261 /* next buddy group */
262 entry = entry->next;
264 /* old group NOT found in new list? */
265 if (g_slist_find(new_groups, group) == NULL) {
266 sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
267 uri,
268 group->name);
269 SIPE_DEBUG_INFO("sipe_buddy_update_groups: removing buddy %s from group '%s'",
270 uri, group->name);
271 /* this should never be NULL */
272 if (oldb)
273 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
274 oldb);
275 buddy_group_remove(buddy, bgd);
280 gchar *sipe_buddy_groups_string(struct sipe_buddy *buddy)
282 guint i = 0;
283 gchar *string;
284 /* creating array from GList, converting guint to gchar * */
285 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
286 GSList *entry = buddy->groups;
288 if (!ids_arr)
289 return(NULL);
291 while (entry) {
292 const struct sipe_group *group = ((struct buddy_group_data *) entry->data)->group;
293 ids_arr[i] = g_strdup_printf("%u", group->id);
294 entry = entry->next;
295 i++;
297 ids_arr[i] = NULL;
299 string = g_strjoinv(" ", ids_arr);
300 g_strfreev(ids_arr);
302 return(string);
305 void sipe_buddy_cleanup_local_list(struct sipe_core_private *sipe_private)
307 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC,
308 NULL,
309 NULL);
310 GSList *entry = buddies;
312 SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: overall %d backend buddies (including clones)",
313 g_slist_length(buddies));
314 SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: %d sipe buddies (unique)",
315 sipe_buddy_count(sipe_private));
316 while (entry) {
317 sipe_backend_buddy bb = entry->data;
318 gchar *bname = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC,
319 bb);
320 gchar *gname = sipe_backend_buddy_get_group_name(SIPE_CORE_PUBLIC,
321 bb);
322 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
323 bname);
325 if (!is_buddy_in_group(buddy, gname)) {
326 SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: REMOVING '%s' from local group '%s', as buddy is not in that group on remote contact list",
327 bname, gname);
328 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, bb);
331 g_free(gname);
332 g_free(bname);
334 entry = entry->next;
337 g_slist_free(buddies);
340 struct sipe_buddy *sipe_buddy_find_by_uri(struct sipe_core_private *sipe_private,
341 const gchar *uri)
343 return(g_hash_table_lookup(sipe_private->buddies->uri, uri));
346 struct sipe_buddy *sipe_buddy_find_by_exchange_key(struct sipe_core_private *sipe_private,
347 const gchar *exchange_key)
349 return(g_hash_table_lookup(sipe_private->buddies->exchange_key,
350 exchange_key));
353 void sipe_buddy_foreach(struct sipe_core_private *sipe_private,
354 GHFunc callback,
355 gpointer callback_data)
357 g_hash_table_foreach(sipe_private->buddies->uri,
358 callback,
359 callback_data);
362 static void buddy_free(struct sipe_buddy *buddy)
364 #ifndef _WIN32
366 * We are calling g_hash_table_foreach_steal(). That means that no
367 * key/value deallocation functions are called. Therefore the glib
368 * hash code does not touch the key (buddy->name) or value (buddy)
369 * of the to-be-deleted hash node at all. It follows that we
371 * - MUST free the memory for the key ourselves and
372 * - ARE allowed to do it in this function
374 * Conclusion: glib must be broken on the Windows platform if sipe
375 * crashes with SIGTRAP when closing. You'll have to live
376 * with the memory leak until this is fixed.
378 g_free(buddy->name);
379 #endif
380 g_free(buddy->exchange_key);
381 g_free(buddy->change_key);
382 g_free(buddy->activity);
383 g_free(buddy->meeting_subject);
384 g_free(buddy->meeting_location);
385 g_free(buddy->note);
387 g_free(buddy->cal_start_time);
388 g_free(buddy->cal_free_busy_base64);
389 g_free(buddy->cal_free_busy);
390 g_free(buddy->last_non_cal_activity);
392 sipe_cal_free_working_hours(buddy->cal_working_hours);
394 g_free(buddy->device_name);
395 g_slist_free_full(buddy->groups, buddy_group_free);
396 g_free(buddy);
399 static gboolean buddy_free_cb(SIPE_UNUSED_PARAMETER gpointer key,
400 gpointer buddy,
401 SIPE_UNUSED_PARAMETER gpointer user_data)
403 buddy_free(buddy);
404 /* We must return TRUE as the key/value have already been deleted */
405 return(TRUE);
408 void sipe_buddy_free(struct sipe_core_private *sipe_private)
410 struct sipe_buddies *buddies = sipe_private->buddies;
412 g_hash_table_foreach_steal(buddies->uri,
413 buddy_free_cb,
414 NULL);
416 /* core is being deallocated, remove all its pending photo requests */
417 while (buddies->pending_photo_requests) {
418 struct photo_response_data *data =
419 buddies->pending_photo_requests->data;
420 buddies->pending_photo_requests =
421 g_slist_remove(buddies->pending_photo_requests, data);
422 photo_response_data_free(data);
425 g_hash_table_destroy(buddies->uri);
426 g_hash_table_destroy(buddies->exchange_key);
427 g_free(buddies);
428 sipe_private->buddies = NULL;
431 static void buddy_set_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key,
432 gpointer value,
433 SIPE_UNUSED_PARAMETER gpointer user_data)
435 struct sipe_buddy *buddy = value;
436 GSList *entry = buddy->groups;
438 buddy->is_obsolete = TRUE;
439 while (entry) {
440 ((struct buddy_group_data *) entry->data)->is_obsolete = TRUE;
441 entry = entry->next;
445 void sipe_buddy_update_start(struct sipe_core_private *sipe_private)
447 g_hash_table_foreach(sipe_private->buddies->uri,
448 buddy_set_obsolete_flag,
449 NULL);
452 static gboolean buddy_check_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key,
453 gpointer value,
454 gpointer user_data)
456 struct sipe_core_private *sipe_private = user_data;
457 struct sipe_buddy *buddy = value;
458 const gchar *uri = buddy->name;
460 if (buddy->is_obsolete) {
461 /* all backend buddies in different groups */
462 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC,
463 uri,
464 NULL);
465 GSList *entry = buddies;
467 SIPE_DEBUG_INFO("buddy_check_obsolete_flag: REMOVING %d backend buddies for '%s'",
468 g_slist_length(buddies),
469 uri);
471 while (entry) {
472 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
473 entry->data);
474 entry = entry->next;
476 g_slist_free(buddies);
478 buddy_free(buddy);
479 /* return TRUE as the key/value have already been deleted */
480 return(TRUE);
482 } else {
483 GSList *entry = buddy->groups;
485 while (entry) {
486 struct buddy_group_data *bgd = entry->data;
488 /* next buddy group */
489 entry = entry->next;
491 if (bgd->is_obsolete) {
492 const struct sipe_group *group = bgd->group;
493 sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
494 uri,
495 group->name);
496 SIPE_DEBUG_INFO("buddy_check_obsolete_flag: removing buddy '%s' from group '%s'",
497 uri, group->name);
498 /* this should never be NULL */
499 if (oldb)
500 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
501 oldb);
502 buddy_group_remove(buddy, bgd);
505 return(FALSE);
509 void sipe_buddy_update_finish(struct sipe_core_private *sipe_private)
511 g_hash_table_foreach_remove(sipe_private->buddies->uri,
512 buddy_check_obsolete_flag,
513 sipe_private);
516 gchar *sipe_core_buddy_status(struct sipe_core_public *sipe_public,
517 const gchar *uri,
518 guint activity,
519 const gchar *status_text)
521 struct sipe_buddy *sbuddy;
522 const char *activity_str;
524 if (!sipe_public) return NULL; /* happens on pidgin exit */
526 sbuddy = sipe_buddy_find_by_uri(SIPE_CORE_PRIVATE, uri);
527 if (!sbuddy) return NULL;
529 activity_str = sbuddy->activity ? sbuddy->activity :
530 (activity == SIPE_ACTIVITY_BUSY) || (activity == SIPE_ACTIVITY_BRB) ?
531 status_text : NULL;
533 if (activity_str && sbuddy->note) {
534 return g_strdup_printf("%s - <i>%s</i>", activity_str, sbuddy->note);
535 } else if (activity_str) {
536 return g_strdup(activity_str);
537 } else if (sbuddy->note) {
538 return g_strdup_printf("<i>%s</i>", sbuddy->note);
539 } else {
540 return NULL;
544 gchar *sipe_buddy_get_alias(struct sipe_core_private *sipe_private,
545 const gchar *with)
547 sipe_backend_buddy pbuddy;
548 gchar *alias = NULL;
549 if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
550 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, pbuddy);
552 return alias;
555 void sipe_core_buddy_group(struct sipe_core_public *sipe_public,
556 const gchar *who,
557 const gchar *old_group_name,
558 const gchar *new_group_name)
560 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(SIPE_CORE_PRIVATE,
561 who);
562 struct sipe_group *old_group = NULL;
563 struct sipe_group *new_group;
565 SIPE_DEBUG_INFO("sipe_core_buddy_group: who:%s old_group_name:%s new_group_name:%s",
566 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
568 if (!buddy)
569 /* buddy not in roaming list */
570 return;
572 if (old_group_name) {
573 old_group = sipe_group_find_by_name(SIPE_CORE_PRIVATE, old_group_name);
575 new_group = sipe_group_find_by_name(SIPE_CORE_PRIVATE, new_group_name);
577 if (old_group) {
578 sipe_buddy_remove_group(buddy, old_group);
579 SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy %s removed from old group %s", who, old_group_name);
582 if (!new_group) {
583 sipe_group_create(SIPE_CORE_PRIVATE, new_group_name, who);
584 } else {
585 sipe_buddy_insert_group(buddy, new_group);
586 if (sipe_ucs_is_migrated(SIPE_CORE_PRIVATE))
587 sipe_ucs_group_add_buddy(SIPE_CORE_PRIVATE,
588 new_group,
589 buddy);
590 else
591 sipe_group_update_buddy(SIPE_CORE_PRIVATE, buddy);
595 void sipe_core_buddy_add(struct sipe_core_public *sipe_public,
596 const gchar *uri,
597 const gchar *group_name)
599 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
601 if (!sipe_buddy_find_by_uri(sipe_private, uri))
602 sipe_buddy_add(sipe_private,
603 uri,
604 NULL,
605 NULL);
606 else
607 SIPE_DEBUG_INFO("sipe_core_buddy_add: buddy %s already in internal list",
608 uri);
610 sipe_core_buddy_group(sipe_public,
611 uri,
612 NULL,
613 group_name);
616 void sipe_buddy_remove(struct sipe_core_private *sipe_private,
617 struct sipe_buddy *buddy)
619 struct sipe_buddies *buddies = sipe_private->buddies;
620 const gchar *uri = buddy->name;
621 GSList *entry = buddy->groups;
622 gchar *action_name = sipe_utils_presence_key(uri);
624 sipe_schedule_cancel(sipe_private, action_name);
625 g_free(action_name);
627 /* If the buddy still has groups, we need to delete backend buddies */
628 while (entry) {
629 const struct sipe_group *group = ((struct buddy_group_data *) entry->data)->group;
630 sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
631 uri,
632 group->name);
633 /* this should never be NULL */
634 if (oldb)
635 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, oldb);
637 entry = entry->next;
640 g_hash_table_remove(buddies->uri, uri);
641 if (buddy->exchange_key)
642 g_hash_table_remove(buddies->exchange_key,
643 buddy->exchange_key);
645 buddy_free(buddy);
649 * Unassociates buddy from group first.
650 * Then see if no groups left, removes buddy completely.
651 * Otherwise updates buddy groups on server.
653 void sipe_core_buddy_remove(struct sipe_core_public *sipe_public,
654 const gchar *uri,
655 const gchar *group_name)
657 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
658 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
659 uri);
660 struct sipe_group *group = NULL;
662 if (!buddy) return;
664 if (group_name) {
665 group = sipe_group_find_by_name(sipe_private, group_name);
666 if (group) {
667 sipe_buddy_remove_group(buddy, group);
668 SIPE_DEBUG_INFO("sipe_core_buddy_remove: buddy %s removed from group %s",
669 uri, group->name);
673 if (g_slist_length(buddy->groups) < 1) {
675 if (sipe_ucs_is_migrated(sipe_private)) {
676 sipe_ucs_group_remove_buddy(sipe_private,
677 group,
678 buddy);
679 } else {
680 gchar *request = g_strdup_printf("<m:URI>%s</m:URI>",
681 buddy->name);
682 sip_soap_request(sipe_private,
683 "deleteContact",
684 request);
685 g_free(request);
688 sipe_buddy_remove(sipe_private, buddy);
689 } else {
690 if (sipe_ucs_is_migrated(sipe_private)) {
691 sipe_ucs_group_remove_buddy(sipe_private,
692 group,
693 buddy);
694 } else
695 /* updates groups on server */
696 sipe_group_update_buddy(sipe_private, buddy);
700 void sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
701 const gchar *uri,
702 guint activity)
704 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
705 struct sipe_buddy *sbuddy = sipe_buddy_find_by_uri(sipe_private,
706 uri);
708 if (!sbuddy) return;
710 /* Check if on 2005 system contact's calendar,
711 * then set/preserve it.
713 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
714 sipe_backend_buddy_set_status(sipe_public, uri, activity);
715 } else {
716 sipe_ocs2005_apply_calendar_status(sipe_private,
717 sbuddy,
718 sipe_status_activity_to_token(activity));
722 void sipe_core_buddy_tooltip_info(struct sipe_core_public *sipe_public,
723 const gchar *uri,
724 const gchar *status_name,
725 gboolean is_online,
726 struct sipe_backend_buddy_tooltip *tooltip)
728 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
729 gchar *note = NULL;
730 gboolean is_oof_note = FALSE;
731 const gchar *activity = NULL;
732 gchar *calendar = NULL;
733 const gchar *meeting_subject = NULL;
734 const gchar *meeting_location = NULL;
735 gchar *access_text = NULL;
737 #define SIPE_ADD_BUDDY_INFO(l, t) \
739 gchar *tmp = g_markup_escape_text((t), -1); \
740 sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), tmp); \
741 g_free(tmp); \
743 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) \
744 sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), (t))
746 if (sipe_public) { /* happens on pidgin exit */
747 struct sipe_buddy *sbuddy = sipe_buddy_find_by_uri(sipe_private,
748 uri);
749 if (sbuddy) {
750 note = sbuddy->note;
751 is_oof_note = sbuddy->is_oof_note;
752 activity = sbuddy->activity;
753 calendar = sipe_cal_get_description(sbuddy);
754 meeting_subject = sbuddy->meeting_subject;
755 meeting_location = sbuddy->meeting_location;
757 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
758 gboolean is_group_access = FALSE;
759 const int container_id = sipe_ocs2007_find_access_level(sipe_private,
760 "user",
761 sipe_get_no_sip_uri(uri),
762 &is_group_access);
763 const char *access_level = sipe_ocs2007_access_level_name(container_id);
764 access_text = is_group_access ?
765 g_strdup(access_level) :
766 g_strdup_printf(SIPE_OCS2007_INDENT_MARKED_FMT,
767 access_level);
771 if (is_online) {
772 const gchar *status_str = activity ? activity : status_name;
774 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
776 if (is_online && !is_empty(calendar)) {
777 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
779 g_free(calendar);
780 if (!is_empty(meeting_location)) {
781 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", uri, meeting_location);
782 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
784 if (!is_empty(meeting_subject)) {
785 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", uri, meeting_subject);
786 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
788 if (note) {
789 gchar *note_italics = g_strdup_printf("<i>%s</i>", note);
790 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", uri, note);
791 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
792 note_italics);
793 g_free(note_italics);
795 if (access_text) {
796 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
797 g_free(access_text);
801 void sipe_buddy_update_property(struct sipe_core_private *sipe_private,
802 const char *uri,
803 sipe_buddy_info_fields propkey,
804 char *property_value)
806 GSList *buddies, *entry;
808 if (property_value)
809 property_value = g_strstrip(property_value);
811 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
812 while (entry) {
813 gchar *prop_str;
814 sipe_backend_buddy p_buddy = entry->data;
816 /* for Display Name */
817 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
818 gchar *alias;
819 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
820 if (property_value && sipe_is_bad_alias(uri, alias)) {
821 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
822 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
824 g_free(alias);
826 alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
827 if (!is_empty(property_value) &&
828 (!sipe_strequal(property_value, alias) || is_empty(alias)) )
830 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
831 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
833 g_free(alias);
835 /* for other properties */
836 else {
837 if (!is_empty(property_value)) {
838 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
839 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
840 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
842 g_free(prop_str);
846 entry = entry->next;
848 g_slist_free(buddies);
852 struct ms_dlx_data;
853 struct ms_dlx_data {
854 GSList *search_rows;
855 gchar *other;
856 guint max_returns;
857 sipe_svc_callback *callback;
858 struct sipe_svc_session *session;
859 gchar *wsse_security;
860 struct sipe_backend_search_token *token;
861 /* must call ms_dlx_free() */
862 void (*failed_callback)(struct sipe_core_private *sipe_private,
863 struct ms_dlx_data *mdd);
866 static void ms_dlx_free(struct ms_dlx_data *mdd)
868 sipe_utils_slist_free_full(mdd->search_rows, g_free);
869 sipe_svc_session_close(mdd->session);
870 g_free(mdd->other);
871 g_free(mdd->wsse_security);
872 g_free(mdd);
875 #define SIPE_SOAP_SEARCH_ROW "<m:row m:attrib=\"%s\" m:value=\"%s\"/>"
876 #define DLX_SEARCH_ITEM \
877 "<AbEntryRequest.ChangeSearchQuery>" \
878 " <SearchOn>%s</SearchOn>" \
879 " <Value>%s</Value>" \
880 "</AbEntryRequest.ChangeSearchQuery>"
882 static gchar * prepare_buddy_search_query(GSList *query_rows, gboolean use_dlx) {
883 gchar **attrs = g_new(gchar *, (g_slist_length(query_rows) / 2) + 1);
884 guint i = 0;
885 gchar *query = NULL;
887 while (query_rows) {
888 gchar *attr;
889 gchar *value;
891 attr = query_rows->data;
892 query_rows = g_slist_next(query_rows);
893 value = query_rows->data;
894 query_rows = g_slist_next(query_rows);
896 if (!attr || !value)
897 break;
899 attrs[i++] = g_markup_printf_escaped(use_dlx ? DLX_SEARCH_ITEM : SIPE_SOAP_SEARCH_ROW,
900 attr, value);
902 attrs[i] = NULL;
904 if (i) {
905 query = g_strjoinv(NULL, attrs);
906 SIPE_DEBUG_INFO("prepare_buddy_search_query: rows:\n%s",
907 query ? query : "");
910 g_strfreev(attrs);
912 return query;
915 static void ms_dlx_webticket(struct sipe_core_private *sipe_private,
916 const gchar *base_uri,
917 const gchar *auth_uri,
918 const gchar *wsse_security,
919 SIPE_UNUSED_PARAMETER const gchar *failure_msg,
920 gpointer callback_data)
922 struct ms_dlx_data *mdd = callback_data;
924 if (wsse_security) {
925 gchar *query = prepare_buddy_search_query(mdd->search_rows, TRUE);
927 SIPE_DEBUG_INFO("ms_dlx_webticket: got ticket for %s",
928 base_uri);
930 if (sipe_svc_ab_entry_request(sipe_private,
931 mdd->session,
932 auth_uri,
933 wsse_security,
934 query,
935 g_slist_length(mdd->search_rows) / 2,
936 mdd->max_returns,
937 mdd->callback,
938 mdd)) {
940 /* keep webticket security token for potential further use */
941 mdd->wsse_security = g_strdup(wsse_security);
943 /* callback data passed down the line */
944 mdd = NULL;
946 g_free(query);
948 } else {
949 /* no ticket: this will show the minmum information */
950 SIPE_DEBUG_ERROR("ms_dlx_webticket: no web ticket for %s",
951 base_uri);
954 if (mdd)
955 mdd->failed_callback(sipe_private, mdd);
958 static void ms_dlx_webticket_request(struct sipe_core_private *sipe_private,
959 struct ms_dlx_data *mdd)
961 if (!sipe_webticket_request(sipe_private,
962 mdd->session,
963 sipe_private->dlx_uri,
964 "AddressBookWebTicketBearer",
965 ms_dlx_webticket,
966 mdd)) {
967 SIPE_DEBUG_ERROR("ms_dlx_webticket_request: couldn't request webticket for %s",
968 sipe_private->dlx_uri);
969 mdd->failed_callback(sipe_private, mdd);
973 static void search_contacts_finalize(struct sipe_core_private *sipe_private,
974 struct sipe_backend_search_results *results,
975 guint match_count,
976 gboolean more)
978 gchar *secondary = g_strdup_printf(
979 dngettext(PACKAGE_NAME,
980 "Found %d contact%s:",
981 "Found %d contacts%s:", match_count),
982 match_count, more ? _(" (more matched your query)") : "");
984 sipe_backend_search_results_finalize(SIPE_CORE_PUBLIC,
985 results,
986 secondary,
987 more);
988 g_free(secondary);
991 static void search_ab_entry_response(struct sipe_core_private *sipe_private,
992 const gchar *uri,
993 SIPE_UNUSED_PARAMETER const gchar *raw,
994 sipe_xml *soap_body,
995 gpointer callback_data)
997 struct ms_dlx_data *mdd = callback_data;
999 if (soap_body) {
1000 const sipe_xml *node;
1001 struct sipe_backend_search_results *results;
1002 GHashTable *found;
1004 SIPE_DEBUG_INFO("search_ab_entry_response: received valid SOAP message from service %s",
1005 uri);
1007 /* any matches? */
1008 node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry");
1009 if (!node) {
1010 SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: no matches");
1011 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1012 mdd->token,
1013 _("No contacts found"));
1014 ms_dlx_free(mdd);
1015 return;
1018 /* OK, we found something - show the results to the user */
1019 results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
1020 mdd->token);
1021 if (!results) {
1022 SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: Unable to display the search results.");
1023 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1024 mdd->token,
1025 _("Unable to display the search results"));
1026 ms_dlx_free(mdd);
1027 return;
1030 /* SearchAbEntryResult can contain duplicates */
1031 found = g_hash_table_new_full(g_str_hash, g_str_equal,
1032 g_free, NULL);
1034 for (/* initialized above */ ; node; node = sipe_xml_twin(node)) {
1035 const sipe_xml *attrs;
1036 gchar *sip_uri = NULL;
1037 gchar *displayname = NULL;
1038 gchar *company = NULL;
1039 gchar *country = NULL;
1040 gchar *email = NULL;
1042 for (attrs = sipe_xml_child(node, "Attributes/Attribute");
1043 attrs;
1044 attrs = sipe_xml_twin(attrs)) {
1045 gchar *name = sipe_xml_data(sipe_xml_child(attrs,
1046 "Name"));
1047 gchar *value = sipe_xml_data(sipe_xml_child(attrs,
1048 "Value"));
1050 if (!is_empty(value)) {
1051 if (sipe_strcase_equal(name, "msrtcsip-primaryuseraddress")) {
1052 g_free(sip_uri);
1053 sip_uri = value;
1054 value = NULL;
1055 } else if (sipe_strcase_equal(name, "displayname")) {
1056 g_free(displayname);
1057 displayname = value;
1058 value = NULL;
1059 } else if (sipe_strcase_equal(name, "mail")) {
1060 g_free(email);
1061 email = value;
1062 value = NULL;
1063 } else if (sipe_strcase_equal(name, "company")) {
1064 g_free(company);
1065 company = value;
1066 value = NULL;
1067 } else if (sipe_strcase_equal(name, "country")) {
1068 g_free(country);
1069 country = value;
1070 value = NULL;
1074 g_free(value);
1075 g_free(name);
1078 if (sip_uri && !g_hash_table_lookup(found, sip_uri)) {
1079 gchar **uri_parts = g_strsplit(sip_uri, ":", 2);
1080 sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
1081 results,
1082 uri_parts[1],
1083 displayname,
1084 company,
1085 country,
1086 email);
1087 g_strfreev(uri_parts);
1089 g_hash_table_insert(found, sip_uri, (gpointer) TRUE);
1090 sip_uri = NULL;
1093 g_free(email);
1094 g_free(country);
1095 g_free(company);
1096 g_free(displayname);
1097 g_free(sip_uri);
1100 search_contacts_finalize(sipe_private, results,
1101 g_hash_table_size(found),
1102 FALSE);
1103 g_hash_table_destroy(found);
1104 ms_dlx_free(mdd);
1106 } else {
1107 mdd->failed_callback(sipe_private, mdd);
1111 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
1112 struct sipmsg *msg,
1113 struct transaction *trans)
1115 struct sipe_backend_search_token *token = trans->payload->data;
1116 struct sipe_backend_search_results *results;
1117 sipe_xml *searchResults;
1118 const sipe_xml *mrow;
1119 guint match_count = 0;
1120 gboolean more = FALSE;
1122 /* valid response? */
1123 if (msg->response != 200) {
1124 SIPE_DEBUG_ERROR("process_search_contact_response: request failed (%d)",
1125 msg->response);
1126 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1127 token,
1128 _("Contact search failed"));
1129 return(FALSE);
1132 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
1134 /* valid XML? */
1135 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
1136 if (!searchResults) {
1137 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
1138 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1139 token,
1140 _("Contact search failed"));
1141 return(FALSE);
1144 /* any matches? */
1145 mrow = sipe_xml_child(searchResults, "Body/Array/row");
1146 if (!mrow) {
1147 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: no matches");
1148 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1149 token,
1150 _("No contacts found"));
1152 sipe_xml_free(searchResults);
1153 return(FALSE);
1156 /* OK, we found something - show the results to the user */
1157 results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
1158 trans->payload->data);
1159 if (!results) {
1160 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: Unable to display the search results.");
1161 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1162 token,
1163 _("Unable to display the search results"));
1165 sipe_xml_free(searchResults);
1166 return FALSE;
1169 for (/* initialized above */ ; mrow; mrow = sipe_xml_twin(mrow)) {
1170 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
1171 sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
1172 results,
1173 uri_parts[1],
1174 sipe_xml_attribute(mrow, "displayName"),
1175 sipe_xml_attribute(mrow, "company"),
1176 sipe_xml_attribute(mrow, "country"),
1177 sipe_xml_attribute(mrow, "email"));
1178 g_strfreev(uri_parts);
1179 match_count++;
1182 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
1183 char *data = sipe_xml_data(mrow);
1184 more = (g_ascii_strcasecmp(data, "true") == 0);
1185 g_free(data);
1188 search_contacts_finalize(sipe_private, results, match_count, more);
1189 sipe_xml_free(searchResults);
1191 return(TRUE);
1194 static void search_soap_request(struct sipe_core_private *sipe_private,
1195 struct sipe_backend_search_token *token,
1196 GSList *search_rows)
1198 gchar *query = prepare_buddy_search_query(search_rows, FALSE);
1199 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1201 payload->data = token;
1203 sip_soap_directory_search(sipe_private,
1204 100,
1205 query,
1206 process_search_contact_response,
1207 payload);
1208 g_free(query);
1211 static void search_ab_entry_failed(struct sipe_core_private *sipe_private,
1212 struct ms_dlx_data *mdd)
1214 /* error using [MS-DLX] server, retry using Active Directory */
1215 search_soap_request(sipe_private, mdd->token, mdd->search_rows);
1216 ms_dlx_free(mdd);
1219 void sipe_core_buddy_search(struct sipe_core_public *sipe_public,
1220 struct sipe_backend_search_token *token,
1221 const gchar *given_name,
1222 const gchar *surname,
1223 const gchar *email,
1224 const gchar *company,
1225 const gchar *country)
1227 GSList *query_rows = NULL;
1229 #define ADD_QUERY_ROW(attr, val) \
1230 if (val) { \
1231 query_rows = g_slist_append(query_rows, g_strdup(attr)); \
1232 query_rows = g_slist_append(query_rows, g_strdup(val)); \
1235 ADD_QUERY_ROW("givenName", given_name);
1236 ADD_QUERY_ROW("sn", surname);
1237 ADD_QUERY_ROW("mail", email);
1238 ADD_QUERY_ROW("company", company);
1239 ADD_QUERY_ROW("c", country);
1241 if (query_rows) {
1242 if (SIPE_CORE_PRIVATE->dlx_uri != NULL) {
1243 struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
1245 mdd->search_rows = query_rows;
1246 mdd->max_returns = 100;
1247 mdd->callback = search_ab_entry_response;
1248 mdd->failed_callback = search_ab_entry_failed;
1249 mdd->session = sipe_svc_session_start();
1250 mdd->token = token;
1252 ms_dlx_webticket_request(SIPE_CORE_PRIVATE, mdd);
1254 } else {
1255 /* no [MS-DLX] server, use Active Directory search instead */
1256 search_soap_request(SIPE_CORE_PRIVATE, token, query_rows);
1257 sipe_utils_slist_free_full(query_rows, g_free);
1259 } else
1260 sipe_backend_search_failed(sipe_public,
1261 token,
1262 _("Invalid contact search query"));
1265 static void get_info_finalize(struct sipe_core_private *sipe_private,
1266 struct sipe_backend_buddy_info *info,
1267 const gchar *uri,
1268 const gchar *server_alias,
1269 const gchar *email)
1271 sipe_backend_buddy bbuddy;
1272 struct sipe_buddy *sbuddy;
1273 gchar *alias;
1274 gchar *value;
1276 if (!info) {
1277 info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1278 } else {
1279 sipe_backend_buddy_info_break(SIPE_CORE_PUBLIC, info);
1281 if (!info)
1282 return;
1284 bbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL);
1286 if (is_empty(server_alias)) {
1287 value = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC,
1288 bbuddy);
1289 if (value) {
1290 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1291 info,
1292 SIPE_BUDDY_INFO_DISPLAY_NAME,
1293 value);
1295 } else {
1296 value = g_strdup(server_alias);
1299 /* present alias if it differs from server alias */
1300 alias = sipe_backend_buddy_get_local_alias(SIPE_CORE_PUBLIC, bbuddy);
1301 if (alias && !sipe_strequal(alias, value))
1303 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1304 info,
1305 SIPE_BUDDY_INFO_ALIAS,
1306 alias);
1308 g_free(alias);
1309 g_free(value);
1311 if (is_empty(email)) {
1312 value = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC,
1313 bbuddy,
1314 SIPE_BUDDY_INFO_EMAIL);
1315 if (value) {
1316 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1317 info,
1318 SIPE_BUDDY_INFO_EMAIL,
1319 value);
1320 g_free(value);
1324 value = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC,
1325 bbuddy,
1326 SIPE_BUDDY_INFO_SITE);
1327 if (value) {
1328 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1329 info,
1330 SIPE_BUDDY_INFO_SITE,
1331 value);
1332 g_free(value);
1335 sbuddy = sipe_buddy_find_by_uri(sipe_private, uri);
1336 if (sbuddy && sbuddy->device_name) {
1337 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1338 info,
1339 SIPE_BUDDY_INFO_DEVICE,
1340 sbuddy->device_name);
1343 sipe_backend_buddy_info_finalize(SIPE_CORE_PUBLIC, info, uri);
1347 static void get_info_ab_entry_response(struct sipe_core_private *sipe_private,
1348 const gchar *uri,
1349 SIPE_UNUSED_PARAMETER const gchar *raw,
1350 sipe_xml *soap_body,
1351 gpointer callback_data)
1353 struct ms_dlx_data *mdd = callback_data;
1354 struct sipe_backend_buddy_info *info = NULL;
1355 gchar *server_alias = NULL;
1356 gchar *email = NULL;
1358 if (soap_body) {
1359 const sipe_xml *node;
1361 SIPE_DEBUG_INFO("get_info_ab_entry_response: received valid SOAP message from service %s",
1362 uri);
1364 info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1366 for (node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
1367 node;
1368 node = sipe_xml_twin(node)) {
1369 gchar *name = sipe_xml_data(sipe_xml_child(node,
1370 "Name"));
1371 gchar *value = sipe_xml_data(sipe_xml_child(node,
1372 "Value"));
1373 const sipe_xml *values = sipe_xml_child(node,
1374 "Values");
1376 /* Single value entries */
1377 if (!is_empty(value)) {
1379 if (sipe_strcase_equal(name, "displayname")) {
1380 g_free(server_alias);
1381 server_alias = value;
1382 value = NULL;
1383 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1384 info,
1385 SIPE_BUDDY_INFO_DISPLAY_NAME,
1386 server_alias);
1387 } else if (sipe_strcase_equal(name, "mail")) {
1388 g_free(email);
1389 email = value;
1390 value = NULL;
1391 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1392 info,
1393 SIPE_BUDDY_INFO_EMAIL,
1394 email);
1395 } else if (sipe_strcase_equal(name, "title")) {
1396 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1397 info,
1398 SIPE_BUDDY_INFO_JOB_TITLE,
1399 value);
1400 } else if (sipe_strcase_equal(name, "company")) {
1401 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1402 info,
1403 SIPE_BUDDY_INFO_COMPANY,
1404 value);
1405 } else if (sipe_strcase_equal(name, "country")) {
1406 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1407 info,
1408 SIPE_BUDDY_INFO_COUNTRY,
1409 value);
1412 } else if (values) {
1413 gchar *first = sipe_xml_data(sipe_xml_child(values,
1414 "string"));
1416 if (sipe_strcase_equal(name, "telephonenumber")) {
1417 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1418 info,
1419 SIPE_BUDDY_INFO_WORK_PHONE,
1420 first);
1423 g_free(first);
1426 g_free(value);
1427 g_free(name);
1431 /* this will show the minmum information */
1432 get_info_finalize(sipe_private,
1433 info,
1434 mdd->other,
1435 server_alias,
1436 email);
1438 g_free(email);
1439 g_free(server_alias);
1440 ms_dlx_free(mdd);
1443 static gboolean process_get_info_response(struct sipe_core_private *sipe_private,
1444 struct sipmsg *msg,
1445 struct transaction *trans)
1447 const gchar *uri = trans->payload->data;
1448 struct sipe_backend_buddy_info *info = NULL;
1449 gchar *server_alias = NULL;
1450 gchar *email = NULL;
1452 SIPE_DEBUG_INFO("Fetching %s's user info for %s",
1453 uri, sipe_private->username);
1455 if (msg->response != 200) {
1456 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
1457 } else {
1458 sipe_xml *searchResults;
1459 const sipe_xml *mrow;
1461 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s",
1462 msg->body ? msg->body : "");
1464 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
1465 if (!searchResults) {
1467 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
1469 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
1470 const gchar *value;
1471 gchar *phone_number;
1473 info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1475 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
1476 email = g_strdup(sipe_xml_attribute(mrow, "email"));
1477 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
1480 * For 2007 system we will take this from ContactCard -
1481 * it has cleaner tel: URIs at least
1483 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1484 char *tel_uri = sip_to_tel_uri(phone_number);
1485 /* trims its parameters, so call first */
1486 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
1487 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
1488 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
1489 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
1490 g_free(tel_uri);
1492 sipe_backend_buddy_refresh_properties(SIPE_CORE_PUBLIC,
1493 uri);
1496 if (!is_empty(server_alias)) {
1497 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1498 info,
1499 SIPE_BUDDY_INFO_DISPLAY_NAME,
1500 server_alias);
1502 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
1503 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1504 info,
1505 SIPE_BUDDY_INFO_JOB_TITLE,
1506 value);
1508 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
1509 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1510 info,
1511 SIPE_BUDDY_INFO_OFFICE,
1512 value);
1514 if (!is_empty(phone_number)) {
1515 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1516 info,
1517 SIPE_BUDDY_INFO_WORK_PHONE,
1518 phone_number);
1520 g_free(phone_number);
1521 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
1522 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1523 info,
1524 SIPE_BUDDY_INFO_COMPANY,
1525 value);
1527 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
1528 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1529 info,
1530 SIPE_BUDDY_INFO_CITY,
1531 value);
1533 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
1534 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1535 info,
1536 SIPE_BUDDY_INFO_STATE,
1537 value);
1539 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
1540 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1541 info,
1542 SIPE_BUDDY_INFO_COUNTRY,
1543 value);
1545 if (!is_empty(email)) {
1546 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1547 info,
1548 SIPE_BUDDY_INFO_EMAIL,
1549 email);
1552 sipe_xml_free(searchResults);
1555 /* this will show the minmum information */
1556 get_info_finalize(sipe_private,
1557 info,
1558 uri,
1559 server_alias,
1560 email);
1562 g_free(server_alias);
1563 g_free(email);
1565 return TRUE;
1568 static void get_info_ab_entry_failed(struct sipe_core_private *sipe_private,
1569 struct ms_dlx_data *mdd)
1571 /* error using [MS-DLX] server, retry using Active Directory */
1572 gchar *query = prepare_buddy_search_query(mdd->search_rows, FALSE);
1573 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1575 payload->destroy = g_free;
1576 payload->data = mdd->other;
1577 mdd->other = NULL;
1579 sip_soap_directory_search(sipe_private,
1581 query,
1582 process_get_info_response,
1583 payload);
1585 ms_dlx_free(mdd);
1586 g_free(query);
1589 void sipe_core_buddy_get_info(struct sipe_core_public *sipe_public,
1590 const gchar *who)
1592 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1594 if (sipe_private->dlx_uri) {
1595 struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
1597 mdd->search_rows = g_slist_append(mdd->search_rows, g_strdup("msRTCSIP-PrimaryUserAddress"));
1598 mdd->search_rows = g_slist_append(mdd->search_rows, g_strdup(who));
1600 mdd->other = g_strdup(who);
1601 mdd->max_returns = 1;
1602 mdd->callback = get_info_ab_entry_response;
1603 mdd->failed_callback = get_info_ab_entry_failed;
1604 mdd->session = sipe_svc_session_start();
1606 ms_dlx_webticket_request(sipe_private, mdd);
1608 } else {
1609 /* no [MS-DLX] server, use Active Directory search instead */
1610 gchar *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW,
1611 "msRTCSIP-PrimaryUserAddress",
1612 who);
1613 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1615 SIPE_DEBUG_INFO("sipe_core_buddy_get_info: row: %s",
1616 row ? row : "");
1618 payload->destroy = g_free;
1619 payload->data = g_strdup(who);
1621 sip_soap_directory_search(sipe_private,
1623 row,
1624 process_get_info_response,
1625 payload);
1626 g_free(row);
1630 static void photo_response_data_free(struct photo_response_data *data)
1632 g_free(data->who);
1633 g_free(data->photo_hash);
1634 if (data->request) {
1635 sipe_http_request_cancel(data->request);
1637 g_free(data);
1640 static void process_buddy_photo_response(struct sipe_core_private *sipe_private,
1641 guint status,
1642 GSList *headers,
1643 const char *body,
1644 gpointer data)
1646 struct photo_response_data *rdata = (struct photo_response_data *) data;
1648 rdata->request = NULL;
1650 if (status == SIPE_HTTP_STATUS_OK) {
1651 const gchar *len_str = sipe_utils_nameval_find(headers,
1652 "Content-Length");
1653 if (len_str) {
1654 gsize photo_size = atoi(len_str);
1655 gpointer photo = g_new(char, photo_size);
1657 if (photo) {
1658 memcpy(photo, body, photo_size);
1660 sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC,
1661 rdata->who,
1662 photo,
1663 photo_size,
1664 rdata->photo_hash);
1669 sipe_private->buddies->pending_photo_requests =
1670 g_slist_remove(sipe_private->buddies->pending_photo_requests, rdata);
1672 photo_response_data_free(rdata);
1675 static gchar *create_x_ms_webticket_header(const gchar *wsse_security)
1677 gchar *assertion = sipe_xml_extract_raw(wsse_security, "saml:Assertion", TRUE);
1678 gchar *wsse_security_base64;
1679 gchar *x_ms_webticket_header;
1681 if (!assertion) {
1682 return NULL;
1685 wsse_security_base64 = g_base64_encode((const guchar *)assertion,
1686 strlen(assertion));
1687 x_ms_webticket_header = g_strdup_printf("X-MS-WebTicket: opaque=%s\r\n",
1688 wsse_security_base64);
1690 g_free(assertion);
1691 g_free(wsse_security_base64);
1693 return x_ms_webticket_header;
1696 static void get_photo_ab_entry_response(struct sipe_core_private *sipe_private,
1697 const gchar *uri,
1698 SIPE_UNUSED_PARAMETER const gchar *raw,
1699 sipe_xml *soap_body,
1700 gpointer callback_data)
1702 struct ms_dlx_data *mdd = callback_data;
1703 gchar *photo_rel_path = NULL;
1704 gchar *photo_hash = NULL;
1705 const gchar *photo_hash_old =
1706 sipe_backend_buddy_get_photo_hash(SIPE_CORE_PUBLIC, mdd->other);
1708 if (soap_body) {
1709 const sipe_xml *node;
1711 SIPE_DEBUG_INFO("get_photo_ab_entry_response: received valid SOAP message from service %s",
1712 uri);
1714 for (node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
1715 node;
1716 node = sipe_xml_twin(node)) {
1717 gchar *name = sipe_xml_data(sipe_xml_child(node, "Name"));
1718 gchar *value = sipe_xml_data(sipe_xml_child(node, "Value"));
1720 if (!is_empty(value)) {
1721 if (sipe_strcase_equal(name, "PhotoRelPath")) {
1722 g_free(photo_rel_path);
1723 photo_rel_path = value;
1724 value = NULL;
1725 } else if (sipe_strcase_equal(name, "PhotoHash")) {
1726 g_free(photo_hash);
1727 photo_hash = value;
1728 value = NULL;
1732 g_free(value);
1733 g_free(name);
1737 if (sipe_private->addressbook_uri && photo_rel_path &&
1738 photo_hash && !sipe_strequal(photo_hash, photo_hash_old)) {
1739 gchar *photo_url = g_strdup_printf("%s/%s",
1740 sipe_private->addressbook_uri, photo_rel_path);
1741 gchar *x_ms_webticket_header = create_x_ms_webticket_header(mdd->wsse_security);
1743 struct photo_response_data *data = g_new(struct photo_response_data, 1);
1744 data->who = g_strdup(mdd->other);
1745 data->photo_hash = photo_hash;
1746 photo_hash = NULL;
1748 data->request = sipe_http_request_get(sipe_private,
1749 photo_url,
1750 x_ms_webticket_header,
1751 process_buddy_photo_response,
1752 data);
1754 if (data->request) {
1755 sipe_private->buddies->pending_photo_requests =
1756 g_slist_append(sipe_private->buddies->pending_photo_requests, data);
1757 sipe_http_request_ready(data->request);
1758 } else {
1759 photo_response_data_free(data);
1762 g_free(x_ms_webticket_header);
1763 g_free(photo_url);
1766 g_free(photo_rel_path);
1767 g_free(photo_hash);
1768 ms_dlx_free(mdd);
1771 static void get_photo_ab_entry_failed(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
1772 struct ms_dlx_data *mdd)
1774 ms_dlx_free(mdd);
1777 static void buddy_fetch_photo(struct sipe_core_private *sipe_private,
1778 const gchar *uri)
1780 if (sipe_backend_uses_photo()) {
1782 /* Lync 2013 or newer: use UCS */
1783 if (SIPE_CORE_PRIVATE_FLAG_IS(LYNC2013)) {
1785 sipe_ucs_get_photo(sipe_private, uri);
1787 /* Lync 2010: use [MS-DLX] */
1788 } else if (sipe_private->dlx_uri &&
1789 sipe_private->addressbook_uri) {
1790 struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
1792 mdd->search_rows = g_slist_append(mdd->search_rows, g_strdup("msRTCSIP-PrimaryUserAddress"));
1793 mdd->search_rows = g_slist_append(mdd->search_rows, g_strdup(uri));
1795 mdd->other = g_strdup(uri);
1796 mdd->max_returns = 1;
1797 mdd->callback = get_photo_ab_entry_response;
1798 mdd->failed_callback = get_photo_ab_entry_failed;
1799 mdd->session = sipe_svc_session_start();
1801 ms_dlx_webticket_request(sipe_private, mdd);
1806 static void buddy_refresh_photos_cb(gpointer uri,
1807 SIPE_UNUSED_PARAMETER gpointer value,
1808 gpointer sipe_private)
1810 buddy_fetch_photo(sipe_private, uri);
1813 void sipe_buddy_refresh_photos(struct sipe_core_private *sipe_private)
1815 g_hash_table_foreach(sipe_private->buddies->uri,
1816 buddy_refresh_photos_cb,
1817 sipe_private);
1820 /* Buddy menu callbacks*/
1822 void sipe_core_buddy_new_chat(struct sipe_core_public *sipe_public,
1823 const gchar *who)
1825 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1827 /* 2007+ conference */
1828 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1829 sipe_conf_add(sipe_private, who);
1831 /* 2005- multiparty chat */
1832 } else {
1833 gchar *self = sip_uri_self(sipe_private);
1834 struct sip_session *session;
1836 session = sipe_session_add_chat(sipe_private,
1837 NULL,
1838 TRUE,
1839 self);
1840 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
1841 session->chat_session,
1842 session->chat_session->title,
1843 self);
1844 g_free(self);
1846 sipe_im_invite(sipe_private, session, who,
1847 NULL, NULL, NULL, FALSE);
1851 void sipe_core_buddy_send_email(struct sipe_core_public *sipe_public,
1852 const gchar *who)
1854 sipe_backend_buddy buddy = sipe_backend_buddy_find(sipe_public,
1855 who,
1856 NULL);
1857 gchar *email = sipe_backend_buddy_get_string(sipe_public,
1858 buddy,
1859 SIPE_BUDDY_INFO_EMAIL);
1861 if (email) {
1862 gchar *command_line = g_strdup_printf(
1863 #ifdef _WIN32
1864 "cmd /c start"
1865 #else
1866 "xdg-email"
1867 #endif
1868 " mailto:%s", email);
1869 g_free(email);
1871 SIPE_DEBUG_INFO("sipe_core_buddy_send_email: going to call email client: %s",
1872 command_line);
1873 g_spawn_command_line_async(command_line, NULL);
1874 g_free(command_line);
1876 } else {
1877 SIPE_DEBUG_INFO("sipe_core_buddy_send_email: no email address stored for buddy=%s",
1878 who);
1882 /* Buddy menu */
1884 static struct sipe_backend_buddy_menu *buddy_menu_phone(struct sipe_core_public *sipe_public,
1885 struct sipe_backend_buddy_menu *menu,
1886 sipe_backend_buddy buddy,
1887 sipe_buddy_info_fields id_phone,
1888 sipe_buddy_info_fields id_display,
1889 const gchar *type)
1891 gchar *phone = sipe_backend_buddy_get_string(sipe_public,
1892 buddy,
1893 id_phone);
1894 if (phone) {
1895 gchar *display = sipe_backend_buddy_get_string(sipe_public,
1896 buddy,
1897 id_display);
1898 gchar *tmp = NULL;
1899 gchar *label = g_strdup_printf("%s %s",
1900 type,
1901 display ? display :
1902 (tmp = sip_tel_uri_denormalize(phone)));
1903 menu = sipe_backend_buddy_menu_add(sipe_public,
1904 menu,
1905 label,
1906 SIPE_BUDDY_MENU_MAKE_CALL,
1907 phone);
1908 g_free(tmp);
1909 g_free(label);
1910 g_free(display);
1911 g_free(phone);
1914 return(menu);
1917 struct sipe_backend_buddy_menu *sipe_core_buddy_create_menu(struct sipe_core_public *sipe_public,
1918 const gchar *buddy_name,
1919 struct sipe_backend_buddy_menu *menu)
1921 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1922 sipe_backend_buddy buddy = sipe_backend_buddy_find(sipe_public,
1923 buddy_name,
1924 NULL);
1925 gchar *self = sip_uri_self(sipe_private);
1927 SIPE_SESSION_FOREACH {
1928 if (!sipe_strcase_equal(self, buddy_name) && session->chat_session)
1930 struct sipe_chat_session *chat_session = session->chat_session;
1931 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
1933 if (sipe_backend_chat_find(chat_session->backend, buddy_name))
1935 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
1937 if (is_conf &&
1938 /* Not conf OP */
1939 !sipe_backend_chat_is_operator(chat_session->backend, buddy_name) &&
1940 /* We are a conf OP */
1941 conf_op) {
1942 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
1943 chat_session->title);
1944 menu = sipe_backend_buddy_menu_add(sipe_public,
1945 menu,
1946 label,
1947 SIPE_BUDDY_MENU_MAKE_CHAT_LEADER,
1948 chat_session);
1949 g_free(label);
1952 if (is_conf &&
1953 /* We are a conf OP */
1954 conf_op) {
1955 gchar *label = g_strdup_printf(_("Remove from '%s'"),
1956 chat_session->title);
1957 menu = sipe_backend_buddy_menu_add(sipe_public,
1958 menu,
1959 label,
1960 SIPE_BUDDY_MENU_REMOVE_FROM_CHAT,
1961 chat_session);
1962 g_free(label);
1965 else
1967 if (!is_conf ||
1968 (is_conf && !session->locked)) {
1969 gchar *label = g_strdup_printf(_("Invite to '%s'"),
1970 chat_session->title);
1971 menu = sipe_backend_buddy_menu_add(sipe_public,
1972 menu,
1973 label,
1974 SIPE_BUDDY_MENU_INVITE_TO_CHAT,
1975 chat_session);
1976 g_free(label);
1980 } SIPE_SESSION_FOREACH_END;
1981 g_free(self);
1983 menu = sipe_backend_buddy_menu_add(sipe_public,
1984 menu,
1985 _("New chat"),
1986 SIPE_BUDDY_MENU_NEW_CHAT,
1987 NULL);
1989 /* add buddy's phone numbers if we have call control */
1990 if (sip_csta_is_idle(sipe_private)) {
1992 /* work phone */
1993 menu = buddy_menu_phone(sipe_public,
1994 menu,
1995 buddy,
1996 SIPE_BUDDY_INFO_WORK_PHONE,
1997 SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY,
1998 _("Work"));
1999 /* mobile phone */
2000 menu = buddy_menu_phone(sipe_public,
2001 menu,
2002 buddy,
2003 SIPE_BUDDY_INFO_MOBILE_PHONE,
2004 SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY,
2005 _("Mobile"));
2007 /* home phone */
2008 menu = buddy_menu_phone(sipe_public,
2009 menu,
2010 buddy,
2011 SIPE_BUDDY_INFO_HOME_PHONE,
2012 SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY,
2013 _("Home"));
2015 /* other phone */
2016 menu = buddy_menu_phone(sipe_public,
2017 menu,
2018 buddy,
2019 SIPE_BUDDY_INFO_OTHER_PHONE,
2020 SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY,
2021 _("Other"));
2023 /* custom1 phone */
2024 menu = buddy_menu_phone(sipe_public,
2025 menu,
2026 buddy,
2027 SIPE_BUDDY_INFO_CUSTOM1_PHONE,
2028 SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY,
2029 _("Custom1"));
2033 gchar *email = sipe_backend_buddy_get_string(sipe_public,
2034 buddy,
2035 SIPE_BUDDY_INFO_EMAIL);
2036 if (email) {
2037 menu = sipe_backend_buddy_menu_add(sipe_public,
2038 menu,
2039 _("Send email..."),
2040 SIPE_BUDDY_MENU_SEND_EMAIL,
2041 NULL);
2042 g_free(email);
2046 /* access level control */
2047 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
2048 menu = sipe_backend_buddy_sub_menu_add(sipe_public,
2049 menu,
2050 _("Access level"),
2051 sipe_ocs2007_access_control_menu(sipe_private,
2052 buddy_name));
2054 return(menu);
2057 guint sipe_buddy_count(struct sipe_core_private *sipe_private)
2059 return(g_hash_table_size(sipe_private->buddies->uri));
2062 static guint sipe_ht_hash_nick(const char *nick)
2064 char *lc = g_utf8_strdown(nick, -1);
2065 guint bucket = g_str_hash(lc);
2066 g_free(lc);
2068 return bucket;
2071 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
2073 char *nick1_norm = NULL;
2074 char *nick2_norm = NULL;
2075 gboolean equal;
2077 if (nick1 == NULL && nick2 == NULL) return TRUE;
2078 if (nick1 == NULL || nick2 == NULL ||
2079 !g_utf8_validate(nick1, -1, NULL) ||
2080 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
2082 nick1_norm = g_utf8_casefold(nick1, -1);
2083 nick2_norm = g_utf8_casefold(nick2, -1);
2084 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
2085 g_free(nick2_norm);
2086 g_free(nick1_norm);
2088 return equal;
2091 void sipe_buddy_init(struct sipe_core_private *sipe_private)
2093 struct sipe_buddies *buddies = g_new0(struct sipe_buddies, 1);
2094 buddies->uri = g_hash_table_new((GHashFunc) sipe_ht_hash_nick,
2095 (GEqualFunc) sipe_ht_equals_nick);
2096 buddies->exchange_key = g_hash_table_new(g_str_hash,
2097 g_str_equal);
2098 sipe_private->buddies = buddies;
2102 Local Variables:
2103 mode: c
2104 c-file-style: "bsd"
2105 indent-tabs-mode: t
2106 tab-width: 8
2107 End: