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"
61 struct photo_response_data
{
64 struct sipe_http_request
*request
;
67 static void buddy_fetch_photo(struct sipe_core_private
*sipe_private
,
69 static void photo_response_data_free(struct photo_response_data
*data
);
71 struct sipe_buddy
*sipe_buddy_add(struct sipe_core_private
*sipe_private
,
73 const gchar
*exchange_key
)
75 struct sipe_buddy
*buddy
= sipe_buddy_find_by_uri(sipe_private
, uri
);
77 buddy
= g_new0(struct sipe_buddy
, 1);
78 buddy
->name
= g_strdup(uri
);
79 buddy
->exchange_key
= g_strdup(exchange_key
);
80 g_hash_table_insert(sipe_private
->buddies
, buddy
->name
, buddy
);
82 SIPE_DEBUG_INFO("sipe_buddy_add: Added buddy %s", uri
);
84 buddy_fetch_photo(sipe_private
, uri
);
86 SIPE_DEBUG_INFO("sipe_buddy_add: Buddy %s already exists", uri
);
92 struct sipe_buddy
*sipe_buddy_find_by_uri(struct sipe_core_private
*sipe_private
,
95 return(g_hash_table_lookup(sipe_private
->buddies
, uri
));
98 void sipe_buddy_foreach(struct sipe_core_private
*sipe_private
,
100 gpointer callback_data
)
102 g_hash_table_foreach(sipe_private
->buddies
, callback
, callback_data
);
105 static void buddy_free(struct sipe_buddy
*buddy
)
109 * We are calling g_hash_table_foreach_steal(). That means that no
110 * key/value deallocation functions are called. Therefore the glib
111 * hash code does not touch the key (buddy->name) or value (buddy)
112 * of the to-be-deleted hash node at all. It follows that we
114 * - MUST free the memory for the key ourselves and
115 * - ARE allowed to do it in this function
117 * Conclusion: glib must be broken on the Windows platform if sipe
118 * crashes with SIGTRAP when closing. You'll have to live
119 * with the memory leak until this is fixed.
123 g_free(buddy
->exchange_key
);
124 g_free(buddy
->activity
);
125 g_free(buddy
->meeting_subject
);
126 g_free(buddy
->meeting_location
);
129 g_free(buddy
->cal_start_time
);
130 g_free(buddy
->cal_free_busy_base64
);
131 g_free(buddy
->cal_free_busy
);
132 g_free(buddy
->last_non_cal_activity
);
134 sipe_cal_free_working_hours(buddy
->cal_working_hours
);
136 g_free(buddy
->device_name
);
137 g_slist_free(buddy
->groups
);
141 static gboolean
buddy_free_cb(SIPE_UNUSED_PARAMETER gpointer key
,
143 SIPE_UNUSED_PARAMETER gpointer user_data
)
146 /* We must return TRUE as the key/value have already been deleted */
150 void sipe_buddy_free(struct sipe_core_private
*sipe_private
)
152 g_hash_table_foreach_steal(sipe_private
->buddies
,
156 /* core is being deallocated, remove all its pending photo requests */
157 while (sipe_private
->pending_photo_requests
) {
158 struct photo_response_data
*data
=
159 sipe_private
->pending_photo_requests
->data
;
160 sipe_private
->pending_photo_requests
=
161 g_slist_remove(sipe_private
->pending_photo_requests
, data
);
162 photo_response_data_free(data
);
165 g_hash_table_destroy(sipe_private
->buddies
);
166 sipe_private
->buddies
= NULL
;
169 gchar
*sipe_core_buddy_status(struct sipe_core_public
*sipe_public
,
172 const gchar
*status_text
)
174 struct sipe_buddy
*sbuddy
;
175 const char *activity_str
;
177 if (!sipe_public
) return NULL
; /* happens on pidgin exit */
179 sbuddy
= g_hash_table_lookup(SIPE_CORE_PRIVATE
->buddies
, uri
);
180 if (!sbuddy
) return NULL
;
182 activity_str
= sbuddy
->activity
? sbuddy
->activity
:
183 (activity
== SIPE_ACTIVITY_BUSY
) || (activity
== SIPE_ACTIVITY_BRB
) ?
186 if (activity_str
&& sbuddy
->note
) {
187 return g_strdup_printf("%s - <i>%s</i>", activity_str
, sbuddy
->note
);
188 } else if (activity_str
) {
189 return g_strdup(activity_str
);
190 } else if (sbuddy
->note
) {
191 return g_strdup_printf("<i>%s</i>", sbuddy
->note
);
197 gchar
*sipe_buddy_get_alias(struct sipe_core_private
*sipe_private
,
200 sipe_backend_buddy pbuddy
;
202 if ((pbuddy
= sipe_backend_buddy_find(SIPE_CORE_PUBLIC
, with
, NULL
))) {
203 alias
= sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC
, pbuddy
);
208 void sipe_core_buddy_group(struct sipe_core_public
*sipe_public
,
210 const gchar
*old_group_name
,
211 const gchar
*new_group_name
)
213 struct sipe_buddy
* buddy
= g_hash_table_lookup(SIPE_CORE_PRIVATE
->buddies
, who
);
214 struct sipe_group
* old_group
= NULL
;
215 struct sipe_group
* new_group
;
217 SIPE_DEBUG_INFO("sipe_core_buddy_group: who:%s old_group_name:%s new_group_name:%s",
218 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
220 if(!buddy
) { // buddy not in roaming list
224 if (old_group_name
) {
225 old_group
= sipe_group_find_by_name(SIPE_CORE_PRIVATE
, old_group_name
);
227 new_group
= sipe_group_find_by_name(SIPE_CORE_PRIVATE
, new_group_name
);
230 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
231 SIPE_DEBUG_INFO("sipe_core_buddy_group: buddy %s removed from old group %s", who
, old_group_name
);
235 sipe_group_create(SIPE_CORE_PRIVATE
, new_group_name
, who
);
237 buddy
->groups
= sipe_utils_slist_insert_unique_sorted(buddy
->groups
,
239 (GCompareFunc
)sipe_group_compare
,
241 sipe_group_update_buddy(SIPE_CORE_PRIVATE
, buddy
);
245 void sipe_core_buddy_add(struct sipe_core_public
*sipe_public
,
247 const gchar
*group_name
)
249 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
251 if (!sipe_buddy_find_by_uri(sipe_private
, uri
)) {
252 struct sipe_buddy
*b
= sipe_buddy_add(sipe_private
, uri
, NULL
);
253 b
->just_added
= TRUE
;
255 sipe_subscribe_presence_single_cb(sipe_private
, b
->name
);
258 SIPE_DEBUG_INFO("sipe_core_buddy_add: buddy %s already in internal list",
262 sipe_core_buddy_group(sipe_public
,
268 void sipe_buddy_remove(struct sipe_core_private
*sipe_private
,
269 struct sipe_buddy
*buddy
)
271 gchar
*action_name
= sipe_utils_presence_key(buddy
->name
);
272 sipe_schedule_cancel(sipe_private
, action_name
);
275 g_hash_table_remove(sipe_private
->buddies
, buddy
->name
);
281 * Unassociates buddy from group first.
282 * Then see if no groups left, removes buddy completely.
283 * Otherwise updates buddy groups on server.
285 void sipe_core_buddy_remove(struct sipe_core_public
*sipe_public
,
287 const gchar
*group_name
)
289 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
290 struct sipe_buddy
*b
= sipe_buddy_find_by_uri(sipe_private
,
296 struct sipe_group
*g
= sipe_group_find_by_name(sipe_private
,
299 b
->groups
= g_slist_remove(b
->groups
, g
);
300 SIPE_DEBUG_INFO("sipe_core_buddy_remove: buddy %s removed from group %s",
305 if (g_slist_length(b
->groups
) < 1) {
306 gchar
*request
= g_strdup_printf("<m:URI>%s</m:URI>",
308 sip_soap_request(sipe_private
,
312 sipe_buddy_remove(sipe_private
, b
);
314 /* updates groups on server */
315 sipe_group_update_buddy(sipe_private
, b
);
320 void sipe_core_buddy_got_status(struct sipe_core_public
*sipe_public
,
324 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
325 struct sipe_buddy
*sbuddy
= sipe_buddy_find_by_uri(sipe_private
,
330 /* Check if on 2005 system contact's calendar,
331 * then set/preserve it.
333 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
334 sipe_backend_buddy_set_status(sipe_public
, uri
, activity
);
336 sipe_ocs2005_apply_calendar_status(sipe_private
,
338 sipe_status_activity_to_token(activity
));
342 void sipe_core_buddy_tooltip_info(struct sipe_core_public
*sipe_public
,
344 const gchar
*status_name
,
346 struct sipe_backend_buddy_tooltip
*tooltip
)
348 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
350 gboolean is_oof_note
= FALSE
;
351 const gchar
*activity
= NULL
;
352 gchar
*calendar
= NULL
;
353 const gchar
*meeting_subject
= NULL
;
354 const gchar
*meeting_location
= NULL
;
355 gchar
*access_text
= NULL
;
357 #define SIPE_ADD_BUDDY_INFO(l, t) \
359 gchar *tmp = g_markup_escape_text((t), -1); \
360 sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), tmp); \
363 #define SIPE_ADD_BUDDY_INFO_NOESCAPE(l, t) \
364 sipe_backend_buddy_tooltip_add(sipe_public, tooltip, (l), (t))
366 if (sipe_public
) { /* happens on pidgin exit */
367 struct sipe_buddy
*sbuddy
= sipe_buddy_find_by_uri(sipe_private
,
371 is_oof_note
= sbuddy
->is_oof_note
;
372 activity
= sbuddy
->activity
;
373 calendar
= sipe_cal_get_description(sbuddy
);
374 meeting_subject
= sbuddy
->meeting_subject
;
375 meeting_location
= sbuddy
->meeting_location
;
377 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
378 gboolean is_group_access
= FALSE
;
379 const int container_id
= sipe_ocs2007_find_access_level(sipe_private
,
381 sipe_get_no_sip_uri(uri
),
383 const char *access_level
= sipe_ocs2007_access_level_name(container_id
);
384 access_text
= is_group_access
?
385 g_strdup(access_level
) :
386 g_strdup_printf(SIPE_OCS2007_INDENT_MARKED_FMT
,
392 const gchar
*status_str
= activity
? activity
: status_name
;
394 SIPE_ADD_BUDDY_INFO(_("Status"), status_str
);
396 if (is_online
&& !is_empty(calendar
)) {
397 SIPE_ADD_BUDDY_INFO(_("Calendar"), calendar
);
400 if (!is_empty(meeting_location
)) {
401 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting location: '%s'", uri
, meeting_location
);
402 SIPE_ADD_BUDDY_INFO(_("Meeting in"), meeting_location
);
404 if (!is_empty(meeting_subject
)) {
405 SIPE_DEBUG_INFO("sipe_tooltip_text: %s meeting subject: '%s'", uri
, meeting_subject
);
406 SIPE_ADD_BUDDY_INFO(_("Meeting about"), meeting_subject
);
409 gchar
*note_italics
= g_strdup_printf("<i>%s</i>", note
);
410 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", uri
, note
);
411 SIPE_ADD_BUDDY_INFO_NOESCAPE(is_oof_note
? _("Out of office note") : _("Note"),
413 g_free(note_italics
);
416 SIPE_ADD_BUDDY_INFO(_("Access level"), access_text
);
421 void sipe_buddy_update_property(struct sipe_core_private
*sipe_private
,
423 sipe_buddy_info_fields propkey
,
424 char *property_value
)
426 GSList
*buddies
, *entry
;
429 property_value
= g_strstrip(property_value
);
431 entry
= buddies
= sipe_backend_buddy_find_all(SIPE_CORE_PUBLIC
, uri
, NULL
); /* all buddies in different groups */
434 sipe_backend_buddy p_buddy
= entry
->data
;
436 /* for Display Name */
437 if (propkey
== SIPE_BUDDY_INFO_DISPLAY_NAME
) {
439 alias
= sipe_backend_buddy_get_alias(SIPE_CORE_PUBLIC
, p_buddy
);
440 if (property_value
&& sipe_is_bad_alias(uri
, alias
)) {
441 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri
, property_value
);
442 sipe_backend_buddy_set_alias(SIPE_CORE_PUBLIC
, p_buddy
, property_value
);
446 alias
= sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC
, p_buddy
);
447 if (!is_empty(property_value
) &&
448 (!sipe_strequal(property_value
, alias
) || is_empty(alias
)) )
450 SIPE_DEBUG_INFO("Replacing service alias for %s with %s", uri
, property_value
);
451 sipe_backend_buddy_set_server_alias(SIPE_CORE_PUBLIC
, p_buddy
, property_value
);
455 /* for other properties */
457 if (!is_empty(property_value
)) {
458 prop_str
= sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC
, p_buddy
, propkey
);
459 if (!prop_str
|| !sipe_strcase_equal(prop_str
, property_value
)) {
460 sipe_backend_buddy_set_string(SIPE_CORE_PUBLIC
, p_buddy
, propkey
, property_value
);
468 g_slist_free(buddies
);
477 sipe_svc_callback
*callback
;
478 struct sipe_svc_session
*session
;
479 gchar
*wsse_security
;
480 struct sipe_backend_search_token
*token
;
481 /* must call ms_dlx_free() */
482 void (*failed_callback
)(struct sipe_core_private
*sipe_private
,
483 struct ms_dlx_data
*mdd
);
486 static void ms_dlx_free(struct ms_dlx_data
*mdd
)
488 sipe_utils_slist_free_full(mdd
->search_rows
, g_free
);
489 sipe_svc_session_close(mdd
->session
);
491 g_free(mdd
->wsse_security
);
495 #define SIPE_SOAP_SEARCH_ROW "<m:row m:attrib=\"%s\" m:value=\"%s\"/>"
496 #define DLX_SEARCH_ITEM \
497 "<AbEntryRequest.ChangeSearchQuery>" \
498 " <SearchOn>%s</SearchOn>" \
499 " <Value>%s</Value>" \
500 "</AbEntryRequest.ChangeSearchQuery>"
502 static gchar
* prepare_buddy_search_query(GSList
*query_rows
, gboolean use_dlx
) {
503 gchar
**attrs
= g_new(gchar
*, (g_slist_length(query_rows
) / 2) + 1);
511 attr
= query_rows
->data
;
512 query_rows
= g_slist_next(query_rows
);
513 value
= query_rows
->data
;
514 query_rows
= g_slist_next(query_rows
);
519 attrs
[i
++] = g_markup_printf_escaped(use_dlx
? DLX_SEARCH_ITEM
: SIPE_SOAP_SEARCH_ROW
,
525 query
= g_strjoinv(NULL
, attrs
);
526 SIPE_DEBUG_INFO("prepare_buddy_search_query: rows:\n%s",
535 static void ms_dlx_webticket(struct sipe_core_private
*sipe_private
,
536 const gchar
*base_uri
,
537 const gchar
*auth_uri
,
538 const gchar
*wsse_security
,
539 SIPE_UNUSED_PARAMETER
const gchar
*failure_msg
,
540 gpointer callback_data
)
542 struct ms_dlx_data
*mdd
= callback_data
;
545 gchar
*query
= prepare_buddy_search_query(mdd
->search_rows
, TRUE
);
547 SIPE_DEBUG_INFO("ms_dlx_webticket: got ticket for %s",
550 if (sipe_svc_ab_entry_request(sipe_private
,
555 g_slist_length(mdd
->search_rows
) / 2,
560 /* keep webticket security token for potential further use */
561 mdd
->wsse_security
= g_strdup(wsse_security
);
563 /* callback data passed down the line */
569 /* no ticket: this will show the minmum information */
570 SIPE_DEBUG_ERROR("ms_dlx_webticket: no web ticket for %s",
575 mdd
->failed_callback(sipe_private
, mdd
);
578 static void ms_dlx_webticket_request(struct sipe_core_private
*sipe_private
,
579 struct ms_dlx_data
*mdd
)
581 if (!sipe_webticket_request(sipe_private
,
583 sipe_private
->dlx_uri
,
584 "AddressBookWebTicketBearer",
587 SIPE_DEBUG_ERROR("ms_dlx_webticket_request: couldn't request webticket for %s",
588 sipe_private
->dlx_uri
);
589 mdd
->failed_callback(sipe_private
, mdd
);
593 static void search_contacts_finalize(struct sipe_core_private
*sipe_private
,
594 struct sipe_backend_search_results
*results
,
598 gchar
*secondary
= g_strdup_printf(
599 dngettext(PACKAGE_NAME
,
600 "Found %d contact%s:",
601 "Found %d contacts%s:", match_count
),
602 match_count
, more
? _(" (more matched your query)") : "");
604 sipe_backend_search_results_finalize(SIPE_CORE_PUBLIC
,
611 static void search_ab_entry_response(struct sipe_core_private
*sipe_private
,
613 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
615 gpointer callback_data
)
617 struct ms_dlx_data
*mdd
= callback_data
;
620 const sipe_xml
*node
;
621 struct sipe_backend_search_results
*results
;
624 SIPE_DEBUG_INFO("search_ab_entry_response: received valid SOAP message from service %s",
628 node
= sipe_xml_child(soap_body
, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry");
630 SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: no matches");
631 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
633 _("No contacts found"));
638 /* OK, we found something - show the results to the user */
639 results
= sipe_backend_search_results_start(SIPE_CORE_PUBLIC
,
642 SIPE_DEBUG_ERROR_NOFORMAT("search_ab_entry_response: Unable to display the search results.");
643 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
645 _("Unable to display the search results"));
650 /* SearchAbEntryResult can contain duplicates */
651 found
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
654 for (/* initialized above */ ; node
; node
= sipe_xml_twin(node
)) {
655 const sipe_xml
*attrs
;
656 gchar
*sip_uri
= NULL
;
657 gchar
*displayname
= NULL
;
658 gchar
*company
= NULL
;
659 gchar
*country
= NULL
;
662 for (attrs
= sipe_xml_child(node
, "Attributes/Attribute");
664 attrs
= sipe_xml_twin(attrs
)) {
665 gchar
*name
= sipe_xml_data(sipe_xml_child(attrs
,
667 gchar
*value
= sipe_xml_data(sipe_xml_child(attrs
,
670 if (!is_empty(value
)) {
671 if (sipe_strcase_equal(name
, "msrtcsip-primaryuseraddress")) {
675 } else if (sipe_strcase_equal(name
, "displayname")) {
679 } else if (sipe_strcase_equal(name
, "mail")) {
683 } else if (sipe_strcase_equal(name
, "company")) {
687 } else if (sipe_strcase_equal(name
, "country")) {
698 if (sip_uri
&& !g_hash_table_lookup(found
, sip_uri
)) {
699 gchar
**uri_parts
= g_strsplit(sip_uri
, ":", 2);
700 sipe_backend_search_results_add(SIPE_CORE_PUBLIC
,
707 g_strfreev(uri_parts
);
709 g_hash_table_insert(found
, sip_uri
, (gpointer
) TRUE
);
720 search_contacts_finalize(sipe_private
, results
,
721 g_hash_table_size(found
),
723 g_hash_table_destroy(found
);
727 mdd
->failed_callback(sipe_private
, mdd
);
731 static gboolean
process_search_contact_response(struct sipe_core_private
*sipe_private
,
733 struct transaction
*trans
)
735 struct sipe_backend_search_token
*token
= trans
->payload
->data
;
736 struct sipe_backend_search_results
*results
;
737 sipe_xml
*searchResults
;
738 const sipe_xml
*mrow
;
739 guint match_count
= 0;
740 gboolean more
= FALSE
;
742 /* valid response? */
743 if (msg
->response
!= 200) {
744 SIPE_DEBUG_ERROR("process_search_contact_response: request failed (%d)",
746 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
748 _("Contact search failed"));
752 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg
->body
? msg
->body
: "");
755 searchResults
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
756 if (!searchResults
) {
757 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
758 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
760 _("Contact search failed"));
765 mrow
= sipe_xml_child(searchResults
, "Body/Array/row");
767 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: no matches");
768 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
770 _("No contacts found"));
772 sipe_xml_free(searchResults
);
776 /* OK, we found something - show the results to the user */
777 results
= sipe_backend_search_results_start(SIPE_CORE_PUBLIC
,
778 trans
->payload
->data
);
780 SIPE_DEBUG_ERROR_NOFORMAT("process_search_contact_response: Unable to display the search results.");
781 sipe_backend_search_failed(SIPE_CORE_PUBLIC
,
783 _("Unable to display the search results"));
785 sipe_xml_free(searchResults
);
789 for (/* initialized above */ ; mrow
; mrow
= sipe_xml_twin(mrow
)) {
790 gchar
**uri_parts
= g_strsplit(sipe_xml_attribute(mrow
, "uri"), ":", 2);
791 sipe_backend_search_results_add(SIPE_CORE_PUBLIC
,
794 sipe_xml_attribute(mrow
, "displayName"),
795 sipe_xml_attribute(mrow
, "company"),
796 sipe_xml_attribute(mrow
, "country"),
797 sipe_xml_attribute(mrow
, "email"));
798 g_strfreev(uri_parts
);
802 if ((mrow
= sipe_xml_child(searchResults
, "Body/directorySearch/moreAvailable")) != NULL
) {
803 char *data
= sipe_xml_data(mrow
);
804 more
= (g_ascii_strcasecmp(data
, "true") == 0);
808 search_contacts_finalize(sipe_private
, results
, match_count
, more
);
809 sipe_xml_free(searchResults
);
814 static void search_soap_request(struct sipe_core_private
*sipe_private
,
815 struct sipe_backend_search_token
*token
,
818 gchar
*query
= prepare_buddy_search_query(search_rows
, FALSE
);
819 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
821 payload
->data
= token
;
823 sip_soap_directory_search(sipe_private
,
826 process_search_contact_response
,
831 static void search_ab_entry_failed(struct sipe_core_private
*sipe_private
,
832 struct ms_dlx_data
*mdd
)
834 /* error using [MS-DLX] server, retry using Active Directory */
835 search_soap_request(sipe_private
, mdd
->token
, mdd
->search_rows
);
839 void sipe_core_buddy_search(struct sipe_core_public
*sipe_public
,
840 struct sipe_backend_search_token
*token
,
841 const gchar
*given_name
,
842 const gchar
*surname
,
844 const gchar
*company
,
845 const gchar
*country
)
847 GSList
*query_rows
= NULL
;
849 #define ADD_QUERY_ROW(attr, val) \
851 query_rows = g_slist_append(query_rows, g_strdup(attr)); \
852 query_rows = g_slist_append(query_rows, g_strdup(val)); \
855 ADD_QUERY_ROW("givenName", given_name
);
856 ADD_QUERY_ROW("sn", surname
);
857 ADD_QUERY_ROW("mail", email
);
858 ADD_QUERY_ROW("company", company
);
859 ADD_QUERY_ROW("c", country
);
862 if (SIPE_CORE_PRIVATE
->dlx_uri
!= NULL
) {
863 struct ms_dlx_data
*mdd
= g_new0(struct ms_dlx_data
, 1);
865 mdd
->search_rows
= query_rows
;
866 mdd
->max_returns
= 100;
867 mdd
->callback
= search_ab_entry_response
;
868 mdd
->failed_callback
= search_ab_entry_failed
;
869 mdd
->session
= sipe_svc_session_start();
872 ms_dlx_webticket_request(SIPE_CORE_PRIVATE
, mdd
);
875 /* no [MS-DLX] server, use Active Directory search instead */
876 search_soap_request(SIPE_CORE_PRIVATE
, token
, query_rows
);
877 sipe_utils_slist_free_full(query_rows
, g_free
);
880 sipe_backend_search_failed(sipe_public
,
882 _("Invalid contact search query"));
885 static void get_info_finalize(struct sipe_core_private
*sipe_private
,
886 struct sipe_backend_buddy_info
*info
,
888 const gchar
*server_alias
,
891 sipe_backend_buddy bbuddy
;
892 struct sipe_buddy
*sbuddy
;
897 info
= sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC
);
899 sipe_backend_buddy_info_break(SIPE_CORE_PUBLIC
, info
);
904 bbuddy
= sipe_backend_buddy_find(SIPE_CORE_PUBLIC
, uri
, NULL
);
906 if (is_empty(server_alias
)) {
907 value
= sipe_backend_buddy_get_server_alias(SIPE_CORE_PUBLIC
,
910 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
912 SIPE_BUDDY_INFO_DISPLAY_NAME
,
916 value
= g_strdup(server_alias
);
919 /* present alias if it differs from server alias */
920 alias
= sipe_backend_buddy_get_local_alias(SIPE_CORE_PUBLIC
, bbuddy
);
921 if (alias
&& !sipe_strequal(alias
, value
))
923 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
925 SIPE_BUDDY_INFO_ALIAS
,
931 if (is_empty(email
)) {
932 value
= sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC
,
934 SIPE_BUDDY_INFO_EMAIL
);
936 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
938 SIPE_BUDDY_INFO_EMAIL
,
944 value
= sipe_backend_buddy_get_string(SIPE_CORE_PUBLIC
,
946 SIPE_BUDDY_INFO_SITE
);
948 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
950 SIPE_BUDDY_INFO_SITE
,
955 sbuddy
= sipe_buddy_find_by_uri(sipe_private
, uri
);
956 if (sbuddy
&& sbuddy
->device_name
) {
957 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
959 SIPE_BUDDY_INFO_DEVICE
,
960 sbuddy
->device_name
);
963 sipe_backend_buddy_info_finalize(SIPE_CORE_PUBLIC
, info
, uri
);
967 static void get_info_ab_entry_response(struct sipe_core_private
*sipe_private
,
969 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
971 gpointer callback_data
)
973 struct ms_dlx_data
*mdd
= callback_data
;
974 struct sipe_backend_buddy_info
*info
= NULL
;
975 gchar
*server_alias
= NULL
;
979 const sipe_xml
*node
;
981 SIPE_DEBUG_INFO("get_info_ab_entry_response: received valid SOAP message from service %s",
984 info
= sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC
);
986 for (node
= sipe_xml_child(soap_body
, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
988 node
= sipe_xml_twin(node
)) {
989 gchar
*name
= sipe_xml_data(sipe_xml_child(node
,
991 gchar
*value
= sipe_xml_data(sipe_xml_child(node
,
993 const sipe_xml
*values
= sipe_xml_child(node
,
996 /* Single value entries */
997 if (!is_empty(value
)) {
999 if (sipe_strcase_equal(name
, "displayname")) {
1000 g_free(server_alias
);
1001 server_alias
= value
;
1003 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1005 SIPE_BUDDY_INFO_DISPLAY_NAME
,
1007 } else if (sipe_strcase_equal(name
, "mail")) {
1011 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1013 SIPE_BUDDY_INFO_EMAIL
,
1015 } else if (sipe_strcase_equal(name
, "title")) {
1016 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1018 SIPE_BUDDY_INFO_JOB_TITLE
,
1020 } else if (sipe_strcase_equal(name
, "company")) {
1021 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1023 SIPE_BUDDY_INFO_COMPANY
,
1025 } else if (sipe_strcase_equal(name
, "country")) {
1026 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1028 SIPE_BUDDY_INFO_COUNTRY
,
1032 } else if (values
) {
1033 gchar
*first
= sipe_xml_data(sipe_xml_child(values
,
1036 if (sipe_strcase_equal(name
, "telephonenumber")) {
1037 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1039 SIPE_BUDDY_INFO_WORK_PHONE
,
1051 /* this will show the minmum information */
1052 get_info_finalize(sipe_private
,
1059 g_free(server_alias
);
1063 static gboolean
process_get_info_response(struct sipe_core_private
*sipe_private
,
1065 struct transaction
*trans
)
1067 const gchar
*uri
= trans
->payload
->data
;
1068 struct sipe_backend_buddy_info
*info
= NULL
;
1069 gchar
*server_alias
= NULL
;
1070 gchar
*email
= NULL
;
1072 SIPE_DEBUG_INFO("Fetching %s's user info for %s",
1073 uri
, sipe_private
->username
);
1075 if (msg
->response
!= 200) {
1076 SIPE_DEBUG_INFO("process_get_info_response: SERVICE response is %d", msg
->response
);
1078 sipe_xml
*searchResults
;
1079 const sipe_xml
*mrow
;
1081 SIPE_DEBUG_INFO("process_get_info_response: body:\n%s",
1082 msg
->body
? msg
->body
: "");
1084 searchResults
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
1085 if (!searchResults
) {
1087 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
1089 } else if ((mrow
= sipe_xml_child(searchResults
, "Body/Array/row"))) {
1091 gchar
*phone_number
;
1093 info
= sipe_backend_buddy_info_start(SIPE_CORE_PUBLIC
);
1095 server_alias
= g_strdup(sipe_xml_attribute(mrow
, "displayName"));
1096 email
= g_strdup(sipe_xml_attribute(mrow
, "email"));
1097 phone_number
= g_strdup(sipe_xml_attribute(mrow
, "phone"));
1100 * For 2007 system we will take this from ContactCard -
1101 * it has cleaner tel: URIs at least
1103 if (!SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
1104 char *tel_uri
= sip_to_tel_uri(phone_number
);
1105 /* trims its parameters, so call first */
1106 sipe_buddy_update_property(sipe_private
, uri
, SIPE_BUDDY_INFO_DISPLAY_NAME
, server_alias
);
1107 sipe_buddy_update_property(sipe_private
, uri
, SIPE_BUDDY_INFO_EMAIL
, email
);
1108 sipe_buddy_update_property(sipe_private
, uri
, SIPE_BUDDY_INFO_WORK_PHONE
, tel_uri
);
1109 sipe_buddy_update_property(sipe_private
, uri
, SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY
, phone_number
);
1112 sipe_backend_buddy_refresh_properties(SIPE_CORE_PUBLIC
,
1116 if (!is_empty(server_alias
)) {
1117 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1119 SIPE_BUDDY_INFO_DISPLAY_NAME
,
1122 if ((value
= sipe_xml_attribute(mrow
, "title")) && strlen(value
) > 0) {
1123 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1125 SIPE_BUDDY_INFO_JOB_TITLE
,
1128 if ((value
= sipe_xml_attribute(mrow
, "office")) && strlen(value
) > 0) {
1129 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1131 SIPE_BUDDY_INFO_OFFICE
,
1134 if (!is_empty(phone_number
)) {
1135 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1137 SIPE_BUDDY_INFO_WORK_PHONE
,
1140 g_free(phone_number
);
1141 if ((value
= sipe_xml_attribute(mrow
, "company")) && strlen(value
) > 0) {
1142 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1144 SIPE_BUDDY_INFO_COMPANY
,
1147 if ((value
= sipe_xml_attribute(mrow
, "city")) && strlen(value
) > 0) {
1148 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1150 SIPE_BUDDY_INFO_CITY
,
1153 if ((value
= sipe_xml_attribute(mrow
, "state")) && strlen(value
) > 0) {
1154 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1156 SIPE_BUDDY_INFO_STATE
,
1159 if ((value
= sipe_xml_attribute(mrow
, "country")) && strlen(value
) > 0) {
1160 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1162 SIPE_BUDDY_INFO_COUNTRY
,
1165 if (!is_empty(email
)) {
1166 sipe_backend_buddy_info_add(SIPE_CORE_PUBLIC
,
1168 SIPE_BUDDY_INFO_EMAIL
,
1172 sipe_xml_free(searchResults
);
1175 /* this will show the minmum information */
1176 get_info_finalize(sipe_private
,
1182 g_free(server_alias
);
1188 static void get_info_ab_entry_failed(struct sipe_core_private
*sipe_private
,
1189 struct ms_dlx_data
*mdd
)
1191 /* error using [MS-DLX] server, retry using Active Directory */
1192 gchar
*query
= prepare_buddy_search_query(mdd
->search_rows
, FALSE
);
1193 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
1195 payload
->destroy
= g_free
;
1196 payload
->data
= mdd
->other
;
1199 sip_soap_directory_search(sipe_private
,
1202 process_get_info_response
,
1209 void sipe_core_buddy_get_info(struct sipe_core_public
*sipe_public
,
1212 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1214 if (sipe_private
->dlx_uri
) {
1215 struct ms_dlx_data
*mdd
= g_new0(struct ms_dlx_data
, 1);
1217 mdd
->search_rows
= g_slist_append(mdd
->search_rows
, g_strdup("msRTCSIP-PrimaryUserAddress"));
1218 mdd
->search_rows
= g_slist_append(mdd
->search_rows
, g_strdup(who
));
1220 mdd
->other
= g_strdup(who
);
1221 mdd
->max_returns
= 1;
1222 mdd
->callback
= get_info_ab_entry_response
;
1223 mdd
->failed_callback
= get_info_ab_entry_failed
;
1224 mdd
->session
= sipe_svc_session_start();
1226 ms_dlx_webticket_request(sipe_private
, mdd
);
1229 /* no [MS-DLX] server, use Active Directory search instead */
1230 gchar
*row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
,
1231 "msRTCSIP-PrimaryUserAddress",
1233 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
1235 SIPE_DEBUG_INFO("sipe_core_buddy_get_info: row: %s",
1238 payload
->destroy
= g_free
;
1239 payload
->data
= g_strdup(who
);
1241 sip_soap_directory_search(sipe_private
,
1244 process_get_info_response
,
1250 static void photo_response_data_free(struct photo_response_data
*data
)
1253 g_free(data
->photo_hash
);
1254 if (data
->request
) {
1255 sipe_http_request_cancel(data
->request
);
1260 static void process_buddy_photo_response(struct sipe_core_private
*sipe_private
,
1266 struct photo_response_data
*rdata
= (struct photo_response_data
*) data
;
1268 rdata
->request
= NULL
;
1270 if (status
== SIPE_HTTP_STATUS_OK
) {
1271 const gchar
*len_str
= sipe_utils_nameval_find(headers
,
1274 gsize photo_size
= atoi(len_str
);
1275 gpointer photo
= g_new(char, photo_size
);
1278 memcpy(photo
, body
, photo_size
);
1280 sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC
,
1289 sipe_private
->pending_photo_requests
=
1290 g_slist_remove(sipe_private
->pending_photo_requests
, rdata
);
1292 photo_response_data_free(rdata
);
1295 static gchar
*create_x_ms_webticket_header(const gchar
*wsse_security
)
1297 gchar
*assertion
= sipe_xml_extract_raw(wsse_security
, "saml:Assertion", TRUE
);
1298 gchar
*wsse_security_base64
;
1299 gchar
*x_ms_webticket_header
;
1305 wsse_security_base64
= g_base64_encode((const guchar
*)assertion
,
1307 x_ms_webticket_header
= g_strdup_printf("X-MS-WebTicket: opaque=%s\r\n",
1308 wsse_security_base64
);
1311 g_free(wsse_security_base64
);
1313 return x_ms_webticket_header
;
1316 static void get_photo_ab_entry_response(struct sipe_core_private
*sipe_private
,
1318 SIPE_UNUSED_PARAMETER
const gchar
*raw
,
1319 sipe_xml
*soap_body
,
1320 gpointer callback_data
)
1322 struct ms_dlx_data
*mdd
= callback_data
;
1323 gchar
*photo_rel_path
= NULL
;
1324 gchar
*photo_hash
= NULL
;
1325 const gchar
*photo_hash_old
=
1326 sipe_backend_buddy_get_photo_hash(SIPE_CORE_PUBLIC
, mdd
->other
);
1329 const sipe_xml
*node
;
1331 SIPE_DEBUG_INFO("get_photo_ab_entry_response: received valid SOAP message from service %s",
1334 for (node
= sipe_xml_child(soap_body
, "Body/SearchAbEntryResponse/SearchAbEntryResult/Items/AbEntry/Attributes/Attribute");
1336 node
= sipe_xml_twin(node
)) {
1337 gchar
*name
= sipe_xml_data(sipe_xml_child(node
, "Name"));
1338 gchar
*value
= sipe_xml_data(sipe_xml_child(node
, "Value"));
1340 if (!is_empty(value
)) {
1341 if (sipe_strcase_equal(name
, "PhotoRelPath")) {
1342 g_free(photo_rel_path
);
1343 photo_rel_path
= value
;
1345 } else if (sipe_strcase_equal(name
, "PhotoHash")) {
1357 if (sipe_private
->addressbook_uri
&& photo_rel_path
&&
1358 photo_hash
&& !sipe_strequal(photo_hash
, photo_hash_old
)) {
1359 gchar
*photo_url
= g_strdup_printf("%s/%s",
1360 sipe_private
->addressbook_uri
, photo_rel_path
);
1361 gchar
*x_ms_webticket_header
= create_x_ms_webticket_header(mdd
->wsse_security
);
1363 struct photo_response_data
*data
= g_new(struct photo_response_data
, 1);
1364 data
->who
= g_strdup(mdd
->other
);
1365 data
->photo_hash
= photo_hash
;
1368 data
->request
= sipe_http_request_get(sipe_private
,
1370 x_ms_webticket_header
,
1371 process_buddy_photo_response
,
1374 if (data
->request
) {
1375 sipe_private
->pending_photo_requests
=
1376 g_slist_append(sipe_private
->pending_photo_requests
, data
);
1377 sipe_http_request_ready(data
->request
);
1379 photo_response_data_free(data
);
1382 g_free(x_ms_webticket_header
);
1386 g_free(photo_rel_path
);
1391 static void get_photo_ab_entry_failed(SIPE_UNUSED_PARAMETER
struct sipe_core_private
*sipe_private
,
1392 struct ms_dlx_data
*mdd
)
1397 static void buddy_fetch_photo(struct sipe_core_private
*sipe_private
,
1400 if (sipe_backend_uses_photo()) {
1402 /* Lync 2013 or newer: use UCS */
1403 if (SIPE_CORE_PRIVATE_FLAG_IS(LYNC2013
)) {
1405 sipe_ucs_get_photo(sipe_private
, uri
);
1407 /* Lync 2010: use [MS-DLX] */
1408 } else if (sipe_private
->dlx_uri
&&
1409 sipe_private
->addressbook_uri
) {
1410 struct ms_dlx_data
*mdd
= g_new0(struct ms_dlx_data
, 1);
1412 mdd
->search_rows
= g_slist_append(mdd
->search_rows
, g_strdup("msRTCSIP-PrimaryUserAddress"));
1413 mdd
->search_rows
= g_slist_append(mdd
->search_rows
, g_strdup(uri
));
1415 mdd
->other
= g_strdup(uri
);
1416 mdd
->max_returns
= 1;
1417 mdd
->callback
= get_photo_ab_entry_response
;
1418 mdd
->failed_callback
= get_photo_ab_entry_failed
;
1419 mdd
->session
= sipe_svc_session_start();
1421 ms_dlx_webticket_request(sipe_private
, mdd
);
1426 static void buddy_refresh_photos_cb(gpointer uri
,
1427 SIPE_UNUSED_PARAMETER gpointer value
,
1428 gpointer sipe_private
)
1430 buddy_fetch_photo(sipe_private
, uri
);
1433 void sipe_buddy_refresh_photos(struct sipe_core_private
*sipe_private
)
1435 g_hash_table_foreach(sipe_private
->buddies
,
1436 buddy_refresh_photos_cb
,
1440 /* Buddy menu callbacks*/
1442 void sipe_core_buddy_new_chat(struct sipe_core_public
*sipe_public
,
1445 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1447 /* 2007+ conference */
1448 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
)) {
1449 sipe_conf_add(sipe_private
, who
);
1451 /* 2005- multiparty chat */
1453 gchar
*self
= sip_uri_self(sipe_private
);
1454 struct sip_session
*session
;
1456 session
= sipe_session_add_chat(sipe_private
,
1460 session
->chat_session
->backend
= sipe_backend_chat_create(SIPE_CORE_PUBLIC
,
1461 session
->chat_session
,
1462 session
->chat_session
->title
,
1466 sipe_im_invite(sipe_private
, session
, who
,
1467 NULL
, NULL
, NULL
, FALSE
);
1471 void sipe_core_buddy_send_email(struct sipe_core_public
*sipe_public
,
1474 sipe_backend_buddy buddy
= sipe_backend_buddy_find(sipe_public
,
1477 gchar
*email
= sipe_backend_buddy_get_string(sipe_public
,
1479 SIPE_BUDDY_INFO_EMAIL
);
1482 gchar
*command_line
= g_strdup_printf(
1488 " mailto:%s", email
);
1491 SIPE_DEBUG_INFO("sipe_core_buddy_send_email: going to call email client: %s",
1493 g_spawn_command_line_async(command_line
, NULL
);
1494 g_free(command_line
);
1497 SIPE_DEBUG_INFO("sipe_core_buddy_send_email: no email address stored for buddy=%s",
1504 static struct sipe_backend_buddy_menu
*buddy_menu_phone(struct sipe_core_public
*sipe_public
,
1505 struct sipe_backend_buddy_menu
*menu
,
1506 sipe_backend_buddy buddy
,
1507 sipe_buddy_info_fields id_phone
,
1508 sipe_buddy_info_fields id_display
,
1511 gchar
*phone
= sipe_backend_buddy_get_string(sipe_public
,
1515 gchar
*display
= sipe_backend_buddy_get_string(sipe_public
,
1519 gchar
*label
= g_strdup_printf("%s %s",
1522 (tmp
= sip_tel_uri_denormalize(phone
)));
1523 menu
= sipe_backend_buddy_menu_add(sipe_public
,
1526 SIPE_BUDDY_MENU_MAKE_CALL
,
1537 struct sipe_backend_buddy_menu
*sipe_core_buddy_create_menu(struct sipe_core_public
*sipe_public
,
1538 const gchar
*buddy_name
,
1539 struct sipe_backend_buddy_menu
*menu
)
1541 struct sipe_core_private
*sipe_private
= SIPE_CORE_PRIVATE
;
1542 sipe_backend_buddy buddy
= sipe_backend_buddy_find(sipe_public
,
1545 gchar
*self
= sip_uri_self(sipe_private
);
1547 SIPE_SESSION_FOREACH
{
1548 if (!sipe_strcase_equal(self
, buddy_name
) && session
->chat_session
)
1550 struct sipe_chat_session
*chat_session
= session
->chat_session
;
1551 gboolean is_conf
= (chat_session
->type
== SIPE_CHAT_TYPE_CONFERENCE
);
1553 if (sipe_backend_chat_find(chat_session
->backend
, buddy_name
))
1555 gboolean conf_op
= sipe_backend_chat_is_operator(chat_session
->backend
, self
);
1559 !sipe_backend_chat_is_operator(chat_session
->backend
, buddy_name
) &&
1560 /* We are a conf OP */
1562 gchar
*label
= g_strdup_printf(_("Make leader of '%s'"),
1563 chat_session
->title
);
1564 menu
= sipe_backend_buddy_menu_add(sipe_public
,
1567 SIPE_BUDDY_MENU_MAKE_CHAT_LEADER
,
1573 /* We are a conf OP */
1575 gchar
*label
= g_strdup_printf(_("Remove from '%s'"),
1576 chat_session
->title
);
1577 menu
= sipe_backend_buddy_menu_add(sipe_public
,
1580 SIPE_BUDDY_MENU_REMOVE_FROM_CHAT
,
1588 (is_conf
&& !session
->locked
)) {
1589 gchar
*label
= g_strdup_printf(_("Invite to '%s'"),
1590 chat_session
->title
);
1591 menu
= sipe_backend_buddy_menu_add(sipe_public
,
1594 SIPE_BUDDY_MENU_INVITE_TO_CHAT
,
1600 } SIPE_SESSION_FOREACH_END
;
1603 menu
= sipe_backend_buddy_menu_add(sipe_public
,
1606 SIPE_BUDDY_MENU_NEW_CHAT
,
1609 /* add buddy's phone numbers if we have call control */
1610 if (sip_csta_is_idle(sipe_private
)) {
1613 menu
= buddy_menu_phone(sipe_public
,
1616 SIPE_BUDDY_INFO_WORK_PHONE
,
1617 SIPE_BUDDY_INFO_WORK_PHONE_DISPLAY
,
1620 menu
= buddy_menu_phone(sipe_public
,
1623 SIPE_BUDDY_INFO_MOBILE_PHONE
,
1624 SIPE_BUDDY_INFO_MOBILE_PHONE_DISPLAY
,
1628 menu
= buddy_menu_phone(sipe_public
,
1631 SIPE_BUDDY_INFO_HOME_PHONE
,
1632 SIPE_BUDDY_INFO_HOME_PHONE_DISPLAY
,
1636 menu
= buddy_menu_phone(sipe_public
,
1639 SIPE_BUDDY_INFO_OTHER_PHONE
,
1640 SIPE_BUDDY_INFO_OTHER_PHONE_DISPLAY
,
1644 menu
= buddy_menu_phone(sipe_public
,
1647 SIPE_BUDDY_INFO_CUSTOM1_PHONE
,
1648 SIPE_BUDDY_INFO_CUSTOM1_PHONE_DISPLAY
,
1653 gchar
*email
= sipe_backend_buddy_get_string(sipe_public
,
1655 SIPE_BUDDY_INFO_EMAIL
);
1657 menu
= sipe_backend_buddy_menu_add(sipe_public
,
1660 SIPE_BUDDY_MENU_SEND_EMAIL
,
1666 /* access level control */
1667 if (SIPE_CORE_PRIVATE_FLAG_IS(OCS2007
))
1668 menu
= sipe_backend_buddy_sub_menu_add(sipe_public
,
1671 sipe_ocs2007_access_control_menu(sipe_private
,
1677 guint
sipe_buddy_count(struct sipe_core_private
*sipe_private
)
1679 return(g_hash_table_size(sipe_private
->buddies
));
1682 static guint
sipe_ht_hash_nick(const char *nick
)
1684 char *lc
= g_utf8_strdown(nick
, -1);
1685 guint bucket
= g_str_hash(lc
);
1691 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
1693 char *nick1_norm
= NULL
;
1694 char *nick2_norm
= NULL
;
1697 if (nick1
== NULL
&& nick2
== NULL
) return TRUE
;
1698 if (nick1
== NULL
|| nick2
== NULL
||
1699 !g_utf8_validate(nick1
, -1, NULL
) ||
1700 !g_utf8_validate(nick2
, -1, NULL
)) return FALSE
;
1702 nick1_norm
= g_utf8_casefold(nick1
, -1);
1703 nick2_norm
= g_utf8_casefold(nick2
, -1);
1704 equal
= g_utf8_collate(nick1_norm
, nick2_norm
) == 0;
1711 void sipe_buddy_init(struct sipe_core_private
*sipe_private
)
1713 sipe_private
->buddies
= g_hash_table_new((GHashFunc
) sipe_ht_hash_nick
,
1714 (GEqualFunc
) sipe_ht_equals_nick
);