carddav.c 28.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
carddav.c
Copyright (C) 2015  Belledonne Communications SARL

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
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 22
#include "private.h"

23
LinphoneCardDavContext* linphone_carddav_context_new(LinphoneFriendList *lfl) {
24
	LinphoneCardDavContext *carddav_context = NULL;
25 26 27 28 29

	if (!linphone_core_vcard_supported()) {
		ms_error("[carddav] vCard isn't available (maybe it wasn't compiled), can't do CardDAV sync");
		return NULL;
	}
30
	if (!lfl || !lfl->uri) {
31 32
		return NULL;
	}
33

34
	carddav_context = (LinphoneCardDavContext *)ms_new0(LinphoneCardDavContext, 1);
35
	carddav_context->friend_list = linphone_friend_list_ref(lfl);
36 37 38
	return carddav_context;
}

39
void linphone_carddav_context_destroy(LinphoneCardDavContext *cdc) {
40
	if (cdc) {
41 42 43 44
		if (cdc->friend_list) {
			linphone_friend_list_unref(cdc->friend_list);
			cdc->friend_list = NULL;
		}
45
		if (cdc->auth_info) {
46
			linphone_auth_info_unref(cdc->auth_info);
47 48
			cdc->auth_info = NULL;
		}
49 50 51 52 53 54 55 56 57 58 59 60
		ms_free(cdc);
	}
}

void linphone_carddav_set_user_data(LinphoneCardDavContext *cdc, void *ud) {
	cdc->user_data = ud;
}

void* linphone_carddav_get_user_data(LinphoneCardDavContext *cdc) {
	return cdc->user_data;
}

61
void linphone_carddav_synchronize(LinphoneCardDavContext *cdc) {
62
	cdc->ctag = cdc->friend_list->revision;
63 64 65
	linphone_carddav_get_current_ctag(cdc);
}

66 67 68 69
static void linphone_carddav_client_to_server_sync_done(LinphoneCardDavContext *cdc, bool_t success, const char *msg) {
	if (!success) {
		ms_error("[carddav] CardDAV client to server sync failure: %s", msg);
	}
70

71 72 73 74 75 76
	if (cdc->sync_done_cb) {
		cdc->sync_done_cb(cdc, success, msg);
	}
}

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 85 86 87 88
	if (cdc->sync_done_cb) {
		cdc->sync_done_cb(cdc, success, msg);
	}
}

89
static int find_matching_friend(LinphoneFriend *lf1, LinphoneFriend *lf2) {
90 91
	LinphoneVcard *lvc1 = linphone_friend_get_vcard(lf1);
	LinphoneVcard *lvc2 = linphone_friend_get_vcard(lf2);
92 93 94 95 96 97
	const char *uid1 = NULL, *uid2 = NULL;
	if (!lvc1 || !lvc2) {
		return 1;
	}
	uid1 = linphone_vcard_get_uid(lvc1);
	uid2 = linphone_vcard_get_uid(lvc2);
Sylvain Berfini's avatar
Sylvain Berfini committed
98 99 100 101
	if (!uid1 || !uid2) {
		return 1;
	}
	return strcmp(uid1, uid2);
102 103
}

104
static void linphone_carddav_response_free(LinphoneCardDavResponse *response) {
Sylvain Berfini's avatar
Sylvain Berfini committed
105 106 107
	if (response->etag) ms_free(response->etag);
	if (response->url) ms_free(response->url);
	if (response->vcard) ms_free(response->vcard);
108 109 110
	ms_free(response);
}

111
static void linphone_carddav_vcards_pulled(LinphoneCardDavContext *cdc, bctbx_list_t *vCards) {
Sylvain Berfini's avatar
Sylvain Berfini committed
112
	bctbx_list_t *vCards_remember = vCards;
113 114
	if (vCards != NULL && bctbx_list_size(vCards) > 0) {
		bctbx_list_t *friends = cdc->friend_list->friends;
115 116 117
		while (vCards) {
			LinphoneCardDavResponse *vCard = (LinphoneCardDavResponse *)vCards->data;
			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 123 124 125
				if (lvc) {
					// Compute downloaded vCards' URL and save it (+ eTag)
					char *vCard_name = strrchr(vCard->url, '/');
					char full_url[300];
126
					snprintf(full_url, sizeof(full_url), "%s%s", cdc->friend_list->uri, vCard_name);
127 128
					linphone_vcard_set_url(lvc, full_url);
					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 137 138 139 140 141 142 143 144
						if (local_friend) {
							LinphoneFriend *lf2 = (LinphoneFriend *)local_friend->data;
							lf->storage_id = lf2->storage_id;
							lf->pol = lf2->pol;
							lf->subscribe = lf2->subscribe;
							lf->refkey = ms_strdup(lf2->refkey);
							lf->presence_received = lf2->presence_received;
							lf->lc = lf2->lc;
							lf->friend_list = lf2->friend_list;
145

146 147 148 149 150 151 152 153 154 155 156 157 158
							if (cdc->contact_updated_cb) {
								ms_debug("Contact updated: %s", linphone_friend_get_name(lf));
								cdc->contact_updated_cb(cdc, lf, lf2);
							}
						} else {
							if (cdc->contact_created_cb) {
								ms_debug("Contact created: %s", linphone_friend_get_name(lf));
								cdc->contact_created_cb(cdc, lf);
							}
						}
						linphone_friend_unref(lf);
					} else {
						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
		}
Sylvain Berfini's avatar
Sylvain Berfini committed
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
}

171 172
static bctbx_list_t* parse_vcards_from_xml_response(const char *body) {
	bctbx_list_t *result = NULL;
173 174 175 176 177 178 179
	xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new();
	xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
	xml_ctx->doc = xmlReadDoc((const unsigned char*)body, 0, NULL, 0);
	if (xml_ctx->doc != NULL) {
		if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end;
		linphone_xml_xpath_context_init_carddav_ns(xml_ctx);
		{
180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
			xmlXPathObjectPtr responses = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/d:multistatus/d:response");
			if (responses != NULL && responses->nodesetval != NULL) {
				xmlNodeSetPtr responses_nodes = responses->nodesetval;
				if (responses_nodes->nodeNr >= 1) {
					int i;
					for (i = 0; i < responses_nodes->nodeNr; i++) {
						xmlNodePtr response_node = responses_nodes->nodeTab[i];
						xml_ctx->xpath_ctx->node = response_node;
						{
							char *etag = linphone_get_xml_text_content(xml_ctx, "d:propstat/d:prop/d:getetag");
							char *url =  linphone_get_xml_text_content(xml_ctx, "d:href");
							char *vcard = linphone_get_xml_text_content(xml_ctx, "d:propstat/d:prop/card:address-data");
							LinphoneCardDavResponse *response = ms_new0(LinphoneCardDavResponse, 1);
							response->etag = ms_strdup(etag);
							response->url = ms_strdup(url);
							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);
Sylvain Berfini's avatar
Sylvain Berfini committed
198 199 200
							linphone_free_xml_text_content(etag);
							linphone_free_xml_text_content(url);
							linphone_free_xml_text_content(vcard);
201 202 203
						}
					}
				}
204
				xmlXPathFreeObject(responses);
205 206 207 208 209 210 211 212
			}
		}
	}
end:
	linphone_xmlparsing_context_destroy(xml_ctx);
	return result;
}

213
static int find_matching_vcard(LinphoneCardDavResponse *response, LinphoneFriend *lf) {
214
	if (!response->url || !lf || !lf->vcard || !linphone_vcard_get_url(lf->vcard)) {
Sylvain Berfini's avatar
Sylvain Berfini committed
215 216
		return 1;
	}
217
	return strcmp(response->url, linphone_vcard_get_url(lf->vcard));
218 219
}

220 221 222 223 224
static void linphone_carddav_vcards_fetched(LinphoneCardDavContext *cdc, bctbx_list_t *vCards) {
	if (vCards != NULL && bctbx_list_size(vCards) > 0) {
		bctbx_list_t *friends = cdc->friend_list->friends;
		bctbx_list_t *friends_to_remove = NULL;
		bctbx_list_t *temp_list = NULL;
225

226 227 228
		while (friends) {
			LinphoneFriend *lf = (LinphoneFriend *)friends->data;
			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 234 235 236
				} else {
					LinphoneCardDavResponse *response = (LinphoneCardDavResponse *)vCard->data;
					ms_debug("Local friend %s is in the remote vCard list, check eTag", linphone_friend_get_name(lf));
					if (response) {
237
						LinphoneVcard *lvc = linphone_friend_get_vcard(lf);
238 239 240
						const char *etag = linphone_vcard_get_etag(lvc);
						ms_debug("Local friend eTag is %s, remote vCard eTag is %s", etag, response->etag);
						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 251 252 253
		while(friends_to_remove) {
			LinphoneFriend *lf = (LinphoneFriend *)friends_to_remove->data;
			if (lf) {
				if (cdc->contact_removed_cb) {
254
					ms_debug("Contact removed: %s", linphone_friend_get_name(lf));
255 256 257
					cdc->contact_removed_cb(cdc, lf);
				}
			}
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
}

267 268
static bctbx_list_t* parse_vcards_etags_from_xml_response(const char *body) {
	bctbx_list_t *result = NULL;
269 270 271 272 273 274 275
	xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new();
	xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
	xml_ctx->doc = xmlReadDoc((const unsigned char*)body, 0, NULL, 0);
	if (xml_ctx->doc != NULL) {
		if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end;
		linphone_xml_xpath_context_init_carddav_ns(xml_ctx);
		{
276 277 278 279 280 281 282 283 284 285 286 287 288 289
			xmlXPathObjectPtr responses = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/d:multistatus/d:response");
			if (responses != NULL && responses->nodesetval != NULL) {
				xmlNodeSetPtr responses_nodes = responses->nodesetval;
				if (responses_nodes->nodeNr >= 1) {
					int i;
					for (i = 0; i < responses_nodes->nodeNr; i++) {
						xmlNodePtr response_node = responses_nodes->nodeTab[i];
						xml_ctx->xpath_ctx->node = response_node;
						{
							char *etag = linphone_get_xml_text_content(xml_ctx, "d:propstat/d:prop/d:getetag");
							char *url =  linphone_get_xml_text_content(xml_ctx, "d:href");
							LinphoneCardDavResponse *response = ms_new0(LinphoneCardDavResponse, 1);
							response->etag = ms_strdup(etag);
							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);
Sylvain Berfini's avatar
Sylvain Berfini committed
292 293
							linphone_free_xml_text_content(etag);
							linphone_free_xml_text_content(url);
294 295 296
						}
					}
				}
297
				xmlXPathFreeObject(responses);
298 299
			}
		}
300
	}
301 302 303
end:
	linphone_xmlparsing_context_destroy(xml_ctx);
	return result;
304 305 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 309 310 311 312
	if (ctag == -1 || ctag > cdc->ctag) {
		cdc->ctag = ctag;
		linphone_carddav_fetch_vcards(cdc);
	} else {
		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
	}
}

317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
static int parse_ctag_value_from_xml_response(const char *body) {
	int result = -1;
	xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new();
	xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
	xml_ctx->doc = xmlReadDoc((const unsigned char*)body, 0, NULL, 0);
	if (xml_ctx->doc != NULL) {
		char *response = NULL;
		if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end;
		linphone_xml_xpath_context_init_carddav_ns(xml_ctx);
		response = linphone_get_xml_text_content(xml_ctx, "/d:multistatus/d:response/d:propstat/d:prop/x1:getctag");
		if (response) {
			result = atoi(response);
			linphone_free_xml_text_content(response);
		}
	}
end:
	linphone_xmlparsing_context_destroy(xml_ctx);
	return result;
335 336
}

337 338 339 340
static void linphone_carddav_query_free(LinphoneCardDavQuery *query) {
	if (!query) {
		return;
	}
341

342 343 344 345
	if (query->http_request_listener) {
		belle_sip_object_unref(query->http_request_listener);
		query->http_request_listener = NULL;
	}
346

347 348
	// Context will be freed later (in sync_done)
	query->context = NULL;
349

Sylvain Berfini's avatar
Sylvain Berfini committed
350 351 352 353 354 355
	if (query->url) {
		ms_free(query->url);
	}
	if (query->body) {
		ms_free(query->body);
	}
356

357 358 359
	ms_free(query);
}

360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
static bool_t is_query_client_to_server_sync(LinphoneCardDavQuery *query) {
	if (!query) {
		ms_error("[carddav] query is NULL...");
		return FALSE;
	}
	switch(query->type) {
		case LinphoneCardDavQueryTypePropfind:
		case LinphoneCardDavQueryTypeAddressbookQuery:
		case LinphoneCardDavQueryTypeAddressbookMultiget:
			return FALSE;
		case LinphoneCardDavQueryTypePut:
		case LinphoneCardDavQueryTypeDelete:
			return TRUE;
		default:
			ms_error("[carddav] Unknown request: %i", query->type);
	}
	return FALSE;
}

379 380
static void process_response_from_carddav_request(void *data, const belle_http_response_event_t *event) {
	LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)data;
381

382 383
	if (event->response) {
		int code = belle_http_response_get_status_code(event->response);
384
		if (code == 207 || code == 200 || code == 201 || code == 204) {
385 386 387
			const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response);
			switch(query->type) {
			case LinphoneCardDavQueryTypePropfind:
388
				linphone_carddav_ctag_fetched(query->context, parse_ctag_value_from_xml_response(body));
389 390
				break;
			case LinphoneCardDavQueryTypeAddressbookQuery:
391
				linphone_carddav_vcards_fetched(query->context, parse_vcards_etags_from_xml_response(body));
392 393
				break;
			case LinphoneCardDavQueryTypeAddressbookMultiget:
394
				linphone_carddav_vcards_pulled(query->context, parse_vcards_from_xml_response(body));
395
				break;
396 397 398 399
			case LinphoneCardDavQueryTypePut:
				{
					belle_sip_header_t *header = belle_sip_message_get_header((belle_sip_message_t *)event->response, "ETag");
					LinphoneFriend *lf = (LinphoneFriend *)query->user_data;
400
					LinphoneVcard *lvc = linphone_friend_get_vcard(lf);
401 402
					if (lf && lvc) {
						if (header) {
403
							const char *etag = belle_sip_header_get_unparsed_value(header);
404 405 406 407 408
							if (!linphone_vcard_get_etag(lvc)) {
								ms_debug("eTag for newly created vCard is: %s", etag);
							} else {
								ms_debug("eTag for updated vCard is: %s", etag);
							}
409
							linphone_vcard_set_etag(lvc, etag);
410

411
							linphone_carddav_client_to_server_sync_done(query->context, TRUE, NULL);
412 413 414 415
							linphone_friend_unref(lf);
						} else {
							// For some reason, server didn't return the eTag of the updated/created vCard
							// 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);
Sylvain Berfini's avatar
Sylvain Berfini committed
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 429
					}
				}
				break;
			case LinphoneCardDavQueryTypeDelete:
430
				linphone_carddav_client_to_server_sync_done(query->context, TRUE, NULL);
431 432
				break;
			default:
433
				ms_error("[carddav] Unknown request: %i", query->type);
434
				break;
435 436
			}
		} else {
437 438
			char msg[100];
			snprintf(msg, sizeof(msg), "Unexpected HTTP response code: %i", code);
439 440 441 442 443
			if (is_query_client_to_server_sync(query)) {
				linphone_carddav_client_to_server_sync_done(query->context, FALSE, msg);
			} else {
				linphone_carddav_server_to_client_sync_done(query->context, FALSE, msg);
			}
444
		}
445
	} else {
446 447 448 449 450
		if (is_query_client_to_server_sync(query)) {
			linphone_carddav_client_to_server_sync_done(query->context, FALSE, "No response found");
		} else {
			linphone_carddav_server_to_client_sync_done(query->context, FALSE, "No response found");
		}
451
	}
452
	linphone_carddav_query_free(query);
453 454 455 456
}

static void process_io_error_from_carddav_request(void *data, const belle_sip_io_error_event_t *event) {
	LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)data;
457
	ms_error("[carddav] I/O error during CardDAV request sending");
458 459 460 461 462
	if (is_query_client_to_server_sync(query)) {
		linphone_carddav_client_to_server_sync_done(query->context, FALSE, "I/O error during CardDAV request sending");
	} else {
		linphone_carddav_server_to_client_sync_done(query->context, FALSE, "I/O error during CardDAV request sending");
	}
463
	linphone_carddav_query_free(query);
464 465 466
}

static void process_auth_requested_from_carddav_request(void *data, belle_sip_auth_event_t *event) {
467 468 469 470 471
	LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)data;
	LinphoneCardDavContext *cdc = query->context;
	const char *realm = belle_sip_auth_event_get_realm(event);
	belle_generic_uri_t *uri = belle_generic_uri_parse(query->url);
	const char *domain = belle_generic_uri_get_host(uri);
472

473 474 475 476 477 478
	if (cdc->auth_info) {
		belle_sip_auth_event_set_username(event, cdc->auth_info->username);
		belle_sip_auth_event_set_passwd(event, cdc->auth_info->passwd);
		belle_sip_auth_event_set_ha1(event, cdc->auth_info->ha1);
	} else {
		LinphoneCore *lc = cdc->friend_list->lc;
479
		const bctbx_list_t *auth_infos = linphone_core_get_auth_info_list(lc);
480

481 482 483 484 485 486 487 488 489 490 491
		ms_debug("Looking for auth info for domain %s and realm %s", domain, realm);
		while (auth_infos) {
			LinphoneAuthInfo *auth_info = (LinphoneAuthInfo *)auth_infos->data;
			if (auth_info->domain && strcmp(domain, auth_info->domain) == 0) {
				if (!auth_info->realm || strcmp(realm, auth_info->realm) == 0) {
					belle_sip_auth_event_set_username(event, auth_info->username);
					belle_sip_auth_event_set_passwd(event, auth_info->passwd);
					belle_sip_auth_event_set_ha1(event, auth_info->ha1);
					cdc->auth_info = linphone_auth_info_clone(auth_info);
					break;
				}
492
			}
493
			auth_infos = bctbx_list_next(auth_infos);
494
		}
495

496 497 498 499 500 501 502 503
		if (!auth_infos) {
			ms_error("[carddav] Authentication requested during CardDAV request sending, and username/password weren't provided");
			if (is_query_client_to_server_sync(query)) {
				linphone_carddav_client_to_server_sync_done(query->context, FALSE, "Authentication requested during CardDAV request sending, and username/password weren't provided");
			} else {
				linphone_carddav_server_to_client_sync_done(query->context, FALSE, "Authentication requested during CardDAV request sending, and username/password weren't provided");
			}
			linphone_carddav_query_free(query);
504
		}
505
	}
506 507 508 509 510 511 512
}

static void linphone_carddav_send_query(LinphoneCardDavQuery *query) {
	belle_http_request_listener_callbacks_t cbs = { 0 };
	belle_generic_uri_t *uri = NULL;
	belle_http_request_t *req = NULL;
	belle_sip_memory_body_handler_t *bh = NULL;
513 514
	LinphoneCardDavContext *cdc = query->context;
	char* ua = NULL;
515 516 517

	uri = belle_generic_uri_parse(query->url);
	if (!uri) {
518 519 520
		if (cdc && cdc->sync_done_cb) {
			cdc->sync_done_cb(cdc, FALSE, "Could not send request, URL is invalid");
		}
521
		belle_sip_error("Could not send request, URL %s is invalid", query->url);
522
		linphone_carddav_query_free(query);
523 524
		return;
	}
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 529 530
		if (cdc && cdc->sync_done_cb) {
			cdc->sync_done_cb(cdc, FALSE, "Could not create belle_http_request_t");
		}
531 532
		belle_sip_object_unref(uri);
		belle_sip_error("Could not create belle_http_request_t");
533
		linphone_carddav_query_free(query);
534 535
		return;
	}
536

537 538 539
	ua = ms_strdup_printf("%s/%s", linphone_core_get_user_agent(cdc->friend_list->lc), linphone_core_get_version());
	belle_sip_message_add_header((belle_sip_message_t *)req, belle_sip_header_create("User-Agent", ua));
	ms_free(ua);
540 541 542 543 544 545 546
	if (query->depth) {
		belle_sip_message_add_header((belle_sip_message_t *)req, belle_sip_header_create("Depth", query->depth));
	} else if (query->ifmatch) {
		belle_sip_message_add_header((belle_sip_message_t *)req, belle_sip_header_create("If-Match", query->ifmatch));
	} else if (strcmp(query->method, "PUT")) {
		belle_sip_message_add_header((belle_sip_message_t *)req, belle_sip_header_create("If-None-Match", "*"));
	}
547

548 549 550 551
	if (query->body) {
		bh = belle_sip_memory_body_handler_new_copy_from_buffer(query->body, strlen(query->body), NULL, NULL);
		belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(req), bh ? BELLE_SIP_BODY_HANDLER(bh) : NULL);
	}
552

553 554 555
	cbs.process_response = process_response_from_carddav_request;
	cbs.process_io_error = process_io_error_from_carddav_request;
	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
}

560
static LinphoneCardDavQuery* linphone_carddav_create_put_query(LinphoneCardDavContext *cdc, LinphoneVcard *lvc) {
561 562 563 564
	LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1);
	query->context = cdc;
	query->depth = NULL;
	query->ifmatch = linphone_vcard_get_etag(lvc);
Sylvain Berfini's avatar
Sylvain Berfini committed
565
	query->body = ms_strdup(linphone_vcard_as_vcard4_string(lvc));
566
	query->method = "PUT";
Sylvain Berfini's avatar
Sylvain Berfini committed
567
	query->url = ms_strdup(linphone_vcard_get_url(lvc));
568 569 570 571 572
	query->type = LinphoneCardDavQueryTypePut;
	return query;
}

static char* generate_url_from_server_address_and_uid(const char *server_url) {
573
	char *result = NULL;
574
	if (server_url) {
Ghislain MARY's avatar
Ghislain MARY committed
575 576 577 578 579 580
		char *uuid = sal_generate_uuid();
		char *url = (char *)(ms_malloc(300));
		snprintf(url, 300, "%s/linphone-%s.vcf", server_url, uuid);
		ms_debug("Generated url is %s", url);
		result = ms_strdup(url);
		ms_free(url);
581
		ms_free(uuid);
582
	}
583
	return result;
584 585
}

586
void linphone_carddav_put_vcard(LinphoneCardDavContext *cdc, LinphoneFriend *lf) {
587
	LinphoneVcard *lvc = linphone_friend_get_vcard(lf);
588
	if (lvc) {
589
		LinphoneCardDavQuery *query = NULL;
590 591 592
		if (!linphone_vcard_get_uid(lvc)) {
			linphone_vcard_generate_unique_id(lvc);
		}
593

594
		if (!linphone_vcard_get_url(lvc)) {
595
			char *url = generate_url_from_server_address_and_uid(cdc->friend_list->uri);
596 597 598 599 600
			if (url) {
				linphone_vcard_set_url(lvc, url);
				ms_free(url);
			} else {
				const char *msg = "vCard doesn't have an URL, and friendlist doesn't have a CardDAV server set either, can't push it";
Sylvain Berfini's avatar
Sylvain Berfini committed
601
				ms_warning("%s", msg);
602 603 604 605 606
				if (cdc && cdc->sync_done_cb) {
					cdc->sync_done_cb(cdc, FALSE, msg);
				}
				return;
			}
607
		}
608

609 610 611 612 613 614
		query = linphone_carddav_create_put_query(cdc, lvc);
		query->user_data = linphone_friend_ref(lf);
		linphone_carddav_send_query(query);
	} else {
		const char *msg = NULL;
		if (!lvc) {
615
			msg = "LinphoneVcard is NULL";
616 617
		} else {
			msg = "Unknown error";
618
		}
619

620
		if (msg) {
621
			ms_error("[carddav] %s", msg);
622
		}
623

624 625 626 627 628 629
		if (cdc && cdc->sync_done_cb) {
			cdc->sync_done_cb(cdc, FALSE, msg);
		}
	}
}

630
static LinphoneCardDavQuery* linphone_carddav_create_delete_query(LinphoneCardDavContext *cdc, LinphoneVcard *lvc) {
631 632 633 634
	LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1);
	query->context = cdc;
	query->depth = NULL;
	query->ifmatch = linphone_vcard_get_etag(lvc);
635
	query->body = NULL;
636
	query->method = "DELETE";
Sylvain Berfini's avatar
Sylvain Berfini committed
637
	query->url = ms_strdup(linphone_vcard_get_url(lvc));
638 639
	query->type = LinphoneCardDavQueryTypeDelete;
	return query;
640 641 642
}

void linphone_carddav_delete_vcard(LinphoneCardDavContext *cdc, LinphoneFriend *lf) {
643
	LinphoneVcard *lvc = linphone_friend_get_vcard(lf);
644 645
	if (lvc && linphone_vcard_get_uid(lvc) && linphone_vcard_get_etag(lvc)) {
		LinphoneCardDavQuery *query = NULL;
646

647
		if (!linphone_vcard_get_url(lvc)) {
648
			char *url = generate_url_from_server_address_and_uid(cdc->friend_list->uri);
649 650 651 652 653 654 655 656 657 658 659
			if (url) {
				linphone_vcard_set_url(lvc, url);
				ms_free(url);
			} else {
				const char *msg = "vCard doesn't have an URL, and friendlist doesn't have a CardDAV server set either, can't delete it";
				ms_warning("%s", msg);
				if (cdc && cdc->sync_done_cb) {
					cdc->sync_done_cb(cdc, FALSE, msg);
				}
				return;
			}
660
		}
661

662 663 664 665 666
		query = linphone_carddav_create_delete_query(cdc, lvc);
		linphone_carddav_send_query(query);
	} else {
		const char *msg = NULL;
		if (!lvc) {
667
			msg = "LinphoneVcard is NULL";
668
		} else if (!linphone_vcard_get_uid(lvc)) {
669
			msg = "LinphoneVcard doesn't have an UID";
670
		} else if (!linphone_vcard_get_etag(lvc)) {
671
			msg = "LinphoneVcard doesn't have an eTag";
672
		}
673

674
		if (msg) {
675
			ms_error("[carddav] %s", msg);
676
		}
677

678 679 680 681
		if (cdc && cdc->sync_done_cb) {
			cdc->sync_done_cb(cdc, FALSE, msg);
		}
	}
682 683 684 685 686 687
}

void linphone_carddav_set_synchronization_done_callback(LinphoneCardDavContext *cdc, LinphoneCardDavSynchronizationDoneCb cb) {
	cdc->sync_done_cb = cb;
}

688 689 690 691 692 693 694 695 696 697 698 699
void linphone_carddav_set_new_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactCreatedCb cb) {
	cdc->contact_created_cb = cb;
}

void linphone_carddav_set_updated_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactUpdatedCb cb) {
	cdc->contact_updated_cb = cb;
}

void linphone_carddav_set_removed_contact_callback(LinphoneCardDavContext *cdc, LinphoneCardDavContactRemovedCb cb) {
	cdc->contact_removed_cb = cb;
}

700 701 702 703
static LinphoneCardDavQuery* linphone_carddav_create_propfind_query(LinphoneCardDavContext *cdc) {
	LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1);
	query->context = cdc;
	query->depth = "0";
704
	query->ifmatch = NULL;
Sylvain Berfini's avatar
Sylvain Berfini committed
705
	query->body = ms_strdup("<d:propfind xmlns:d=\"DAV:\" xmlns:cs=\"http://calendarserver.org/ns/\"><d:prop><cs:getctag /></d:prop></d:propfind>");
706
	query->method = "PROPFIND";
Sylvain Berfini's avatar
Sylvain Berfini committed
707
	query->url = ms_strdup(cdc->friend_list->uri);
708 709 710 711 712 713 714 715 716 717 718 719 720
	query->type = LinphoneCardDavQueryTypePropfind;
	return query;
}

void linphone_carddav_get_current_ctag(LinphoneCardDavContext *cdc) {
	LinphoneCardDavQuery *query = linphone_carddav_create_propfind_query(cdc);
	linphone_carddav_send_query(query);
}

static LinphoneCardDavQuery* linphone_carddav_create_addressbook_query(LinphoneCardDavContext *cdc) {
	LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1);
	query->context = cdc;
	query->depth = "1";
721
	query->ifmatch = NULL;
Sylvain Berfini's avatar
Sylvain Berfini committed
722
	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>");
723
	query->method = "REPORT";
Sylvain Berfini's avatar
Sylvain Berfini committed
724
	query->url = ms_strdup(cdc->friend_list->uri);
725 726 727 728 729 730 731 732 733
	query->type = LinphoneCardDavQueryTypeAddressbookQuery;
	return query;
}

void linphone_carddav_fetch_vcards(LinphoneCardDavContext *cdc) {
	LinphoneCardDavQuery *query = linphone_carddav_create_addressbook_query(cdc);
	linphone_carddav_send_query(query);
}

734
static LinphoneCardDavQuery* linphone_carddav_create_addressbook_multiget_query(LinphoneCardDavContext *cdc, bctbx_list_t *vcards) {
735
	LinphoneCardDavQuery *query = (LinphoneCardDavQuery *)ms_new0(LinphoneCardDavQuery, 1);
736 737
	char *body = (char *)ms_malloc((bctbx_list_size(vcards) + 1) * 300 * sizeof(char));
	bctbx_list_t *iterator = vcards;
738

739 740
	query->context = cdc;
	query->depth = "1";
741
	query->ifmatch = NULL;
742
	query->method = "REPORT";
Sylvain Berfini's avatar
Sylvain Berfini committed
743
	query->url = ms_strdup(cdc->friend_list->uri);
744
	query->type = LinphoneCardDavQueryTypeAddressbookMultiget;
745

746
	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>");
747 748
	while (iterator) {
		LinphoneCardDavResponse *response = (LinphoneCardDavResponse *)iterator->data;
749
		if (response) {
750 751
			char temp_body[300];
			snprintf(temp_body, sizeof(temp_body), "<d:href>%s</d:href>", response->url);
Sylvain Berfini's avatar
Sylvain Berfini committed
752
			strcat(body, temp_body);
753
			iterator = bctbx_list_next(iterator);
754
		}
755
	}
Sylvain Berfini's avatar
Sylvain Berfini committed
756
	strcat(body, "</card:addressbook-multiget>");
757
	query->body = ms_strdup(body);
Sylvain Berfini's avatar
Sylvain Berfini committed
758
	ms_free(body);
759

760 761 762
	return query;
}

763
void linphone_carddav_pull_vcards(LinphoneCardDavContext *cdc, bctbx_list_t *vcards_to_pull) {
764 765
	LinphoneCardDavQuery *query = linphone_carddav_create_addressbook_multiget_query(cdc, vcards_to_pull);
	linphone_carddav_send_query(query);
766
}