6 * Copyright (C) 2010-2013 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #include "sipe-common.h"
37 #include "sip-transport.h"
38 #include "sipe-backend.h"
39 #include "sipe-buddy.h"
41 #include "sipe-chat.h"
42 #include "sipe-conf.h"
43 #include "sipe-core.h"
44 #include "sipe-core-private.h"
45 #include "sipe-group.h"
46 #include "sipe-http.h"
49 #include "sipe-ocs2005.h"
50 #include "sipe-ocs2007.h"
51 #include "sipe-schedule.h"
52 #include "sipe-session.h"
53 #include "sipe-status.h"
54 #include "sipe-subscriptions.h"
57 #include "sipe-utils.h"
58 #include "sipe-webticket.h"
63 GHashTable
*exchange_key
;
65 /* Pending photo download HTTP requests */
66 GSList
*pending_photo_requests
;
69 struct buddy_group_data
{
70 const struct sipe_group
*group
;
74 struct photo_response_data
{
77 struct sipe_http_request
*request
;
80 static void buddy_fetch_photo(struct sipe_core_private
*sipe_private
,
82 static void photo_response_data_free(struct photo_response_data
*data
);
84 struct sipe_buddy
*sipe_buddy_add(struct sipe_core_private
*sipe_private
,
86 const gchar
*exchange_key
,
87 const gchar
*change_key
)
89 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
90 gchar
*normalized_uri
= g_ascii_strdown(uri
, -1);
91 struct sipe_buddy
*buddy
= sipe_buddy_find_by_uri(sipe_private
,
95 struct sipe_buddies
*buddies
= sipe_private
->buddies
;
97 buddy
= g_new0(struct sipe_buddy
, 1);
98 buddy
->name
= normalized_uri
;
99 g_hash_table_insert(buddies
->uri
,
104 buddy
->exchange_key
= g_strdup(exchange_key
);
105 g_hash_table_insert(buddies
->exchange_key
,
110 buddy
->change_key
= g_strdup(change_key
);
113 SIPE_DEBUG_INFO("sipe_buddy_add: Added buddy %s", normalized_uri
);
115 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES
)) {
116 buddy
->just_added
= TRUE
;
117 sipe_subscribe_presence_single_cb(sipe_private
,
121 buddy_fetch_photo(sipe_private
, normalized_uri
);
123 normalized_uri
= NULL
; /* buddy takes ownership */
125 SIPE_DEBUG_INFO("sipe_buddy_add: Buddy %s already exists", normalized_uri
);
126 buddy
->is_obsolete
= FALSE
;
128 g_free(normalized_uri
);
133 static gboolean
is_buddy_in_group(struct sipe_buddy
*buddy
,
137 GSList
*entry
= buddy
->groups
;
140 struct buddy_group_data
*bgd
= entry
->data
;
141 if (sipe_strequal(bgd
->group
->name
, name
)) {
142 bgd
->is_obsolete
= FALSE
;
152 void sipe_buddy_add_to_group(struct sipe_core_private
*sipe_private
,
153 struct sipe_buddy
*buddy
,
154 struct sipe_group
*group
,
157 const gchar
*uri
= buddy
->name
;
158 const gchar
*group_name
= group
->name
;
159 sipe_backend_buddy bb
= sipe_backend_buddy_find(SIPE_CORE_PUBLIC
,
164 bb
= sipe_backend_buddy_add(SIPE_CORE_PUBLIC
,
168 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: created backend buddy '%s' with alias '%s'",
169 uri
, alias
? alias
: "<NONE>");
173 if (!is_empty(alias
)) {
174 gchar
*old_alias
= sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC
,
177 if (sipe_strcase_equal(sipe_get_no_sip_uri(uri
),
179 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC
,
182 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: replaced alias for buddy '%s': old '%s' new '%s'",
183 uri
, old_alias
, alias
);
188 if (!is_buddy_in_group(buddy
, group_name
)) {
189 sipe_buddy_insert_group(buddy
, group
);
190 SIPE_DEBUG_INFO("sipe_buddy_add_to_group: added buddy %s to group %s",
195 static gint
buddy_group_compare(gconstpointer a
, gconstpointer b
)
197 return(((const struct buddy_group_data
*)a
)->group
->id
-
198 ((const struct buddy_group_data
*)b
)->group
->id
);
201 void sipe_buddy_insert_group(struct sipe_buddy
*buddy
,
202 struct sipe_group
*group
)
204 struct buddy_group_data
*bgd
= g_new0(struct buddy_group_data
, 1);
208 buddy
->groups
= sipe_utils_slist_insert_unique_sorted(buddy
->groups
,
214 static void buddy_group_free(gpointer data
)
219 static void buddy_group_remove(struct sipe_buddy
*buddy
,
220 struct buddy_group_data
*bgd
)
222 buddy
->groups
= g_slist_remove(buddy
->groups
, bgd
);
223 buddy_group_free(bgd
);
226 static void sipe_buddy_remove_group(struct sipe_buddy
*buddy
,
227 const struct sipe_group
*group
)
229 GSList
*entry
= buddy
->groups
;
230 struct buddy_group_data
*bgd
= NULL
;
234 if (bgd
->group
== group
)
239 buddy_group_remove(buddy
, bgd
);
242 void sipe_buddy_update_groups(struct sipe_core_private
*sipe_private
,
243 struct sipe_buddy
*buddy
,
246 const gchar
*uri
= buddy
->name
;
247 GSList
*entry
= buddy
->groups
;
250 struct buddy_group_data
*bgd
= entry
->data
;
251 const struct sipe_group
*group
= bgd
->group
;
253 /* next buddy group */
256 /* old group NOT found in new list? */
257 if (g_slist_find(new_groups
, group
) == NULL
) {
258 sipe_backend_buddy oldb
= sipe_backend_buddy_find(SIPE_CORE_PUBLIC
,
261 SIPE_DEBUG_INFO("sipe_buddy_update_groups: removing buddy %s from group '%s'",
263 /* this should never be NULL */
265 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC
,
267 buddy_group_remove(buddy
, bgd
);
272 gchar
*sipe_buddy_groups_string(struct sipe_buddy
*buddy
)
276 /* creating array from GList, converting guint to gchar * */
277 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
278 GSList
*entry
= buddy
->groups
;
284 const struct sipe_group
*group
= ((struct buddy_group_data
*) entry
->data
)->group
;
285 ids_arr
[i
] = g_strdup_printf("%u", group
->id
);
291 string
= g_strjoinv(" ", ids_arr
);
297 void sipe_buddy_cleanup_local_list(struct sipe_core_private
*sipe_private
)
299 GSList
*buddies
= sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC
,
302 GSList
*entry
= buddies
;
304 SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: overall %d backend buddies (including clones)",
305 g_slist_length(buddies
));
306 SIPE_DEBUG_INFO("sipe_buddy_cleanup_local_list: %d sipe buddies (unique)",
307 sipe_buddy_count(sipe_private
));
309 sipe_backend_buddy bb
= entry
->data
;
310 gchar
*bname
= sipe_backend_buddy_get_name(SIPE_CORE_PUBLIC
,
312 gchar
*gname
= sipe_backend_buddy_get_group_name(SIPE_CORE_PUBLIC
,
314 struct sipe_buddy
*buddy
= sipe_buddy_find_by_uri(sipe_private
,
317 if (!is_buddy_in_group(buddy
, gname
)) {
318 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",
320 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC
, bb
);
329 g_slist_free(buddies
);
332 struct sipe_buddy
*sipe_buddy_find_by_uri(struct sipe_core_private
*sipe_private
,
335 return(g_hash_table_lookup(sipe_private
->buddies
->uri
, uri
));
338 struct sipe_buddy
*sipe_buddy_find_by_exchange_key(struct sipe_core_private
*sipe_private
,
339 const gchar
*exchange_key
)
341 return(g_hash_table_lookup(sipe_private
->buddies
->exchange_key
,
345 void sipe_buddy_foreach(struct sipe_core_private
*sipe_private
,
347 gpointer callback_data
)
349 g_hash_table_foreach(sipe_private
->buddies
->uri
,
354 static void buddy_free(struct sipe_buddy
*buddy
)
358 * We are calling g_hash_table_foreach_steal(). That means that no
359 * key/value deallocation functions are called. Therefore the glib
360 * hash code does not touch the key (buddy->name) or value (buddy)
361 * of the to-be-deleted hash node at all. It follows that we
363 * - MUST free the memory for the key ourselves and
364 * - ARE allowed to do it in this function
366 * Conclusion: glib must be broken on the Windows platform if sipe
367 * crashes with SIGTRAP when closing. You'll have to live
368 * with the memory leak until this is fixed.
372 g_free(buddy
->exchange_key
);
373 g_free(buddy
->change_key
);
374 g_free(buddy
->activity
);
375 g_free(buddy
->meeting_subject
);
376 g_free(buddy
->meeting_location
);
379 g_free(buddy
->cal_start_time
);
380 g_free(buddy
->cal_free_busy_base64
);
381 g_free(buddy
->cal_free_busy
);
382 g_free(buddy
->last_non_cal_activity
);
384 sipe_cal_free_working_hours(buddy
->cal_working_hours
);
386 g_free(buddy
->device_name
);
387 g_slist_free_full(buddy
->groups
, buddy_group_free
);
391 static gboolean
buddy_free_cb(SIPE_UNUSED_PARAMETER gpointer key
,
393 SIPE_UNUSED_PARAMETER gpointer user_data
)
396 /* We must return TRUE as the key/value have already been deleted */
400 void sipe_buddy_free(struct sipe_core_private
*sipe_private
)
402 struct sipe_buddies
*buddies
= sipe_private
->buddies
;
404 g_hash_table_foreach_steal(buddies
->uri
,
408 /* core is being deallocated, remove all its pending photo requests */
409 while (buddies
->pending_photo_requests
) {
410 struct photo_response_data
*data
=
411 buddies
->pending_photo_requests
->data
;
412 buddies
->pending_photo_requests
=
413 g_slist_remove(buddies
->pending_photo_requests
, data
);
414 photo_response_data_free(data
);
417 g_hash_table_destroy(buddies
->uri
);
418 g_hash_table_destroy(buddies
->exchange_key
);
420 sipe_private
->buddies
= NULL
;
423 static void buddy_set_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key
,
425 SIPE_UNUSED_PARAMETER gpointer user_data
)
427 struct sipe_buddy
*buddy
= value
;
428 GSList
*entry
= buddy
->groups
;
430 buddy
->is_obsolete
= TRUE
;
432 ((struct buddy_group_data
*) entry
->data
)->is_obsolete
= TRUE
;
437 void sipe_buddy_update_start(struct sipe_core_private
*sipe_private
)
439 g_hash_table_foreach(sipe_private
->buddies
->uri
,
440 buddy_set_obsolete_flag
,
444 static gboolean
buddy_check_obsolete_flag(SIPE_UNUSED_PARAMETER gpointer key
,
448 struct sipe_core_private
*sipe_private
= user_data
;
449 struct sipe_buddy
*buddy
= value
;
450 const gchar
*uri
= buddy
->name
;
452 if (buddy
->is_obsolete
) {
453 /* all backend buddies in different groups */
454 GSList
*buddies
= sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC
,
457 GSList
*entry
= buddies
;
459 SIPE_DEBUG_INFO("buddy_check_obsolete_flag: REMOVING %d backend buddies for '%s'",
460 g_slist_length(buddies
),
464 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC
,
468 g_slist_free(buddies
);
471 /* return TRUE as the key/value have already been deleted */
475 GSList
*entry
= buddy
->groups
;
478 struct buddy_group_data
*bgd
= entry
->data
;
480 /* next buddy group */
483 if (bgd
->is_obsolete
) {
484 const struct sipe_group
*group
= bgd
->group
;
485 sipe_backend_buddy oldb
= sipe_backend_buddy_find(SIPE_CORE_PUBLIC
,
488 SIPE_DEBUG_INFO("buddy_check_obsolete_flag: removing buddy '%s' from group '%s'",
490 /* this should never be NULL */
492 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC
,
494 buddy_group_remove(buddy
, bgd
);
501 void sipe_buddy_update_finish(struct sipe_core_private
*sipe_private
)
503 g_hash_table_foreach_remove(sipe_private
->buddies
->uri
,
504 buddy_check_obsolete_flag
,
508 gchar
*sipe_core_buddy_status(struct sipe_core_public
*sipe_public
,
511 const gchar
*status_text
)
513 struct sipe_buddy
*sbuddy
;
514 const char *activity_str
;
516 if (!sipe_public
) return NULL
; /* happens on pidgin exit */
518 sbuddy
= sipe_buddy_find_by_uri(SIPE_CORE_PRIVATE
, uri
);
519 if (!sbuddy
) return NULL
;
521 activity_str
= sbuddy
->activity
? sbuddy
->activity
:
522 (activity
== SIPE_ACTIVITY_BUSY
) || (activity
== SIPE_ACTIVITY_BRB
) ?
525 if (activity_str
&& sbuddy
->note
) {
526 return g_strdup_printf("%s - <i>%s</i>", activity_str
, sbuddy
->note
);
527 } else if (activity_str
) {
528 return g_strdup(activity_str
);
529 } else if (sbuddy
->note
) {
530 return g_strdup_printf("<i>%s</i>", sbuddy
->note
);
536 gchar
*sipe_buddy_get_alias(struct sipe_core_private
*sipe_private
,
539 sipe_backend_buddy pbuddy
;
541 if ((pbuddy
= sipe_backend_buddy_find(SIPE_CORE_PUBLIC
, with
, NULL
))) {
542 alias
= sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC
, pbuddy
);
547 void sipe_core_buddy_group(struct sipe_core_public
*sipe_public
,
549 const gchar
*old_group_name
,
550 const gchar
*new_group_name
)
552 struct sipe_buddy
*buddy
= sipe_buddy_find_by_uri(SIPE_CORE_PRIVATE
,
554 struct sipe_group
*old_group
= NULL
;
555 struct sipe_group
*new_group
;
557 SIPE_DEBUG_INFO("sipe_core_buddy_group: who:%s old_group_name:%s new_group_name:%s",
558 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
561 /* buddy not in roaming list */
564 if (old_group_name
) {
565 old_group
= sipe_group_find_by_name(SIPE_CORE_PRIVATE
, old_group_name
);
567 new_group
= sipe_group_find_by_name(SIPE_CORE_PRIVATE
, new_group_name
);
570 sipe_buddy_remove_group(buddy
, old_group
);
571 SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy %s removed from old group %s", who
, old_group_name
);
575 sipe_group_create(SIPE_CORE_PRIVATE
, new_group_name
, who
);
577 sipe_buddy_insert_group(buddy
, new_group
);
578 sipe_group_update_buddy(SIPE_CORE_PRIVATE
, buddy
);
582 void sipe_core_buddy_add(struct sipe_core_public
*sipe_public
,
584 const gchar
*group_name
)
586 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
588 if (!sipe_buddy_find_by_uri(sipe_private
, uri
))
589 sipe_buddy_add(sipe_private
,
594 SIPE_DEBUG_INFO("sipe_core_buddy_add: buddy %s already in internal list",
597 sipe_core_buddy_group(sipe_public
,
603 void sipe_buddy_remove(struct sipe_core_private
*sipe_private
,
604 struct sipe_buddy
*buddy
)
606 struct sipe_buddies
*buddies
= sipe_private
->buddies
;
607 const gchar
*uri
= buddy
->name
;
608 GSList
*entry
= buddy
->groups
;
609 gchar
*action_name
= sipe_utils_presence_key(uri
);
611 sipe_schedule_cancel(sipe_private
, action_name
);
614 /* If the buddy still has groups, we need to delete backend buddies */
616 const struct sipe_group
*group
= ((struct buddy_group_data
*) entry
->data
)->group
;
617 sipe_backend_buddy oldb
= sipe_backend_buddy_find(SIPE_CORE_PUBLIC
,
620 /* this should never be NULL */
622 sipe_backend_buddy_remove(SIPE_CORE_PUBLIC
, oldb
);
627 g_hash_table_remove(buddies
->uri
, uri
);
628 if (buddy
->exchange_key
)
629 g_hash_table_remove(buddies
->exchange_key
,
630 buddy
->exchange_key
);
636 * Unassociates buddy from group first.
637 * Then see if no groups left, removes buddy completely.
638 * Otherwise updates buddy groups on server.
640 void sipe_core_buddy_remove(struct sipe_core_public
*sipe_public
,
642 const gchar
*group_name
)
644 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
645 struct sipe_buddy
*buddy
= sipe_buddy_find_by_uri(sipe_private
,
647 struct sipe_group
*group
= NULL
;
652 group
= sipe_group_find_by_name(sipe_private
, group_name
);
654 sipe_buddy_remove_group(buddy
, group
);
655 SIPE_DEBUG_INFO("sipe_core_buddy_remove: buddy %s removed from group %s",
660 if (g_slist_length(buddy
->groups
) < 1) {
662 if (sipe_ucs_is_migrated(sipe_private
)) {
663 sipe_ucs_group_remove_buddy(sipe_private
,
667 gchar
*request
= g_strdup_printf("<m:URI>%s</m:URI>",
669 sip_soap_request(sipe_private
,
675 sipe_buddy_remove(sipe_private
, buddy
);
677 if (sipe_ucs_is_migrated(sipe_private
)) {
678 sipe_ucs_group_remove_buddy(sipe_private
,
682 /* updates groups on server */
683 sipe_group_update_buddy(sipe_private
, buddy
);
687 void sipe_core_buddy_got_status(struct sipe_core_public
*sipe_public
,
691 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
692 struct sipe_buddy
*sbuddy
= sipe_buddy_find_by_uri(sipe_private
,
697 /* Check if on 2005 system contact's calendar,
698 * then set/preserve it.
700 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
701 sipe_backend_buddy_set_status(sipe_public
, uri
, activity
);
703 sipe_ocs2005_apply_calendar_status(sipe_private
,
705 sipe_status_activity_to_token(activity
));
709 void sipe_core_buddy_tooltip_info(struct sipe_core_public
*sipe_public
,
711 const gchar
*status_name
,
713 struct sipe_backend_buddy_tooltip
*tooltip
)
715 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
717 gboolean is_oof_note
= FALSE
;
718 const gchar
*activity
= NULL
;
719 gchar
*calendar
= NULL
;
720 const gchar
*meeting_subject
= NULL
;
721 const gchar
*meeting_location
= NULL
;
722 gchar
*access_text
= NULL
;
724 #define SIPE_ADD_BUDDY_INFO(l, t) \
726 gchar *tmp = g_markup_escape_text((t), -1); \
727 sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), tmp); \
730 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) \
731 sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), (t))
733 if (sipe_public
) { /* happens on pidgin exit */
734 struct sipe_buddy
*sbuddy
= sipe_buddy_find_by_uri(sipe_private
,
738 is_oof_note
= sbuddy
->is_oof_note
;
739 activity
= sbuddy
->activity
;
740 calendar
= sipe_cal_get_description(sbuddy
);
741 meeting_subject
= sbuddy
->meeting_subject
;
742 meeting_location
= sbuddy
->meeting_location
;
744 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
745 gboolean is_group_access
= FALSE
;
746 const int container_id
= sipe_ocs2007_find_access_level(sipe_private
,
748 sipe_get_no_sip_uri(uri
),
750 const char *access_level
= sipe_ocs2007_access_level_name(container_id
);
751 access_text
= is_group_access
?
752 g_strdup(access_level
) :
753 g_strdup_printf(SIPE_OCS2007_INDENT_MARKED_FMT
,
759 const gchar
*status_str
= activity
? activity
: status_name
;
761 SIPE_ADD_BUDDY_INFO(_("Status"), status_str
);
763 if (is_online
&& !is_empty(calendar
)) {
764 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar
);
767 if (!is_empty(meeting_location
)) {
768 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", uri
, meeting_location
);
769 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location
);
771 if (!is_empty(meeting_subject
)) {
772 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", uri
, meeting_subject
);
773 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject
);
776 gchar
*note_italics
= g_strdup_printf("<i>%s</i>", note
);
777 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", uri
, note
);
778 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note
? _("Out of office note") : _("Note"),
780 g_free(note_italics
);
783 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text
);
788 void sipe_buddy_update_property(struct sipe_core_private
*sipe_private
,
790 sipe_buddy_info_fields propkey
,
791 char *property_value
)
793 GSList
*buddies
, *entry
;
796 property_value
= g_strstrip(property_value
);
798 entry
= buddies
= sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC
, uri
, NULL
); /* all buddies in different groups */
801 sipe_backend_buddy p_buddy
= entry
->data
;
803 /* for Display Name */
804 if (propkey
== SIPE_BUDDY_INFO_DISPLAY_NAME
) {
806 alias
= sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC
, p_buddy
);
807 if (property_value
&& sipe_is_bad_alias(uri
, alias
)) {
808 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri
, property_value
);
809 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC
, p_buddy
, property_value
);
813 alias
= sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC
, p_buddy
);
814 if (!is_empty(property_value
) &&
815 (!sipe_strequal(property_value
, alias
) || is_empty(alias
)) )
817 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri
, property_value
);
818 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC
, p_buddy
, property_value
);
822 /* for other properties */
824 if (!is_empty(property_value
)) {
825 prop_str
= sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC
, p_buddy
, propkey
);
826 if (!prop_str
|| !sipe_strcase_equal(prop_str
, property_value
)) {
827 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC
, p_buddy
, propkey
, property_value
);
835 g_slist_free(buddies
);
844 sipe_svc_callback
*callback
;
845 struct sipe_svc_session
*session
;
846 gchar
*wsse_security
;
847 struct sipe_backend_search_token
*token
;
848 /* must call ms_dlx_free() */
849 void (*failed_callback
)(struct sipe_core_private
*sipe_private
,
850 struct ms_dlx_data
*mdd
);
853 static void ms_dlx_free(struct ms_dlx_data
*mdd
)
855 sipe_utils_slist_free_full(mdd
->search_rows
, g_free
);
856 sipe_svc_session_close(mdd
->session
);
858 g_free(mdd
->wsse_security
);
862 #define SIPE_SOAP_SEARCH_ROW "<m:row m:attrib=\"%s\" m:value=\"%s\"/>"
863 #define DLX_SEARCH_ITEM \
864 "<AbEntryRequest.ChangeSearchQuery>" \
865 " <SearchOn>%s</SearchOn>" \
866 " <Value>%s</Value>" \
867 "</AbEntryRequest.ChangeSearchQuery>"
869 static gchar
* prepare_buddy_search_query(GSList
*query_rows
, gboolean use_dlx
) {
870 gchar
**attrs
= g_new(gchar
*, (g_slist_length(query_rows
) / 2) + 1);
878 attr
= query_rows
->data
;
879 query_rows
= g_slist_next(query_rows
);
880 value
= query_rows
->data
;
881 query_rows
= g_slist_next(query_rows
);
886 attrs
[i
++] = g_markup_printf_escaped(use_dlx
? DLX_SEARCH_ITEM
: SIPE_SOAP_SEARCH_ROW
,
892 query
= g_strjoinv(NULL
, attrs
);
893 SIPE_DEBUG_INFO("prepare_buddy_search_query: rows:\n%s",
902 static void ms_dlx_webticket(struct sipe_core_private
*sipe_private
,
903 const gchar
*base_uri
,
904 const gchar
*auth_uri
,
905 const gchar
*wsse_security
,
906 SIPE_UNUSED_PARAMETER
const gchar
*failure_msg
,
907 gpointer callback_data
)
909 struct ms_dlx_data
*mdd
= callback_data
;
912 gchar
*query
= prepare_buddy_search_query(mdd
->search_rows
, TRUE
);
914 SIPE_DEBUG_INFO("ms_dlx_webticket: got ticket for %s",
917 if (sipe_svc_ab_entry_request(sipe_private
,
922 g_slist_length(mdd
->search_rows
) / 2,
927 /* keep webticket security token for potential further use */
928 mdd
->wsse_security
= g_strdup(wsse_security
);
930 /* callback data passed down the line */
936 /* no ticket: this will show the minmum information */
937 SIPE_DEBUG_ERROR("ms_dlx_webticket: no web ticket for %s",
942 mdd
->failed_callback(sipe_private
, mdd
);
945 static void ms_dlx_webticket_request(struct sipe_core_private
*sipe_private
,
946 struct ms_dlx_data
*mdd
)
948 if (!sipe_webticket_request(sipe_private
,
950 sipe_private
->dlx_uri
,
951 "AddressBookWebTicketBearer",
954 SIPE_DEBUG_ERROR("ms_dlx_webticket_request: couldn't request webticket for %s",
955 sipe_private
->dlx_uri
);
956 mdd
->failed_callback(sipe_private
, mdd
);
960 static void search_contacts_finalize(struct sipe_core_private
*sipe_private
,
961 struct sipe_backend_search_results
*results
,
965 gchar
*secondary
= g_strdup_printf(
966 dngettext(PACKAGE_NAME
,
967 "Found %d contact%s:",
968 "Found %d contacts%s:", match_count
),
969 match_count
, more
? _(" (more matched your query)") : "");
971 sipe_backend_search_results_finalize(SIPE_CORE_PUBLIC
,
978 static void search_ab_entry_response(struct sipe_core_private
*sipe_private
,
980 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
982 gpointer callback_data
)
984 struct ms_dlx_data
*mdd
= callback_data
;
987 const sipe_xml
*node
;
988 struct sipe_backend_search_results
*results
;
991 SIPE_DEBUG_INFO("search_ab_entry_response: received valid SOAP message from service %s",
995 node
= sipe_xml_child(soap_body
, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry");
997 SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: no matches");
998 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
1000 _("No contacts found"));
1005 /* OK, we found something - show the results to the user */
1006 results
= sipe_backend_search_results_start(SIPE_CORE_PUBLIC
,
1009 SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: Unable to display the search results.");
1010 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
1012 _("Unable to display the search results"));
1017 /* SearchAbEntryResult can contain duplicates */
1018 found
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
1021 for (/* initialized above */ ; node
; node
= sipe_xml_twin(node
)) {
1022 const sipe_xml
*attrs
;
1023 gchar
*sip_uri
= NULL
;
1024 gchar
*displayname
= NULL
;
1025 gchar
*company
= NULL
;
1026 gchar
*country
= NULL
;
1027 gchar
*email
= NULL
;
1029 for (attrs
= sipe_xml_child(node
, "Attributes/Attribute");
1031 attrs
= sipe_xml_twin(attrs
)) {
1032 gchar
*name
= sipe_xml_data(sipe_xml_child(attrs
,
1034 gchar
*value
= sipe_xml_data(sipe_xml_child(attrs
,
1037 if (!is_empty(value
)) {
1038 if (sipe_strcase_equal(name
, "msrtcsip-primaryuseraddress")) {
1042 } else if (sipe_strcase_equal(name
, "displayname")) {
1043 g_free(displayname
);
1044 displayname
= value
;
1046 } else if (sipe_strcase_equal(name
, "mail")) {
1050 } else if (sipe_strcase_equal(name
, "company")) {
1054 } else if (sipe_strcase_equal(name
, "country")) {
1065 if (sip_uri
&& !g_hash_table_lookup(found
, sip_uri
)) {
1066 gchar
**uri_parts
= g_strsplit(sip_uri
, ":", 2);
1067 sipe_backend_search_results_add(SIPE_CORE_PUBLIC
,
1074 g_strfreev(uri_parts
);
1076 g_hash_table_insert(found
, sip_uri
, (gpointer
) TRUE
);
1083 g_free(displayname
);
1087 search_contacts_finalize(sipe_private
, results
,
1088 g_hash_table_size(found
),
1090 g_hash_table_destroy(found
);
1094 mdd
->failed_callback(sipe_private
, mdd
);
1098 static gboolean
process_search_contact_response(struct sipe_core_private
*sipe_private
,
1100 struct transaction
*trans
)
1102 struct sipe_backend_search_token
*token
= trans
->payload
->data
;
1103 struct sipe_backend_search_results
*results
;
1104 sipe_xml
*searchResults
;
1105 const sipe_xml
*mrow
;
1106 guint match_count
= 0;
1107 gboolean more
= FALSE
;
1109 /* valid response? */
1110 if (msg
->response
!= 200) {
1111 SIPE_DEBUG_ERROR("process_search_contact_response: request failed (%d)",
1113 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
1115 _("Contact search failed"));
1119 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg
->body
? msg
->body
: "");
1122 searchResults
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
1123 if (!searchResults
) {
1124 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
1125 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
1127 _("Contact search failed"));
1132 mrow
= sipe_xml_child(searchResults
, "Body/Array/row");
1134 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: no matches");
1135 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
1137 _("No contacts found"));
1139 sipe_xml_free(searchResults
);
1143 /* OK, we found something - show the results to the user */
1144 results
= sipe_backend_search_results_start(SIPE_CORE_PUBLIC
,
1145 trans
->payload
->data
);
1147 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: Unable to display the search results.");
1148 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
1150 _("Unable to display the search results"));
1152 sipe_xml_free(searchResults
);
1156 for (/* initialized above */ ; mrow
; mrow
= sipe_xml_twin(mrow
)) {
1157 gchar
**uri_parts
= g_strsplit(sipe_xml_attribute(mrow
, "uri"), ":", 2);
1158 sipe_backend_search_results_add(SIPE_CORE_PUBLIC
,
1161 sipe_xml_attribute(mrow
, "displayName"),
1162 sipe_xml_attribute(mrow
, "company"),
1163 sipe_xml_attribute(mrow
, "country"),
1164 sipe_xml_attribute(mrow
, "email"));
1165 g_strfreev(uri_parts
);
1169 if ((mrow
= sipe_xml_child(searchResults
, "Body/directorySearch/moreAvailable")) != NULL
) {
1170 char *data
= sipe_xml_data(mrow
);
1171 more
= (g_ascii_strcasecmp(data
, "true") == 0);
1175 search_contacts_finalize(sipe_private
, results
, match_count
, more
);
1176 sipe_xml_free(searchResults
);
1181 static void search_soap_request(struct sipe_core_private
*sipe_private
,
1182 struct sipe_backend_search_token
*token
,
1183 GSList
*search_rows
)
1185 gchar
*query
= prepare_buddy_search_query(search_rows
, FALSE
);
1186 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
1188 payload
->data
= token
;
1190 sip_soap_directory_search(sipe_private
,
1193 process_search_contact_response
,
1198 static void search_ab_entry_failed(struct sipe_core_private
*sipe_private
,
1199 struct ms_dlx_data
*mdd
)
1201 /* error using [MS-DLX] server, retry using Active Directory */
1202 search_soap_request(sipe_private
, mdd
->token
, mdd
->search_rows
);
1206 void sipe_core_buddy_search(struct sipe_core_public
*sipe_public
,
1207 struct sipe_backend_search_token
*token
,
1208 const gchar
*given_name
,
1209 const gchar
*surname
,
1211 const gchar
*company
,
1212 const gchar
*country
)
1214 GSList
*query_rows
= NULL
;
1216 #define ADD_QUERY_ROW(attr, val) \
1218 query_rows = g_slist_append(query_rows, g_strdup(attr)); \
1219 query_rows = g_slist_append(query_rows, g_strdup(val)); \
1222 ADD_QUERY_ROW("givenName", given_name
);
1223 ADD_QUERY_ROW("sn", surname
);
1224 ADD_QUERY_ROW("mail", email
);
1225 ADD_QUERY_ROW("company", company
);
1226 ADD_QUERY_ROW("c", country
);
1229 if (SIPE_CORE_PRIVATE
->dlx_uri
!= NULL
) {
1230 struct ms_dlx_data
*mdd
= g_new0(struct ms_dlx_data
, 1);
1232 mdd
->search_rows
= query_rows
;
1233 mdd
->max_returns
= 100;
1234 mdd
->callback
= search_ab_entry_response
;
1235 mdd
->failed_callback
= search_ab_entry_failed
;
1236 mdd
->session
= sipe_svc_session_start();
1239 ms_dlx_webticket_request(SIPE_CORE_PRIVATE
, mdd
);
1242 /* no [MS-DLX] server, use Active Directory search instead */
1243 search_soap_request(SIPE_CORE_PRIVATE
, token
, query_rows
);
1244 sipe_utils_slist_free_full(query_rows
, g_free
);
1247 sipe_backend_search_failed(sipe_public
,
1249 _("Invalid contact search query"));
1252 static void get_info_finalize(struct sipe_core_private
*sipe_private
,
1253 struct sipe_backend_buddy_info
*info
,
1255 const gchar
*server_alias
,
1258 sipe_backend_buddy bbuddy
;
1259 struct sipe_buddy
*sbuddy
;
1264 info
= sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC
);
1266 sipe_backend_buddy_info_break(SIPE_CORE_PUBLIC
, info
);
1271 bbuddy
= sipe_backend_buddy_find(SIPE_CORE_PUBLIC
, uri
, NULL
);
1273 if (is_empty(server_alias
)) {
1274 value
= sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC
,
1277 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1279 SIPE_BUDDY_INFO_DISPLAY_NAME
,
1283 value
= g_strdup(server_alias
);
1286 /* present alias if it differs from server alias */
1287 alias
= sipe_backend_buddy_get_local_alias(SIPE_CORE_PUBLIC
, bbuddy
);
1288 if (alias
&& !sipe_strequal(alias
, value
))
1290 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1292 SIPE_BUDDY_INFO_ALIAS
,
1298 if (is_empty(email
)) {
1299 value
= sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC
,
1301 SIPE_BUDDY_INFO_EMAIL
);
1303 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1305 SIPE_BUDDY_INFO_EMAIL
,
1311 value
= sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC
,
1313 SIPE_BUDDY_INFO_SITE
);
1315 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1317 SIPE_BUDDY_INFO_SITE
,
1322 sbuddy
= sipe_buddy_find_by_uri(sipe_private
, uri
);
1323 if (sbuddy
&& sbuddy
->device_name
) {
1324 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1326 SIPE_BUDDY_INFO_DEVICE
,
1327 sbuddy
->device_name
);
1330 sipe_backend_buddy_info_finalize(SIPE_CORE_PUBLIC
, info
, uri
);
1334 static void get_info_ab_entry_response(struct sipe_core_private
*sipe_private
,
1336 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
1337 sipe_xml
*soap_body
,
1338 gpointer callback_data
)
1340 struct ms_dlx_data
*mdd
= callback_data
;
1341 struct sipe_backend_buddy_info
*info
= NULL
;
1342 gchar
*server_alias
= NULL
;
1343 gchar
*email
= NULL
;
1346 const sipe_xml
*node
;
1348 SIPE_DEBUG_INFO("get_info_ab_entry_response: received valid SOAP message from service %s",
1351 info
= sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC
);
1353 for (node
= sipe_xml_child(soap_body
, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
1355 node
= sipe_xml_twin(node
)) {
1356 gchar
*name
= sipe_xml_data(sipe_xml_child(node
,
1358 gchar
*value
= sipe_xml_data(sipe_xml_child(node
,
1360 const sipe_xml
*values
= sipe_xml_child(node
,
1363 /* Single value entries */
1364 if (!is_empty(value
)) {
1366 if (sipe_strcase_equal(name
, "displayname")) {
1367 g_free(server_alias
);
1368 server_alias
= value
;
1370 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1372 SIPE_BUDDY_INFO_DISPLAY_NAME
,
1374 } else if (sipe_strcase_equal(name
, "mail")) {
1378 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1380 SIPE_BUDDY_INFO_EMAIL
,
1382 } else if (sipe_strcase_equal(name
, "title")) {
1383 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1385 SIPE_BUDDY_INFO_JOB_TITLE
,
1387 } else if (sipe_strcase_equal(name
, "company")) {
1388 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1390 SIPE_BUDDY_INFO_COMPANY
,
1392 } else if (sipe_strcase_equal(name
, "country")) {
1393 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1395 SIPE_BUDDY_INFO_COUNTRY
,
1399 } else if (values
) {
1400 gchar
*first
= sipe_xml_data(sipe_xml_child(values
,
1403 if (sipe_strcase_equal(name
, "telephonenumber")) {
1404 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1406 SIPE_BUDDY_INFO_WORK_PHONE
,
1418 /* this will show the minmum information */
1419 get_info_finalize(sipe_private
,
1426 g_free(server_alias
);
1430 static gboolean
process_get_info_response(struct sipe_core_private
*sipe_private
,
1432 struct transaction
*trans
)
1434 const gchar
*uri
= trans
->payload
->data
;
1435 struct sipe_backend_buddy_info
*info
= NULL
;
1436 gchar
*server_alias
= NULL
;
1437 gchar
*email
= NULL
;
1439 SIPE_DEBUG_INFO("Fetching %s's user info for %s",
1440 uri
, sipe_private
->username
);
1442 if (msg
->response
!= 200) {
1443 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg
->response
);
1445 sipe_xml
*searchResults
;
1446 const sipe_xml
*mrow
;
1448 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s",
1449 msg
->body
? msg
->body
: "");
1451 searchResults
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
1452 if (!searchResults
) {
1454 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
1456 } else if ((mrow
= sipe_xml_child(searchResults
, "Body/Array/row"))) {
1458 gchar
*phone_number
;
1460 info
= sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC
);
1462 server_alias
= g_strdup(sipe_xml_attribute(mrow
, "displayName"));
1463 email
= g_strdup(sipe_xml_attribute(mrow
, "email"));
1464 phone_number
= g_strdup(sipe_xml_attribute(mrow
, "phone"));
1467 * For 2007 system we will take this from ContactCard -
1468 * it has cleaner tel: URIs at least
1470 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
1471 char *tel_uri
= sip_to_tel_uri(phone_number
);
1472 /* trims its parameters, so call first */
1473 sipe_buddy_update_property(sipe_private
, uri
, SIPE_BUDDY_INFO_DISPLAY_NAME
, server_alias
);
1474 sipe_buddy_update_property(sipe_private
, uri
, SIPE_BUDDY_INFO_EMAIL
, email
);
1475 sipe_buddy_update_property(sipe_private
, uri
, SIPE_BUDDY_INFO_WORK_PHONE
, tel_uri
);
1476 sipe_buddy_update_property(sipe_private
, uri
, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY
, phone_number
);
1479 sipe_backend_buddy_refresh_properties(SIPE_CORE_PUBLIC
,
1483 if (!is_empty(server_alias
)) {
1484 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1486 SIPE_BUDDY_INFO_DISPLAY_NAME
,
1489 if ((value
= sipe_xml_attribute(mrow
, "title")) && strlen(value
) > 0) {
1490 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1492 SIPE_BUDDY_INFO_JOB_TITLE
,
1495 if ((value
= sipe_xml_attribute(mrow
, "office")) && strlen(value
) > 0) {
1496 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1498 SIPE_BUDDY_INFO_OFFICE
,
1501 if (!is_empty(phone_number
)) {
1502 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1504 SIPE_BUDDY_INFO_WORK_PHONE
,
1507 g_free(phone_number
);
1508 if ((value
= sipe_xml_attribute(mrow
, "company")) && strlen(value
) > 0) {
1509 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1511 SIPE_BUDDY_INFO_COMPANY
,
1514 if ((value
= sipe_xml_attribute(mrow
, "city")) && strlen(value
) > 0) {
1515 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1517 SIPE_BUDDY_INFO_CITY
,
1520 if ((value
= sipe_xml_attribute(mrow
, "state")) && strlen(value
) > 0) {
1521 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1523 SIPE_BUDDY_INFO_STATE
,
1526 if ((value
= sipe_xml_attribute(mrow
, "country")) && strlen(value
) > 0) {
1527 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1529 SIPE_BUDDY_INFO_COUNTRY
,
1532 if (!is_empty(email
)) {
1533 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1535 SIPE_BUDDY_INFO_EMAIL
,
1539 sipe_xml_free(searchResults
);
1542 /* this will show the minmum information */
1543 get_info_finalize(sipe_private
,
1549 g_free(server_alias
);
1555 static void get_info_ab_entry_failed(struct sipe_core_private
*sipe_private
,
1556 struct ms_dlx_data
*mdd
)
1558 /* error using [MS-DLX] server, retry using Active Directory */
1559 gchar
*query
= prepare_buddy_search_query(mdd
->search_rows
, FALSE
);
1560 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
1562 payload
->destroy
= g_free
;
1563 payload
->data
= mdd
->other
;
1566 sip_soap_directory_search(sipe_private
,
1569 process_get_info_response
,
1576 void sipe_core_buddy_get_info(struct sipe_core_public
*sipe_public
,
1579 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1581 if (sipe_private
->dlx_uri
) {
1582 struct ms_dlx_data
*mdd
= g_new0(struct ms_dlx_data
, 1);
1584 mdd
->search_rows
= g_slist_append(mdd
->search_rows
, g_strdup("msRTCSIP-PrimaryUserAddress"));
1585 mdd
->search_rows
= g_slist_append(mdd
->search_rows
, g_strdup(who
));
1587 mdd
->other
= g_strdup(who
);
1588 mdd
->max_returns
= 1;
1589 mdd
->callback
= get_info_ab_entry_response
;
1590 mdd
->failed_callback
= get_info_ab_entry_failed
;
1591 mdd
->session
= sipe_svc_session_start();
1593 ms_dlx_webticket_request(sipe_private
, mdd
);
1596 /* no [MS-DLX] server, use Active Directory search instead */
1597 gchar
*row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
,
1598 "msRTCSIP-PrimaryUserAddress",
1600 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
1602 SIPE_DEBUG_INFO("sipe_core_buddy_get_info: row: %s",
1605 payload
->destroy
= g_free
;
1606 payload
->data
= g_strdup(who
);
1608 sip_soap_directory_search(sipe_private
,
1611 process_get_info_response
,
1617 static void photo_response_data_free(struct photo_response_data
*data
)
1620 g_free(data
->photo_hash
);
1621 if (data
->request
) {
1622 sipe_http_request_cancel(data
->request
);
1627 static void process_buddy_photo_response(struct sipe_core_private
*sipe_private
,
1633 struct photo_response_data
*rdata
= (struct photo_response_data
*) data
;
1635 rdata
->request
= NULL
;
1637 if (status
== SIPE_HTTP_STATUS_OK
) {
1638 const gchar
*len_str
= sipe_utils_nameval_find(headers
,
1641 gsize photo_size
= atoi(len_str
);
1642 gpointer photo
= g_new(char, photo_size
);
1645 memcpy(photo
, body
, photo_size
);
1647 sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC
,
1656 sipe_private
->buddies
->pending_photo_requests
=
1657 g_slist_remove(sipe_private
->buddies
->pending_photo_requests
, rdata
);
1659 photo_response_data_free(rdata
);
1662 static gchar
*create_x_ms_webticket_header(const gchar
*wsse_security
)
1664 gchar
*assertion
= sipe_xml_extract_raw(wsse_security
, "saml:Assertion", TRUE
);
1665 gchar
*wsse_security_base64
;
1666 gchar
*x_ms_webticket_header
;
1672 wsse_security_base64
= g_base64_encode((const guchar
*)assertion
,
1674 x_ms_webticket_header
= g_strdup_printf("X-MS-WebTicket: opaque=%s\r\n",
1675 wsse_security_base64
);
1678 g_free(wsse_security_base64
);
1680 return x_ms_webticket_header
;
1683 static void get_photo_ab_entry_response(struct sipe_core_private
*sipe_private
,
1685 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
1686 sipe_xml
*soap_body
,
1687 gpointer callback_data
)
1689 struct ms_dlx_data
*mdd
= callback_data
;
1690 gchar
*photo_rel_path
= NULL
;
1691 gchar
*photo_hash
= NULL
;
1692 const gchar
*photo_hash_old
=
1693 sipe_backend_buddy_get_photo_hash(SIPE_CORE_PUBLIC
, mdd
->other
);
1696 const sipe_xml
*node
;
1698 SIPE_DEBUG_INFO("get_photo_ab_entry_response: received valid SOAP message from service %s",
1701 for (node
= sipe_xml_child(soap_body
, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
1703 node
= sipe_xml_twin(node
)) {
1704 gchar
*name
= sipe_xml_data(sipe_xml_child(node
, "Name"));
1705 gchar
*value
= sipe_xml_data(sipe_xml_child(node
, "Value"));
1707 if (!is_empty(value
)) {
1708 if (sipe_strcase_equal(name
, "PhotoRelPath")) {
1709 g_free(photo_rel_path
);
1710 photo_rel_path
= value
;
1712 } else if (sipe_strcase_equal(name
, "PhotoHash")) {
1724 if (sipe_private
->addressbook_uri
&& photo_rel_path
&&
1725 photo_hash
&& !sipe_strequal(photo_hash
, photo_hash_old
)) {
1726 gchar
*photo_url
= g_strdup_printf("%s/%s",
1727 sipe_private
->addressbook_uri
, photo_rel_path
);
1728 gchar
*x_ms_webticket_header
= create_x_ms_webticket_header(mdd
->wsse_security
);
1730 struct photo_response_data
*data
= g_new(struct photo_response_data
, 1);
1731 data
->who
= g_strdup(mdd
->other
);
1732 data
->photo_hash
= photo_hash
;
1735 data
->request
= sipe_http_request_get(sipe_private
,
1737 x_ms_webticket_header
,
1738 process_buddy_photo_response
,
1741 if (data
->request
) {
1742 sipe_private
->buddies
->pending_photo_requests
=
1743 g_slist_append(sipe_private
->buddies
->pending_photo_requests
, data
);
1744 sipe_http_request_ready(data
->request
);
1746 photo_response_data_free(data
);
1749 g_free(x_ms_webticket_header
);
1753 g_free(photo_rel_path
);
1758 static void get_photo_ab_entry_failed(SIPE_UNUSED_PARAMETER
struct sipe_core_private
*sipe_private
,
1759 struct ms_dlx_data
*mdd
)
1764 static void buddy_fetch_photo(struct sipe_core_private
*sipe_private
,
1767 if (sipe_backend_uses_photo()) {
1769 /* Lync 2013 or newer: use UCS */
1770 if (SIPE_CORE_PRIVATE_FLAG_IS(LYNC2013
)) {
1772 sipe_ucs_get_photo(sipe_private
, uri
);
1774 /* Lync 2010: use [MS-DLX] */
1775 } else if (sipe_private
->dlx_uri
&&
1776 sipe_private
->addressbook_uri
) {
1777 struct ms_dlx_data
*mdd
= g_new0(struct ms_dlx_data
, 1);
1779 mdd
->search_rows
= g_slist_append(mdd
->search_rows
, g_strdup("msRTCSIP-PrimaryUserAddress"));
1780 mdd
->search_rows
= g_slist_append(mdd
->search_rows
, g_strdup(uri
));
1782 mdd
->other
= g_strdup(uri
);
1783 mdd
->max_returns
= 1;
1784 mdd
->callback
= get_photo_ab_entry_response
;
1785 mdd
->failed_callback
= get_photo_ab_entry_failed
;
1786 mdd
->session
= sipe_svc_session_start();
1788 ms_dlx_webticket_request(sipe_private
, mdd
);
1793 static void buddy_refresh_photos_cb(gpointer uri
,
1794 SIPE_UNUSED_PARAMETER gpointer value
,
1795 gpointer sipe_private
)
1797 buddy_fetch_photo(sipe_private
, uri
);
1800 void sipe_buddy_refresh_photos(struct sipe_core_private
*sipe_private
)
1802 g_hash_table_foreach(sipe_private
->buddies
->uri
,
1803 buddy_refresh_photos_cb
,
1807 /* Buddy menu callbacks*/
1809 void sipe_core_buddy_new_chat(struct sipe_core_public
*sipe_public
,
1812 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1814 /* 2007+ conference */
1815 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
1816 sipe_conf_add(sipe_private
, who
);
1818 /* 2005- multiparty chat */
1820 gchar
*self
= sip_uri_self(sipe_private
);
1821 struct sip_session
*session
;
1823 session
= sipe_session_add_chat(sipe_private
,
1827 session
->chat_session
->backend
= sipe_backend_chat_create(SIPE_CORE_PUBLIC
,
1828 session
->chat_session
,
1829 session
->chat_session
->title
,
1833 sipe_im_invite(sipe_private
, session
, who
,
1834 NULL
, NULL
, NULL
, FALSE
);
1838 void sipe_core_buddy_send_email(struct sipe_core_public
*sipe_public
,
1841 sipe_backend_buddy buddy
= sipe_backend_buddy_find(sipe_public
,
1844 gchar
*email
= sipe_backend_buddy_get_string(sipe_public
,
1846 SIPE_BUDDY_INFO_EMAIL
);
1849 gchar
*command_line
= g_strdup_printf(
1855 " mailto:%s", email
);
1858 SIPE_DEBUG_INFO("sipe_core_buddy_send_email: going to call email client: %s",
1860 g_spawn_command_line_async(command_line
, NULL
);
1861 g_free(command_line
);
1864 SIPE_DEBUG_INFO("sipe_core_buddy_send_email: no email address stored for buddy=%s",
1871 static struct sipe_backend_buddy_menu
*buddy_menu_phone(struct sipe_core_public
*sipe_public
,
1872 struct sipe_backend_buddy_menu
*menu
,
1873 sipe_backend_buddy buddy
,
1874 sipe_buddy_info_fields id_phone
,
1875 sipe_buddy_info_fields id_display
,
1878 gchar
*phone
= sipe_backend_buddy_get_string(sipe_public
,
1882 gchar
*display
= sipe_backend_buddy_get_string(sipe_public
,
1886 gchar
*label
= g_strdup_printf("%s %s",
1889 (tmp
= sip_tel_uri_denormalize(phone
)));
1890 menu
= sipe_backend_buddy_menu_add(sipe_public
,
1893 SIPE_BUDDY_MENU_MAKE_CALL
,
1904 struct sipe_backend_buddy_menu
*sipe_core_buddy_create_menu(struct sipe_core_public
*sipe_public
,
1905 const gchar
*buddy_name
,
1906 struct sipe_backend_buddy_menu
*menu
)
1908 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1909 sipe_backend_buddy buddy
= sipe_backend_buddy_find(sipe_public
,
1912 gchar
*self
= sip_uri_self(sipe_private
);
1914 SIPE_SESSION_FOREACH
{
1915 if (!sipe_strcase_equal(self
, buddy_name
) && session
->chat_session
)
1917 struct sipe_chat_session
*chat_session
= session
->chat_session
;
1918 gboolean is_conf
= (chat_session
->type
== SIPE_CHAT_TYPE_CONFERENCE
);
1920 if (sipe_backend_chat_find(chat_session
->backend
, buddy_name
))
1922 gboolean conf_op
= sipe_backend_chat_is_operator(chat_session
->backend
, self
);
1926 !sipe_backend_chat_is_operator(chat_session
->backend
, buddy_name
) &&
1927 /* We are a conf OP */
1929 gchar
*label
= g_strdup_printf(_("Make leader of '%s'"),
1930 chat_session
->title
);
1931 menu
= sipe_backend_buddy_menu_add(sipe_public
,
1934 SIPE_BUDDY_MENU_MAKE_CHAT_LEADER
,
1940 /* We are a conf OP */
1942 gchar
*label
= g_strdup_printf(_("Remove from '%s'"),
1943 chat_session
->title
);
1944 menu
= sipe_backend_buddy_menu_add(sipe_public
,
1947 SIPE_BUDDY_MENU_REMOVE_FROM_CHAT
,
1955 (is_conf
&& !session
->locked
)) {
1956 gchar
*label
= g_strdup_printf(_("Invite to '%s'"),
1957 chat_session
->title
);
1958 menu
= sipe_backend_buddy_menu_add(sipe_public
,
1961 SIPE_BUDDY_MENU_INVITE_TO_CHAT
,
1967 } SIPE_SESSION_FOREACH_END
;
1970 menu
= sipe_backend_buddy_menu_add(sipe_public
,
1973 SIPE_BUDDY_MENU_NEW_CHAT
,
1976 /* add buddy's phone numbers if we have call control */
1977 if (sip_csta_is_idle(sipe_private
)) {
1980 menu
= buddy_menu_phone(sipe_public
,
1983 SIPE_BUDDY_INFO_WORK_PHONE
,
1984 SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY
,
1987 menu
= buddy_menu_phone(sipe_public
,
1990 SIPE_BUDDY_INFO_MOBILE_PHONE
,
1991 SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY
,
1995 menu
= buddy_menu_phone(sipe_public
,
1998 SIPE_BUDDY_INFO_HOME_PHONE
,
1999 SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY
,
2003 menu
= buddy_menu_phone(sipe_public
,
2006 SIPE_BUDDY_INFO_OTHER_PHONE
,
2007 SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY
,
2011 menu
= buddy_menu_phone(sipe_public
,
2014 SIPE_BUDDY_INFO_CUSTOM1_PHONE
,
2015 SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY
,
2020 gchar
*email
= sipe_backend_buddy_get_string(sipe_public
,
2022 SIPE_BUDDY_INFO_EMAIL
);
2024 menu
= sipe_backend_buddy_menu_add(sipe_public
,
2027 SIPE_BUDDY_MENU_SEND_EMAIL
,
2033 /* access level control */
2034 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
))
2035 menu
= sipe_backend_buddy_sub_menu_add(sipe_public
,
2038 sipe_ocs2007_access_control_menu(sipe_private
,
2044 guint
sipe_buddy_count(struct sipe_core_private
*sipe_private
)
2046 return(g_hash_table_size(sipe_private
->buddies
->uri
));
2049 static guint
sipe_ht_hash_nick(const char *nick
)
2051 char *lc
= g_utf8_strdown(nick
, -1);
2052 guint bucket
= g_str_hash(lc
);
2058 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
2060 char *nick1_norm
= NULL
;
2061 char *nick2_norm
= NULL
;
2064 if (nick1
== NULL
&& nick2
== NULL
) return TRUE
;
2065 if (nick1
== NULL
|| nick2
== NULL
||
2066 !g_utf8_validate(nick1
, -1, NULL
) ||
2067 !g_utf8_validate(nick2
, -1, NULL
)) return FALSE
;
2069 nick1_norm
= g_utf8_casefold(nick1
, -1);
2070 nick2_norm
= g_utf8_casefold(nick2
, -1);
2071 equal
= g_utf8_collate(nick1_norm
, nick2_norm
) == 0;
2078 void sipe_buddy_init(struct sipe_core_private
*sipe_private
)
2080 struct sipe_buddies
*buddies
= g_new0(struct sipe_buddies
, 1);
2081 buddies
->uri
= g_hash_table_new((GHashFunc
) sipe_ht_hash_nick
,
2082 (GEqualFunc
) sipe_ht_equals_nick
);
2083 buddies
->exchange_key
= g_hash_table_new(g_str_hash
,
2085 sipe_private
->buddies
= buddies
;