friendlist.c 35.3 KB
Newer Older
<
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
linphone
Copyright (C) 2010-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 21 22
*/

#include "linphonecore.h"
#include "private.h"

johan's avatar
johan committed
23
#include <bctoolbox/crypto.h>
24

25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneFriendListCbs);

BELLE_SIP_INSTANCIATE_VPTR(LinphoneFriendListCbs, belle_sip_object_t,
	NULL, // destroy
	NULL, // clone
	NULL, // Marshall
	FALSE
);

static LinphoneFriendListCbs * linphone_friend_list_cbs_new(void) {
	return belle_sip_object_new(LinphoneFriendListCbs);
}

LinphoneFriendListCbs * linphone_friend_list_get_callbacks(const LinphoneFriendList *list) {
	return list->cbs;
}

LinphoneFriendListCbs * linphone_friend_list_cbs_ref(LinphoneFriendListCbs *cbs) {
	belle_sip_object_ref(cbs);
	return cbs;
}

void linphone_friend_list_cbs_unref(LinphoneFriendListCbs *cbs) {
	belle_sip_object_unref(cbs);
}

void *linphone_friend_list_cbs_get_user_data(const LinphoneFriendListCbs *cbs) {
	return cbs->user_data;
}

void linphone_friend_list_cbs_set_user_data(LinphoneFriendListCbs *cbs, void *ud) {
	cbs->user_data = ud;
}

59
LinphoneFriendListCbsContactCreatedCb linphone_friend_list_cbs_get_contact_created(const LinphoneFriendListCbs *cbs) {
60 61 62
	return cbs->contact_created_cb;
}

63
void linphone_friend_list_cbs_set_contact_created(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactCreatedCb cb) {
64 65 66
	cbs->contact_created_cb = cb;
}

67
LinphoneFriendListCbsContactDeletedCb linphone_friend_list_cbs_get_contact_deleted(const LinphoneFriendListCbs *cbs) {
68 69 70
	return cbs->contact_deleted_cb;
}

71
void linphone_friend_list_cbs_set_contact_deleted(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactDeletedCb cb) {
72 73
	cbs->contact_deleted_cb = cb;
}
74

75
LinphoneFriendListCbsContactUpdatedCb linphone_friend_list_cbs_get_contact_updated(const LinphoneFriendListCbs *cbs) {
76 77 78
	return cbs->contact_updated_cb;
}

79
void linphone_friend_list_cbs_set_contact_updated(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsContactUpdatedCb cb) {
80 81 82
	cbs->contact_updated_cb = cb;
}

83
LinphoneFriendListCbsSyncStateChangedCb linphone_friend_list_cbs_get_sync_status_changed(const LinphoneFriendListCbs *cbs) {
84 85 86
	return cbs->sync_state_changed_cb;
}

87
void linphone_friend_list_cbs_set_sync_status_changed(LinphoneFriendListCbs *cbs, LinphoneFriendListCbsSyncStateChangedCb cb) {
88 89 90
	cbs->sync_state_changed_cb = cb;
}

91 92 93 94 95 96 97 98 99 100 101 102 103 104
static int add_uri_entry(xmlTextWriterPtr writer, int err, const char *uri) {
	if (err >= 0) {
		err = xmlTextWriterStartElement(writer, (const xmlChar *)"entry");
	}
	if (err >= 0) {
		err = xmlTextWriterWriteAttribute(writer, (const xmlChar *)"uri", (const xmlChar *)uri);
	}
	if (err >= 0) {
		/* Close the "entry" element. */
		err = xmlTextWriterEndElement(writer);
	}
	return err;
}

105 106
static char * create_resource_list_xml(const LinphoneFriendList *list) {
	char *xml_content = NULL;
107
	bctbx_list_t *elem;
108 109 110 111
	xmlBufferPtr buf;
	xmlTextWriterPtr writer;
	int err;

112
	if (bctbx_list_size(list->friends) <= 0) return NULL;
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137

	buf = xmlBufferCreate();
	if (buf == NULL) {
		ms_error("%s: Error creating the XML buffer", __FUNCTION__);
		return NULL;
	}
	writer = xmlNewTextWriterMemory(buf, 0);
	if (writer == NULL) {
		ms_error("%s: Error creating the XML writer", __FUNCTION__);
		return NULL;
	}

	xmlTextWriterSetIndent(writer,1);
	err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL);
	if (err >= 0) {
		err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"resource-lists", (const xmlChar *)"urn:ietf:params:xml:ns:resource-lists");
	}
	if (err >= 0) {
		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"xsi",
						    NULL, (const xmlChar *)"http://www.w3.org/2001/XMLSchema-instance");
	}

	if (err>= 0) {
		err = xmlTextWriterStartElement(writer, (const xmlChar *)"list");
	}
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
	for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
		bctbx_list_t *iterator;
		bctbx_list_t *addresses = linphone_friend_get_addresses(lf);
		bctbx_list_t *numbers = linphone_friend_get_phone_numbers(lf);
		iterator = addresses;
		while (iterator) {
			LinphoneAddress *addr = (LinphoneAddress *)bctbx_list_get_data(iterator);
			if (addr) {
				char *uri = linphone_address_as_string_uri_only(addr);
				if (uri) {
					err = add_uri_entry(writer, err, uri);
					ms_free(uri);
				}
			}
			iterator = bctbx_list_next(iterator);
154
		}
155 156 157
		iterator = numbers;
		while (iterator) {
			const char *number = (const char *)bctbx_list_get_data(iterator);
158 159
			const char *uri = linphone_friend_phone_number_to_sip_uri(lf, number);
			if (uri) err = add_uri_entry(writer, err, uri);
160
			iterator = bctbx_list_next(iterator);
161
		}
162
		if (addresses) bctbx_list_free_with_data(addresses, (bctbx_list_free_func)linphone_address_unref);
163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
	}
	if (err >= 0) {
		/* Close the "list" element. */
		err = xmlTextWriterEndElement(writer);
	}

	if (err >= 0) {
		/* Close the "resource-lists" element. */
		err = xmlTextWriterEndElement(writer);
	}
	if (err >= 0) {
		err = xmlTextWriterEndDocument(writer);
	}
	if (err > 0) {
		/* xmlTextWriterEndDocument returns the size of the content. */
		xml_content = ms_strdup((char *)buf->content);
	}
	xmlFreeTextWriter(writer);
	xmlBufferFree(buf);

	return xml_content;
}

186 187 188 189 190 191
static void linphone_friend_list_parse_multipart_related_body(LinphoneFriendList *list, const LinphoneContent *body, const char *first_part_body) {
	xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new();
	xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
	xml_ctx->doc = xmlReadDoc((const unsigned char*)first_part_body, 0, NULL, 0);
	if (xml_ctx->doc != NULL) {
		char xpath_str[MAX_XPATH_LENGTH];
192
		LinphoneFriend *lf;
193 194
		LinphoneContent *presence_part;
		xmlXPathObjectPtr resource_object;
195 196
		const char *version_str = NULL;
		const char *full_state_str = NULL;
197
		const char *uri = NULL;
198 199
		bool_t full_state = FALSE;
		int version;
200 201 202 203
		int i;

		if (linphone_create_xml_xpath_context(xml_ctx) < 0) goto end;
		xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"rlmi", (const xmlChar *)"urn:ietf:params:xml:ns:rlmi");
204 205 206 207 208 209 210

		version_str = linphone_get_xml_attribute_text_content(xml_ctx, "/rlmi:list", "version");
		if (version_str == NULL) {
			ms_warning("rlmi+xml: No version attribute in list");
			goto end;
		}
		version = atoi(version_str);
Ghislain MARY's avatar
Ghislain MARY committed
211
		linphone_free_xml_text_content(version_str);
212 213
		if (version < list->expected_notification_version) {
			ms_warning("rlmi+xml: Discarding received notification with version %d because %d was expected", version, list->expected_notification_version);
214
			linphone_friend_list_update_subscriptions(list, NULL, FALSE); /* Refresh subscription to get new full state notify. */
215 216 217 218 219 220 221 222 223
			goto end;
		}

		full_state_str = linphone_get_xml_attribute_text_content(xml_ctx, "/rlmi:list", "fullState");
		if (full_state_str == NULL) {
			ms_warning("rlmi+xml: No fullState attribute in list");
			goto end;
		}
		if ((strcmp(full_state_str, "true") == 0) || (strcmp(full_state_str, "1") == 0)) {
224
			bctbx_list_t *l = list->friends;
225 226
			for (; l != NULL; l = bctbx_list_next(l)) {
				lf = (LinphoneFriend *)bctbx_list_get_data(l);
227
				linphone_friend_clear_presence_models(lf);
228 229 230 231 232 233 234 235 236 237
			}
			full_state = TRUE;
		}
		linphone_free_xml_text_content(full_state_str);
		if ((list->expected_notification_version == 0) && (full_state == FALSE)) {
			ms_warning("rlmi+xml: Notification with version 0 is not full state, this is not valid");
			goto end;
		}
		list->expected_notification_version = version + 1;

238 239 240 241 242 243
		resource_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/rlmi:list/rlmi:resource");
		if ((resource_object != NULL) && (resource_object->nodesetval != NULL)) {
			for (i = 1; i <= resource_object->nodesetval->nodeNr; i++) {
				snprintf(xpath_str, sizeof(xpath_str), "/rlmi:list/rlmi:resource[%i]/@uri", i);
				uri = linphone_get_xml_text_content(xml_ctx, xpath_str);
				if (uri == NULL) continue;
244 245
				lf = linphone_friend_list_find_friend_by_uri(list, uri);
				if (lf != NULL) {
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
					const char *state = NULL;
					snprintf(xpath_str, sizeof(xpath_str),"/rlmi:list/rlmi:resource[%i]/rlmi:instance/@state", i);
					state = linphone_get_xml_text_content(xml_ctx, xpath_str);
					if ((state != NULL) && (strcmp(state, "active") == 0)) {
						const char *cid = NULL;
						snprintf(xpath_str, sizeof(xpath_str),"/rlmi:list/rlmi:resource[%i]/rlmi:instance/@cid", i);
						cid = linphone_get_xml_text_content(xml_ctx, xpath_str);
						if (cid != NULL) {
							presence_part = linphone_content_find_part_by_header(body, "Content-Id", cid);
							if (presence_part == NULL) {
								ms_warning("rlmi+xml: Cannot find part with Content-Id: %s", cid);
							} else {
								SalPresenceModel *presence = NULL;
								linphone_notify_parse_presence(linphone_content_get_type(presence_part), linphone_content_get_subtype(presence_part), linphone_content_get_string_buffer(presence_part), &presence);
								if (presence != NULL) {
261
									const char *phone_number = linphone_friend_sip_uri_to_phone_number(lf, uri);
262
									lf->presence_received = TRUE;
263
									if (phone_number) linphone_friend_set_presence_model_for_uri_or_tel(lf, phone_number, (LinphonePresenceModel *)presence);
264
									else linphone_friend_set_presence_model_for_uri_or_tel(lf, uri, (LinphonePresenceModel *)presence);
265
									if (full_state == FALSE) {
266 267
										if (phone_number)
											linphone_core_notify_notify_presence_received_for_uri_or_tel(list->lc, lf, phone_number, (LinphonePresenceModel *)presence);
268 269
										else
											linphone_core_notify_notify_presence_received_for_uri_or_tel(list->lc, lf, uri, (LinphonePresenceModel *)presence);
270
										linphone_core_notify_notify_presence_received(list->lc, lf);
271
									}
272
								}
Ghislain MARY's avatar
Ghislain MARY committed
273
								linphone_content_unref(presence_part);
274 275 276 277 278
							}
						}
						if (cid != NULL) linphone_free_xml_text_content(cid);
					}
					if (state != NULL) linphone_free_xml_text_content(state);
279
					lf->subscribe_active = TRUE;
280 281 282 283 284
				}
				linphone_free_xml_text_content(uri);
			}
		}
		if (resource_object != NULL) xmlXPathFreeObject(resource_object);
285 286

		if (full_state == TRUE) {
287 288 289
			bctbx_list_t *addresses;
			bctbx_list_t *numbers;
			bctbx_list_t *iterator;
290
			bctbx_list_t *l = list->friends;
291 292
			for (; l != NULL; l = bctbx_list_next(l)) {
				lf = (LinphoneFriend *)bctbx_list_get_data(l);
293 294 295 296 297 298 299 300 301 302 303
				addresses = linphone_friend_get_addresses(lf);
				numbers = linphone_friend_get_phone_numbers(lf);
				iterator = addresses;
				while (iterator) {
					LinphoneAddress *addr = (LinphoneAddress *)bctbx_list_get_data(iterator);
					char *uri = linphone_address_as_string_uri_only(addr);
					const LinphonePresenceModel *presence = linphone_friend_get_presence_model_for_uri_or_tel(lf, uri);
					linphone_core_notify_notify_presence_received_for_uri_or_tel(list->lc, lf, uri, presence);
					ms_free(uri);
					iterator = bctbx_list_next(iterator);
				}
Ghislain MARY's avatar
Ghislain MARY committed
304
				if (addresses) bctbx_list_free_with_data(addresses, (bctbx_list_free_func)linphone_address_unref);
305 306 307 308 309 310 311
				iterator = numbers;
				while (iterator) {
					const char *number = (const char *)bctbx_list_get_data(iterator);
					const LinphonePresenceModel *presence = linphone_friend_get_presence_model_for_uri_or_tel(lf, number);
					linphone_core_notify_notify_presence_received_for_uri_or_tel(list->lc, lf, number, presence);
					iterator = bctbx_list_next(iterator);
				}
Ghislain MARY's avatar
Ghislain MARY committed
312
				if (numbers) bctbx_list_free(numbers);
313 314
				if (linphone_friend_is_presence_received(lf) == TRUE) {
					linphone_core_notify_notify_presence_received(list->lc, lf);
315 316 317
				}
			}
		}
318 319 320 321 322 323 324 325
	} else {
		ms_warning("Wrongly formatted rlmi+xml body: %s", xml_ctx->errorBuffer);
	}

end:
	linphone_xmlparsing_context_destroy(xml_ctx);
}

326
static bool_t linphone_friend_list_has_subscribe_inactive(const LinphoneFriendList *list) {
327
	bctbx_list_t *l = list->friends;
328
	bool_t has_subscribe_inactive = FALSE;
329 330
	for (; l != NULL; l = bctbx_list_next(l)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(l);
331
		if (lf->subscribe_active != TRUE) {
332 333 334 335 336 337 338
			has_subscribe_inactive = TRUE;
			break;
		}
	}
	return has_subscribe_inactive;
}

339 340
static LinphoneFriendList * linphone_friend_list_new(void) {
	LinphoneFriendList *list = belle_sip_object_new(LinphoneFriendList);
341
	list->cbs = linphone_friend_list_cbs_new();
342
	list->enable_subscriptions = TRUE;
343 344 345 346
	belle_sip_object_ref(list);
	return list;
}

347 348 349
static void linphone_friend_list_destroy(LinphoneFriendList *list) {
	if (list->display_name != NULL) ms_free(list->display_name);
	if (list->rls_uri != NULL) ms_free(list->rls_uri);
350
	if (list->content_digest != NULL) ms_free(list->content_digest);
351 352 353
	if (list->event != NULL) {
		linphone_event_terminate(list->event);
		linphone_event_unref(list->event);
354
		list->event = NULL;
355
	}
356 357
	if (list->uri != NULL) ms_free(list->uri);
	if (list->cbs) linphone_friend_list_cbs_unref(list->cbs);
358 359
	if (list->dirty_friends_to_update) list->dirty_friends_to_update = bctbx_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref);
	if (list->friends) list->friends = bctbx_list_free_with_data(list->friends, (void (*)(void *))_linphone_friend_release);
360 361 362 363 364 365 366 367 368 369 370 371
}

BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneFriendList);

BELLE_SIP_INSTANCIATE_VPTR(LinphoneFriendList, belle_sip_object_t,
	(belle_sip_object_destroy_t)linphone_friend_list_destroy,
	NULL, // clone
	NULL, // marshal
	TRUE
);


372 373 374
LinphoneFriendList * linphone_core_create_friend_list(LinphoneCore *lc) {
	LinphoneFriendList *list = linphone_friend_list_new();
	list->lc = lc;
375 376 377 378 379 380 381 382
	return list;
}

LinphoneFriendList * linphone_friend_list_ref(LinphoneFriendList *list) {
	belle_sip_object_ref(list);
	return list;
}

383 384 385
void _linphone_friend_list_release(LinphoneFriendList *list){
	/*drops all references to core and unref*/
	list->lc = NULL;
Simon Morlat's avatar
Simon Morlat committed
386 387 388 389
	if (list->event != NULL) {
		linphone_event_unref(list->event);
		list->event = NULL;
	}
390 391 392 393
	if (list->cbs) {
		linphone_friend_list_cbs_unref(list->cbs);
		list->cbs = NULL;
	}
394
	if (list->dirty_friends_to_update) {
395
		list->dirty_friends_to_update = bctbx_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref);
396 397
	}
	if (list->friends) {
398
		list->friends = bctbx_list_free_with_data(list->friends, (void (*)(void *))_linphone_friend_release);
399
	}
400
	linphone_friend_list_unref(list);
401 402
}

403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
void linphone_friend_list_unref(LinphoneFriendList *list) {
	belle_sip_object_unref(list);
}

void * linphone_friend_list_get_user_data(const LinphoneFriendList *list) {
	return list->user_data;
}

void linphone_friend_list_set_user_data(LinphoneFriendList *list, void *ud) {
	list->user_data = ud;
}

const char * linphone_friend_list_get_display_name(const LinphoneFriendList *list) {
	return list->display_name;
}

void linphone_friend_list_set_display_name(LinphoneFriendList *list, const char *display_name) {
	if (list->display_name != NULL) {
		ms_free(list->display_name);
		list->display_name = NULL;
	}
	if (display_name != NULL) {
		list->display_name = ms_strdup(display_name);
426
		linphone_core_store_friends_list_in_db(list->lc, list);
427 428 429 430 431 432 433 434 435 436 437 438 439 440
	}
}

const char * linphone_friend_list_get_rls_uri(const LinphoneFriendList *list) {
	return list->rls_uri;
}

void linphone_friend_list_set_rls_uri(LinphoneFriendList *list, const char *rls_uri) {
	if (list->rls_uri != NULL) {
		ms_free(list->rls_uri);
		list->rls_uri = NULL;
	}
	if (rls_uri != NULL) {
		list->rls_uri = ms_strdup(rls_uri);
441
		linphone_core_store_friends_list_in_db(list->lc, list);
442 443 444
	}
}

445
static LinphoneFriendListStatus _linphone_friend_list_add_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t synchronize) {
Ghislain MARY's avatar
Ghislain MARY committed
446
	LinphoneFriendListStatus status = LinphoneFriendListInvalidFriend;
447
	LinphoneAddress *addr = linphone_friend_get_address(lf);
Ghislain MARY's avatar
Ghislain MARY committed
448

449
	if (!list || !addr || lf->friend_list) {
Sylvain Berfini's avatar
Sylvain Berfini committed
450 451
		if (!list)
			ms_error("linphone_friend_list_add_friend(): invalid list, null");
452
		if (!addr)
453
			ms_error("linphone_friend_list_add_friend(): invalid friend, no sip uri");
454
		if (lf->friend_list)
455
			ms_error("linphone_friend_list_add_friend(): invalid friend, already in list");
456
		if (addr) linphone_address_unref(addr);
457
		return status;
Simon Morlat's avatar
Simon Morlat committed
458
	}
459
	if (bctbx_list_find(list->friends, lf) != NULL) {
460 461 462
		char *tmp = NULL;
		if (addr) tmp = linphone_address_as_string(addr);
		ms_warning("Friend %s already in list [%s], ignored.", tmp ? tmp : "unknown", list->display_name);
Ghislain MARY's avatar
Ghislain MARY committed
463
		if (tmp) ms_free(tmp);
464
	} else {
Ghislain MARY's avatar
Ghislain MARY committed
465
		status = linphone_friend_list_import_friend(list, lf, synchronize);
466
		linphone_friend_save(lf, lf->lc);
467
	}
Ghislain MARY's avatar
Ghislain MARY committed
468
	if (addr) linphone_address_unref(addr);
Ghislain MARY's avatar
Ghislain MARY committed
469 470 471 472 473
	if (list->rls_uri == NULL) {
		/* Mimic the behaviour of linphone_core_add_friend() when a resource list server is not in use */
		linphone_friend_apply(lf, lf->lc);
	}
	return status;
474 475
}

476 477 478 479 480 481 482 483
LinphoneFriendListStatus linphone_friend_list_add_friend(LinphoneFriendList *list, LinphoneFriend *lf) {
	return _linphone_friend_list_add_friend(list, lf, TRUE);
}

LinphoneFriendListStatus linphone_friend_list_add_local_friend(LinphoneFriendList *list, LinphoneFriend *lf) {
	return _linphone_friend_list_add_friend(list, lf, FALSE);
}

484
LinphoneFriendListStatus linphone_friend_list_import_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t synchronize) {
485 486 487
	LinphoneAddress *addr = linphone_friend_get_address(lf);
	if (!addr || lf->friend_list) {
		if (!addr)
488 489 490
			ms_error("linphone_friend_list_add_friend(): invalid friend, no sip uri");
		if (lf->friend_list)
			ms_error("linphone_friend_list_add_friend(): invalid friend, already in list");
491
		if (addr) linphone_address_unref(addr);
Sylvain Berfini's avatar
Sylvain Berfini committed
492 493
		return LinphoneFriendListInvalidFriend;
	}
Ghislain MARY's avatar
Ghislain MARY committed
494
	linphone_address_unref(addr);
495
	lf->friend_list = list;
496
	lf->lc = list->lc;
497
	list->friends = bctbx_list_append(list->friends, linphone_friend_ref(lf));
498
	if (synchronize) {
499
		list->dirty_friends_to_update = bctbx_list_append(list->dirty_friends_to_update, linphone_friend_ref(lf));
500
	}
501 502 503
	return LinphoneFriendListOK;
}

504
static void carddav_done(LinphoneCardDavContext *cdc, bool_t success, const char *msg) {
505 506 507
	if (cdc && cdc->friend_list->cbs->sync_state_changed_cb) {
		cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, success ? LinphoneFriendListSyncSuccessful : LinphoneFriendListSyncFailure, msg);
	}
508 509 510
	linphone_carddav_context_destroy(cdc);
}

511
static LinphoneFriendListStatus _linphone_friend_list_remove_friend(LinphoneFriendList *list, LinphoneFriend *lf, bool_t remove_from_server) {
512
	bctbx_list_t *elem = bctbx_list_find(list->friends, lf);
513
	if (elem == NULL) return LinphoneFriendListNonExistentFriend;
514

515
#ifdef SQLITE_STORAGE_ENABLED
516 517 518
	if (lf && lf->lc && lf->lc->friends_db) {
		linphone_core_remove_friend_from_db(lf->lc, lf);
	}
519
#endif
520
	if (remove_from_server) {
521
		LinphoneVcard *lvc = linphone_friend_get_vcard(lf);
522 523
		if (lvc && linphone_vcard_get_uid(lvc)) {
			LinphoneCardDavContext *cdc = linphone_carddav_context_new(list);
524 525 526 527 528 529
			if (cdc) {
				cdc->sync_done_cb = carddav_done;
				if (cdc->friend_list->cbs->sync_state_changed_cb) {
					cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, LinphoneFriendListSyncStarted, NULL);
				}
				linphone_carddav_delete_vcard(cdc, lf);
530 531
			}
		}
532
	}
533 534 535
	if (!list->lc->friends_db_file) {
		linphone_core_write_friends_config(list->lc);
	}
536

537 538
	lf->friend_list = NULL;
	linphone_friend_unref(lf);
539
	list->friends = bctbx_list_remove_link(list->friends, elem);
540
	return LinphoneFriendListOK;
541
}
542

543 544 545 546
LinphoneFriendListStatus linphone_friend_list_remove_friend(LinphoneFriendList *list, LinphoneFriend *lf) {
	return _linphone_friend_list_remove_friend(list, lf, TRUE);
}

547
const bctbx_list_t * linphone_friend_list_get_friends(const LinphoneFriendList *list) {
548 549 550
	return list->friends;
}

551
void linphone_friend_list_update_dirty_friends(LinphoneFriendList *list) {
552
	bctbx_list_t *dirty_friends = list->dirty_friends_to_update;
553

554 555 556
	while (dirty_friends) {
		LinphoneCardDavContext *cdc = linphone_carddav_context_new(list);
		if (cdc) {
557
			LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(dirty_friends);
558
			cdc->sync_done_cb = carddav_done;
559
			if (lf) {
560 561 562
				if (cdc->friend_list->cbs->sync_state_changed_cb) {
					cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, LinphoneFriendListSyncStarted, NULL);
				}
563 564 565
				linphone_carddav_put_vcard(cdc, lf);
			}
		}
566
		dirty_friends = bctbx_list_next(dirty_friends);
567
	}
568
	list->dirty_friends_to_update = bctbx_list_free_with_data(list->dirty_friends_to_update, (void (*)(void *))linphone_friend_unref);
569 570 571
}

static void carddav_created(LinphoneCardDavContext *cdc, LinphoneFriend *lf) {
572
	if (cdc) {
573
		LinphoneFriendList *lfl = cdc->friend_list;
574
		linphone_friend_list_import_friend(lfl, lf, FALSE);
575
		if (cdc->friend_list->cbs->contact_created_cb) {
576
			cdc->friend_list->cbs->contact_created_cb(lfl, lf);
577
		}
578 579 580 581
	}
}

static void carddav_removed(LinphoneCardDavContext *cdc, LinphoneFriend *lf) {
582
	if (cdc) {
583
		LinphoneFriendList *lfl = cdc->friend_list;
584
		_linphone_friend_list_remove_friend(lfl, lf, FALSE);
585
		if (cdc->friend_list->cbs->contact_deleted_cb) {
586
			cdc->friend_list->cbs->contact_deleted_cb(lfl, lf);
587
		}
588 589 590
	}
}

591
static void carddav_updated(LinphoneCardDavContext *cdc, LinphoneFriend *lf_new, LinphoneFriend *lf_old) {
592 593
	if (cdc) {
		LinphoneFriendList *lfl = cdc->friend_list;
594
		bctbx_list_t *elem = bctbx_list_find(lfl->friends, lf_old);
595
		if (elem) {
596
			elem->data = linphone_friend_ref(lf_new);
597
		}
598
		linphone_core_store_friend_in_db(lf_new->lc, lf_new);
599

600 601 602
		if (cdc->friend_list->cbs->contact_updated_cb) {
			cdc->friend_list->cbs->contact_updated_cb(lfl, lf_new, lf_old);
		}
603
		linphone_friend_unref(lf_old);
604
	}
605 606
}

607
void linphone_friend_list_synchronize_friends_from_server(LinphoneFriendList *list) {
608
	LinphoneCardDavContext *cdc = NULL;
609

610 611 612 613
	if (!list || !list->uri || !list->lc) {
		ms_error("FATAL");
		return;
	}
614

615
	cdc = linphone_carddav_context_new(list);
616 617 618
	if (cdc) {
		cdc->contact_created_cb = carddav_created;
		cdc->contact_removed_cb = carddav_removed;
619
		cdc->contact_updated_cb = carddav_updated;
620
		cdc->sync_done_cb = carddav_done;
621 622 623
		if (cdc && cdc->friend_list->cbs->sync_state_changed_cb) {
			cdc->friend_list->cbs->sync_state_changed_cb(cdc->friend_list, LinphoneFriendListSyncStarted, NULL);
		}
624 625 626 627
		linphone_carddav_synchronize(cdc);
	}
}

628
LinphoneFriend * linphone_friend_list_find_friend_by_address(const LinphoneFriendList *list, const LinphoneAddress *address) {
629
	LinphoneFriend *lf = NULL;
630
	LinphoneFriend *result = NULL;
631
	const bctbx_list_t *elem;
632 633 634 635
	const char *param = linphone_address_get_uri_param(address, "user");
	bool_t find_phone_number = FALSE;

	if (param && (strcmp(param, "phone") == 0)) find_phone_number = TRUE;
636 637 638
	for (elem = list->friends; (elem != NULL) && (result == NULL); elem = bctbx_list_next(elem)) {
		bctbx_list_t *iterator;
		lf = (LinphoneFriend *)bctbx_list_get_data(elem);
639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657
		if (find_phone_number == TRUE) {
			char *uri = linphone_address_as_string_uri_only(address);
			const char *phone_number = linphone_friend_sip_uri_to_phone_number(lf, uri);
			bctbx_list_t *phone_numbers = linphone_friend_get_phone_numbers(lf);
			iterator = phone_numbers;
			ms_free(uri);
			while (iterator && (result == NULL)) {
				const char *number = (const char *)bctbx_list_get_data(iterator);
				if (strcmp(number, phone_number) == 0) result = lf;
				iterator = bctbx_list_next(iterator);
			}
		} else {
			bctbx_list_t *addresses = linphone_friend_get_addresses(lf);
			iterator = addresses;
			while (iterator && (result == NULL)) {
				LinphoneAddress *lfaddr = (LinphoneAddress *)bctbx_list_get_data(iterator);
				if (linphone_address_weak_equal(lfaddr, address)) result = lf;
				iterator = bctbx_list_next(iterator);
			}
Ghislain MARY's avatar
Ghislain MARY committed
658
			bctbx_list_free_with_data(addresses, (bctbx_list_free_func)linphone_address_unref);
659
		}
660
	}
661
	return result;
662 663 664 665
}

LinphoneFriend * linphone_friend_list_find_friend_by_uri(const LinphoneFriendList *list, const char *uri) {
	LinphoneAddress *address = linphone_address_new(uri);
666
	LinphoneFriend *lf = address ? linphone_friend_list_find_friend_by_address(list, address) : NULL;
667
	if (address) linphone_address_unref(address);
668
	return lf;
669 670 671
}

LinphoneFriend * linphone_friend_list_find_friend_by_ref_key(const LinphoneFriendList *list, const char *ref_key) {
672
	const bctbx_list_t *elem;
673
	if (ref_key == NULL) return NULL;
674 675
	for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
676
		if ((lf->refkey != NULL) && (strcmp(lf->refkey, ref_key) == 0)) return lf;
677 678 679 680 681
	}
	return NULL;
}

LinphoneFriend * linphone_friend_list_find_friend_by_inc_subscribe(const LinphoneFriendList *list, SalOp *op) {
682
	const bctbx_list_t *elem;
683 684
	for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
685
		if (bctbx_list_find(lf->insubs, op)) return lf;
686 687 688 689 690
	}
	return NULL;
}

LinphoneFriend * linphone_friend_list_find_friend_by_out_subscribe(const LinphoneFriendList *list, SalOp *op) {
691
	const bctbx_list_t *elem;
692 693
	for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
694
		if (lf->outsub && ((lf->outsub == op) || sal_op_is_forked_of(lf->outsub, op))) return lf;
695 696 697 698
	}
	return NULL;
}

699 700
static void linphone_friend_list_close_subscriptions(LinphoneFriendList *list) {
	/* FIXME we should wait until subscription to complete. */
jehan's avatar
jehan committed
701 702
	if (list->event) {
		linphone_event_terminate(list->event);
703 704
		linphone_event_unref(list->event);
		list->event = NULL;
705
	}
706
	bctbx_list_for_each(list->friends, (void (*)(void *))linphone_friend_close_subscriptions);
707 708 709
}

void linphone_friend_list_update_subscriptions(LinphoneFriendList *list, LinphoneProxyConfig *cfg, bool_t only_when_registered) {
710
	const bctbx_list_t *elem;
711 712 713 714 715 716
	if (list->rls_uri != NULL) {
		if (list->enable_subscriptions) {
			LinphoneAddress *address = linphone_address_new(list->rls_uri);
			char *xml_content = create_resource_list_xml(list);
			if ((address != NULL) && (xml_content != NULL) && (linphone_friend_list_has_subscribe_inactive(list) == TRUE)) {
				unsigned char digest[16];
jehan's avatar
jehan committed
717
				bctbx_md5((unsigned char *)xml_content, strlen(xml_content), digest);
718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749
				if ((list->event != NULL) && (list->content_digest != NULL) && (memcmp(list->content_digest, digest, sizeof(digest)) == 0)) {
					/* The content has not changed, only refresh the event. */
					linphone_event_refresh_subscribe(list->event);
				} else {
					LinphoneContent *content;
					int expires = lp_config_get_int(list->lc->config, "sip", "rls_presence_expires", 3600);
					list->expected_notification_version = 0;
					if (list->content_digest != NULL) ms_free(list->content_digest);
					list->content_digest = ms_malloc(sizeof(digest));
					memcpy(list->content_digest, digest, sizeof(digest));
					if (list->event != NULL) {
						linphone_event_terminate(list->event);
						linphone_event_unref(list->event);
					}
					list->event = linphone_core_create_subscribe(list->lc, address, "presence", expires);
					linphone_event_ref(list->event);
					linphone_event_set_internal(list->event, TRUE);
					linphone_event_add_custom_header(list->event, "Require", "recipient-list-subscribe");
					linphone_event_add_custom_header(list->event, "Supported", "eventlist");
					linphone_event_add_custom_header(list->event, "Accept", "multipart/related, application/pidf+xml, application/rlmi+xml");
					linphone_event_add_custom_header(list->event, "Content-Disposition", "recipient-list");
					content = linphone_core_create_content(list->lc);
					linphone_content_set_type(content, "application");
					linphone_content_set_subtype(content, "resource-lists+xml");
					linphone_content_set_string_buffer(content, xml_content);
					if (linphone_core_content_encoding_supported(list->lc, "deflate")) {
						linphone_content_set_encoding(content, "deflate");
						linphone_event_add_custom_header(list->event, "Accept-Encoding", "deflate");
					}
					linphone_event_send_subscribe(list->event, content);
					linphone_content_unref(content);
					linphone_event_set_user_data(list->event, list);
750
				}
751
			}
752 753 754 755
			if (address != NULL) linphone_address_unref(address);
			if (xml_content != NULL) ms_free(xml_content);
		} else {
			ms_message("Friends list [%p] subscription update skipped since subscriptions not enabled yet", list);
756
		}
757
	} else if (list->enable_subscriptions) {
758 759
		for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
			LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
760
			linphone_friend_update_subscribes(lf, cfg, only_when_registered);
761
		}
762 763 764 765
	}
}

void linphone_friend_list_invalidate_subscriptions(LinphoneFriendList *list) {
766
	const bctbx_list_t *elem;
767 768
	for (elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
769
		linphone_friend_invalidate_subscription(lf);
770 771 772 773
	}
}

void linphone_friend_list_notify_presence(LinphoneFriendList *list, LinphonePresenceModel *presence) {
774
	const bctbx_list_t *elem;
775 776
	for(elem = list->friends; elem != NULL; elem = bctbx_list_next(elem)) {
		LinphoneFriend *lf = (LinphoneFriend *)bctbx_list_get_data(elem);
777
		linphone_friend_notify(lf, presence);
778 779
	}
}
780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801

void linphone_friend_list_notify_presence_received(LinphoneFriendList *list, LinphoneEvent *lev, const LinphoneContent *body) {
	if (linphone_content_is_multipart(body)) {
		LinphoneContent *first_part;
		const char *type = linphone_content_get_type(body);
		const char *subtype = linphone_content_get_subtype(body);

		if ((strcmp(type, "multipart") != 0) || (strcmp(subtype, "related") != 0)) {
			ms_warning("multipart presence notified but it is not 'multipart/related'");
			return;
		}

		first_part = linphone_content_get_part(body, 0);
		if (first_part == NULL) {
			ms_warning("'multipart/related' presence notified but it doesn't contain any part");
			return;
		}

		type = linphone_content_get_type(first_part);
		subtype = linphone_content_get_subtype(first_part);
		if ((strcmp(type, "application") != 0) || (strcmp(subtype, "rlmi+xml") != 0)) {
			ms_warning("multipart presence notified but first part is not 'application/rlmi+xml'");
Ghislain MARY's avatar
Ghislain MARY committed
802
			linphone_content_unref(first_part);
803 804 805 806
			return;
		}

		linphone_friend_list_parse_multipart_related_body(list, body, linphone_content_get_string_buffer(first_part));
Ghislain MARY's avatar
Ghislain MARY committed
807
		linphone_content_unref(first_part);
808 809
	}
}
810 811 812 813 814 815 816 817 818 819 820 821

const char * linphone_friend_list_get_uri(const LinphoneFriendList *list) {
	return list->uri;
}

void linphone_friend_list_set_uri(LinphoneFriendList *list, const char *uri) {
	if (list->uri != NULL) {
		ms_free(list->uri);
		list->uri = NULL;
	}
	if (uri != NULL) {
		list->uri = ms_strdup(uri);
822
		linphone_core_store_friends_list_in_db(list->lc, list);
823 824 825 826 827
	}
}

void linphone_friend_list_update_revision(LinphoneFriendList *list, int rev) {
	list->revision = rev;
828
	linphone_core_store_friends_list_in_db(list->lc, list);
829 830
}

831 832 833 834 835 836 837 838 839 840 841
void linphone_friend_list_subscription_state_changed(LinphoneCore *lc, LinphoneEvent *lev, LinphoneSubscriptionState state) {
	LinphoneFriendList *list = (LinphoneFriendList *)linphone_event_get_user_data(lev);
	if (!list) {
		ms_warning("core [%p] Receiving unexpected state [%s] for event [%p], no associated friend list",lc
					, linphone_subscription_state_to_string(state)
				   , lev);
	} else {
		ms_message("Receiving new state [%s] for event [%p] for friend list [%p]"
				   , linphone_subscription_state_to_string(state)
				   , lev
				   , list);
842

843
		if (state == LinphoneSubscriptionOutgoingProgress && linphone_event_get_reason(lev) == LinphoneReasonNoMatch) {
844 845 846 847 848
			ms_message("Resseting version count for friend list [%p]",list);
			list->expected_notification_version = 0;
		}
	}
}
849

850
LinphoneCore* linphone_friend_list_get_core(const LinphoneFriendList *list) {
851 852
	return list->lc;
}
853 854

int linphone_friend_list_import_friends_from_vcard4_file(LinphoneFriendList *list, const char *vcard_file) {
855 856
	bctbx_list_t *vcards = NULL;
	bctbx_list_t *vcards_iterator = NULL;
857
	int count = 0;
858

859 860 861 862
	if (!linphone_core_vcard_supported()) {
		ms_error("vCard support wasn't enabled at compilation time");
		return -1;
	}
863 864 865 866
	if (!list) {
		ms_error("Can't import into a NULL list");
		return -1;
	}
867
	
868
	vcards = linphone_vcard_context_get_vcard_list_from_file(list->lc->vcard_context, vcard_file);
869 870 871 872 873
	vcards_iterator = vcards;
	if (!vcards) {
		ms_error("Failed to parse the file %s", vcard_file);
		return -1;
	}
874

875 876
	while (vcards_iterator != NULL && bctbx_list_get_data(vcards_iterator) != NULL) {
		LinphoneVcard *vcard = (LinphoneVcard *)bctbx_list_get_data(vcards_iterator);
877 878 879
		LinphoneFriend *lf = linphone_friend_new_from_vcard(vcard);
		if (lf) {
			if (LinphoneFriendListOK == linphone_friend_list_import_friend(list, lf, TRUE)) {
880
				linphone_friend_save(lf, lf->lc);
881 882 883 884 885 886
				count++;
			}
			linphone_friend_unref(lf);
		} else {
			linphone_vcard_free(vcard);
		}
887
		vcards_iterator = bctbx_list_next(vcards_iterator);
888
	}
889
	bctbx_list_free(vcards);
890
	linphone_core_store_friends_list_in_db(list->lc, list);
891 892 893 894
	return count;
}

int linphone_friend_list_import_friends_from_vcard4_buffer(LinphoneFriendList *list, const char *vcard_buffer) {
895 896
	bctbx_list_t *vcards = NULL;
	bctbx_list_t *vcards_iterator = NULL;
897
	int count = 0;
898

899 900 901 902
	if (!linphone_core_vcard_supported()) {
		ms_error("vCard support wasn't enabled at compilation time");
		return -1;
	}
903 904 905 906
	if (!list) {
		ms_error("Can't import into a NULL list");
		return -1;
	}
907
	
908
	vcards = linphone_vcard_context_get_vcard_list_from_buffer(list->lc->vcard_context, vcard_buffer);
909 910 911 912 913
	vcards_iterator = vcards;
	if (!vcards) {
		ms_error("Failed to parse the buffer");
		return -1;
	}
914

915 916
	while (vcards_iterator != NULL && bctbx_list_get_data(vcards_iterator) != NULL) {
		LinphoneVcard *vcard = (LinphoneVcard *)bctbx_list_get_data(vcards_iterator);
917 918 919 920 921 922 923 924 925
		LinphoneFriend *lf = linphone_friend_new_from_vcard(vcard);
		if (lf) {
			if (LinphoneFriendListOK == linphone_friend_list_import_friend(list, lf, TRUE)) {
				count++;
			}
			linphone_friend_unref(lf);
		} else {
			linphone_vcard_free(vcard);
		}
926
		vcards_iterator = bctbx_list_next(vcards_iterator);
927
	}
928
	bctbx_list_free(vcards);