purple: work around broken dbus-server.h
[siplcs.git] / src / core / sipe-ucs.c
blob2f07cf4475097ae758553f3184fd6d946865d87b
1 /**
2 * @file sipe-ucs.c
4 * pidgin-sipe
6 * Copyright (C) 2013-2016 SIPE Project <http://sipe.sourceforge.net/>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * Implementation for Unified Contact Store [MS-OXWSCOS]
25 * <http://msdn.microsoft.com/en-us/library/jj194130.aspx>
26 * EWS Reference
27 * <http://msdn.microsoft.com/en-us/library/office/bb204119.aspx>
28 * Photo Web Service Protocol [MS-OXWSPHOTO]
29 * <http://msdn.microsoft.com/en-us/library/jj194353.aspx>
30 * FindPeople operation
31 * <http://msdn.microsoft.com/en-us/library/office/jj191039.aspx>
34 #include <string.h>
36 #include <glib.h>
37 #include <time.h>
39 #include "sipe-backend.h"
40 #include "sipe-buddy.h"
41 #include "sipe-common.h"
42 #include "sipe-core.h"
43 #include "sipe-core-private.h"
44 #include "sipe-ews-autodiscover.h"
45 #include "sipe-group.h"
46 #include "sipe-http.h"
47 #include "sipe-nls.h"
48 #include "sipe-subscriptions.h"
49 #include "sipe-ucs.h"
50 #include "sipe-utils.h"
51 #include "sipe-xml.h"
53 struct sipe_ucs_transaction {
54 GSList *pending_requests;
57 typedef void (ucs_callback)(struct sipe_core_private *sipe_private,
58 struct sipe_ucs_transaction *trans,
59 const sipe_xml *body,
60 gpointer callback_data);
62 struct ucs_request {
63 gchar *body;
64 ucs_callback *cb;
65 gpointer cb_data;
66 struct sipe_ucs_transaction *transaction;
67 struct sipe_http_request *request;
70 struct sipe_ucs {
71 struct ucs_request *active_request;
72 GSList *transactions;
73 GSList *default_transaction;
74 gchar *ews_url;
75 time_t last_response;
76 guint group_id;
77 gboolean migrated;
78 gboolean shutting_down;
81 static void sipe_ucs_request_free(struct sipe_core_private *sipe_private,
82 struct ucs_request *data)
84 struct sipe_ucs *ucs = sipe_private->ucs;
85 struct sipe_ucs_transaction *trans = data->transaction;
87 /* remove request from transaction */
88 trans->pending_requests = g_slist_remove(trans->pending_requests,
89 data);
90 sipe_private->ucs->active_request = NULL;
92 /* remove completed transactions (except default transaction) */
93 if (!trans->pending_requests &&
94 (trans != ucs->default_transaction->data)) {
95 ucs->transactions = g_slist_remove(ucs->transactions,
96 trans);
97 g_free(trans);
100 if (data->request)
101 sipe_http_request_cancel(data->request);
102 if (data->cb)
103 /* Callback: aborted */
104 (*data->cb)(sipe_private, NULL, NULL, data->cb_data);
105 g_free(data->body);
106 g_free(data);
109 static void sipe_ucs_next_request(struct sipe_core_private *sipe_private);
110 static void sipe_ucs_http_response(struct sipe_core_private *sipe_private,
111 guint status,
112 SIPE_UNUSED_PARAMETER GSList *headers,
113 const gchar *body,
114 gpointer callback_data)
116 struct ucs_request *data = callback_data;
118 SIPE_DEBUG_INFO("sipe_ucs_http_response: code %d", status);
119 data->request = NULL;
121 if ((status == SIPE_HTTP_STATUS_OK) && body) {
122 sipe_xml *xml = sipe_xml_parse(body, strlen(body));
123 const sipe_xml *soap_body = sipe_xml_child(xml, "Body");
124 /* Callback: success */
125 (*data->cb)(sipe_private,
126 data->transaction,
127 soap_body,
128 data->cb_data);
129 sipe_xml_free(xml);
130 } else {
131 /* Callback: failed */
132 (*data->cb)(sipe_private, NULL, NULL, data->cb_data);
135 /* already been called */
136 data->cb = NULL;
138 sipe_ucs_request_free(sipe_private, data);
139 sipe_ucs_next_request(sipe_private);
142 static void sipe_ucs_next_request(struct sipe_core_private *sipe_private)
144 struct sipe_ucs *ucs = sipe_private->ucs;
145 struct sipe_ucs_transaction *trans;
147 if (ucs->active_request || ucs->shutting_down || !ucs->ews_url)
148 return;
150 trans = ucs->transactions->data;
151 while (trans->pending_requests) {
152 struct ucs_request *data = trans->pending_requests->data;
153 gchar *soap = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
154 "<soap:Envelope"
155 " xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
156 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
157 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
158 " >"
159 " <soap:Header>"
160 " <t:RequestServerVersion Version=\"Exchange2013\" />"
161 " </soap:Header>"
162 " <soap:Body>"
163 " %s"
164 " </soap:Body>"
165 "</soap:Envelope>",
166 data->body);
167 struct sipe_http_request *request = sipe_http_request_post(sipe_private,
168 ucs->ews_url,
169 NULL,
170 soap,
171 "text/xml; charset=UTF-8",
172 sipe_ucs_http_response,
173 data);
174 g_free(soap);
176 if (request) {
177 g_free(data->body);
178 data->body = NULL;
179 data->request = request;
181 ucs->active_request = data;
183 sipe_core_email_authentication(sipe_private,
184 request);
185 sipe_http_request_allow_redirect(request);
186 sipe_http_request_ready(request);
188 break;
189 } else {
190 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_next_request: failed to create HTTP connection");
191 sipe_ucs_request_free(sipe_private, data);
196 static gboolean sipe_ucs_http_request(struct sipe_core_private *sipe_private,
197 struct sipe_ucs_transaction *trans,
198 gchar *body, /* takes ownership */
199 ucs_callback *callback,
200 gpointer callback_data)
202 struct sipe_ucs *ucs = sipe_private->ucs;
204 if (!ucs || ucs->shutting_down) {
205 SIPE_DEBUG_ERROR("sipe_ucs_http_request: new UCS request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
206 "Body: %s\n",
207 body ? body : "<EMPTY>");
208 g_free(body);
209 return(FALSE);
211 } else {
212 struct ucs_request *data = g_new0(struct ucs_request, 1);
214 data->cb = callback;
215 data->cb_data = callback_data;
216 data->body = body;
218 if (!trans)
219 trans = ucs->default_transaction->data;
220 data->transaction = trans;
221 trans->pending_requests = g_slist_append(trans->pending_requests,
222 data);
224 sipe_ucs_next_request(sipe_private);
225 return(TRUE);
229 struct sipe_ucs_transaction *sipe_ucs_transaction(struct sipe_core_private *sipe_private)
231 struct sipe_ucs *ucs = sipe_private->ucs;
232 struct sipe_ucs_transaction *trans;
234 if (!ucs)
235 return(NULL);
237 /* always insert new transactions before default transaction */
238 trans = g_new0(struct sipe_ucs_transaction, 1);
239 ucs->transactions = g_slist_insert_before(ucs->transactions,
240 ucs->default_transaction,
241 trans);
243 return(trans);
246 static void sipe_ucs_search_response(struct sipe_core_private *sipe_private,
247 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
248 const sipe_xml *body,
249 gpointer callback_data)
251 const sipe_xml *persona_node;
252 struct sipe_backend_search_results *results = NULL;
253 guint match_count = 0;
255 for (persona_node = sipe_xml_child(body,
256 "FindPeopleResponse/People/Persona");
257 persona_node;
258 persona_node = sipe_xml_twin(persona_node)) {
259 const sipe_xml *address = sipe_xml_child(persona_node,
260 "ImAddress");
262 /* only display Persona nodes which have an "ImAddress" node */
263 if (address) {
264 gchar *uri;
265 gchar *displayname;
266 gchar *company;
267 gchar *email;
269 /* OK, we found something - show the results to the user */
270 match_count++;
271 if (!results) {
272 results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
273 callback_data);
274 if (!results) {
275 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_search_response: Unable to display the search results.");
276 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
277 callback_data,
278 _("Unable to display the search results"));
279 return;
283 uri = sipe_xml_data(address);
284 displayname = sipe_xml_data(sipe_xml_child(persona_node,
285 "DisplayName"));
286 company = sipe_xml_data(sipe_xml_child(persona_node,
287 "CompanyName"));
288 email = sipe_xml_data(sipe_xml_child(persona_node,
289 "EmailAddress/EmailAddress"));
291 sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
292 results,
293 sipe_get_no_sip_uri(uri),
294 displayname,
295 company,
296 NULL,
297 email);
299 g_free(email);
300 g_free(company);
301 g_free(displayname);
302 g_free(uri);
306 if (match_count > 0)
307 sipe_buddy_search_contacts_finalize(sipe_private,
308 results,
309 match_count,
310 FALSE);
311 else
312 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
313 callback_data,
314 _("No contacts found"));
317 void sipe_ucs_search(struct sipe_core_private *sipe_private,
318 struct sipe_backend_search_token *token,
319 const gchar *given_name,
320 const gchar *surname,
321 const gchar *email,
322 const gchar *sipid,
323 const gchar *company,
324 const gchar *country)
326 guint count = 0;
327 GString *query = g_string_new(NULL);
330 * Search GAL for matching entries
332 * QueryString should support field properties and quoting ("")
333 * according to the specification. But in my trials I couldn't get
334 * them to work. Concatenate all query words to a single string.
335 * Only items that match ALL words will be returned by this query.
337 #define ADD_QUERY_VALUE(val) \
338 if (val) { \
339 if (count++) \
340 g_string_append_c(query, ' '); \
341 g_string_append(query, val); \
344 ADD_QUERY_VALUE(given_name);
345 ADD_QUERY_VALUE(surname);
346 ADD_QUERY_VALUE(email);
347 ADD_QUERY_VALUE(sipid);
348 ADD_QUERY_VALUE(company);
349 ADD_QUERY_VALUE(country);
351 if (count > 0) {
352 gchar *body = g_markup_printf_escaped("<m:FindPeople>"
353 " <m:PersonaShape>"
354 " <t:BaseShape>IdOnly</t:BaseShape>"
355 " <t:AdditionalProperties>"
356 " <t:FieldURI FieldURI=\"persona:CompanyName\"/>"
357 " <t:FieldURI FieldURI=\"persona:DisplayName\"/>"
358 " <t:FieldURI FieldURI=\"persona:EmailAddress\"/>"
359 " <t:FieldURI FieldURI=\"persona:ImAddress\"/>"
360 /* Locations doesn't seem to work
361 " <t:FieldURI FieldURI=\"persona:Locations\"/>"
363 " </t:AdditionalProperties>"
364 " </m:PersonaShape>"
365 " <m:IndexedPageItemView BasePoint=\"Beginning\" MaxEntriesReturned=\"100\" Offset=\"0\"/>"
367 * I have no idea why Exchnage doesn't accept this
368 * FieldURI for restrictions. Without it the search
369 * will return users that don't have an ImAddress
370 * and we need to filter them out ourselves :-(
371 " <m:Restriction>"
372 " <t:Exists>"
373 " <t:FieldURI FieldURI=\"persona:ImAddress\"/>"
374 " </t:Exists>"
375 " </m:Restriction>"
377 " <m:ParentFolderId>"
378 " <t:DistinguishedFolderId Id=\"directory\"/>"
379 " </m:ParentFolderId>"
380 " <m:QueryString>%s</m:QueryString>"
381 "</m:FindPeople>",
382 query->str);
384 if (!sipe_ucs_http_request(sipe_private,
385 NULL,
386 body,
387 sipe_ucs_search_response,
388 token))
389 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
390 token,
391 _("Contact search failed"));
392 } else
393 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
394 token,
395 _("Invalid contact search query"));
397 g_string_free(query, TRUE);
400 static void sipe_ucs_ignore_response(struct sipe_core_private *sipe_private,
401 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
402 SIPE_UNUSED_PARAMETER const sipe_xml *body,
403 SIPE_UNUSED_PARAMETER gpointer callback_data)
405 SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_ignore_response: done");
406 sipe_private->ucs->last_response = time(NULL);
409 static void ucs_extract_keys(const sipe_xml *persona_node,
410 const gchar **key,
411 const gchar **change)
413 const sipe_xml *attr_node;
416 * extract Exchange key - play the guessing game :-(
418 * We can't use the "DisplayName" node, because the text is localized.
420 * Assume that IsQuickContact == "true" and IsHidden == "false" means
421 * this Attribution node contains the information for the Lync contact.
423 for (attr_node = sipe_xml_child(persona_node,
424 "Attributions/Attribution");
425 attr_node;
426 attr_node = sipe_xml_twin(attr_node)) {
427 const sipe_xml *id_node = sipe_xml_child(attr_node,
428 "SourceId");
429 gchar *hidden = sipe_xml_data(sipe_xml_child(attr_node,
430 "IsHidden"));
431 gchar *quick = sipe_xml_data(sipe_xml_child(attr_node,
432 "IsQuickContact"));
433 if (id_node &&
434 sipe_strcase_equal(hidden, "false") &&
435 sipe_strcase_equal(quick, "true")) {
436 *key = sipe_xml_attribute(id_node, "Id");
437 *change = sipe_xml_attribute(id_node, "ChangeKey");
438 g_free(quick);
439 g_free(hidden);
440 break;
442 g_free(quick);
443 g_free(hidden);
447 static void sipe_ucs_add_new_im_contact_to_group_response(struct sipe_core_private *sipe_private,
448 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
449 const sipe_xml *body,
450 gpointer callback_data)
452 gchar *who = callback_data;
453 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private, who);
454 const sipe_xml *persona_node = sipe_xml_child(body,
455 "AddNewImContactToGroupResponse/Persona");
457 sipe_private->ucs->last_response = time(NULL);
459 if (persona_node &&
460 buddy &&
461 is_empty(buddy->exchange_key) &&
462 is_empty(buddy->change_key)) {
463 const gchar *key = NULL;
464 const gchar *change = NULL;
466 ucs_extract_keys(persona_node, &key, &change);
468 if (!is_empty(key) && !is_empty(change)) {
470 sipe_buddy_add_keys(sipe_private,
471 buddy,
472 key,
473 change);
475 SIPE_DEBUG_INFO("sipe_ucs_add_new_im_contact_to_group_response: persona URI '%s' key '%s' change '%s'",
476 buddy->name, key, change);
480 g_free(who);
483 void sipe_ucs_group_add_buddy(struct sipe_core_private *sipe_private,
484 struct sipe_ucs_transaction *trans,
485 struct sipe_group *group,
486 struct sipe_buddy *buddy,
487 const gchar *who)
489 /* existing or new buddy? */
490 if (buddy && buddy->exchange_key) {
491 gchar *body = g_strdup_printf("<m:AddImContactToGroup>"
492 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
493 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
494 "</m:AddImContactToGroup>",
495 buddy->exchange_key,
496 buddy->change_key,
497 group->exchange_key,
498 group->change_key);
500 sipe_ucs_http_request(sipe_private,
501 trans,
502 body,
503 sipe_ucs_ignore_response,
504 NULL);
505 } else {
506 gchar *payload = g_strdup(who);
507 gchar *body = g_strdup_printf("<m:AddNewImContactToGroup>"
508 " <m:ImAddress>%s</m:ImAddress>"
509 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
510 "</m:AddNewImContactToGroup>",
511 sipe_get_no_sip_uri(who),
512 group->exchange_key,
513 group->change_key);
515 if (!sipe_ucs_http_request(sipe_private,
516 trans,
517 body,
518 sipe_ucs_add_new_im_contact_to_group_response,
519 payload))
520 g_free(payload);
524 void sipe_ucs_group_remove_buddy(struct sipe_core_private *sipe_private,
525 struct sipe_ucs_transaction *trans,
526 struct sipe_group *group,
527 struct sipe_buddy *buddy)
529 if (group) {
531 * If a contact is removed from last group, it will also be
532 * removed from contact list completely. The documentation has
533 * a RemoveContactFromImList operation, but that doesn't seem
534 * to work at all, i.e. it is always rejected by the server.
536 gchar *body = g_strdup_printf("<m:RemoveImContactFromGroup>"
537 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
538 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
539 "</m:RemoveImContactFromGroup>",
540 buddy->exchange_key,
541 buddy->change_key,
542 group->exchange_key,
543 group->change_key);
545 sipe_ucs_http_request(sipe_private,
546 trans,
547 body,
548 sipe_ucs_ignore_response,
549 NULL);
553 static struct sipe_group *ucs_create_group(struct sipe_core_private *sipe_private,
554 const sipe_xml *group_node)
556 const sipe_xml *id_node = sipe_xml_child(group_node,
557 "ExchangeStoreId");
558 const gchar *key = sipe_xml_attribute(id_node, "Id");
559 const gchar *change = sipe_xml_attribute(id_node, "ChangeKey");
560 struct sipe_group *group = NULL;
562 if (!(is_empty(key) || is_empty(change))) {
563 gchar *name = sipe_xml_data(sipe_xml_child(group_node,
564 "DisplayName"));
565 group = sipe_group_add(sipe_private,
566 name,
567 key,
568 change,
569 /* sipe_group must have unique ID */
570 ++sipe_private->ucs->group_id);
571 g_free(name);
574 return(group);
577 static void sipe_ucs_add_im_group_response(struct sipe_core_private *sipe_private,
578 struct sipe_ucs_transaction *trans,
579 const sipe_xml *body,
580 gpointer callback_data)
582 gchar *who = callback_data;
583 const sipe_xml *group_node = sipe_xml_child(body,
584 "AddImGroupResponse/ImGroup");
585 struct sipe_group *group = ucs_create_group(sipe_private, group_node);
587 sipe_private->ucs->last_response = time(NULL);
589 if (group) {
590 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
591 who);
593 if (buddy)
594 sipe_buddy_insert_group(buddy, group);
596 sipe_ucs_group_add_buddy(sipe_private,
597 trans,
598 group,
599 buddy,
600 who);
603 g_free(who);
606 void sipe_ucs_group_create(struct sipe_core_private *sipe_private,
607 struct sipe_ucs_transaction *trans,
608 const gchar *name,
609 const gchar *who)
611 gchar *payload = g_strdup(who);
612 /* new_name can contain restricted characters */
613 gchar *body = g_markup_printf_escaped("<m:AddImGroup>"
614 " <m:DisplayName>%s</m:DisplayName>"
615 "</m:AddImGroup>",
616 name);
618 if (!sipe_ucs_http_request(sipe_private,
619 trans,
620 body,
621 sipe_ucs_add_im_group_response,
622 payload))
623 g_free(payload);
626 void sipe_ucs_group_rename(struct sipe_core_private *sipe_private,
627 struct sipe_group *group,
628 const gchar *new_name)
630 /* new_name can contain restricted characters */
631 gchar *body = g_markup_printf_escaped("<m:SetImGroup>"
632 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
633 " <m:NewDisplayName>%s</m:NewDisplayName>"
634 "</m:SetImGroup>",
635 group->exchange_key,
636 group->change_key,
637 new_name);
639 sipe_ucs_http_request(sipe_private,
640 NULL,
641 body,
642 sipe_ucs_ignore_response,
643 NULL);
646 void sipe_ucs_group_remove(struct sipe_core_private *sipe_private,
647 struct sipe_group *group)
649 gchar *body = g_strdup_printf("<m:RemoveImGroup>"
650 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
651 "</m:RemoveImGroup>",
652 group->exchange_key,
653 group->change_key);
655 sipe_ucs_http_request(sipe_private,
656 NULL,
657 body,
658 sipe_ucs_ignore_response,
659 NULL);
662 static void ucs_init_failure(struct sipe_core_private *sipe_private)
664 /* Did the user specify any email settings? */
665 gboolean default_settings =
666 is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
667 SIPE_SETTING_EMAIL_URL)) &&
668 is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
669 SIPE_SETTING_EMAIL_LOGIN)) &&
670 is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
671 SIPE_SETTING_EMAIL_PASSWORD));
673 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
674 _("UCS initialization failed!"),
675 default_settings ?
676 _("Couldn't find an Exchange server with the default Email settings. Therefore the contacts list will not work.\n\nYou'll need to provide Email settings in the account setup.") :
677 _("Couldn't find an Exchange server with the Email settings provided in the account setup. Therefore the contacts list will not work.\n\nPlease correct your Email settings."));
680 static void sipe_ucs_get_im_item_list_response(struct sipe_core_private *sipe_private,
681 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
682 const sipe_xml *body,
683 SIPE_UNUSED_PARAMETER gpointer callback_data)
685 const sipe_xml *node = sipe_xml_child(body,
686 "GetImItemListResponse/ImItemList");
688 if (node) {
689 const sipe_xml *persona_node;
690 const sipe_xml *group_node;
691 GHashTable *uri_to_alias = g_hash_table_new_full(g_str_hash,
692 g_str_equal,
693 NULL,
694 g_free);
696 /* Start processing contact list */
697 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
698 sipe_group_update_start(sipe_private);
699 sipe_buddy_update_start(sipe_private);
700 } else
701 sipe_backend_buddy_list_processing_start(SIPE_CORE_PUBLIC);
703 for (persona_node = sipe_xml_child(node, "Personas/Persona");
704 persona_node;
705 persona_node = sipe_xml_twin(persona_node)) {
706 gchar *address = sipe_xml_data(sipe_xml_child(persona_node,
707 "ImAddress"));
708 const gchar *key = NULL;
709 const gchar *change = NULL;
711 ucs_extract_keys(persona_node, &key, &change);
713 if (!(is_empty(address) || is_empty(key) || is_empty(change))) {
714 gchar *alias = sipe_xml_data(sipe_xml_child(persona_node,
715 "DisplayName"));
717 * it seems to be undefined if ImAddress node
718 * contains "sip:" prefix or not...
720 gchar *uri = sip_uri(address);
721 struct sipe_buddy *buddy = sipe_buddy_add(sipe_private,
722 uri,
723 key,
724 change);
725 g_free(uri);
727 /* hash table takes ownership of alias */
728 g_hash_table_insert(uri_to_alias,
729 buddy->name,
730 alias);
732 SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: persona URI '%s' key '%s' change '%s'",
733 buddy->name, key, change);
735 g_free(address);
738 for (group_node = sipe_xml_child(node, "Groups/ImGroup");
739 group_node;
740 group_node = sipe_xml_twin(group_node)) {
741 struct sipe_group *group = ucs_create_group(sipe_private,
742 group_node);
744 if (group) {
745 const sipe_xml *member_node;
747 for (member_node = sipe_xml_child(group_node,
748 "MemberCorrelationKey/ItemId");
749 member_node;
750 member_node = sipe_xml_twin(member_node)) {
751 struct sipe_buddy *buddy = sipe_buddy_find_by_exchange_key(sipe_private,
752 sipe_xml_attribute(member_node,
753 "Id"));
754 if (buddy)
755 sipe_buddy_add_to_group(sipe_private,
756 buddy,
757 group,
758 g_hash_table_lookup(uri_to_alias,
759 buddy->name));
764 g_hash_table_destroy(uri_to_alias);
766 /* Finished processing contact list */
767 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
768 sipe_buddy_update_finish(sipe_private);
769 sipe_group_update_finish(sipe_private);
770 } else {
771 sipe_buddy_cleanup_local_list(sipe_private);
772 sipe_backend_buddy_list_processing_finish(SIPE_CORE_PUBLIC);
773 sipe_subscribe_presence_initial(sipe_private);
775 } else if (sipe_private->ucs) {
776 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_get_im_item_list_response: query failed, contact list operations will not work!");
777 ucs_init_failure(sipe_private);
781 static void ucs_get_im_item_list(struct sipe_core_private *sipe_private)
783 if (sipe_private->ucs->migrated)
784 sipe_ucs_http_request(sipe_private,
785 /* prioritize over pending default requests */
786 sipe_ucs_transaction(sipe_private),
787 g_strdup("<m:GetImItemList/>"),
788 sipe_ucs_get_im_item_list_response,
789 NULL);
792 static void ucs_set_ews_url(struct sipe_core_private *sipe_private,
793 const gchar *ews_url)
795 struct sipe_ucs *ucs = sipe_private->ucs;
797 SIPE_DEBUG_INFO("ucs_set_ews_url: '%s'", ews_url);
798 ucs->ews_url = g_strdup(ews_url);
800 /* this will trigger sending of the first deferred request */
801 ucs_get_im_item_list(sipe_private);
804 static void ucs_ews_autodiscover_cb(struct sipe_core_private *sipe_private,
805 const struct sipe_ews_autodiscover_data *ews_data,
806 SIPE_UNUSED_PARAMETER gpointer callback_data)
808 struct sipe_ucs *ucs = sipe_private->ucs;
809 const gchar *ews_url = NULL;
811 if (!ucs)
812 return;
814 if (ews_data)
815 ews_url = ews_data->ews_url;
817 if (is_empty(ews_url)) {
818 SIPE_DEBUG_ERROR_NOFORMAT("ucs_ews_autodiscover_cb: can't detect EWS URL, contact list operations will not work!");
819 ucs_init_failure(sipe_private);
820 } else {
821 ucs_set_ews_url(sipe_private, ews_url);
825 gboolean sipe_ucs_is_migrated(struct sipe_core_private *sipe_private)
827 return(sipe_private->ucs ? sipe_private->ucs->migrated : FALSE);
830 const gchar *sipe_ucs_ews_url(struct sipe_core_private *sipe_private)
832 return(sipe_private->ucs ? sipe_private->ucs->ews_url : NULL);
835 void sipe_ucs_init(struct sipe_core_private *sipe_private,
836 gboolean migrated)
838 struct sipe_ucs *ucs;
840 if (sipe_private->ucs) {
841 struct sipe_ucs *ucs = sipe_private->ucs;
844 * contact list update trigger -> request list again
846 * If the trigger arrives less than 10 seconds after our
847 * last UCS response, then ignore it, because it is caused
848 * by our own changes to the contact list.
850 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
851 if ((time(NULL) - ucs->last_response) >= 10)
852 ucs_get_im_item_list(sipe_private);
853 else
854 SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_init: ignoring this contact list update - triggered by our last change");
857 ucs->last_response = 0;
858 return;
861 sipe_private->ucs = ucs = g_new0(struct sipe_ucs, 1);
862 ucs->migrated = migrated;
864 /* create default transaction */
865 sipe_ucs_transaction(sipe_private);
866 ucs->default_transaction = ucs->transactions;
868 if (migrated) {
869 /* user specified a service URL? */
870 const gchar *ews_url = sipe_backend_setting(SIPE_CORE_PUBLIC, SIPE_SETTING_EMAIL_URL);
872 if (is_empty(ews_url))
873 sipe_ews_autodiscover_start(sipe_private,
874 ucs_ews_autodiscover_cb,
875 NULL);
876 else
877 ucs_set_ews_url(sipe_private, ews_url);
881 void sipe_ucs_free(struct sipe_core_private *sipe_private)
883 struct sipe_ucs *ucs = sipe_private->ucs;
884 GSList *entry;
886 if (!ucs)
887 return;
889 /* UCS stack is shutting down: reject all new requests */
890 ucs->shutting_down = TRUE;
892 entry = ucs->transactions;
893 while (entry) {
894 struct sipe_ucs_transaction *trans = entry->data;
895 GSList *entry2 = trans->pending_requests;
897 /* transactions get deleted by sipe_ucs_request_free() */
898 entry = entry->next;
900 while (entry2) {
901 struct ucs_request *request = entry2->data;
903 /* transactions get deleted by sipe_ucs_request_free() */
904 entry2 = entry2->next;
906 sipe_ucs_request_free(sipe_private, request);
910 /* only default transaction is left... */
911 sipe_utils_slist_free_full(ucs->transactions, g_free);
913 g_free(ucs->ews_url);
914 g_free(ucs);
915 sipe_private->ucs = NULL;
919 Local Variables:
920 mode: c
921 c-file-style: "bsd"
922 indent-tabs-mode: t
923 tab-width: 8
924 End: