ucs: add RemoveImGroup operation
[siplcs.git] / src / core / sipe-ucs.c
blob286d3855d13c223566fd5109aa100c69bd0f9192
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>
32 #include "sipe-backend.h"
33 #include "sipe-buddy.h"
34 #include "sipe-common.h"
35 #include "sipe-core.h"
36 #include "sipe-core-private.h"
37 #include "sipe-digest.h"
38 #include "sipe-ews-autodiscover.h"
39 #include "sipe-group.h"
40 #include "sipe-http.h"
41 #include "sipe-subscriptions.h"
42 #include "sipe-ucs.h"
43 #include "sipe-utils.h"
44 #include "sipe-xml.h"
46 typedef void (ucs_callback)(struct sipe_core_private *sipe_private,
47 const sipe_xml *body,
48 gpointer callback_data);
50 struct ucs_deferred {
51 ucs_callback *cb;
52 gpointer cb_data;
53 gchar *body;
56 struct ucs_request {
57 ucs_callback *cb;
58 gpointer cb_data;
59 struct sipe_http_request *request;
62 struct sipe_ucs {
63 gchar *ews_url;
64 GSList *deferred_requests;
65 GSList *pending_requests;
66 gboolean migrated;
67 gboolean shutting_down;
70 static void sipe_ucs_deferred_free(struct sipe_core_private *sipe_private,
71 struct ucs_deferred *data)
73 if (data->cb)
74 /* Callback: aborted */
75 (*data->cb)(sipe_private, NULL, data->cb_data);
76 g_free(data->body);
77 g_free(data);
80 static void sipe_ucs_request_free(struct sipe_core_private *sipe_private,
81 struct ucs_request *data)
83 if (data->request)
84 sipe_http_request_cancel(data->request);
85 if (data->cb)
86 /* Callback: aborted */
87 (*data->cb)(sipe_private, NULL, data->cb_data);
88 g_free(data);
91 static void sipe_ucs_http_response(struct sipe_core_private *sipe_private,
92 guint status,
93 SIPE_UNUSED_PARAMETER GSList *headers,
94 const gchar *body,
95 gpointer callback_data)
97 struct ucs_request *data = callback_data;
98 struct sipe_ucs *ucs = sipe_private->ucs;
100 SIPE_DEBUG_INFO("sipe_ucs_http_response: code %d", status);
101 data->request = NULL;
103 if ((status == SIPE_HTTP_STATUS_OK) && body) {
104 sipe_xml *xml = sipe_xml_parse(body, strlen(body));
105 const sipe_xml *soap_body = sipe_xml_child(xml, "Body");
106 /* Callback: success */
107 (*data->cb)(sipe_private, soap_body, data->cb_data);
108 sipe_xml_free(xml);
109 } else {
110 /* Callback: failed */
111 (*data->cb)(sipe_private, NULL, data->cb_data);
114 /* already been called */
115 data->cb = NULL;
117 ucs->pending_requests = g_slist_remove(ucs->pending_requests,
118 data);
119 sipe_ucs_request_free(sipe_private, data);
122 static gboolean sipe_ucs_http_request(struct sipe_core_private *sipe_private,
123 const gchar *body,
124 ucs_callback *callback,
125 gpointer callback_data)
127 struct sipe_ucs *ucs = sipe_private->ucs;
128 gboolean success = FALSE;
130 if (ucs->shutting_down) {
131 SIPE_DEBUG_ERROR("sipe_ucs_http_request: new UCS request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
132 "Body: %s\n",
133 body ? body : "<EMPTY>");
135 } else if (ucs->ews_url) {
136 struct ucs_request *data = g_new0(struct ucs_request, 1);
137 gchar *soap = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
138 "<soap:Envelope"
139 " xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
140 " xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
141 " xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
142 " >"
143 " <soap:Header>"
144 " <t:RequestServerVersion Version=\"Exchange2013\" />"
145 " </soap:Header>"
146 " <soap:Body>"
147 " %s"
148 " </soap:Body>"
149 "</soap:Envelope>",
150 body);
151 struct sipe_http_request *request = sipe_http_request_post(sipe_private,
152 ucs->ews_url,
153 NULL,
154 soap,
155 "text/xml; charset=UTF-8",
156 sipe_ucs_http_response,
157 data);
158 g_free(soap);
160 if (request) {
161 data->cb = callback;
162 data->cb_data = callback_data;
163 data->request = request;
165 ucs->pending_requests = g_slist_prepend(ucs->pending_requests,
166 data);
168 sipe_core_email_authentication(sipe_private,
169 request);
170 sipe_http_request_allow_redirect(request);
171 sipe_http_request_ready(request);
173 success = TRUE;
174 } else {
175 SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_http_request: failed to create HTTP connection");
176 g_free(data);
179 } else {
180 struct ucs_deferred *data = g_new0(struct ucs_deferred, 1);
181 data->cb = callback;
182 data->cb_data = callback_data;
183 data->body = g_strdup(body);
185 ucs->deferred_requests = g_slist_prepend(ucs->deferred_requests,
186 data);
187 success = TRUE;
190 return(success);
193 static void sipe_ucs_get_user_photo_response(struct sipe_core_private *sipe_private,
194 const sipe_xml *body,
195 gpointer callback_data)
197 gchar *uri = callback_data;
198 const sipe_xml *node = sipe_xml_child(body,
199 "GetUserPhotoResponse/PictureData");
201 if (node) {
202 gchar *base64;
203 gsize photo_size;
204 guchar *photo;
205 guchar digest[SIPE_DIGEST_SHA1_LENGTH];
206 gchar *digest_string;
208 /* decode photo data */
209 base64 = sipe_xml_data(node);
210 photo = g_base64_decode(base64, &photo_size);
211 g_free(base64);
213 /* EWS doesn't provide a hash -> calculate SHA-1 digest */
214 sipe_digest_sha1(photo, photo_size, digest);
215 digest_string = buff_to_hex_str(digest,
216 SIPE_DIGEST_SHA1_LENGTH);
218 /* backend frees "photo" */
219 sipe_backend_buddy_set_photo(SIPE_CORE_PUBLIC,
220 uri,
221 photo,
222 photo_size,
223 digest_string);
224 g_free(digest_string);
227 g_free(uri);
230 void sipe_ucs_get_photo(struct sipe_core_private *sipe_private,
231 const gchar *uri)
233 gchar *payload = g_strdup(uri);
234 gchar *body = g_strdup_printf("<m:GetUserPhoto>"
235 " <m:Email>%s</m:Email>"
236 " <m:SizeRequested>HR48x48</m:SizeRequested>"
237 "</m:GetUserPhoto>",
238 sipe_get_no_sip_uri(uri));
240 if (!sipe_ucs_http_request(sipe_private,
241 body,
242 sipe_ucs_get_user_photo_response,
243 payload))
244 g_free(payload);
246 g_free(body);
249 static void sipe_ucs_ignore_response(SIPE_UNUSED_PARAMETER struct sipe_core_private *sipe_private,
250 SIPE_UNUSED_PARAMETER const sipe_xml *body,
251 SIPE_UNUSED_PARAMETER gpointer callback_data)
253 SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_ignore_response: done");
256 void sipe_ucs_group_rename(struct sipe_core_private *sipe_private,
257 struct sipe_group *group,
258 const gchar *new_name)
260 /* new_name can contain restricted characters */
261 gchar *body = g_markup_printf_escaped("<m:SetImGroup>"
262 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
263 " <m:NewDisplayName>%s</m:NewDisplayName>"
264 "</m:SetImGroup>",
265 group->exchange_key,
266 group->change_key,
267 new_name);
269 sipe_ucs_http_request(sipe_private,
270 body,
271 sipe_ucs_ignore_response,
272 NULL);
273 g_free(body);
276 void sipe_ucs_group_remove(struct sipe_core_private *sipe_private,
277 struct sipe_group *group)
279 gchar *body = g_strdup_printf("<m:RemoveImGroup>"
280 " <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
281 "</m:RemoveImGroup>",
282 group->exchange_key,
283 group->change_key);
285 sipe_ucs_http_request(sipe_private,
286 body,
287 sipe_ucs_ignore_response,
288 NULL);
289 g_free(body);
292 static void sipe_ucs_get_im_item_list_response(struct sipe_core_private *sipe_private,
293 const sipe_xml *body,
294 SIPE_UNUSED_PARAMETER gpointer callback_data)
296 const sipe_xml *node = sipe_xml_child(body,
297 "GetImItemListResponse/ImItemList");
299 if (node) {
300 const sipe_xml *persona_node;
301 const sipe_xml *group_node;
303 /* Start processing contact list */
304 sipe_backend_buddy_list_processing_start(SIPE_CORE_PUBLIC);
306 for (persona_node = sipe_xml_child(node, "Personas/Persona");
307 persona_node;
308 persona_node = sipe_xml_twin(persona_node)) {
309 gchar *address = sipe_xml_data(sipe_xml_child(persona_node,
310 "ImAddress"));
311 const gchar *key = NULL;
312 const gchar *change = NULL;
313 const sipe_xml *attr_node;
315 /* extract Exchange key - not sure if this is correct */
316 for (attr_node = sipe_xml_child(persona_node,
317 "Attributions/Attribution");
318 attr_node;
319 attr_node = sipe_xml_twin(attr_node)) {
320 const sipe_xml *id_node = sipe_xml_child(attr_node,
321 "SourceId");
322 gchar *type = sipe_xml_data(sipe_xml_child(attr_node,
323 "DisplayName"));
324 if (id_node &&
325 sipe_strequal(type, "Lync Contacts")) {
326 key = sipe_xml_attribute(id_node, "Id");
327 change = sipe_xml_attribute(id_node, "ChangeKey");
328 g_free(type);
329 break;
331 g_free(type);
334 if (!(is_empty(address) || is_empty(key) || is_empty(change))) {
335 gchar *uri = sip_uri_from_name(address);
336 struct sipe_buddy *buddy = sipe_buddy_add(sipe_private,
337 uri,
338 key,
339 change);
340 g_free(uri);
342 SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: persona URI '%s' key '%s' change '%s'",
343 buddy->name, key, change);
345 g_free(address);
348 for (group_node = sipe_xml_child(node, "Groups/ImGroup");
349 group_node;
350 group_node = sipe_xml_twin(group_node)) {
351 const sipe_xml *id_node = sipe_xml_child(group_node,
352 "ExchangeStoreId");
353 const gchar *key = sipe_xml_attribute(id_node, "Id");
354 const gchar *change = sipe_xml_attribute(id_node, "ChangeKey");
356 if (!(is_empty(key) || is_empty(change))) {
357 gchar *name = sipe_xml_data(sipe_xml_child(group_node,
358 "DisplayName"));
359 struct sipe_group *group = sipe_group_add(sipe_private,
360 name,
361 key,
362 change,
364 const sipe_xml *member_node;
366 g_free(name);
368 if (group) {
369 SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: group '%s' key '%s' change '%s'",
370 group->name, key, change);
372 for (member_node = sipe_xml_child(group_node,
373 "MemberCorrelationKey/ItemId");
374 member_node;
375 member_node = sipe_xml_twin(member_node)) {
376 struct sipe_buddy *buddy = sipe_buddy_find_by_exchange_key(sipe_private,
377 sipe_xml_attribute(member_node,
378 "Id"));
379 if (buddy)
380 sipe_buddy_add_to_group(sipe_private,
381 buddy,
382 group,
383 /* alias will be set via buddy presence update */
384 NULL);
390 /* Finished processing contact list */
391 sipe_buddy_cleanup_local_list(sipe_private);
392 sipe_backend_buddy_list_processing_finish(SIPE_CORE_PUBLIC);
393 sipe_subscribe_presence_initial(sipe_private);
397 static void ucs_ews_autodiscover_cb(struct sipe_core_private *sipe_private,
398 const struct sipe_ews_autodiscover_data *ews_data,
399 SIPE_UNUSED_PARAMETER gpointer callback_data)
401 struct sipe_ucs *ucs = sipe_private->ucs;
402 const gchar *ews_url;
404 if (!ucs || !ews_data)
405 return;
407 ews_url = ews_data->ews_url;
408 if (is_empty(ews_url)) {
409 SIPE_DEBUG_ERROR_NOFORMAT("ucs_ews_autodiscover_cb: can't detect EWS URL, contact list operations will not work!");
410 return;
413 SIPE_DEBUG_INFO("ucs_ews_autodiscover_cb: EWS URL '%s'", ews_url);
414 ucs->ews_url = g_strdup(ews_url);
416 /* Request migrated contact list */
417 if (ucs->migrated)
418 sipe_ucs_http_request(sipe_private,
419 "<m:GetImItemList/>",
420 sipe_ucs_get_im_item_list_response,
421 NULL);
423 /* EWS URL is valid, send all deferred requests now */
424 if (ucs->deferred_requests) {
425 GSList *entry = ucs->deferred_requests;
426 while (entry) {
427 struct ucs_deferred *data = entry->data;
429 sipe_ucs_http_request(sipe_private,
430 data->body,
431 data->cb,
432 data->cb_data);
434 /* callback & data has been forwarded */
435 data->cb = NULL;
436 sipe_ucs_deferred_free(sipe_private, data);
438 entry = entry->next;
440 g_slist_free(ucs->deferred_requests);
441 ucs->deferred_requests = NULL;
445 gboolean sipe_ucs_is_migrated(struct sipe_core_private *sipe_private)
447 return(sipe_private->ucs ? sipe_private->ucs->migrated : FALSE);
450 void sipe_ucs_init(struct sipe_core_private *sipe_private,
451 gboolean migrated)
453 struct sipe_ucs *ucs;
455 if (sipe_private->ucs)
456 return;
458 sipe_private->ucs = ucs = g_new0(struct sipe_ucs, 1);
459 ucs->migrated = migrated;
461 sipe_ews_autodiscover_start(sipe_private,
462 ucs_ews_autodiscover_cb,
463 NULL);
466 void sipe_ucs_free(struct sipe_core_private *sipe_private)
468 struct sipe_ucs *ucs = sipe_private->ucs;
470 if (!ucs)
471 return;
473 /* UCS stack is shutting down: reject all new requests */
474 ucs->shutting_down = TRUE;
476 if (ucs->deferred_requests) {
477 GSList *entry = ucs->deferred_requests;
478 while (entry) {
479 sipe_ucs_deferred_free(sipe_private, entry->data);
480 entry = entry->next;
482 g_slist_free(ucs->deferred_requests);
485 if (ucs->pending_requests) {
486 GSList *entry = ucs->pending_requests;
487 while (entry) {
488 sipe_ucs_request_free(sipe_private, entry->data);
489 entry = entry->next;
491 g_slist_free(ucs->pending_requests);
494 g_free(ucs->ews_url);
495 g_free(ucs);
496 sipe_private->ucs = NULL;
500 Local Variables:
501 mode: c
502 c-file-style: "bsd"
503 indent-tabs-mode: t
504 tab-width: 8
505 End: