Release 1.25.0 -- Buddy Idle Time, RTF
[siplcs.git] / src / core / sipe-ucs.c
blob756ea0d636bbb2edab33a58f974a164e3a358cd7
1 /**
2 * @file sipe-ucs.c
4 * pidgin-sipe
6 * Copyright (C) 2013-2018 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 #ifdef HAVE_CONFIG_H
35 #include "config.h"
36 #endif
38 #include <string.h>
40 #include <glib.h>
41 #include <time.h>
43 #include "sipe-backend.h"
44 #include "sipe-buddy.h"
45 #include "sipe-common.h"
46 #include "sipe-core.h"
47 #include "sipe-core-private.h"
48 #include "sipe-ews-autodiscover.h"
49 #include "sipe-group.h"
50 #include "sipe-http.h"
51 #include "sipe-nls.h"
52 #include "sipe-subscriptions.h"
53 #include "sipe-ucs.h"
54 #include "sipe-utils.h"
55 #include "sipe-xml.h"
57 struct sipe_ucs_transaction {
58 GSList *pending_requests;
61 typedef void (ucs_callback)(struct sipe_core_private *sipe_private,
62 struct sipe_ucs_transaction *trans,
63 const sipe_xml *body,
64 gpointer callback_data);
66 struct ucs_request {
67 gchar *body;
68 ucs_callback *cb;
69 gpointer cb_data;
70 struct sipe_ucs_transaction *transaction;
71 struct sipe_http_request *request;
74 struct sipe_ucs {
75 struct ucs_request *active_request;
76 GSList *transactions;
77 GSList *default_transaction;
78 gchar *ews_url;
79 time_t last_response;
80 guint group_id;
81 gboolean migrated;
82 gboolean shutting_down;
85 static void sipe_ucs_request_free(struct sipe_core_private *sipe_private,
86 struct ucs_request *data)
88 struct sipe_ucs *ucs = sipe_private->ucs;
89 struct sipe_ucs_transaction *trans = data->transaction;
91 /* remove request from transaction */
92 trans->pending_requests = g_slist_remove(trans->pending_requests,
93 data);
94 sipe_private->ucs->active_request = NULL;
96 /* remove completed transactions (except default transaction) */
97 if (!trans->pending_requests &&
98 (trans != ucs->default_transaction->data)) {
99 ucs->transactions = g_slist_remove(ucs->transactions,
100 trans);
101 g_free(trans);
104 if (data->request)
105 sipe_http_request_cancel(data->request);
106 if (data->cb)
107 /* Callback: aborted */
108 (*data->cb)(sipe_private, NULL, NULL, data->cb_data);
109 g_free(data->body);
110 g_free(data);
113 static void sipe_ucs_next_request(struct sipe_core_private *sipe_private);
114 static void sipe_ucs_http_response(struct sipe_core_private *sipe_private,
115 guint status,
116 SIPE_UNUSED_PARAMETER GSList *headers,
117 const gchar *body,
118 gpointer callback_data)
120 struct ucs_request *data = callback_data;
122 SIPE_DEBUG_INFO("sipe_ucs_http_response: code %d", status);
123 data->request = NULL;
125 if ((status == SIPE_HTTP_STATUS_OK) && body) {
126 sipe_xml *xml = sipe_xml_parse(body, strlen(body));
127 const sipe_xml *soap_body = sipe_xml_child(xml, "Body");
128 /* Callback: success */
129 (*data->cb)(sipe_private,
130 data->transaction,
131 soap_body,
132 data->cb_data);
133 sipe_xml_free(xml);
134 } else {
135 /* Callback: failed */
136 (*data->cb)(sipe_private, NULL, NULL, data->cb_data);
139 /* already been called */
140 data->cb = NULL;
142 sipe_ucs_request_free(sipe_private, data);
143 sipe_ucs_next_request(sipe_private);
146 static void sipe_ucs_next_request(struct sipe_core_private *sipe_private)
148 struct sipe_ucs *ucs = sipe_private->ucs;
149 struct sipe_ucs_transaction *trans;
151 if (ucs->active_request || ucs->shutting_down || !ucs->ews_url)
152 return;
154 trans = ucs->transactions->data;
155 while (trans->pending_requests) {
156 struct ucs_request *data = trans->pending_requests->data;
157 gchar *soap = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
158 "<soap:Envelope"
159 " xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
160 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
161 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
162 " >"
163 " <soap:Header>"
164 " <t:RequestServerVersion Version=\"Exchange2013\" />"
165 " </soap:Header>"
166 " <soap:Body>"
167 " %s"
168 " </soap:Body>"
169 "</soap:Envelope>",
170 data->body);
171 struct sipe_http_request *request = sipe_http_request_post(sipe_private,
172 ucs->ews_url,
173 NULL,
174 soap,
175 "text/xml; charset=UTF-8",
176 sipe_ucs_http_response,
177 data);
178 g_free(soap);
180 if (request) {
181 g_free(data->body);
182 data->body = NULL;
183 data->request = request;
185 ucs->active_request = data;
187 sipe_core_email_authentication(sipe_private,
188 request);
189 sipe_http_request_allow_redirect(request);
190 sipe_http_request_ready(request);
192 break;
193 } else {
194 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_next_request: failed to create HTTP connection");
195 sipe_ucs_request_free(sipe_private, data);
200 static gboolean sipe_ucs_http_request(struct sipe_core_private *sipe_private,
201 struct sipe_ucs_transaction *trans,
202 gchar *body, /* takes ownership */
203 ucs_callback *callback,
204 gpointer callback_data)
206 struct sipe_ucs *ucs = sipe_private->ucs;
208 if (!ucs || ucs->shutting_down) {
209 SIPE_DEBUG_ERROR("sipe_ucs_http_request: new UCS request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
210 "Body: %s\n",
211 body ? body : "<EMPTY>");
212 g_free(body);
213 return(FALSE);
215 } else {
216 struct ucs_request *data = g_new0(struct ucs_request, 1);
218 data->cb = callback;
219 data->cb_data = callback_data;
220 data->body = body;
222 if (!trans)
223 trans = ucs->default_transaction->data;
224 data->transaction = trans;
225 trans->pending_requests = g_slist_append(trans->pending_requests,
226 data);
228 sipe_ucs_next_request(sipe_private);
229 return(TRUE);
233 struct sipe_ucs_transaction *sipe_ucs_transaction(struct sipe_core_private *sipe_private)
235 struct sipe_ucs *ucs = sipe_private->ucs;
236 struct sipe_ucs_transaction *trans;
238 if (!ucs)
239 return(NULL);
241 /* always insert new transactions before default transaction */
242 trans = g_new0(struct sipe_ucs_transaction, 1);
243 ucs->transactions = g_slist_insert_before(ucs->transactions,
244 ucs->default_transaction,
245 trans);
247 return(trans);
250 static void sipe_ucs_search_response(struct sipe_core_private *sipe_private,
251 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
252 const sipe_xml *body,
253 gpointer callback_data)
255 const sipe_xml *persona_node;
256 struct sipe_backend_search_results *results = NULL;
257 guint match_count = 0;
259 for (persona_node = sipe_xml_child(body,
260 "FindPeopleResponse/People/Persona");
261 persona_node;
262 persona_node = sipe_xml_twin(persona_node)) {
263 const sipe_xml *address = sipe_xml_child(persona_node,
264 "ImAddress");
266 /* only display Persona nodes which have an "ImAddress" node */
267 if (address) {
268 gchar *uri;
269 gchar *displayname;
270 gchar *company;
271 gchar *email;
273 /* OK, we found something - show the results to the user */
274 match_count++;
275 if (!results) {
276 results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
277 callback_data);
278 if (!results) {
279 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_search_response: Unable to display the search results.");
280 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
281 callback_data,
282 _("Unable to display the search results"));
283 return;
287 uri = sipe_xml_data(address);
288 displayname = sipe_xml_data(sipe_xml_child(persona_node,
289 "DisplayName"));
290 company = sipe_xml_data(sipe_xml_child(persona_node,
291 "CompanyName"));
292 email = sipe_xml_data(sipe_xml_child(persona_node,
293 "EmailAddress/EmailAddress"));
295 sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
296 results,
297 sipe_get_no_sip_uri(uri),
298 displayname,
299 company,
300 NULL,
301 email);
303 g_free(email);
304 g_free(company);
305 g_free(displayname);
306 g_free(uri);
310 if (match_count > 0)
311 sipe_buddy_search_contacts_finalize(sipe_private,
312 results,
313 match_count,
314 FALSE);
315 else
316 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
317 callback_data,
318 _("No contacts found"));
321 void sipe_ucs_search(struct sipe_core_private *sipe_private,
322 struct sipe_backend_search_token *token,
323 const gchar *given_name,
324 const gchar *surname,
325 const gchar *email,
326 const gchar *sipid,
327 const gchar *company,
328 const gchar *country)
330 guint count = 0;
331 GString *query = g_string_new(NULL);
334 * Search GAL for matching entries
336 * QueryString should support field properties and quoting ("")
337 * according to the specification. But in my trials I couldn't get
338 * them to work. Concatenate all query words to a single string.
339 * Only items that match ALL words will be returned by this query.
341 #define ADD_QUERY_VALUE(val) \
342 if (val) { \
343 if (count++) \
344 g_string_append_c(query, ' '); \
345 g_string_append(query, val); \
348 ADD_QUERY_VALUE(given_name);
349 ADD_QUERY_VALUE(surname);
350 ADD_QUERY_VALUE(email);
351 ADD_QUERY_VALUE(sipid);
352 ADD_QUERY_VALUE(company);
353 ADD_QUERY_VALUE(country);
355 if (count > 0) {
356 gchar *body = g_markup_printf_escaped("<m:FindPeople>"
357 " <m:PersonaShape>"
358 " <t:BaseShape>IdOnly</t:BaseShape>"
359 " <t:AdditionalProperties>"
360 " <t:FieldURI FieldURI=\"persona:CompanyName\"/>"
361 " <t:FieldURI FieldURI=\"persona:DisplayName\"/>"
362 " <t:FieldURI FieldURI=\"persona:EmailAddress\"/>"
363 " <t:FieldURI FieldURI=\"persona:ImAddress\"/>"
364 /* Locations doesn't seem to work
365 " <t:FieldURI FieldURI=\"persona:Locations\"/>"
367 " </t:AdditionalProperties>"
368 " </m:PersonaShape>"
369 " <m:IndexedPageItemView BasePoint=\"Beginning\" MaxEntriesReturned=\"100\" Offset=\"0\"/>"
371 * I have no idea why Exchnage doesn't accept this
372 * FieldURI for restrictions. Without it the search
373 * will return users that don't have an ImAddress
374 * and we need to filter them out ourselves :-(
375 " <m:Restriction>"
376 " <t:Exists>"
377 " <t:FieldURI FieldURI=\"persona:ImAddress\"/>"
378 " </t:Exists>"
379 " </m:Restriction>"
381 " <m:ParentFolderId>"
382 " <t:DistinguishedFolderId Id=\"directory\"/>"
383 " </m:ParentFolderId>"
384 " <m:QueryString>%s</m:QueryString>"
385 "</m:FindPeople>",
386 query->str);
388 if (!sipe_ucs_http_request(sipe_private,
389 NULL,
390 body,
391 sipe_ucs_search_response,
392 token))
393 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
394 token,
395 _("Contact search failed"));
396 } else
397 sipe_backend_search_failed(SIPE_CORE_PUBLIC,
398 token,
399 _("Invalid contact search query"));
401 g_string_free(query, TRUE);
404 static void sipe_ucs_ignore_response(struct sipe_core_private *sipe_private,
405 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
406 SIPE_UNUSED_PARAMETER const sipe_xml *body,
407 SIPE_UNUSED_PARAMETER gpointer callback_data)
409 SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_ignore_response: done");
410 sipe_private->ucs->last_response = time(NULL);
413 static void ucs_extract_keys(const sipe_xml *persona_node,
414 const gchar **key,
415 const gchar **change)
417 const sipe_xml *attr_node;
420 * extract Exchange key - play the guessing game :-(
422 * We can't use the "DisplayName" node, because the text is localized.
424 * Assume that IsQuickContact == "true" and IsHidden == "false" means
425 * this Attribution node contains the information for the Lync contact.
427 for (attr_node = sipe_xml_child(persona_node,
428 "Attributions/Attribution");
429 attr_node;
430 attr_node = sipe_xml_twin(attr_node)) {
431 const sipe_xml *id_node = sipe_xml_child(attr_node,
432 "SourceId");
433 gchar *hidden = sipe_xml_data(sipe_xml_child(attr_node,
434 "IsHidden"));
435 gchar *quick = sipe_xml_data(sipe_xml_child(attr_node,
436 "IsQuickContact"));
437 if (id_node &&
438 sipe_strcase_equal(hidden, "false") &&
439 sipe_strcase_equal(quick, "true")) {
440 *key = sipe_xml_attribute(id_node, "Id");
441 *change = sipe_xml_attribute(id_node, "ChangeKey");
442 g_free(quick);
443 g_free(hidden);
444 break;
446 g_free(quick);
447 g_free(hidden);
451 static void sipe_ucs_add_new_im_contact_to_group_response(struct sipe_core_private *sipe_private,
452 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
453 const sipe_xml *body,
454 gpointer callback_data)
456 gchar *who = callback_data;
457 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private, who);
458 const sipe_xml *persona_node = sipe_xml_child(body,
459 "AddNewImContactToGroupResponse/Persona");
461 sipe_private->ucs->last_response = time(NULL);
463 if (persona_node &&
464 buddy &&
465 is_empty(buddy->exchange_key) &&
466 is_empty(buddy->change_key)) {
467 const gchar *key = NULL;
468 const gchar *change = NULL;
470 ucs_extract_keys(persona_node, &key, &change);
472 if (!is_empty(key) && !is_empty(change)) {
474 sipe_buddy_add_keys(sipe_private,
475 buddy,
476 key,
477 change);
479 SIPE_DEBUG_INFO("sipe_ucs_add_new_im_contact_to_group_response: persona URI '%s' key '%s' change '%s'",
480 buddy->name, key, change);
484 g_free(who);
487 void sipe_ucs_group_add_buddy(struct sipe_core_private *sipe_private,
488 struct sipe_ucs_transaction *trans,
489 struct sipe_group *group,
490 struct sipe_buddy *buddy,
491 const gchar *who)
493 /* existing or new buddy? */
494 if (buddy && buddy->exchange_key) {
495 gchar *body = g_strdup_printf("<m:AddImContactToGroup>"
496 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
497 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
498 "</m:AddImContactToGroup>",
499 buddy->exchange_key,
500 buddy->change_key,
501 group->exchange_key,
502 group->change_key);
504 sipe_ucs_http_request(sipe_private,
505 trans,
506 body,
507 sipe_ucs_ignore_response,
508 NULL);
509 } else {
510 gchar *payload = g_strdup(who);
511 gchar *body = g_strdup_printf("<m:AddNewImContactToGroup>"
512 " <m:ImAddress>%s</m:ImAddress>"
513 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
514 "</m:AddNewImContactToGroup>",
515 sipe_get_no_sip_uri(who),
516 group->exchange_key,
517 group->change_key);
519 if (!sipe_ucs_http_request(sipe_private,
520 trans,
521 body,
522 sipe_ucs_add_new_im_contact_to_group_response,
523 payload))
524 g_free(payload);
528 void sipe_ucs_group_remove_buddy(struct sipe_core_private *sipe_private,
529 struct sipe_ucs_transaction *trans,
530 struct sipe_group *group,
531 struct sipe_buddy *buddy)
533 if (group) {
535 * If a contact is removed from last group, it will also be
536 * removed from contact list completely. The documentation has
537 * a RemoveContactFromImList operation, but that doesn't seem
538 * to work at all, i.e. it is always rejected by the server.
540 gchar *body = g_strdup_printf("<m:RemoveImContactFromGroup>"
541 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
542 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
543 "</m:RemoveImContactFromGroup>",
544 buddy->exchange_key,
545 buddy->change_key,
546 group->exchange_key,
547 group->change_key);
549 sipe_ucs_http_request(sipe_private,
550 trans,
551 body,
552 sipe_ucs_ignore_response,
553 NULL);
557 static struct sipe_group *ucs_create_group(struct sipe_core_private *sipe_private,
558 const sipe_xml *group_node)
560 const sipe_xml *id_node = sipe_xml_child(group_node,
561 "ExchangeStoreId");
562 const gchar *key = sipe_xml_attribute(id_node, "Id");
563 const gchar *change = sipe_xml_attribute(id_node, "ChangeKey");
564 struct sipe_group *group = NULL;
566 if (!(is_empty(key) || is_empty(change))) {
567 gchar *name = sipe_xml_data(sipe_xml_child(group_node,
568 "DisplayName"));
569 group = sipe_group_add(sipe_private,
570 name,
571 key,
572 change,
573 /* sipe_group must have unique ID */
574 ++sipe_private->ucs->group_id);
575 g_free(name);
578 return(group);
581 static void sipe_ucs_add_im_group_response(struct sipe_core_private *sipe_private,
582 struct sipe_ucs_transaction *trans,
583 const sipe_xml *body,
584 gpointer callback_data)
586 gchar *who = callback_data;
587 const sipe_xml *group_node = sipe_xml_child(body,
588 "AddImGroupResponse/ImGroup");
589 struct sipe_group *group = ucs_create_group(sipe_private, group_node);
591 sipe_private->ucs->last_response = time(NULL);
593 if (group) {
594 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
595 who);
597 if (buddy)
598 sipe_buddy_insert_group(buddy, group);
600 sipe_ucs_group_add_buddy(sipe_private,
601 trans,
602 group,
603 buddy,
604 who);
607 g_free(who);
610 void sipe_ucs_group_create(struct sipe_core_private *sipe_private,
611 struct sipe_ucs_transaction *trans,
612 const gchar *name,
613 const gchar *who)
615 gchar *payload = g_strdup(who);
616 /* new_name can contain restricted characters */
617 gchar *body = g_markup_printf_escaped("<m:AddImGroup>"
618 " <m:DisplayName>%s</m:DisplayName>"
619 "</m:AddImGroup>",
620 name);
622 if (!sipe_ucs_http_request(sipe_private,
623 trans,
624 body,
625 sipe_ucs_add_im_group_response,
626 payload))
627 g_free(payload);
630 void sipe_ucs_group_rename(struct sipe_core_private *sipe_private,
631 struct sipe_group *group,
632 const gchar *new_name)
634 /* new_name can contain restricted characters */
635 gchar *body = g_markup_printf_escaped("<m:SetImGroup>"
636 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
637 " <m:NewDisplayName>%s</m:NewDisplayName>"
638 "</m:SetImGroup>",
639 group->exchange_key,
640 group->change_key,
641 new_name);
643 sipe_ucs_http_request(sipe_private,
644 NULL,
645 body,
646 sipe_ucs_ignore_response,
647 NULL);
650 void sipe_ucs_group_remove(struct sipe_core_private *sipe_private,
651 struct sipe_group *group)
653 gchar *body = g_strdup_printf("<m:RemoveImGroup>"
654 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
655 "</m:RemoveImGroup>",
656 group->exchange_key,
657 group->change_key);
659 sipe_ucs_http_request(sipe_private,
660 NULL,
661 body,
662 sipe_ucs_ignore_response,
663 NULL);
666 static void ucs_init_failure(struct sipe_core_private *sipe_private)
668 /* Did the user specify any email settings? */
669 gboolean default_settings =
670 is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
671 SIPE_SETTING_EMAIL_URL)) &&
672 is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
673 SIPE_SETTING_EMAIL_LOGIN)) &&
674 is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
675 SIPE_SETTING_EMAIL_PASSWORD));
677 sipe_backend_notify_error(SIPE_CORE_PUBLIC,
678 _("UCS initialization failed!"),
679 default_settings ?
680 _("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.") :
681 _("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."));
684 static void sipe_ucs_get_im_item_list_response(struct sipe_core_private *sipe_private,
685 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
686 const sipe_xml *body,
687 SIPE_UNUSED_PARAMETER gpointer callback_data)
689 const sipe_xml *node = sipe_xml_child(body,
690 "GetImItemListResponse/ImItemList");
692 if (node) {
693 const sipe_xml *persona_node;
694 const sipe_xml *group_node;
695 GHashTable *uri_to_alias = g_hash_table_new_full(g_str_hash,
696 g_str_equal,
697 NULL,
698 g_free);
700 /* Start processing contact list */
701 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
702 sipe_group_update_start(sipe_private);
703 sipe_buddy_update_start(sipe_private);
704 } else
705 sipe_backend_buddy_list_processing_start(SIPE_CORE_PUBLIC);
707 for (persona_node = sipe_xml_child(node, "Personas/Persona");
708 persona_node;
709 persona_node = sipe_xml_twin(persona_node)) {
710 gchar *address = sipe_xml_data(sipe_xml_child(persona_node,
711 "ImAddress"));
712 const gchar *key = NULL;
713 const gchar *change = NULL;
715 ucs_extract_keys(persona_node, &key, &change);
717 if (!(is_empty(address) || is_empty(key) || is_empty(change))) {
718 gchar *alias = sipe_xml_data(sipe_xml_child(persona_node,
719 "DisplayName"));
721 * it seems to be undefined if ImAddress node
722 * contains "sip:" prefix or not...
724 gchar *uri = sip_uri(address);
725 struct sipe_buddy *buddy = sipe_buddy_add(sipe_private,
726 uri,
727 key,
728 change);
729 g_free(uri);
731 /* hash table takes ownership of alias */
732 g_hash_table_insert(uri_to_alias,
733 buddy->name,
734 alias);
736 SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: persona URI '%s' key '%s' change '%s'",
737 buddy->name, key, change);
739 g_free(address);
742 for (group_node = sipe_xml_child(node, "Groups/ImGroup");
743 group_node;
744 group_node = sipe_xml_twin(group_node)) {
745 struct sipe_group *group = ucs_create_group(sipe_private,
746 group_node);
748 if (group) {
749 const sipe_xml *member_node;
751 for (member_node = sipe_xml_child(group_node,
752 "MemberCorrelationKey/ItemId");
753 member_node;
754 member_node = sipe_xml_twin(member_node)) {
755 struct sipe_buddy *buddy = sipe_buddy_find_by_exchange_key(sipe_private,
756 sipe_xml_attribute(member_node,
757 "Id"));
758 if (buddy)
759 sipe_buddy_add_to_group(sipe_private,
760 buddy,
761 group,
762 g_hash_table_lookup(uri_to_alias,
763 buddy->name));
768 g_hash_table_destroy(uri_to_alias);
770 /* Finished processing contact list */
771 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
772 sipe_buddy_update_finish(sipe_private);
773 sipe_group_update_finish(sipe_private);
774 } else {
775 sipe_buddy_cleanup_local_list(sipe_private);
776 sipe_backend_buddy_list_processing_finish(SIPE_CORE_PUBLIC);
777 sipe_subscribe_presence_initial(sipe_private);
779 } else if (sipe_private->ucs) {
780 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_get_im_item_list_response: query failed, contact list operations will not work!");
781 ucs_init_failure(sipe_private);
785 static void ucs_get_im_item_list(struct sipe_core_private *sipe_private)
787 if (sipe_private->ucs->migrated)
788 sipe_ucs_http_request(sipe_private,
789 /* prioritize over pending default requests */
790 sipe_ucs_transaction(sipe_private),
791 g_strdup("<m:GetImItemList/>"),
792 sipe_ucs_get_im_item_list_response,
793 NULL);
796 static void ucs_set_ews_url(struct sipe_core_private *sipe_private,
797 const gchar *ews_url)
799 struct sipe_ucs *ucs = sipe_private->ucs;
801 SIPE_DEBUG_INFO("ucs_set_ews_url: '%s'", ews_url);
802 ucs->ews_url = g_strdup(ews_url);
804 /* this will trigger sending of the first deferred request */
805 ucs_get_im_item_list(sipe_private);
808 static void ucs_ews_autodiscover_cb(struct sipe_core_private *sipe_private,
809 const struct sipe_ews_autodiscover_data *ews_data,
810 SIPE_UNUSED_PARAMETER gpointer callback_data)
812 struct sipe_ucs *ucs = sipe_private->ucs;
813 const gchar *ews_url = NULL;
815 if (!ucs)
816 return;
818 if (ews_data)
819 ews_url = ews_data->ews_url;
821 if (is_empty(ews_url)) {
822 SIPE_DEBUG_ERROR_NOFORMAT("ucs_ews_autodiscover_cb: can't detect EWS URL, contact list operations will not work!");
823 ucs_init_failure(sipe_private);
824 } else {
825 ucs_set_ews_url(sipe_private, ews_url);
829 gboolean sipe_ucs_is_migrated(struct sipe_core_private *sipe_private)
831 return(sipe_private->ucs ? sipe_private->ucs->migrated : FALSE);
834 const gchar *sipe_ucs_ews_url(struct sipe_core_private *sipe_private)
836 return(sipe_private->ucs ? sipe_private->ucs->ews_url : NULL);
839 void sipe_ucs_init(struct sipe_core_private *sipe_private,
840 gboolean migrated)
842 struct sipe_ucs *ucs;
844 if (sipe_private->ucs) {
845 struct sipe_ucs *ucs = sipe_private->ucs;
848 * contact list update trigger -> request list again
850 * If the trigger arrives less than 10 seconds after our
851 * last UCS response, then ignore it, because it is caused
852 * by our own changes to the contact list.
854 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
855 if ((time(NULL) - ucs->last_response) >= 10)
856 ucs_get_im_item_list(sipe_private);
857 else
858 SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_init: ignoring this contact list update - triggered by our last change");
861 ucs->last_response = 0;
862 return;
865 sipe_private->ucs = ucs = g_new0(struct sipe_ucs, 1);
866 ucs->migrated = migrated;
868 /* create default transaction */
869 sipe_ucs_transaction(sipe_private);
870 ucs->default_transaction = ucs->transactions;
872 if (migrated) {
873 /* user specified a service URL? */
874 const gchar *ews_url = sipe_backend_setting(SIPE_CORE_PUBLIC, SIPE_SETTING_EMAIL_URL);
876 if (is_empty(ews_url))
877 sipe_ews_autodiscover_start(sipe_private,
878 ucs_ews_autodiscover_cb,
879 NULL);
880 else
881 ucs_set_ews_url(sipe_private, ews_url);
885 void sipe_ucs_free(struct sipe_core_private *sipe_private)
887 struct sipe_ucs *ucs = sipe_private->ucs;
888 GSList *entry;
890 if (!ucs)
891 return;
893 /* UCS stack is shutting down: reject all new requests */
894 ucs->shutting_down = TRUE;
896 entry = ucs->transactions;
897 while (entry) {
898 struct sipe_ucs_transaction *trans = entry->data;
899 GSList *entry2 = trans->pending_requests;
901 /* transactions get deleted by sipe_ucs_request_free() */
902 entry = entry->next;
904 while (entry2) {
905 struct ucs_request *request = entry2->data;
907 /* transactions get deleted by sipe_ucs_request_free() */
908 entry2 = entry2->next;
910 sipe_ucs_request_free(sipe_private, request);
914 /* only default transaction is left... */
915 sipe_utils_slist_free_full(ucs->transactions, g_free);
917 g_free(ucs->ews_url);
918 g_free(ucs);
919 sipe_private->ucs = NULL;
923 Local Variables:
924 mode: c
925 c-file-style: "bsd"
926 indent-tabs-mode: t
927 tab-width: 8
928 End: