mime: add support for GMIME 3.0 API
[siplcs.git] / src / core / sipe-buddy.c
blob27590c7b14a90de3ba3562c44913ac4b4b86860f
1 /**
2 * @file sipe-buddy.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2017 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-digest.h"
49 #include "sipe-group.h"
50 #include "sipe-http.h"
51 #include "sipe-im.h"
52 #include "sipe-nls.h"
53 #include "sipe-ocs2005.h"
54 #include "sipe-ocs2007.h"
55 #include "sipe-schedule.h"
56 #include "sipe-session.h"
57 #include "sipe-status.h"
58 #include "sipe-subscriptions.h"
59 #include "sipe-svc.h"
60 #include "sipe-ucs.h"
61 #include "sipe-utils.h"
62 #include "sipe-webticket.h"
63 #include "sipe-xml.h"
65 struct sipe_buddies {
66 GHashTable *uri;
67 GHashTable *exchange_key;
69 /* Pending photo download HTTP requests */
70 GSList *pending_photo_requests;
73 struct buddy_group_data {
74 const struct sipe_group *group;
75 gboolean is_obsolete;
78 struct photo_response_data {
79 gchar *who;
80 gchar *photo_hash;
81 struct sipe_http_request *request;
84 static void buddy_fetch_photo(struct sipe_core_private *sipe_private,
85 const gchar *uri);
86 static void photo_response_data_free(struct photo_response_data *data);
88 void sipe_buddy_add_keys(struct sipe_core_private *sipe_private,
89 struct sipe_buddy *buddy,
90 const gchar *exchange_key,
91 const gchar *change_key)
93 if (exchange_key) {
94 buddy->exchange_key = g_strdup(exchange_key);
95 g_hash_table_insert(sipe_private->buddies->exchange_key,
96 buddy->exchange_key,
97 buddy);
99 if (change_key)
100 buddy->change_key = g_strdup(change_key);
103 struct sipe_buddy *sipe_buddy_add(struct sipe_core_private *sipe_private,
104 const gchar *uri,
105 const gchar *exchange_key,
106 const gchar *change_key)
108 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
109 gchar *normalized_uri = g_ascii_strdown(uri, -1);
110 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
111 normalized_uri);
113 if (!buddy) {
114 buddy = g_new0(struct sipe_buddy, 1);
115 buddy->name = normalized_uri;
116 g_hash_table_insert(sipe_private->buddies->uri,
117 buddy->name,
118 buddy);
120 sipe_buddy_add_keys(sipe_private,
121 buddy,
122 exchange_key,
123 change_key);
125 SIPE_DEBUG_INFO("sipe_buddy_add: Added buddy %s", normalized_uri);
127 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
128 buddy->just_added = TRUE;
129 sipe_subscribe_presence_single_cb(sipe_private,
130 buddy->name);
133 buddy_fetch_photo(sipe_private, normalized_uri);
135 normalized_uri = NULL; /* buddy takes ownership */
136 } else {
137 SIPE_DEBUG_INFO("sipe_buddy_add: Buddy %s already exists", normalized_uri);
138 buddy->is_obsolete = FALSE;
140 g_free(normalized_uri);
142 return(buddy);
145 static gboolean is_buddy_in_group(struct sipe_buddy *buddy,
146 const gchar *name)
148 if (buddy) {
149 GSList *entry = buddy->groups;
151 while (entry) {
152 struct buddy_group_data *bgd = entry->data;
153 if (sipe_strequal(bgd->group->name, name)) {
154 bgd->is_obsolete = FALSE;
155 return(TRUE);
157 entry = entry->next;
161 return(FALSE);
164 void sipe_buddy_add_to_group(struct sipe_core_private *sipe_private,
165 struct sipe_buddy *buddy,
166 struct sipe_group *group,
167 const gchar *alias)
169 const gchar *uri = buddy->name;
170 const gchar *group_name = group->name;
171 sipe_backend_buddy bb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
172 uri,
173 group_name);
175 if (!bb) {
176 bb = sipe_backend_buddy_add(SIPE_CORE_PUBLIC,
177 uri,
178 alias,
179 group_name);
180 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: created backend buddy '%s' with alias '%s'",
181 uri, alias ? alias : "<NONE>");
185 if (!is_empty(alias)) {
186 gchar *old_alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC,
187 bb);
189 if (sipe_strcase_equal(sipe_get_no_sip_uri(uri),
190 old_alias)) {
191 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC,
193 alias);
194 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: replaced alias for buddy '%s': old '%s' new '%s'",
195 uri, old_alias, alias);
197 g_free(old_alias);
200 if (!is_buddy_in_group(buddy, group_name)) {
201 sipe_buddy_insert_group(buddy, group);
202 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: added buddy %s to group %s",
203 uri, group_name);
207 static gint buddy_group_compare(gconstpointer a, gconstpointer b)
209 return(((const struct buddy_group_data *)a)->group->id -
210 ((const struct buddy_group_data *)b)->group->id);
213 void sipe_buddy_insert_group(struct sipe_buddy *buddy,
214 struct sipe_group *group)
216 struct buddy_group_data *bgd = g_new0(struct buddy_group_data, 1);
218 bgd->group = group;
220 buddy->groups = sipe_utils_slist_insert_unique_sorted(buddy->groups,
221 bgd,
222 buddy_group_compare,
223 NULL);
226 static void buddy_group_free(gpointer data)
228 g_free(data);
231 static void buddy_group_remove(struct sipe_buddy *buddy,
232 struct buddy_group_data *bgd)
234 buddy->groups = g_slist_remove(buddy->groups, bgd);
235 buddy_group_free(bgd);
238 static void sipe_buddy_remove_group(struct sipe_buddy *buddy,
239 const struct sipe_group *group)
241 GSList *entry = buddy->groups;
242 struct buddy_group_data *bgd = NULL;
244 while (entry) {
245 bgd = entry->data;
246 if (bgd->group == group)
247 break;
248 entry = entry->next;
251 buddy_group_remove(buddy, bgd);
254 void sipe_buddy_update_groups(struct sipe_core_private *sipe_private,
255 struct sipe_buddy *buddy,
256 GSList *new_groups)
258 const gchar *uri = buddy->name;
259 GSList *entry = buddy->groups;
261 while (entry) {
262 struct buddy_group_data *bgd = entry->data;
263 const struct sipe_group *group = bgd->group;
265 /* next buddy group */
266 entry = entry->next;
268 /* old group NOT found in new list? */
269 if (g_slist_find(new_groups, group) == NULL) {
270 sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
271 uri,
272 group->name);
273 SIPE_DEBUG_INFO("sipe_buddy_update_groups: removing buddy %s from group '%s'",
274 uri, group->name);
275 /* this should never be NULL */
276 if (oldb)
277 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
278 oldb);
279 buddy_group_remove(buddy, bgd);
284 gchar *sipe_buddy_groups_string(struct sipe_buddy *buddy)
286 guint i = 0;
287 gchar *string;
288 /* creating array from GList, converting guint to gchar * */
289 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
290 GSList *entry = buddy->groups;
292 if (!ids_arr)
293 return(NULL);
295 while (entry) {
296 const struct sipe_group *group = ((struct buddy_group_data *) entry->data)->group;
297 ids_arr[i] = g_strdup_printf("%u", group->id);
298 entry = entry->next;
299 i++;
301 ids_arr[i] = NULL;
303 string = g_strjoinv(" ", ids_arr);
304 g_strfreev(ids_arr);
306 return(string);
309 void sipe_buddy_cleanup_local_list(struct sipe_core_private *sipe_private)
311 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC,
312 NULL,
313 NULL);
314 GSList *entry = buddies;
316 SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: overall %d backend buddies (including clones)",
317 g_slist_length(buddies));
318 SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: %d sipe buddies (unique)",
319 sipe_buddy_count(sipe_private));
320 while (entry) {
321 sipe_backend_buddy bb = entry->data;
322 gchar *bname = sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC,
323 bb);
324 gchar *gname = sipe_backend_buddy_get_group_name(SIPE_CORE_PUBLIC,
325 bb);
326 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
327 bname);
329 if (!is_buddy_in_group(buddy, gname)) {
330 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",
331 bname, gname);
332 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, bb);
335 g_free(gname);
336 g_free(bname);
338 entry = entry->next;
341 g_slist_free(buddies);
344 struct sipe_buddy *sipe_buddy_find_by_uri(struct sipe_core_private *sipe_private,
345 const gchar *uri)
347 if (!uri) return(NULL);
348 return(g_hash_table_lookup(sipe_private->buddies->uri, uri));
351 struct sipe_buddy *sipe_buddy_find_by_exchange_key(struct sipe_core_private *sipe_private,
352 const gchar *exchange_key)
354 return(g_hash_table_lookup(sipe_private->buddies->exchange_key,
355 exchange_key));
358 void sipe_buddy_foreach(struct sipe_core_private *sipe_private,
359 GHFunc callback,
360 gpointer callback_data)
362 g_hash_table_foreach(sipe_private->buddies->uri,
363 callback,
364 callback_data);
367 static void buddy_free(struct sipe_buddy *buddy)
369 #ifndef _WIN32
371 * We are calling g_hash_table_foreach_steal(). That means that no
372 * key/value deallocation functions are called. Therefore the glib
373 * hash code does not touch the key (buddy->name) or value (buddy)
374 * of the to-be-deleted hash node at all. It follows that we
376 * - MUST free the memory for the key ourselves and
377 * - ARE allowed to do it in this function
379 * Conclusion: glib must be broken on the Windows platform if sipe
380 * crashes with SIGTRAP when closing. You'll have to live
381 * with the memory leak until this is fixed.
383 g_free(buddy->name);
384 #endif
385 g_free(buddy->exchange_key);
386 g_free(buddy->change_key);
387 g_free(buddy->activity);
388 g_free(buddy->meeting_subject);
389 g_free(buddy->meeting_location);
390 g_free(buddy->note);
392 g_free(buddy->cal_start_time);
393 g_free(buddy->cal_free_busy_base64);
394 g_free(buddy->cal_free_busy);
395 g_free(buddy->last_non_cal_activity);
397 sipe_cal_free_working_hours(buddy->cal_working_hours);
399 g_free(buddy->device_name);
400 sipe_utils_slist_free_full(buddy->groups, buddy_group_free);
401 g_free(buddy);
404 static gboolean buddy_free_cb(SIPE_UNUSED_PARAMETER gpointer key,
405 gpointer buddy,
406 SIPE_UNUSED_PARAMETER gpointer user_data)
408 buddy_free(buddy);
409 /* We must return TRUE as the key/value have already been deleted */
410 return(TRUE);
413 void sipe_buddy_free(struct sipe_core_private *sipe_private)
415 struct sipe_buddies *buddies = sipe_private->buddies;
417 g_hash_table_foreach_steal(buddies->uri,
418 buddy_free_cb,
419 NULL);
421 /* core is being deallocated, remove all its pending photo requests */
422 while (buddies->pending_photo_requests) {
423 struct photo_response_data *data =
424 buddies->pending_photo_requests->data;
425 buddies->pending_photo_requests =
426 g_slist_remove(buddies->pending_photo_requests, data);
427 photo_response_data_free(data);
430 g_hash_table_destroy(buddies->uri);
431 g_hash_table_destroy(buddies->exchange_key);
432 g_free(buddies);
433 sipe_private->buddies = NULL;
436 static void buddy_set_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key,
437 gpointer value,
438 SIPE_UNUSED_PARAMETER gpointer user_data)
440 struct sipe_buddy *buddy = value;
441 GSList *entry = buddy->groups;
443 buddy->is_obsolete = TRUE;
444 while (entry) {
445 ((struct buddy_group_data *) entry->data)->is_obsolete = TRUE;
446 entry = entry->next;
450 void sipe_buddy_update_start(struct sipe_core_private *sipe_private)
452 g_hash_table_foreach(sipe_private->buddies->uri,
453 buddy_set_obsolete_flag,
454 NULL);
457 static gboolean buddy_check_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key,
458 gpointer value,
459 gpointer user_data)
461 struct sipe_core_private *sipe_private = user_data;
462 struct sipe_buddy *buddy = value;
463 const gchar *uri = buddy->name;
465 if (buddy->is_obsolete) {
466 /* all backend buddies in different groups */
467 GSList *buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC,
468 uri,
469 NULL);
470 GSList *entry = buddies;
472 SIPE_DEBUG_INFO("buddy_check_obsolete_flag: REMOVING %d backend buddies for '%s'",
473 g_slist_length(buddies),
474 uri);
476 while (entry) {
477 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
478 entry->data);
479 entry = entry->next;
481 g_slist_free(buddies);
483 buddy_free(buddy);
484 /* return TRUE as the key/value have already been deleted */
485 return(TRUE);
487 } else {
488 GSList *entry = buddy->groups;
490 while (entry) {
491 struct buddy_group_data *bgd = entry->data;
493 /* next buddy group */
494 entry = entry->next;
496 if (bgd->is_obsolete) {
497 const struct sipe_group *group = bgd->group;
498 sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
499 uri,
500 group->name);
501 SIPE_DEBUG_INFO("buddy_check_obsolete_flag: removing buddy '%s' from group '%s'",
502 uri, group->name);
503 /* this should never be NULL */
504 if (oldb)
505 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC,
506 oldb);
507 buddy_group_remove(buddy, bgd);
510 return(FALSE);
514 void sipe_buddy_update_finish(struct sipe_core_private *sipe_private)
516 g_hash_table_foreach_remove(sipe_private->buddies->uri,
517 buddy_check_obsolete_flag,
518 sipe_private);
521 gchar *sipe_core_buddy_status(struct sipe_core_public *sipe_public,
522 const gchar *uri,
523 guint activity,
524 const gchar *status_text)
526 struct sipe_buddy *sbuddy;
527 GString *status;
529 if (!sipe_public) return NULL; /* happens on pidgin exit */
531 sbuddy = sipe_buddy_find_by_uri(SIPE_CORE_PRIVATE, uri);
532 if (!sbuddy) return NULL;
534 status = g_string_new(sbuddy->activity ? sbuddy->activity :
535 (activity == SIPE_ACTIVITY_BUSY) || (activity == SIPE_ACTIVITY_BRB) ?
536 status_text : NULL);
538 if (sbuddy->is_mobile) {
539 if (status->len)
540 g_string_append(status, " - ");
541 g_string_append(status, _("Mobile"));
544 if (sbuddy->note) {
545 if (status->len)
546 g_string_append(status, " - ");
547 g_string_append(status, sbuddy->note);
550 /* return NULL instead of empty status text */
551 return(g_string_free(status, status->len ? FALSE : TRUE));
554 gchar *sipe_buddy_get_alias(struct sipe_core_private *sipe_private,
555 const gchar *with)
557 sipe_backend_buddy pbuddy;
558 gchar *alias = NULL;
559 if ((pbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, with, NULL))) {
560 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, pbuddy);
562 return alias;
565 void sipe_core_buddy_group(struct sipe_core_public *sipe_public,
566 const gchar *who,
567 const gchar *old_group_name,
568 const gchar *new_group_name)
570 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
571 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
572 who);
573 struct sipe_group *old_group = NULL;
574 struct sipe_group *new_group;
575 struct sipe_ucs_transaction *ucs_trans = NULL;
577 SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy '%s' old group '%s' new group '%s'",
578 who ? who : "",
579 old_group_name ? old_group_name : "<UNDEFINED>",
580 new_group_name ? new_group_name : "<UNDEFINED>");
582 if (!buddy)
583 /* buddy not in roaming list */
584 return;
586 old_group = sipe_group_find_by_name(sipe_private, old_group_name);
587 if (old_group) {
588 sipe_buddy_remove_group(buddy, old_group);
589 SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy '%s' removed from old group '%s'",
590 who, old_group_name);
593 new_group = sipe_group_find_by_name(sipe_private, new_group_name);
594 if (new_group) {
595 sipe_buddy_insert_group(buddy, new_group);
596 SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy '%s' added to new group '%s'",
597 who, new_group_name);
600 if (sipe_ucs_is_migrated(sipe_private)) {
602 /* UCS handling */
603 ucs_trans = sipe_ucs_transaction(sipe_private);
605 if (new_group) {
607 * 1. new buddy added to existing group
608 * 2. existing buddy moved from old to existing group
610 sipe_ucs_group_add_buddy(sipe_private,
611 ucs_trans,
612 new_group,
613 buddy,
614 buddy->name);
615 if (old_group)
616 sipe_ucs_group_remove_buddy(sipe_private,
617 ucs_trans,
618 old_group,
619 buddy);
621 } else if (old_group) {
623 * 3. existing buddy removed from one of its groups
624 * 4. existing buddy removed from last group
626 sipe_ucs_group_remove_buddy(sipe_private,
627 ucs_trans,
628 old_group,
629 buddy);
630 if (g_slist_length(buddy->groups) < 1)
631 sipe_buddy_remove(sipe_private,
632 buddy);
633 /* buddy no longer valid */
636 /* non-UCS handling */
637 } else if (new_group)
638 sipe_group_update_buddy(sipe_private, buddy);
640 /* 5. buddy added to new group */
641 if (!new_group)
642 sipe_group_create(sipe_private,
643 ucs_trans,
644 new_group_name,
645 who);
648 void sipe_core_buddy_add(struct sipe_core_public *sipe_public,
649 const gchar *uri,
650 const gchar *group_name)
652 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
654 if (!sipe_buddy_find_by_uri(sipe_private, uri))
655 sipe_buddy_add(sipe_private,
656 uri,
657 NULL,
658 NULL);
659 else
660 SIPE_DEBUG_INFO("sipe_core_buddy_add: buddy %s already in internal list",
661 uri);
663 sipe_core_buddy_group(sipe_public,
664 uri,
665 NULL,
666 group_name);
669 void sipe_buddy_remove(struct sipe_core_private *sipe_private,
670 struct sipe_buddy *buddy)
672 struct sipe_buddies *buddies = sipe_private->buddies;
673 const gchar *uri = buddy->name;
674 GSList *entry = buddy->groups;
675 gchar *action_name = sipe_utils_presence_key(uri);
677 sipe_schedule_cancel(sipe_private, action_name);
678 g_free(action_name);
680 /* If the buddy still has groups, we need to delete backend buddies */
681 while (entry) {
682 const struct sipe_group *group = ((struct buddy_group_data *) entry->data)->group;
683 sipe_backend_buddy oldb = sipe_backend_buddy_find(SIPE_CORE_PUBLIC,
684 uri,
685 group->name);
686 /* this should never be NULL */
687 if (oldb)
688 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC, oldb);
690 entry = entry->next;
693 g_hash_table_remove(buddies->uri, uri);
694 if (buddy->exchange_key)
695 g_hash_table_remove(buddies->exchange_key,
696 buddy->exchange_key);
698 buddy_free(buddy);
702 * Unassociates buddy from group first.
703 * Then see if no groups left, removes buddy completely.
704 * Otherwise updates buddy groups on server.
706 void sipe_core_buddy_remove(struct sipe_core_public *sipe_public,
707 const gchar *uri,
708 const gchar *group_name)
710 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
711 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
712 uri);
713 struct sipe_group *group = NULL;
715 if (!buddy) return;
717 if (group_name) {
718 group = sipe_group_find_by_name(sipe_private, group_name);
719 if (group) {
720 sipe_buddy_remove_group(buddy, group);
721 SIPE_DEBUG_INFO("sipe_core_buddy_remove: buddy '%s' removed from group '%s'",
722 uri, group->name);
726 if (g_slist_length(buddy->groups) < 1) {
728 if (sipe_ucs_is_migrated(sipe_private)) {
729 sipe_ucs_group_remove_buddy(sipe_private,
730 NULL,
731 group,
732 buddy);
733 } else {
734 gchar *request = g_strdup_printf("<m:URI>%s</m:URI>",
735 buddy->name);
736 sip_soap_request(sipe_private,
737 "deleteContact",
738 request);
739 g_free(request);
742 sipe_buddy_remove(sipe_private, buddy);
743 } else {
744 if (sipe_ucs_is_migrated(sipe_private)) {
745 sipe_ucs_group_remove_buddy(sipe_private,
746 NULL,
747 group,
748 buddy);
749 } else
750 /* updates groups on server */
751 sipe_group_update_buddy(sipe_private, buddy);
755 void sipe_core_buddy_got_status(struct sipe_core_public *sipe_public,
756 const gchar *uri,
757 guint activity)
759 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
760 struct sipe_buddy *sbuddy = sipe_buddy_find_by_uri(sipe_private,
761 uri);
763 if (!sbuddy) return;
765 /* Check if on 2005 system contact's calendar,
766 * then set/preserve it.
768 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
769 sipe_backend_buddy_set_status(sipe_public, uri, activity);
770 } else {
771 sipe_ocs2005_apply_calendar_status(sipe_private,
772 sbuddy,
773 sipe_status_activity_to_token(activity));
777 void sipe_core_buddy_tooltip_info(struct sipe_core_public *sipe_public,
778 const gchar *uri,
779 const gchar *status_name,
780 gboolean is_online,
781 struct sipe_backend_buddy_tooltip *tooltip)
783 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
784 gchar *note = NULL;
785 gboolean is_oof_note = FALSE;
786 const gchar *activity = NULL;
787 gchar *calendar = NULL;
788 const gchar *meeting_subject = NULL;
789 const gchar *meeting_location = NULL;
790 gchar *access_text = NULL;
792 #define SIPE_ADD_BUDDY_INFO(l, t) \
794 gchar *tmp = g_markup_escape_text((t), -1); \
795 sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), tmp); \
796 g_free(tmp); \
798 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) \
799 sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), (t))
801 if (sipe_public) { /* happens on pidgin exit */
802 struct sipe_buddy *sbuddy = sipe_buddy_find_by_uri(sipe_private,
803 uri);
804 if (sbuddy) {
805 note = sbuddy->note;
806 is_oof_note = sbuddy->is_oof_note;
807 activity = sbuddy->activity;
808 calendar = sipe_cal_get_description(sbuddy);
809 meeting_subject = sbuddy->meeting_subject;
810 meeting_location = sbuddy->meeting_location;
812 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
813 gboolean is_group_access = FALSE;
814 const int container_id = sipe_ocs2007_find_access_level(sipe_private,
815 "user",
816 sipe_get_no_sip_uri(uri),
817 &is_group_access);
818 const char *access_level = sipe_ocs2007_access_level_name(container_id);
819 access_text = is_group_access ?
820 g_strdup(access_level) :
821 g_strdup_printf(SIPE_OCS2007_INDENT_MARKED_FMT,
822 access_level);
826 if (is_online) {
827 const gchar *status_str = activity ? activity : status_name;
829 SIPE_ADD_BUDDY_INFO(_("Status"), status_str);
831 if (is_online && !is_empty(calendar)) {
832 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar);
834 g_free(calendar);
835 if (!is_empty(meeting_location)) {
836 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", uri, meeting_location);
837 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location);
839 if (!is_empty(meeting_subject)) {
840 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", uri, meeting_subject);
841 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject);
843 if (note) {
844 gchar *note_italics = g_strdup_printf("<i>%s</i>", note);
845 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", uri, note);
846 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note ? _("Out of office note") : _("Note"),
847 note_italics);
848 g_free(note_italics);
850 if (access_text) {
851 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text);
852 g_free(access_text);
856 void sipe_buddy_update_property(struct sipe_core_private *sipe_private,
857 const char *uri,
858 sipe_buddy_info_fields propkey,
859 char *property_value)
861 GSList *buddies, *entry;
863 if (property_value)
864 property_value = g_strstrip(property_value);
866 entry = buddies = sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC, uri, NULL); /* all buddies in different groups */
867 while (entry) {
868 gchar *prop_str;
869 sipe_backend_buddy p_buddy = entry->data;
871 /* for Display Name */
872 if (propkey == SIPE_BUDDY_INFO_DISPLAY_NAME) {
873 gchar *alias;
874 alias = sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC, p_buddy);
875 if (property_value && sipe_is_bad_alias(uri, alias)) {
876 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri, property_value);
877 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
879 g_free(alias);
881 alias = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC, p_buddy);
882 if (!is_empty(property_value) &&
883 (!sipe_strequal(property_value, alias) || is_empty(alias)) )
885 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri, property_value);
886 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC, p_buddy, property_value);
888 g_free(alias);
890 /* for other properties */
891 else {
892 if (!is_empty(property_value)) {
893 prop_str = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC, p_buddy, propkey);
894 if (!prop_str || !sipe_strcase_equal(prop_str, property_value)) {
895 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC, p_buddy, propkey, property_value);
897 g_free(prop_str);
901 entry = entry->next;
903 g_slist_free(buddies);
907 struct ms_dlx_data;
908 struct ms_dlx_data {
909 GSList *search_rows;
910 gchar *other;
911 guint max_returns;
912 sipe_svc_callback *callback;
913 struct sipe_svc_session *session;
914 gchar *wsse_security;
915 struct sipe_backend_search_token *token;
916 /* must call ms_dlx_free() */
917 void (*failed_callback)(struct sipe_core_private *sipe_private,
918 struct ms_dlx_data *mdd);
921 static void free_search_rows(GSList *search_rows)
923 sipe_utils_slist_free_full(search_rows, g_free);
926 static void ms_dlx_free(struct ms_dlx_data *mdd)
928 free_search_rows(mdd->search_rows);
929 sipe_svc_session_close(mdd->session);
930 g_free(mdd->other);
931 g_free(mdd->wsse_security);
932 g_free(mdd);
935 #define SIPE_SOAP_SEARCH_ROW "<m:row m:attrib=\"%s\" m:value=\"%s\"/>"
936 #define DLX_SEARCH_ITEM \
937 "<AbEntryRequest.ChangeSearchQuery>" \
938 " <SearchOn>%s</SearchOn>" \
939 " <Value>%s</Value>" \
940 "</AbEntryRequest.ChangeSearchQuery>"
942 static gchar * prepare_buddy_search_query(GSList *query_rows, gboolean use_dlx) {
943 gchar **attrs = g_new(gchar *, (g_slist_length(query_rows) / 2) + 1);
944 guint i = 0;
945 gchar *query = NULL;
947 while (query_rows) {
948 gchar *attr;
949 gchar *value;
950 gchar *tmp = NULL;
952 attr = query_rows->data;
953 query_rows = g_slist_next(query_rows);
954 value = query_rows->data;
955 query_rows = g_slist_next(query_rows);
957 if (!value)
958 break;
961 * Special value for SIP ID
963 * Active Directory seems only to be able to search for
964 * SIP URIs. Make sure search string starts with "sip:".
966 if (!attr) {
967 attr = "msRTCSIP-PrimaryUserAddress";
968 if (!use_dlx)
969 value = tmp = sip_uri(value);
972 attrs[i++] = g_markup_printf_escaped(use_dlx ? DLX_SEARCH_ITEM : SIPE_SOAP_SEARCH_ROW,
973 attr, value);
974 g_free(tmp);
976 attrs[i] = NULL;
978 if (i) {
979 query = g_strjoinv(NULL, attrs);
980 SIPE_DEBUG_INFO("prepare_buddy_search_query: rows:\n%s",
981 query ? query : "");
984 g_strfreev(attrs);
986 return query;
989 static void ms_dlx_webticket(struct sipe_core_private *sipe_private,
990 const gchar *base_uri,
991 const gchar *auth_uri,
992 const gchar *wsse_security,
993 SIPE_UNUSED_PARAMETER const gchar *failure_msg,
994 gpointer callback_data)
996 struct ms_dlx_data *mdd = callback_data;
998 if (wsse_security) {
999 guint length = g_slist_length(mdd->search_rows);
1000 gchar *search;
1002 SIPE_DEBUG_INFO("ms_dlx_webticket: got ticket for %s",
1003 base_uri);
1005 if (length > 0) {
1006 /* complex search */
1007 gchar *query = prepare_buddy_search_query(mdd->search_rows, TRUE);
1008 search = g_strdup_printf("<ChangeSearch xmlns:q1=\"DistributionListExpander\" soapenc:arrayType=\"q1:AbEntryRequest.ChangeSearchQuery[%d]\">"
1009 " %s"
1010 "</ChangeSearch>",
1011 length / 2,
1012 query);
1013 g_free(query);
1014 } else {
1015 /* simple search */
1016 search = g_strdup_printf("<BasicSearch>"
1017 " <SearchList>c,company,displayName,givenName,mail,mailNickname,msRTCSIP-PrimaryUserAddress,sn</SearchList>"
1018 " <Value>%s</Value>"
1019 " <Verb>BeginsWith</Verb>"
1020 "</BasicSearch>",
1021 mdd->other);
1024 if (sipe_svc_ab_entry_request(sipe_private,
1025 mdd->session,
1026 auth_uri,
1027 wsse_security,
1028 search,
1029 mdd->max_returns,
1030 mdd->callback,
1031 mdd)) {
1033 /* keep webticket security token for potential further use */
1034 g_free(mdd->wsse_security);
1035 mdd->wsse_security = g_strdup(wsse_security);
1037 /* callback data passed down the line */
1038 mdd = NULL;
1040 g_free(search);
1042 } else {
1043 /* no ticket: this will show the minmum information */
1044 SIPE_DEBUG_ERROR("ms_dlx_webticket: no web ticket for %s",
1045 base_uri);
1048 if (mdd)
1049 mdd->failed_callback(sipe_private, mdd);
1052 static void ms_dlx_webticket_request(struct sipe_core_private *sipe_private,
1053 struct ms_dlx_data *mdd)
1055 if (!sipe_webticket_request_with_port(sipe_private,
1056 mdd->session,
1057 sipe_private->dlx_uri,
1058 "AddressBookWebTicketBearer",
1059 ms_dlx_webticket,
1060 mdd)) {
1061 SIPE_DEBUG_ERROR("ms_dlx_webticket_request: couldn't request webticket for %s",
1062 sipe_private->dlx_uri);
1063 mdd->failed_callback(sipe_private, mdd);
1067 void sipe_buddy_search_contacts_finalize(struct sipe_core_private *sipe_private,
1068 struct sipe_backend_search_results *results,
1069 guint match_count,
1070 gboolean more)
1072 gchar *secondary = g_strdup_printf(
1073 dngettext(PACKAGE_NAME,
1074 "Found %d contact%s:",
1075 "Found %d contacts%s:", match_count),
1076 match_count, more ? _(" (more matched your query)") : "");
1078 sipe_backend_search_results_finalize(SIPE_CORE_PUBLIC,
1079 results,
1080 secondary,
1081 more);
1082 g_free(secondary);
1085 static void search_ab_entry_response(struct sipe_core_private *sipe_private,
1086 const gchar *uri,
1087 SIPE_UNUSED_PARAMETER const gchar *raw,
1088 sipe_xml *soap_body,
1089 gpointer callback_data)
1091 struct ms_dlx_data *mdd = callback_data;
1093 if (soap_body) {
1094 const sipe_xml *node;
1095 struct sipe_backend_search_results *results;
1096 GHashTable *found;
1098 SIPE_DEBUG_INFO("search_ab_entry_response: received valid SOAP message from service %s",
1099 uri);
1101 /* any matches? */
1102 node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry");
1103 if (!node) {
1104 /* try again with simple search, if possible */
1105 if (mdd->other && mdd->search_rows) {
1106 SIPE_DEBUG_INFO_NOFORMAT("search_ab_entry_response: no matches, retrying with simple search");
1108 /* throw away original search query */
1109 free_search_rows(mdd->search_rows);
1110 mdd->search_rows = NULL;
1112 ms_dlx_webticket_request(sipe_private, mdd);
1114 /* callback data passed down the line */
1115 return;
1117 } else {
1118 SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: no matches");
1120 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1121 mdd->token,
1122 _("No contacts found"));
1123 ms_dlx_free(mdd);
1124 return;
1128 /* OK, we found something - show the results to the user */
1129 results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
1130 mdd->token);
1131 if (!results) {
1132 SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: Unable to display the search results.");
1133 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1134 mdd->token,
1135 _("Unable to display the search results"));
1136 ms_dlx_free(mdd);
1137 return;
1140 /* SearchAbEntryResult can contain duplicates */
1141 found = g_hash_table_new_full(g_str_hash, g_str_equal,
1142 g_free, NULL);
1144 for (/* initialized above */ ; node; node = sipe_xml_twin(node)) {
1145 const sipe_xml *attrs;
1146 gchar *sip_uri = NULL;
1147 gchar *displayname = NULL;
1148 gchar *company = NULL;
1149 gchar *country = NULL;
1150 gchar *email = NULL;
1152 for (attrs = sipe_xml_child(node, "Attributes/Attribute");
1153 attrs;
1154 attrs = sipe_xml_twin(attrs)) {
1155 gchar *name = sipe_xml_data(sipe_xml_child(attrs,
1156 "Name"));
1157 gchar *value = sipe_xml_data(sipe_xml_child(attrs,
1158 "Value"));
1160 if (!is_empty(value)) {
1161 if (sipe_strcase_equal(name, "msrtcsip-primaryuseraddress")) {
1162 g_free(sip_uri);
1163 sip_uri = value;
1164 value = NULL;
1165 } else if (sipe_strcase_equal(name, "displayname")) {
1166 g_free(displayname);
1167 displayname = value;
1168 value = NULL;
1169 } else if (sipe_strcase_equal(name, "mail")) {
1170 g_free(email);
1171 email = value;
1172 value = NULL;
1173 } else if (sipe_strcase_equal(name, "company")) {
1174 g_free(company);
1175 company = value;
1176 value = NULL;
1177 } else if (sipe_strcase_equal(name, "country")) {
1178 g_free(country);
1179 country = value;
1180 value = NULL;
1184 g_free(value);
1185 g_free(name);
1188 if (sip_uri && !g_hash_table_lookup(found, sip_uri)) {
1189 gchar **uri_parts = g_strsplit(sip_uri, ":", 2);
1190 sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
1191 results,
1192 uri_parts[1],
1193 displayname,
1194 company,
1195 country,
1196 email);
1197 g_strfreev(uri_parts);
1199 g_hash_table_insert(found, sip_uri, (gpointer) TRUE);
1200 sip_uri = NULL;
1203 g_free(email);
1204 g_free(country);
1205 g_free(company);
1206 g_free(displayname);
1207 g_free(sip_uri);
1210 sipe_buddy_search_contacts_finalize(sipe_private, results,
1211 g_hash_table_size(found),
1212 FALSE);
1213 g_hash_table_destroy(found);
1214 ms_dlx_free(mdd);
1216 } else {
1217 mdd->failed_callback(sipe_private, mdd);
1221 static gboolean process_search_contact_response(struct sipe_core_private *sipe_private,
1222 struct sipmsg *msg,
1223 struct transaction *trans)
1225 struct sipe_backend_search_token *token = trans->payload->data;
1226 struct sipe_backend_search_results *results;
1227 sipe_xml *searchResults;
1228 const sipe_xml *mrow;
1229 guint match_count = 0;
1230 gboolean more = FALSE;
1232 /* valid response? */
1233 if (msg->response != 200) {
1234 SIPE_DEBUG_ERROR("process_search_contact_response: request failed (%d)",
1235 msg->response);
1236 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1237 token,
1238 _("Contact search failed"));
1239 return(FALSE);
1242 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg->body ? msg->body : "");
1244 /* valid XML? */
1245 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
1246 if (!searchResults) {
1247 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
1248 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1249 token,
1250 _("Contact search failed"));
1251 return(FALSE);
1254 /* any matches? */
1255 mrow = sipe_xml_child(searchResults, "Body/Array/row");
1256 if (!mrow) {
1257 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: no matches");
1258 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1259 token,
1260 _("No contacts found"));
1262 sipe_xml_free(searchResults);
1263 return(FALSE);
1266 /* OK, we found something - show the results to the user */
1267 results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
1268 trans->payload->data);
1269 if (!results) {
1270 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: Unable to display the search results.");
1271 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
1272 token,
1273 _("Unable to display the search results"));
1275 sipe_xml_free(searchResults);
1276 return FALSE;
1279 for (/* initialized above */ ; mrow; mrow = sipe_xml_twin(mrow)) {
1280 gchar **uri_parts = g_strsplit(sipe_xml_attribute(mrow, "uri"), ":", 2);
1281 sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
1282 results,
1283 uri_parts[1],
1284 sipe_xml_attribute(mrow, "displayName"),
1285 sipe_xml_attribute(mrow, "company"),
1286 sipe_xml_attribute(mrow, "country"),
1287 sipe_xml_attribute(mrow, "email"));
1288 g_strfreev(uri_parts);
1289 match_count++;
1292 if ((mrow = sipe_xml_child(searchResults, "Body/directorySearch/moreAvailable")) != NULL) {
1293 char *data = sipe_xml_data(mrow);
1294 more = (g_ascii_strcasecmp(data, "true") == 0);
1295 g_free(data);
1298 sipe_buddy_search_contacts_finalize(sipe_private, results, match_count, more);
1299 sipe_xml_free(searchResults);
1301 return(TRUE);
1304 static void search_soap_request(struct sipe_core_private *sipe_private,
1305 GDestroyNotify destroy,
1306 void *data,
1307 guint max,
1308 SoapTransCallback callback,
1309 GSList *search_rows)
1311 gchar *query = prepare_buddy_search_query(search_rows, FALSE);
1312 struct transaction_payload *payload = g_new0(struct transaction_payload, 1);
1314 payload->destroy = destroy;
1315 payload->data = data;
1317 sip_soap_directory_search(sipe_private,
1318 max,
1319 query,
1320 callback,
1321 payload);
1322 g_free(query);
1325 static void search_ab_entry_failed(struct sipe_core_private *sipe_private,
1326 struct ms_dlx_data *mdd)
1328 /* error using [MS-DLX] server, retry using Active Directory */
1329 if (mdd->search_rows)
1330 search_soap_request(sipe_private,
1331 NULL,
1332 mdd->token,
1333 100,
1334 process_search_contact_response,
1335 mdd->search_rows);
1336 ms_dlx_free(mdd);
1339 void sipe_core_buddy_search(struct sipe_core_public *sipe_public,
1340 struct sipe_backend_search_token *token,
1341 const gchar *given_name,
1342 const gchar *surname,
1343 const gchar *email,
1344 const gchar *sipid,
1345 const gchar *company,
1346 const gchar *country)
1348 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1350 /* Lync 2013 or newer: use UCS if contacts are migrated */
1351 if (SIPE_CORE_PRIVATE_FLAG_IS(LYNC2013) &&
1352 sipe_ucs_is_migrated(sipe_private)) {
1354 sipe_ucs_search(sipe_private,
1355 token,
1356 given_name,
1357 surname,
1358 email,
1359 sipid,
1360 company,
1361 country);
1363 } else {
1364 GSList *query_rows = NULL;
1365 guint count = 0;
1366 const gchar *simple = NULL;
1368 #define ADD_QUERY_ROW(attr, val) \
1369 if (val) { \
1370 query_rows = g_slist_append(query_rows, g_strdup(attr)); \
1371 query_rows = g_slist_append(query_rows, g_strdup(val)); \
1372 simple = val; \
1373 count++; \
1376 ADD_QUERY_ROW("givenName", given_name);
1377 ADD_QUERY_ROW("sn", surname);
1378 ADD_QUERY_ROW("mail", email);
1379 /* prepare_buddy_search_query() interprets NULL as SIP ID */
1380 ADD_QUERY_ROW(NULL, sipid);
1381 ADD_QUERY_ROW("company", company);
1382 ADD_QUERY_ROW("c", country);
1384 if (query_rows) {
1385 if (sipe_private->dlx_uri != NULL) {
1386 struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
1388 mdd->search_rows = query_rows;
1389 /* user entered only one search string, remember that one */
1390 if (count == 1)
1391 mdd->other = g_strdup(simple);
1392 mdd->max_returns = 100;
1393 mdd->callback = search_ab_entry_response;
1394 mdd->failed_callback = search_ab_entry_failed;
1395 mdd->session = sipe_svc_session_start();
1396 mdd->token = token;
1398 ms_dlx_webticket_request(sipe_private, mdd);
1400 } else {
1401 /* no [MS-DLX] server, use Active Directory search instead */
1402 search_soap_request(sipe_private,
1403 NULL,
1404 token,
1405 100,
1406 process_search_contact_response,
1407 query_rows);
1408 free_search_rows(query_rows);
1410 } else
1411 sipe_backend_search_failed(sipe_public,
1412 token,
1413 _("Invalid contact search query"));
1417 static void get_info_finalize(struct sipe_core_private *sipe_private,
1418 struct sipe_backend_buddy_info *info,
1419 const gchar *uri,
1420 const gchar *server_alias,
1421 const gchar *email)
1423 sipe_backend_buddy bbuddy;
1424 struct sipe_buddy *sbuddy;
1425 gchar *alias;
1426 gchar *value;
1428 if (!info) {
1429 info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1430 } else {
1431 sipe_backend_buddy_info_break(SIPE_CORE_PUBLIC, info);
1433 if (!info)
1434 return;
1436 bbuddy = sipe_backend_buddy_find(SIPE_CORE_PUBLIC, uri, NULL);
1438 if (is_empty(server_alias)) {
1439 value = sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC,
1440 bbuddy);
1441 if (value) {
1442 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1443 info,
1444 SIPE_BUDDY_INFO_DISPLAY_NAME,
1445 value);
1447 } else {
1448 value = g_strdup(server_alias);
1451 /* present alias if it differs from server alias */
1452 alias = sipe_backend_buddy_get_local_alias(SIPE_CORE_PUBLIC, bbuddy);
1453 if (alias && !sipe_strequal(alias, value))
1455 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1456 info,
1457 SIPE_BUDDY_INFO_ALIAS,
1458 alias);
1460 g_free(alias);
1461 g_free(value);
1463 if (is_empty(email)) {
1464 value = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC,
1465 bbuddy,
1466 SIPE_BUDDY_INFO_EMAIL);
1467 if (value) {
1468 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1469 info,
1470 SIPE_BUDDY_INFO_EMAIL,
1471 value);
1472 g_free(value);
1476 value = sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC,
1477 bbuddy,
1478 SIPE_BUDDY_INFO_SITE);
1479 if (value) {
1480 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1481 info,
1482 SIPE_BUDDY_INFO_SITE,
1483 value);
1484 g_free(value);
1487 sbuddy = sipe_buddy_find_by_uri(sipe_private, uri);
1488 if (sbuddy && sbuddy->device_name) {
1489 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1490 info,
1491 SIPE_BUDDY_INFO_DEVICE,
1492 sbuddy->device_name);
1495 sipe_backend_buddy_info_finalize(SIPE_CORE_PUBLIC, info, uri);
1499 static void get_info_ab_entry_response(struct sipe_core_private *sipe_private,
1500 const gchar *uri,
1501 SIPE_UNUSED_PARAMETER const gchar *raw,
1502 sipe_xml *soap_body,
1503 gpointer callback_data)
1505 struct ms_dlx_data *mdd = callback_data;
1506 struct sipe_backend_buddy_info *info = NULL;
1507 gchar *server_alias = NULL;
1508 gchar *email = NULL;
1510 if (soap_body) {
1511 const sipe_xml *node;
1513 SIPE_DEBUG_INFO("get_info_ab_entry_response: received valid SOAP message from service %s",
1514 uri);
1516 info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1518 for (node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
1519 node;
1520 node = sipe_xml_twin(node)) {
1521 gchar *name = sipe_xml_data(sipe_xml_child(node,
1522 "Name"));
1523 gchar *value = sipe_xml_data(sipe_xml_child(node,
1524 "Value"));
1525 const sipe_xml *values = sipe_xml_child(node,
1526 "Values");
1528 /* Single value entries */
1529 if (!is_empty(value)) {
1531 if (sipe_strcase_equal(name, "displayname")) {
1532 g_free(server_alias);
1533 server_alias = value;
1534 value = NULL;
1535 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1536 info,
1537 SIPE_BUDDY_INFO_DISPLAY_NAME,
1538 server_alias);
1539 } else if (sipe_strcase_equal(name, "mail")) {
1540 g_free(email);
1541 email = value;
1542 value = NULL;
1543 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1544 info,
1545 SIPE_BUDDY_INFO_EMAIL,
1546 email);
1547 } else if (sipe_strcase_equal(name, "title")) {
1548 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1549 info,
1550 SIPE_BUDDY_INFO_JOB_TITLE,
1551 value);
1552 } else if (sipe_strcase_equal(name, "company")) {
1553 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1554 info,
1555 SIPE_BUDDY_INFO_COMPANY,
1556 value);
1557 } else if (sipe_strcase_equal(name, "country")) {
1558 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1559 info,
1560 SIPE_BUDDY_INFO_COUNTRY,
1561 value);
1564 } else if (values) {
1565 gchar *first = sipe_xml_data(sipe_xml_child(values,
1566 "string"));
1568 if (sipe_strcase_equal(name, "telephonenumber")) {
1569 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1570 info,
1571 SIPE_BUDDY_INFO_WORK_PHONE,
1572 first);
1575 g_free(first);
1578 g_free(value);
1579 g_free(name);
1583 /* this will show the minmum information */
1584 get_info_finalize(sipe_private,
1585 info,
1586 mdd->other,
1587 server_alias,
1588 email);
1590 g_free(email);
1591 g_free(server_alias);
1592 ms_dlx_free(mdd);
1595 static gboolean process_get_info_response(struct sipe_core_private *sipe_private,
1596 struct sipmsg *msg,
1597 struct transaction *trans)
1599 const gchar *uri = trans->payload->data;
1600 struct sipe_backend_buddy_info *info = NULL;
1601 gchar *server_alias = NULL;
1602 gchar *email = NULL;
1604 SIPE_DEBUG_INFO("Fetching %s's user info for %s",
1605 uri, sipe_private->username);
1607 if (msg->response != 200) {
1608 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg->response);
1609 } else {
1610 sipe_xml *searchResults;
1611 const sipe_xml *mrow;
1613 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s",
1614 msg->body ? msg->body : "");
1616 searchResults = sipe_xml_parse(msg->body, msg->bodylen);
1617 if (!searchResults) {
1619 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
1621 } else if ((mrow = sipe_xml_child(searchResults, "Body/Array/row"))) {
1622 const gchar *value;
1623 gchar *phone_number;
1625 info = sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC);
1627 server_alias = g_strdup(sipe_xml_attribute(mrow, "displayName"));
1628 email = g_strdup(sipe_xml_attribute(mrow, "email"));
1629 phone_number = g_strdup(sipe_xml_attribute(mrow, "phone"));
1632 * For 2007 system we will take this from ContactCard -
1633 * it has cleaner tel: URIs at least
1635 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
1636 char *tel_uri = sip_to_tel_uri(phone_number);
1637 /* trims its parameters, so call first */
1638 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_DISPLAY_NAME, server_alias);
1639 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_EMAIL, email);
1640 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE, tel_uri);
1641 sipe_buddy_update_property(sipe_private, uri, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY, phone_number);
1642 g_free(tel_uri);
1644 sipe_backend_buddy_refresh_properties(SIPE_CORE_PUBLIC,
1645 uri);
1648 if (!is_empty(server_alias)) {
1649 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1650 info,
1651 SIPE_BUDDY_INFO_DISPLAY_NAME,
1652 server_alias);
1654 if ((value = sipe_xml_attribute(mrow, "title")) && strlen(value) > 0) {
1655 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1656 info,
1657 SIPE_BUDDY_INFO_JOB_TITLE,
1658 value);
1660 if ((value = sipe_xml_attribute(mrow, "office")) && strlen(value) > 0) {
1661 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1662 info,
1663 SIPE_BUDDY_INFO_OFFICE,
1664 value);
1666 if (!is_empty(phone_number)) {
1667 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1668 info,
1669 SIPE_BUDDY_INFO_WORK_PHONE,
1670 phone_number);
1672 g_free(phone_number);
1673 if ((value = sipe_xml_attribute(mrow, "company")) && strlen(value) > 0) {
1674 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1675 info,
1676 SIPE_BUDDY_INFO_COMPANY,
1677 value);
1679 if ((value = sipe_xml_attribute(mrow, "city")) && strlen(value) > 0) {
1680 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1681 info,
1682 SIPE_BUDDY_INFO_CITY,
1683 value);
1685 if ((value = sipe_xml_attribute(mrow, "state")) && strlen(value) > 0) {
1686 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1687 info,
1688 SIPE_BUDDY_INFO_STATE,
1689 value);
1691 if ((value = sipe_xml_attribute(mrow, "country")) && strlen(value) > 0) {
1692 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1693 info,
1694 SIPE_BUDDY_INFO_COUNTRY,
1695 value);
1697 if (!is_empty(email)) {
1698 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC,
1699 info,
1700 SIPE_BUDDY_INFO_EMAIL,
1701 email);
1704 sipe_xml_free(searchResults);
1707 /* this will show the minmum information */
1708 get_info_finalize(sipe_private,
1709 info,
1710 uri,
1711 server_alias,
1712 email);
1714 g_free(server_alias);
1715 g_free(email);
1717 return TRUE;
1720 static void get_info_ab_entry_failed(struct sipe_core_private *sipe_private,
1721 struct ms_dlx_data *mdd)
1723 /* error using [MS-DLX] server, retry using Active Directory */
1724 search_soap_request(sipe_private,
1725 g_free,
1726 mdd->other,
1728 process_get_info_response,
1729 mdd->search_rows);
1730 mdd->other = NULL;
1731 ms_dlx_free(mdd);
1734 static GSList *search_rows_for_uri(const gchar *uri)
1736 /* prepare_buddy_search_query() interprets NULL as SIP ID */
1737 GSList *l = g_slist_append(NULL, NULL);
1738 return(g_slist_append(l, g_strdup(uri)));
1741 void sipe_core_buddy_get_info(struct sipe_core_public *sipe_public,
1742 const gchar *who)
1744 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
1745 GSList *search_rows = search_rows_for_uri(who);
1747 if (sipe_private->dlx_uri) {
1748 struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
1750 mdd->search_rows = search_rows;
1751 mdd->other = g_strdup(who);
1752 mdd->max_returns = 1;
1753 mdd->callback = get_info_ab_entry_response;
1754 mdd->failed_callback = get_info_ab_entry_failed;
1755 mdd->session = sipe_svc_session_start();
1757 ms_dlx_webticket_request(sipe_private, mdd);
1759 } else {
1760 /* no [MS-DLX] server, use Active Directory search instead */
1761 search_soap_request(sipe_private,
1762 g_free,
1763 g_strdup(who),
1765 process_get_info_response,
1766 search_rows);
1767 free_search_rows(search_rows);
1771 static void photo_response_data_free(struct photo_response_data *data)
1773 g_free(data->who);
1774 g_free(data->photo_hash);
1775 if (data->request) {
1776 sipe_http_request_cancel(data->request);
1778 g_free(data);
1781 static void photo_response_data_remove(struct sipe_core_private *sipe_private,
1782 struct photo_response_data *data)
1784 data->request = NULL;
1785 sipe_private->buddies->pending_photo_requests =
1786 g_slist_remove(sipe_private->buddies->pending_photo_requests, data);
1787 photo_response_data_free(data);
1790 static void process_buddy_photo_response(struct sipe_core_private *sipe_private,
1791 guint status,
1792 GSList *headers,
1793 const char *body,
1794 gpointer data)
1796 struct photo_response_data *rdata = (struct photo_response_data *) data;
1798 if (status == SIPE_HTTP_STATUS_OK) {
1799 const gchar *len_str = sipe_utils_nameval_find(headers,
1800 "Content-Length");
1801 if (len_str) {
1802 gsize photo_size = atoi(len_str);
1803 gpointer photo = g_new(char, photo_size);
1805 if (photo) {
1806 memcpy(photo, body, photo_size);
1808 sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC,
1809 rdata->who,
1810 photo,
1811 photo_size,
1812 rdata->photo_hash);
1817 photo_response_data_remove(sipe_private, rdata);
1820 static void process_get_user_photo_response(struct sipe_core_private *sipe_private,
1821 guint status,
1822 SIPE_UNUSED_PARAMETER GSList *headers,
1823 const gchar *body,
1824 gpointer data)
1826 struct photo_response_data *rdata = (struct photo_response_data *) data;
1828 if ((status == SIPE_HTTP_STATUS_OK) && body) {
1829 sipe_xml *xml = sipe_xml_parse(body, strlen(body));
1830 const sipe_xml *node = sipe_xml_child(xml,
1831 "Body/GetUserPhotoResponse/PictureData");
1833 if (node) {
1834 gchar *base64;
1835 gsize photo_size;
1836 guchar *photo;
1838 /* decode photo data */
1839 base64 = sipe_xml_data(node);
1840 photo = g_base64_decode(base64, &photo_size);
1841 g_free(base64);
1843 /* EWS doesn't provide a hash -> calculate SHA-1 digest */
1844 if (!rdata->photo_hash) {
1845 guchar digest[SIPE_DIGEST_SHA1_LENGTH];
1846 sipe_digest_sha1(photo, photo_size, digest);
1848 /* rdata takes ownership of digest string */
1849 rdata->photo_hash = buff_to_hex_str(digest,
1850 SIPE_DIGEST_SHA1_LENGTH);
1853 /* backend frees "photo" */
1854 sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC,
1855 rdata->who,
1856 photo,
1857 photo_size,
1858 rdata->photo_hash);
1861 sipe_xml_free(xml);
1864 photo_response_data_remove(sipe_private, rdata);
1867 static gchar *create_x_ms_webticket_header(const gchar *wsse_security)
1869 gchar *assertion = sipe_xml_extract_raw(wsse_security, "Assertion", TRUE);
1870 gchar *wsse_security_base64;
1871 gchar *x_ms_webticket_header;
1873 if (!assertion) {
1874 return NULL;
1877 wsse_security_base64 = g_base64_encode((const guchar *)assertion,
1878 strlen(assertion));
1879 x_ms_webticket_header = g_strdup_printf("X-MS-WebTicket: opaque=%s\r\n",
1880 wsse_security_base64);
1882 g_free(assertion);
1883 g_free(wsse_security_base64);
1885 return x_ms_webticket_header;
1888 /* see also sipe_ucs_http_request() */
1889 static struct sipe_http_request *get_user_photo_request(struct sipe_core_private *sipe_private,
1890 struct photo_response_data *data,
1891 const gchar *ews_url,
1892 const gchar *email)
1894 gchar *soap = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
1895 "<soap:Envelope"
1896 " xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
1897 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
1898 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
1899 " >"
1900 " <soap:Header>"
1901 " <t:RequestServerVersion Version=\"Exchange2013\" />"
1902 " </soap:Header>"
1903 " <soap:Body>"
1904 " <m:GetUserPhoto>"
1905 " <m:Email>%s</m:Email>"
1906 " <m:SizeRequested>HR48x48</m:SizeRequested>"
1907 " </m:GetUserPhoto>"
1908 " </soap:Body>"
1909 "</soap:Envelope>",
1910 email);
1911 struct sipe_http_request *request = sipe_http_request_post(sipe_private,
1912 ews_url,
1913 NULL,
1914 soap,
1915 "text/xml; charset=UTF-8",
1916 process_get_user_photo_response,
1917 data);
1918 g_free(soap);
1920 if (request) {
1921 sipe_core_email_authentication(sipe_private,
1922 request);
1923 sipe_http_request_allow_redirect(request);
1924 } else {
1925 SIPE_DEBUG_ERROR_NOFORMAT("get_user_photo_request: failed to create HTTP connection");
1928 return(request);
1931 static void photo_response_data_finalize(struct sipe_core_private *sipe_private,
1932 struct photo_response_data *data,
1933 const gchar *uri,
1934 const gchar *photo_hash)
1936 if (data->request) {
1937 data->who = g_strdup(uri);
1938 data->photo_hash = g_strdup(photo_hash);
1940 sipe_private->buddies->pending_photo_requests =
1941 g_slist_append(sipe_private->buddies->pending_photo_requests, data);
1942 sipe_http_request_ready(data->request);
1943 } else {
1944 photo_response_data_free(data);
1948 void sipe_buddy_update_photo(struct sipe_core_private *sipe_private,
1949 const gchar *uri,
1950 const gchar *photo_hash,
1951 const gchar *photo_url,
1952 const gchar *headers)
1954 const gchar *photo_hash_old =
1955 sipe_backend_buddy_get_photo_hash(SIPE_CORE_PUBLIC, uri);
1957 if (!sipe_strequal(photo_hash, photo_hash_old)) {
1958 struct photo_response_data *data = g_new0(struct photo_response_data, 1);
1960 SIPE_DEBUG_INFO("sipe_buddy_update_photo: who '%s' url '%s' hash '%s'",
1961 uri, photo_url, photo_hash);
1963 /* Photo URL is embedded XML? */
1964 if (g_str_has_prefix(photo_url, "<") &&
1965 g_str_has_suffix(photo_url, ">")) {
1966 /* add dummy root to embedded XML string */
1967 gchar *tmp = g_strdup_printf("<r>%s</r>", photo_url);
1968 sipe_xml *xml = sipe_xml_parse(tmp, strlen(tmp));
1969 g_free(tmp);
1971 if (xml) {
1972 gchar *ews_url = sipe_xml_data(sipe_xml_child(xml, "ewsUrl"));
1973 gchar *email = sipe_xml_data(sipe_xml_child(xml, "primarySMTP"));
1975 if (!is_empty(ews_url) && !is_empty(email)) {
1977 * Workaround for missing Office 365 buddy icons
1979 * (All?) Office 365 contact cards have the following
1980 * XML embedded as the photo URI XML node text:
1982 * <ewsUrl>https://outlook.office365.com/EWS/Exchange.asmx/WSSecurity</ewsUrl>
1983 * <primarySMTP>user@company.com</primarySMTP>
1985 * The simple HTTP request by get_user_photo_request()
1986 * is rejected with 401. But the response contains
1988 * WWW-Authenticate: Basic Realm=""
1990 * to which the HTTP transport answers with a retry
1991 * using Basic authentication. That in turn is rejected
1992 * with 500 and thus the buddy icon retrieval fails.
1994 * As a quick workaround strip the trailing "/WSSecurity"
1995 * from the URL. The HTTP request for the buddy icon
1996 * retrieval will work with this stripped URL.
1998 * @TODO: this is probably not the correct approach.
1999 * get_user_photo_request() should be updated
2000 * to support also a webticket request.
2002 gchar *tmp = g_strrstr(ews_url, "/WSSecurity");
2003 if (tmp)
2004 *tmp = '\0';
2006 data->request = get_user_photo_request(sipe_private,
2007 data,
2008 ews_url,
2009 email);
2012 g_free(email);
2013 g_free(ews_url);
2014 sipe_xml_free(xml);
2016 } else {
2017 data->request = sipe_http_request_get(sipe_private,
2018 photo_url,
2019 headers,
2020 process_buddy_photo_response,
2021 data);
2024 photo_response_data_finalize(sipe_private,
2025 data,
2026 uri,
2027 photo_hash);
2031 static void get_photo_ab_entry_response(struct sipe_core_private *sipe_private,
2032 const gchar *uri,
2033 SIPE_UNUSED_PARAMETER const gchar *raw,
2034 sipe_xml *soap_body,
2035 gpointer callback_data)
2037 struct ms_dlx_data *mdd = callback_data;
2038 gchar *photo_rel_path = NULL;
2039 gchar *photo_hash = NULL;
2041 if (soap_body) {
2042 const sipe_xml *node;
2044 SIPE_DEBUG_INFO("get_photo_ab_entry_response: received valid SOAP message from service %s",
2045 uri);
2047 for (node = sipe_xml_child(soap_body, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
2048 node;
2049 node = sipe_xml_twin(node)) {
2050 gchar *name = sipe_xml_data(sipe_xml_child(node, "Name"));
2051 gchar *value = sipe_xml_data(sipe_xml_child(node, "Value"));
2053 if (!is_empty(value)) {
2054 if (sipe_strcase_equal(name, "PhotoRelPath")) {
2055 g_free(photo_rel_path);
2056 photo_rel_path = value;
2057 value = NULL;
2058 } else if (sipe_strcase_equal(name, "PhotoHash")) {
2059 g_free(photo_hash);
2060 photo_hash = value;
2061 value = NULL;
2065 g_free(value);
2066 g_free(name);
2070 if (sipe_private->addressbook_uri && photo_rel_path && photo_hash) {
2071 gchar *photo_url = g_strdup_printf("%s/%s",
2072 sipe_private->addressbook_uri, photo_rel_path);
2073 gchar *x_ms_webticket_header = create_x_ms_webticket_header(mdd->wsse_security);
2075 sipe_buddy_update_photo(sipe_private,
2076 mdd->other,
2077 photo_hash,
2078 photo_url,
2079 x_ms_webticket_header);
2081 g_free(x_ms_webticket_header);
2082 g_free(photo_url);
2085 g_free(photo_rel_path);
2086 g_free(photo_hash);
2087 ms_dlx_free(mdd);
2090 static void get_photo_ab_entry_failed(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
2091 struct ms_dlx_data *mdd)
2093 ms_dlx_free(mdd);
2096 static void buddy_fetch_photo(struct sipe_core_private *sipe_private,
2097 const gchar *uri)
2099 if (sipe_backend_uses_photo()) {
2101 /* Lync 2013 or newer: use UCS if contacts are migrated */
2102 if (SIPE_CORE_PRIVATE_FLAG_IS(LYNC2013) &&
2103 sipe_ucs_is_migrated(sipe_private)) {
2104 struct photo_response_data *data = g_new0(struct photo_response_data, 1);
2106 data->request = get_user_photo_request(sipe_private,
2107 data,
2108 sipe_ucs_ews_url(sipe_private),
2109 sipe_get_no_sip_uri(uri));
2110 photo_response_data_finalize(sipe_private,
2111 data,
2112 uri,
2113 /* there is no hash */
2114 NULL);
2116 /* Lync 2010: use [MS-DLX] */
2117 } else if (sipe_private->dlx_uri &&
2118 sipe_private->addressbook_uri) {
2119 struct ms_dlx_data *mdd = g_new0(struct ms_dlx_data, 1);
2121 mdd->search_rows = search_rows_for_uri(uri);
2122 mdd->other = g_strdup(uri);
2123 mdd->max_returns = 1;
2124 mdd->callback = get_photo_ab_entry_response;
2125 mdd->failed_callback = get_photo_ab_entry_failed;
2126 mdd->session = sipe_svc_session_start();
2128 ms_dlx_webticket_request(sipe_private, mdd);
2133 static void buddy_refresh_photos_cb(gpointer uri,
2134 SIPE_UNUSED_PARAMETER gpointer value,
2135 gpointer sipe_private)
2137 buddy_fetch_photo(sipe_private, uri);
2140 void sipe_buddy_refresh_photos(struct sipe_core_private *sipe_private)
2142 g_hash_table_foreach(sipe_private->buddies->uri,
2143 buddy_refresh_photos_cb,
2144 sipe_private);
2147 /* Buddy menu callbacks*/
2149 void sipe_core_buddy_new_chat(struct sipe_core_public *sipe_public,
2150 const gchar *who)
2152 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
2154 /* 2007+ conference */
2155 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007)) {
2156 sipe_conf_add(sipe_private, who);
2158 /* 2005- multiparty chat */
2159 } else {
2160 gchar *self = sip_uri_self(sipe_private);
2161 struct sip_session *session;
2163 session = sipe_session_add_chat(sipe_private,
2164 NULL,
2165 TRUE,
2166 self);
2167 session->chat_session->backend = sipe_backend_chat_create(SIPE_CORE_PUBLIC,
2168 session->chat_session,
2169 session->chat_session->title,
2170 self);
2171 g_free(self);
2173 sipe_im_invite(sipe_private, session, who,
2174 NULL, NULL, NULL, FALSE);
2178 void sipe_core_buddy_send_email(struct sipe_core_public *sipe_public,
2179 const gchar *who)
2181 sipe_backend_buddy buddy = sipe_backend_buddy_find(sipe_public,
2182 who,
2183 NULL);
2184 gchar *email = sipe_backend_buddy_get_string(sipe_public,
2185 buddy,
2186 SIPE_BUDDY_INFO_EMAIL);
2188 if (email) {
2189 gchar *command_line = g_strdup_printf(
2190 #ifdef _WIN32
2191 "cmd /c start"
2192 #else
2193 "xdg-email"
2194 #endif
2195 " mailto:%s", email);
2196 g_free(email);
2198 SIPE_DEBUG_INFO("sipe_core_buddy_send_email: going to call email client: %s",
2199 command_line);
2200 g_spawn_command_line_async(command_line, NULL);
2201 g_free(command_line);
2203 } else {
2204 SIPE_DEBUG_INFO("sipe_core_buddy_send_email: no email address stored for buddy=%s",
2205 who);
2209 /* Buddy menu */
2211 static struct sipe_backend_buddy_menu *buddy_menu_phone(struct sipe_core_public *sipe_public,
2212 struct sipe_backend_buddy_menu *menu,
2213 sipe_backend_buddy buddy,
2214 sipe_buddy_info_fields id_phone,
2215 sipe_buddy_info_fields id_display,
2216 const gchar *type)
2218 gchar *phone = sipe_backend_buddy_get_string(sipe_public,
2219 buddy,
2220 id_phone);
2221 if (phone) {
2222 gchar *display = sipe_backend_buddy_get_string(sipe_public,
2223 buddy,
2224 id_display);
2225 gchar *tmp = NULL;
2226 gchar *label = g_strdup_printf("%s %s",
2227 type,
2228 display ? display :
2229 (tmp = sip_tel_uri_denormalize(phone)));
2230 menu = sipe_backend_buddy_menu_add(sipe_public,
2231 menu,
2232 label,
2233 SIPE_BUDDY_MENU_MAKE_CALL,
2234 phone);
2235 g_free(tmp);
2236 g_free(label);
2237 g_free(display);
2238 g_free(phone);
2241 return(menu);
2244 struct sipe_backend_buddy_menu *sipe_core_buddy_create_menu(struct sipe_core_public *sipe_public,
2245 const gchar *buddy_name,
2246 struct sipe_backend_buddy_menu *menu)
2248 struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
2249 sipe_backend_buddy buddy = sipe_backend_buddy_find(sipe_public,
2250 buddy_name,
2251 NULL);
2252 gchar *self = sip_uri_self(sipe_private);
2254 SIPE_SESSION_FOREACH {
2255 if (!sipe_strcase_equal(self, buddy_name) && session->chat_session)
2257 struct sipe_chat_session *chat_session = session->chat_session;
2258 gboolean is_conf = (chat_session->type == SIPE_CHAT_TYPE_CONFERENCE);
2260 if (sipe_backend_chat_find(chat_session->backend, buddy_name))
2262 gboolean conf_op = sipe_backend_chat_is_operator(chat_session->backend, self);
2264 if (is_conf &&
2265 /* Not conf OP */
2266 !sipe_backend_chat_is_operator(chat_session->backend, buddy_name) &&
2267 /* We are a conf OP */
2268 conf_op) {
2269 gchar *label = g_strdup_printf(_("Make leader of '%s'"),
2270 chat_session->title);
2271 menu = sipe_backend_buddy_menu_add(sipe_public,
2272 menu,
2273 label,
2274 SIPE_BUDDY_MENU_MAKE_CHAT_LEADER,
2275 chat_session);
2276 g_free(label);
2279 if (is_conf &&
2280 /* We are a conf OP */
2281 conf_op) {
2282 gchar *label = g_strdup_printf(_("Remove from '%s'"),
2283 chat_session->title);
2284 menu = sipe_backend_buddy_menu_add(sipe_public,
2285 menu,
2286 label,
2287 SIPE_BUDDY_MENU_REMOVE_FROM_CHAT,
2288 chat_session);
2289 g_free(label);
2292 else
2294 if (!is_conf ||
2295 (is_conf && !session->locked)) {
2296 gchar *label = g_strdup_printf(_("Invite to '%s'"),
2297 chat_session->title);
2298 menu = sipe_backend_buddy_menu_add(sipe_public,
2299 menu,
2300 label,
2301 SIPE_BUDDY_MENU_INVITE_TO_CHAT,
2302 chat_session);
2303 g_free(label);
2307 } SIPE_SESSION_FOREACH_END;
2308 g_free(self);
2310 menu = sipe_backend_buddy_menu_add(sipe_public,
2311 menu,
2312 _("New chat"),
2313 SIPE_BUDDY_MENU_NEW_CHAT,
2314 NULL);
2316 /* add buddy's phone numbers if we have call control */
2317 if (sip_csta_is_idle(sipe_private)) {
2319 /* work phone */
2320 menu = buddy_menu_phone(sipe_public,
2321 menu,
2322 buddy,
2323 SIPE_BUDDY_INFO_WORK_PHONE,
2324 SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY,
2325 _("Work"));
2326 /* mobile phone */
2327 menu = buddy_menu_phone(sipe_public,
2328 menu,
2329 buddy,
2330 SIPE_BUDDY_INFO_MOBILE_PHONE,
2331 SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY,
2332 _("Mobile"));
2334 /* home phone */
2335 menu = buddy_menu_phone(sipe_public,
2336 menu,
2337 buddy,
2338 SIPE_BUDDY_INFO_HOME_PHONE,
2339 SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY,
2340 _("Home"));
2342 /* other phone */
2343 menu = buddy_menu_phone(sipe_public,
2344 menu,
2345 buddy,
2346 SIPE_BUDDY_INFO_OTHER_PHONE,
2347 SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY,
2348 _("Other"));
2350 /* custom1 phone */
2351 menu = buddy_menu_phone(sipe_public,
2352 menu,
2353 buddy,
2354 SIPE_BUDDY_INFO_CUSTOM1_PHONE,
2355 SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY,
2356 _("Custom1"));
2360 gchar *email = sipe_backend_buddy_get_string(sipe_public,
2361 buddy,
2362 SIPE_BUDDY_INFO_EMAIL);
2363 if (email) {
2364 menu = sipe_backend_buddy_menu_add(sipe_public,
2365 menu,
2366 _("Send email..."),
2367 SIPE_BUDDY_MENU_SEND_EMAIL,
2368 NULL);
2369 g_free(email);
2373 /* access level control */
2374 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007))
2375 menu = sipe_backend_buddy_sub_menu_add(sipe_public,
2376 menu,
2377 _("Access level"),
2378 sipe_ocs2007_access_control_menu(sipe_private,
2379 buddy_name));
2381 return(menu);
2384 guint sipe_buddy_count(struct sipe_core_private *sipe_private)
2386 return(g_hash_table_size(sipe_private->buddies->uri));
2389 static guint sipe_ht_hash_nick(const char *nick)
2391 char *lc = g_utf8_strdown(nick, -1);
2392 guint bucket = g_str_hash(lc);
2393 g_free(lc);
2395 return bucket;
2398 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
2400 char *nick1_norm = NULL;
2401 char *nick2_norm = NULL;
2402 gboolean equal;
2404 if (nick1 == NULL && nick2 == NULL) return TRUE;
2405 if (nick1 == NULL || nick2 == NULL ||
2406 !g_utf8_validate(nick1, -1, NULL) ||
2407 !g_utf8_validate(nick2, -1, NULL)) return FALSE;
2409 nick1_norm = g_utf8_casefold(nick1, -1);
2410 nick2_norm = g_utf8_casefold(nick2, -1);
2411 equal = g_utf8_collate(nick1_norm, nick2_norm) == 0;
2412 g_free(nick2_norm);
2413 g_free(nick1_norm);
2415 return equal;
2418 void sipe_buddy_init(struct sipe_core_private *sipe_private)
2420 struct sipe_buddies *buddies = g_new0(struct sipe_buddies, 1);
2421 buddies->uri = g_hash_table_new((GHashFunc) sipe_ht_hash_nick,
2422 (GEqualFunc) sipe_ht_equals_nick);
2423 buddies->exchange_key = g_hash_table_new(g_str_hash,
2424 g_str_equal);
2425 sipe_private->buddies = buddies;
2429 Local Variables:
2430 mode: c
2431 c-file-style: "bsd"
2432 indent-tabs-mode: t
2433 tab-width: 8
2434 End: