buddy: add GetUserPhoto SOAP request
[siplcs.git] / src / core / sipe-buddy.c
blob284a3ce9ea7cfd9fb11d06105c5ce2d2a3a94ed1
1 /**
2 * @file sipe-buddy.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2016 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
22 * GetUserPhoto operation
23 * <http://msdn.microsoft.com/en-us/library/office/jj900502.aspx>
26 #ifdef HAVE_CONFIG_H
27 #include "config.h"
28 #endif
30 #include <stdlib.h>
31 #include <string.h>
32 #include <time.h>
34 #include <glib.h>
36 #include "sipe-common.h"
37 #include "sipmsg.h"
38 #include "sip-csta.h"
39 #include "sip-soap.h"
40 #include "sip-transport.h"
41 #include "sipe-backend.h"
42 #include "sipe-buddy.h"
43 #include "sipe-cal.h"
44 #include "sipe-chat.h"
45 #include "sipe-conf.h"
46 #include "sipe-core.h"
47 #include "sipe-core-private.h"
48 #include "sipe-group.h"
49 #include "sipe-http.h"
50 #include "sipe-im.h"
51 #include "sipe-nls.h"
52 #include "sipe-ocs2005.h"
53 #include "sipe-ocs2007.h"
54 #include "sipe-schedule.h"
55 #include "sipe-session.h"
56 #include "sipe-status.h"
57 #include "sipe-subscriptions.h"
58 #include "sipe-svc.h"
59 #include "sipe-ucs.h"
60 #include "sipe-utils.h"
61 #include "sipe-webticket.h"
62 #include "sipe-xml.h"
64 struct sipe_buddies {
65 GHashTable *uri;
66 GHashTable *exchange_key;
68 /* Pending photo download HTTP requests */
69 GSList *pending_photo_requests;
72 struct buddy_group_data {
73 const struct sipe_group *group;
74 gboolean is_obsolete;
77 struct photo_response_data {
78 gchar *who;
79 gchar *photo_hash;
80 struct sipe_http_request *request;
83 static void buddy_fetch_photo(struct sipe_core_private *sipe_private,
84 const gchar *uri);
85 static void photo_response_data_free(struct photo_response_data *data);
87 void sipe_buddy_add_keys(struct sipe_core_private *sipe_private,
88 struct sipe_buddy *buddy,
89 const gchar *exchange_key,
90 const gchar *change_key)
92 if (exchange_key) {
93 buddy->exchange_key = g_strdup(exchange_key);
94 g_hash_table_insert(sipe_private->buddies->exchange_key,
95 buddy->exchange_key,
96 buddy);
98 if (change_key)
99 buddy->change_key = g_strdup(change_key);
102 struct sipe_buddy *sipe_buddy_add(struct sipe_core_private *sipe_private,
103 const gchar *uri,
104 const gchar *exchange_key,
105 const gchar *change_key)
107 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
108 gchar *normalized_uri = g_ascii_strdown(uri, -1);
109 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
110 normalized_uri);
112 if (!buddy) {
113 buddy = g_new0(struct sipe_buddy, 1);
114 buddy->name = normalized_uri;
115 g_hash_table_insert(sipe_private->buddies->uri,
116 buddy->name,
117 buddy);
119 sipe_buddy_add_keys(sipe_private,
120 buddy,
121 exchange_key,
122 change_key);
124 SIPE_DEBUG_INFO("sipe_buddy_add: Added buddy %s", normalized_uri);
126 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
127 buddy->just_added = TRUE;
128 sipe_subscribe_presence_single_cb(sipe_private,
129 buddy->name);
132 buddy_fetch_photo(sipe_private, normalized_uri);
134 normalized_uri = NULL; /* buddy takes ownership */
135 } else {
136 SIPE_DEBUG_INFO("sipe_buddy_add: Buddy %s already exists", normalized_uri);
137 buddy->is_obsolete = FALSE;
139 g_free(normalized_uri);
141 return(buddy);
144 static gboolean is_buddy_in_group(struct sipe_buddy *buddy,
145 const gchar *name)
147 if (buddy) {
148 GSList *entry = buddy->groups;
150 while (entry) {
151 struct buddy_group_data *bgd = entry->data;
152 if (sipe_strequal(bgd->group->name, name)) {
153 bgd->is_obsolete = FALSE;
154 return(TRUE);
156 entry = entry->next;
160 return(FALSE);
163 void sipe_buddy_add_to_group(struct sipe_core_private *sipe_private,
164 struct sipe_buddy *buddy,
165 struct sipe_group *group,
166 const gchar *alias)
168 const gchar *uri = buddy->name;
169 const gchar *group_name = group->name;
170 sipe_backend_buddy bb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
171 uri,
172 group_name);
174 if (!bb) {
175 bb = sipe_backend_buddy_add(SIPE_CORE_PUBLIC,
176 uri,
177 alias,
178 group_name);
179 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: created backend buddy '%s' with alias '%s'",
180 uri, alias ? alias : "<NONE>");
184 if (!is_empty(alias)) {
185 gchar *old_alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC,
186 bb);
188 if (sipe_strcase_equal(sipe_get_no_sip_uri(uri),
189 old_alias)) {
190 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC,
192 alias);
193 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: replaced alias for buddy '%s': old '%s' new '%s'",
194 uri, old_alias, alias);
196 g_free(old_alias);
199 if (!is_buddy_in_group(buddy, group_name)) {
200 sipe_buddy_insert_group(buddy, group);
201 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: added buddy %s to group %s",
202 uri, group_name);
206 static gint buddy_group_compare(gconstpointer a, gconstpointer b)
208 return(((const struct buddy_group_data *)a)->group->id -
209 ((const struct buddy_group_data *)b)->group->id);
212 void sipe_buddy_insert_group(struct sipe_buddy *buddy,
213 struct sipe_group *group)
215 struct buddy_group_data *bgd = g_new0(struct buddy_group_data, 1);
217 bgd->group = group;
219 buddy->groups = sipe_utils_slist_insert_unique_sorted(buddy->groups,
220 bgd,
221 buddy_group_compare,
222 NULL);
225 static void buddy_group_free(gpointer data)
227 g_free(data);
230 static void buddy_group_remove(struct sipe_buddy *buddy,
231 struct buddy_group_data *bgd)
233 buddy->groups = g_slist_remove(buddy->groups, bgd);
234 buddy_group_free(bgd);
237 static void sipe_buddy_remove_group(struct sipe_buddy *buddy,
238 const struct sipe_group *group)
240 GSList *entry = buddy->groups;
241 struct buddy_group_data *bgd = NULL;
243 while (entry) {
244 bgd = entry->data;
245 if (bgd->group == group)
246 break;
247 entry = entry->next;
250 buddy_group_remove(buddy, bgd);
253 void sipe_buddy_update_groups(struct sipe_core_private *sipe_private,
254 struct sipe_buddy *buddy,
255 GSList *new_groups)
257 const gchar *uri = buddy->name;
258 GSList *entry = buddy->groups;
260 while (entry) {
261 struct buddy_group_data *bgd = entry->data;
262 const struct sipe_group *group = bgd->group;
264 /* next buddy group */
265 entry = entry->next;
267 /* old group NOT found in new list? */
268 if (g_slist_find(new_groups, group) == NULL) {
269 sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
270 uri,
271 group->name);
272 SIPE_DEBUG_INFO("sipe_buddy_update_groups: removing buddy %s from group '%s'",
273 uri, group->name);
274 /* this should never be NULL */
275 if (oldb)
276 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
277 oldb);
278 buddy_group_remove(buddy, bgd);
283 gchar *sipe_buddy_groups_string(struct sipe_buddy *buddy)
285 guint i = 0;
286 gchar *string;
287 /* creating array from GList, converting guint to gchar * */
288 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
289 GSList *entry = buddy->groups;
291 if (!ids_arr)
292 return(NULL);
294 while (entry) {
295 const struct sipe_group *group = ((struct buddy_group_data *) entry->data)->group;
296 ids_arr[i] = g_strdup_printf("%u", group->id);
297 entry = entry->next;
298 i++;
300 ids_arr[i] = NULL;
302 string = g_strjoinv(" ", ids_arr);
303 g_strfreev(ids_arr);
305 return(string);
308 void sipe_buddy_cleanup_local_list(struct sipe_core_private *sipe_private)
310 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC,
311 NULL,
312 NULL);
313 GSList *entry = buddies;
315 SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: overall %d backend buddies (including clones)",
316 g_slist_length(buddies));
317 SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: %d sipe buddies (unique)",
318 sipe_buddy_count(sipe_private));
319 while (entry) {
320 sipe_backend_buddy bb = entry->data;
321 gchar *bname = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC,
322 bb);
323 gchar *gname = sipe_backend_buddy_get_group_name(SIPE_CORE_PUBLIC,
324 bb);
325 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
326 bname);
328 if (!is_buddy_in_group(buddy, gname)) {
329 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",
330 bname, gname);
331 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, bb);
334 g_free(gname);
335 g_free(bname);
337 entry = entry->next;
340 g_slist_free(buddies);
343 struct sipe_buddy *sipe_buddy_find_by_uri(struct sipe_core_private *sipe_private,
344 const gchar *uri)
346 return(g_hash_table_lookup(sipe_private->buddies->uri, uri));
349 struct sipe_buddy *sipe_buddy_find_by_exchange_key(struct sipe_core_private *sipe_private,
350 const gchar *exchange_key)
352 return(g_hash_table_lookup(sipe_private->buddies->exchange_key,
353 exchange_key));
356 void sipe_buddy_foreach(struct sipe_core_private *sipe_private,
357 GHFunc callback,
358 gpointer callback_data)
360 g_hash_table_foreach(sipe_private->buddies->uri,
361 callback,
362 callback_data);
365 static void buddy_free(struct sipe_buddy *buddy)
367 #ifndef _WIN32
369 * We are calling g_hash_table_foreach_steal(). That means that no
370 * key/value deallocation functions are called. Therefore the glib
371 * hash code does not touch the key (buddy->name) or value (buddy)
372 * of the to-be-deleted hash node at all. It follows that we
374 * - MUST free the memory for the key ourselves and
375 * - ARE allowed to do it in this function
377 * Conclusion: glib must be broken on the Windows platform if sipe
378 * crashes with SIGTRAP when closing. You'll have to live
379 * with the memory leak until this is fixed.
381 g_free(buddy->name);
382 #endif
383 g_free(buddy->exchange_key);
384 g_free(buddy->change_key);
385 g_free(buddy->activity);
386 g_free(buddy->meeting_subject);
387 g_free(buddy->meeting_location);
388 g_free(buddy->note);
390 g_free(buddy->cal_start_time);
391 g_free(buddy->cal_free_busy_base64);
392 g_free(buddy->cal_free_busy);
393 g_free(buddy->last_non_cal_activity);
395 sipe_cal_free_working_hours(buddy->cal_working_hours);
397 g_free(buddy->device_name);
398 sipe_utils_slist_free_full(buddy->groups, buddy_group_free);
399 g_free(buddy);
402 static gboolean buddy_free_cb(SIPE_UNUSED_PARAMETER gpointer key,
403 gpointer buddy,
404 SIPE_UNUSED_PARAMETER gpointer user_data)
406 buddy_free(buddy);
407 /* We must return TRUE as the key/value have already been deleted */
408 return(TRUE);
411 void sipe_buddy_free(struct sipe_core_private *sipe_private)
413 struct sipe_buddies *buddies = sipe_private->buddies;
415 g_hash_table_foreach_steal(buddies->uri,
416 buddy_free_cb,
417 NULL);
419 /* core is being deallocated, remove all its pending photo requests */
420 while (buddies->pending_photo_requests) {
421 struct photo_response_data *data =
422 buddies->pending_photo_requests->data;
423 buddies->pending_photo_requests =
424 g_slist_remove(buddies->pending_photo_requests, data);
425 photo_response_data_free(data);
428 g_hash_table_destroy(buddies->uri);
429 g_hash_table_destroy(buddies->exchange_key);
430 g_free(buddies);
431 sipe_private->buddies = NULL;
434 static void buddy_set_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key,
435 gpointer value,
436 SIPE_UNUSED_PARAMETER gpointer user_data)
438 struct sipe_buddy *buddy = value;
439 GSList *entry = buddy->groups;
441 buddy->is_obsolete = TRUE;
442 while (entry) {
443 ((struct buddy_group_data *) entry->data)->is_obsolete = TRUE;
444 entry = entry->next;
448 void sipe_buddy_update_start(struct sipe_core_private *sipe_private)
450 g_hash_table_foreach(sipe_private->buddies->uri,
451 buddy_set_obsolete_flag,
452 NULL);
455 static gboolean buddy_check_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key,
456 gpointer value,
457 gpointer user_data)
459 struct sipe_core_private *sipe_private = user_data;
460 struct sipe_buddy *buddy = value;
461 const gchar *uri = buddy->name;
463 if (buddy->is_obsolete) {
464 /* all backend buddies in different groups */
465 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC,
466 uri,
467 NULL);
468 GSList *entry = buddies;
470 SIPE_DEBUG_INFO("buddy_check_obsolete_flag: REMOVING %d backend buddies for '%s'",
471 g_slist_length(buddies),
472 uri);
474 while (entry) {
475 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
476 entry->data);
477 entry = entry->next;
479 g_slist_free(buddies);
481 buddy_free(buddy);
482 /* return TRUE as the key/value have already been deleted */
483 return(TRUE);
485 } else {
486 GSList *entry = buddy->groups;
488 while (entry) {
489 struct buddy_group_data *bgd = entry->data;
491 /* next buddy group */
492 entry = entry->next;
494 if (bgd->is_obsolete) {
495 const struct sipe_group *group = bgd->group;
496 sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
497 uri,
498 group->name);
499 SIPE_DEBUG_INFO("buddy_check_obsolete_flag: removing buddy '%s' from group '%s'",
500 uri, group->name);
501 /* this should never be NULL */
502 if (oldb)
503 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
504 oldb);
505 buddy_group_remove(buddy, bgd);
508 return(FALSE);
512 void sipe_buddy_update_finish(struct sipe_core_private *sipe_private)
514 g_hash_table_foreach_remove(sipe_private->buddies->uri,
515 buddy_check_obsolete_flag,
516 sipe_private);
519 gchar *sipe_core_buddy_status(struct sipe_core_public *sipe_public,
520 const gchar *uri,
521 guint activity,
522 const gchar *status_text)
524 struct sipe_buddy *sbuddy;
525 GString *status;
527 if (!sipe_public) return NULL; /* happens on pidgin exit */
529 sbuddy = sipe_buddy_find_by_uri(SIPE_CORE_PRIVATE, uri);
530 if (!sbuddy) return NULL;
532 status = g_string_new(sbuddy->activity ? sbuddy->activity :
533 (activity == SIPE_ACTIVITY_BUSY) || (activity == SIPE_ACTIVITY_BRB) ?
534 status_text : NULL);
536 if (sbuddy->is_mobile) {
537 if (status->len)
538 g_string_append(status, " - ");
539 g_string_append(status, _("Mobile"));
542 if (sbuddy->note) {
543 if (status->len)
544 g_string_append(status, " - ");
545 g_string_append(status, sbuddy->note);
548 /* return NULL instead of empty status text */
549 return(g_string_free(status, status->len ? FALSE : TRUE));
552 gchar *sipe_buddy_get_alias(struct sipe_core_private *sipe_private,
553 const gchar *with)
555 sipe_backend_buddy pbuddy;
556 gchar *alias = NULL;
557 if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
558 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, pbuddy);
560 return alias;
563 void sipe_core_buddy_group(struct sipe_core_public *sipe_public,
564 const gchar *who,
565 const gchar *old_group_name,
566 const gchar *new_group_name)
568 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
569 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
570 who);
571 struct sipe_group *old_group = NULL;
572 struct sipe_group *new_group;
573 struct sipe_ucs_transaction *ucs_trans = NULL;
575 SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy '%s' old group '%s' new group '%s'",
576 who ? who : "",
577 old_group_name ? old_group_name : "<UNDEFINED>",
578 new_group_name ? new_group_name : "<UNDEFINED>");
580 if (!buddy)
581 /* buddy not in roaming list */
582 return;
584 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
585 if (old_group) {
586 sipe_buddy_remove_group(buddy, old_group);
587 SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy '%s' removed from old group '%s'",
588 who, old_group_name);
591 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
592 if (new_group) {
593 sipe_buddy_insert_group(buddy, new_group);
594 SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy '%s' added to new group '%s'",
595 who, new_group_name);
598 if (sipe_ucs_is_migrated(sipe_private)) {
600 /* UCS handling */
601 ucs_trans = sipe_ucs_transaction(sipe_private);
603 if (new_group) {
605 * 1. new buddy added to existing group
606 * 2. existing buddy moved from old to existing group
608 sipe_ucs_group_add_buddy(sipe_private,
609 ucs_trans,
610 new_group,
611 buddy,
612 buddy->name);
613 if (old_group)
614 sipe_ucs_group_remove_buddy(sipe_private,
615 ucs_trans,
616 old_group,
617 buddy);
619 } else if (old_group) {
621 * 3. existing buddy removed from one of its groups
622 * 4. existing buddy removed from last group
624 sipe_ucs_group_remove_buddy(sipe_private,
625 ucs_trans,
626 old_group,
627 buddy);
628 if (g_slist_length(buddy->groups) < 1)
629 sipe_buddy_remove(sipe_private,
630 buddy);
631 /* buddy no longer valid */
634 /* non-UCS handling */
635 } else if (new_group)
636 sipe_group_update_buddy(sipe_private, buddy);
638 /* 5. buddy added to new group */
639 if (!new_group)
640 sipe_group_create(sipe_private,
641 ucs_trans,
642 new_group_name,
643 who);
646 void sipe_core_buddy_add(struct sipe_core_public *sipe_public,
647 const gchar *uri,
648 const gchar *group_name)
650 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
652 if (!sipe_buddy_find_by_uri(sipe_private, uri))
653 sipe_buddy_add(sipe_private,
654 uri,
655 NULL,
656 NULL);
657 else
658 SIPE_DEBUG_INFO("sipe_core_buddy_add: buddy %s already in internal list",
659 uri);
661 sipe_core_buddy_group(sipe_public,
662 uri,
663 NULL,
664 group_name);
667 void sipe_buddy_remove(struct sipe_core_private *sipe_private,
668 struct sipe_buddy *buddy)
670 struct sipe_buddies *buddies = sipe_private->buddies;
671 const gchar *uri = buddy->name;
672 GSList *entry = buddy->groups;
673 gchar *action_name = sipe_utils_presence_key(uri);
675 sipe_schedule_cancel(sipe_private, action_name);
676 g_free(action_name);
678 /* If the buddy still has groups, we need to delete backend buddies */
679 while (entry) {
680 const struct sipe_group *group = ((struct buddy_group_data *) entry->data)->group;
681 sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
682 uri,
683 group->name);
684 /* this should never be NULL */
685 if (oldb)
686 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, oldb);
688 entry = entry->next;
691 g_hash_table_remove(buddies->uri, uri);
692 if (buddy->exchange_key)
693 g_hash_table_remove(buddies->exchange_key,
694 buddy->exchange_key);
696 buddy_free(buddy);
700 * Unassociates buddy from group first.
701 * Then see if no groups left, removes buddy completely.
702 * Otherwise updates buddy groups on server.
704 void sipe_core_buddy_remove(struct sipe_core_public *sipe_public,
705 const gchar *uri,
706 const gchar *group_name)
708 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
709 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
710 uri);
711 struct sipe_group *group = NULL;
713 if (!buddy) return;
715 if (group_name) {
716 group = sipe_group_find_by_name(sipe_private, group_name);
717 if (group) {
718 sipe_buddy_remove_group(buddy, group);
719 SIPE_DEBUG_INFO("sipe_core_buddy_remove: buddy '%s' removed from group '%s'",
720 uri, group->name);
724 if (g_slist_length(buddy->groups) < 1) {
726 if (sipe_ucs_is_migrated(sipe_private)) {
727 sipe_ucs_group_remove_buddy(sipe_private,
728 NULL,
729 group,
730 buddy);
731 } else {
732 gchar *request = g_strdup_printf("<m:URI>%s</m:URI>",
733 buddy->name);
734 sip_soap_request(sipe_private,
735 "deleteContact",
736 request);
737 g_free(request);
740 sipe_buddy_remove(sipe_private, buddy);
741 } else {
742 if (sipe_ucs_is_migrated(sipe_private)) {
743 sipe_ucs_group_remove_buddy(sipe_private,
744 NULL,
745 group,
746 buddy);
747 } else
748 /* updates groups on server */
749 sipe_group_update_buddy(sipe_private, buddy);
753 void sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
754 const gchar *uri,
755 guint activity)
757 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
758 struct sipe_buddy *sbuddy = sipe_buddy_find_by_uri(sipe_private,
759 uri);
761 if (!sbuddy) return;
763 /* Check if on 2005 system contact's calendar,
764 * then set/preserve it.
766 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
767 sipe_backend_buddy_set_status(sipe_public, uri, activity);
768 } else {
769 sipe_ocs2005_apply_calendar_status(sipe_private,
770 sbuddy,
771 sipe_status_activity_to_token(activity));
775 void sipe_core_buddy_tooltip_info(struct sipe_core_public *sipe_public,
776 const gchar *uri,
777 const gchar *status_name,
778 gboolean is_online,
779 struct sipe_backend_buddy_tooltip *tooltip)
781 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
782 gchar *note = NULL;
783 gboolean is_oof_note = FALSE;
784 const gchar *activity = NULL;
785 gchar *calendar = NULL;
786 const gchar *meeting_subject = NULL;
787 const gchar *meeting_location = NULL;
788 gchar *access_text = NULL;
790 #define SIPE_ADD_BUDDY_INFO(l, t) \
792 gchar *tmp = g_markup_escape_text((t), -1); \
793 sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), tmp); \
794 g_free(tmp); \
796 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) \
797 sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), (t))
799 if (sipe_public) { /* happens on pidgin exit */
800 struct sipe_buddy *sbuddy = sipe_buddy_find_by_uri(sipe_private,
801 uri);
802 if (sbuddy) {
803 note = sbuddy->note;
804 is_oof_note = sbuddy->is_oof_note;
805 activity = sbuddy->activity;
806 calendar = sipe_cal_get_description(sbuddy);
807 meeting_subject = sbuddy->meeting_subject;
808 meeting_location = sbuddy->meeting_location;
810 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
811 gboolean is_group_access = FALSE;
812 const int container_id = sipe_ocs2007_find_access_level(sipe_private,
813 "user",
814 sipe_get_no_sip_uri(uri),
815 &is_group_access);
816 const char *access_level = sipe_ocs2007_access_level_name(container_id);
817 access_text = is_group_access ?
818 g_strdup(access_level) :
819 g_strdup_printf(SIPE_OCS2007_INDENT_MARKED_FMT,
820 access_level);
824 if (is_online) {
825 const gchar *status_str = activity ? activity : status_name;
827 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
829 if (is_online && !is_empty(calendar)) {
830 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
832 g_free(calendar);
833 if (!is_empty(meeting_location)) {
834 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", uri, meeting_location);
835 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
837 if (!is_empty(meeting_subject)) {
838 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", uri, meeting_subject);
839 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
841 if (note) {
842 gchar *note_italics = g_strdup_printf("<i>%s</i>", note);
843 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", uri, note);
844 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
845 note_italics);
846 g_free(note_italics);
848 if (access_text) {
849 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
850 g_free(access_text);
854 void sipe_buddy_update_property(struct sipe_core_private *sipe_private,
855 const char *uri,
856 sipe_buddy_info_fields propkey,
857 char *property_value)
859 GSList *buddies, *entry;
861 if (property_value)
862 property_value = g_strstrip(property_value);
864 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
865 while (entry) {
866 gchar *prop_str;
867 sipe_backend_buddy p_buddy = entry->data;
869 /* for Display Name */
870 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
871 gchar *alias;
872 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
873 if (property_value && sipe_is_bad_alias(uri, alias)) {
874 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
875 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
877 g_free(alias);
879 alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
880 if (!is_empty(property_value) &&
881 (!sipe_strequal(property_value, alias) || is_empty(alias)) )
883 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
884 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
886 g_free(alias);
888 /* for other properties */
889 else {
890 if (!is_empty(property_value)) {
891 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
892 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
893 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
895 g_free(prop_str);
899 entry = entry->next;
901 g_slist_free(buddies);
905 struct ms_dlx_data;
906 struct ms_dlx_data {
907 GSList *search_rows;
908 gchar *other;
909 guint max_returns;
910 sipe_svc_callback *callback;
911 struct sipe_svc_session *session;
912 gchar *wsse_security;
913 struct sipe_backend_search_token *token;
914 /* must call ms_dlx_free() */
915 void (*failed_callback)(struct sipe_core_private *sipe_private,
916 struct ms_dlx_data *mdd);
919 static void free_search_rows(GSList *search_rows)
921 sipe_utils_slist_free_full(search_rows, g_free);
924 static void ms_dlx_free(struct ms_dlx_data *mdd)
926 free_search_rows(mdd->search_rows);
927 sipe_svc_session_close(mdd->session);
928 g_free(mdd->other);
929 g_free(mdd->wsse_security);
930 g_free(mdd);
933 #define SIPE_SOAP_SEARCH_ROW "<m:row m:attrib=\"%s\" m:value=\"%s\"/>"
934 #define DLX_SEARCH_ITEM \
935 "<AbEntryRequest.ChangeSearchQuery>" \
936 " <SearchOn>%s</SearchOn>" \
937 " <Value>%s</Value>" \
938 "</AbEntryRequest.ChangeSearchQuery>"
940 static gchar * prepare_buddy_search_query(GSList *query_rows, gboolean use_dlx) {
941 gchar **attrs = g_new(gchar *, (g_slist_length(query_rows) / 2) + 1);
942 guint i = 0;
943 gchar *query = NULL;
945 while (query_rows) {
946 gchar *attr;
947 gchar *value;
948 gchar *tmp = NULL;
950 attr = query_rows->data;
951 query_rows = g_slist_next(query_rows);
952 value = query_rows->data;
953 query_rows = g_slist_next(query_rows);
955 if (!value)
956 break;
959 * Special value for SIP ID
961 * Active Directory seems only to be able to search for
962 * SIP URIs. Make sure search string starts with "sip:".
964 if (!attr) {
965 attr = "msRTCSIP-PrimaryUserAddress";
966 if (!use_dlx)
967 value = tmp = sip_uri(value);
970 attrs[i++] = g_markup_printf_escaped(use_dlx ? DLX_SEARCH_ITEM : SIPE_SOAP_SEARCH_ROW,
971 attr, value);
972 g_free(tmp);
974 attrs[i] = NULL;
976 if (i) {
977 query = g_strjoinv(NULL, attrs);
978 SIPE_DEBUG_INFO("prepare_buddy_search_query: rows:\n%s",
979 query ? query : "");
982 g_strfreev(attrs);
984 return query;
987 static void ms_dlx_webticket(struct sipe_core_private *sipe_private,
988 const gchar *base_uri,
989 const gchar *auth_uri,
990 const gchar *wsse_security,
991 SIPE_UNUSED_PARAMETER const gchar *failure_msg,
992 gpointer callback_data)
994 struct ms_dlx_data *mdd = callback_data;
996 if (wsse_security) {
997 guint length = g_slist_length(mdd->search_rows);
998 gchar *search;
1000 SIPE_DEBUG_INFO("ms_dlx_webticket: got ticket for %s",
1001 base_uri);
1003 if (length > 0) {
1004 /* complex search */
1005 gchar *query = prepare_buddy_search_query(mdd->search_rows, TRUE);
1006 search = g_strdup_printf("<ChangeSearch xmlns:q1=\"DistributionListExpander\" soapenc:arrayType=\"q1:AbEntryRequest.ChangeSearchQuery[%d]\">"
1007 " %s"
1008 "</ChangeSearch>",
1009 length / 2,
1010 query);
1011 g_free(query);
1012 } else {
1013 /* simple search */
1014 search = g_strdup_printf("<BasicSearch>"
1015 " <SearchList>c,company,displayName,givenName,mail,mailNickname,msRTCSIP-PrimaryUserAddress,sn</SearchList>"
1016 " <Value>%s</Value>"
1017 " <Verb>BeginsWith</Verb>"
1018 "</BasicSearch>",
1019 mdd->other);
1022 if (sipe_svc_ab_entry_request(sipe_private,
1023 mdd->session,
1024 auth_uri,
1025 wsse_security,
1026 search,
1027 mdd->max_returns,
1028 mdd->callback,
1029 mdd)) {
1031 /* keep webticket security token for potential further use */
1032 g_free(mdd->wsse_security);
1033 mdd->wsse_security = g_strdup(wsse_security);
1035 /* callback data passed down the line */
1036 mdd = NULL;
1038 g_free(search);
1040 } else {
1041 /* no ticket: this will show the minmum information */
1042 SIPE_DEBUG_ERROR("ms_dlx_webticket: no web ticket for %s",
1043 base_uri);
1046 if (mdd)
1047 mdd->failed_callback(sipe_private, mdd);
1050 static void ms_dlx_webticket_request(struct sipe_core_private *sipe_private,
1051 struct ms_dlx_data *mdd)
1053 if (!sipe_webticket_request(sipe_private,
1054 mdd->session,
1055 sipe_private->dlx_uri,
1056 "AddressBookWebTicketBearer",
1057 ms_dlx_webticket,
1058 mdd)) {
1059 SIPE_DEBUG_ERROR("ms_dlx_webticket_request: couldn't request webticket for %s",
1060 sipe_private->dlx_uri);
1061 mdd->failed_callback(sipe_private, mdd);
1065 void sipe_buddy_search_contacts_finalize(struct sipe_core_private *sipe_private,
1066 struct sipe_backend_search_results *results,
1067 guint match_count,
1068 gboolean more)
1070 gchar *secondary = g_strdup_printf(
1071 dngettext(PACKAGE_NAME,
1072 "Found %d contact%s:",
1073 "Found %d contacts%s:", match_count),
1074 match_count, more ? _(" (more matched your query)") : "");
1076 sipe_backend_search_results_finalize(SIPE_CORE_PUBLIC,
1077 results,
1078 secondary,
1079 more);
1080 g_free(secondary);
1083 static void search_ab_entry_response(struct sipe_core_private *sipe_private,
1084 const gchar *uri,
1085 SIPE_UNUSED_PARAMETER const gchar *raw,
1086 sipe_xml *soap_body,
1087 gpointer callback_data)
1089 struct ms_dlx_data *mdd = callback_data;
1091 if (soap_body) {
1092 const sipe_xml *node;
1093 struct sipe_backend_search_results *results;
1094 GHashTable *found;
1096 SIPE_DEBUG_INFO("search_ab_entry_response: received valid SOAP message from service %s",
1097 uri);
1099 /* any matches? */
1100 node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry");
1101 if (!node) {
1102 /* try again with simple search, if possible */
1103 if (mdd->other && mdd->search_rows) {
1104 SIPE_DEBUG_INFO_NOFORMAT("search_ab_entry_response: no matches, retrying with simple search");
1106 /* throw away original search query */
1107 free_search_rows(mdd->search_rows);
1108 mdd->search_rows = NULL;
1110 ms_dlx_webticket_request(sipe_private, mdd);
1112 /* callback data passed down the line */
1113 return;
1115 } else {
1116 SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: no matches");
1118 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1119 mdd->token,
1120 _("No contacts found"));
1121 ms_dlx_free(mdd);
1122 return;
1126 /* OK, we found something - show the results to the user */
1127 results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
1128 mdd->token);
1129 if (!results) {
1130 SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: Unable to display the search results.");
1131 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1132 mdd->token,
1133 _("Unable to display the search results"));
1134 ms_dlx_free(mdd);
1135 return;
1138 /* SearchAbEntryResult can contain duplicates */
1139 found = g_hash_table_new_full(g_str_hash, g_str_equal,
1140 g_free, NULL);
1142 for (/* initialized above */ ; node; node = sipe_xml_twin(node)) {
1143 const sipe_xml *attrs;
1144 gchar *sip_uri = NULL;
1145 gchar *displayname = NULL;
1146 gchar *company = NULL;
1147 gchar *country = NULL;
1148 gchar *email = NULL;
1150 for (attrs = sipe_xml_child(node, "Attributes/Attribute");
1151 attrs;
1152 attrs = sipe_xml_twin(attrs)) {
1153 gchar *name = sipe_xml_data(sipe_xml_child(attrs,
1154 "Name"));
1155 gchar *value = sipe_xml_data(sipe_xml_child(attrs,
1156 "Value"));
1158 if (!is_empty(value)) {
1159 if (sipe_strcase_equal(name, "msrtcsip-primaryuseraddress")) {
1160 g_free(sip_uri);
1161 sip_uri = value;
1162 value = NULL;
1163 } else if (sipe_strcase_equal(name, "displayname")) {
1164 g_free(displayname);
1165 displayname = value;
1166 value = NULL;
1167 } else if (sipe_strcase_equal(name, "mail")) {
1168 g_free(email);
1169 email = value;
1170 value = NULL;
1171 } else if (sipe_strcase_equal(name, "company")) {
1172 g_free(company);
1173 company = value;
1174 value = NULL;
1175 } else if (sipe_strcase_equal(name, "country")) {
1176 g_free(country);
1177 country = value;
1178 value = NULL;
1182 g_free(value);
1183 g_free(name);
1186 if (sip_uri && !g_hash_table_lookup(found, sip_uri)) {
1187 gchar **uri_parts = g_strsplit(sip_uri, ":", 2);
1188 sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
1189 results,
1190 uri_parts[1],
1191 displayname,
1192 company,
1193 country,
1194 email);
1195 g_strfreev(uri_parts);
1197 g_hash_table_insert(found, sip_uri, (gpointer) TRUE);
1198 sip_uri = NULL;
1201 g_free(email);
1202 g_free(country);
1203 g_free(company);
1204 g_free(displayname);
1205 g_free(sip_uri);
1208 sipe_buddy_search_contacts_finalize(sipe_private, results,
1209 g_hash_table_size(found),
1210 FALSE);
1211 g_hash_table_destroy(found);
1212 ms_dlx_free(mdd);
1214 } else {
1215 mdd->failed_callback(sipe_private, mdd);
1219 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
1220 struct sipmsg *msg,
1221 struct transaction *trans)
1223 struct sipe_backend_search_token *token = trans->payload->data;
1224 struct sipe_backend_search_results *results;
1225 sipe_xml *searchResults;
1226 const sipe_xml *mrow;
1227 guint match_count = 0;
1228 gboolean more = FALSE;
1230 /* valid response? */
1231 if (msg->response != 200) {
1232 SIPE_DEBUG_ERROR("process_search_contact_response: request failed (%d)",
1233 msg->response);
1234 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1235 token,
1236 _("Contact search failed"));
1237 return(FALSE);
1240 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
1242 /* valid XML? */
1243 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
1244 if (!searchResults) {
1245 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
1246 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1247 token,
1248 _("Contact search failed"));
1249 return(FALSE);
1252 /* any matches? */
1253 mrow = sipe_xml_child(searchResults, "Body/Array/row");
1254 if (!mrow) {
1255 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: no matches");
1256 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1257 token,
1258 _("No contacts found"));
1260 sipe_xml_free(searchResults);
1261 return(FALSE);
1264 /* OK, we found something - show the results to the user */
1265 results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
1266 trans->payload->data);
1267 if (!results) {
1268 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: Unable to display the search results.");
1269 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1270 token,
1271 _("Unable to display the search results"));
1273 sipe_xml_free(searchResults);
1274 return FALSE;
1277 for (/* initialized above */ ; mrow; mrow = sipe_xml_twin(mrow)) {
1278 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
1279 sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
1280 results,
1281 uri_parts[1],
1282 sipe_xml_attribute(mrow, "displayName"),
1283 sipe_xml_attribute(mrow, "company"),
1284 sipe_xml_attribute(mrow, "country"),
1285 sipe_xml_attribute(mrow, "email"));
1286 g_strfreev(uri_parts);
1287 match_count++;
1290 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
1291 char *data = sipe_xml_data(mrow);
1292 more = (g_ascii_strcasecmp(data, "true") == 0);
1293 g_free(data);
1296 sipe_buddy_search_contacts_finalize(sipe_private, results, match_count, more);
1297 sipe_xml_free(searchResults);
1299 return(TRUE);
1302 static void search_soap_request(struct sipe_core_private *sipe_private,
1303 GDestroyNotify destroy,
1304 void *data,
1305 guint max,
1306 SoapTransCallback callback,
1307 GSList *search_rows)
1309 gchar *query = prepare_buddy_search_query(search_rows, FALSE);
1310 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1312 payload->destroy = destroy;
1313 payload->data = data;
1315 sip_soap_directory_search(sipe_private,
1316 max,
1317 query,
1318 callback,
1319 payload);
1320 g_free(query);
1323 static void search_ab_entry_failed(struct sipe_core_private *sipe_private,
1324 struct ms_dlx_data *mdd)
1326 /* error using [MS-DLX] server, retry using Active Directory */
1327 if (mdd->search_rows)
1328 search_soap_request(sipe_private,
1329 NULL,
1330 mdd->token,
1331 100,
1332 process_search_contact_response,
1333 mdd->search_rows);
1334 ms_dlx_free(mdd);
1337 void sipe_core_buddy_search(struct sipe_core_public *sipe_public,
1338 struct sipe_backend_search_token *token,
1339 const gchar *given_name,
1340 const gchar *surname,
1341 const gchar *email,
1342 const gchar *sipid,
1343 const gchar *company,
1344 const gchar *country)
1346 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1348 /* Lync 2013 or newer: use UCS if contacts are migrated */
1349 if (SIPE_CORE_PRIVATE_FLAG_IS(LYNC2013) &&
1350 sipe_ucs_is_migrated(sipe_private)) {
1352 sipe_ucs_search(sipe_private,
1353 token,
1354 given_name,
1355 surname,
1356 email,
1357 sipid,
1358 company,
1359 country);
1361 } else {
1362 GSList *query_rows = NULL;
1363 guint count = 0;
1364 const gchar *simple = NULL;
1366 #define ADD_QUERY_ROW(attr, val) \
1367 if (val) { \
1368 query_rows = g_slist_append(query_rows, g_strdup(attr)); \
1369 query_rows = g_slist_append(query_rows, g_strdup(val)); \
1370 simple = val; \
1371 count++; \
1374 ADD_QUERY_ROW("givenName", given_name);
1375 ADD_QUERY_ROW("sn", surname);
1376 ADD_QUERY_ROW("mail", email);
1377 /* prepare_buddy_search_query() interprets NULL as SIP ID */
1378 ADD_QUERY_ROW(NULL, sipid);
1379 ADD_QUERY_ROW("company", company);
1380 ADD_QUERY_ROW("c", country);
1382 if (query_rows) {
1383 if (sipe_private->dlx_uri != NULL) {
1384 struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
1386 mdd->search_rows = query_rows;
1387 /* user entered only one search string, remember that one */
1388 if (count == 1)
1389 mdd->other = g_strdup(simple);
1390 mdd->max_returns = 100;
1391 mdd->callback = search_ab_entry_response;
1392 mdd->failed_callback = search_ab_entry_failed;
1393 mdd->session = sipe_svc_session_start();
1394 mdd->token = token;
1396 ms_dlx_webticket_request(sipe_private, mdd);
1398 } else {
1399 /* no [MS-DLX] server, use Active Directory search instead */
1400 search_soap_request(sipe_private,
1401 NULL,
1402 token,
1403 100,
1404 process_search_contact_response,
1405 query_rows);
1406 free_search_rows(query_rows);
1408 } else
1409 sipe_backend_search_failed(sipe_public,
1410 token,
1411 _("Invalid contact search query"));
1415 static void get_info_finalize(struct sipe_core_private *sipe_private,
1416 struct sipe_backend_buddy_info *info,
1417 const gchar *uri,
1418 const gchar *server_alias,
1419 const gchar *email)
1421 sipe_backend_buddy bbuddy;
1422 struct sipe_buddy *sbuddy;
1423 gchar *alias;
1424 gchar *value;
1426 if (!info) {
1427 info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1428 } else {
1429 sipe_backend_buddy_info_break(SIPE_CORE_PUBLIC, info);
1431 if (!info)
1432 return;
1434 bbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL);
1436 if (is_empty(server_alias)) {
1437 value = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC,
1438 bbuddy);
1439 if (value) {
1440 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1441 info,
1442 SIPE_BUDDY_INFO_DISPLAY_NAME,
1443 value);
1445 } else {
1446 value = g_strdup(server_alias);
1449 /* present alias if it differs from server alias */
1450 alias = sipe_backend_buddy_get_local_alias(SIPE_CORE_PUBLIC, bbuddy);
1451 if (alias && !sipe_strequal(alias, value))
1453 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1454 info,
1455 SIPE_BUDDY_INFO_ALIAS,
1456 alias);
1458 g_free(alias);
1459 g_free(value);
1461 if (is_empty(email)) {
1462 value = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC,
1463 bbuddy,
1464 SIPE_BUDDY_INFO_EMAIL);
1465 if (value) {
1466 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1467 info,
1468 SIPE_BUDDY_INFO_EMAIL,
1469 value);
1470 g_free(value);
1474 value = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC,
1475 bbuddy,
1476 SIPE_BUDDY_INFO_SITE);
1477 if (value) {
1478 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1479 info,
1480 SIPE_BUDDY_INFO_SITE,
1481 value);
1482 g_free(value);
1485 sbuddy = sipe_buddy_find_by_uri(sipe_private, uri);
1486 if (sbuddy && sbuddy->device_name) {
1487 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1488 info,
1489 SIPE_BUDDY_INFO_DEVICE,
1490 sbuddy->device_name);
1493 sipe_backend_buddy_info_finalize(SIPE_CORE_PUBLIC, info, uri);
1497 static void get_info_ab_entry_response(struct sipe_core_private *sipe_private,
1498 const gchar *uri,
1499 SIPE_UNUSED_PARAMETER const gchar *raw,
1500 sipe_xml *soap_body,
1501 gpointer callback_data)
1503 struct ms_dlx_data *mdd = callback_data;
1504 struct sipe_backend_buddy_info *info = NULL;
1505 gchar *server_alias = NULL;
1506 gchar *email = NULL;
1508 if (soap_body) {
1509 const sipe_xml *node;
1511 SIPE_DEBUG_INFO("get_info_ab_entry_response: received valid SOAP message from service %s",
1512 uri);
1514 info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1516 for (node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
1517 node;
1518 node = sipe_xml_twin(node)) {
1519 gchar *name = sipe_xml_data(sipe_xml_child(node,
1520 "Name"));
1521 gchar *value = sipe_xml_data(sipe_xml_child(node,
1522 "Value"));
1523 const sipe_xml *values = sipe_xml_child(node,
1524 "Values");
1526 /* Single value entries */
1527 if (!is_empty(value)) {
1529 if (sipe_strcase_equal(name, "displayname")) {
1530 g_free(server_alias);
1531 server_alias = value;
1532 value = NULL;
1533 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1534 info,
1535 SIPE_BUDDY_INFO_DISPLAY_NAME,
1536 server_alias);
1537 } else if (sipe_strcase_equal(name, "mail")) {
1538 g_free(email);
1539 email = value;
1540 value = NULL;
1541 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1542 info,
1543 SIPE_BUDDY_INFO_EMAIL,
1544 email);
1545 } else if (sipe_strcase_equal(name, "title")) {
1546 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1547 info,
1548 SIPE_BUDDY_INFO_JOB_TITLE,
1549 value);
1550 } else if (sipe_strcase_equal(name, "company")) {
1551 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1552 info,
1553 SIPE_BUDDY_INFO_COMPANY,
1554 value);
1555 } else if (sipe_strcase_equal(name, "country")) {
1556 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1557 info,
1558 SIPE_BUDDY_INFO_COUNTRY,
1559 value);
1562 } else if (values) {
1563 gchar *first = sipe_xml_data(sipe_xml_child(values,
1564 "string"));
1566 if (sipe_strcase_equal(name, "telephonenumber")) {
1567 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1568 info,
1569 SIPE_BUDDY_INFO_WORK_PHONE,
1570 first);
1573 g_free(first);
1576 g_free(value);
1577 g_free(name);
1581 /* this will show the minmum information */
1582 get_info_finalize(sipe_private,
1583 info,
1584 mdd->other,
1585 server_alias,
1586 email);
1588 g_free(email);
1589 g_free(server_alias);
1590 ms_dlx_free(mdd);
1593 static gboolean process_get_info_response(struct sipe_core_private *sipe_private,
1594 struct sipmsg *msg,
1595 struct transaction *trans)
1597 const gchar *uri = trans->payload->data;
1598 struct sipe_backend_buddy_info *info = NULL;
1599 gchar *server_alias = NULL;
1600 gchar *email = NULL;
1602 SIPE_DEBUG_INFO("Fetching %s's user info for %s",
1603 uri, sipe_private->username);
1605 if (msg->response != 200) {
1606 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
1607 } else {
1608 sipe_xml *searchResults;
1609 const sipe_xml *mrow;
1611 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s",
1612 msg->body ? msg->body : "");
1614 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
1615 if (!searchResults) {
1617 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
1619 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
1620 const gchar *value;
1621 gchar *phone_number;
1623 info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1625 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
1626 email = g_strdup(sipe_xml_attribute(mrow, "email"));
1627 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
1630 * For 2007 system we will take this from ContactCard -
1631 * it has cleaner tel: URIs at least
1633 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1634 char *tel_uri = sip_to_tel_uri(phone_number);
1635 /* trims its parameters, so call first */
1636 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
1637 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
1638 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
1639 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
1640 g_free(tel_uri);
1642 sipe_backend_buddy_refresh_properties(SIPE_CORE_PUBLIC,
1643 uri);
1646 if (!is_empty(server_alias)) {
1647 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1648 info,
1649 SIPE_BUDDY_INFO_DISPLAY_NAME,
1650 server_alias);
1652 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
1653 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1654 info,
1655 SIPE_BUDDY_INFO_JOB_TITLE,
1656 value);
1658 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
1659 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1660 info,
1661 SIPE_BUDDY_INFO_OFFICE,
1662 value);
1664 if (!is_empty(phone_number)) {
1665 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1666 info,
1667 SIPE_BUDDY_INFO_WORK_PHONE,
1668 phone_number);
1670 g_free(phone_number);
1671 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
1672 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1673 info,
1674 SIPE_BUDDY_INFO_COMPANY,
1675 value);
1677 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
1678 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1679 info,
1680 SIPE_BUDDY_INFO_CITY,
1681 value);
1683 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
1684 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1685 info,
1686 SIPE_BUDDY_INFO_STATE,
1687 value);
1689 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
1690 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1691 info,
1692 SIPE_BUDDY_INFO_COUNTRY,
1693 value);
1695 if (!is_empty(email)) {
1696 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1697 info,
1698 SIPE_BUDDY_INFO_EMAIL,
1699 email);
1702 sipe_xml_free(searchResults);
1705 /* this will show the minmum information */
1706 get_info_finalize(sipe_private,
1707 info,
1708 uri,
1709 server_alias,
1710 email);
1712 g_free(server_alias);
1713 g_free(email);
1715 return TRUE;
1718 static void get_info_ab_entry_failed(struct sipe_core_private *sipe_private,
1719 struct ms_dlx_data *mdd)
1721 /* error using [MS-DLX] server, retry using Active Directory */
1722 search_soap_request(sipe_private,
1723 g_free,
1724 mdd->other,
1726 process_get_info_response,
1727 mdd->search_rows);
1728 mdd->other = NULL;
1729 ms_dlx_free(mdd);
1732 static GSList *search_rows_for_uri(const gchar *uri)
1734 /* prepare_buddy_search_query() interprets NULL as SIP ID */
1735 GSList *l = g_slist_append(NULL, NULL);
1736 return(g_slist_append(l, g_strdup(uri)));
1739 void sipe_core_buddy_get_info(struct sipe_core_public *sipe_public,
1740 const gchar *who)
1742 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1743 GSList *search_rows = search_rows_for_uri(who);
1745 if (sipe_private->dlx_uri) {
1746 struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
1748 mdd->search_rows = search_rows;
1749 mdd->other = g_strdup(who);
1750 mdd->max_returns = 1;
1751 mdd->callback = get_info_ab_entry_response;
1752 mdd->failed_callback = get_info_ab_entry_failed;
1753 mdd->session = sipe_svc_session_start();
1755 ms_dlx_webticket_request(sipe_private, mdd);
1757 } else {
1758 /* no [MS-DLX] server, use Active Directory search instead */
1759 search_soap_request(sipe_private,
1760 g_free,
1761 g_strdup(who),
1763 process_get_info_response,
1764 search_rows);
1765 free_search_rows(search_rows);
1769 static void photo_response_data_free(struct photo_response_data *data)
1771 g_free(data->who);
1772 g_free(data->photo_hash);
1773 if (data->request) {
1774 sipe_http_request_cancel(data->request);
1776 g_free(data);
1779 static void photo_response_data_remove(struct sipe_core_private *sipe_private,
1780 struct photo_response_data *data)
1782 data->request = NULL;
1783 sipe_private->buddies->pending_photo_requests =
1784 g_slist_remove(sipe_private->buddies->pending_photo_requests, data);
1785 photo_response_data_free(data);
1788 static void process_buddy_photo_response(struct sipe_core_private *sipe_private,
1789 guint status,
1790 GSList *headers,
1791 const char *body,
1792 gpointer data)
1794 struct photo_response_data *rdata = (struct photo_response_data *) data;
1796 if (status == SIPE_HTTP_STATUS_OK) {
1797 const gchar *len_str = sipe_utils_nameval_find(headers,
1798 "Content-Length");
1799 if (len_str) {
1800 gsize photo_size = atoi(len_str);
1801 gpointer photo = g_new(char, photo_size);
1803 if (photo) {
1804 memcpy(photo, body, photo_size);
1806 sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC,
1807 rdata->who,
1808 photo,
1809 photo_size,
1810 rdata->photo_hash);
1815 photo_response_data_remove(sipe_private, rdata);
1818 /* copied from sipe_ucs_http_request() */
1819 static void process_get_user_photo_response(struct sipe_core_private *sipe_private,
1820 guint status,
1821 SIPE_UNUSED_PARAMETER GSList *headers,
1822 SIPE_UNUSED_PARAMETER const gchar *body,
1823 gpointer data)
1825 struct photo_response_data *rdata = (struct photo_response_data *) data;
1827 SIPE_DEBUG_INFO("process_get_user_photo_response: %d", status);
1829 photo_response_data_remove(sipe_private, rdata);
1832 static gchar *create_x_ms_webticket_header(const gchar *wsse_security)
1834 gchar *assertion = sipe_xml_extract_raw(wsse_security, "Assertion", TRUE);
1835 gchar *wsse_security_base64;
1836 gchar *x_ms_webticket_header;
1838 if (!assertion) {
1839 return NULL;
1842 wsse_security_base64 = g_base64_encode((const guchar *)assertion,
1843 strlen(assertion));
1844 x_ms_webticket_header = g_strdup_printf("X-MS-WebTicket: opaque=%s\r\n",
1845 wsse_security_base64);
1847 g_free(assertion);
1848 g_free(wsse_security_base64);
1850 return x_ms_webticket_header;
1853 /* copied from sipe_ucs_get_photo() & friends */
1854 static struct sipe_http_request *photo_url_embedded_xml(struct sipe_core_private *sipe_private,
1855 struct photo_response_data *data,
1856 const gchar *photo_url)
1858 /* add dummy root to embedded XML string */
1859 gchar *tmp = g_strdup_printf("<r>%s</r>", photo_url);
1860 sipe_xml *xml = sipe_xml_parse(tmp, strlen(tmp));
1861 struct sipe_http_request *request = NULL;
1863 g_free(tmp);
1864 if (xml) {
1865 gchar *ews_url = sipe_xml_data(sipe_xml_child(xml, "ewsUrl"));
1866 gchar *email = sipe_xml_data(sipe_xml_child(xml, "primarySMTP"));
1868 if (!is_empty(ews_url) && !is_empty(email)) {
1869 gchar *soap = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
1870 "<soap:Envelope"
1871 " xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
1872 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
1873 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
1874 " >"
1875 " <soap:Header>"
1876 " <t:RequestServerVersion Version=\"Exchange2013\" />"
1877 " </soap:Header>"
1878 " <soap:Body>"
1879 " <m:GetUserPhoto>"
1880 " <m:Email>%s</m:Email>"
1881 " <m:SizeRequested>HR48x48</m:SizeRequested>"
1882 " </m:GetUserPhoto>"
1883 " </soap:Body>"
1884 "</soap:Envelope>",
1885 email);
1887 request = sipe_http_request_post(sipe_private,
1888 ews_url,
1889 NULL,
1890 soap,
1891 "text/xml; charset=UTF-8",
1892 process_get_user_photo_response,
1893 data);
1894 g_free(soap);
1896 if (request) {
1897 sipe_core_email_authentication(sipe_private,
1898 request);
1899 sipe_http_request_allow_redirect(request);
1900 } else {
1901 SIPE_DEBUG_ERROR_NOFORMAT("photo_url_embedded_xml: failed to create HTTP connection");
1905 g_free(email);
1906 g_free(ews_url);
1907 sipe_xml_free(xml);
1910 return(request);
1913 void sipe_buddy_update_photo(struct sipe_core_private *sipe_private,
1914 const gchar *uri,
1915 const gchar *photo_hash,
1916 const gchar *photo_url,
1917 const gchar *headers)
1919 const gchar *photo_hash_old =
1920 sipe_backend_buddy_get_photo_hash(SIPE_CORE_PUBLIC, uri);
1922 if (!sipe_strequal(photo_hash, photo_hash_old)) {
1923 struct photo_response_data *data = g_new(struct photo_response_data, 1);
1925 SIPE_DEBUG_INFO("sipe_buddy_update_photo: who '%s' url '%s' hash '%s'",
1926 uri, photo_url, photo_hash);
1928 data->who = g_strdup(uri);
1929 data->photo_hash = g_strdup(photo_hash);
1931 /* Photo URL is embedded XML? */
1932 if (g_str_has_prefix(photo_url, "<") &&
1933 g_str_has_suffix(photo_url, ">")) {
1934 data->request = photo_url_embedded_xml(sipe_private,
1935 data,
1936 photo_url);
1937 } else {
1938 data->request = sipe_http_request_get(sipe_private,
1939 photo_url,
1940 headers,
1941 process_buddy_photo_response,
1942 data);
1945 if (data->request) {
1946 sipe_private->buddies->pending_photo_requests =
1947 g_slist_append(sipe_private->buddies->pending_photo_requests, data);
1948 sipe_http_request_ready(data->request);
1949 } else {
1950 photo_response_data_free(data);
1955 static void get_photo_ab_entry_response(struct sipe_core_private *sipe_private,
1956 const gchar *uri,
1957 SIPE_UNUSED_PARAMETER const gchar *raw,
1958 sipe_xml *soap_body,
1959 gpointer callback_data)
1961 struct ms_dlx_data *mdd = callback_data;
1962 gchar *photo_rel_path = NULL;
1963 gchar *photo_hash = NULL;
1965 if (soap_body) {
1966 const sipe_xml *node;
1968 SIPE_DEBUG_INFO("get_photo_ab_entry_response: received valid SOAP message from service %s",
1969 uri);
1971 for (node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
1972 node;
1973 node = sipe_xml_twin(node)) {
1974 gchar *name = sipe_xml_data(sipe_xml_child(node, "Name"));
1975 gchar *value = sipe_xml_data(sipe_xml_child(node, "Value"));
1977 if (!is_empty(value)) {
1978 if (sipe_strcase_equal(name, "PhotoRelPath")) {
1979 g_free(photo_rel_path);
1980 photo_rel_path = value;
1981 value = NULL;
1982 } else if (sipe_strcase_equal(name, "PhotoHash")) {
1983 g_free(photo_hash);
1984 photo_hash = value;
1985 value = NULL;
1989 g_free(value);
1990 g_free(name);
1994 if (sipe_private->addressbook_uri && photo_rel_path && photo_hash) {
1995 gchar *photo_url = g_strdup_printf("%s/%s",
1996 sipe_private->addressbook_uri, photo_rel_path);
1997 gchar *x_ms_webticket_header = create_x_ms_webticket_header(mdd->wsse_security);
1999 sipe_buddy_update_photo(sipe_private,
2000 mdd->other,
2001 photo_hash,
2002 photo_url,
2003 x_ms_webticket_header);
2005 g_free(x_ms_webticket_header);
2006 g_free(photo_url);
2009 g_free(photo_rel_path);
2010 g_free(photo_hash);
2011 ms_dlx_free(mdd);
2014 static void get_photo_ab_entry_failed(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2015 struct ms_dlx_data *mdd)
2017 ms_dlx_free(mdd);
2020 static void buddy_fetch_photo(struct sipe_core_private *sipe_private,
2021 const gchar *uri)
2023 if (sipe_backend_uses_photo()) {
2025 /* Lync 2013 or newer: use UCS if contacts are migrated */
2026 if (SIPE_CORE_PRIVATE_FLAG_IS(LYNC2013) &&
2027 sipe_ucs_is_migrated(sipe_private)) {
2029 sipe_ucs_get_photo(sipe_private, uri);
2031 /* Lync 2010: use [MS-DLX] */
2032 } else if (sipe_private->dlx_uri &&
2033 sipe_private->addressbook_uri) {
2034 struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
2036 mdd->search_rows = search_rows_for_uri(uri);
2037 mdd->other = g_strdup(uri);
2038 mdd->max_returns = 1;
2039 mdd->callback = get_photo_ab_entry_response;
2040 mdd->failed_callback = get_photo_ab_entry_failed;
2041 mdd->session = sipe_svc_session_start();
2043 ms_dlx_webticket_request(sipe_private, mdd);
2048 static void buddy_refresh_photos_cb(gpointer uri,
2049 SIPE_UNUSED_PARAMETER gpointer value,
2050 gpointer sipe_private)
2052 buddy_fetch_photo(sipe_private, uri);
2055 void sipe_buddy_refresh_photos(struct sipe_core_private *sipe_private)
2057 g_hash_table_foreach(sipe_private->buddies->uri,
2058 buddy_refresh_photos_cb,
2059 sipe_private);
2062 /* Buddy menu callbacks*/
2064 void sipe_core_buddy_new_chat(struct sipe_core_public *sipe_public,
2065 const gchar *who)
2067 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
2069 /* 2007+ conference */
2070 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
2071 sipe_conf_add(sipe_private, who);
2073 /* 2005- multiparty chat */
2074 } else {
2075 gchar *self = sip_uri_self(sipe_private);
2076 struct sip_session *session;
2078 session = sipe_session_add_chat(sipe_private,
2079 NULL,
2080 TRUE,
2081 self);
2082 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
2083 session->chat_session,
2084 session->chat_session->title,
2085 self);
2086 g_free(self);
2088 sipe_im_invite(sipe_private, session, who,
2089 NULL, NULL, NULL, FALSE);
2093 void sipe_core_buddy_send_email(struct sipe_core_public *sipe_public,
2094 const gchar *who)
2096 sipe_backend_buddy buddy = sipe_backend_buddy_find(sipe_public,
2097 who,
2098 NULL);
2099 gchar *email = sipe_backend_buddy_get_string(sipe_public,
2100 buddy,
2101 SIPE_BUDDY_INFO_EMAIL);
2103 if (email) {
2104 gchar *command_line = g_strdup_printf(
2105 #ifdef _WIN32
2106 "cmd /c start"
2107 #else
2108 "xdg-email"
2109 #endif
2110 " mailto:%s", email);
2111 g_free(email);
2113 SIPE_DEBUG_INFO("sipe_core_buddy_send_email: going to call email client: %s",
2114 command_line);
2115 g_spawn_command_line_async(command_line, NULL);
2116 g_free(command_line);
2118 } else {
2119 SIPE_DEBUG_INFO("sipe_core_buddy_send_email: no email address stored for buddy=%s",
2120 who);
2124 /* Buddy menu */
2126 static struct sipe_backend_buddy_menu *buddy_menu_phone(struct sipe_core_public *sipe_public,
2127 struct sipe_backend_buddy_menu *menu,
2128 sipe_backend_buddy buddy,
2129 sipe_buddy_info_fields id_phone,
2130 sipe_buddy_info_fields id_display,
2131 const gchar *type)
2133 gchar *phone = sipe_backend_buddy_get_string(sipe_public,
2134 buddy,
2135 id_phone);
2136 if (phone) {
2137 gchar *display = sipe_backend_buddy_get_string(sipe_public,
2138 buddy,
2139 id_display);
2140 gchar *tmp = NULL;
2141 gchar *label = g_strdup_printf("%s %s",
2142 type,
2143 display ? display :
2144 (tmp = sip_tel_uri_denormalize(phone)));
2145 menu = sipe_backend_buddy_menu_add(sipe_public,
2146 menu,
2147 label,
2148 SIPE_BUDDY_MENU_MAKE_CALL,
2149 phone);
2150 g_free(tmp);
2151 g_free(label);
2152 g_free(display);
2153 g_free(phone);
2156 return(menu);
2159 struct sipe_backend_buddy_menu *sipe_core_buddy_create_menu(struct sipe_core_public *sipe_public,
2160 const gchar *buddy_name,
2161 struct sipe_backend_buddy_menu *menu)
2163 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
2164 sipe_backend_buddy buddy = sipe_backend_buddy_find(sipe_public,
2165 buddy_name,
2166 NULL);
2167 gchar *self = sip_uri_self(sipe_private);
2169 SIPE_SESSION_FOREACH {
2170 if (!sipe_strcase_equal(self, buddy_name) && session->chat_session)
2172 struct sipe_chat_session *chat_session = session->chat_session;
2173 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
2175 if (sipe_backend_chat_find(chat_session->backend, buddy_name))
2177 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
2179 if (is_conf &&
2180 /* Not conf OP */
2181 !sipe_backend_chat_is_operator(chat_session->backend, buddy_name) &&
2182 /* We are a conf OP */
2183 conf_op) {
2184 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
2185 chat_session->title);
2186 menu = sipe_backend_buddy_menu_add(sipe_public,
2187 menu,
2188 label,
2189 SIPE_BUDDY_MENU_MAKE_CHAT_LEADER,
2190 chat_session);
2191 g_free(label);
2194 if (is_conf &&
2195 /* We are a conf OP */
2196 conf_op) {
2197 gchar *label = g_strdup_printf(_("Remove from '%s'"),
2198 chat_session->title);
2199 menu = sipe_backend_buddy_menu_add(sipe_public,
2200 menu,
2201 label,
2202 SIPE_BUDDY_MENU_REMOVE_FROM_CHAT,
2203 chat_session);
2204 g_free(label);
2207 else
2209 if (!is_conf ||
2210 (is_conf && !session->locked)) {
2211 gchar *label = g_strdup_printf(_("Invite to '%s'"),
2212 chat_session->title);
2213 menu = sipe_backend_buddy_menu_add(sipe_public,
2214 menu,
2215 label,
2216 SIPE_BUDDY_MENU_INVITE_TO_CHAT,
2217 chat_session);
2218 g_free(label);
2222 } SIPE_SESSION_FOREACH_END;
2223 g_free(self);
2225 menu = sipe_backend_buddy_menu_add(sipe_public,
2226 menu,
2227 _("New chat"),
2228 SIPE_BUDDY_MENU_NEW_CHAT,
2229 NULL);
2231 /* add buddy's phone numbers if we have call control */
2232 if (sip_csta_is_idle(sipe_private)) {
2234 /* work phone */
2235 menu = buddy_menu_phone(sipe_public,
2236 menu,
2237 buddy,
2238 SIPE_BUDDY_INFO_WORK_PHONE,
2239 SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY,
2240 _("Work"));
2241 /* mobile phone */
2242 menu = buddy_menu_phone(sipe_public,
2243 menu,
2244 buddy,
2245 SIPE_BUDDY_INFO_MOBILE_PHONE,
2246 SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY,
2247 _("Mobile"));
2249 /* home phone */
2250 menu = buddy_menu_phone(sipe_public,
2251 menu,
2252 buddy,
2253 SIPE_BUDDY_INFO_HOME_PHONE,
2254 SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY,
2255 _("Home"));
2257 /* other phone */
2258 menu = buddy_menu_phone(sipe_public,
2259 menu,
2260 buddy,
2261 SIPE_BUDDY_INFO_OTHER_PHONE,
2262 SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY,
2263 _("Other"));
2265 /* custom1 phone */
2266 menu = buddy_menu_phone(sipe_public,
2267 menu,
2268 buddy,
2269 SIPE_BUDDY_INFO_CUSTOM1_PHONE,
2270 SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY,
2271 _("Custom1"));
2275 gchar *email = sipe_backend_buddy_get_string(sipe_public,
2276 buddy,
2277 SIPE_BUDDY_INFO_EMAIL);
2278 if (email) {
2279 menu = sipe_backend_buddy_menu_add(sipe_public,
2280 menu,
2281 _("Send email..."),
2282 SIPE_BUDDY_MENU_SEND_EMAIL,
2283 NULL);
2284 g_free(email);
2288 /* access level control */
2289 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
2290 menu = sipe_backend_buddy_sub_menu_add(sipe_public,
2291 menu,
2292 _("Access level"),
2293 sipe_ocs2007_access_control_menu(sipe_private,
2294 buddy_name));
2296 return(menu);
2299 guint sipe_buddy_count(struct sipe_core_private *sipe_private)
2301 return(g_hash_table_size(sipe_private->buddies->uri));
2304 static guint sipe_ht_hash_nick(const char *nick)
2306 char *lc = g_utf8_strdown(nick, -1);
2307 guint bucket = g_str_hash(lc);
2308 g_free(lc);
2310 return bucket;
2313 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
2315 char *nick1_norm = NULL;
2316 char *nick2_norm = NULL;
2317 gboolean equal;
2319 if (nick1 == NULL && nick2 == NULL) return TRUE;
2320 if (nick1 == NULL || nick2 == NULL ||
2321 !g_utf8_validate(nick1, -1, NULL) ||
2322 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
2324 nick1_norm = g_utf8_casefold(nick1, -1);
2325 nick2_norm = g_utf8_casefold(nick2, -1);
2326 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
2327 g_free(nick2_norm);
2328 g_free(nick1_norm);
2330 return equal;
2333 void sipe_buddy_init(struct sipe_core_private *sipe_private)
2335 struct sipe_buddies *buddies = g_new0(struct sipe_buddies, 1);
2336 buddies->uri = g_hash_table_new((GHashFunc) sipe_ht_hash_nick,
2337 (GEqualFunc) sipe_ht_equals_nick);
2338 buddies->exchange_key = g_hash_table_new(g_str_hash,
2339 g_str_equal);
2340 sipe_private->buddies = buddies;
2344 Local Variables:
2345 mode: c
2346 c-file-style: "bsd"
2347 indent-tabs-mode: t
2348 tab-width: 8
2349 End: