faacd729dab8577f036be80cc570c3f1c30b03f0
[siplcs.git] / src / core / sipe-ucs.c
blobfaacd729dab8577f036be80cc570c3f1c30b03f0
1 /**
2 * @file sipe-ucs.c
4 * pidgin-sipe
6 * Copyright (C) 2013 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>
28 #include <string.h>
30 #include <glib.h>
31 #include <time.h>
33 #include "sipe-backend.h"
34 #include "sipe-buddy.h"
35 #include "sipe-common.h"
36 #include "sipe-core.h"
37 #include "sipe-core-private.h"
38 #include "sipe-digest.h"
39 #include "sipe-ews-autodiscover.h"
40 #include "sipe-group.h"
41 #include "sipe-http.h"
42 #include "sipe-subscriptions.h"
43 #include "sipe-ucs.h"
44 #include "sipe-utils.h"
45 #include "sipe-xml.h"
47 struct sipe_ucs_transaction {
48 GSList *pending_requests;
51 typedef void (ucs_callback)(struct sipe_core_private *sipe_private,
52 struct sipe_ucs_transaction *trans,
53 const sipe_xml *body,
54 gpointer callback_data);
56 struct ucs_request {
57 gchar *body;
58 ucs_callback *cb;
59 gpointer cb_data;
60 struct sipe_ucs_transaction *transaction;
61 struct sipe_http_request *request;
64 struct sipe_ucs {
65 struct ucs_request *active_request;
66 GSList *transactions;
67 GSList *default_transaction;
68 gchar *ews_url;
69 time_t last_response;
70 guint group_id;
71 gboolean migrated;
72 gboolean shutting_down;
75 static void sipe_ucs_request_free(struct sipe_core_private *sipe_private,
76 struct ucs_request *data)
78 struct sipe_ucs *ucs = sipe_private->ucs;
79 struct sipe_ucs_transaction *trans = data->transaction;
81 /* remove request from transaction */
82 trans->pending_requests = g_slist_remove(trans->pending_requests,
83 data);
84 sipe_private->ucs->active_request = NULL;
86 /* remove completed transactions (except default transaction) */
87 if (!trans->pending_requests &&
88 (trans != ucs->default_transaction->data)) {
89 ucs->transactions = g_slist_remove(ucs->transactions,
90 trans);
91 g_free(trans);
94 if (data->request)
95 sipe_http_request_cancel(data->request);
96 if (data->cb)
97 /* Callback: aborted */
98 (*data->cb)(sipe_private, NULL, NULL, data->cb_data);
99 g_free(data->body);
100 g_free(data);
103 static void sipe_ucs_next_request(struct sipe_core_private *sipe_private);
104 static void sipe_ucs_http_response(struct sipe_core_private *sipe_private,
105 guint status,
106 SIPE_UNUSED_PARAMETER GSList *headers,
107 const gchar *body,
108 gpointer callback_data)
110 struct ucs_request *data = callback_data;
112 SIPE_DEBUG_INFO("sipe_ucs_http_response: code %d", status);
113 data->request = NULL;
115 if ((status == SIPE_HTTP_STATUS_OK) && body) {
116 sipe_xml *xml = sipe_xml_parse(body, strlen(body));
117 const sipe_xml *soap_body = sipe_xml_child(xml, "Body");
118 /* Callback: success */
119 (*data->cb)(sipe_private,
120 data->transaction,
121 soap_body,
122 data->cb_data);
123 sipe_xml_free(xml);
124 } else {
125 /* Callback: failed */
126 (*data->cb)(sipe_private, NULL, NULL, data->cb_data);
129 /* already been called */
130 data->cb = NULL;
132 sipe_ucs_request_free(sipe_private, data);
133 sipe_ucs_next_request(sipe_private);
136 static void sipe_ucs_next_request(struct sipe_core_private *sipe_private)
138 struct sipe_ucs *ucs = sipe_private->ucs;
139 struct sipe_ucs_transaction *trans;
141 if (ucs->active_request || ucs->shutting_down || !ucs->ews_url)
142 return;
144 trans = ucs->transactions->data;
145 while (trans->pending_requests) {
146 struct ucs_request *data = trans->pending_requests->data;
147 gchar *soap = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
148 "<soap:Envelope"
149 " xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
150 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
151 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
152 " >"
153 " <soap:Header>"
154 " <t:RequestServerVersion Version=\"Exchange2013\" />"
155 " </soap:Header>"
156 " <soap:Body>"
157 " %s"
158 " </soap:Body>"
159 "</soap:Envelope>",
160 data->body);
161 struct sipe_http_request *request = sipe_http_request_post(sipe_private,
162 ucs->ews_url,
163 NULL,
164 soap,
165 "text/xml; charset=UTF-8",
166 sipe_ucs_http_response,
167 data);
168 g_free(soap);
170 if (request) {
171 g_free(data->body);
172 data->body = NULL;
173 data->request = request;
175 ucs->active_request = data;
177 sipe_core_email_authentication(sipe_private,
178 request);
179 sipe_http_request_allow_redirect(request);
180 sipe_http_request_ready(request);
182 break;
183 } else {
184 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_next_request: failed to create HTTP connection");
185 sipe_ucs_request_free(sipe_private, data);
190 static gboolean sipe_ucs_http_request(struct sipe_core_private *sipe_private,
191 struct sipe_ucs_transaction *trans,
192 gchar *body, /* takes ownership */
193 ucs_callback *callback,
194 gpointer callback_data)
196 struct sipe_ucs *ucs = sipe_private->ucs;
198 if (!ucs || ucs->shutting_down) {
199 SIPE_DEBUG_ERROR("sipe_ucs_http_request: new UCS request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
200 "Body: %s\n",
201 body ? body : "<EMPTY>");
202 g_free(body);
203 return(FALSE);
205 } else {
206 struct ucs_request *data = g_new0(struct ucs_request, 1);
208 data->cb = callback;
209 data->cb_data = callback_data;
210 data->body = body;
212 if (!trans)
213 trans = ucs->default_transaction->data;
214 data->transaction = trans;
215 trans->pending_requests = g_slist_append(trans->pending_requests,
216 data);
218 sipe_ucs_next_request(sipe_private);
219 return(TRUE);
223 struct sipe_ucs_transaction *sipe_ucs_transaction(struct sipe_core_private *sipe_private)
225 struct sipe_ucs *ucs = sipe_private->ucs;
226 struct sipe_ucs_transaction *trans;
228 if (!ucs)
229 return(NULL);
231 /* always insert new transactions before default transaction */
232 trans = g_new0(struct sipe_ucs_transaction, 1);
233 ucs->transactions = g_slist_insert_before(ucs->transactions,
234 ucs->default_transaction,
235 trans);
237 return(trans);
240 static void sipe_ucs_get_user_photo_response(struct sipe_core_private *sipe_private,
241 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
242 const sipe_xml *body,
243 gpointer callback_data)
245 gchar *uri = callback_data;
246 const sipe_xml *node = sipe_xml_child(body,
247 "GetUserPhotoResponse/PictureData");
249 if (node) {
250 gchar *base64;
251 gsize photo_size;
252 guchar *photo;
253 guchar digest[SIPE_DIGEST_SHA1_LENGTH];
254 gchar *digest_string;
256 /* decode photo data */
257 base64 = sipe_xml_data(node);
258 photo = g_base64_decode(base64, &photo_size);
259 g_free(base64);
261 /* EWS doesn't provide a hash -> calculate SHA-1 digest */
262 sipe_digest_sha1(photo, photo_size, digest);
263 digest_string = buff_to_hex_str(digest,
264 SIPE_DIGEST_SHA1_LENGTH);
266 /* backend frees "photo" */
267 sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC,
268 uri,
269 photo,
270 photo_size,
271 digest_string);
272 g_free(digest_string);
275 g_free(uri);
278 void sipe_ucs_get_photo(struct sipe_core_private *sipe_private,
279 const gchar *uri)
281 gchar *payload = g_strdup(uri);
282 gchar *body = g_strdup_printf("<m:GetUserPhoto>"
283 " <m:Email>%s</m:Email>"
284 " <m:SizeRequested>HR48x48</m:SizeRequested>"
285 "</m:GetUserPhoto>",
286 sipe_get_no_sip_uri(uri));
288 if (!sipe_ucs_http_request(sipe_private,
289 NULL,
290 body,
291 sipe_ucs_get_user_photo_response,
292 payload))
293 g_free(payload);
296 static void sipe_ucs_ignore_response(struct sipe_core_private *sipe_private,
297 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
298 SIPE_UNUSED_PARAMETER const sipe_xml *body,
299 SIPE_UNUSED_PARAMETER gpointer callback_data)
301 SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_ignore_response: done");
302 sipe_private->ucs->last_response = time(NULL);
305 static void ucs_extract_keys(const sipe_xml *persona_node,
306 const gchar **key,
307 const gchar **change)
309 const sipe_xml *attr_node;
312 * extract Exchange key - play the guessing game :-(
314 * We can't use the "DisplayName" node, because the text is localized.
316 * Assume that IsQuickContact == "true" and IsHidden == "false" means
317 * this Attribution node contains the information for the Lync contact.
319 for (attr_node = sipe_xml_child(persona_node,
320 "Attributions/Attribution");
321 attr_node;
322 attr_node = sipe_xml_twin(attr_node)) {
323 const sipe_xml *id_node = sipe_xml_child(attr_node,
324 "SourceId");
325 gchar *hidden = sipe_xml_data(sipe_xml_child(attr_node,
326 "IsHidden"));
327 gchar *quick = sipe_xml_data(sipe_xml_child(attr_node,
328 "IsQuickContact"));
329 if (id_node &&
330 sipe_strcase_equal(hidden, "false") &&
331 sipe_strcase_equal(quick, "true")) {
332 *key = sipe_xml_attribute(id_node, "Id");
333 *change = sipe_xml_attribute(id_node, "ChangeKey");
334 g_free(quick);
335 g_free(hidden);
336 break;
338 g_free(quick);
339 g_free(hidden);
343 static void sipe_ucs_add_new_im_contact_to_group_response(struct sipe_core_private *sipe_private,
344 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
345 const sipe_xml *body,
346 gpointer callback_data)
348 gchar *who = callback_data;
349 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private, who);
350 const sipe_xml *persona_node = sipe_xml_child(body,
351 "AddNewImContactToGroupResponse/Persona");
353 sipe_private->ucs->last_response = time(NULL);
355 if (persona_node &&
356 buddy &&
357 is_empty(buddy->exchange_key) &&
358 is_empty(buddy->change_key)) {
359 const gchar *key = NULL;
360 const gchar *change = NULL;
362 ucs_extract_keys(persona_node, &key, &change);
364 if (!is_empty(key) && !is_empty(change)) {
366 sipe_buddy_add_keys(sipe_private,
367 buddy,
368 key,
369 change);
371 SIPE_DEBUG_INFO("sipe_ucs_add_new_im_contact_to_group_response: persona URI '%s' key '%s' change '%s'",
372 buddy->name, key, change);
376 g_free(who);
379 void sipe_ucs_group_add_buddy(struct sipe_core_private *sipe_private,
380 struct sipe_ucs_transaction *trans,
381 struct sipe_group *group,
382 struct sipe_buddy *buddy,
383 const gchar *who)
385 /* existing or new buddy? */
386 if (buddy && buddy->exchange_key) {
387 gchar *body = g_strdup_printf("<m:AddImContactToGroup>"
388 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
389 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
390 "</m:AddImContactToGroup>",
391 buddy->exchange_key,
392 buddy->change_key,
393 group->exchange_key,
394 group->change_key);
396 sipe_ucs_http_request(sipe_private,
397 trans,
398 body,
399 sipe_ucs_ignore_response,
400 NULL);
401 } else {
402 gchar *payload = g_strdup(who);
403 gchar *body = g_strdup_printf("<m:AddNewImContactToGroup>"
404 " <m:ImAddress>%s</m:ImAddress>"
405 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
406 "</m:AddNewImContactToGroup>",
407 sipe_get_no_sip_uri(who),
408 group->exchange_key,
409 group->change_key);
411 if (!sipe_ucs_http_request(sipe_private,
412 trans,
413 body,
414 sipe_ucs_add_new_im_contact_to_group_response,
415 payload))
416 g_free(payload);
420 void sipe_ucs_group_remove_buddy(struct sipe_core_private *sipe_private,
421 struct sipe_ucs_transaction *trans,
422 struct sipe_group *group,
423 struct sipe_buddy *buddy)
425 if (group) {
427 * If a contact is removed from last group, it will also be
428 * removed from contact list completely. The documentation has
429 * a RemoveContactFromImList operation, but that doesn't seem
430 * to work at all, i.e. it is always rejected by the server.
432 gchar *body = g_strdup_printf("<m:RemoveImContactFromGroup>"
433 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
434 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
435 "</m:RemoveImContactFromGroup>",
436 buddy->exchange_key,
437 buddy->change_key,
438 group->exchange_key,
439 group->change_key);
441 sipe_ucs_http_request(sipe_private,
442 trans,
443 body,
444 sipe_ucs_ignore_response,
445 NULL);
449 static struct sipe_group *ucs_create_group(struct sipe_core_private *sipe_private,
450 const sipe_xml *group_node)
452 const sipe_xml *id_node = sipe_xml_child(group_node,
453 "ExchangeStoreId");
454 const gchar *key = sipe_xml_attribute(id_node, "Id");
455 const gchar *change = sipe_xml_attribute(id_node, "ChangeKey");
456 struct sipe_group *group = NULL;
458 if (!(is_empty(key) || is_empty(change))) {
459 gchar *name = sipe_xml_data(sipe_xml_child(group_node,
460 "DisplayName"));
461 group = sipe_group_add(sipe_private,
462 name,
463 key,
464 change,
465 /* sipe_group must have unique ID */
466 ++sipe_private->ucs->group_id);
467 g_free(name);
470 return(group);
473 static void sipe_ucs_add_im_group_response(struct sipe_core_private *sipe_private,
474 struct sipe_ucs_transaction *trans,
475 const sipe_xml *body,
476 gpointer callback_data)
478 gchar *who = callback_data;
479 const sipe_xml *group_node = sipe_xml_child(body,
480 "AddImGroupResponse/ImGroup");
481 struct sipe_group *group = ucs_create_group(sipe_private, group_node);
483 sipe_private->ucs->last_response = time(NULL);
485 if (group) {
486 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
487 who);
489 if (buddy)
490 sipe_buddy_insert_group(buddy, group);
492 sipe_ucs_group_add_buddy(sipe_private,
493 trans,
494 group,
495 buddy,
496 who);
499 g_free(who);
502 void sipe_ucs_group_create(struct sipe_core_private *sipe_private,
503 struct sipe_ucs_transaction *trans,
504 const gchar *name,
505 const gchar *who)
507 gchar *payload = g_strdup(who);
508 /* new_name can contain restricted characters */
509 gchar *body = g_markup_printf_escaped("<m:AddImGroup>"
510 " <m:DisplayName>%s</m:DisplayName>"
511 "</m:AddImGroup>",
512 name);
514 if (!sipe_ucs_http_request(sipe_private,
515 trans,
516 body,
517 sipe_ucs_add_im_group_response,
518 payload))
519 g_free(payload);
522 void sipe_ucs_group_rename(struct sipe_core_private *sipe_private,
523 struct sipe_group *group,
524 const gchar *new_name)
526 /* new_name can contain restricted characters */
527 gchar *body = g_markup_printf_escaped("<m:SetImGroup>"
528 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
529 " <m:NewDisplayName>%s</m:NewDisplayName>"
530 "</m:SetImGroup>",
531 group->exchange_key,
532 group->change_key,
533 new_name);
535 sipe_ucs_http_request(sipe_private,
536 NULL,
537 body,
538 sipe_ucs_ignore_response,
539 NULL);
542 void sipe_ucs_group_remove(struct sipe_core_private *sipe_private,
543 struct sipe_group *group)
545 gchar *body = g_strdup_printf("<m:RemoveImGroup>"
546 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
547 "</m:RemoveImGroup>",
548 group->exchange_key,
549 group->change_key);
551 sipe_ucs_http_request(sipe_private,
552 NULL,
553 body,
554 sipe_ucs_ignore_response,
555 NULL);
558 static void sipe_ucs_get_im_item_list_response(struct sipe_core_private *sipe_private,
559 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
560 const sipe_xml *body,
561 SIPE_UNUSED_PARAMETER gpointer callback_data)
563 const sipe_xml *node = sipe_xml_child(body,
564 "GetImItemListResponse/ImItemList");
566 if (node) {
567 const sipe_xml *persona_node;
568 const sipe_xml *group_node;
569 GHashTable *uri_to_alias = g_hash_table_new_full(g_str_hash,
570 g_str_equal,
571 NULL,
572 g_free);
574 /* Start processing contact list */
575 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
576 sipe_group_update_start(sipe_private);
577 sipe_buddy_update_start(sipe_private);
578 } else
579 sipe_backend_buddy_list_processing_start(SIPE_CORE_PUBLIC);
581 for (persona_node = sipe_xml_child(node, "Personas/Persona");
582 persona_node;
583 persona_node = sipe_xml_twin(persona_node)) {
584 gchar *address = sipe_xml_data(sipe_xml_child(persona_node,
585 "ImAddress"));
586 const gchar *key = NULL;
587 const gchar *change = NULL;
589 ucs_extract_keys(persona_node, &key, &change);
591 if (!(is_empty(address) || is_empty(key) || is_empty(change))) {
592 gchar *alias = sipe_xml_data(sipe_xml_child(persona_node,
593 "DisplayName"));
594 gchar *uri = sip_uri_from_name(address);
595 struct sipe_buddy *buddy = sipe_buddy_add(sipe_private,
596 uri,
597 key,
598 change);
599 g_free(uri);
601 /* hash table takes ownership of alias */
602 g_hash_table_insert(uri_to_alias,
603 buddy->name,
604 alias);
606 SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: persona URI '%s' key '%s' change '%s'",
607 buddy->name, key, change);
609 g_free(address);
612 for (group_node = sipe_xml_child(node, "Groups/ImGroup");
613 group_node;
614 group_node = sipe_xml_twin(group_node)) {
615 struct sipe_group *group = ucs_create_group(sipe_private,
616 group_node);
618 if (group) {
619 const sipe_xml *member_node;
621 for (member_node = sipe_xml_child(group_node,
622 "MemberCorrelationKey/ItemId");
623 member_node;
624 member_node = sipe_xml_twin(member_node)) {
625 struct sipe_buddy *buddy = sipe_buddy_find_by_exchange_key(sipe_private,
626 sipe_xml_attribute(member_node,
627 "Id"));
628 if (buddy)
629 sipe_buddy_add_to_group(sipe_private,
630 buddy,
631 group,
632 g_hash_table_lookup(uri_to_alias,
633 buddy->name));
638 g_hash_table_destroy(uri_to_alias);
640 /* Finished processing contact list */
641 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
642 sipe_buddy_update_finish(sipe_private);
643 sipe_group_update_finish(sipe_private);
644 } else {
645 sipe_buddy_cleanup_local_list(sipe_private);
646 sipe_backend_buddy_list_processing_finish(SIPE_CORE_PUBLIC);
647 sipe_subscribe_presence_initial(sipe_private);
652 static void ucs_get_im_item_list(struct sipe_core_private *sipe_private)
654 if (sipe_private->ucs->migrated)
655 sipe_ucs_http_request(sipe_private,
656 /* prioritize over pending default requests */
657 sipe_ucs_transaction(sipe_private),
658 g_strdup("<m:GetImItemList/>"),
659 sipe_ucs_get_im_item_list_response,
660 NULL);
663 static void ucs_ews_autodiscover_cb(struct sipe_core_private *sipe_private,
664 const struct sipe_ews_autodiscover_data *ews_data,
665 SIPE_UNUSED_PARAMETER gpointer callback_data)
667 struct sipe_ucs *ucs = sipe_private->ucs;
668 const gchar *ews_url;
670 if (!ucs || !ews_data)
671 return;
673 ews_url = ews_data->ews_url;
674 if (is_empty(ews_url)) {
675 SIPE_DEBUG_ERROR_NOFORMAT("ucs_ews_autodiscover_cb: can't detect EWS URL, contact list operations will not work!");
676 return;
679 SIPE_DEBUG_INFO("ucs_ews_autodiscover_cb: EWS URL '%s'", ews_url);
680 ucs->ews_url = g_strdup(ews_url);
682 /* this will trigger sending of the first deferred request */
683 ucs_get_im_item_list(sipe_private);
686 gboolean sipe_ucs_is_migrated(struct sipe_core_private *sipe_private)
688 return(sipe_private->ucs ? sipe_private->ucs->migrated : FALSE);
691 void sipe_ucs_init(struct sipe_core_private *sipe_private,
692 gboolean migrated)
694 struct sipe_ucs *ucs;
696 if (sipe_private->ucs) {
697 struct sipe_ucs *ucs = sipe_private->ucs;
700 * contact list update trigger -> request list again
702 * If the trigger arrives less than 10 seconds after our
703 * last UCS response, then ignore it, because it is caused
704 * by our own changes to the contact list.
706 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
707 if ((time(NULL) - ucs->last_response) >= 10)
708 ucs_get_im_item_list(sipe_private);
709 else
710 SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_init: ignoring this contact list update - triggered by our last change");
713 ucs->last_response = 0;
714 return;
717 sipe_private->ucs = ucs = g_new0(struct sipe_ucs, 1);
718 ucs->migrated = migrated;
720 /* create default transaction */
721 sipe_ucs_transaction(sipe_private);
722 ucs->default_transaction = ucs->transactions;
724 sipe_ews_autodiscover_start(sipe_private,
725 ucs_ews_autodiscover_cb,
726 NULL);
729 void sipe_ucs_free(struct sipe_core_private *sipe_private)
731 struct sipe_ucs *ucs = sipe_private->ucs;
732 GSList *entry;
734 if (!ucs)
735 return;
737 /* UCS stack is shutting down: reject all new requests */
738 ucs->shutting_down = TRUE;
740 entry = ucs->transactions;
741 while (entry) {
742 struct sipe_ucs_transaction *trans = entry->data;
743 GSList *entry2 = trans->pending_requests;
745 /* transactions get deleted by sipe_ucs_request_free() */
746 entry = entry->next;
748 while (entry2) {
749 struct ucs_request *request = entry2->data;
751 /* transactions get deleted by sipe_ucs_request_free() */
752 entry2 = entry2->next;
754 sipe_ucs_request_free(sipe_private, request);
758 /* only default transaction is left... */
759 sipe_utils_slist_free_full(ucs->transactions, g_free);
761 g_free(ucs->ews_url);
762 g_free(ucs);
763 sipe_private->ucs = NULL;
767 Local Variables:
768 mode: c
769 c-file-style: "bsd"
770 indent-tabs-mode: t
771 tab-width: 8
772 End: