From a0c95c4c41ee22f29b7e53e87dcb86b3ce464c48 Mon Sep 17 00:00:00 2001
From: Sylvain Berfini <sylvain.berfini@belledonne-communications.com>
Date: Tue, 18 Mar 2025 14:02:51 +0100
Subject: [PATCH] Added some APIs on Friend wrapping vCard methods

---
 include/linphone/api/c-friend.h | 41 +++++++++++++++++++++++++++++++--
 include/linphone/utils/utils.h  |  1 +
 src/c-wrapper/api/c-account.cpp | 19 +--------------
 src/c-wrapper/api/c-friend.cpp  | 36 +++++++++++++++++++++++++++++
 src/friend/friend.cpp           |  7 ++++--
 src/utils/utils.cpp             | 16 +++++++++++++
 6 files changed, 98 insertions(+), 22 deletions(-)

diff --git a/include/linphone/api/c-friend.h b/include/linphone/api/c-friend.h
index 4eea43a5e8..5de27a5301 100644
--- a/include/linphone/api/c-friend.h
+++ b/include/linphone/api/c-friend.h
@@ -161,7 +161,7 @@ LINPHONE_PUBLIC void linphone_friend_remove_phone_number_with_label(LinphoneFrie
                                                                     const LinphoneFriendPhoneNumber *phone_number);
 
 /**
- * Set the display name for this friend
+ * Sets the display name for this friend
  * @param linphone_friend #LinphoneFriend object @notnil
  * @param name the display name to set @maybenil
  * @return 0 if successful, -1 otherwise
@@ -169,12 +169,42 @@ LINPHONE_PUBLIC void linphone_friend_remove_phone_number_with_label(LinphoneFrie
 LINPHONE_PUBLIC LinphoneStatus linphone_friend_set_name(LinphoneFriend *linphone_friend, const char *name);
 
 /**
- * Get the display name for this friend
+ * Sets the last name for this friend if vCard is available
+ * @param linphone_friend #LinphoneFriend object @notnil
+ * @param last_name the last name to set @maybenil
+ * @return 0 if successful, -1 otherwise
+ */
+LINPHONE_PUBLIC LinphoneStatus linphone_friend_set_last_name(LinphoneFriend *linphone_friend, const char *last_name);
+
+/**
+ * Sets the first name for this friend is available
+ * @param linphone_friend #LinphoneFriend object @notnil
+ * @param first_name the first name to set @maybenil
+ * @return 0 if successful, -1 otherwise
+ */
+LINPHONE_PUBLIC LinphoneStatus linphone_friend_set_first_name(LinphoneFriend *linphone_friend, const char *first_name);
+
+/**
+ * Gets the display name for this friend
  * @param linphone_friend #LinphoneFriend object @notnil
  * @return The display name of this friend. @maybenil
  */
 LINPHONE_PUBLIC const char *linphone_friend_get_name(const LinphoneFriend *linphone_friend);
 
+/**
+ * Gets the last name for this friend if vCard exists
+ * @param linphone_friend #LinphoneFriend object @notnil
+ * @return The last name of this friend. @maybenil
+ */
+LINPHONE_PUBLIC const char *linphone_friend_get_last_name(const LinphoneFriend *linphone_friend);
+
+/**
+ * Gets the first name for this friend if vCard exists
+ * @param linphone_friend #LinphoneFriend object @notnil
+ * @return The first name of this friend. @maybenil
+ */
+LINPHONE_PUBLIC const char *linphone_friend_get_first_name(const LinphoneFriend *linphone_friend);
+
 /**
  * get subscription flag value
  * @param linphone_friend #LinphoneFriend object @notnil
@@ -357,6 +387,13 @@ LINPHONE_PUBLIC LinphoneCore *linphone_friend_get_core(const LinphoneFriend *lin
  */
 LINPHONE_PUBLIC LinphoneVcard *linphone_friend_get_vcard(const LinphoneFriend *linphone_friend);
 
+/**
+ * Returns the a string matching the vCard inside the friend, if any
+ * @param linphone_friend #LinphoneFriend object @notnil
+ * @return the vCard as a string or NULL. @maybenil
+ */
+LINPHONE_PUBLIC const char *linphone_friend_dump_vcard(const LinphoneFriend *linphone_friend);
+
 /**
  * Binds a vCard object to a friend
  * @param linphone_friend #LinphoneFriend object @notnil
diff --git a/include/linphone/utils/utils.h b/include/linphone/utils/utils.h
index b7668e2219..e2916d635b 100644
--- a/include/linphone/utils/utils.h
+++ b/include/linphone/utils/utils.h
@@ -140,6 +140,7 @@ inline std::string join(const std::vector<T> &elems, const S &delim) {
 }
 LINPHONE_PUBLIC std::string trim(const std::string &str);
 LINPHONE_PUBLIC std::string normalizeFilename(const std::string &str);
+LINPHONE_PUBLIC std::string flattenPhoneNumber(const std::string &str);
 
 template <typename T>
 inline const T &getEmptyConstRefObject() {
diff --git a/src/c-wrapper/api/c-account.cpp b/src/c-wrapper/api/c-account.cpp
index 99769b8a16..97f2c7de24 100644
--- a/src/c-wrapper/api/c-account.cpp
+++ b/src/c-wrapper/api/c-account.cpp
@@ -329,23 +329,6 @@ bool_t linphone_account_is_phone_number(const LinphoneAccount *account, const ch
 	return TRUE;
 }
 
-static char *linphone_account_flatten_phone_number(const char *number) {
-	char *unescaped_phone_number = belle_sip_username_unescape_unnecessary_characters(number);
-	char *result = reinterpret_cast<char *>(ms_malloc0(strlen(unescaped_phone_number) + 1));
-	char *w = result;
-	const char *r;
-
-	for (r = unescaped_phone_number; *r != '\0'; ++r) {
-		if (*r == '+' || isdigit(*r)) {
-			*w++ = *r;
-		}
-	}
-
-	*w++ = '\0';
-	belle_sip_free(unescaped_phone_number);
-	return result;
-}
-
 char *linphone_account_normalize_phone_number(const LinphoneAccount *account, const char *username) {
 	AccountLogContextualizer logContextualizer(account);
 
@@ -368,7 +351,7 @@ char *linphone_account_normalize_phone_number(const LinphoneAccount *account, co
 		linphone_account_params_unref(accountParams);
 	}
 
-	char *flatten = linphone_account_flatten_phone_number(username);
+	char *flatten = ms_strdup(Utils::flattenPhoneNumber(username).c_str());
 	lDebug() << "Flattened number is [" << flatten << "] for [" << username << "]";
 
 	// if local short number, do not add international prefix
diff --git a/src/c-wrapper/api/c-friend.cpp b/src/c-wrapper/api/c-friend.cpp
index dce4d693be..a534f8d899 100644
--- a/src/c-wrapper/api/c-friend.cpp
+++ b/src/c-wrapper/api/c-friend.cpp
@@ -171,6 +171,18 @@ const char *linphone_friend_get_name(const LinphoneFriend *lf) {
 	return L_STRING_TO_C(Friend::toCpp(lf)->getName());
 }
 
+const char *linphone_friend_get_last_name(const LinphoneFriend *lf) {
+	if (!lf) return NULL;
+	const std::shared_ptr<Vcard> vcard = Friend::toCpp(lf)->getVcard();
+	return vcard ? L_STRING_TO_C(vcard->getFamilyName()) : NULL;
+}
+
+const char *linphone_friend_get_first_name(const LinphoneFriend *lf) {
+	if (!lf) return NULL;
+	const std::shared_ptr<Vcard> vcard = Friend::toCpp(lf)->getVcard();
+	return vcard ? L_STRING_TO_C(vcard->getGivenName()) : NULL;
+}
+
 const char *linphone_friend_get_native_uri(const LinphoneFriend *lf) {
 	if (!lf) return NULL;
 	return L_STRING_TO_C(Friend::toCpp(lf)->getNativeUri());
@@ -241,6 +253,12 @@ LinphoneVcard *linphone_friend_get_vcard(const LinphoneFriend *lf) {
 	return vcard ? vcard->toC() : nullptr;
 }
 
+const char *linphone_friend_dump_vcard(const LinphoneFriend *lf) {
+	if (!lf) return NULL;
+	const std::shared_ptr<Vcard> vcard = Friend::toCpp(lf)->getVcard();
+	return vcard ? L_STRING_TO_C(vcard->asVcard4String()) : NULL;
+}
+
 bool_t linphone_friend_has_capability(const LinphoneFriend *lf, const LinphoneFriendCapability capability) {
 	return Friend::toCpp(lf)->hasCapability(capability);
 }
@@ -328,6 +346,24 @@ LinphoneStatus linphone_friend_set_name(LinphoneFriend *lf, const char *name) {
 	return Friend::toCpp(lf)->setName(L_C_TO_STRING(name));
 }
 
+LinphoneStatus linphone_friend_set_last_name(LinphoneFriend *lf, const char *last_name) {
+	const std::shared_ptr<Vcard> vcard = Friend::toCpp(lf)->getVcard();
+	if (vcard) {
+		vcard->setFamilyName(L_C_TO_STRING(last_name));
+		return 0;
+	}
+	return -1;
+}
+
+LinphoneStatus linphone_friend_set_first_name(LinphoneFriend *lf, const char *first_name) {
+	const std::shared_ptr<Vcard> vcard = Friend::toCpp(lf)->getVcard();
+	if (vcard) {
+		vcard->setGivenName(L_C_TO_STRING(first_name));
+		return 0;
+	}
+	return -1;
+}
+
 void linphone_friend_set_native_uri(LinphoneFriend *lf, const char *native_uri) {
 	if (!lf) return;
 	Friend::toCpp(lf)->setNativeUri(L_C_TO_STRING(native_uri));
diff --git a/src/friend/friend.cpp b/src/friend/friend.cpp
index 0871b300e4..9480976023 100644
--- a/src/friend/friend.cpp
+++ b/src/friend/friend.cpp
@@ -548,9 +548,10 @@ void Friend::addAddress(const std::shared_ptr<const Address> &address) {
 
 void Friend::addPhoneNumber(const std::string &phoneNumber) {
 	if (phoneNumber.empty()) return;
+	auto flattenedPhoneNumber = Utils::flattenPhoneNumber(phoneNumber);
 
 	for (auto existing : getPhoneNumbers()) {
-		if (existing == phoneNumber) {
+		if (flattenedPhoneNumber == Utils::flattenPhoneNumber(existing)) {
 			lInfo() << "Trying to add an already existing phone number to friend, skipping";
 			return;
 		}
@@ -570,10 +571,12 @@ void Friend::addPhoneNumberWithLabel(const std::shared_ptr<const FriendPhoneNumb
 	if (!phoneNumber) return;
 	const std::string &phone = phoneNumber->getPhoneNumber();
 	if (phone.empty()) return;
+	auto flattenedPhoneNumber = Utils::flattenPhoneNumber(phone);
 
 	const std::string &label = phoneNumber->getLabel();
 	for (auto &existing : getPhoneNumbersWithLabel()) {
-		if (existing->getPhoneNumber() == phone && existing->getLabel() == label) {
+		if (existing->getLabel() == label &&
+		    flattenedPhoneNumber == Utils::flattenPhoneNumber(existing->getPhoneNumber())) {
 			lInfo() << "Trying to add an already existing phone number / label to friend, skipping";
 			return;
 		}
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
index b9c30d2685..f296bb65a9 100644
--- a/src/utils/utils.cpp
+++ b/src/utils/utils.cpp
@@ -228,6 +228,22 @@ string Utils::trim(const string &str) {
 	return (itBack <= itFront ? string() : string(itFront, itBack));
 }
 
+std::string Utils::flattenPhoneNumber(const std::string &str) {
+	std::string result;
+	const char *number = str.c_str();
+	char *unescaped_phone_number = belle_sip_username_unescape_unnecessary_characters(number);
+	const char *r;
+
+	for (r = unescaped_phone_number; *r != '\0'; ++r) {
+		if (*r == '+' || isdigit(*r)) {
+			result += *r;
+		}
+	}
+
+	belle_sip_free(unescaped_phone_number);
+	return result;
+}
+
 std::string Utils::normalizeFilename(const std::string &str) {
 	std::string result(str);
 #ifdef _WIN32
-- 
GitLab