c4d1df60be1acace54d5bb053596a2daf4024311
[siplcs.git] / src / core / sipe-ucs.c
blobc4d1df60be1acace54d5bb053596a2daf4024311
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;
311 /* extract Exchange key - not sure if this is correct */
312 for (attr_node = sipe_xml_child(persona_node,
313 "Attributions/Attribution");
314 attr_node;
315 attr_node = sipe_xml_twin(attr_node)) {
316 const sipe_xml *id_node = sipe_xml_child(attr_node,
317 "SourceId");
318 gchar *type = sipe_xml_data(sipe_xml_child(attr_node,
319 "DisplayName"));
320 if (id_node &&
321 sipe_strequal(type, "Lync Contacts")) {
322 *key = sipe_xml_attribute(id_node, "Id");
323 *change = sipe_xml_attribute(id_node, "ChangeKey");
324 g_free(type);
325 break;
327 g_free(type);
331 static void sipe_ucs_add_new_im_contact_to_group_response(struct sipe_core_private *sipe_private,
332 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
333 const sipe_xml *body,
334 gpointer callback_data)
336 gchar *who = callback_data;
337 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private, who);
338 const sipe_xml *persona_node = sipe_xml_child(body,
339 "AddNewImContactToGroupResponse/Persona");
341 sipe_private->ucs->last_response = time(NULL);
343 if (persona_node &&
344 buddy &&
345 is_empty(buddy->exchange_key) &&
346 is_empty(buddy->change_key)) {
347 const gchar *key = NULL;
348 const gchar *change = NULL;
350 ucs_extract_keys(persona_node, &key, &change);
352 if (!is_empty(key) && !is_empty(change)) {
354 sipe_buddy_add_keys(sipe_private,
355 buddy,
356 key,
357 change);
359 SIPE_DEBUG_INFO("sipe_ucs_add_new_im_contact_to_group_response: persona URI '%s' key '%s' change '%s'",
360 buddy->name, key, change);
364 g_free(who);
367 void sipe_ucs_group_add_buddy(struct sipe_core_private *sipe_private,
368 struct sipe_ucs_transaction *trans,
369 struct sipe_group *group,
370 struct sipe_buddy *buddy,
371 const gchar *who)
373 /* existing or new buddy? */
374 if (buddy && buddy->exchange_key) {
375 gchar *body = g_strdup_printf("<m:AddImContactToGroup>"
376 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
377 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
378 "</m:AddImContactToGroup>",
379 buddy->exchange_key,
380 buddy->change_key,
381 group->exchange_key,
382 group->change_key);
384 sipe_ucs_http_request(sipe_private,
385 trans,
386 body,
387 sipe_ucs_ignore_response,
388 NULL);
389 } else {
390 gchar *payload = g_strdup(who);
391 gchar *body = g_strdup_printf("<m:AddNewImContactToGroup>"
392 " <m:ImAddress>%s</m:ImAddress>"
393 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
394 "</m:AddNewImContactToGroup>",
395 sipe_get_no_sip_uri(who),
396 group->exchange_key,
397 group->change_key);
399 if (!sipe_ucs_http_request(sipe_private,
400 trans,
401 body,
402 sipe_ucs_add_new_im_contact_to_group_response,
403 payload))
404 g_free(payload);
408 void sipe_ucs_group_remove_buddy(struct sipe_core_private *sipe_private,
409 struct sipe_ucs_transaction *trans,
410 struct sipe_group *group,
411 struct sipe_buddy *buddy)
413 if (group) {
415 * If a contact is removed from last group, it will also be
416 * removed from contact list completely. The documentation has
417 * a RemoveContactFromImList operation, but that doesn't seem
418 * to work at all, i.e. it is always rejected by the server.
420 gchar *body = g_strdup_printf("<m:RemoveImContactFromGroup>"
421 " <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
422 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
423 "</m:RemoveImContactFromGroup>",
424 buddy->exchange_key,
425 buddy->change_key,
426 group->exchange_key,
427 group->change_key);
429 sipe_ucs_http_request(sipe_private,
430 trans,
431 body,
432 sipe_ucs_ignore_response,
433 NULL);
437 static struct sipe_group *ucs_create_group(struct sipe_core_private *sipe_private,
438 const sipe_xml *group_node)
440 const sipe_xml *id_node = sipe_xml_child(group_node,
441 "ExchangeStoreId");
442 const gchar *key = sipe_xml_attribute(id_node, "Id");
443 const gchar *change = sipe_xml_attribute(id_node, "ChangeKey");
444 struct sipe_group *group = NULL;
446 if (!(is_empty(key) || is_empty(change))) {
447 gchar *name = sipe_xml_data(sipe_xml_child(group_node,
448 "DisplayName"));
449 group = sipe_group_add(sipe_private,
450 name,
451 key,
452 change,
453 /* sipe_group must have unique ID */
454 ++sipe_private->ucs->group_id);
455 g_free(name);
458 return(group);
461 static void sipe_ucs_add_im_group_response(struct sipe_core_private *sipe_private,
462 struct sipe_ucs_transaction *trans,
463 const sipe_xml *body,
464 gpointer callback_data)
466 gchar *who = callback_data;
467 const sipe_xml *group_node = sipe_xml_child(body,
468 "AddImGroupResponse/ImGroup");
469 struct sipe_group *group = ucs_create_group(sipe_private, group_node);
471 sipe_private->ucs->last_response = time(NULL);
473 if (group) {
474 struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
475 who);
477 if (buddy)
478 sipe_buddy_insert_group(buddy, group);
480 sipe_ucs_group_add_buddy(sipe_private,
481 trans,
482 group,
483 buddy,
484 who);
487 g_free(who);
490 void sipe_ucs_group_create(struct sipe_core_private *sipe_private,
491 struct sipe_ucs_transaction *trans,
492 const gchar *name,
493 const gchar *who)
495 gchar *payload = g_strdup(who);
496 /* new_name can contain restricted characters */
497 gchar *body = g_markup_printf_escaped("<m:AddImGroup>"
498 " <m:DisplayName>%s</m:DisplayName>"
499 "</m:AddImGroup>",
500 name);
502 if (!sipe_ucs_http_request(sipe_private,
503 trans,
504 body,
505 sipe_ucs_add_im_group_response,
506 payload))
507 g_free(payload);
510 void sipe_ucs_group_rename(struct sipe_core_private *sipe_private,
511 struct sipe_group *group,
512 const gchar *new_name)
514 /* new_name can contain restricted characters */
515 gchar *body = g_markup_printf_escaped("<m:SetImGroup>"
516 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
517 " <m:NewDisplayName>%s</m:NewDisplayName>"
518 "</m:SetImGroup>",
519 group->exchange_key,
520 group->change_key,
521 new_name);
523 sipe_ucs_http_request(sipe_private,
524 NULL,
525 body,
526 sipe_ucs_ignore_response,
527 NULL);
530 void sipe_ucs_group_remove(struct sipe_core_private *sipe_private,
531 struct sipe_group *group)
533 gchar *body = g_strdup_printf("<m:RemoveImGroup>"
534 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
535 "</m:RemoveImGroup>",
536 group->exchange_key,
537 group->change_key);
539 sipe_ucs_http_request(sipe_private,
540 NULL,
541 body,
542 sipe_ucs_ignore_response,
543 NULL);
546 static void sipe_ucs_get_im_item_list_response(struct sipe_core_private *sipe_private,
547 SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
548 const sipe_xml *body,
549 SIPE_UNUSED_PARAMETER gpointer callback_data)
551 const sipe_xml *node = sipe_xml_child(body,
552 "GetImItemListResponse/ImItemList");
554 if (node) {
555 const sipe_xml *persona_node;
556 const sipe_xml *group_node;
557 GHashTable *uri_to_alias = g_hash_table_new_full(g_str_hash,
558 g_str_equal,
559 NULL,
560 g_free);
562 /* Start processing contact list */
563 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
564 sipe_group_update_start(sipe_private);
565 sipe_buddy_update_start(sipe_private);
566 } else
567 sipe_backend_buddy_list_processing_start(SIPE_CORE_PUBLIC);
569 for (persona_node = sipe_xml_child(node, "Personas/Persona");
570 persona_node;
571 persona_node = sipe_xml_twin(persona_node)) {
572 gchar *address = sipe_xml_data(sipe_xml_child(persona_node,
573 "ImAddress"));
574 const gchar *key = NULL;
575 const gchar *change = NULL;
577 ucs_extract_keys(persona_node, &key, &change);
579 if (!(is_empty(address) || is_empty(key) || is_empty(change))) {
580 gchar *alias = sipe_xml_data(sipe_xml_child(persona_node,
581 "DisplayName"));
582 gchar *uri = sip_uri_from_name(address);
583 struct sipe_buddy *buddy = sipe_buddy_add(sipe_private,
584 uri,
585 key,
586 change);
587 g_free(uri);
589 /* hash table takes ownership of alias */
590 g_hash_table_insert(uri_to_alias,
591 buddy->name,
592 alias);
594 SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: persona URI '%s' key '%s' change '%s'",
595 buddy->name, key, change);
597 g_free(address);
600 for (group_node = sipe_xml_child(node, "Groups/ImGroup");
601 group_node;
602 group_node = sipe_xml_twin(group_node)) {
603 struct sipe_group *group = ucs_create_group(sipe_private,
604 group_node);
606 if (group) {
607 const sipe_xml *member_node;
609 for (member_node = sipe_xml_child(group_node,
610 "MemberCorrelationKey/ItemId");
611 member_node;
612 member_node = sipe_xml_twin(member_node)) {
613 struct sipe_buddy *buddy = sipe_buddy_find_by_exchange_key(sipe_private,
614 sipe_xml_attribute(member_node,
615 "Id"));
616 if (buddy)
617 sipe_buddy_add_to_group(sipe_private,
618 buddy,
619 group,
620 g_hash_table_lookup(uri_to_alias,
621 buddy->name));
626 g_hash_table_destroy(uri_to_alias);
628 /* Finished processing contact list */
629 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
630 sipe_buddy_update_finish(sipe_private);
631 sipe_group_update_finish(sipe_private);
632 } else {
633 sipe_buddy_cleanup_local_list(sipe_private);
634 sipe_backend_buddy_list_processing_finish(SIPE_CORE_PUBLIC);
635 sipe_subscribe_presence_initial(sipe_private);
640 static void ucs_get_im_item_list(struct sipe_core_private *sipe_private)
642 if (sipe_private->ucs->migrated)
643 sipe_ucs_http_request(sipe_private,
644 /* prioritize over pending default requests */
645 sipe_ucs_transaction(sipe_private),
646 g_strdup("<m:GetImItemList/>"),
647 sipe_ucs_get_im_item_list_response,
648 NULL);
651 static void ucs_ews_autodiscover_cb(struct sipe_core_private *sipe_private,
652 const struct sipe_ews_autodiscover_data *ews_data,
653 SIPE_UNUSED_PARAMETER gpointer callback_data)
655 struct sipe_ucs *ucs = sipe_private->ucs;
656 const gchar *ews_url;
658 if (!ucs || !ews_data)
659 return;
661 ews_url = ews_data->ews_url;
662 if (is_empty(ews_url)) {
663 SIPE_DEBUG_ERROR_NOFORMAT("ucs_ews_autodiscover_cb: can't detect EWS URL, contact list operations will not work!");
664 return;
667 SIPE_DEBUG_INFO("ucs_ews_autodiscover_cb: EWS URL '%s'", ews_url);
668 ucs->ews_url = g_strdup(ews_url);
670 /* this will trigger sending of the first deferred request */
671 ucs_get_im_item_list(sipe_private);
674 gboolean sipe_ucs_is_migrated(struct sipe_core_private *sipe_private)
676 return(sipe_private->ucs ? sipe_private->ucs->migrated : FALSE);
679 void sipe_ucs_init(struct sipe_core_private *sipe_private,
680 gboolean migrated)
682 struct sipe_ucs *ucs;
684 if (sipe_private->ucs) {
685 struct sipe_ucs *ucs = sipe_private->ucs;
688 * contact list update trigger -> request list again
690 * If the trigger arrives less than 10 seconds after our
691 * last UCS response, then ignore it, because it is caused
692 * by our own changes to the contact list.
694 if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
695 if ((time(NULL) - ucs->last_response) >= 10)
696 ucs_get_im_item_list(sipe_private);
697 else
698 SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_init: ignoring this contact list update - triggered by our last change");
701 ucs->last_response = 0;
702 return;
705 sipe_private->ucs = ucs = g_new0(struct sipe_ucs, 1);
706 ucs->migrated = migrated;
708 /* create default transaction */
709 sipe_ucs_transaction(sipe_private);
710 ucs->default_transaction = ucs->transactions;
712 sipe_ews_autodiscover_start(sipe_private,
713 ucs_ews_autodiscover_cb,
714 NULL);
717 void sipe_ucs_free(struct sipe_core_private *sipe_private)
719 struct sipe_ucs *ucs = sipe_private->ucs;
720 GSList *entry;
722 if (!ucs)
723 return;
725 /* UCS stack is shutting down: reject all new requests */
726 ucs->shutting_down = TRUE;
728 entry = ucs->transactions;
729 while (entry) {
730 struct sipe_ucs_transaction *trans = entry->data;
731 GSList *entry2 = trans->pending_requests;
733 /* transactions get deleted by sipe_ucs_request_free() */
734 entry = entry->next;
736 while (entry2) {
737 struct ucs_request *request = entry2->data;
739 /* transactions get deleted by sipe_ucs_request_free() */
740 entry2 = entry2->next;
742 sipe_ucs_request_free(sipe_private, request);
746 /* only default transaction is left... */
747 sipe_utils_slist_free_full(ucs->transactions, g_free);
749 g_free(ucs->ews_url);
750 g_free(ucs);
751 sipe_private->ucs = NULL;
755 Local Variables:
756 mode: c
757 c-file-style: "bsd"
758 indent-tabs-mode: t
759 tab-width: 8
760 End: