1 /*
2 carddav.c
3 Copyright (C) 2015 Belledonne Communications SARL
4
5 This program is free software; you can redistribute it and/or
6 modify it under the terms of the GNU General Public License
7 as published by the Free Software Foundation; either version 2
8 of the License, or (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20 #include "linphone/core.h"
21 #include "private.h"
22
linphone_carddav_context_new(LinphoneFriendList * lfl)23 LinphoneCardDavContext* linphone_carddav_context_new(LinphoneFriendList *lfl) {
24 LinphoneCardDavContext *carddav_context = NULL;
25
26 if (!linphone_core_vcard_supported()) {
27 ms_error("[carddav] vCard isn't available (maybe it wasn't compiled), can't do CardDAV sync");
28 return NULL;
29 }
30 if (!lfl || !lfl->uri) {
31 return NULL;
32 }
33
34 carddav_context = (LinphoneCardDavContext *)ms_new0(LinphoneCardDavContext, 1);
35 carddav_context->friend_list = linphone_friend_list_ref(lfl);
36 return carddav_context;
37 }
38
linphone_carddav_context_destroy(LinphoneCardDavContext * cdc)39 void linphone_carddav_context_destroy(LinphoneCardDavContext *cdc) {
40 if (cdc) {
41 if (cdc->friend_list) {
42 linphone_friend_list_unref(cdc->friend_list);
43 cdc->friend_list = NULL;
44 }
45 if (cdc->auth_info) {
46 linphone_auth_info_unref(cdc->auth_info);
47 cdc->auth_info = NULL;
48 }
49 ms_free(cdc);
50 }
51 }
52
linphone_carddav_set_user_data(LinphoneCardDavContext * cdc,void * ud)53 void linphone_carddav_set_user_data(LinphoneCardDavContext *cdc, void *ud) {
54 cdc->user_data = ud;
55 }
56
linphone_carddav_get_user_data(LinphoneCardDavContext * cdc)57 void* linphone_carddav_get_user_data(LinphoneCardDavContext *cdc) {
58 return cdc->user_data;
59 }
60
linphone_carddav_synchronize(LinphoneCardDavContext * cdc)61 void linphone_carddav_synchronize(LinphoneCardDavContext *cdc) {
62 cdc->ctag = cdc->friend_list->revision;
63 linphone_carddav_get_current_ctag(cdc);
64 }
65
linphone_carddav_client_to_server_sync_done(LinphoneCardDavContext * cdc,bool_t success,const char * msg)66 static void linphone_carddav_client_to_server_sync_done(LinphoneCardDavContext *cdc, bool_t success, const char *msg) {
67 if (!success) {
68 ms_error("[carddav] CardDAV client to server sync failure: %s", msg);
69 }
70
71 if (cdc->sync_done_cb) {
72 cdc->sync_done_cb(cdc, success, msg);
73 }
74 }
75
linphone_carddav_server_to_client_sync_done(LinphoneCardDavContext * cdc,bool_t success,const char * msg)76 static void linphone_carddav_server_to_client_sync_done(LinphoneCardDavContext *cdc, bool_t success, const char *msg) {
77 if (success) {
78 ms_debug("CardDAV sync successful, saving new cTag: %i", cdc->ctag);
79 linphone_friend_list_update_revision(cdc->friend_list, cdc->ctag);
80 } else {
81 ms_error("[carddav] CardDAV server to client sync failure: %s", msg);
82 }
83
84 if (cdc->sync_done_cb) {
85 cdc->sync_done_cb(cdc, success, msg);
86 }
87 }
88
find_matching_friend(LinphoneFriend * lf1,LinphoneFriend * lf2)89 static int find_matching_friend(LinphoneFriend *lf1, LinphoneFriend *lf2) {
90 LinphoneVcard *lvc1 = linphone_friend_get_vcard(lf1);
91 LinphoneVcard *lvc2 = linphone_friend_get_vcard(lf2);
92 const char *uid1 = NULL, *uid2 = NULL;
93 if (!lvc1 || !lvc2) {
94 return 1;
95 }
96 uid1 = linphone_vcard_get_uid(lvc1);
97 uid2 = linphone_vcard_get_uid(lvc2);
98 if (!uid1 || !uid2) {
99 return 1;
100 }
101 return strcmp(uid1, uid2);
102 }
103
linphone_carddav_response_free(LinphoneCardDavResponse * response)104 static void linphone_carddav_response_free(LinphoneCardDavResponse *response) {
105 if (response->etag) ms_free(response->etag);
106 if (response->url) ms_free(response->url);
107 if (response->vcard) ms_free(response->vcard);
108 ms_free(response);
109 }
110
linphone_carddav_vcards_pulled(LinphoneCardDavContext * cdc,bctbx_list_t * vCards)111 static void linphone_carddav_vcards_pulled(LinphoneCardDavContext *cdc, bctbx_list_t *vCards) {
112 bctbx_list_t *vCards_remember = vCards;
113 if (vCards != NULL && bctbx_list_size(vCards) > 0) {
114 bctbx_list_t *friends = cdc->friend_list->friends;
115 while (vCards) {
116 LinphoneCardDavResponse *vCard = (LinphoneCardDavResponse *)vCards->data;
117 if (vCard) {
118 LinphoneVcard *lvc = linphone_vcard_context_get_vcard_from_buffer(cdc->friend_list->lc->vcard_context, vCard->vcard);
119 LinphoneFriend *lf = NULL;
120 bctbx_list_t *local_friend = NULL;
121
122 if (lvc) {
123 // Compute downloaded vCards' URL and save it (+ eTag)
124 char *vCard_name = strrchr(vCard->url, '/');
125 char full_url[300];
126 snprintf(full_url, sizeof(full_url), "%s%s", cdc->friend_list->uri, vCard_name);
127 linphone_vcard_set_url(lvc, full_url);
128 linphone_vcard_set_etag(lvc, vCard->etag);
129 ms_debug("Downloaded vCard etag/url are %s and %s", vCard->etag, full_url);
130
131 lf = linphone_friend_new_from_vcard(lvc);
132 linphone_vcard_unref(lvc); /*ref is now owned by friend*/
133 if (lf) {
134 local_friend = bctbx_list_find_custom(friends, (int (*)(const void*, const void*))find_matching_friend, lf);
135
136 if (local_friend) {
137 LinphoneFriend *lf2 = (LinphoneFriend *)local_friend->data;
138 lf->storage_id = lf2->storage_id;
139 lf->pol = lf2->pol;
140 lf->subscribe = lf2->subscribe;
141 lf->refkey = ms_strdup(lf2->refkey);
142 lf->presence_received = lf2->presence_received;
143 lf->lc = lf2->lc;
144 lf->friend_list = lf2->friend_list;
145
146 if (cdc->contact_updated_cb) {
147 ms_debug("Contact updated: %s", linphone_friend_get_name(lf));
148 cdc->contact_updated_cb(cdc, lf, lf2);
149 }
150 } else {
151 if (cdc->contact_created_cb) {
152 ms_debug("Contact created: %s", linphone_friend_get_name(lf));
153 cdc->contact_created_cb(cdc, lf);
154 }
155 }
156 linphone_friend_unref(lf);
157 } else {
158 ms_error("[carddav] Couldn't create a friend from vCard");
159 }
160 } else {
161 ms_error("[carddav] Couldn't parse vCard %s", vCard->vcard);
162 }
163 }
164 vCards = bctbx_list_next(vCards);
165 }
166 bctbx_list_free_with_data(vCards_remember, (void (*)(void *))linphone_carddav_response_free);
167 }
168 linphone_carddav_server_to_client_sync_done(cdc, TRUE, NULL);
169 }
170
parse_vcards_from_xml_response(const char * body)171 static bctbx_list_t* parse_vcards_from_xml_response(const char *body) {
172 bctbx_list_t *result = NULL;
173 xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new();
174 xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
175 xml_ctx->doc = xmlReadDoc((const unsigned char*)body, 0, NULL, 0);
176 if (xml_ctx->doc != NULL) {
177 if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end;
178 linphone_xml_xpath_context_init_carddav_ns(xml_ctx);
179 {
180 xmlXPathObjectPtr responses = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/d:multistatus/d:response");
181 if (responses != NULL && responses->nodesetval != NULL) {
182 xmlNodeSetPtr responses_nodes = responses->nodesetval;
183 if (responses_nodes->nodeNr >= 1) {
184 int i;
185 for (i = 0; i < responses_nodes->nodeNr; i++) {
186 xmlNodePtr response_node = responses_nodes->nodeTab[i];
187 xml_ctx->xpath_ctx->node = response_node;
188 {
189 char *etag = linphone_get_xml_text_content(xml_ctx, "d:propstat/d:prop/d:getetag");
190 char *url = linphone_get_xml_text_content(xml_ctx, "d:href");
191 char *vcard = linphone_get_xml_text_content(xml_ctx, "d:propstat/d:prop/card:address-data");
192 LinphoneCardDavResponse *response = ms_new0(LinphoneCardDavResponse, 1);
193 response->etag = ms_strdup(etag);
194 response->url = ms_strdup(url);
195 response->vcard = ms_strdup(vcard);
196 result = bctbx_list_append(result, response);
197 ms_debug("Added vCard object with eTag %s, URL %s and vCard %s", etag, url, vcard);
198 linphone_free_xml_text_content(etag);
199 linphone_free_xml_text_content(url);
200 linphone_free_xml_text_content(vcard);
201 }
202 }
203 }
204 xmlXPathFreeObject(responses);
205 }
206 }
207 }
208 end:
209 linphone_xmlparsing_context_destroy(xml_ctx);
210 return result;
211 }
212
find_matching_vcard(LinphoneCardDavResponse * response,LinphoneFriend * lf)213 static int find_matching_vcard(LinphoneCardDavResponse *response, LinphoneFriend *lf) {
214 if (!response->url || !lf || !lf->vcard || !linphone_vcard_get_url(lf->vcard)) {
215 return 1;
216 }
217 return strcmp(response->url, linphone_vcard_get_url(lf->vcard));
218 }
219
linphone_carddav_vcards_fetched(LinphoneCardDavContext * cdc,bctbx_list_t * vCards)220 static void linphone_carddav_vcards_fetched(LinphoneCardDavContext *cdc, bctbx_list_t *vCards) {
221 if (vCards != NULL && bctbx_list_size(vCards) > 0) {
222 bctbx_list_t *friends = cdc->friend_list->friends;
223 bctbx_list_t *friends_to_remove = NULL;
224 bctbx_list_t *temp_list = NULL;
225
226 while (friends) {
227 LinphoneFriend *lf = (LinphoneFriend *)friends->data;
228 if (lf) {
229 bctbx_list_t *vCard = bctbx_list_find_custom(vCards, (int (*)(const void*, const void*))find_matching_vcard, lf);
230 if (!vCard) {
231 ms_debug("Local friend %s isn't in the remote vCard list, delete it", linphone_friend_get_name(lf));
232 temp_list = bctbx_list_append(temp_list, linphone_friend_ref(lf));
233 } else {
234 LinphoneCardDavResponse *response = (LinphoneCardDavResponse *)vCard->data;
235 ms_debug("Local friend %s is in the remote vCard list, check eTag", linphone_friend_get_name(lf));
236 if (response) {
237 LinphoneVcard *lvc = linphone_friend_get_vcard(lf);
238 const char *etag = linphone_vcard_get_etag(lvc);
239 ms_debug("Local friend eTag is %s, remote vCard eTag is %s", etag, response->etag);
240 if (lvc && etag && strcmp(etag, response->etag) == 0) {
241 bctbx_list_remove(vCards, vCard);
242 linphone_carddav_response_free(response);
243 }
244 }
245 }
246 }
247 friends = bctbx_list_next(friends);
248 }
249 friends_to_remove = temp_list;
250 while(friends_to_remove) {
251 LinphoneFriend *lf = (LinphoneFriend *)friends_to_remove->data;
252 if (lf) {
253 if (cdc->contact_removed_cb) {
254 ms_debug("Contact removed: %s", linphone_friend_get_name(lf));
255 cdc->contact_removed_cb(cdc, lf);
256 }
257 }
258 friends_to_remove = bctbx_list_next(friends_to_remove);
259 }
260 temp_list = bctbx_list_free_with_data(temp_list, (void (*)(void *))linphone_friend_unref);
261
262 linphone_carddav_pull_vcards(cdc, vCards);
263 bctbx_list_free_with_data(vCards, (void (*)(void *))linphone_carddav_response_free);
264 }
265 }
266
parse_vcards_etags_from_xml_response(const char * body)267 static bctbx_list_t* parse_vcards_etags_from_xml_response(const char *body) {
268 bctbx_list_t *result = NULL;
269 xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new();
270 xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
271 xml_ctx->doc = xmlReadDoc((const unsigned char*)body, 0, NULL, 0);
272 if (xml_ctx->doc != NULL) {
273 if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end;
274 linphone_xml_xpath_context_init_carddav_ns(xml_ctx);
275 {
276 xmlXPathObjectPtr responses = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/d:multistatus/d:response");
277 if (responses != NULL && responses->nodesetval != NULL) {
278 xmlNodeSetPtr responses_nodes = responses->nodesetval;
279 if (responses_nodes->nodeNr >= 1) {
280 int i;
281 for (i = 0; i < responses_nodes->nodeNr; i++) {
282 xmlNodePtr response_node = responses_nodes->nodeTab[i];
283 xml_ctx->xpath_ctx->node = response_node;
284 {
285 char *etag = linphone_get_xml_text_content(xml_ctx, "d:propstat/d:prop/d:getetag");
286 char *url = linphone_get_xml_text_content(xml_ctx, "d:href");
287 LinphoneCardDavResponse *response = ms_new0(LinphoneCardDavResponse, 1);
288 response->etag = ms_strdup(etag);
289 response->url = ms_strdup(url);
290 result = bctbx_list_append(result, response);
291 ms_debug("Added vCard object with eTag %s and URL %s", etag, url);
292 linphone_free_xml_text_content(etag);
293 linphone_free_xml_text_content(url);
294 }
295 }
296 }
297 xmlXPathFreeObject(responses);
298 }
299 }
300 }
301 end:
302 linphone_xmlparsing_context_destroy(xml_ctx);
303 return result;
304 }
305
linphone_carddav_ctag_fetched(LinphoneCardDavContext * cdc,int ctag)306 static void linphone_carddav_ctag_fetched(LinphoneCardDavContext *cdc, int ctag) {
307 ms_debug("Remote cTag for CardDAV addressbook is %i, local one is %i", ctag, cdc->ctag);
308 if (ctag == -1 || ctag > cdc->ctag) {
309 cdc->ctag = ctag;
310 linphone_carddav_fetch_vcards(cdc);
311 } else {
312 ms_message("No changes found on server, skipping sync");
313 linphone_carddav_server_to_client_sync_done(cdc, TRUE, "Synchronization skipped because cTag already up to date");
314 }
315 }
316
parse_ctag_value_from_xml_response(const char * body)317 static int parse_ctag_value_from_xml_response(const char *body) {
318 int result = -1;
319 xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new();
320 xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
321 xml_ctx->doc = xmlReadDoc((const unsigned char*)body, 0, NULL, 0);
322 if (xml_ctx->doc != NULL) {
323 char *response = NULL;
324 if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end;
325 linphone_xml_xpath_context_init_carddav_ns(xml_ctx);
326 response = linphone_get_xml_text_content(xml_ctx, "/d:multistatus/d:response/d:propstat/d:prop/x1:getctag");
327 if (response) {
328 result = atoi(response);
329 linphone_free_xml_text_content(response);
330 }
331 }
332 end:
333 linphone_xmlparsing_context_destroy(xml_ctx);
334 return result;
335 }
336
linphone_carddav_query_free(LinphoneCardDavQuery * query)337 static void linphone_carddav_query_free(LinphoneCardDavQuery *query) {
338 if (!query) {
339 return;
340 }
341
342 if (query->http_request_listener) {
343 belle_sip_object_unref(query->http_request_listener);
344 query->http_request_listener = NULL;
345 }
346
347 // Context will be freed later (in sync_done)
348 query->context = NULL;
349
350 if (query->url) {
351 ms_free(query->url);
352 }
353 if (query->body) {
354 ms_free(query->body);
355 }
356
357 ms_free(query);
358 }
359
is_query_client_to_server_sync(LinphoneCardDavQuery * query)360 static bool_t is_query_client_to_server_sync(LinphoneCardDavQuery *query) {
361 if (!query) {
362 ms_error("[carddav] query is NULL...");
363 return FALSE;
364 }
365 switch(query->type) {
366 case LinphoneCardDavQueryTypePropfind:
367 case LinphoneCardDavQueryTypeAddressbookQuery:
368 case LinphoneCardDavQueryTypeAddressbookMultiget:
369 return FALSE;
370 case LinphoneCardDavQueryTypePut:
371 case LinphoneCardDavQueryTypeDelete:
372 return TRUE;
373 default:
374 ms_error("[carddav] Unknown request: %i", query->type);
375 }
376 return FALSE;
377 }
378
process_response_from_carddav_request(void * data,const belle_http_response_event_t * event)379 static void process_response_from_carddav_request(void *data, const belle_http_response_event_t *event) {
380 LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)data;
381
382 if (event->response) {
383 int code = belle_http_response_get_status_code(event->response);
384 if (code == 207 || code == 200 || code == 201 || code == 204) {
385 const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response);
386 switch(query->type) {
387 case LinphoneCardDavQueryTypePropfind:
388 linphone_carddav_ctag_fetched(query->context, parse_ctag_value_from_xml_response(body));
389 break;
390 case LinphoneCardDavQueryTypeAddressbookQuery:
391 linphone_carddav_vcards_fetched(query->context, parse_vcards_etags_from_xml_response(body));
392 break;
393 case LinphoneCardDavQueryTypeAddressbookMultiget:
394 linphone_carddav_vcards_pulled(query->context, parse_vcards_from_xml_response(body));
395 break;
396 case LinphoneCardDavQueryTypePut:
397 {
398 belle_sip_header_t *header = belle_sip_message_get_header((belle_sip_message_t *)event->response, "ETag");
399 LinphoneFriend *lf = (LinphoneFriend *)query->user_data;
400 LinphoneVcard *lvc = linphone_friend_get_vcard(lf);
401 if (lf && lvc) {
402 if (header) {
403 const char *etag = belle_sip_header_get_unparsed_value(header);
404 if (!linphone_vcard_get_etag(lvc)) {
405 ms_debug("eTag for newly created vCard is: %s", etag);
406 } else {
407 ms_debug("eTag for updated vCard is: %s", etag);
408 }
409 linphone_vcard_set_etag(lvc, etag);
410
411 linphone_carddav_client_to_server_sync_done(query->context, TRUE, NULL);
412 linphone_friend_unref(lf);
413 } else {
414 // For some reason, server didn't return the eTag of the updated/created vCard
415 // We need to do a GET on the vCard to get the correct one
416 bctbx_list_t *vcard = NULL;
417 LinphoneCardDavResponse *response = (LinphoneCardDavResponse *)ms_new0(LinphoneCardDavResponse, 1);
418 response->url = ms_strdup(linphone_vcard_get_url(lvc));
419 vcard = bctbx_list_append(vcard, response);
420 linphone_carddav_pull_vcards(query->context, vcard);
421 bctbx_list_free_with_data(vcard, (void (*)(void *))linphone_carddav_response_free);
422 }
423 }
424 else {
425 linphone_carddav_client_to_server_sync_done(query->context, FALSE, "No LinphoneFriend found in user_data field of query");
426 }
427 }
428 break;
429 case LinphoneCardDavQueryTypeDelete:
430 linphone_carddav_client_to_server_sync_done(query->context, TRUE, NULL);
431 break;
432 default:
433 ms_error("[carddav] Unknown request: %i", query->type);
434 break;
435 }
436 } else {
437 char msg[100];
438 snprintf(msg, sizeof(msg), "Unexpected HTTP response code: %i", code);
439 if (is_query_client_to_server_sync(query)) {
440 linphone_carddav_client_to_server_sync_done(query->context, FALSE, msg);
441 } else {
442 linphone_carddav_server_to_client_sync_done(query->context, FALSE, msg);
443 }
444 }
445 } else {
446 if (is_query_client_to_server_sync(query)) {
447 linphone_carddav_client_to_server_sync_done(query->context, FALSE, "No response found");
448 } else {
449 linphone_carddav_server_to_client_sync_done(query->context, FALSE, "No response found");
450 }
451 }
452 linphone_carddav_query_free(query);
453 }
454
process_io_error_from_carddav_request(void * data,const belle_sip_io_error_event_t * event)455 static void process_io_error_from_carddav_request(void *data, const belle_sip_io_error_event_t *event) {
456 LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)data;
457 ms_error("[carddav] I/O error during CardDAV request sending");
458 if (is_query_client_to_server_sync(query)) {
459 linphone_carddav_client_to_server_sync_done(query->context, FALSE, "I/O error during CardDAV request sending");
460 } else {
461 linphone_carddav_server_to_client_sync_done(query->context, FALSE, "I/O error during CardDAV request sending");
462 }
463 linphone_carddav_query_free(query);
464 }
465
process_auth_requested_from_carddav_request(void * data,belle_sip_auth_event_t * event)466 static void process_auth_requested_from_carddav_request(void *data, belle_sip_auth_event_t *event) {
467 LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)data;
468 LinphoneCardDavContext *cdc = query->context;
469 const char *realm = belle_sip_auth_event_get_realm(event);
470 belle_generic_uri_t *uri = belle_generic_uri_parse(query->url);
471 const char *domain = belle_generic_uri_get_host(uri);
472
473 if (cdc->auth_info) {
474 belle_sip_auth_event_set_username(event, cdc->auth_info->username);
475 belle_sip_auth_event_set_passwd(event, cdc->auth_info->passwd);
476 belle_sip_auth_event_set_ha1(event, cdc->auth_info->ha1);
477 } else {
478 LinphoneCore *lc = cdc->friend_list->lc;
479 const bctbx_list_t *auth_infos = linphone_core_get_auth_info_list(lc);
480
481 ms_debug("Looking for auth info for domain %s and realm %s", domain, realm);
482 while (auth_infos) {
483 LinphoneAuthInfo *auth_info = (LinphoneAuthInfo *)auth_infos->data;
484 if (auth_info->domain && strcmp(domain, auth_info->domain) == 0) {
485 if (!auth_info->realm || strcmp(realm, auth_info->realm) == 0) {
486 belle_sip_auth_event_set_username(event, auth_info->username);
487 belle_sip_auth_event_set_passwd(event, auth_info->passwd);
488 belle_sip_auth_event_set_ha1(event, auth_info->ha1);
489 cdc->auth_info = linphone_auth_info_clone(auth_info);
490 break;
491 }
492 }
493 auth_infos = bctbx_list_next(auth_infos);
494 }
495
496 if (!auth_infos) {
497 ms_error("[carddav] Authentication requested during CardDAV request sending, and username/password weren't provided");
498 if (is_query_client_to_server_sync(query)) {
499 linphone_carddav_client_to_server_sync_done(query->context, FALSE, "Authentication requested during CardDAV request sending, and username/password weren't provided");
500 } else {
501 linphone_carddav_server_to_client_sync_done(query->context, FALSE, "Authentication requested during CardDAV request sending, and username/password weren't provided");
502 }
503 linphone_carddav_query_free(query);
504 }
505 }
506 }
507
linphone_carddav_send_query(LinphoneCardDavQuery * query)508 static void linphone_carddav_send_query(LinphoneCardDavQuery *query) {
509 belle_http_request_listener_callbacks_t cbs = { 0 };
510 belle_generic_uri_t *uri = NULL;
511 belle_http_request_t *req = NULL;
512 belle_sip_memory_body_handler_t *bh = NULL;
513 LinphoneCardDavContext *cdc = query->context;
514 char* ua = NULL;
515
516 uri = belle_generic_uri_parse(query->url);
517 if (!uri) {
518 if (cdc && cdc->sync_done_cb) {
519 cdc->sync_done_cb(cdc, FALSE, "Could not send request, URL is invalid");
520 }
521 belle_sip_error("Could not send request, URL %s is invalid", query->url);
522 linphone_carddav_query_free(query);
523 return;
524 }
525 req = belle_http_request_create(query->method, uri, belle_sip_header_content_type_create("application", "xml; charset=utf-8"), NULL);
526
527 if (!req) {
528 if (cdc && cdc->sync_done_cb) {
529 cdc->sync_done_cb(cdc, FALSE, "Could not create belle_http_request_t");
530 }
531 belle_sip_object_unref(uri);
532 belle_sip_error("Could not create belle_http_request_t");
533 linphone_carddav_query_free(query);
534 return;
535 }
536
537 ua = ms_strdup_printf("%s/%s", linphone_core_get_user_agent(cdc->friend_list->lc), linphone_core_get_version());
538 belle_sip_message_add_header((belle_sip_message_t *)req, belle_sip_header_create("User-Agent", ua));
539 ms_free(ua);
540 if (query->depth) {
541 belle_sip_message_add_header((belle_sip_message_t *)req, belle_sip_header_create("Depth", query->depth));
542 } else if (query->ifmatch) {
543 belle_sip_message_add_header((belle_sip_message_t *)req, belle_sip_header_create("If-Match", query->ifmatch));
544 } else if (strcmp(query->method, "PUT")) {
545 belle_sip_message_add_header((belle_sip_message_t *)req, belle_sip_header_create("If-None-Match", "*"));
546 }
547
548 if (query->body) {
549 bh = belle_sip_memory_body_handler_new_copy_from_buffer(query->body, strlen(query->body), NULL, NULL);
550 belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(req), bh ? BELLE_SIP_BODY_HANDLER(bh) : NULL);
551 }
552
553 cbs.process_response = process_response_from_carddav_request;
554 cbs.process_io_error = process_io_error_from_carddav_request;
555 cbs.process_auth_requested = process_auth_requested_from_carddav_request;
556 query->http_request_listener = belle_http_request_listener_create_from_callbacks(&cbs, query);
557 belle_http_provider_send_request(query->context->friend_list->lc->http_provider, req, query->http_request_listener);
558 }
559
linphone_carddav_create_put_query(LinphoneCardDavContext * cdc,LinphoneVcard * lvc)560 static LinphoneCardDavQuery* linphone_carddav_create_put_query(LinphoneCardDavContext *cdc, LinphoneVcard *lvc) {
561 LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1);
562 query->context = cdc;
563 query->depth = NULL;
564 query->ifmatch = linphone_vcard_get_etag(lvc);
565 query->body = ms_strdup(linphone_vcard_as_vcard4_string(lvc));
566 query->method = "PUT";
567 query->url = ms_strdup(linphone_vcard_get_url(lvc));
568 query->type = LinphoneCardDavQueryTypePut;
569 return query;
570 }
571
generate_url_from_server_address_and_uid(const char * server_url)572 static char* generate_url_from_server_address_and_uid(const char *server_url) {
573 char *result = NULL;
574 if (server_url) {
575 char *uuid = ms_malloc(64);
576 if (sal_generate_uuid(uuid, 64) == 0) {
577 char *url = ms_malloc(300);
578 snprintf(url, 300, "%s/linphone-%s.vcf", server_url, uuid);
579 ms_debug("Generated url is %s", url);
580 result = ms_strdup(url);
581 ms_free(url);
582 }
583 ms_free(uuid);
584 }
585 return result;
586 }
587
linphone_carddav_put_vcard(LinphoneCardDavContext * cdc,LinphoneFriend * lf)588 void linphone_carddav_put_vcard(LinphoneCardDavContext *cdc, LinphoneFriend *lf) {
589 LinphoneVcard *lvc = linphone_friend_get_vcard(lf);
590 if (lvc) {
591 LinphoneCardDavQuery *query = NULL;
592 if (!linphone_vcard_get_uid(lvc)) {
593 linphone_vcard_generate_unique_id(lvc);
594 }
595
596 if (!linphone_vcard_get_url(lvc)) {
597 char *url = generate_url_from_server_address_and_uid(cdc->friend_list->uri);
598 if (url) {
599 linphone_vcard_set_url(lvc, url);
600 ms_free(url);
601 } else {
602 const char *msg = "vCard doesn't have an URL, and friendlist doesn't have a CardDAV server set either, can't push it";
603 ms_warning("%s", msg);
604 if (cdc && cdc->sync_done_cb) {
605 cdc->sync_done_cb(cdc, FALSE, msg);
606 }
607 return;
608 }
609 }
610
611 query = linphone_carddav_create_put_query(cdc, lvc);
612 query->user_data = linphone_friend_ref(lf);
613 linphone_carddav_send_query(query);
614 } else {
615 const char *msg = NULL;
616 if (!lvc) {
617 msg = "LinphoneVcard is NULL";
618 } else {
619 msg = "Unknown error";
620 }
621
622 if (msg) {
623 ms_error("[carddav] %s", msg);
624 }
625
626 if (cdc && cdc->sync_done_cb) {
627 cdc->sync_done_cb(cdc, FALSE, msg);
628 }
629 }
630 }
631
linphone_carddav_create_delete_query(LinphoneCardDavContext * cdc,LinphoneVcard * lvc)632 static LinphoneCardDavQuery* linphone_carddav_create_delete_query(LinphoneCardDavContext *cdc, LinphoneVcard *lvc) {
633 LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1);
634 query->context = cdc;
635 query->depth = NULL;
636 query->ifmatch = linphone_vcard_get_etag(lvc);
637 query->body = NULL;
638 query->method = "DELETE";
639 query->url = ms_strdup(linphone_vcard_get_url(lvc));
640 query->type = LinphoneCardDavQueryTypeDelete;
641 return query;
642 }
643
linphone_carddav_delete_vcard(LinphoneCardDavContext * cdc,LinphoneFriend * lf)644 void linphone_carddav_delete_vcard(LinphoneCardDavContext *cdc, LinphoneFriend *lf) {
645 LinphoneVcard *lvc = linphone_friend_get_vcard(lf);
646 if (lvc && linphone_vcard_get_uid(lvc) && linphone_vcard_get_etag(lvc)) {
647 LinphoneCardDavQuery *query = NULL;
648
649 if (!linphone_vcard_get_url(lvc)) {
650 char *url = generate_url_from_server_address_and_uid(cdc->friend_list->uri);
651 if (url) {
652 linphone_vcard_set_url(lvc, url);
653 ms_free(url);
654 } else {
655 const char *msg = "vCard doesn't have an URL, and friendlist doesn't have a CardDAV server set either, can't delete it";
656 ms_warning("%s", msg);
657 if (cdc && cdc->sync_done_cb) {
658 cdc->sync_done_cb(cdc, FALSE, msg);
659 }
660 return;
661 }
662 }
663
664 query = linphone_carddav_create_delete_query(cdc, lvc);
665 linphone_carddav_send_query(query);
666 } else {
667 const char *msg = NULL;
668 if (!lvc) {
669 msg = "LinphoneVcard is NULL";
670 } else if (!linphone_vcard_get_uid(lvc)) {
671 msg = "LinphoneVcard doesn't have an UID";
672 } else if (!linphone_vcard_get_etag(lvc)) {
673 msg = "LinphoneVcard doesn't have an eTag";
674 }
675
676 if (msg) {
677 ms_error("[carddav] %s", msg);
678 }
679
680 if (cdc && cdc->sync_done_cb) {
681 cdc->sync_done_cb(cdc, FALSE, msg);
682 }
683 }
684 }
685
linphone_carddav_set_synchronization_done_callback(LinphoneCardDavContext * cdc,LinphoneCardDavSynchronizationDoneCb cb)686 void linphone_carddav_set_synchronization_done_callback(LinphoneCardDavContext *cdc, LinphoneCardDavSynchronizationDoneCb cb) {
687 cdc->sync_done_cb = cb;
688 }
689
linphone_carddav_set_new_contact_callback(LinphoneCardDavContext * cdc,LinphoneCardDavContactCreatedCb cb)690 void linphone_carddav_set_new_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactCreatedCb cb) {
691 cdc->contact_created_cb = cb;
692 }
693
linphone_carddav_set_updated_contact_callback(LinphoneCardDavContext * cdc,LinphoneCardDavContactUpdatedCb cb)694 void linphone_carddav_set_updated_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactUpdatedCb cb) {
695 cdc->contact_updated_cb = cb;
696 }
697
linphone_carddav_set_removed_contact_callback(LinphoneCardDavContext * cdc,LinphoneCardDavContactRemovedCb cb)698 void linphone_carddav_set_removed_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactRemovedCb cb) {
699 cdc->contact_removed_cb = cb;
700 }
701
linphone_carddav_create_propfind_query(LinphoneCardDavContext * cdc)702 static LinphoneCardDavQuery* linphone_carddav_create_propfind_query(LinphoneCardDavContext *cdc) {
703 LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1);
704 query->context = cdc;
705 query->depth = "0";
706 query->ifmatch = NULL;
707 query->body = ms_strdup("<d:propfind xmlns:d=\"DAV:\" xmlns:cs=\"http://calendarserver.org/ns/\"><d:prop><cs:getctag /></d:prop></d:propfind>");
708 query->method = "PROPFIND";
709 query->url = ms_strdup(cdc->friend_list->uri);
710 query->type = LinphoneCardDavQueryTypePropfind;
711 return query;
712 }
713
linphone_carddav_get_current_ctag(LinphoneCardDavContext * cdc)714 void linphone_carddav_get_current_ctag(LinphoneCardDavContext *cdc) {
715 LinphoneCardDavQuery *query = linphone_carddav_create_propfind_query(cdc);
716 linphone_carddav_send_query(query);
717 }
718
linphone_carddav_create_addressbook_query(LinphoneCardDavContext * cdc)719 static LinphoneCardDavQuery* linphone_carddav_create_addressbook_query(LinphoneCardDavContext *cdc) {
720 LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1);
721 query->context = cdc;
722 query->depth = "1";
723 query->ifmatch = NULL;
724 query->body = ms_strdup("<card:addressbook-query xmlns:d=\"DAV:\" xmlns:card=\"urn:ietf:params:xml:ns:carddav\"><d:prop><d:getetag /></d:prop><card:filter></card:filter></card:addressbook-query>");
725 query->method = "REPORT";
726 query->url = ms_strdup(cdc->friend_list->uri);
727 query->type = LinphoneCardDavQueryTypeAddressbookQuery;
728 return query;
729 }
730
linphone_carddav_fetch_vcards(LinphoneCardDavContext * cdc)731 void linphone_carddav_fetch_vcards(LinphoneCardDavContext *cdc) {
732 LinphoneCardDavQuery *query = linphone_carddav_create_addressbook_query(cdc);
733 linphone_carddav_send_query(query);
734 }
735
linphone_carddav_create_addressbook_multiget_query(LinphoneCardDavContext * cdc,bctbx_list_t * vcards)736 static LinphoneCardDavQuery* linphone_carddav_create_addressbook_multiget_query(LinphoneCardDavContext *cdc, bctbx_list_t *vcards) {
737 LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1);
738 char *body = (char *)ms_malloc((bctbx_list_size(vcards) + 1) * 300 * sizeof(char));
739 bctbx_list_t *iterator = vcards;
740
741 query->context = cdc;
742 query->depth = "1";
743 query->ifmatch = NULL;
744 query->method = "REPORT";
745 query->url = ms_strdup(cdc->friend_list->uri);
746 query->type = LinphoneCardDavQueryTypeAddressbookMultiget;
747
748 sprintf(body, "%s", "<card:addressbook-multiget xmlns:d=\"DAV:\" xmlns:card=\"urn:ietf:params:xml:ns:carddav\"><d:prop><d:getetag /><card:address-data content-type='text/vcard' version='4.0'/></d:prop>");
749 while (iterator) {
750 LinphoneCardDavResponse *response = (LinphoneCardDavResponse *)iterator->data;
751 if (response) {
752 char temp_body[300];
753 snprintf(temp_body, sizeof(temp_body), "<d:href>%s</d:href>", response->url);
754 strcat(body, temp_body);
755 iterator = bctbx_list_next(iterator);
756 }
757 }
758 strcat(body, "</card:addressbook-multiget>");
759 query->body = ms_strdup(body);
760 ms_free(body);
761
762 return query;
763 }
764
linphone_carddav_pull_vcards(LinphoneCardDavContext * cdc,bctbx_list_t * vcards_to_pull)765 void linphone_carddav_pull_vcards(LinphoneCardDavContext *cdc, bctbx_list_t *vcards_to_pull) {
766 LinphoneCardDavQuery *query = linphone_carddav_create_addressbook_multiget_query(cdc, vcards_to_pull);
767 linphone_carddav_send_query(query);
768 }
769