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