diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c
index 397e951d03b02046e429a0ed954bf9a79ae19dec..cc4b300c417e5b8b3272f226b81e963ad9838a18 100644
--- a/coreapi/linphonecore.c
+++ b/coreapi/linphonecore.c
@@ -1180,7 +1180,7 @@ static void process_response_from_post_file_log_collection(void *data, const bel
 		} else if (code == 200) { /* The file has been uploaded correctly, get the server reply */
 			const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response);
 			FileTransferChatMessageModifier fileTransferModifier = FileTransferChatMessageModifier(NULL);
-			FileTransferContent *content = new FileTransferContent();
+			auto content = FileTransferContent::create<FileTransferContent>();
 			fileTransferModifier.parseFileTransferXmlIntoContent(body, content);
 			string fileUrl = content->getFileUrl();
 
@@ -1190,7 +1190,6 @@ static void process_response_from_post_file_log_collection(void *data, const bel
 				    core, LinphoneCoreLogCollectionUploadStateDelivered, url);
 			}
 
-			delete content;
 			clean_log_collection_upload_context(core);
 		} else {
 			ms_error("Unexpected HTTP response code %i during log collection upload to %s", code,
@@ -2789,7 +2788,7 @@ static void linphone_core_internal_notify_received(LinphoneCore *lc,
 			const char *factoryUri = linphone_proxy_config_get_conference_factory_uri(proxy);
 			if (factoryUri && (strcmp(resourceAddrUri.c_str(), factoryUri) == 0)) {
 				L_GET_PRIVATE_FROM_C_OBJECT(lc)->remoteListEventHandler->notifyReceived(
-				    ev, body ? L_GET_CPP_PTR_FROM_C_OBJECT(body) : nullptr);
+				    ev, body ? Content::toCpp(body)->getSharedFromThis() : nullptr);
 				return;
 			}
 		}
@@ -2800,7 +2799,7 @@ static void linphone_core_internal_notify_received(LinphoneCore *lc,
 		shared_ptr<MediaConference::Conference> audioVideoConference =
 		    L_GET_CPP_PTR_FROM_C_OBJECT(lc)->findAudioVideoConference(conferenceId);
 
-		Content content = body ? *L_GET_CPP_PTR_FROM_C_OBJECT(body) : Content();
+		Content content = body ? *Content::toCpp(body) : Content();
 		if (chatRoom) {
 			shared_ptr<ClientGroupChatRoom> cgcr;
 			if (chatRoom->getCapabilities() & ChatRoom::Capabilities::Proxy)
@@ -4990,16 +4989,16 @@ LinphoneCall *linphone_core_invite_address_with_params_2(LinphoneCore *lc,
                                                          const char *subject,
                                                          const LinphoneContent *content) {
 	CoreLogContextualizer logContextualizer(lc);
-	const char *from = NULL;
-	LinphoneAccount *account = NULL;
-	LinphoneAddress *temp_url = NULL;
-	LinphoneAddress *parsed_url2 = NULL;
+	const char *from = nullptr;
+	LinphoneAccount *account = nullptr;
+	LinphoneAddress *temp_url = nullptr;
+	LinphoneAddress *parsed_url2 = nullptr;
 	LinphoneCall *call;
 	LinphoneCallParams *cp;
 
 	if (!addr) {
 		ms_error("Can't invite a NULL address");
-		return NULL;
+		return nullptr;
 	}
 
 	// Check that sound resources can be freed before moving on
@@ -5011,7 +5010,7 @@ LinphoneCall *linphone_core_invite_address_with_params_2(LinphoneCore *lc,
 		     !Call::toCpp(current_call)->canSoundResourcesBeFreed())) {
 			ms_error("linphone_core_invite_address_with_params(): sound are locked by another call and they cannot be "
 			         "freed. Call attempt is rejected.");
-			return NULL;
+			return nullptr;
 		}
 	}
 
@@ -5026,7 +5025,7 @@ LinphoneCall *linphone_core_invite_address_with_params_2(LinphoneCore *lc,
 	from = linphone_call_params_get_from_header(params);
 
 	// If no account is found, then look up for one either using either the from or the to address
-	if (account == NULL) {
+	if (account == nullptr) {
 		temp_url = from ? linphone_address_new(from) : linphone_address_clone(addr);
 		account = linphone_core_lookup_known_account(lc, temp_url);
 		if (account && !from) {
@@ -5038,24 +5037,24 @@ LinphoneCall *linphone_core_invite_address_with_params_2(LinphoneCore *lc,
 
 	// If variable is still NULL, then the SDK has to make a decision because one is dependent from
 	// the other one. In such a scenario, it is assumed that the application wishes to use the default account
-	if (account == NULL) account = linphone_core_get_default_account(lc);
+	if (account == nullptr) account = linphone_core_get_default_account(lc);
 
 	// If an account has been found earlier on either because it has been set in the call params or it is the default
 	// one or it has been deduced thanks to the from or to addresses, then get the from address if not already set in
 	// the call params
-	if ((from == NULL) && (account != NULL)) {
+	if ((from == nullptr) && (account != nullptr)) {
 		const LinphoneAccountParams *account_params = linphone_account_get_params(account);
 		from = linphone_account_params_get_identity(account_params);
 	}
 
 	/* if no account or no identity defined for this account, default to primary contact*/
-	if (from == NULL) from = linphone_core_get_primary_contact(lc);
+	if (from == nullptr) from = linphone_core_get_primary_contact(lc);
 
 	parsed_url2 = linphone_address_new(from);
 
 	cp = _linphone_call_params_copy(params);
 	if (!linphone_call_params_has_avpf_enabled_been_set(cp)) {
-		if (account != NULL) {
+		if (account != nullptr) {
 			linphone_call_params_enable_avpf(cp, linphone_account_is_avpf_enabled(account));
 			const LinphoneAccountParams *account_params = linphone_account_get_params(account);
 			linphone_call_params_set_avpf_rr_interval(
@@ -5075,7 +5074,7 @@ LinphoneCall *linphone_core_invite_address_with_params_2(LinphoneCore *lc,
 		ms_warning("we had a problem in adding the call into the invite ... weird");
 		linphone_call_unref(call);
 		linphone_call_params_unref(cp);
-		return NULL;
+		return nullptr;
 	}
 
 	// Try to free up resources after adding it to the call list.
@@ -5085,17 +5084,17 @@ LinphoneCall *linphone_core_invite_address_with_params_2(LinphoneCore *lc,
 		ms_error("linphone_core_invite_address_with_params(): sound is required for this call but another call is "
 		         "already locking the sound resource. The call is automatically terminated.");
 		linphone_call_terminate(call);
-		return NULL;
+		return nullptr;
 	}
 
 	/* Unless this call is for a conference, it becomes now the current one*/
 	if (linphone_call_params_get_local_conference_mode(params) == FALSE)
 		L_GET_PRIVATE_FROM_C_OBJECT(lc)->setCurrentCall(Call::toCpp(call)->getSharedFromThis());
 	bool defer = Call::toCpp(call)->initiateOutgoing(L_C_TO_STRING(subject),
-	                                                 content ? L_GET_CPP_PTR_FROM_C_OBJECT(content) : NULL);
+	                                                 content ? Content::toCpp(content)->getSharedFromThis() : nullptr);
 	if (!defer) {
-		if (Call::toCpp(call)->startInvite(NULL, L_C_TO_STRING(subject),
-		                                   content ? L_GET_CPP_PTR_FROM_C_OBJECT(content) : NULL) != 0) {
+		if (Call::toCpp(call)->startInvite(nullptr, L_C_TO_STRING(subject),
+		                                   content ? Content::toCpp(content)->getSharedFromThis() : nullptr) != 0) {
 			/* The call has already gone to error and released state, so do not return it */
 			call = nullptr;
 		}
@@ -9560,7 +9559,23 @@ LinphoneConference *linphone_core_get_conference(LinphoneCore *lc) {
 }
 
 void linphone_core_enable_conference_server(LinphoneCore *lc, bool_t enable) {
+#ifdef HAVE_LIME_X3DH
+	// We need to change the encryption engine if it has been instanciated before.
+	auto core = L_GET_CPP_PTR_FROM_C_OBJECT(lc);
+	bool enabled = core->limeX3dhEnabled();
+
+	if (enabled) {
+		core->enableLimeX3dh(false);
+	}
+#endif
+
 	linphone_config_set_int(linphone_core_get_config(lc), "misc", "conference_server_enabled", enable);
+
+#ifdef HAVE_LIME_X3DH
+	if (enabled) {
+		core->enableLimeX3dh(true);
+	}
+#endif
 }
 
 void linphone_core_enable_fec(LinphoneCore *lc, bool_t enable) {
diff --git a/coreapi/local_conference.cpp b/coreapi/local_conference.cpp
index 18e3ce71aa3674c58d6490c15d8ce463effe6c8f..fd9fbb1362aea691b67992c51c6e33a2c2300920 100644
--- a/coreapi/local_conference.cpp
+++ b/coreapi/local_conference.cpp
@@ -249,7 +249,7 @@ void LocalConference::updateConferenceInformation(SalCallOp *op) {
 
 void LocalConference::fillInvitedParticipantList(SalCallOp *op, bool cancelling) {
 	mInvitedParticipants.clear();
-	const auto resourceList = op->getContentInRemote(ContentType::ResourceLists);
+	const auto &resourceList = op->getContentInRemote(ContentType::ResourceLists);
 	if (!resourceList.isEmpty()) {
 		auto invitees = Utils::parseResourceLists(resourceList);
 		mInvitedParticipants = invitees;
@@ -968,21 +968,21 @@ bool LocalConference::dialOutAddresses(const std::list<std::shared_ptr<Address>>
 		}
 	}
 
-	Content resourceList;
-	resourceList.setBodyFromUtf8(Utils::getResourceLists(addresses));
-	resourceList.setContentType(ContentType::ResourceLists);
-	resourceList.setContentDisposition(ContentDisposition::RecipientList);
+	auto resourceList = Content::create();
+	resourceList->setBodyFromUtf8(Utils::getResourceLists(addresses));
+	resourceList->setContentType(ContentType::ResourceLists);
+	resourceList->setContentDisposition(ContentDisposition::RecipientList);
 	if (linphone_core_content_encoding_supported(getCore()->getCCore(), "deflate")) {
-		resourceList.setContentEncoding("deflate");
+		resourceList->setContentEncoding("deflate");
 	}
-	if (!resourceList.isEmpty()) {
+	if (!resourceList->isEmpty()) {
 		L_GET_CPP_PTR_FROM_C_OBJECT(new_params)->addCustomContent(resourceList);
 	}
 
-	Content sipfrag;
+	auto sipfrag = Content::create();
 	const auto organizerUri = organizer->getUri();
-	sipfrag.setBodyFromLocale("From: <" + organizerUri.toString() + ">");
-	sipfrag.setContentType(ContentType::SipFrag);
+	sipfrag->setBodyFromLocale("From: <" + organizerUri.toString() + ">");
+	sipfrag->setContentType(ContentType::SipFrag);
 	L_GET_CPP_PTR_FROM_C_OBJECT(new_params)->addCustomContent(sipfrag);
 	auto success = (inviteAddresses(addressList, new_params) == 0);
 	linphone_call_params_unref(new_params);
@@ -1332,7 +1332,7 @@ bool LocalConference::addParticipant(std::shared_ptr<LinphonePrivate::Call> call
 		auto op = session->getPrivate()->getOp();
 		const auto resourceList = op ? op->getContentInRemote(ContentType::ResourceLists) : Content();
 
-		// If no resource list is provided in the INVITE, there is not need to call participants
+		// If no resource list is provided in the INVITE, there is no need to call participants
 		if ((initialState == ConferenceInterface::State::CreationPending) && dialout && !resourceList.isEmpty()) {
 			list<std::shared_ptr<Address>> addresses;
 			for (auto &participant : mInvitedParticipants) {
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 647f83eab33b1bf42a87c574ca4e00a460ec90e2..114e4d669a135187b6885351ab13be8893fb0cc7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -188,7 +188,6 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES
 	containers/lru-cache.h
 	content/content-disposition.h
 	content/content-manager.h
-	content/content-p.h
 	content/content-type.h
 	content/content.h
 	content/file-content.h
@@ -242,7 +241,6 @@ set(LINPHONE_CXX_OBJECTS_PRIVATE_HEADER_FILES
 	nat/ice-service.h
 	nat/stun-client.h
 	nat/nat-policy.h
-	object/app-data-container.h
 	object/base-object-p.h
 	object/base-object.h
 	object/clonable-object-p.h
@@ -486,7 +484,6 @@ set(LINPHONE_CXX_OBJECTS_SOURCE_FILES
 	nat/ice-service.cpp
 	nat/stun-client.cpp
 	nat/nat-policy.cpp
-	object/app-data-container.cpp
 	object/base-object.cpp
 	object/clonable-object.cpp
 	object/object.cpp
diff --git a/src/c-wrapper/api/c-call-params.cpp b/src/c-wrapper/api/c-call-params.cpp
index e62e5a4bd3f8e65fdf86b57f7c088553c183c2e5..16dd0568b756946f61918a8af0a11626768c8c24 100644
--- a/src/c-wrapper/api/c-call-params.cpp
+++ b/src/c-wrapper/api/c-call-params.cpp
@@ -658,18 +658,19 @@ void linphone_call_params_set_conference_creation(LinphoneCallParams *params, bo
 }
 
 bctbx_list_t *linphone_call_params_get_custom_contents(const LinphoneCallParams *params) {
-	const list<LinphonePrivate::Content> &contents = L_GET_CPP_PTR_FROM_C_OBJECT(params)->getCustomContents();
+	const list<std::shared_ptr<LinphonePrivate::Content>> &contents =
+	    L_GET_CPP_PTR_FROM_C_OBJECT(params)->getCustomContents();
 	bctbx_list_t *c_contents = nullptr;
 	for (auto &content : contents) {
-		LinphoneContent *c_content = L_GET_C_BACK_PTR(&content);
+		LinphoneContent *c_content = content->toC();
 		c_contents = bctbx_list_append(c_contents, linphone_content_ref(c_content));
 	}
 	return c_contents;
 }
 
 void linphone_call_params_add_custom_content(LinphoneCallParams *params, LinphoneContent *content) {
-	LinphonePrivate::Content *cppContent = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-	L_GET_CPP_PTR_FROM_C_OBJECT(params)->addCustomContent(*cppContent);
+	L_GET_CPP_PTR_FROM_C_OBJECT(params)->addCustomContent(
+	    LinphonePrivate::Content::toCpp(content)->getSharedFromThis());
 }
 
 bool_t linphone_call_params_rtp_bundle_enabled(const LinphoneCallParams *params) {
diff --git a/src/c-wrapper/api/c-chat-message.cpp b/src/c-wrapper/api/c-chat-message.cpp
index 4480f9f7b8384361a5c1250208dd98e54443f39d..a0f745532cc3e123571b4cb540654161da4bb739 100644
--- a/src/c-wrapper/api/c-chat-message.cpp
+++ b/src/c-wrapper/api/c-chat-message.cpp
@@ -381,8 +381,8 @@ LinphoneStatus linphone_chat_message_put_char(LinphoneChatMessage *msg, uint32_t
 
 void linphone_chat_message_add_file_content(LinphoneChatMessage *msg, LinphoneContent *c_content) {
 	LinphonePrivate::ChatMessageLogContextualizer logContextualizer(msg);
-	LinphonePrivate::FileContent *fileContent = new LinphonePrivate::FileContent();
-	LinphonePrivate::Content *content = L_GET_CPP_PTR_FROM_C_OBJECT(c_content);
+	auto fileContent = LinphonePrivate::FileContent::create<LinphonePrivate::FileContent>();
+	auto content = LinphonePrivate::Content::toCpp(c_content);
 
 	fileContent->setContentType(content->getContentType());
 	// If content type hasn't been set, use application/octet-stream
@@ -413,7 +413,7 @@ void linphone_chat_message_add_file_content(LinphoneChatMessage *msg, LinphoneCo
 
 void linphone_chat_message_add_text_content(LinphoneChatMessage *msg, const char *text) {
 	LinphonePrivate::ChatMessageLogContextualizer logContextualizer(msg);
-	LinphonePrivate::Content *content = new LinphonePrivate::Content();
+	auto content = LinphonePrivate::Content::create();
 	LinphonePrivate::ContentType contentType = LinphonePrivate::ContentType::PlainText;
 	content->setContentType(contentType);
 	content->setBodyFromLocale(L_C_TO_STRING(text));
@@ -422,7 +422,7 @@ void linphone_chat_message_add_text_content(LinphoneChatMessage *msg, const char
 
 void linphone_chat_message_add_utf8_text_content(LinphoneChatMessage *msg, const char *text) {
 	LinphonePrivate::ChatMessageLogContextualizer logContextualizer(msg);
-	LinphonePrivate::Content *content = new LinphonePrivate::Content();
+	auto content = LinphonePrivate::Content::create();
 	LinphonePrivate::ContentType contentType = LinphonePrivate::ContentType::PlainText;
 	content->setContentType(contentType);
 	content->setBodyFromUtf8(L_C_TO_STRING(text));
@@ -432,13 +432,12 @@ void linphone_chat_message_add_utf8_text_content(LinphoneChatMessage *msg, const
 void linphone_chat_message_add_content(LinphoneChatMessage *msg, LinphoneContent *c_content) {
 	LinphonePrivate::ChatMessageLogContextualizer logContextualizer(msg);
 	if (linphone_content_is_voice_recording(c_content)) {
-		linphone_content_ref(c_content);
-		LinphonePrivate::Content *content =
-		    static_cast<LinphonePrivate::FileContent *>(L_GET_CPP_PTR_FROM_C_OBJECT(c_content));
+		auto content = dynamic_pointer_cast<LinphonePrivate::FileContent>(
+		    LinphonePrivate::Content::toCpp(c_content)->getSharedFromThis());
 		L_GET_CPP_PTR_FROM_C_OBJECT(msg)->addContent(content);
 	} else {
-		LinphonePrivate::Content *content = L_GET_CPP_PTR_FROM_C_OBJECT(c_content);
-		LinphonePrivate::Content *cppContent = new LinphonePrivate::Content();
+		auto content = LinphonePrivate::Content::toCpp(c_content);
+		auto cppContent = LinphonePrivate::Content::create();
 		cppContent->setContentType(content->getContentType());
 		cppContent->setBody(content->getBody());
 		cppContent->setUserData(content->getUserData());
@@ -448,13 +447,14 @@ void linphone_chat_message_add_content(LinphoneChatMessage *msg, LinphoneContent
 
 void linphone_chat_message_remove_content(LinphoneChatMessage *msg, LinphoneContent *content) {
 	LinphonePrivate::ChatMessageLogContextualizer logContextualizer(msg);
-	L_GET_CPP_PTR_FROM_C_OBJECT(msg)->removeContent(L_GET_CPP_PTR_FROM_C_OBJECT(content));
+	L_GET_CPP_PTR_FROM_C_OBJECT(msg)->removeContent(LinphonePrivate::Content::toCpp(content)->toSharedPtr());
 }
 
 const bctbx_list_t *linphone_chat_message_get_contents(const LinphoneChatMessage *msg) {
 	LinphonePrivate::ChatMessageLogContextualizer logContextualizer(msg);
 	if (msg->cache.contents) bctbx_free(msg->cache.contents);
-	msg->cache.contents = L_GET_RESOLVED_C_LIST_FROM_CPP_LIST(L_GET_CPP_PTR_FROM_C_OBJECT(msg)->getContents());
+	msg->cache.contents =
+	    LinphonePrivate::Content::getCListFromCppList(L_GET_CPP_PTR_FROM_C_OBJECT(msg)->getContents(), false);
 	return msg->cache.contents;
 }
 
@@ -465,7 +465,7 @@ bool_t linphone_chat_message_has_text_content(const LinphoneChatMessage *msg) {
 
 const char *linphone_chat_message_get_text_content(const LinphoneChatMessage *msg) {
 	LinphonePrivate::ChatMessageLogContextualizer logContextualizer(msg);
-	const LinphonePrivate::Content *content = L_GET_PRIVATE_FROM_C_OBJECT(msg)->getTextContent();
+	const auto content = L_GET_PRIVATE_FROM_C_OBJECT(msg)->getTextContent();
 	if (content->isEmpty()) return nullptr;
 	msg->cache.textContentBody = content->getBodyAsString();
 	return L_STRING_TO_C(msg->cache.textContentBody);
@@ -490,7 +490,7 @@ bctbx_list_t *linphone_chat_message_get_participants_by_imdn_state(const Linphon
 
 bool_t linphone_chat_message_download_content(LinphoneChatMessage *msg, LinphoneContent *c_content) {
 	LinphonePrivate::ChatMessageLogContextualizer logContextualizer(msg);
-	LinphonePrivate::Content *content = L_GET_CPP_PTR_FROM_C_OBJECT(c_content);
+	auto content = LinphonePrivate::Content::toCpp(c_content)->getSharedFromThis();
 	if (!content->isFileTransfer()) {
 		if (content->isFile()) {
 			lError() << "LinphoneContent [" << content
@@ -500,8 +500,7 @@ bool_t linphone_chat_message_download_content(LinphoneChatMessage *msg, Linphone
 		}
 		return false;
 	}
-	LinphonePrivate::FileTransferContent *fileTransferContent =
-	    static_cast<LinphonePrivate::FileTransferContent *>(content);
+	auto fileTransferContent = dynamic_pointer_cast<LinphonePrivate::FileTransferContent>(content);
 	return !!L_GET_CPP_PTR_FROM_C_OBJECT(msg)->downloadFile(fileTransferContent);
 }
 
@@ -558,8 +557,8 @@ int linphone_chat_message_set_utf8_text(LinphoneChatMessage *msg, const char *te
 }
 
 LinphoneContent *linphone_chat_message_get_file_transfer_information(const LinphoneChatMessage *msg) {
-	const LinphonePrivate::Content *content = L_GET_PRIVATE_FROM_C_OBJECT(msg)->getFileTransferInformation();
-	if (content) return L_GET_C_BACK_PTR(content);
+	const auto &content = L_GET_PRIVATE_FROM_C_OBJECT(msg)->getFileTransferInformation();
+	if (content) return content->toC();
 	return NULL;
 }
 
diff --git a/src/c-wrapper/api/c-content.cpp b/src/c-wrapper/api/c-content.cpp
index a94383323ca99de62d7659c6e94268f4dd36768f..fb8a92f50305fff2d7ef322357df9bff0c4b409f 100644
--- a/src/c-wrapper/api/c-content.cpp
+++ b/src/c-wrapper/api/c-content.cpp
@@ -21,7 +21,6 @@
 #include <bctoolbox/defs.h>
 
 #include "linphone/api/c-content.h"
-#include "linphone/wrapper_utils.h"
 
 #include "c-wrapper/c-wrapper.h"
 #include "content/content-type.h"
@@ -32,97 +31,58 @@
 // =============================================================================
 
 using namespace std;
-
-static void _linphone_content_constructor(LinphoneContent *content);
-static void _linphone_content_destructor(LinphoneContent *content);
-static void _linphone_content_c_clone(LinphoneContent *dest, const LinphoneContent *src);
-
-L_DECLARE_C_CLONABLE_OBJECT_IMPL_WITH_XTORS(
-    Content,
-    _linphone_content_constructor,
-    _linphone_content_destructor,
-    _linphone_content_c_clone,
-
-    void *cryptoContext; // Used to encrypt file for RCS file transfer.
-
-    mutable size_t size;
-    bool_t is_dirty = FALSE;
-    SalBodyHandler * body_handler;
-
-    struct Cache {
-	    string name;
-	    string type;
-	    string subtype;
-	    string buffer;
-	    string file_path;
-	    string header_value;
-    } mutable cache;)
-
-static void _linphone_content_constructor(LinphoneContent *content) {
-	new (&content->cache) LinphoneContent::Cache();
-}
-
-static void _linphone_content_destructor(LinphoneContent *content) {
-	content->cache.~Cache();
-	if (content->body_handler) sal_body_handler_unref(content->body_handler);
-}
-
-static void _linphone_content_c_clone(LinphoneContent *dest, const LinphoneContent *src) {
-	new (&dest->cache) LinphoneContent::Cache();
-	dest->size = src->size;
-	dest->cache = src->cache;
-	if (!src->is_dirty && src->body_handler) dest->body_handler = sal_body_handler_ref(src->body_handler);
-}
+using namespace LinphonePrivate;
 
 // =============================================================================
 // Reference and user data handling functions.
 // =============================================================================
 
 LinphoneContent *linphone_content_ref(LinphoneContent *content) {
-	belle_sip_object_ref(content);
+	Content::toCpp(content)->ref();
 	return content;
 }
 
 void linphone_content_unref(LinphoneContent *content) {
-	belle_sip_object_unref(content);
+	Content::toCpp(content)->unref();
 }
 
 void *linphone_content_get_user_data(const LinphoneContent *content) {
-	return L_GET_CPP_PTR_FROM_C_OBJECT(content)->getUserData().getValue<void *>();
+	return Content::toCpp(content)->getUserData().getValue<void *>();
 }
 
 void linphone_content_set_user_data(LinphoneContent *content, void *user_data) {
-	return L_GET_CPP_PTR_FROM_C_OBJECT(content)->setUserData(user_data);
+	return Content::toCpp(content)->setUserData(user_data);
 }
 
 // =============================================================================
 
 const char *linphone_content_get_type(const LinphoneContent *content) {
-	content->cache.type = L_GET_CPP_PTR_FROM_C_OBJECT(content)->getContentType().getType();
-	return content->cache.type.c_str();
+	return L_STRING_TO_C(Content::toCpp(content)->getContentType().getType());
 }
 
 void linphone_content_set_type(LinphoneContent *content, const char *type) {
-	LinphonePrivate::ContentType contentType = L_GET_CPP_PTR_FROM_C_OBJECT(content)->getContentType();
+	auto contentCpp = Content::toCpp(content);
+	auto contentType = contentCpp->getContentType();
 	contentType.setType(L_C_TO_STRING(type));
-	L_GET_CPP_PTR_FROM_C_OBJECT(content)->setContentType(contentType);
+	contentCpp->setContentType(contentType);
 }
 
 const char *linphone_content_get_subtype(const LinphoneContent *content) {
-	content->cache.subtype = L_GET_CPP_PTR_FROM_C_OBJECT(content)->getContentType().getSubType();
-	return content->cache.subtype.c_str();
+	return L_STRING_TO_C(Content::toCpp(content)->getContentType().getSubType());
 }
 
 void linphone_content_set_subtype(LinphoneContent *content, const char *subtype) {
-	LinphonePrivate::ContentType contentType = L_GET_CPP_PTR_FROM_C_OBJECT(content)->getContentType();
+	auto contentCpp = Content::toCpp(content);
+	ContentType contentType = contentCpp->getContentType();
 	contentType.setSubType(L_C_TO_STRING(subtype));
-	L_GET_CPP_PTR_FROM_C_OBJECT(content)->setContentType(contentType);
+	contentCpp->setContentType(contentType);
 }
 
 void linphone_content_add_content_type_parameter(LinphoneContent *content, const char *name, const char *value) {
-	LinphonePrivate::ContentType contentType = L_GET_CPP_PTR_FROM_C_OBJECT(content)->getContentType();
+	auto contentCpp = Content::toCpp(content);
+	ContentType contentType = contentCpp->getContentType();
 	contentType.addParameter(L_C_TO_STRING(name), L_C_TO_STRING(value));
-	L_GET_CPP_PTR_FROM_C_OBJECT(content)->setContentType(contentType);
+	contentCpp->setContentType(contentType);
 }
 
 const uint8_t *linphone_content_get_buffer(const LinphoneContent *content) {
@@ -130,101 +90,88 @@ const uint8_t *linphone_content_get_buffer(const LinphoneContent *content) {
 }
 
 void linphone_content_set_buffer(LinphoneContent *content, const uint8_t *buffer, size_t size) {
-	content->is_dirty = TRUE;
-	L_GET_CPP_PTR_FROM_C_OBJECT(content)->setBody(buffer, size);
+	Content::toCpp(content)->setBody(buffer, size);
 }
 
 const char *linphone_content_get_string_buffer(const LinphoneContent *content) {
-	content->cache.buffer = L_GET_CPP_PTR_FROM_C_OBJECT(content)->getBodyAsUtf8String();
-	return content->cache.buffer.c_str();
+	return L_STRING_TO_C(Content::toCpp(content)->getBodyAsUtf8String());
 }
 
 const char *linphone_content_get_utf8_text(const LinphoneContent *content) {
-	content->cache.buffer = L_GET_CPP_PTR_FROM_C_OBJECT(content)->getBodyAsUtf8String();
-	return content->cache.buffer.c_str();
+	return L_STRING_TO_C(Content::toCpp(content)->getBodyAsUtf8String());
 }
 
 void linphone_content_set_utf8_text(LinphoneContent *content, const char *buffer) {
-	content->is_dirty = TRUE;
-	L_GET_CPP_PTR_FROM_C_OBJECT(content)->setBodyFromUtf8(L_C_TO_STRING(buffer));
+	Content::toCpp(content)->setBodyFromUtf8(L_C_TO_STRING(buffer));
 }
 
 void linphone_content_set_string_buffer(LinphoneContent *content, const char *buffer) {
-	content->is_dirty = TRUE;
-	L_GET_CPP_PTR_FROM_C_OBJECT(content)->setBodyFromUtf8(L_C_TO_STRING(buffer));
+	Content::toCpp(content)->setBodyFromUtf8(L_C_TO_STRING(buffer));
 }
 
 size_t linphone_content_get_file_size(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
+	const auto c = Content::toCpp(content);
 	size_t size = 0;
-	if (c->isFile()) size = static_cast<const LinphonePrivate::FileContent *>(c)->getFileSize();
-	else if (c->isFileTransfer()) size = static_cast<const LinphonePrivate::FileTransferContent *>(c)->getFileSize();
+	if (c->isFile()) size = dynamic_cast<const FileContent *>(c)->getFileSize();
+	else if (c->isFileTransfer()) size = dynamic_cast<const FileTransferContent *>(c)->getFileSize();
 	return size;
 }
 
 size_t linphone_content_get_size(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-	size_t size = c->getSize();
-	if (size == 0) {
-		size = content->size;
-	}
-	return size;
+	return Content::toCpp(content)->getSize();
 }
 
 void linphone_content_set_size(LinphoneContent *content, size_t size) {
-	content->size = size;
+	Content::toCpp(content)->setSize(size);
 }
 
 const char *linphone_content_get_encoding(const LinphoneContent *content) {
-	return L_STRING_TO_C(L_GET_CPP_PTR_FROM_C_OBJECT(content)->getContentEncoding());
+	return L_STRING_TO_C(Content::toCpp(content)->getContentEncoding());
 }
 
 void linphone_content_set_encoding(LinphoneContent *content, const char *encoding) {
-	L_GET_CPP_PTR_FROM_C_OBJECT(content)->setContentEncoding(L_C_TO_STRING(encoding));
+	Content::toCpp(content)->setContentEncoding(L_C_TO_STRING(encoding));
 }
 
 const char *linphone_content_get_disposition(const LinphoneContent *content) {
-	const LinphonePrivate::ContentDisposition &contentDisposition =
-		L_GET_CPP_PTR_FROM_C_OBJECT(content)->getContentDisposition();
+	const auto &contentDisposition = Content::toCpp(content)->getContentDisposition();
 	return L_STRING_TO_C(contentDisposition.asString());
 }
 
 void linphone_content_set_disposition(LinphoneContent *content, const char *disposition) {
-	if (disposition != nullptr) {
-		string strDisposition = L_C_TO_STRING(disposition);
-		if (!strDisposition.empty()) {
-			LinphonePrivate::ContentDisposition contentDisposition =
-			    LinphonePrivate::ContentDisposition(strDisposition);
-			L_GET_CPP_PTR_FROM_C_OBJECT(content)->setContentDisposition(contentDisposition);
-		}
+	string strDisposition = L_C_TO_STRING(disposition);
+	if (!strDisposition.empty()) {
+		ContentDisposition contentDisposition = ContentDisposition(strDisposition);
+		Content::toCpp(content)->setContentDisposition(contentDisposition);
 	}
 }
 
 const char *linphone_content_get_name(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-	if (c->isFile()) return static_cast<const LinphonePrivate::FileContent *>(c)->getFileName().c_str();
-	else if (c->isFileTransfer())
-		return static_cast<const LinphonePrivate::FileTransferContent *>(c)->getFileName().c_str();
-	return content->cache.name.c_str();
+	const auto c = Content::toCpp(content);
+
+	if (c->isFile()) return L_STRING_TO_C(dynamic_cast<const FileContent *>(c)->getFileName());
+	else if (c->isFileTransfer()) return L_STRING_TO_C(dynamic_cast<const FileTransferContent *>(c)->getFileName());
+
+	return L_STRING_TO_C(c->getName());
 }
 
 void linphone_content_set_name(LinphoneContent *content, const char *name) {
-	LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-	if (c->isFile()) static_cast<LinphonePrivate::FileContent *>(c)->setFileName(L_C_TO_STRING(name));
-	else if (c->isFileTransfer())
-		static_cast<LinphonePrivate::FileTransferContent *>(c)->setFileName(L_C_TO_STRING(name));
-	else content->cache.name = L_C_TO_STRING(name);
+	auto c = Content::toCpp(content);
+	if (c->isFile()) dynamic_cast<FileContent *>(c)->setFileName(L_C_TO_STRING(name));
+	else if (c->isFileTransfer()) dynamic_cast<FileTransferContent *>(c)->setFileName(L_C_TO_STRING(name));
+	else c->setName(L_C_TO_STRING(name));
 }
 
 bool_t linphone_content_is_multipart(const LinphoneContent *content) {
-	return L_GET_CPP_PTR_FROM_C_OBJECT(content)->getContentType().isMultipart();
+	return Content::toCpp(content)->getContentType().isMultipart();
 }
 
 bctbx_list_t *linphone_content_get_parts(const LinphoneContent *content) {
+	const auto c = Content::toCpp(content);
 	bctbx_list_t *parts = nullptr;
 	SalBodyHandler *bodyHandler;
-	if (!content->is_dirty && content->body_handler) {
-		bodyHandler = sal_body_handler_ref(content->body_handler);
+	if (!c->isDirty() && c->getBodyHandler() != nullptr) {
+		bodyHandler = sal_body_handler_ref(c->getBodyHandler());
 	} else {
 		bodyHandler = sal_body_handler_from_content(content);
 	}
@@ -235,9 +182,9 @@ bctbx_list_t *linphone_content_get_parts(const LinphoneContent *content) {
 	}
 
 	const bctbx_list_t *sal_parts = sal_body_handler_get_parts(bodyHandler);
-	bctbx_list_t *it = (bctbx_list_t *)sal_parts;
+	auto it = (bctbx_list_t *)sal_parts;
 	while (it != nullptr) {
-		SalBodyHandler *bh = (SalBodyHandler *)it->data;
+		auto bh = (SalBodyHandler *)it->data;
 		LinphoneContent *part = linphone_content_from_sal_body_handler(bh);
 		parts = bctbx_list_append(parts, linphone_content_ref(part));
 		linphone_content_unref(part);
@@ -249,9 +196,10 @@ bctbx_list_t *linphone_content_get_parts(const LinphoneContent *content) {
 }
 
 LinphoneContent *linphone_content_get_part(const LinphoneContent *content, int idx) {
+	const auto c = Content::toCpp(content);
 	SalBodyHandler *bodyHandler;
-	if (!content->is_dirty && content->body_handler) {
-		bodyHandler = sal_body_handler_ref(content->body_handler);
+	if (!c->isDirty() && c->getBodyHandler() != nullptr) {
+		bodyHandler = sal_body_handler_ref(c->getBodyHandler());
 	} else {
 		bodyHandler = sal_body_handler_from_content(content);
 	}
@@ -269,9 +217,10 @@ LinphoneContent *linphone_content_get_part(const LinphoneContent *content, int i
 
 LinphoneContent *
 linphone_content_find_part_by_header(const LinphoneContent *content, const char *headerName, const char *headerValue) {
+	const auto c = Content::toCpp(content);
 	SalBodyHandler *bodyHandler;
-	if (!content->is_dirty && content->body_handler) {
-		bodyHandler = sal_body_handler_ref(content->body_handler);
+	if (!c->isDirty() && c->getBodyHandler() != nullptr) {
+		bodyHandler = sal_body_handler_ref(c->getBodyHandler());
 	} else {
 		bodyHandler = sal_body_handler_from_content(content);
 	}
@@ -288,83 +237,67 @@ linphone_content_find_part_by_header(const LinphoneContent *content, const char
 }
 
 const char *linphone_content_get_custom_header(const LinphoneContent *content, const char *headerName) {
-	SalBodyHandler *bodyHandler;
-	if (!content->is_dirty && content->body_handler) {
-		bodyHandler = sal_body_handler_ref(content->body_handler);
-	} else {
-		bodyHandler = sal_body_handler_from_content(content);
-	}
-
-	content->cache.header_value = L_C_TO_STRING(sal_body_handler_get_header(bodyHandler, headerName));
-	sal_body_handler_unref(bodyHandler);
-	return content->cache.header_value.c_str();
+	return L_STRING_TO_C(Content::toCpp(content)->getCustomHeader(L_C_TO_STRING(headerName)));
 }
 
 void linphone_content_add_custom_header(LinphoneContent *content, const char *header_name, const char *header_value) {
-	LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-	c->addHeader(L_C_TO_STRING(header_name), L_C_TO_STRING(header_value));
+	Content::toCpp(content)->addHeader(L_C_TO_STRING(header_name), L_C_TO_STRING(header_value));
 }
 
 const char *linphone_content_get_key(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
+	const auto c = Content::toCpp(content);
 	if (c->isFileTransfer()) {
-		const LinphonePrivate::FileTransferContent *ftc = static_cast<const LinphonePrivate::FileTransferContent *>(c);
+		auto ftc = dynamic_cast<const FileTransferContent *>(c);
 		return ftc->getFileKey().data();
 	}
 	return nullptr;
 }
 
 size_t linphone_content_get_key_size(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
+	const auto c = Content::toCpp(content);
 	if (c->isFileTransfer()) {
-		const LinphonePrivate::FileTransferContent *ftc = static_cast<const LinphonePrivate::FileTransferContent *>(c);
+		auto ftc = dynamic_cast<const FileTransferContent *>(c);
 		return ftc->getFileKeySize();
 	}
 	return 0;
 }
 
 void linphone_content_set_key(LinphoneContent *content, const char *key, const size_t keyLength) {
-	LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
+	auto c = Content::toCpp(content);
 	if (c->isFileTransfer()) {
-		LinphonePrivate::FileTransferContent *ftc = static_cast<LinphonePrivate::FileTransferContent *>(c);
+		auto ftc = dynamic_cast<FileTransferContent *>(c);
 		ftc->setFileKey(key, keyLength);
 	}
 }
 
 const char *linphone_content_get_authTag(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
+	const auto c = Content::toCpp(content);
 	if (c->isFileTransfer()) {
-		const LinphonePrivate::FileTransferContent *ftc = static_cast<const LinphonePrivate::FileTransferContent *>(c);
+		auto ftc = dynamic_cast<const FileTransferContent *>(c);
 		return ftc->getFileAuthTag().data();
 	}
 	return nullptr;
 }
 
 size_t linphone_content_get_authTag_size(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
+	const auto c = Content::toCpp(content);
 	if (c->isFileTransfer()) {
-		const LinphonePrivate::FileTransferContent *ftc = static_cast<const LinphonePrivate::FileTransferContent *>(c);
+		auto ftc = dynamic_cast<const FileTransferContent *>(c);
 		return ftc->getFileAuthTagSize();
 	}
 	return 0;
 }
 
 void linphone_content_set_authTag(LinphoneContent *content, const char *tag, const size_t tagLength) {
-	LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
+	auto c = Content::toCpp(content);
 	if (c->isFileTransfer()) {
-		LinphonePrivate::FileTransferContent *ftc = static_cast<LinphonePrivate::FileTransferContent *>(c);
+		auto ftc = dynamic_cast<FileTransferContent *>(c);
 		ftc->setFileAuthTag(tag, tagLength);
 	}
 }
 
 const char *linphone_content_get_file_path(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-	if (c->isFile()) {
-		return L_STRING_TO_C(static_cast<const LinphonePrivate::FileContent *>(c)->getFilePath());
-	} else if (c->isFileTransfer()) {
-		return L_STRING_TO_C(static_cast<const LinphonePrivate::FileTransferContent *>(c)->getFilePath());
-	}
-	return L_STRING_TO_C(content->cache.file_path);
+	return L_STRING_TO_C(Content::toCpp(content)->getFilePath());
 }
 
 /* function deprecated on 07/01/2022 export_plain_file is more explicit */
@@ -373,77 +306,66 @@ char *linphone_content_get_plain_file_path(const LinphoneContent *content) {
 }
 
 char *linphone_content_export_plain_file(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
+	const Content *c = Content::toCpp(content);
 	if (c->isFile()) {
-		auto filePath = static_cast<const LinphonePrivate::FileContent *>(c)->exportPlainFile();
+		auto filePath = dynamic_cast<const FileContent *>(c)->exportPlainFile();
 		return bctbx_strdup(L_STRING_TO_C(filePath));
 	} else if (c->isFileTransfer()) {
-		auto filePath = static_cast<const LinphonePrivate::FileTransferContent *>(c)->exportPlainFile();
+		auto filePath = dynamic_cast<const FileTransferContent *>(c)->exportPlainFile();
 		return bctbx_strdup(L_STRING_TO_C(filePath));
 	}
-	return NULL;
+	return nullptr;
 }
 
 void linphone_content_set_file_path(LinphoneContent *content, const char *file_path) {
-	LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-	if (c->isFile()) {
-		static_cast<LinphonePrivate::FileContent *>(c)->setFilePath(L_C_TO_STRING(file_path));
-	} else if (c->isFileTransfer()) {
-		static_cast<LinphonePrivate::FileTransferContent *>(c)->setFilePath(L_C_TO_STRING(file_path));
-	}
-	content->cache.file_path = L_C_TO_STRING(file_path);
+	Content::toCpp(content)->setFilePath(L_C_TO_STRING(file_path));
 }
 
 int linphone_content_get_file_duration(LinphoneContent *content) {
-	LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-	if (c->isFile()) return static_cast<LinphonePrivate::FileContent *>(c)->getFileDuration();
-	else if (c->isFileTransfer()) return static_cast<LinphonePrivate::FileTransferContent *>(c)->getFileDuration();
+	auto c = Content::toCpp(content);
+	if (c->isFile()) return dynamic_cast<FileContent *>(c)->getFileDuration();
+	else if (c->isFileTransfer()) return dynamic_cast<FileTransferContent *>(c)->getFileDuration();
 	return -1;
 }
 
 bool_t linphone_content_is_text(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-	return c->getContentType() == LinphonePrivate::ContentType::PlainText;
+	return Content::toCpp(content)->getContentType() == ContentType::PlainText;
 }
 
 bool_t linphone_content_is_voice_recording(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
+	const auto c = Content::toCpp(content);
 	if (c->isFile()) {
-		return c->getContentType().strongEqual(LinphonePrivate::ContentType::VoiceRecording);
+		return c->getContentType().strongEqual(ContentType::VoiceRecording);
 	} else if (c->isFileTransfer()) {
-		return static_cast<const LinphonePrivate::FileTransferContent *>(c)->getFileContentType().strongEqual(
-		    LinphonePrivate::ContentType::VoiceRecording);
+		return dynamic_cast<const FileTransferContent *>(c)->getFileContentType().strongEqual(
+		    ContentType::VoiceRecording);
 	}
 	return false;
 }
 
 bool_t linphone_content_is_icalendar(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
+	const auto c = Content::toCpp(content);
 	if (c->isFileTransfer()) {
-		return static_cast<const LinphonePrivate::FileTransferContent *>(c)->getFileContentType().strongEqual(
-		    LinphonePrivate::ContentType::Icalendar);
+		return dynamic_cast<const FileTransferContent *>(c)->getFileContentType().strongEqual(ContentType::Icalendar);
 	} else {
-		return c->getContentType().strongEqual(LinphonePrivate::ContentType::Icalendar);
+		return c->getContentType().strongEqual(ContentType::Icalendar);
 	}
-	return false;
 }
 
 bool_t linphone_content_is_file(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-	return c->isFile();
+	return Content::toCpp(content)->isFile();
 }
 
 bool_t linphone_content_is_file_transfer(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-	return c->isFileTransfer();
+	return Content::toCpp(content)->isFileTransfer();
 }
 
 bool_t linphone_content_is_file_encrypted(const LinphoneContent *content) {
-	const LinphonePrivate::Content *c = L_GET_CPP_PTR_FROM_C_OBJECT(content);
+	const auto c = Content::toCpp(content);
 	if (c->isFile()) {
-		return static_cast<const LinphonePrivate::FileContent *>(c)->isEncrypted();
+		return dynamic_cast<const FileContent *>(c)->isEncrypted();
 	} else if (c->isFileTransfer()) {
-		return static_cast<const LinphonePrivate::FileTransferContent *>(c)->isEncrypted();
+		return dynamic_cast<const FileTransferContent *>(c)->isEncrypted();
 	}
 	return FALSE;
 }
@@ -453,54 +375,7 @@ bool_t linphone_content_is_file_encrypted(const LinphoneContent *content) {
 // =============================================================================
 
 static LinphoneContent *linphone_content_new_with_body_handler(const SalBodyHandler *bodyHandler, bool parseMultipart) {
-	LinphoneContent *content = L_INIT(Content);
-	content->cryptoContext = nullptr;
-	LinphonePrivate::Content *c = new LinphonePrivate::Content();
-	L_SET_CPP_PTR_FROM_C_OBJECT(content, c);
-	content->body_handler = nullptr;
-
-	if (!bodyHandler) return content;
-
-	content->body_handler = sal_body_handler_ref((SalBodyHandler *)bodyHandler);
-
-	linphone_content_set_type(content, sal_body_handler_get_type(bodyHandler));
-	linphone_content_set_subtype(content, sal_body_handler_get_subtype(bodyHandler));
-	for (const belle_sip_list_t *params = sal_body_handler_get_content_type_parameters_names(bodyHandler); params;
-	     params = params->next) {
-		const char *paramName = reinterpret_cast<const char *>(params->data);
-		const char *paramValue = sal_body_handler_get_content_type_parameter(bodyHandler, paramName);
-		linphone_content_add_content_type_parameter(content, paramName, paramValue);
-	}
-
-	if (linphone_content_is_multipart(content) && parseMultipart) {
-		belle_sip_multipart_body_handler_t *mpbh = BELLE_SIP_MULTIPART_BODY_HANDLER(bodyHandler);
-		char *body = belle_sip_object_to_string(mpbh);
-		linphone_content_set_utf8_text(content, body);
-		belle_sip_free(body);
-	} else {
-		linphone_content_set_utf8_text(content, reinterpret_cast<char *>(sal_body_handler_get_data(bodyHandler)));
-	}
-
-	const belle_sip_list_t *headers =
-	    reinterpret_cast<const belle_sip_list_t *>(sal_body_handler_get_headers(bodyHandler));
-	while (headers) {
-		belle_sip_header_t *cHeader = BELLE_SIP_HEADER(headers->data);
-		LinphonePrivate::Header header =
-		    LinphonePrivate::Header(belle_sip_header_get_name(cHeader), belle_sip_header_get_unparsed_value(cHeader));
-		L_GET_CPP_PTR_FROM_C_OBJECT(content)->addHeader(header);
-		headers = headers->next;
-	}
-
-	if (sal_body_handler_get_encoding(bodyHandler))
-		linphone_content_set_encoding(content, sal_body_handler_get_encoding(bodyHandler));
-
-	const char *disposition = sal_body_handler_get_content_disposition(bodyHandler);
-	if (disposition) {
-		LinphonePrivate::ContentDisposition contentDisposition = LinphonePrivate::ContentDisposition(disposition);
-		L_GET_CPP_PTR_FROM_C_OBJECT(content)->setContentDisposition(contentDisposition);
-	}
-
-	return content;
+	return Content::createCObject(bodyHandler, parseMultipart);
 }
 
 LinphoneContent *linphone_content_new(void) {
@@ -508,7 +383,7 @@ LinphoneContent *linphone_content_new(void) {
 }
 
 LinphoneContent *linphone_content_copy(const LinphoneContent *ref) {
-	return reinterpret_cast<LinphoneContent *>(belle_sip_object_clone(BELLE_SIP_OBJECT(ref)));
+	return Content::toCpp(ref)->clone()->toC();
 }
 
 LinphoneContent *linphone_core_create_content(BCTBX_UNUSED(LinphoneCore *lc)) {
@@ -518,7 +393,7 @@ LinphoneContent *linphone_core_create_content(BCTBX_UNUSED(LinphoneCore *lc)) {
 // Crypto context is managed(allocated/freed) by the encryption function,
 // so provide the address of field in the private structure.
 void **linphone_content_get_cryptoContext_address(LinphoneContent *content) {
-	return &content->cryptoContext;
+	return Content::toCpp(content)->getCryptoContextAddress();
 }
 
 LinphoneContent *linphone_content_from_sal_body_handler(const SalBodyHandler *bodyHandler, bool parseMultipart) {
@@ -528,57 +403,5 @@ LinphoneContent *linphone_content_from_sal_body_handler(const SalBodyHandler *bo
 
 SalBodyHandler *sal_body_handler_from_content(const LinphoneContent *content, bool parseMultipart) {
 	if (!content) return nullptr;
-
-	if (!content->is_dirty && content->body_handler) return sal_body_handler_ref(content->body_handler);
-
-	auto cppContent = L_GET_CPP_PTR_FROM_C_OBJECT(content);
-
-	SalBodyHandler *bodyHandler;
-	LinphonePrivate::ContentType contentType = cppContent->getContentType();
-	if (contentType.isMultipart() && parseMultipart) {
-		size_t size = linphone_content_get_size(content);
-		char *buffer = bctbx_strdup(cppContent->getBodyAsUtf8String().c_str());
-		const char *boundary = L_STRING_TO_C(contentType.getParameter("boundary").getValue());
-		belle_sip_multipart_body_handler_t *bh = NULL;
-		if (boundary) bh = belle_sip_multipart_body_handler_new_from_buffer(buffer, size, boundary);
-		else if (size > 2) {
-			size_t startIndex = 2, index;
-			while (startIndex < size &&
-			       (buffer[startIndex] != '-' || buffer[startIndex - 1] != '-' // Take accout of first "--"
-			        || (startIndex > 2 && buffer[startIndex - 2] != '\n')))    // Must be at the beginning of the line
-				++startIndex;
-			index = startIndex;
-			while (index < size && buffer[index] != '\n' && buffer[index] != '\r')
-				++index;
-			if (startIndex != index) {
-				char *boundaryStr = bctbx_strndup(buffer + startIndex, (int)(index - startIndex));
-				bh = belle_sip_multipart_body_handler_new_from_buffer(buffer, size, boundaryStr);
-				bctbx_free(boundaryStr);
-			}
-		}
-
-		bodyHandler = reinterpret_cast<SalBodyHandler *>(BELLE_SIP_BODY_HANDLER(bh));
-		bctbx_free(buffer);
-	} else {
-		bodyHandler = sal_body_handler_new();
-		sal_body_handler_set_data(bodyHandler, belle_sip_strdup(cppContent->getBodyAsUtf8String().c_str()));
-	}
-
-	for (const auto &header : cppContent->getHeaders()) {
-		sal_body_handler_add_header(bodyHandler, header.getName().c_str(), header.getValueWithParams().c_str());
-	}
-
-	sal_body_handler_set_type(bodyHandler, contentType.getType().c_str());
-	sal_body_handler_set_subtype(bodyHandler, contentType.getSubType().c_str());
-	sal_body_handler_set_size(bodyHandler, linphone_content_get_size(content));
-	for (const auto &param : contentType.getParameters())
-		sal_body_handler_set_content_type_parameter(bodyHandler, param.getName().c_str(), param.getValue().c_str());
-
-	if (linphone_content_get_encoding(content))
-		sal_body_handler_set_encoding(bodyHandler, linphone_content_get_encoding(content));
-
-	const LinphonePrivate::ContentDisposition &disposition = cppContent->getContentDisposition();
-	if (disposition.isValid()) sal_body_handler_set_content_disposition(bodyHandler, disposition.asString().c_str());
-
-	return bodyHandler;
+	return Content::getBodyHandlerFromContent(*Content::toCpp(content), parseMultipart);
 }
diff --git a/src/c-wrapper/api/c-event.cpp b/src/c-wrapper/api/c-event.cpp
index f6fe47d6fad4947222816d55863634bc4e49def1..9c685c1c3d5af474085cd05c0774a14adadeb3d1 100644
--- a/src/c-wrapper/api/c-event.cpp
+++ b/src/c-wrapper/api/c-event.cpp
@@ -514,18 +514,20 @@ LinphoneEvent *linphone_event_new_subscribe_with_op(LinphoneCore *lc,
                                                     SalSubscribeOp *op,
                                                     LinphoneSubscriptionDir dir,
                                                     const char *name) {
-	return (new EventSubscribe(L_GET_CPP_PTR_FROM_C_OBJECT(lc), op, dir, L_C_TO_STRING(name), false))->toC();
+	return EventSubscribe::createCObject<EventSubscribe>(L_GET_CPP_PTR_FROM_C_OBJECT(lc), op, dir, L_C_TO_STRING(name),
+	                                                     false);
 }
 
 LinphoneEvent *linphone_event_new_subscribe_with_out_of_dialog_op(LinphoneCore *lc,
                                                                   SalSubscribeOp *op,
                                                                   LinphoneSubscriptionDir dir,
                                                                   const char *name) {
-	return (new EventSubscribe(L_GET_CPP_PTR_FROM_C_OBJECT(lc), op, dir, L_C_TO_STRING(name), true))->toC();
+	return EventSubscribe::createCObject<EventSubscribe>(L_GET_CPP_PTR_FROM_C_OBJECT(lc), op, dir, L_C_TO_STRING(name),
+	                                                     true);
 }
 
 LinphoneEvent *linphone_event_new_publish_with_op(LinphoneCore *lc, SalPublishOp *op, const char *name) {
-	return (new EventPublish(L_GET_CPP_PTR_FROM_C_OBJECT(lc), op, L_C_TO_STRING(name)))->toC();
+	return EventPublish::createCObject<EventPublish>(L_GET_CPP_PTR_FROM_C_OBJECT(lc), op, L_C_TO_STRING(name));
 }
 
 #endif /* LINPHONE_EVENT_H_ */
diff --git a/src/c-wrapper/api/c-recorder.cpp b/src/c-wrapper/api/c-recorder.cpp
index 798197e08b5e6f3191a597dff128b8e1f86aae1e..f533fb5fcc6dc928e09c7ff1604748c7a5f970db 100644
--- a/src/c-wrapper/api/c-recorder.cpp
+++ b/src/c-wrapper/api/c-recorder.cpp
@@ -76,9 +76,10 @@ float linphone_recorder_get_capture_volume(const LinphoneRecorder *recorder) {
 }
 
 LinphoneContent *linphone_recorder_create_content(LinphoneRecorder *recorder) {
-	LinphonePrivate::Content *fileContent = Recorder::toCpp(recorder)->createContent();
+	auto fileContent = Recorder::toCpp(recorder)->createContent();
 	if (fileContent != nullptr) {
-		return L_GET_C_BACK_PTR(fileContent);
+		fileContent->ref();
+		return fileContent->toC();
 	}
 	return nullptr;
 }
diff --git a/src/call/call.cpp b/src/call/call.cpp
index f309344f005d651a79a907207fc22d7ecc095766..46190cc2fe43b9b1d7dcca7c200e90c5130c6ae4 100644
--- a/src/call/call.cpp
+++ b/src/call/call.cpp
@@ -182,7 +182,7 @@ void Call::initiateIncoming() {
 	getActiveSession()->initiateIncoming();
 }
 
-bool Call::initiateOutgoing(const string &subject, const Content *content) {
+bool Call::initiateOutgoing(const string &subject, const std::shared_ptr<const Content> content) {
 	shared_ptr<CallSession> session = getActiveSession();
 	bool defer = session->initiateOutgoing(subject, content);
 	session->getPrivate()->createOp();
@@ -215,7 +215,9 @@ void Call::pauseForTransfer() {
 	static_pointer_cast<MediaSession>(getActiveSession())->getPrivate()->pauseForTransfer();
 }
 
-int Call::startInvite(const std::shared_ptr<Address> &destination, const std::string subject, const Content *content) {
+int Call::startInvite(const std::shared_ptr<Address> &destination,
+                      const std::string subject,
+                      const std::shared_ptr<const Content> content) {
 	return getActiveSession()->startInvite(destination, subject, content);
 }
 
diff --git a/src/call/call.h b/src/call/call.h
index b371d286de9638f984781b9d6f5a580ba22f4c59..21fd5228df317029606585ff9a880a7bb78584fd 100644
--- a/src/call/call.h
+++ b/src/call/call.h
@@ -213,7 +213,7 @@ public:
 	// -----------------------------------------------------------------------------
 	void createPlayer() const;
 	void initiateIncoming();
-	bool initiateOutgoing(const std::string &subject = "", const Content *content = nullptr);
+	bool initiateOutgoing(const std::string &subject = "", const std::shared_ptr<const Content> content = nullptr);
 	void iterate(time_t currentRealTime, bool oneSecondElapsed);
 	void notifyRinging();
 	void startIncomingNotification();
@@ -222,7 +222,7 @@ public:
 	void pauseForTransfer();
 	int startInvite(const std::shared_ptr<Address> &destination,
 	                const std::string subject = std::string(),
-	                const Content *content = nullptr);
+	                const std::shared_ptr<const Content> content = nullptr);
 	std::shared_ptr<Call> startReferredCall(const MediaSessionParams *params);
 
 	// -----------------------------------------------------------------------------
diff --git a/src/chat/chat-message/chat-message-p.h b/src/chat/chat-message/chat-message-p.h
index 40691e961d72e36b89a46fab4a886b90bdb6cee4..e28b37e634dbe73d6ec7507c688629a7a2ca832c 100644
--- a/src/chat/chat-message/chat-message-p.h
+++ b/src/chat/chat-message/chat-message-p.h
@@ -120,12 +120,12 @@ public:
 
 	void loadContentsFromDatabase() const;
 
-	std::list<Content *> &getContents() {
+	std::list<std::shared_ptr<Content>> &getContents() {
 		loadContentsFromDatabase();
 		return contents;
 	}
 
-	const std::list<Content *> &getContents() const {
+	const std::list<std::shared_ptr<Content>> &getContents() const {
 		loadContentsFromDatabase();
 		return contents;
 	}
@@ -224,16 +224,16 @@ public:
 	void setCallId(const std::string &id);
 
 	bool hasTextContent() const;
-	const Content *getTextContent() const;
+	const std::shared_ptr<Content> getTextContent() const;
 	bool hasConferenceInvitationContent() const;
 
 	bool hasFileTransferContent() const;
-	const Content *getFileTransferContent() const;
-	const Content *getFileTransferInformation() const;
+	const std::shared_ptr<Content> getFileTransferContent() const;
+	const std::shared_ptr<Content> getFileTransferInformation() const;
 
-	void addContent(Content *content);
-	void removeContent(Content *content);
-	void replaceContent(Content *contentToRemove, Content *contentToAdd);
+	void addContent(std::shared_ptr<Content> content);
+	void removeContent(std::shared_ptr<Content> content);
+	void replaceContent(std::shared_ptr<Content> contentToRemove, std::shared_ptr<Content> contentToAdd);
 
 	bool downloadFile();
 
@@ -312,7 +312,7 @@ private:
 	long ephemeralLifetime = 0;
 	time_t ephemeralExpireTime = 0;
 
-	std::list<Content *> contents;
+	std::list<std::shared_ptr<Content>> contents;
 	mutable std::list<std::shared_ptr<ChatMessageReaction>> reactions;
 
 	bool encryptionPrevented = false;
diff --git a/src/chat/chat-message/chat-message-reaction.cpp b/src/chat/chat-message/chat-message-reaction.cpp
index 97541eade51e099e1cd9ea8a381c59edb9e8037c..671a7d05d208a4d003e658414e4100d4f3018ea5 100644
--- a/src/chat/chat-message/chat-message-reaction.cpp
+++ b/src/chat/chat-message/chat-message-reaction.cpp
@@ -118,7 +118,7 @@ void ChatMessageReaction::send() {
 	reactionMessage->addListener(getSharedFromThis());
 	reactionMessage->getPrivate()->setReactionToMessageId(messageId);
 
-	Content *content = new Content();
+	auto content = Content::create();
 	content->setContentType(ContentType::PlainText);
 	content->setBodyFromUtf8(reaction);
 	reactionMessage->addContent(content);
diff --git a/src/chat/chat-message/chat-message.cpp b/src/chat/chat-message/chat-message.cpp
index d759e2c97d341e2b23e82220edf2f693b904a8d2..6bf0e6659a8ea998a01d1a9b268ce4739032aefd 100644
--- a/src/chat/chat-message/chat-message.cpp
+++ b/src/chat/chat-message/chat-message.cpp
@@ -65,14 +65,6 @@ ChatMessagePrivate::ChatMessagePrivate(const std::shared_ptr<AbstractChatRoom> &
 }
 
 ChatMessagePrivate::~ChatMessagePrivate() {
-	for (Content *content : contents) {
-		if (content->isFileTransfer()) {
-			FileTransferContent *fileTransferContent = static_cast<FileTransferContent *>(content);
-			delete fileTransferContent->getFileContent();
-		}
-		delete content;
-	}
-
 	if (salOp) {
 		salOp->setUserPointer(nullptr);
 		salOp->unref();
@@ -466,7 +458,7 @@ string ChatMessagePrivate::getSalCustomHeaderValue(const string &name) const {
 // -----------------------------------------------------------------------------
 
 bool ChatMessagePrivate::hasTextContent() const {
-	for (const Content *c : getContents()) {
+	for (const auto &c : getContents()) {
 		if (c->getContentType() == ContentType::PlainText) {
 			return true;
 		}
@@ -474,17 +466,17 @@ bool ChatMessagePrivate::hasTextContent() const {
 	return false;
 }
 
-const Content *ChatMessagePrivate::getTextContent() const {
-	for (const Content *c : getContents()) {
+const std::shared_ptr<Content> ChatMessagePrivate::getTextContent() const {
+	for (const auto &c : getContents()) {
 		if (c->getContentType() == ContentType::PlainText) {
 			return c;
 		}
 	}
-	return &Utils::getEmptyConstRefObject<Content>();
+	return nullptr;
 }
 
 bool ChatMessagePrivate::hasConferenceInvitationContent() const {
-	for (const Content *c : getContents()) {
+	for (const auto &c : getContents()) {
 		if (c->getContentType().strongEqual(ContentType::Icalendar)) {
 			return true;
 		}
@@ -493,7 +485,7 @@ bool ChatMessagePrivate::hasConferenceInvitationContent() const {
 }
 
 bool ChatMessagePrivate::hasFileTransferContent() const {
-	for (const Content *c : contents) {
+	for (const auto &c : contents) {
 		if (c->isFileTransfer()) {
 			return true;
 		}
@@ -501,13 +493,13 @@ bool ChatMessagePrivate::hasFileTransferContent() const {
 	return false;
 }
 
-const Content *ChatMessagePrivate::getFileTransferContent() const {
-	for (const Content *c : contents) {
+const std::shared_ptr<Content> ChatMessagePrivate::getFileTransferContent() const {
+	for (const auto &c : contents) {
 		if (c->isFileTransfer()) {
 			return c;
 		}
 	}
-	return &Utils::getEmptyConstRefObject<Content>();
+	return nullptr;
 }
 
 const string &ChatMessagePrivate::getFileTransferFilepath() const {
@@ -523,9 +515,10 @@ void ChatMessagePrivate::setEphemeralExpireTime(time_t expireTime) {
 }
 
 const string &ChatMessagePrivate::getAppdata() const {
-	for (const Content *c : getContents()) {
-		if (!c->getAppData("legacy").empty()) {
-			return c->getAppData("legacy");
+	for (const auto &c : getContents()) {
+		const auto &legacy = c->getProperty("legacy");
+		if (legacy.isValid() && !legacy.getValue<string>().empty()) {
+			return legacy.getValue<string>();
 		}
 	}
 	return Utils::getEmptyConstRefObject<string>();
@@ -533,8 +526,8 @@ const string &ChatMessagePrivate::getAppdata() const {
 
 void ChatMessagePrivate::setAppdata(const string &data) {
 	bool contentFound = false;
-	for (Content *c : getContents()) {
-		c->setAppData("legacy", data);
+	for (auto &c : getContents()) {
+		c->setProperty("legacy", Variant{data});
 		contentFound = true;
 		break;
 	}
@@ -548,7 +541,7 @@ const string &ChatMessagePrivate::getExternalBodyUrl() const {
 		return externalBodyUrl;
 	}
 	if (hasFileTransferContent()) {
-		FileTransferContent *content = (FileTransferContent *)getFileTransferContent();
+		const auto &content = static_pointer_cast<FileTransferContent>(getFileTransferContent());
 		return content->getFileUrl();
 	}
 	return Utils::getEmptyConstRefObject<string>();
@@ -562,7 +555,7 @@ const ContentType &ChatMessagePrivate::getContentType() const {
 	loadContentsFromDatabase();
 	if (direction == ChatMessage::Direction::Incoming) {
 		if (!contents.empty()) {
-			Content *content = contents.front();
+			auto &content = contents.front();
 			cContentType = content->getContentType();
 		} else {
 			cContentType = internalContent.getContentType();
@@ -572,7 +565,7 @@ const ContentType &ChatMessagePrivate::getContentType() const {
 			cContentType = internalContent.getContentType();
 		} else {
 			if (!contents.empty()) {
-				Content *content = contents.front();
+				auto &content = contents.front();
 				cContentType = content->getContentType();
 			}
 		}
@@ -599,7 +592,7 @@ const string &ChatMessagePrivate::getText() const {
 		if (hasTextContent()) {
 			cText = getTextContent()->getBodyAsString();
 		} else if (!contents.empty()) {
-			Content *content = contents.front();
+			auto &content = contents.front();
 			cText = content->getBodyAsString();
 		} else {
 			cText = internalContent.getBodyAsString();
@@ -609,7 +602,7 @@ const string &ChatMessagePrivate::getText() const {
 			cText = internalContent.getBodyAsString();
 		} else {
 			if (!contents.empty()) {
-				Content *content = contents.front();
+				auto &content = contents.front();
 				cText = content->getBodyAsString();
 			}
 		}
@@ -636,7 +629,7 @@ const string &ChatMessagePrivate::getUtf8Text() const {
 		if (hasTextContent()) {
 			cText = getTextContent()->getBodyAsUtf8String();
 		} else if (!contents.empty()) {
-			Content *content = contents.front();
+			auto &content = contents.front();
 			cText = content->getBodyAsUtf8String();
 		} else {
 			cText = internalContent.getBodyAsUtf8String();
@@ -646,7 +639,7 @@ const string &ChatMessagePrivate::getUtf8Text() const {
 			cText = internalContent.getBodyAsUtf8String();
 		} else {
 			if (!contents.empty()) {
-				Content *content = contents.front();
+				auto &content = contents.front();
 				cText = content->getBodyAsUtf8String();
 			}
 		}
@@ -671,14 +664,13 @@ void ChatMessagePrivate::setUtf8Text(const string &text) {
 	}
 }
 
-const Content *ChatMessagePrivate::getFileTransferInformation() const {
+const std::shared_ptr<Content> ChatMessagePrivate::getFileTransferInformation() const {
 	if (hasFileTransferContent()) {
 		return getFileTransferContent();
 	}
-	for (const Content *c : getContents()) {
+	for (const auto &c : getContents()) {
 		if (c->isFile()) {
-			FileContent *fileContent = (FileContent *)c;
-			return fileContent;
+			return c;
 		}
 	}
 	return nullptr;
@@ -688,23 +680,24 @@ bool ChatMessagePrivate::downloadFile() {
 	L_Q();
 
 	for (auto &content : getContents())
-		if (content->isFileTransfer()) return q->downloadFile(static_cast<FileTransferContent *>(content));
+		if (content->isFileTransfer()) return q->downloadFile(static_pointer_cast<FileTransferContent>(content));
 
 	return false;
 }
 
-void ChatMessagePrivate::addContent(Content *content) {
+void ChatMessagePrivate::addContent(std::shared_ptr<Content> content) {
 	getContents().push_back(content);
 }
 
-void ChatMessagePrivate::removeContent(Content *content) {
+void ChatMessagePrivate::removeContent(std::shared_ptr<Content> content) {
 	getContents().remove(content);
 }
 
-void ChatMessagePrivate::replaceContent(Content *contentToRemove, Content *contentToAdd) {
-	list<Content *>::iterator it = contents.begin();
+void ChatMessagePrivate::replaceContent(std::shared_ptr<Content> contentToRemove,
+                                        std::shared_ptr<Content> contentToAdd) {
+	list<std::shared_ptr<Content>>::iterator it = contents.begin();
 	while (it != contents.end()) {
-		Content *content = *it;
+		auto &content = *it;
 		if (content == contentToRemove) {
 			it = contents.erase(it);
 			it = contents.insert(it, contentToAdd);
@@ -870,7 +863,7 @@ LinphoneReason ChatMessagePrivate::receive() {
 
 	if (contents.empty()) {
 		// All previous modifiers only altered the internal content, let's fill the content list
-		contents.push_back(new Content(internalContent));
+		contents.push_back(Content::create(internalContent));
 	}
 
 	for (auto &content : contents) {
@@ -904,7 +897,7 @@ LinphoneReason ChatMessagePrivate::receive() {
 
 	if (errorCode <= 0) {
 		bool foundSupportContentType = false;
-		for (Content *c : contents) {
+		for (auto &c : contents) {
 			ContentType ct(c->getContentType());
 			ct.cleanParameters();
 			string contenttype = ct.getType() + "/" + ct.getSubType();
@@ -1019,9 +1012,9 @@ void ChatMessagePrivate::handleAutoDownload() {
 		bool_t autoDownloadVoiceRecordings =
 		    linphone_core_is_auto_download_voice_recordings_enabled(q->getCore()->getCCore());
 		bool_t autoDownloadIcalendars = linphone_core_is_auto_download_icalendars_enabled(q->getCore()->getCCore());
-		for (Content *c : contents) {
+		for (auto &c : contents) {
 			if (c->isFileTransfer()) {
-				FileTransferContent *ftc = static_cast<FileTransferContent *>(c);
+				auto ftc = static_pointer_cast<FileTransferContent>(c);
 				ContentType fileContentType = ftc->getFileContentType();
 
 				if ((maxSize == 0 || (maxSize > 0 && ftc->getFileSize() <= (size_t)maxSize)) ||
@@ -1061,12 +1054,12 @@ void ChatMessagePrivate::handleAutoDownload() {
 	// Accepted when a message is sent
 	setParticipantState(chatRoom->getMe()->getAddress(), ChatMessage::State::Delivered, ::ms_time(NULL));
 
-	for (Content *c : contents) {
+	for (auto &c : contents) {
 		ContentType contentType = c->getContentType();
 
 		if (contentType.strongEqual(ContentType::Icalendar)) {
-			LinphoneConferenceInfo *cConfInfo = linphone_factory_create_conference_info_from_icalendar_content(
-			    linphone_factory_get(), L_GET_C_BACK_PTR(c));
+			LinphoneConferenceInfo *cConfInfo =
+			    linphone_factory_create_conference_info_from_icalendar_content(linphone_factory_get(), c->toC());
 
 			if (cConfInfo != nullptr) {
 				auto confInfo = ConferenceInfo::toCpp(cConfInfo)->getSharedFromThis();
@@ -1090,16 +1083,15 @@ void ChatMessagePrivate::restoreFileTransferContentAsFileContent() {
 	}
 
 	// Restore FileContents and remove FileTransferContents
-	list<Content *>::iterator it = contents.begin();
+	list<std::shared_ptr<Content>>::iterator it = contents.begin();
 	while (it != contents.end()) {
-		Content *content = *it;
+		auto &content = *it;
 		if (content && content->isFileTransfer()) {
-			FileTransferContent *fileTransferContent = static_cast<FileTransferContent *>(content);
-			FileContent *fileContent = fileTransferContent->getFileContent();
+			auto fileTransferContent = static_pointer_cast<FileTransferContent>(content);
+			auto fileContent = fileTransferContent->getFileContent();
 			if (fileContent) {
 				it = contents.erase(it);
 				it = contents.insert(it, fileContent);
-				delete fileTransferContent;
 			} else {
 				lWarning() << "Found FileTransferContent but no associated FileContent";
 				it++;
@@ -1293,7 +1285,7 @@ void ChatMessagePrivate::send() {
 
 	if (internalContent.isEmpty()) {
 		if (!contents.empty()) {
-			internalContent = *(contents.front());
+			internalContent = Content(*contents.front());
 		} else if (externalBodyUrl.empty()) { // When using external body url, there is no content
 			lError() << "Trying to send a message without any content !";
 			return;
@@ -1764,17 +1756,17 @@ bool ChatMessage::isReadOnly() const {
 	return d->isReadOnly;
 }
 
-const list<Content *> &ChatMessage::getContents() const {
+const list<std::shared_ptr<Content>> &ChatMessage::getContents() const {
 	L_D();
 	return d->getContents();
 }
 
-void ChatMessage::addContent(Content *content) {
+void ChatMessage::addContent(std::shared_ptr<Content> content) {
 	L_D();
 	if (!d->isReadOnly) d->addContent(content);
 }
 
-void ChatMessage::removeContent(Content *content) {
+void ChatMessage::removeContent(std::shared_ptr<Content> content) {
 	L_D();
 	if (!d->isReadOnly) d->removeContent(content);
 }
@@ -1810,7 +1802,7 @@ void ChatMessage::send() {
 	getChatRoom()->getPrivate()->sendChatMessage(getSharedFromThis());
 }
 
-bool ChatMessage::downloadFile(FileTransferContent *fileTransferContent) {
+bool ChatMessage::downloadFile(std::shared_ptr<FileTransferContent> fileTransferContent) {
 	L_D();
 	return d->fileTransferChatMessageModifier.downloadFile(getSharedFromThis(), fileTransferContent);
 }
diff --git a/src/chat/chat-message/chat-message.h b/src/chat/chat-message/chat-message.h
index a34c4804c13b94ac0d922f9e3395e9eb0392ff70..7d3bcb94201f4c037f745855f7ddcd4c59a72428 100644
--- a/src/chat/chat-message/chat-message.h
+++ b/src/chat/chat-message/chat-message.h
@@ -150,9 +150,9 @@ public:
 	bool getToBeStored() const;
 	virtual void setToBeStored(bool value);
 
-	const std::list<Content *> &getContents() const;
-	void addContent(Content *content);
-	void removeContent(Content *content);
+	const std::list<std::shared_ptr<Content>> &getContents() const;
+	void addContent(std::shared_ptr<Content> content);
+	void removeContent(std::shared_ptr<Content> content);
 
 	std::list<ParticipantImdnState> getParticipantsByImdnState(State state) const;
 	std::list<ParticipantImdnState> getParticipantsState() const;
@@ -160,7 +160,7 @@ public:
 	const Content &getInternalContent() const;
 	void setInternalContent(const Content &content);
 
-	bool downloadFile(FileTransferContent *content);
+	bool downloadFile(std::shared_ptr<FileTransferContent> content);
 	bool isFileTransferInProgress() const;
 	void fileUploadEndBackgroundTask();
 
diff --git a/src/chat/chat-message/imdn-message.cpp b/src/chat/chat-message/imdn-message.cpp
index 71b0b3561176c6a313d8206955e0ad47ef6753bf..b0fbdd1f4f0ecd0d15d9c8c8fd9b01b485020807 100644
--- a/src/chat/chat-message/imdn-message.cpp
+++ b/src/chat/chat-message/imdn-message.cpp
@@ -79,7 +79,7 @@ ImdnMessage::ImdnMessage(const Context &context) : NotificationMessage(*new Imdn
 			continue;
 		}
 
-		Content *content = new Content();
+		auto content = Content::create();
 		content->setContentDisposition(ContentDisposition::Notification);
 		content->setContentType(ContentType::Imdn);
 		content->setBodyFromUtf8(
@@ -94,7 +94,7 @@ ImdnMessage::ImdnMessage(const Context &context) : NotificationMessage(*new Imdn
 			continue;
 		}
 
-		Content *content = new Content();
+		auto content = Content::create();
 		content->setContentDisposition(ContentDisposition::Notification);
 		content->setContentType(ContentType::Imdn);
 		content->setBodyFromUtf8(
@@ -109,7 +109,7 @@ ImdnMessage::ImdnMessage(const Context &context) : NotificationMessage(*new Imdn
 			continue;
 		}
 
-		Content *content = new Content();
+		auto content = Content::create();
 		content->setContentDisposition(ContentDisposition::Notification);
 		content->setContentType(ContentType::Imdn);
 		content->setBodyFromUtf8(
diff --git a/src/chat/chat-message/is-composing-message.cpp b/src/chat/chat-message/is-composing-message.cpp
index d2c99cb2696705f21c99a5aba0324a2c93ec04e7..23ce20c516c821b0dc129062b848da4781ef9498 100644
--- a/src/chat/chat-message/is-composing-message.cpp
+++ b/src/chat/chat-message/is-composing-message.cpp
@@ -35,7 +35,7 @@ IsComposingMessage::IsComposingMessage(const shared_ptr<AbstractChatRoom> &chatR
                                        bool isComposing)
     : NotificationMessage(*new NotificationMessagePrivate(chatRoom, ChatMessage::Direction::Outgoing)) {
 	L_D();
-	Content *content = new Content();
+	auto content = Content::create();
 	content->setContentType(ContentType::ImIsComposing);
 	content->setBodyFromUtf8(isComposingHandler.createXml(isComposing));
 	addContent(content);
diff --git a/src/chat/chat-room/abstract-chat-room.h b/src/chat/chat-room/abstract-chat-room.h
index 2a7f719c127032ebc7e459a46dad0aba533f0706..8f380f5e27d081f0e80f481109fdcf7e7884e6c4 100644
--- a/src/chat/chat-room/abstract-chat-room.h
+++ b/src/chat/chat-room/abstract-chat-room.h
@@ -135,7 +135,7 @@ public:
 	virtual std::shared_ptr<ChatMessage> createChatMessage(const std::string &text) = 0;
 	virtual std::shared_ptr<ChatMessage> createChatMessageFromUtf8(const std::string &text) = 0;
 
-	virtual std::shared_ptr<ChatMessage> createFileTransferMessage(FileContent *content) = 0;
+	virtual std::shared_ptr<ChatMessage> createFileTransferMessage(const std::shared_ptr<FileContent> &content) = 0;
 	virtual std::shared_ptr<ChatMessage> createForwardMessage(const std::shared_ptr<ChatMessage> &msg) = 0;
 	virtual std::shared_ptr<ChatMessage> createReplyMessage(const std::shared_ptr<ChatMessage> &msg) = 0;
 
diff --git a/src/chat/chat-room/chat-room.cpp b/src/chat/chat-room/chat-room.cpp
index 1e865019cf469ea1e48d58fbf98243b14cf445e4..d00c1f194c2f060a4a5f628dddc1065f7f800541 100644
--- a/src/chat/chat-room/chat-room.cpp
+++ b/src/chat/chat-room/chat-room.cpp
@@ -166,7 +166,7 @@ void ChatRoomPrivate::realtimeTextReceived(uint32_t character, const shared_ptr<
 
 			shared_ptr<ChatMessage> pendingMessage = q->createChatMessage();
 			pendingMessage->getPrivate()->setDirection(ChatMessage::Direction::Incoming);
-			Content *content = new Content();
+			auto content = Content::create();
 			content->setContentType(ContentType::PlainText);
 			content->setBodyFromUtf8(completeText);
 			pendingMessage->addContent(content);
@@ -699,7 +699,7 @@ shared_ptr<ChatMessage> ChatRoom::createChatMessage() {
 // Deprecated
 shared_ptr<ChatMessage> ChatRoom::createChatMessage(const string &text) {
 	shared_ptr<ChatMessage> chatMessage = createChatMessage();
-	Content *content = new Content();
+	auto content = Content::create();
 	content->setContentType(ContentType::PlainText);
 	content->setBodyFromLocale(text);
 	chatMessage->addContent(content);
@@ -708,14 +708,14 @@ shared_ptr<ChatMessage> ChatRoom::createChatMessage(const string &text) {
 
 shared_ptr<ChatMessage> ChatRoom::createChatMessageFromUtf8(const string &text) {
 	shared_ptr<ChatMessage> chatMessage = createChatMessage();
-	Content *content = new Content();
+	auto content = Content::create();
 	content->setContentType(ContentType::PlainText);
 	content->setBodyFromUtf8(text);
 	chatMessage->addContent(content);
 	return chatMessage;
 }
 
-shared_ptr<ChatMessage> ChatRoom::createFileTransferMessage(FileContent *content) {
+shared_ptr<ChatMessage> ChatRoom::createFileTransferMessage(const std::shared_ptr<FileContent> &content) {
 	shared_ptr<ChatMessage> chatMessage = createChatMessage();
 	chatMessage->addContent(content);
 	return chatMessage;
@@ -723,8 +723,8 @@ shared_ptr<ChatMessage> ChatRoom::createFileTransferMessage(FileContent *content
 
 shared_ptr<ChatMessage> ChatRoom::createForwardMessage(const shared_ptr<ChatMessage> &msg) {
 	shared_ptr<ChatMessage> chatMessage = createChatMessage();
-	for (const Content *c : msg->getContents()) {
-		chatMessage->addContent(c->clone());
+	for (const auto &c : msg->getContents()) {
+		chatMessage->addContent(c->clone()->toSharedPtr());
 	}
 
 	// set forward info
diff --git a/src/chat/chat-room/chat-room.h b/src/chat/chat-room/chat-room.h
index c78b1a599a99617f000d18fea5d5a47f8369f2ee..29cc8e5c0dcf5ae7d1ccb427b145402ca6324de9 100644
--- a/src/chat/chat-room/chat-room.h
+++ b/src/chat/chat-room/chat-room.h
@@ -78,7 +78,7 @@ public:
 	std::shared_ptr<ChatMessage> createChatMessage(const std::string &text) override;
 	std::shared_ptr<ChatMessage> createChatMessageFromUtf8(const std::string &text) override;
 
-	std::shared_ptr<ChatMessage> createFileTransferMessage(FileContent *content) override;
+	std::shared_ptr<ChatMessage> createFileTransferMessage(const std::shared_ptr<FileContent> &content) override;
 	std::shared_ptr<ChatMessage> createForwardMessage(const std::shared_ptr<ChatMessage> &msg) override;
 	std::shared_ptr<ChatMessage> createReplyMessage(const std::shared_ptr<ChatMessage> &msg) override;
 
diff --git a/src/chat/chat-room/client-group-chat-room.cpp b/src/chat/chat-room/client-group-chat-room.cpp
index 5a90c5784331c8e7af7a0027d0f7239d15d55830..a2c6b01af808f49b3bbf94275cbf723a2626dda6 100644
--- a/src/chat/chat-room/client-group-chat-room.cpp
+++ b/src/chat/chat-room/client-group-chat-room.cpp
@@ -725,14 +725,14 @@ bool ClientGroupChatRoom::addParticipants(const list<std::shared_ptr<Address>> &
 
 void ClientGroupChatRoom::sendInvite(std::shared_ptr<CallSession> &session,
                                      const list<std::shared_ptr<Address>> &addressList) {
-	Content content;
-	content.setBodyFromUtf8(Utils::getResourceLists(addressList));
-	content.setContentType(ContentType::ResourceLists);
-	content.setContentDisposition(ContentDisposition::RecipientList);
+	auto content = Content::create();
+	content->setBodyFromUtf8(Utils::getResourceLists(addressList));
+	content->setContentType(ContentType::ResourceLists);
+	content->setContentDisposition(ContentDisposition::RecipientList);
 	if (linphone_core_content_encoding_supported(getCore()->getCCore(), "deflate")) {
-		content.setContentEncoding("deflate");
+		content->setContentEncoding("deflate");
 	}
-	session->startInvite(nullptr, getUtf8Subject(), &content);
+	session->startInvite(nullptr, getUtf8Subject(), content);
 }
 
 bool ClientGroupChatRoom::removeParticipant(const shared_ptr<Participant> &participant) {
@@ -862,20 +862,20 @@ void ClientGroupChatRoom::exhume() {
 	        << "]";
 	d->localExhumePending = true;
 
-	Content content;
+	auto content = Content::create();
 	list<std::shared_ptr<Address>> addresses;
 	addresses.push_front(remoteParticipant);
-	content.setBodyFromUtf8(Utils::getResourceLists(addresses));
-	content.setContentType(ContentType::ResourceLists);
-	content.setContentDisposition(ContentDisposition::RecipientList);
+	content->setBodyFromUtf8(Utils::getResourceLists(addresses));
+	content->setContentType(ContentType::ResourceLists);
+	content->setContentDisposition(ContentDisposition::RecipientList);
 	if (linphone_core_content_encoding_supported(getCore()->getCCore(), "deflate")) {
-		content.setContentEncoding("deflate");
+		content->setContentEncoding("deflate");
 	}
 
 	const auto &conferenceFactoryAddress =
 	    Core::getConferenceFactoryAddress(getCore(), getConferenceId().getLocalAddress());
 	auto session = d->createSessionTo(conferenceFactoryAddress);
-	session->startInvite(nullptr, getUtf8Subject(), &content);
+	session->startInvite(nullptr, getUtf8Subject(), content);
 	setState(ConferenceInterface::State::CreationPending);
 }
 
diff --git a/src/chat/chat-room/proxy-chat-room.cpp b/src/chat/chat-room/proxy-chat-room.cpp
index fe9bdf650b44aa2caecae9b0e0d2b21cb8cbd4e3..c539ae951e2138d46cf3208d9cb4b2a2179f71ba 100644
--- a/src/chat/chat-room/proxy-chat-room.cpp
+++ b/src/chat/chat-room/proxy-chat-room.cpp
@@ -216,7 +216,7 @@ shared_ptr<ChatMessage> ProxyChatRoom::createChatMessageFromUtf8(const string &t
 	L_D();
 	return d->chatRoom->createChatMessageFromUtf8(text);
 }
-shared_ptr<ChatMessage> ProxyChatRoom::createFileTransferMessage(FileContent *content) {
+shared_ptr<ChatMessage> ProxyChatRoom::createFileTransferMessage(const std::shared_ptr<FileContent> &content) {
 	L_D();
 	return d->chatRoom->createFileTransferMessage(content);
 }
diff --git a/src/chat/chat-room/proxy-chat-room.h b/src/chat/chat-room/proxy-chat-room.h
index 8a69f72e6334aeee7cbe78e20c828b6224c11772..5ba41e882f3c481927256df7f23866251af6dd5f 100644
--- a/src/chat/chat-room/proxy-chat-room.h
+++ b/src/chat/chat-room/proxy-chat-room.h
@@ -74,7 +74,7 @@ public:
 	createChatMessage(const std::string &text) override; // Deprecated. Text is in System Locale
 	std::shared_ptr<ChatMessage> createChatMessageFromUtf8(const std::string &text) override;
 
-	std::shared_ptr<ChatMessage> createFileTransferMessage(FileContent *content) override;
+	std::shared_ptr<ChatMessage> createFileTransferMessage(const std::shared_ptr<FileContent> &content) override;
 	std::shared_ptr<ChatMessage> createForwardMessage(const std::shared_ptr<ChatMessage> &msg) override;
 	std::shared_ptr<ChatMessage> createReplyMessage(const std::shared_ptr<ChatMessage> &msg) override;
 
diff --git a/src/chat/chat-room/server-group-chat-room.cpp b/src/chat/chat-room/server-group-chat-room.cpp
index d63e2957c32918c3b7a559d7b9f646b8d73d63f6..115f9adc5539cdf8932d6db35223bf5eefa14cc9 100644
--- a/src/chat/chat-room/server-group-chat-room.cpp
+++ b/src/chat/chat-room/server-group-chat-room.cpp
@@ -1106,13 +1106,13 @@ void ServerGroupChatRoomPrivate::inviteDevice(const shared_ptr<ParticipantDevice
 		return;
 	}
 
-	Content content;
-	content.setBodyFromUtf8(Utils::getResourceLists(addressesList));
-	content.setContentType(ContentType::ResourceLists);
-	content.setContentDisposition(ContentDisposition::RecipientListHistory);
+	auto content = Content::create();
+	content->setBodyFromUtf8(Utils::getResourceLists(addressesList));
+	content->setContentType(ContentType::ResourceLists);
+	content->setContentDisposition(ContentDisposition::RecipientListHistory);
 	if (linphone_core_content_encoding_supported(q->getCore()->getCCore(), "deflate"))
-		content.setContentEncoding("deflate");
-	session->startInvite(nullptr, q->getUtf8Subject(), &content);
+		content->setContentEncoding("deflate");
+	session->startInvite(nullptr, q->getUtf8Subject(), content);
 }
 
 void ServerGroupChatRoomPrivate::byeDevice(const std::shared_ptr<ParticipantDevice> &device) {
diff --git a/src/chat/encryption/encryption-engine.h b/src/chat/encryption/encryption-engine.h
index fe92754a50be44a3fd64ecd1d21ac15a23aa2627..bbeaba602208c75254766aa56c96c5e86a8df598 100644
--- a/src/chat/encryption/encryption-engine.h
+++ b/src/chat/encryption/encryption-engine.h
@@ -64,9 +64,10 @@ public:
 		return false;
 	}
 
-	virtual void generateFileTransferKey(BCTBX_UNUSED(const std::shared_ptr<AbstractChatRoom> &ChatRoom),
-	                                     BCTBX_UNUSED(const std::shared_ptr<ChatMessage> &message),
-	                                     BCTBX_UNUSED(FileTransferContent *fileTransferContent)) {
+	virtual void
+	generateFileTransferKey(BCTBX_UNUSED(const std::shared_ptr<AbstractChatRoom> &ChatRoom),
+	                        BCTBX_UNUSED(const std::shared_ptr<ChatMessage> &message),
+	                        BCTBX_UNUSED(const std::shared_ptr<FileTransferContent> &fileTransferContent)) {
 	}
 
 	virtual int downloadingFile(BCTBX_UNUSED(const std::shared_ptr<ChatMessage> &message),
@@ -74,7 +75,7 @@ public:
 	                            BCTBX_UNUSED(const uint8_t *buffer),
 	                            BCTBX_UNUSED(size_t size),
 	                            BCTBX_UNUSED(uint8_t *decryptedBuffer),
-	                            BCTBX_UNUSED(FileTransferContent *fileTransferContent)) {
+	                            BCTBX_UNUSED(const std::shared_ptr<FileTransferContent> &fileTransferContent)) {
 		return 0;
 	}
 
@@ -83,11 +84,11 @@ public:
 	                          BCTBX_UNUSED(const uint8_t *buffer),
 	                          BCTBX_UNUSED(size_t *size),
 	                          BCTBX_UNUSED(uint8_t *encryptedBuffer),
-	                          BCTBX_UNUSED(FileTransferContent *fileTransferContent)) {
+	                          BCTBX_UNUSED(const std::shared_ptr<FileTransferContent> &fileTransferContent)) {
 		return 0;
 	}
 
-	virtual int cancelFileTransfer(BCTBX_UNUSED(FileTransferContent *fileTransferContent)) {
+	virtual int cancelFileTransfer(BCTBX_UNUSED(const std::shared_ptr<FileTransferContent> &fileTransferContent)) {
 		return 0;
 	}
 
diff --git a/src/chat/encryption/legacy-encryption-engine.cpp b/src/chat/encryption/legacy-encryption-engine.cpp
index 59d9e220bf2423a603cba454195e8505be90f508..117fa9dc9bdbca3a4d02032521056c3cbf853d7a 100644
--- a/src/chat/encryption/legacy-encryption-engine.cpp
+++ b/src/chat/encryption/legacy-encryption-engine.cpp
@@ -96,9 +96,10 @@ bool LegacyEncryptionEngine::isEncryptionEnabledForFileTransfer(const shared_ptr
 	return false;
 }
 
-void LegacyEncryptionEngine::generateFileTransferKey(const shared_ptr<AbstractChatRoom> &chatRoom,
-                                                     const shared_ptr<ChatMessage> &message,
-                                                     BCTBX_UNUSED(FileTransferContent *fileTransferContent)) {
+void LegacyEncryptionEngine::generateFileTransferKey(
+    const shared_ptr<AbstractChatRoom> &chatRoom,
+    const shared_ptr<ChatMessage> &message,
+    BCTBX_UNUSED(const std::shared_ptr<FileTransferContent> &fileTransferContent)) {
 	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(chatRoom->getCore()->getCCore());
 	LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
 	LinphoneImEncryptionEngineCbsGenerateFileTransferKeyCb generate_file_transfer_key_cb =
@@ -108,12 +109,13 @@ void LegacyEncryptionEngine::generateFileTransferKey(const shared_ptr<AbstractCh
 	}
 }
 
-int LegacyEncryptionEngine::downloadingFile(const shared_ptr<ChatMessage> &message,
-                                            size_t offset,
-                                            const uint8_t *buffer,
-                                            size_t size,
-                                            uint8_t *decryptedBuffer,
-                                            BCTBX_UNUSED(FileTransferContent *fileTransferContent)) {
+int LegacyEncryptionEngine::downloadingFile(
+    const shared_ptr<ChatMessage> &message,
+    size_t offset,
+    const uint8_t *buffer,
+    size_t size,
+    uint8_t *decryptedBuffer,
+    BCTBX_UNUSED(const std::shared_ptr<FileTransferContent> &fileTransferContent)) {
 	shared_ptr<AbstractChatRoom> chatRoom = message->getChatRoom();
 	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(chatRoom->getCore()->getCCore());
 	LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
@@ -125,12 +127,13 @@ int LegacyEncryptionEngine::downloadingFile(const shared_ptr<ChatMessage> &messa
 	return -1;
 }
 
-int LegacyEncryptionEngine::uploadingFile(const shared_ptr<ChatMessage> &message,
-                                          size_t offset,
-                                          const uint8_t *buffer,
-                                          size_t *size,
-                                          uint8_t *encryptedBuffer,
-                                          BCTBX_UNUSED(FileTransferContent *fileTransferContent)) {
+int LegacyEncryptionEngine::uploadingFile(
+    const shared_ptr<ChatMessage> &message,
+    size_t offset,
+    const uint8_t *buffer,
+    size_t *size,
+    uint8_t *encryptedBuffer,
+    BCTBX_UNUSED(const std::shared_ptr<FileTransferContent> &fileTransferContent)) {
 	shared_ptr<AbstractChatRoom> chatRoom = message->getChatRoom();
 	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(chatRoom->getCore()->getCCore());
 	LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
@@ -142,7 +145,8 @@ int LegacyEncryptionEngine::uploadingFile(const shared_ptr<ChatMessage> &message
 	return -1;
 }
 
-int LegacyEncryptionEngine::cancelFileTransfer(BCTBX_UNUSED(FileTransferContent *fileTransferContent)) {
+int LegacyEncryptionEngine::cancelFileTransfer(
+    BCTBX_UNUSED(const std::shared_ptr<FileTransferContent> &fileTransferContent)) {
 	return 0;
 }
 
diff --git a/src/chat/encryption/legacy-encryption-engine.h b/src/chat/encryption/legacy-encryption-engine.h
index 4a2b5cec49f22aec9fa9cbbb8802de3a8a12dfe8..c0e84e5bad17813f3886f5d8da1231b68a31c519 100644
--- a/src/chat/encryption/legacy-encryption-engine.h
+++ b/src/chat/encryption/legacy-encryption-engine.h
@@ -38,20 +38,20 @@ public:
 	bool isEncryptionEnabledForFileTransfer(const std::shared_ptr<AbstractChatRoom> &ChatRoom) override;
 	void generateFileTransferKey(const std::shared_ptr<AbstractChatRoom> &ChatRoom,
 	                             const std::shared_ptr<ChatMessage> &message,
-	                             FileTransferContent *fileTransferContent) override;
+	                             const std::shared_ptr<FileTransferContent> &fileTransferContent) override;
 	int downloadingFile(const std::shared_ptr<ChatMessage> &message,
 	                    size_t offset,
 	                    const uint8_t *buffer,
 	                    size_t size,
 	                    uint8_t *decrypted_buffer,
-	                    FileTransferContent *fileTransferContent) override;
+	                    const std::shared_ptr<FileTransferContent> &fileTransferContent) override;
 	int uploadingFile(const std::shared_ptr<ChatMessage> &message,
 	                  size_t offset,
 	                  const uint8_t *buffer,
 	                  size_t *size,
 	                  uint8_t *encrypted_buffer,
-	                  FileTransferContent *fileTransferContent) override;
-	int cancelFileTransfer(FileTransferContent *fileTransferContent) override;
+	                  const std::shared_ptr<FileTransferContent> &fileTransferContent) override;
+	int cancelFileTransfer(const std::shared_ptr<FileTransferContent> &fileTransferContent) override;
 };
 
 LINPHONE_END_NAMESPACE
diff --git a/src/chat/encryption/lime-x3dh-encryption-engine.cpp b/src/chat/encryption/lime-x3dh-encryption-engine.cpp
index 7bee4e4b1801185a800cf7df11b28702ec2da84e..ba241a3a9f54a4be97e08188e14318ff9226d5c6 100644
--- a/src/chat/encryption/lime-x3dh-encryption-engine.cpp
+++ b/src/chat/encryption/lime-x3dh-encryption-engine.cpp
@@ -356,19 +356,19 @@ ChatMessageModifier::Result LimeX3dhEncryptionEngine::processOutgoingMessage(con
 					    }
 				    }
 
-				    list<Content *> contents;
+				    list<shared_ptr<Content>> contents;
 
 				    // ---------------------------------------------- CPIM
 
 				    // Replaces SIPFRAG since version 4.4.0
 				    CpimChatMessageModifier ccmm;
-				    Content *cpimContent = ccmm.createMinimalCpimContentForLimeMessage(message);
+				    auto cpimContent = ccmm.createMinimalCpimContentForLimeMessage(message);
 				    contents.push_back(std::move(cpimContent));
 
 				    // ---------------------------------------------- SIPFRAG
 
 				    // For backward compatibility only since 4.4.0
-				    Content *sipfrag = new Content();
+				    auto sipfrag = Content::create();
 				    sipfrag->setBodyFromLocale("From: <" + localDeviceId + ">");
 				    sipfrag->setContentType(ContentType::SipFrag);
 				    contents.push_back(std::move(sipfrag));
@@ -377,7 +377,7 @@ ChatMessageModifier::Result LimeX3dhEncryptionEngine::processOutgoingMessage(con
 
 				    for (const lime::RecipientData &recipient : filteredRecipients) {
 					    string cipherHeaderB64 = bctoolbox::encodeBase64(recipient.DRmessage);
-					    Content *cipherHeader = new Content();
+					    auto cipherHeader = Content::create();
 					    cipherHeader->setBodyFromLocale(cipherHeaderB64);
 					    cipherHeader->setContentType(ContentType::LimeKey);
 					    cipherHeader->addHeader("Content-Id", recipient.deviceId);
@@ -390,13 +390,13 @@ ChatMessageModifier::Result LimeX3dhEncryptionEngine::processOutgoingMessage(con
 
 				    const vector<uint8_t> *binaryCipherMessage = cipherMessage.get();
 				    string cipherMessageB64 = bctoolbox::encodeBase64(*binaryCipherMessage);
-				    Content *cipherMessage = new Content();
-				    cipherMessage->setBodyFromLocale(cipherMessageB64);
-				    cipherMessage->setContentType(ContentType::OctetStream);
-				    cipherMessage->addHeader("Content-Description", "Encrypted message");
-				    contents.push_back(std::move(cipherMessage));
+				    auto cipherMessageC = Content::create();
+				    cipherMessageC->setBodyFromLocale(cipherMessageB64);
+				    cipherMessageC->setContentType(ContentType::OctetStream);
+				    cipherMessageC->addHeader("Content-Description", "Encrypted message");
+				    contents.push_back(std::move(cipherMessageC));
 
-				    Content finalContent = ContentManager::contentListToMultipart(contents, true);
+				    auto finalContent = ContentManager::contentListToMultipart(contents, true);
 
 				    /* Septembre 2022 note:
 				     * Because of a scandalous ancient bug in belle-sip, we are forced to set
@@ -418,10 +418,6 @@ ChatMessageModifier::Result LimeX3dhEncryptionEngine::processOutgoingMessage(con
 				    message->getPrivate()->send();
 				    *result = ChatMessageModifier::Result::Done;
 
-				    // TODO can be improved
-				    for (const auto &content : contents) {
-					    delete content;
-				    }
 			    } else {
 				    lError() << "[LIME] operation failed: " << errorMessage;
 				    message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(),
@@ -454,10 +450,10 @@ ChatMessageModifier::Result LimeX3dhEncryptionEngine::processIncomingMessage(con
 
 	const Content *internalContent;
 	if (!message->getInternalContent().isEmpty()) internalContent = &(message->getInternalContent());
-	else internalContent = message->getContents().front();
+	else internalContent = message->getContents().front().get();
 
 	// Check if the message is encrypted and unwrap the multipart
-	if (!isMessageEncrypted(internalContent)) {
+	if (!isMessageEncrypted(*internalContent)) {
 		lError() << "[LIME] unexpected content-type: " << internalContent->getContentType();
 		// Set unencrypted content warning flag because incoming message type is unexpected
 		message->getPrivate()->setUnencryptedContentWarning(true);
@@ -590,9 +586,10 @@ bool LimeX3dhEncryptionEngine::isEncryptionEnabledForFileTransfer(const shared_p
 #define FILE_TRANSFER_AUTH_TAG_SIZE 16
 #define FILE_TRANSFER_KEY_SIZE 32
 
-void LimeX3dhEncryptionEngine::generateFileTransferKey(BCTBX_UNUSED(const shared_ptr<AbstractChatRoom> &chatRoom),
-                                                       BCTBX_UNUSED(const shared_ptr<ChatMessage> &message),
-                                                       FileTransferContent *fileTransferContent) {
+void LimeX3dhEncryptionEngine::generateFileTransferKey(
+    BCTBX_UNUSED(const shared_ptr<AbstractChatRoom> &chatRoom),
+    BCTBX_UNUSED(const shared_ptr<ChatMessage> &message),
+    const std::shared_ptr<FileTransferContent> &fileTransferContent) {
 	char keyBuffer[FILE_TRANSFER_KEY_SIZE]; // temporary storage of generated key: 192 bits of key + 64 bits of initial
 	                                        // vector
 	// generate a random 192 bits key + 64 bits of initial vector and store it into the file_transfer_information->key
@@ -607,18 +604,18 @@ int LimeX3dhEncryptionEngine::downloadingFile(BCTBX_UNUSED(const shared_ptr<Chat
                                               const uint8_t *buffer,
                                               size_t size,
                                               uint8_t *decrypted_buffer,
-                                              FileTransferContent *fileTransferContent) {
+                                              const std::shared_ptr<FileTransferContent> &fileTransferContent) {
 	if (fileTransferContent == nullptr) return -1;
 
-	Content *content = static_cast<Content *>(fileTransferContent);
+	auto content = static_pointer_cast<Content>(fileTransferContent);
 	const char *fileKey = fileTransferContent->getFileKey().data();
 	if (!fileKey) return -1;
 
 	if (!buffer) {
 		// get the authentication tag
 		char authTag[FILE_TRANSFER_AUTH_TAG_SIZE]; // store the authentication tag generated at the end of decryption
-		int ret = bctbx_aes_gcm_decryptFile(linphone_content_get_cryptoContext_address(L_GET_C_BACK_PTR(content)), NULL,
-		                                    FILE_TRANSFER_AUTH_TAG_SIZE, authTag, NULL);
+		int ret = bctbx_aes_gcm_decryptFile(content->getCryptoContextAddress(), NULL, FILE_TRANSFER_AUTH_TAG_SIZE,
+		                                    authTag, NULL);
 		if (ret < 0) return ret;
 		// compare auth tag if we have one
 		const size_t fileAuthTagSize = fileTransferContent->getFileAuthTagSize();
@@ -636,8 +633,8 @@ int LimeX3dhEncryptionEngine::downloadingFile(BCTBX_UNUSED(const shared_ptr<Chat
 		}
 	}
 
-	return bctbx_aes_gcm_decryptFile(linphone_content_get_cryptoContext_address(L_GET_C_BACK_PTR(content)),
-	                                 (unsigned char *)fileKey, size, (char *)decrypted_buffer, (char *)buffer);
+	return bctbx_aes_gcm_decryptFile(content->getCryptoContextAddress(), (unsigned char *)fileKey, size,
+	                                 (char *)decrypted_buffer, (char *)buffer);
 }
 
 int LimeX3dhEncryptionEngine::uploadingFile(BCTBX_UNUSED(const shared_ptr<ChatMessage> &message),
@@ -645,10 +642,10 @@ int LimeX3dhEncryptionEngine::uploadingFile(BCTBX_UNUSED(const shared_ptr<ChatMe
                                             const uint8_t *buffer,
                                             size_t *size,
                                             uint8_t *encrypted_buffer,
-                                            FileTransferContent *fileTransferContent) {
+                                            const std::shared_ptr<FileTransferContent> &fileTransferContent) {
 	if (fileTransferContent == nullptr) return -1;
 
-	Content *content = static_cast<Content *>(fileTransferContent);
+	auto content = static_pointer_cast<Content>(fileTransferContent);
 	const char *fileKey = fileTransferContent->getFileKey().data();
 	if (!fileKey) return -1;
 
@@ -656,8 +653,8 @@ int LimeX3dhEncryptionEngine::uploadingFile(BCTBX_UNUSED(const shared_ptr<ChatMe
 	if (!buffer || *size == 0) {
 		char authTag[FILE_TRANSFER_AUTH_TAG_SIZE]; // store the authentication tag generated at the end of encryption,
 		                                           // size is fixed at 16 bytes
-		int ret = bctbx_aes_gcm_encryptFile(linphone_content_get_cryptoContext_address(L_GET_C_BACK_PTR(content)), NULL,
-		                                    FILE_TRANSFER_AUTH_TAG_SIZE, NULL, authTag);
+		int ret = bctbx_aes_gcm_encryptFile(content->getCryptoContextAddress(), NULL, FILE_TRANSFER_AUTH_TAG_SIZE, NULL,
+		                                    authTag);
 		fileTransferContent->setFileAuthTag(authTag, 16);
 		return ret;
 	}
@@ -670,18 +667,16 @@ int LimeX3dhEncryptionEngine::uploadingFile(BCTBX_UNUSED(const shared_ptr<ChatMe
 		*size -= (*size % 16);
 	}
 
-	return bctbx_aes_gcm_encryptFile(linphone_content_get_cryptoContext_address(L_GET_C_BACK_PTR(content)),
-	                                 (unsigned char *)fileKey, *size, (char *)buffer, (char *)encrypted_buffer);
+	return bctbx_aes_gcm_encryptFile(content->getCryptoContextAddress(), (unsigned char *)fileKey, *size,
+	                                 (char *)buffer, (char *)encrypted_buffer);
 
 	return 0;
 }
 
-int LimeX3dhEncryptionEngine::cancelFileTransfer(FileTransferContent *fileTransferContent) {
-	Content *content = static_cast<Content *>(fileTransferContent);
+int LimeX3dhEncryptionEngine::cancelFileTransfer(const std::shared_ptr<FileTransferContent> &fileTransferContent) {
 	// calling decrypt with no data and no buffer to write the tag will simply release the encryption context and delete
 	// it
-	return bctbx_aes_gcm_decryptFile(linphone_content_get_cryptoContext_address(L_GET_C_BACK_PTR(content)), NULL, 0,
-	                                 NULL, NULL);
+	return bctbx_aes_gcm_decryptFile(fileTransferContent->getCryptoContextAddress(), NULL, 0, NULL, NULL);
 }
 
 EncryptionEngine::EngineType LimeX3dhEncryptionEngine::getEngineType() {
diff --git a/src/chat/encryption/lime-x3dh-encryption-engine.h b/src/chat/encryption/lime-x3dh-encryption-engine.h
index a970bf7891ee49a1191e5b7610a3ccf86bcfc985..7f9f6eea9649eed13e0f72fe8c291bbafcb98936 100644
--- a/src/chat/encryption/lime-x3dh-encryption-engine.h
+++ b/src/chat/encryption/lime-x3dh-encryption-engine.h
@@ -69,23 +69,23 @@ public:
 
 	void generateFileTransferKey(const std::shared_ptr<AbstractChatRoom> &ChatRoom,
 	                             const std::shared_ptr<ChatMessage> &message,
-	                             FileTransferContent *fileTransferContent) override;
+	                             const std::shared_ptr<FileTransferContent> &fileTransferContent) override;
 
 	int downloadingFile(const std::shared_ptr<ChatMessage> &message,
 	                    size_t offset,
 	                    const uint8_t *buffer,
 	                    size_t size,
 	                    uint8_t *decrypted_buffer,
-	                    FileTransferContent *fileTransferContent) override;
+	                    const std::shared_ptr<FileTransferContent> &fileTransferContent) override;
 
 	int uploadingFile(const std::shared_ptr<ChatMessage> &message,
 	                  size_t offset,
 	                  const uint8_t *buffer,
 	                  size_t *size,
 	                  uint8_t *encrypted_buffer,
-	                  FileTransferContent *fileTransferContent) override;
+	                  const std::shared_ptr<FileTransferContent> &fileTransferContent) override;
 
-	int cancelFileTransfer(FileTransferContent *fileTransferContent) override;
+	int cancelFileTransfer(const std::shared_ptr<FileTransferContent> &fileTransferContent) override;
 
 	void mutualAuthentication(MSZrtpContext *zrtpContext,
 	                          const std::shared_ptr<SalMediaDescription> &localMediaDescription,
diff --git a/src/chat/encryption/lime-x3dh-server-engine.cpp b/src/chat/encryption/lime-x3dh-server-engine.cpp
index 50ae7faeb4b90282033766f262586885efd3a86e..8181ef664f5a038fa35b1de3f7b61dfe035b0a05 100644
--- a/src/chat/encryption/lime-x3dh-server-engine.cpp
+++ b/src/chat/encryption/lime-x3dh-server-engine.cpp
@@ -69,10 +69,10 @@ LimeX3dhEncryptionServerEngine::processOutgoingMessage(const std::shared_ptr<Cha
 	}
 
 	if (!message->getInternalContent().isEmpty()) internalContent = &(message->getInternalContent());
-	else internalContent = message->getContents().front();
+	else internalContent = message->getContents().front().get();
 
 	// Check if the message is encrypted
-	if (!internalContent || !isMessageEncrypted(internalContent)) {
+	if (!internalContent || !isMessageEncrypted(*internalContent)) {
 		return ChatMessageModifier::Result::Skipped;
 	}
 
@@ -112,8 +112,8 @@ EncryptionEngine::EngineType LimeX3dhEncryptionServerEngine::getEngineType() {
 	return engineType;
 }
 
-bool LimeX3dhUtils::isMessageEncrypted(const Content *internalContent) {
-	const ContentType &incomingContentType = internalContent->getContentType();
+bool LimeX3dhUtils::isMessageEncrypted(const Content &internalContent) {
+	const ContentType &incomingContentType = internalContent.getContentType();
 	ContentType expectedContentType = ContentType::Encrypted;
 
 	if (incomingContentType == expectedContentType) {
diff --git a/src/chat/encryption/lime-x3dh-server-engine.h b/src/chat/encryption/lime-x3dh-server-engine.h
index 2a26f749554d6c7dc9a6e1a2a471de562e40de74..68a1421cd31af3d7009b3f2235760045ba16c91d 100644
--- a/src/chat/encryption/lime-x3dh-server-engine.h
+++ b/src/chat/encryption/lime-x3dh-server-engine.h
@@ -30,7 +30,7 @@ LINPHONE_BEGIN_NAMESPACE
 
 class LimeX3dhUtils {
 public:
-	static bool isMessageEncrypted(const Content *internalContent);
+	static bool isMessageEncrypted(const Content &internalContent);
 };
 
 class LimeX3dhEncryptionServerEngine : public EncryptionEngine, public CoreListener, private LimeX3dhUtils {
diff --git a/src/chat/modifier/cpim-chat-message-modifier.cpp b/src/chat/modifier/cpim-chat-message-modifier.cpp
index 3bdef9d2d79fd6e006b9bfc5e6d529de3b0e2ee0..c572875e44ba71cb9454c73fd7b08bf1eb50dacf 100644
--- a/src/chat/modifier/cpim-chat-message-modifier.cpp
+++ b/src/chat/modifier/cpim-chat-message-modifier.cpp
@@ -138,7 +138,7 @@ ChatMessageModifier::Result CpimChatMessageModifier::encode(const shared_ptr<Cha
 		// We're the first ChatMessageModifier to be called, we'll create the private content from the public one
 		// We take the first one because if there is more of them, the multipart modifier should have been called first
 		// So we should not be in this block
-		content = message->getContents().front();
+		content = message->getContents().front().get();
 	}
 
 	const string contentBody = content->getBodyAsUtf8String();
@@ -163,7 +163,7 @@ ChatMessageModifier::Result CpimChatMessageModifier::encode(const shared_ptr<Cha
 ChatMessageModifier::Result CpimChatMessageModifier::decode(const shared_ptr<ChatMessage> &message, int &errorCode) {
 	const Content *content = nullptr;
 	if (!message->getInternalContent().isEmpty()) content = &(message->getInternalContent());
-	else if (message->getContents().size() > 0) content = message->getContents().front();
+	else if (message->getContents().size() > 0) content = message->getContents().front().get();
 
 	if (content == nullptr) {
 		lError() << "[CPIM] Couldn't find a valid content in the message";
@@ -299,7 +299,8 @@ string CpimChatMessageModifier::cpimAddressUri(const std::shared_ptr<Address> &a
 	return addr->asStringUriOnly();
 }
 
-Content *CpimChatMessageModifier::createMinimalCpimContentForLimeMessage(const shared_ptr<ChatMessage> &message) const {
+std::shared_ptr<Content>
+CpimChatMessageModifier::createMinimalCpimContentForLimeMessage(const shared_ptr<ChatMessage> &message) const {
 	shared_ptr<AbstractChatRoom> chatRoom = message->getChatRoom();
 	const string &localDeviceId = chatRoom->getLocalAddress()->asStringUriOnly();
 
@@ -310,7 +311,7 @@ Content *CpimChatMessageModifier::createMinimalCpimContentForLimeMessage(const s
 	    Cpim::GenericHeader(imdnNamespace + "." + imdnMessageIdHeader, message->getImdnMessageId()));
 	cpimMessage.addContentHeader(Cpim::GenericHeader("Content-Type", ContentType::PlainText.getMediaType()));
 
-	Content *cpimContent = new Content();
+	auto cpimContent = Content::create();
 	cpimContent->setContentType(ContentType::Cpim);
 	cpimContent->setBodyFromLocale(cpimMessage.asString());
 
@@ -321,16 +322,16 @@ std::shared_ptr<LinphonePrivate::Address>
 CpimChatMessageModifier::parseFromHeaderCpimContentInLimeMessage(const std::shared_ptr<ChatMessage> &message) const {
 	const Content *content = nullptr;
 	if (!message->getInternalContent().isEmpty()) content = &(message->getInternalContent());
-	else if (message->getContents().size() > 0) content = message->getContents().front();
+	else if (message->getContents().size() > 0) content = message->getContents().front().get();
 
 	if (content == nullptr) {
 		return nullptr;
 	}
 	list<Content> contentList = ContentManager::multipartToContentList(*content);
 
-	for (const auto &content : contentList) {
-		if (content.getContentType() != ContentType::Cpim) continue;
-		const string contentBody = content.getBodyAsString();
+	for (const auto &c : contentList) {
+		if (c.getContentType() != ContentType::Cpim) continue;
+		const string contentBody = c.getBodyAsString();
 		const shared_ptr<const Cpim::Message> cpimMessage = Cpim::Message::createFromString(contentBody);
 		if (cpimMessage && cpimMessage->getMessageHeader("From")) {
 			return Address::create(
diff --git a/src/chat/modifier/cpim-chat-message-modifier.h b/src/chat/modifier/cpim-chat-message-modifier.h
index 1f06b5675f3feee8d431743a7f6a1aeb61220c8e..8749e667245d6ba750a72f5e381a9e174328eb66 100644
--- a/src/chat/modifier/cpim-chat-message-modifier.h
+++ b/src/chat/modifier/cpim-chat-message-modifier.h
@@ -33,7 +33,7 @@ public:
 
 	Result encode(const std::shared_ptr<ChatMessage> &message, int &errorCode) override;
 	Result decode(const std::shared_ptr<ChatMessage> &message, int &errorCode) override;
-	Content *createMinimalCpimContentForLimeMessage(const std::shared_ptr<ChatMessage> &message) const;
+	std::shared_ptr<Content> createMinimalCpimContentForLimeMessage(const std::shared_ptr<ChatMessage> &message) const;
 	std::string parseMinimalCpimContentInLimeMessage(const std::shared_ptr<ChatMessage> &message,
 	                                                 const Content &cpimContent) const;
 	std::shared_ptr<LinphonePrivate::Address>
diff --git a/src/chat/modifier/file-transfer-chat-message-modifier.cpp b/src/chat/modifier/file-transfer-chat-message-modifier.cpp
index b1676d21d0f316efb7af39341bb80a44ae567427..7ff79b8ef11218aef0ff313acf0c253493c3e3c6 100644
--- a/src/chat/modifier/file-transfer-chat-message-modifier.cpp
+++ b/src/chat/modifier/file-transfer-chat-message-modifier.cpp
@@ -32,7 +32,6 @@
 #include "chat/encryption/encryption-engine.h"
 #include "conference/participant.h"
 #include "content/content-type.h"
-#include "content/content.h"
 #include "core/core.h"
 #include "logger/logger.h"
 
@@ -72,10 +71,10 @@ ChatMessageModifier::Result FileTransferChatMessageModifier::encode(const shared
 	currentFileContentToTransfer = nullptr;
 	currentFileTransferContent = nullptr;
 	// For each FileContent, upload it and create a FileTransferContent
-	for (Content *content : message->getContents()) {
+	for (auto &content : message->getContents()) {
 		if (content->isFile()) {
 			lInfo() << "Found file content [" << content << "], set it for file upload";
-			FileContent *fileContent = (FileContent *)content;
+			auto fileContent = static_pointer_cast<FileContent>(content);
 			currentFileContentToTransfer = fileContent;
 			break;
 		}
@@ -115,7 +114,7 @@ void FileTransferChatMessageModifier::fileTransferOnProgress(BCTBX_UNUSED(belle_
 
 	LinphoneChatMessage *msg = L_GET_C_BACK_PTR(message);
 	LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
-	LinphoneContent *content = L_GET_C_BACK_PTR((Content *)currentFileContentToTransfer);
+	LinphoneContent *content = currentFileContentToTransfer->toC();
 	// Deprecated: use list of callbacks now
 	if (linphone_chat_message_cbs_get_file_transfer_progress_indication(cbs)) {
 		linphone_chat_message_cbs_get_file_transfer_progress_indication(cbs)(msg, content, offset, total);
@@ -156,14 +155,14 @@ int FileTransferChatMessageModifier::onSendBody(BCTBX_UNUSED(belle_sip_user_body
 		return BELLE_SIP_STOP;
 	}
 
-	// if we've not reach the end of file yet, ask for more data
+	// if we've not reached the end of file yet, ask for more data
 	// in case of file body handler, won't be called
 	if (currentFileContentToTransfer->getFilePath().empty() && offset < currentFileContentToTransfer->getFileSize()) {
 		// get data from call back
 		LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
 		LinphoneChatMessageCbsFileTransferSendCb file_transfer_send_cb =
 		    linphone_chat_message_cbs_get_file_transfer_send(cbs);
-		LinphoneContent *content = L_GET_C_BACK_PTR((Content *)currentFileContentToTransfer);
+		LinphoneContent *content = currentFileContentToTransfer->toC();
 		// Deprecated: use list of callbacks now
 		if (file_transfer_send_cb) {
 			LinphoneBuffer *lb = file_transfer_send_cb(msg, content, offset, *size);
@@ -247,7 +246,7 @@ FileTransferChatMessageModifier::prepare_upload_body_handler(shared_ptr<ChatMess
 	EncryptionEngine *imee = message->getCore()->getEncryptionEngine();
 	if (imee) isFileEncryptionEnabled = imee->isEncryptionEnabledForFileTransfer(chatRoom);
 
-	FileTransferContent *fileTransferContent = new FileTransferContent();
+	auto fileTransferContent = FileTransferContent::create<FileTransferContent>();
 	fileTransferContent->setContentType(ContentType::FileTransfer);
 	fileTransferContent->setFileSize(currentFileContentToTransfer->getFileSize()); // Copy file size information
 	fileTransferContent->setFilePath(currentFileContentToTransfer->getFilePath()); // Copy file path information
@@ -291,7 +290,7 @@ FileTransferChatMessageModifier::prepare_upload_body_handler(shared_ptr<ChatMess
 		uint8_t *buf = (uint8_t *)ms_malloc(buf_size);
 		memcpy(buf, currentFileContentToTransfer->getBody().data(), buf_size);
 
-		EncryptionEngine *imee = message->getCore()->getEncryptionEngine();
+		imee = message->getCore()->getEncryptionEngine();
 		if (imee) {
 			size_t max_size = buf_size;
 			uint8_t *encrypted_buffer = (uint8_t *)ms_malloc0(max_size);
@@ -340,7 +339,7 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht
 			auto bh = prepare_upload_body_handler(message);
 
 			// Save currentFileContentToTransfer pointer as it will be set to NULL in releaseHttpRequest
-			FileContent *fileContent = currentFileContentToTransfer;
+			auto fileContent = currentFileContentToTransfer;
 			releaseHttpRequest();
 			currentFileContentToTransfer = fileContent;
 
@@ -349,7 +348,7 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht
 		} else if (code == 200) { // file has been uploaded correctly, get server reply and send it
 			const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response);
 			if (body && strlen(body) > 0) {
-				FileTransferContent *parsedXmlFileTransferContent = new FileTransferContent();
+				auto parsedXmlFileTransferContent = FileTransferContent::create<FileTransferContent>();
 				parseFileTransferXmlIntoContent(body, parsedXmlFileTransferContent);
 
 				if (parsedXmlFileTransferContent->getFileName().empty() ||
@@ -357,7 +356,6 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht
 					lWarning()
 					    << "Received response from server but unable to parse file name or URL, file transfer failed";
 					message->getPrivate()->replaceContent(currentFileTransferContent, currentFileContentToTransfer);
-					delete currentFileTransferContent;
 					currentFileTransferContent = nullptr;
 
 					message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(),
@@ -365,7 +363,6 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht
 					releaseHttpRequest();
 					fileUploadEndBackgroundTask();
 
-					delete parsedXmlFileTransferContent;
 					return;
 				}
 
@@ -386,7 +383,6 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht
 				string xml_body = dumpFileTransferContentAsXmlString(
 				    parsedXmlFileTransferContent, contentKey, contentKeySize, contentAuthTag, contentAuthTagSize,
 				    escapeFileName(currentFileContentToTransfer->getFileNameUtf8()));
-				delete parsedXmlFileTransferContent;
 
 				currentFileTransferContent->setBodyFromUtf8(xml_body.c_str());
 				currentFileTransferContent = nullptr;
@@ -399,7 +395,6 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht
 			} else {
 				lWarning() << "Received empty response from server, file transfer failed";
 				message->getPrivate()->replaceContent(currentFileTransferContent, currentFileContentToTransfer);
-				delete currentFileTransferContent;
 				currentFileTransferContent = nullptr;
 
 				message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(),
@@ -411,7 +406,6 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht
 			lWarning() << "Received HTTP code response " << code
 			           << " for file transfer, probably meaning file is too large";
 			message->getPrivate()->replaceContent(currentFileTransferContent, currentFileContentToTransfer);
-			delete currentFileTransferContent;
 			currentFileTransferContent = nullptr;
 
 			message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(),
@@ -422,7 +416,6 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht
 			lWarning() << "Received HTTP code response " << code
 			           << " for file transfer, probably meaning that our credentials were rejected";
 			message->getPrivate()->replaceContent(currentFileTransferContent, currentFileContentToTransfer);
-			delete currentFileTransferContent;
 			currentFileTransferContent = nullptr;
 
 			message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(),
@@ -432,7 +425,6 @@ void FileTransferChatMessageModifier::processResponseFromPostFile(const belle_ht
 		} else {
 			lWarning() << "Unhandled HTTP code response " << code << " for file transfer";
 			message->getPrivate()->replaceContent(currentFileTransferContent, currentFileContentToTransfer);
-			delete currentFileTransferContent;
 			currentFileTransferContent = nullptr;
 
 			message->getPrivate()->setParticipantState(message->getChatRoom()->getMe()->getAddress(),
@@ -592,9 +584,9 @@ ChatMessageModifier::Result FileTransferChatMessageModifier::decode(const shared
                                                                     BCTBX_UNUSED(int &errorCode)) {
 	chatMessage = message;
 
-	Content internalContent = message->getInternalContent();
+	const auto &internalContent = message->getInternalContent();
 	if (internalContent.getContentType() == ContentType::FileTransfer) {
-		FileTransferContent *fileTransferContent = new FileTransferContent();
+		auto fileTransferContent = FileTransferContent::create<FileTransferContent>();
 		fileTransferContent->setContentType(internalContent.getContentType());
 		fileTransferContent->setBody(internalContent.getBody());
 		string xml_body = fileTransferContent->getBodyAsUtf8String();
@@ -603,9 +595,9 @@ ChatMessageModifier::Result FileTransferChatMessageModifier::decode(const shared
 		return ChatMessageModifier::Result::Done;
 	}
 
-	for (Content *content : message->getContents()) {
+	for (auto &content : message->getContents()) {
 		if (content->isFileTransfer()) {
-			FileTransferContent *fileTransferContent = static_cast<FileTransferContent *>(content);
+			auto fileTransferContent = static_pointer_cast<FileTransferContent>(content);
 			string xml_body = fileTransferContent->getBodyAsUtf8String();
 			parseFileTransferXmlIntoContent(xml_body.c_str(), fileTransferContent);
 		}
@@ -658,7 +650,7 @@ void FileTransferChatMessageModifier::onRecvBody(BCTBX_UNUSED(belle_sip_user_bod
 		if (currentFileContentToTransfer->getFilePath().empty()) {
 			LinphoneChatMessage *msg = L_GET_C_BACK_PTR(message);
 			LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
-			LinphoneContent *content = L_GET_C_BACK_PTR((Content *)currentFileContentToTransfer);
+			LinphoneContent *content = currentFileContentToTransfer->toC();
 			LinphoneBuffer *lb = linphone_buffer_new_from_data(buffer, size);
 			// Deprecated: use list of callbacks now
 			if (linphone_chat_message_cbs_get_file_transfer_recv(cbs)) {
@@ -683,7 +675,7 @@ static void _chat_message_on_recv_end(belle_sip_user_body_handler_t *bh, void *d
 	d->onRecvEnd(bh);
 }
 
-static void renameFileAfterAutoDownload(shared_ptr<Core> core, FileContent *fileContent) {
+static void renameFileAfterAutoDownload(shared_ptr<Core> core, shared_ptr<FileContent> fileContent) {
 	string file = fileContent->getFileNameSys();
 	size_t foundDot = file.find_last_of(".");
 	string fileName = file;
@@ -730,7 +722,7 @@ void FileTransferChatMessageModifier::onRecvEnd(BCTBX_UNUSED(belle_sip_user_body
 		if (currentFileContentToTransfer->getFilePath().empty()) {
 			LinphoneChatMessage *msg = L_GET_C_BACK_PTR(message);
 			LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
-			LinphoneContent *content = L_GET_C_BACK_PTR((Content *)currentFileContentToTransfer);
+			LinphoneContent *content = currentFileContentToTransfer->toC();
 			LinphoneBuffer *lb = linphone_buffer_new();
 			// Deprecated: use list of callbacks now
 			if (linphone_chat_message_cbs_get_file_transfer_recv(cbs)) {
@@ -745,13 +737,12 @@ void FileTransferChatMessageModifier::onRecvEnd(BCTBX_UNUSED(belle_sip_user_body
 
 		if (message->getState() != ChatMessage::State::FileTransferError) {
 			// Remove the FileTransferContent from the message and store the FileContent
-			FileContent *fileContent = currentFileContentToTransfer;
+			auto fileContent = currentFileContentToTransfer;
 
 			if (currentFileTransferContent != nullptr) {
 				lInfo() << "Found downloaded file transfer content [" << currentFileTransferContent
 				        << "], removing it to keep only the file content [" << fileContent << "]";
 				message->getPrivate()->replaceContent(currentFileTransferContent, fileContent);
-				delete currentFileTransferContent;
 				currentFileTransferContent = nullptr;
 			} else {
 				lWarning() << "Download seems successful but file transfer content not found, adding file content ["
@@ -781,8 +772,8 @@ static void _chat_process_response_headers_from_get_file(void *data, const belle
 	d->processResponseHeadersFromGetFile(event);
 }
 
-static FileContent *createFileTransferInformationFromHeaders(const belle_sip_message_t *m) {
-	FileContent *fileContent = new FileContent();
+static std::shared_ptr<FileContent> createFileTransferInformationFromHeaders(const belle_sip_message_t *m) {
+	auto fileContent = FileContent::create<FileContent>();
 
 	belle_sip_header_content_length_t *content_length_hdr =
 	    BELLE_SIP_HEADER_CONTENT_LENGTH(belle_sip_message_get_header(m, "Content-Length"));
@@ -832,7 +823,7 @@ void FileTransferChatMessageModifier::processResponseHeadersFromGetFile(const be
 			lInfo() << "Extracted content length " << currentFileContentToTransfer->getFileSize() << " from header";
 		} else {
 			lWarning() << "No file transfer information for message [" << message << "]: creating...";
-			FileContent *content = createFileTransferInformationFromHeaders(response);
+			auto content = createFileTransferInformationFromHeaders(response);
 			message->addContent(content);
 		}
 
@@ -932,8 +923,8 @@ void FileTransferChatMessageModifier::processResponseFromGetFile(const belle_htt
 	}
 }
 
-static void createFileContentFromFileTransferContent(FileTransferContent *fileTransferContent) {
-	FileContent *fileContent = new FileContent();
+static void createFileContentFromFileTransferContent(std::shared_ptr<FileTransferContent> &fileTransferContent) {
+	auto fileContent = FileContent::create<FileContent>();
 
 	fileContent->setFileName(fileTransferContent->getFileName());
 	fileContent->setFileSize(fileTransferContent->getFileSize());
@@ -946,7 +937,7 @@ static void createFileContentFromFileTransferContent(FileTransferContent *fileTr
 }
 
 bool FileTransferChatMessageModifier::downloadFile(const shared_ptr<ChatMessage> &message,
-                                                   FileTransferContent *fileTransferContent) {
+                                                   std::shared_ptr<FileTransferContent> &fileTransferContent) {
 	chatMessage = message;
 
 	if (httpRequest) {
@@ -958,8 +949,11 @@ bool FileTransferChatMessageModifier::downloadFile(const shared_ptr<ChatMessage>
 		lError() << "Content type is not a FileTransfer.";
 		return false;
 	}
-	FileContent *fileContent = fileTransferContent->getFileContent();
-	if (fileContent) delete fileContent;
+	auto fileContent = fileTransferContent->getFileContent();
+	if (fileContent) {
+		fileTransferContent->setFileContent(nullptr);
+		fileContent.reset();
+	}
 	createFileContentFromFileTransferContent(fileTransferContent);
 	fileContent = fileTransferContent->getFileContent();
 	currentFileContentToTransfer = fileContent;
@@ -1075,7 +1069,7 @@ string FileTransferChatMessageModifier::createFakeFileTransferFromUrl(const stri
 }
 
 string FileTransferChatMessageModifier::dumpFileTransferContentAsXmlString(
-    const FileTransferContent *parsedXmlFileTransferContent,
+    const std::shared_ptr<FileTransferContent> &parsedXmlFileTransferContent,
     const unsigned char *contentKey,
     size_t contentKeySize,
     const unsigned char *contentAuthTag,
@@ -1156,12 +1150,12 @@ static std::string cleanDownloadFileName(std::string fileName) {
 }
 #endif
 
-void FileTransferChatMessageModifier::parseFileTransferXmlIntoContent(const char *xml,
-                                                                      FileTransferContent *fileTransferContent) const {
+void FileTransferChatMessageModifier::parseFileTransferXmlIntoContent(
+    const char *xml, std::shared_ptr<FileTransferContent> &fileTransferContent) const {
 #ifdef HAVE_XML2
 	xmlDocPtr xmlMessageBody;
 	xmlNodePtr cur;
-	/* parse the msg body to get all informations from it */
+	/* parse the msg body to get all information from it */
 	xmlMessageBody = xmlParseDoc((const xmlChar *)xml);
 
 	cur = xmlDocGetRootElement(xmlMessageBody);
@@ -1201,13 +1195,11 @@ void FileTransferChatMessageModifier::parseFileTransferXmlIntoContent(const char
 							xmlFree(fileDuration);
 						}
 						if (!xmlStrcmp(cur->name, (const xmlChar *)"data")) {
-							xmlChar *fileUrl = nullptr;
-							fileUrl = xmlGetProp(cur, (const xmlChar *)"url");
+							xmlChar *fileUrl = xmlGetProp(cur, (const xmlChar *)"url");
 							fileTransferContent->setFileUrl(fileUrl ? (const char *)fileUrl : "");
 							xmlFree(fileUrl);
 
-							xmlChar *validUntil = nullptr;
-							validUntil = xmlGetProp(cur, (const xmlChar *)"until");
+							xmlChar *validUntil = xmlGetProp(cur, (const xmlChar *)"until");
 							if (validUntil !=
 							    NULL) { // Will be null when fake XML is made for external body URL message
 								fileTransferContent->setProperty("validUntil", std::string((char *)validUntil));
diff --git a/src/chat/modifier/file-transfer-chat-message-modifier.h b/src/chat/modifier/file-transfer-chat-message-modifier.h
index 208f89edda21266b5da30149d9da09e3f9dfc134..c2f0c0f4e41e7a7f6b1b601d86fd3cf363f1145a 100644
--- a/src/chat/modifier/file-transfer-chat-message-modifier.h
+++ b/src/chat/modifier/file-transfer-chat-message-modifier.h
@@ -63,19 +63,22 @@ public:
 	void processIoErrorDownload(const belle_sip_io_error_event_t *event);
 	void processResponseFromGetFile(const belle_http_response_event_t *event);
 
-	bool downloadFile(const std::shared_ptr<ChatMessage> &message, FileTransferContent *fileTransferContent);
+	bool downloadFile(const std::shared_ptr<ChatMessage> &message,
+	                  std::shared_ptr<FileTransferContent> &fileTransferContent);
 	void cancelFileTransfer();
 	bool isFileTransferInProgressAndValid() const;
 	std::string createFakeFileTransferFromUrl(const std::string &url);
 	void fileUploadEndBackgroundTask();
 
-	void parseFileTransferXmlIntoContent(const char *xml, FileTransferContent *fileTransferContent) const;
-	std::string dumpFileTransferContentAsXmlString(const FileTransferContent *parsedXmlFileTransferContent,
-	                                               const unsigned char *contentKey,
-	                                               size_t contentKeySize,
-	                                               const unsigned char *contentAuthTag,
-	                                               size_t contentAuthTagSize,
-	                                               const std::string &realFileName) const;
+	void parseFileTransferXmlIntoContent(const char *xml,
+	                                     std::shared_ptr<FileTransferContent> &fileTransferContent) const;
+	std::string
+	dumpFileTransferContentAsXmlString(const std::shared_ptr<FileTransferContent> &parsedXmlFileTransferContent,
+	                                   const unsigned char *contentKey,
+	                                   size_t contentKeySize,
+	                                   const unsigned char *contentAuthTag,
+	                                   size_t contentAuthTagSize,
+	                                   const std::string &realFileName) const;
 
 private:
 	// Body handler is optional, but if set this method takes owneship of it, even in error cases.
@@ -95,8 +98,8 @@ private:
 	std::string unEscapeFileName(const std::string &fileName) const;
 
 	std::weak_ptr<ChatMessage> chatMessage;
-	FileContent *currentFileContentToTransfer = nullptr;
-	FileTransferContent *currentFileTransferContent = nullptr;
+	std::shared_ptr<FileContent> currentFileContentToTransfer = nullptr;
+	std::shared_ptr<FileTransferContent> currentFileTransferContent = nullptr;
 
 	belle_http_request_t *httpRequest = nullptr;
 	belle_http_request_listener_t *httpListener = nullptr;
diff --git a/src/chat/modifier/multipart-chat-message-modifier.cpp b/src/chat/modifier/multipart-chat-message-modifier.cpp
index b0adc284633970d728aa71d8958a577778e4fec9..2536c34cd3287da0649ab8a87bd584bb4b109b3f 100644
--- a/src/chat/modifier/multipart-chat-message-modifier.cpp
+++ b/src/chat/modifier/multipart-chat-message-modifier.cpp
@@ -41,7 +41,7 @@ ChatMessageModifier::Result MultipartChatMessageModifier::encode(const shared_pt
                                                                  BCTBX_UNUSED(int &errorCode)) {
 	if (message->getContents().size() <= 1) return ChatMessageModifier::Result::Skipped;
 
-	Content content = ContentManager::contentListToMultipart(message->getContents());
+	auto content = ContentManager::contentListToMultipart(message->getContents());
 	message->setInternalContent(content);
 
 	return ChatMessageModifier::Result::Done;
@@ -50,10 +50,10 @@ ChatMessageModifier::Result MultipartChatMessageModifier::encode(const shared_pt
 ChatMessageModifier::Result MultipartChatMessageModifier::decode(const shared_ptr<ChatMessage> &message,
                                                                  BCTBX_UNUSED(int &errorCode)) {
 	if (message->getInternalContent().getContentType().isMultipart()) {
-		for (Content &c : ContentManager::multipartToContentList(message->getInternalContent())) {
-			Content *content;
+		for (auto &c : ContentManager::multipartToContentList(message->getInternalContent())) {
+			std::shared_ptr<Content> content;
 			if (c.getContentType() == ContentType::FileTransfer) {
-				content = new FileTransferContent();
+				content = FileTransferContent::create<FileTransferContent>();
 				content->setContentType(c.getContentType());
 				content->setContentDisposition(c.getContentDisposition());
 				content->setContentEncoding(c.getContentEncoding());
@@ -62,7 +62,7 @@ ChatMessageModifier::Result MultipartChatMessageModifier::decode(const shared_pt
 				}
 				content->setBody(c.getBody());
 			} else {
-				content = new Content(c);
+				content = Content::create(c);
 			}
 			message->addContent(content);
 		}
diff --git a/src/conference/conference-scheduler.cpp b/src/conference/conference-scheduler.cpp
index 71df127fb5e8cdc3bf4a589c8fbc339e3a727145..78cbff3109b232367d22f4ddf95c0f91d96037a5 100644
--- a/src/conference/conference-scheduler.cpp
+++ b/src/conference/conference-scheduler.cpp
@@ -369,12 +369,12 @@ void ConferenceScheduler::onCallSessionSetTerminated(const shared_ptr<CallSessio
 			addressesList.unique([](const auto &addr1, const auto &addr2) { return addr1->weakEqual(*addr2); });
 
 			if (!addressesList.empty()) {
-				Content content;
-				content.setBodyFromUtf8(Utils::getResourceLists(addressesList));
-				content.setContentType(ContentType::ResourceLists);
-				content.setContentDisposition(ContentDisposition::RecipientList);
+				auto content = Content::create();
+				content->setBodyFromUtf8(Utils::getResourceLists(addressesList));
+				content->setContentType(ContentType::ResourceLists);
+				content->setContentDisposition(ContentDisposition::RecipientList);
 				if (linphone_core_content_encoding_supported(getCore()->getCCore(), "deflate")) {
-					content.setContentEncoding("deflate");
+					content->setContentEncoding("deflate");
 				}
 
 				L_GET_CPP_PTR_FROM_C_OBJECT(new_params)->addCustomContent(content);
@@ -435,7 +435,7 @@ shared_ptr<ChatMessage> ConferenceScheduler::createInvitationChatMessage(shared_
 		message = chatRoom->createChatMessageFromUtf8(mConferenceInfo->toIcsString(cancel, sequence));
 		message->getPrivate()->setContentType(ContentType::Icalendar);
 	} else {
-		FileContent *content = new FileContent(); // content will be deleted by ChatMessage
+		auto content = FileContent::create<FileContent>(); // content will be deleted by ChatMessage
 		content->setContentType(ContentType::Icalendar);
 		content->setFileName("conference.ics");
 		content->setBodyFromUtf8(mConferenceInfo->toIcsString(cancel, sequence));
diff --git a/src/conference/handlers/local-conference-event-handler.cpp b/src/conference/handlers/local-conference-event-handler.cpp
index ee1b5aa102a3b536b942fe042369f825c9fb232b..80a4d80a7af9743489fdb919aea7f0eed4cc0154 100644
--- a/src/conference/handlers/local-conference-event-handler.cpp
+++ b/src/conference/handlers/local-conference-event-handler.cpp
@@ -60,11 +60,12 @@ LocalConferenceEventHandler::LocalConferenceEventHandler(Conference *conference,
 
 // -----------------------------------------------------------------------------
 
-void LocalConferenceEventHandler::notifyFullState(const Content &notify, const shared_ptr<ParticipantDevice> &device) {
+void LocalConferenceEventHandler::notifyFullState(const std::shared_ptr<Content> &notify,
+                                                  const shared_ptr<ParticipantDevice> &device) {
 	notifyParticipantDevice(notify, device);
 }
 
-void LocalConferenceEventHandler::notifyAllExceptDevice(const Content &notify,
+void LocalConferenceEventHandler::notifyAllExceptDevice(const std::shared_ptr<Content> &notify,
                                                         const shared_ptr<ParticipantDevice> &exceptDevice) {
 	for (const auto &participant : conf->getParticipants()) {
 		for (const auto &device : participant->getDevices()) {
@@ -76,7 +77,7 @@ void LocalConferenceEventHandler::notifyAllExceptDevice(const Content &notify,
 	}
 }
 
-void LocalConferenceEventHandler::notifyAllExcept(const Content &notify,
+void LocalConferenceEventHandler::notifyAllExcept(const std::shared_ptr<Content> &notify,
                                                   const shared_ptr<Participant> &exceptParticipant) {
 	for (const auto &participant : conf->getParticipants()) {
 		if (participant != exceptParticipant) {
@@ -85,13 +86,13 @@ void LocalConferenceEventHandler::notifyAllExcept(const Content &notify,
 	}
 }
 
-void LocalConferenceEventHandler::notifyAll(const Content &notify) {
+void LocalConferenceEventHandler::notifyAll(const std::shared_ptr<Content> &notify) {
 	for (const auto &participant : conf->getParticipants()) {
 		notifyParticipant(notify, participant);
 	}
 }
 
-Content LocalConferenceEventHandler::createNotifyFullState(const shared_ptr<EventSubscribe> &ev) {
+std::shared_ptr<Content> LocalConferenceEventHandler::createNotifyFullState(const shared_ptr<EventSubscribe> &ev) {
 	vector<string> acceptedContents = vector<string>();
 	if (ev) {
 		const auto message = (belle_sip_message_t *)ev->getOp()->getRecvCustomHeaders();
@@ -399,12 +400,12 @@ void LocalConferenceEventHandler::addMediaCapabilities(const std::shared_ptr<Par
 	endpoint.getMedia().push_back(text);
 }
 
-Content LocalConferenceEventHandler::createNotifyMultipart(int notifyId) {
+std::shared_ptr<Content> LocalConferenceEventHandler::createNotifyMultipart(int notifyId) {
 
 	list<shared_ptr<EventLog>> events = conf->getCore()->getPrivate()->mainDb->getConferenceNotifiedEvents(
 	    ConferenceId(conf->getConferenceAddress(), conf->getConferenceAddress()), static_cast<unsigned int>(notifyId));
 
-	list<Content> contents;
+	list<shared_ptr<Content>> contents;
 	for (const auto &eventLog : events) {
 		string body;
 		shared_ptr<ConferenceNotifiedEvent> notifiedEvent = static_pointer_cast<ConferenceNotifiedEvent>(eventLog);
@@ -493,15 +494,12 @@ Content LocalConferenceEventHandler::createNotifyMultipart(int notifyId) {
 		contents.emplace_back(makeContent(body));
 	}
 
-	if (contents.empty()) return Content();
+	if (contents.empty()) return Content::create();
 
-	list<Content *> contentPtrs;
-	for (auto &content : contents)
-		contentPtrs.push_back(&content);
-	Content multipart = ContentManager::contentListToMultipart(contentPtrs);
+	Content multipart = ContentManager::contentListToMultipart(contents);
 	if (linphone_core_content_encoding_supported(conf->getCore()->getCCore(), "deflate"))
 		multipart.setContentEncoding("deflate");
-	return multipart;
+	return Content::create(multipart);
 }
 
 string LocalConferenceEventHandler::createNotifyParticipantAdded(const std::shared_ptr<Address> &pAddress) {
@@ -949,7 +947,8 @@ string LocalConferenceEventHandler::createNotifyAvailableMediaChanged(
 	return createNotify(confInfo);
 }
 
-void LocalConferenceEventHandler::notifyParticipant(const Content &notify, const shared_ptr<Participant> &participant) {
+void LocalConferenceEventHandler::notifyParticipant(const std::shared_ptr<Content> &notify,
+                                                    const shared_ptr<Participant> &participant) {
 	for (const auto &device : participant->getDevices()) {
 		/* Only notify to device that are present in the conference. */
 		switch (device->getState()) {
@@ -969,7 +968,7 @@ void LocalConferenceEventHandler::notifyParticipant(const Content &notify, const
 	}
 }
 
-void LocalConferenceEventHandler::notifyParticipantDevice(const Content &notify,
+void LocalConferenceEventHandler::notifyParticipantDevice(const shared_ptr<Content> &notify,
                                                           const shared_ptr<ParticipantDevice> &device) {
 	if (!device->isSubscribedToConferenceEventPackage()) return;
 
@@ -979,7 +978,7 @@ void LocalConferenceEventHandler::notifyParticipantDevice(const Content &notify,
 	cbs->notifyResponseCb = notifyResponseCb;
 	ev->addCallbacks(cbs);
 
-	LinphoneContent *cContent = notify.isEmpty() ? nullptr : L_GET_C_BACK_PTR(&notify);
+	LinphoneContent *cContent = notify->isEmpty() ? nullptr : notify->toC();
 	ev->notify(cContent);
 	linphone_core_notify_notify_sent(conf->getCore()->getCCore(), ev->toC(), cContent);
 }
@@ -1064,7 +1063,7 @@ LinphoneStatus LocalConferenceEventHandler::subscribeReceived(const shared_ptr<E
 			           << lastNotify << "] - sending a notify full state in an attempt to recover from this situation";
 			notifyFullState(createNotifyFullState(ev), device);
 		} else {
-			notifyParticipantDevice(Content(), device);
+			notifyParticipantDevice(Content::create(), device);
 		}
 	}
 
@@ -1088,7 +1087,8 @@ void LocalConferenceEventHandler::subscriptionStateChanged(const shared_ptr<Even
 	}
 }
 
-Content LocalConferenceEventHandler::getNotifyForId(int notifyId, const shared_ptr<EventSubscribe> &ev) {
+std::shared_ptr<Content> LocalConferenceEventHandler::getNotifyForId(int notifyId,
+                                                                     const shared_ptr<EventSubscribe> &ev) {
 	unsigned int lastNotify = conf->getLastNotify();
 
 	const int fullStateTrigger = linphone_config_get_int(linphone_core_get_config(conf->getCore()->getCCore()), "misc",
@@ -1097,25 +1097,23 @@ Content LocalConferenceEventHandler::getNotifyForId(int notifyId, const shared_p
 	    (notifyId > static_cast<int>(lastNotify)) || (static_cast<int>(lastNotify) - notifyId) > fullStateTrigger;
 	if ((notifyId == 0) || forceFullState) {
 		auto content = createNotifyFullState(ev);
-		list<Content *> contentPtrs;
-		contentPtrs.push_back(&content);
-		auto multipart = ContentManager::contentListToMultipart(contentPtrs);
-		return multipart;
+		auto multipart = ContentManager::contentListToMultipart({content});
+		return Content::create(multipart);
 	} else if (notifyId < static_cast<int>(lastNotify)) {
 		return createNotifyMultipart(notifyId);
 	}
 
-	return Content();
+	return Content::create();
 }
 
-Content LocalConferenceEventHandler::makeContent(const std::string &xml) {
-	Content content;
-	content.setContentType(ContentType::ConferenceInfo);
+std::shared_ptr<Content> LocalConferenceEventHandler::makeContent(const std::string &xml) {
+	auto content = Content::create();
+	content->setContentType(ContentType::ConferenceInfo);
 	if (linphone_core_content_encoding_supported(conf->getCore()->getCCore(), "deflate")) {
-		content.setContentEncoding("deflate");
+		content->setContentEncoding("deflate");
 	}
 	if (!xml.empty()) {
-		content.setBodyFromUtf8(xml);
+		content->setBodyFromUtf8(xml);
 	}
 	return content;
 }
diff --git a/src/conference/handlers/local-conference-event-handler.h b/src/conference/handlers/local-conference-event-handler.h
index 33eeef9698df27be19292e1f39402c86c7fb443d..ab53f66df1b92c2ef83db44f3df2eb62c467b525 100644
--- a/src/conference/handlers/local-conference-event-handler.h
+++ b/src/conference/handlers/local-conference-event-handler.h
@@ -65,15 +65,16 @@ public:
 	LinphoneStatus subscribeReceived(const std::shared_ptr<EventSubscribe> &ev);
 	void subscriptionStateChanged(const std::shared_ptr<EventSubscribe> &ev, LinphoneSubscriptionState state);
 
-	Content getNotifyForId(int notifyId, const std::shared_ptr<EventSubscribe> &ev);
+	std::shared_ptr<Content> getNotifyForId(int notifyId, const std::shared_ptr<EventSubscribe> &ev);
 
 	// protected:
-	void notifyFullState(const Content &notify, const std::shared_ptr<ParticipantDevice> &device);
-	void notifyAllExcept(const Content &notify, const std::shared_ptr<Participant> &exceptParticipant);
-	void notifyAllExceptDevice(const Content &notify, const std::shared_ptr<ParticipantDevice> &exceptDevice);
-	void notifyAll(const Content &notify);
-	Content createNotifyFullState(const std::shared_ptr<EventSubscribe> &ev);
-	Content createNotifyMultipart(int notifyId);
+	void notifyFullState(const std::shared_ptr<Content> &notify, const std::shared_ptr<ParticipantDevice> &device);
+	void notifyAllExcept(const std::shared_ptr<Content> &notify, const std::shared_ptr<Participant> &exceptParticipant);
+	void notifyAllExceptDevice(const std::shared_ptr<Content> &notify,
+	                           const std::shared_ptr<ParticipantDevice> &exceptDevice);
+	void notifyAll(const std::shared_ptr<Content> &notify);
+	std::shared_ptr<Content> createNotifyFullState(const std::shared_ptr<EventSubscribe> &ev);
+	std::shared_ptr<Content> createNotifyMultipart(int notifyId);
 
 	// Conference
 	std::string createNotifyAvailableMediaChanged(const std::map<ConferenceMediaCapabilities, bool> mediaCapabilities);
@@ -216,9 +217,10 @@ private:
 	std::string createNotifySubjectChanged(const std::string &subject);
 	std::string createNotifyEphemeralLifetime(const long &lifetime);
 	std::string createNotifyEphemeralMode(const EventLog::Type &type);
-	Content makeContent(const std::string &xml);
-	void notifyParticipant(const Content &notify, const std::shared_ptr<Participant> &participant);
-	void notifyParticipantDevice(const Content &notify, const std::shared_ptr<ParticipantDevice> &device);
+	std::shared_ptr<Content> makeContent(const std::string &xml);
+	void notifyParticipant(const std::shared_ptr<Content> &notify, const std::shared_ptr<Participant> &participant);
+	void notifyParticipantDevice(const std::shared_ptr<Content> &notify,
+	                             const std::shared_ptr<ParticipantDevice> &device);
 
 	std::shared_ptr<Participant> getConferenceParticipant(const std::shared_ptr<Address> &address) const;
 
diff --git a/src/conference/handlers/local-conference-list-event-handler.cpp b/src/conference/handlers/local-conference-list-event-handler.cpp
index 9c2199e2df341d52e3eb39008253343280999b07..6da1aef8c84138612476535b78c81add26fcf018 100644
--- a/src/conference/handlers/local-conference-list-event-handler.cpp
+++ b/src/conference/handlers/local-conference-list-event-handler.cpp
@@ -144,15 +144,15 @@ void LocalConferenceListEventHandler::subscribeReceived(const std::shared_ptr<Ev
 			int notifyId = (notifyIdStr.empty() || device->getState() == ParticipantDevice::State::Joining)
 			                   ? 0
 			                   : Utils::stoi(notifyIdStr);
-			Content content = handler->getNotifyForId(notifyId, device->getConferenceSubscribeEvent());
-			if (content.isEmpty()) continue;
+			auto content = handler->getNotifyForId(notifyId, device->getConferenceSubscribeEvent());
+			if (content->isEmpty()) continue;
 
 			noContent = false;
 			char token[17];
 			belle_sip_random_token(token, sizeof(token));
-			content.addHeader("Content-Id", token);
-			content.addHeader("Content-Length", Utils::toString(content.getSize()));
-			contents.push_back(std::move(content));
+			content->addHeader("Content-Id", token);
+			content->addHeader("Content-Length", Utils::toString(content->getSize()));
+			contents.push_back(std::move(*content));
 
 			// Add entry into the Rlmi content of the notify body
 			Xsd::Rlmi::Resource resource(addr->asStringUriOnly());
@@ -185,7 +185,7 @@ void LocalConferenceListEventHandler::subscribeReceived(const std::shared_ptr<Ev
 	Content multipart = ContentManager::contentListToMultipart(contentsAsPtr);
 	if (linphone_core_content_encoding_supported(getCore()->getCCore(), "deflate"))
 		multipart.setContentEncoding("deflate");
-	LinphoneContent *cContent = L_GET_C_BACK_PTR(&multipart);
+	LinphoneContent *cContent = Content::createCObject(multipart);
 	shared_ptr<EventCbs> cbs = EventCbs::create();
 	cbs->setUserData(this);
 	cbs->notifyResponseCb = notifyResponseCb;
diff --git a/src/conference/handlers/remote-conference-event-handler.cpp b/src/conference/handlers/remote-conference-event-handler.cpp
index ca67637038076c3f694645887888e19816efa49a..ebe2eeb09caeab17fd2665e0432e1bd863a9d36c 100644
--- a/src/conference/handlers/remote-conference-event-handler.cpp
+++ b/src/conference/handlers/remote-conference-event-handler.cpp
@@ -805,8 +805,8 @@ void RemoteConferenceEventHandler::multipartNotifyReceived(std::shared_ptr<Event
 
 void RemoteConferenceEventHandler::multipartNotifyReceived(const Content &content) {
 	lInfo() << "multipart NOTIFY received for conference: " << getConferenceId();
-	for (const auto &content : ContentManager::multipartToContentList(content)) {
-		notifyReceived(content);
+	for (const auto &c : ContentManager::multipartToContentList(content)) {
+		notifyReceived(c);
 	}
 }
 
diff --git a/src/conference/handlers/remote-conference-list-event-handler.cpp b/src/conference/handlers/remote-conference-list-event-handler.cpp
index e2d48333dfd42cccfa3b2940520fd4ccf3e75217..8e3d361c833e8d7e4b8d1b8f633d90532857dc1b 100644
--- a/src/conference/handlers/remote-conference-list-event-handler.cpp
+++ b/src/conference/handlers/remote-conference-list-event-handler.cpp
@@ -82,8 +82,8 @@ void RemoteConferenceListEventHandler::subscribe(const shared_ptr<Account> &acco
 
 	if (handlers.empty()) return;
 
-	Content content;
-	content.setContentType(ContentType::ResourceLists);
+	auto content = Content::create();
+	content->setContentType(ContentType::ResourceLists);
 
 	Xsd::ResourceLists::ResourceLists rl = Xsd::ResourceLists::ResourceLists();
 	Xsd::ResourceLists::ListType l = Xsd::ResourceLists::ListType();
@@ -117,7 +117,7 @@ void RemoteConferenceListEventHandler::subscribe(const shared_ptr<Account> &acco
 	Xsd::XmlSchema::NamespaceInfomap map;
 	stringstream xmlBody;
 	serializeResourceLists(xmlBody, rl, map);
-	content.setBodyFromUtf8(xmlBody.str());
+	content->setBodyFromUtf8(xmlBody.str());
 
 	if (account->getState() != LinphoneRegistrationOk) return;
 
@@ -138,11 +138,11 @@ void RemoteConferenceListEventHandler::subscribe(const shared_ptr<Account> &acco
 	evSub->addCustomHeader("Content-Disposition", "recipient-list");
 	LinphoneCore *lc = getCore()->getCCore();
 	if (linphone_core_content_encoding_supported(lc, "deflate")) {
-		content.setContentEncoding("deflate");
+		content->setContentEncoding("deflate");
 		evSub->addCustomHeader("Accept-Encoding", "deflate");
 	}
 	evSub->setProperty("event-handler-private", this);
-	LinphoneContent *cContent = L_GET_C_BACK_PTR(&content);
+	LinphoneContent *cContent = content->toC();
 	evSub->send(cContent);
 
 	levs.push_back(evSub);
@@ -176,7 +176,8 @@ bool RemoteConferenceListEventHandler::getInitialSubscriptionUnderWayFlag(const
 	return (handler) ? handler->getInitialSubscriptionUnderWayFlag() : false;
 }
 
-void RemoteConferenceListEventHandler::notifyReceived(std::shared_ptr<Event> notifyLev, const Content *notifyContent) {
+void RemoteConferenceListEventHandler::notifyReceived(std::shared_ptr<Event> notifyLev,
+                                                      const std::shared_ptr<const Content> &notifyContent) {
 	const auto &from = notifyLev->getFrom();
 	auto it = std::find_if(levs.begin(), levs.end(),
 	                       [&from](const auto &lev) { return (*Address::create(lev->getOp()->getFrom()) == *from); });
diff --git a/src/conference/handlers/remote-conference-list-event-handler.h b/src/conference/handlers/remote-conference-list-event-handler.h
index e455f4c4bffc051e94ba52ff3ddeb1dc8a8569dc..54aaf24897e39291bbb15d194a7ab6bf36939e93 100644
--- a/src/conference/handlers/remote-conference-list-event-handler.h
+++ b/src/conference/handlers/remote-conference-list-event-handler.h
@@ -53,7 +53,7 @@ public:
 	void unsubscribe() override;
 	void unsubscribe(const std::shared_ptr<Account> &account);
 	void invalidateSubscription() override;
-	void notifyReceived(std::shared_ptr<Event> notifyLev, const Content *notifyContent);
+	void notifyReceived(std::shared_ptr<Event> notifyLev, const std::shared_ptr<const Content> &notifyContent);
 	void addHandler(RemoteConferenceEventHandler *handler);
 	void removeHandler(RemoteConferenceEventHandler *handler);
 	void clearHandlers();
diff --git a/src/conference/params/call-session-params-p.h b/src/conference/params/call-session-params-p.h
index 1ce2d63c34383b35d11782d5d32d7a311c4e526c..0e8d5a67e81865499de5e1d30536a1c9637d08b7 100644
--- a/src/conference/params/call-session-params-p.h
+++ b/src/conference/params/call-session-params-p.h
@@ -126,7 +126,7 @@ private:
 	std::unordered_map<std::string, std::string> customContactParameters;
 	std::shared_ptr<CallSession>
 	    referer; /* In case call creation is consecutive to an incoming transfer, this points to the original call */
-	std::list<Content> customContents;
+	std::list<std::shared_ptr<Content>> customContents;
 	std::list<LinphoneSrtpSuite> srtpSuites{};
 
 	time_t startTime = (time_t)-1;
diff --git a/src/conference/params/call-session-params.cpp b/src/conference/params/call-session-params.cpp
index 7f0cee2d03f95c668543a49d8b429581c2e4a4f7..da73b7760635fb9cbbc9a2f4d3cc2327cc0ae773 100644
--- a/src/conference/params/call-session-params.cpp
+++ b/src/conference/params/call-session-params.cpp
@@ -301,12 +301,12 @@ std::string CallSessionParams::getCustomContactParameter(const std::string &para
 
 // -----------------------------------------------------------------------------
 
-void CallSessionParams::addCustomContent(const Content &content) {
+void CallSessionParams::addCustomContent(const std::shared_ptr<Content> &content) {
 	L_D();
 	d->customContents.push_back(std::move(content));
 }
 
-const list<Content> &CallSessionParams::getCustomContents() const {
+const list<std::shared_ptr<Content>> &CallSessionParams::getCustomContents() const {
 	L_D();
 	return d->customContents;
 }
diff --git a/src/conference/params/call-session-params.h b/src/conference/params/call-session-params.h
index e186985186793c6aa792fb7173b27a6834722273..7808b0293e51a1fc0d72b4a1a6fe93be1d5506b5 100644
--- a/src/conference/params/call-session-params.h
+++ b/src/conference/params/call-session-params.h
@@ -85,8 +85,8 @@ public:
 	void clearCustomContactParameters();
 	std::string getCustomContactParameter(const std::string &paramName) const;
 
-	void addCustomContent(const Content &content);
-	const std::list<Content> &getCustomContents() const;
+	void addCustomContent(const std::shared_ptr<Content> &content);
+	const std::list<std::shared_ptr<Content>> &getCustomContents() const;
 
 	std::shared_ptr<Account> getAccount() const;
 	void setAccount(std::shared_ptr<Account> account);
diff --git a/src/conference/session/call-session.cpp b/src/conference/session/call-session.cpp
index 4f4273b32809c6604e16cf62ef4a7447f451ea91..7221d34eb537d251f398bf8094cd4306b8f2ed91 100644
--- a/src/conference/session/call-session.cpp
+++ b/src/conference/session/call-session.cpp
@@ -1108,13 +1108,13 @@ void CallSessionPrivate::repairByInviteWithReplaces() {
 	string fromTag = op->getLocalTag();
 	string toTag = op->getRemoteTag();
 	// Restore INVITE body if any, for example while creating a chat room
-	Content content = Content(op->getLocalBody());
+	auto content = Content::create(op->getLocalBody());
 
 	op->killDialog();
 	createOp();
 	op->setReplaces(callId.c_str(), fromTag,
 	                toTag.empty() ? "0" : toTag); // empty tag is set to 0 as defined by rfc3891
-	q->startInvite(nullptr, subject, &content);   // Don't forget to set subject from call-session (and not from OP)
+	q->startInvite(nullptr, subject, content);    // Don't forget to set subject from call-session (and not from OP)
 }
 
 void CallSessionPrivate::refreshContactAddress() {
@@ -1474,7 +1474,8 @@ bool CallSession::hasTransferPending() {
 void CallSession::initiateIncoming() {
 }
 
-bool CallSession::initiateOutgoing(BCTBX_UNUSED(const string &subject), BCTBX_UNUSED(const Content *content)) {
+bool CallSession::initiateOutgoing(BCTBX_UNUSED(const string &subject),
+                                   BCTBX_UNUSED(const std::shared_ptr<const Content> content)) {
 	L_D();
 	bool defer = false;
 	d->setState(CallSession::State::OutgoingInit, "Starting outgoing call");
@@ -1573,7 +1574,7 @@ void CallSession::startPushIncomingNotification() {
 
 int CallSession::startInvite(const std::shared_ptr<Address> &destination,
                              const string &subject,
-                             const Content *content) {
+                             const std::shared_ptr<const Content> content) {
 	L_D();
 	d->subject = subject;
 	/* Try to be best-effort in giving real local or routable contact address */
@@ -1590,8 +1591,8 @@ int CallSession::startInvite(const std::shared_ptr<Address> &destination,
 	}
 
 	// If a custom Content has been set in the call params, create a multipart body for the INVITE
-	for (auto &content : d->params->getCustomContents()) {
-		d->op->addAdditionalLocalBody(content);
+	for (auto &c : d->params->getCustomContents()) {
+		d->op->addAdditionalLocalBody(*c);
 	}
 
 	int result = d->op->call(d->log->getFromAddress()->toString().c_str(), destinationStr, subject);
@@ -1673,7 +1674,7 @@ LinphoneStatus CallSession::transfer(const string &dest) {
 LinphoneStatus CallSession::update(const CallSessionParams *csp,
                                    const UpdateMethod method,
                                    const string &subject,
-                                   const Content *content) {
+                                   const std::shared_ptr<Content> content) {
 	L_D();
 	CallSession::State nextState;
 	CallSession::State initialState = d->state;
@@ -1834,7 +1835,7 @@ const CallSessionParams *CallSession::getRemoteParams() {
 
 		const list<Content> additionnalContents = d->op->getAdditionalRemoteBodies();
 		for (auto &content : additionnalContents)
-			d->remoteParams->addCustomContent(content);
+			d->remoteParams->addCustomContent(Content::create(content));
 
 		return d->remoteParams;
 	}
diff --git a/src/conference/session/call-session.h b/src/conference/session/call-session.h
index 7c4127cf415613888958c1be339b67683802caa5..89ecf43372789f4e36cf1be6434588744766fe89 100644
--- a/src/conference/session/call-session.h
+++ b/src/conference/session/call-session.h
@@ -131,7 +131,8 @@ public:
 	bool isCapabilityNegotiationEnabled() const;
 	const std::list<LinphoneMediaEncryption> getSupportedEncryptions() const;
 	virtual void initiateIncoming();
-	virtual bool initiateOutgoing(const std::string &subject = "", const Content *content = nullptr);
+	virtual bool initiateOutgoing(const std::string &subject = "",
+	                              const std::shared_ptr<const Content> content = nullptr);
 	virtual void iterate(time_t currentRealTime, bool oneSecondElapsed);
 	LinphoneStatus redirect(const std::string &redirectUri);
 	LinphoneStatus redirect(const Address &redirectAddr);
@@ -140,7 +141,7 @@ public:
 	void startPushIncomingNotification();
 	virtual int startInvite(const std::shared_ptr<Address> &destination,
 	                        const std::string &subject = "",
-	                        const Content *content = nullptr);
+	                        const std::shared_ptr<const Content> content = nullptr);
 	LinphoneStatus terminate(const LinphoneErrorInfo *ei = nullptr);
 	LinphoneStatus transfer(const std::shared_ptr<CallSession> &dest);
 	LinphoneStatus transfer(const std::shared_ptr<Address> &dest);
@@ -148,7 +149,7 @@ public:
 	LinphoneStatus update(const CallSessionParams *csp,
 	                      const UpdateMethod method = UpdateMethod::Default,
 	                      const std::string &subject = "",
-	                      const Content *content = nullptr);
+	                      const std::shared_ptr<Content> content = nullptr);
 
 	CallSessionParams *getCurrentParams() const;
 	LinphoneCallDir getDirection() const;
diff --git a/src/conference/session/media-session.cpp b/src/conference/session/media-session.cpp
index 9346f525b5b1dca40097ff7ef2cbcae4ea6f813d..4341b8811f6a3021879dcd5d762f5aabf2c33918 100644
--- a/src/conference/session/media-session.cpp
+++ b/src/conference/session/media-session.cpp
@@ -4293,7 +4293,7 @@ void MediaSession::initiateIncoming() {
 	}
 }
 
-bool MediaSession::initiateOutgoing(const string &subject, const Content *content) {
+bool MediaSession::initiateOutgoing(const string &subject, const std::shared_ptr<const Content> content) {
 	L_D();
 	bool defer = CallSession::initiateOutgoing(subject, content);
 
@@ -4633,7 +4633,7 @@ int MediaSession::getRandomRtpPort(const SalStreamDescription &stream) const {
 
 int MediaSession::startInvite(const std::shared_ptr<Address> &destination,
                               const string &subject,
-                              const Content *content) {
+                              const std::shared_ptr<const Content> content) {
 	L_D();
 
 	if (d->getOp() == nullptr) d->createOp();
@@ -5215,7 +5215,7 @@ const MediaSessionParams *MediaSession::getRemoteParams() {
 		const list<Content> &additionnalContents = d->op->getAdditionalRemoteBodies();
 		for (auto &content : additionnalContents) {
 			if (!params) params = new MediaSessionParams();
-			params->addCustomContent(content);
+			params->addCustomContent(Content::create(content));
 		}
 		d->setRemoteParams(params);
 		if (!params) {
diff --git a/src/conference/session/media-session.h b/src/conference/session/media-session.h
index f617530052d74b95836ffc688201bb5badb68ff7..b4a95ca7580c5ee80bf100c2378543330e21fa55 100644
--- a/src/conference/session/media-session.h
+++ b/src/conference/session/media-session.h
@@ -77,7 +77,8 @@ public:
 	               const std::shared_ptr<const Address> &to) override;
 	LinphoneStatus deferUpdate() override;
 	void initiateIncoming() override;
-	bool initiateOutgoing(const std::string &subject = "", const Content *content = nullptr) override;
+	bool initiateOutgoing(const std::string &subject = "",
+	                      const std::shared_ptr<const Content> content = nullptr) override;
 	void iterate(time_t currentRealTime, bool oneSecondElapsed) override;
 	LinphoneStatus pauseFromConference();
 	LinphoneStatus pause();
@@ -89,7 +90,7 @@ public:
 	void startIncomingNotification(bool notifyRinging = true) override;
 	int startInvite(const std::shared_ptr<Address> &destination,
 	                const std::string &subject = "",
-	                const Content *content = nullptr) override;
+	                const std::shared_ptr<const Content> content = nullptr) override;
 	bool startRecording();
 	void stopRecording();
 	bool isRecording();
diff --git a/src/content/content-disposition.cpp b/src/content/content-disposition.cpp
index de5b99f92165ba5b60feabcb291ae56144174ac3..f2da56b0ca181c722f040fe7d7880fd35fe83270 100644
--- a/src/content/content-disposition.cpp
+++ b/src/content/content-disposition.cpp
@@ -35,7 +35,8 @@ class ContentDispositionPrivate : public ClonableObjectPrivate {
 public:
 	string disposition;
 	string parameter;
-	mutable string fullDisposition; // Introduced to be able to extract a C pointer from the string returned by asString for the c-wrapper function
+	mutable string fullDisposition; // Introduced to be able to extract a C pointer from the string returned by asString
+	                                // for the c-wrapper function
 };
 
 // -----------------------------------------------------------------------------
@@ -98,12 +99,11 @@ void ContentDisposition::setParameter(const string &parameter) {
 	d->parameter = parameter;
 }
 
-const string &ContentDisposition::asString () const{
+const string &ContentDisposition::asString() const {
 	L_D();
 	if (isValid()) {
 		d->fullDisposition = d->disposition;
-		if (!d->parameter.empty())
-			d->fullDisposition += ";" + d->parameter;
+		if (!d->parameter.empty()) d->fullDisposition += ";" + d->parameter;
 	} else {
 		d->fullDisposition.clear();
 	}
diff --git a/src/content/content-disposition.h b/src/content/content-disposition.h
index 7a72494f727d673a8022898345a999bf4aded293..0736b397217e479296df42a4259452e101eb03d9 100644
--- a/src/content/content-disposition.h
+++ b/src/content/content-disposition.h
@@ -55,7 +55,7 @@ public:
 	const std::string &getParameter() const;
 	void setParameter(const std::string &parameter);
 
-	const std::string &asString () const;
+	const std::string &asString() const;
 
 	static const ContentDisposition Notification;
 	static const ContentDisposition RecipientList;
diff --git a/src/content/content-manager.cpp b/src/content/content-manager.cpp
index c12c3a663c7f86c129eecfad188a20f035d6da68..ab81020282d6d78661c661c08be70778ed70eb63 100644
--- a/src/content/content-manager.cpp
+++ b/src/content/content-manager.cpp
@@ -39,16 +39,12 @@ LINPHONE_BEGIN_NAMESPACE
 // -----------------------------------------------------------------------------
 
 list<Content> ContentManager::multipartToContentList(const Content &content) {
-	LinphoneContent *cContent = L_GET_C_BACK_PTR(&content);
-	SalBodyHandler *sbh = sal_body_handler_from_content(cContent);
+	SalBodyHandler *sbh = Content::getBodyHandlerFromContent(content);
 
 	list<Content> contents;
 	for (const belle_sip_list_t *parts = sal_body_handler_get_parts(sbh); parts; parts = parts->next) {
-		SalBodyHandler *part = (SalBodyHandler *)parts->data;
-		LinphoneContent *cContent = linphone_content_from_sal_body_handler(part, false);
-		Content *cppContent = L_GET_CPP_PTR_FROM_C_OBJECT(cContent);
-		contents.push_back(*cppContent);
-		linphone_content_unref(cContent);
+		auto part = (SalBodyHandler *)parts->data;
+		contents.emplace_back(part, false);
 	}
 
 	sal_body_handler_unref(sbh);
@@ -61,32 +57,46 @@ ContentManager::contentListToMultipart(const list<Content *> &contents, const st
 	    belle_sip_multipart_body_handler_new(nullptr, nullptr, nullptr, boundary.empty() ? nullptr : boundary.c_str());
 	mpbh = (belle_sip_multipart_body_handler_t *)belle_sip_object_ref(mpbh);
 
-	for (Content *content : contents) {
-		LinphoneContent *cContent = L_GET_C_BACK_PTR(content);
-		SalBodyHandler *sbh = sal_body_handler_from_content(cContent, false);
+	for (auto &content : contents) {
+		SalBodyHandler *sbh = Content::getBodyHandlerFromContent(*content, false);
 		belle_sip_multipart_body_handler_add_part(mpbh, BELLE_SIP_BODY_HANDLER(sbh));
 	}
 
-	SalBodyHandler *sbh = (SalBodyHandler *)mpbh;
+	auto sbh = (SalBodyHandler *)mpbh;
 	sal_body_handler_set_type(sbh, ContentType::Multipart.getType().c_str());
 	sal_body_handler_set_subtype(sbh, encrypted ? ContentType::Encrypted.getSubType().c_str()
 	                                            : ContentType::Multipart.getSubType().c_str());
 	sal_body_handler_set_content_type_parameter(sbh, "boundary", belle_sip_multipart_body_handler_get_boundary(mpbh));
 
-	LinphoneContent *cContent = linphone_content_from_sal_body_handler(sbh);
+	auto content = Content(sbh);
 	belle_sip_object_unref(mpbh);
 
-	Content content = *L_GET_CPP_PTR_FROM_C_OBJECT(cContent);
-	linphone_content_unref(cContent);
 	return content;
 }
 
+Content ContentManager::contentListToMultipart(const list<shared_ptr<Content>> &contents,
+                                               const string &boundary,
+                                               bool encrypted) {
+	list<Content *> contentsPtrs;
+	for (const auto &c : contents)
+		contentsPtrs.push_back(c.get());
+	return contentListToMultipart(contentsPtrs, boundary, encrypted);
+}
+
 Content ContentManager::contentListToMultipart(const std::list<Content *> &contents, bool encrypted) {
 	return contentListToMultipart(contents, "", encrypted);
 }
 
+Content ContentManager::contentListToMultipart(const list<shared_ptr<Content>> &contents, bool encrypted) {
+	return contentListToMultipart(contents, "", encrypted);
+}
+
 Content ContentManager::contentListToMultipart(const std::list<Content *> &contents) {
 	return contentListToMultipart(contents, "", false);
 }
 
+Content ContentManager::contentListToMultipart(const list<shared_ptr<Content>> &contents) {
+	return contentListToMultipart(contents, "", false);
+}
+
 LINPHONE_END_NAMESPACE
diff --git a/src/content/content-manager.h b/src/content/content-manager.h
index 3010d35db266b01879ab12c3260838edc60dfe84..0397a76671f250510474159c4b3ae559532486fb 100644
--- a/src/content/content-manager.h
+++ b/src/content/content-manager.h
@@ -36,9 +36,14 @@ LINPHONE_PUBLIC std::list<Content> multipartToContentList(const Content &content
 LINPHONE_PUBLIC Content contentListToMultipart(const std::list<Content *> &contents,
                                                const std::string &boundary,
                                                bool encrypted);
+LINPHONE_PUBLIC Content contentListToMultipart(const std::list<std::shared_ptr<Content>> &contents,
+                                               const std::string &boundary,
+                                               bool encrypted);
 /* There is no reason to set the boundary, prefer this form of the encode method: */
 LINPHONE_PUBLIC Content contentListToMultipart(const std::list<Content *> &contents, bool encrypted);
+LINPHONE_PUBLIC Content contentListToMultipart(const std::list<std::shared_ptr<Content>> &contents, bool encrypted);
 LINPHONE_PUBLIC Content contentListToMultipart(const std::list<Content *> &contents);
+LINPHONE_PUBLIC Content contentListToMultipart(const std::list<std::shared_ptr<Content>> &contents);
 } // namespace ContentManager
 
 LINPHONE_END_NAMESPACE
diff --git a/src/content/content-p.h b/src/content/content-p.h
deleted file mode 100644
index b8bb98a6d7d594a44db4d63fa22304afad38b182..0000000000000000000000000000000000000000
--- a/src/content/content-p.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (c) 2010-2022 Belledonne Communications SARL.
- *
- * This file is part of Liblinphone
- * (see https://gitlab.linphone.org/BC/public/liblinphone).
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef _L_CONTENT_P_H_
-#define _L_CONTENT_P_H_
-
-#include "content-disposition.h"
-#include "content-type.h"
-#include "content.h"
-#include "object/clonable-object-p.h"
-
-// =============================================================================
-
-LINPHONE_BEGIN_NAMESPACE
-
-class Header;
-
-class ContentPrivate : public ClonableObjectPrivate {
-private:
-	std::vector<char> body;
-	ContentType contentType;
-	ContentDisposition contentDisposition;
-	std::string contentEncoding;
-	std::list<Header> headers;
-
-	const std::list<std::pair<std::string, std::string>>::const_iterator
-	findHeader(const std::string &headerName) const;
-
-	L_DECLARE_PUBLIC(Content);
-};
-
-LINPHONE_END_NAMESPACE
-
-#endif // ifndef _L_CONTENT_P_H_
diff --git a/src/content/content.cpp b/src/content/content.cpp
index 7194c33b6452e6de4c1b5f45456e571566d727c5..2b9f5ccddf0469e3b88724bed33d0339bd6d1ec3 100644
--- a/src/content/content.cpp
+++ b/src/content/content.cpp
@@ -18,19 +18,14 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-// TODO: Remove me later.
-#include "linphone/core.h"
-
-#include "factory/factory.h"
-#include "linphone/utils/algorithm.h"
-#include "linphone/utils/utils.h"
-
-#include "content-p.h"
-#include "content-type.h"
-#include "header/header.h"
+#include "content.h"
 
 #include "bctoolbox/port.h"
 #include "bctoolbox/vfs_encrypted.hh"
+
+#include "factory/factory.h"
+#include "header/header-param.h"
+#include "linphone/utils/algorithm.h"
 #include "logger/logger.h"
 
 // =============================================================================
@@ -41,166 +36,220 @@ LINPHONE_BEGIN_NAMESPACE
 
 // =============================================================================
 
-Content::Content() : ClonableObject(*new ContentPrivate) {
-}
+Content::Content(const SalBodyHandler *bodyHandler, bool parseMultipart) {
+	if (bodyHandler == nullptr) return;
 
-Content::Content(const Content &other) : ClonableObject(*new ContentPrivate), AppDataContainer(other) {
-	copy(other);
+	mBodyHandler = sal_body_handler_ref((SalBodyHandler *)bodyHandler);
+
+	mContentType.setType(sal_body_handler_get_type(bodyHandler));
+	mContentType.setSubType(sal_body_handler_get_subtype(bodyHandler));
+	for (const belle_sip_list_t *params = sal_body_handler_get_content_type_parameters_names(bodyHandler); params;
+	     params = params->next) {
+		const char *paramName = reinterpret_cast<const char *>(params->data);
+		const char *paramValue = sal_body_handler_get_content_type_parameter(bodyHandler, paramName);
+		mContentType.addParameter(paramName, paramValue);
+	}
+
+	if (mContentType.isMultipart() && parseMultipart) {
+		belle_sip_multipart_body_handler_t *mpbh = BELLE_SIP_MULTIPART_BODY_HANDLER(bodyHandler);
+		char *body = belle_sip_object_to_string(mpbh);
+		setBodyFromUtf8(body);
+		belle_sip_free(body);
+	} else {
+		setBodyFromUtf8(reinterpret_cast<char *>(sal_body_handler_get_data(bodyHandler)));
+	}
+
+	auto headers = reinterpret_cast<const belle_sip_list_t *>(sal_body_handler_get_headers(bodyHandler));
+	while (headers) {
+		belle_sip_header_t *cHeader = BELLE_SIP_HEADER(headers->data);
+		Header header = Header(belle_sip_header_get_name(cHeader), belle_sip_header_get_unparsed_value(cHeader));
+		addHeader(header);
+		headers = headers->next;
+	}
+
+	if (sal_body_handler_get_encoding(bodyHandler)) mContentEncoding = sal_body_handler_get_encoding(bodyHandler);
+
+	const char *disposition = sal_body_handler_get_content_disposition(bodyHandler);
+	if (disposition) mContentDisposition = ContentDisposition(disposition);
 }
 
-Content::Content(Content &&other) : ClonableObject(*new ContentPrivate), AppDataContainer(std::move(other)) {
-	L_D();
-	ContentPrivate *dOther = other.getPrivate();
-	d->body = std::move(dOther->body);
-	d->contentType = std::move(dOther->contentType);
-	d->contentDisposition = std::move(dOther->contentDisposition);
-	d->contentEncoding = std::move(dOther->contentEncoding);
-	d->headers = std::move(dOther->headers);
+Content::Content(const Content &other) : HybridObject(other), PropertyContainer(other) {
+	copy(other);
 }
 
-Content::Content(ContentPrivate &p) : ClonableObject(p) {
+Content::Content(Content &&other) noexcept : HybridObject(std::move(other)) {
+	mBody = std::move(other.mBody);
+	mContentType = std::move(other.mContentType);
+	mContentDisposition = std::move(other.mContentDisposition);
+	mContentEncoding = std::move(other.mContentEncoding);
+	mHeaders = std::move(other.mHeaders);
+	mCryptoContext = std::move(other.mCryptoContext);
+	other.mCryptoContext = nullptr;
+	mSize = std::move(other.mSize);
+	mIsDirty = std::move(other.mIsDirty);
+	mBodyHandler = std::move(other.mBodyHandler);
+	other.mBodyHandler = nullptr;
 }
 
 Content::~Content() {
-	L_D();
 	/*
 	 * Fills the body with zeros before releasing since it may contain
 	 * private data like cipher keys or decoded messages.
 	 */
-	d->body.assign(d->body.size(), 0);
+	mBody.assign(mBody.size(), 0);
+	if (mBodyHandler != nullptr) sal_body_handler_unref(mBodyHandler);
 }
 
 Content &Content::operator=(const Content &other) {
 	if (this != &other) {
-		AppDataContainer::operator=(other);
+		PropertyContainer::operator=(other);
 		copy(other);
 	}
 	return *this;
 }
 
-Content &Content::operator=(Content &&other) {
-	L_D();
-	AppDataContainer::operator=(std::move(other));
-	ContentPrivate *dOther = other.getPrivate();
-	d->body = std::move(dOther->body);
-	d->contentType = std::move(dOther->contentType);
-	d->contentDisposition = std::move(dOther->contentDisposition);
-	d->contentEncoding = std::move(dOther->contentEncoding);
-	d->headers = std::move(dOther->headers);
+Content &Content::operator=(Content &&other) noexcept {
+	mBody = std::move(other.mBody);
+	mContentType = std::move(other.mContentType);
+	mContentDisposition = std::move(other.mContentDisposition);
+	mContentEncoding = std::move(other.mContentEncoding);
+	mHeaders = std::move(other.mHeaders);
+	mCryptoContext = std::move(other.mCryptoContext);
+	other.mCryptoContext = nullptr;
+	mSize = std::move(other.mSize);
+	mIsDirty = std::move(other.mIsDirty);
+	mBodyHandler = std::move(other.mBodyHandler);
+	other.mBodyHandler = nullptr;
 	return *this;
 }
 
 bool Content::operator==(const Content &other) const {
-	L_D();
-	return d->contentType == other.getContentType() && d->body == other.getBody() &&
-	       d->contentDisposition == other.getContentDisposition() && d->contentEncoding == other.getContentEncoding() &&
-	       d->headers == other.getHeaders();
+	return mContentType == other.getContentType() && mBody == other.getBody() &&
+	       mContentDisposition == other.getContentDisposition() && mContentEncoding == other.getContentEncoding() &&
+	       mHeaders == other.getHeaders();
 }
 
 void Content::copy(const Content &other) {
-	L_D();
-	d->body = other.getBody();
-	d->contentType = other.getContentType();
-	d->contentDisposition = other.getContentDisposition();
-	d->contentEncoding = other.getContentEncoding();
-	d->headers = other.getHeaders();
+	mBody = other.getBody();
+	mContentType = other.getContentType();
+	mContentDisposition = other.getContentDisposition();
+	mContentEncoding = other.getContentEncoding();
+	mHeaders = other.getHeaders();
+	mSize = other.mSize;
+	mCache = other.mCache;
+	if (!mIsDirty && mBodyHandler != nullptr) mBodyHandler = sal_body_handler_ref(other.mBodyHandler);
 }
 
 const ContentType &Content::getContentType() const {
-	L_D();
-	return d->contentType;
+	return mContentType;
 }
 
 ContentType &Content::getContentType() {
-	L_D();
-	return d->contentType;
+	return mContentType;
 }
 
 void Content::setContentType(const ContentType &contentType) {
-	L_D();
-	d->contentType = contentType;
+	mContentType = contentType;
 }
 
 const ContentDisposition &Content::getContentDisposition() const {
-	L_D();
-	return d->contentDisposition;
+	return mContentDisposition;
 }
 
 void Content::setContentDisposition(const ContentDisposition &contentDisposition) {
-	L_D();
-	d->contentDisposition = contentDisposition;
+	mContentDisposition = contentDisposition;
 }
 
 const string &Content::getContentEncoding() const {
-	L_D();
-	return d->contentEncoding;
+	return mContentEncoding;
 }
 
 void Content::setContentEncoding(const string &contentEncoding) {
-	L_D();
-	d->contentEncoding = contentEncoding;
+	mContentEncoding = contentEncoding;
 }
 
 const vector<char> &Content::getBody() const {
-	L_D();
-	return d->body;
+	return mBody;
 }
 
 string Content::getBodyAsString() const {
-	L_D();
-	return Utils::utf8ToLocale(string(d->body.begin(), d->body.end()));
+	return Utils::utf8ToLocale(string(mBody.begin(), mBody.end()));
 }
 
-string Content::getBodyAsUtf8String() const {
-	L_D();
-	return string(d->body.begin(), d->body.end());
+const string &Content::getBodyAsUtf8String() const {
+	mCache.buffer = string(mBody.begin(), mBody.end());
+	return mCache.buffer;
 }
 
 void Content::setBody(const vector<char> &body) {
-	L_D();
-	d->body = body;
+	mBody = body;
 }
 
 void Content::setBody(vector<char> &&body) {
-	L_D();
-	d->body = std::move(body);
+	mBody = std::move(body);
 }
 
 void Content::setBodyFromLocale(const string &body) {
-	L_D();
 	string toUtf8 = Utils::localeToUtf8(body);
-	d->body = vector<char>(toUtf8.cbegin(), toUtf8.cend());
+	mBody = vector<char>(toUtf8.cbegin(), toUtf8.cend());
 }
 
 void Content::setBody(const void *buffer, size_t size) {
-	L_D();
+	mIsDirty = true;
+
 	const char *start = static_cast<const char *>(buffer);
-	if (start != nullptr) d->body = vector<char>(start, start + size);
-	else d->body.clear();
+	if (start != nullptr) mBody = vector<char>(start, start + size);
+	else mBody.clear();
 }
 
 void Content::setBodyFromUtf8(const string &body) {
-	L_D();
-	d->body = vector<char>(body.cbegin(), body.cend());
+	mIsDirty = true;
+
+	mBody = vector<char>(body.cbegin(), body.cend());
+}
+
+const std::string &Content::getName() const {
+	return mCache.name;
+}
+
+void Content::setName(const std::string &name) {
+	mCache.name = name;
 }
 
 size_t Content::getSize() const {
-	L_D();
-	return d->body.size();
+	return mBody.empty() ? mSize : mBody.size();
+}
+
+void Content::setSize(size_t size) {
+	mSize = size;
+}
+
+SalBodyHandler *Content::getBodyHandler() const {
+	return mBodyHandler;
+}
+
+void Content::setBodyHandler(SalBodyHandler *bodyHandler) {
+	mBodyHandler = bodyHandler;
+}
+
+void **Content::getCryptoContextAddress() {
+	return &mCryptoContext;
 }
 
 bool Content::isEmpty() const {
 	return getSize() == 0;
 }
 
+bool Content::isDirty() const {
+	return mIsDirty;
+}
+
 bool Content::isMultipart() const {
-	L_D();
-	return d->contentType.isValid() && d->contentType == ContentType::Multipart;
+	return mContentType.isValid() && mContentType == ContentType::Multipart;
 }
 
 bool Content::isValid() const {
-	L_D();
-	return d->contentType.isValid() || (d->contentType.isEmpty() && d->body.empty());
+	return mContentType.isValid() || (mContentType.isEmpty() && mBody.empty());
 }
 
 bool Content::isFile() const {
@@ -211,42 +260,58 @@ bool Content::isFileTransfer() const {
 	return false;
 }
 
+const std::string &Content::getFilePath() const {
+	return mCache.filePath;
+}
+
+void Content::setFilePath(const std::string &path) {
+	mCache.filePath = path;
+}
+
 void Content::addHeader(const string &headerName, const string &headerValue) {
-	L_D();
 	removeHeader(headerName);
 	Header header = Header(headerName, headerValue);
-	d->headers.push_back(header);
+	mHeaders.push_back(header);
 }
 
 void Content::addHeader(const Header &header) {
-	L_D();
 	removeHeader(header.getName());
-	d->headers.push_back(header);
+	mHeaders.push_back(header);
 }
 
 const list<Header> &Content::getHeaders() const {
-	L_D();
-	return d->headers;
+	return mHeaders;
 }
 
 const Header &Content::getHeader(const string &headerName) const {
-	L_D();
-	list<Header>::const_iterator it = findHeader(headerName);
-	if (it != d->headers.cend()) {
+	auto it = findHeader(headerName);
+	if (it != mHeaders.cend()) {
 		return *it;
 	}
 	return Utils::getEmptyConstRefObject<Header>();
 }
 
 void Content::removeHeader(const string &headerName) {
-	L_D();
 	auto it = findHeader(headerName);
-	if (it != d->headers.cend()) d->headers.remove(*it);
+	if (it != mHeaders.cend()) mHeaders.remove(*it);
 }
 
 list<Header>::const_iterator Content::findHeader(const string &headerName) const {
-	L_D();
-	return findIf(d->headers, [&headerName](const Header &header) { return header.getName() == headerName; });
+	return findIf(mHeaders, [&headerName](const Header &header) { return header.getName() == headerName; });
+}
+
+const std::string &Content::getCustomHeader(const std::string &headerName) const {
+	SalBodyHandler *bodyHandler;
+
+	if (!mIsDirty && mBodyHandler != nullptr) {
+		bodyHandler = sal_body_handler_ref(mBodyHandler);
+	} else {
+		bodyHandler = getBodyHandlerFromContent(*this);
+	}
+
+	mCache.headerValue = L_C_TO_STRING(sal_body_handler_get_header(bodyHandler, headerName.c_str()));
+	sal_body_handler_unref(bodyHandler);
+	return mCache.headerValue;
 }
 
 void Content::setUserData(const Variant &userData) {
@@ -257,6 +322,58 @@ Variant Content::getUserData() const {
 	return getProperty("LinphonePrivate::Content::userData");
 }
 
+SalBodyHandler *Content::getBodyHandlerFromContent(const Content &content, bool parseMultipart) {
+	if (!content.mIsDirty && content.mBodyHandler != nullptr) return sal_body_handler_ref(content.mBodyHandler);
+
+	SalBodyHandler *bodyHandler;
+	ContentType contentType = content.mContentType;
+	if (contentType.isMultipart() && parseMultipart) {
+		size_t size = content.getSize();
+		char *buffer = bctbx_strdup(content.getBodyAsUtf8String().c_str());
+		const char *boundary = L_STRING_TO_C(contentType.getParameter("boundary").getValue());
+		belle_sip_multipart_body_handler_t *bh = nullptr;
+		if (boundary) bh = belle_sip_multipart_body_handler_new_from_buffer(buffer, size, boundary);
+		else if (size > 2) {
+			size_t startIndex = 2, index;
+			while (startIndex < size &&
+			       (buffer[startIndex] != '-' || buffer[startIndex - 1] != '-' // Take accout of first "--"
+			        || (startIndex > 2 && buffer[startIndex - 2] != '\n')))    // Must be at the beginning of the line
+				++startIndex;
+			index = startIndex;
+			while (index < size && buffer[index] != '\n' && buffer[index] != '\r')
+				++index;
+			if (startIndex != index) {
+				char *boundaryStr = bctbx_strndup(buffer + startIndex, (int)(index - startIndex));
+				bh = belle_sip_multipart_body_handler_new_from_buffer(buffer, size, boundaryStr);
+				bctbx_free(boundaryStr);
+			}
+		}
+
+		bodyHandler = reinterpret_cast<SalBodyHandler *>(BELLE_SIP_BODY_HANDLER(bh));
+		bctbx_free(buffer);
+	} else {
+		bodyHandler = sal_body_handler_new();
+		sal_body_handler_set_data(bodyHandler, belle_sip_strdup(content.getBodyAsUtf8String().c_str()));
+	}
+
+	for (const auto &header : content.getHeaders()) {
+		sal_body_handler_add_header(bodyHandler, header.getName().c_str(), header.getValueWithParams().c_str());
+	}
+
+	sal_body_handler_set_type(bodyHandler, contentType.getType().c_str());
+	sal_body_handler_set_subtype(bodyHandler, contentType.getSubType().c_str());
+	sal_body_handler_set_size(bodyHandler, content.getSize());
+	for (const auto &param : contentType.getParameters())
+		sal_body_handler_set_content_type_parameter(bodyHandler, param.getName().c_str(), param.getValue().c_str());
+
+	if (!content.mContentEncoding.empty()) sal_body_handler_set_encoding(bodyHandler, content.mContentEncoding.c_str());
+
+	const ContentDisposition &disposition = content.getContentDisposition();
+	if (disposition.isValid()) sal_body_handler_set_content_disposition(bodyHandler, disposition.asString().c_str());
+
+	return bodyHandler;
+}
+
 bool Content::isFileEncrypted(const string &filePath) const {
 	if (filePath.empty()) {
 		return false;
@@ -287,7 +404,7 @@ const string Content::exportPlainFileFromEncryptedFile(const string &filePath) c
 		return filePath;
 	}
 
-	// plain files are stored in a "evfs" subdirectory of the cache directory
+	// plain files are stored in an "evfs" subdirectory of the cache directory
 	std::string cacheDir(Factory::get()->getCacheDir(nullptr) + "/evfs/");
 
 	// Create the directory if it is not present
diff --git a/src/content/content.h b/src/content/content.h
index 85ffd5dd6ae5e6c6b8f8f1cb9255e325576f1fec..cb33f90a8f294da4572f5240d86b1a3d6a7a93b8 100644
--- a/src/content/content.h
+++ b/src/content/content.h
@@ -24,33 +24,33 @@
 #include <list>
 #include <vector>
 
-#include "object/app-data-container.h"
-#include "object/clonable-object.h"
+#include "belle-sip/object++.hh"
 
-// =============================================================================
+#include "c-wrapper/internal/c-sal.h"
+#include "content-disposition.h"
+#include "content-type.h"
+#include "header/header.h"
+#include "linphone/api/c-types.h"
+#include "object/property-container.h"
 
-L_DECL_C_STRUCT(LinphoneContent);
+// =============================================================================
 
 LINPHONE_BEGIN_NAMESPACE
 
-class ContentDisposition;
-class ContentType;
-class ContentPrivate;
-class Header;
-
-class LINPHONE_PUBLIC Content : public ClonableObject, public AppDataContainer {
+class LINPHONE_PUBLIC Content : public bellesip::HybridObject<LinphoneContent, Content>, public PropertyContainer {
 public:
-	Content();
+	Content() = default;
+	explicit Content(const SalBodyHandler *bodyHandler, bool parseMultipart = true);
 	Content(const Content &other);
-	Content(Content &&other);
-	~Content();
+	Content(Content &&other) noexcept;
+	virtual ~Content();
 
 	Content *clone() const override {
 		return new Content(*this);
 	}
 
 	Content &operator=(const Content &other);
-	Content &operator=(Content &&other);
+	Content &operator=(Content &&other) noexcept;
 
 	bool operator==(const Content &other) const;
 
@@ -70,7 +70,7 @@ public:
 
 	const std::vector<char> &getBody() const;
 	std::string getBodyAsString() const;
-	std::string getBodyAsUtf8String() const;
+	const std::string &getBodyAsUtf8String() const;
 
 	void setBody(const std::vector<char> &body);
 	void setBody(std::vector<char> &&body);
@@ -78,33 +78,63 @@ public:
 	void setBody(const void *buffer, size_t size);
 	void setBodyFromUtf8(const std::string &body);
 
+	const std::string &getName() const;
+	void setName(const std::string &name);
+
 	size_t getSize() const;
+	void setSize(size_t size);
+
+	SalBodyHandler *getBodyHandler() const;
+	void setBodyHandler(SalBodyHandler *bodyHandler);
+
+	void **getCryptoContextAddress();
 
 	bool isValid() const;
 	bool isMultipart() const;
 	bool isEmpty() const;
+	bool isDirty() const;
 
 	virtual bool isFile() const;
 	virtual bool isFileTransfer() const;
 
+	virtual const std::string &getFilePath() const;
+	virtual void setFilePath(const std::string &path);
+
 	const std::list<Header> &getHeaders() const;
 	const Header &getHeader(const std::string &headerName) const;
 	void addHeader(const std::string &headerName, const std::string &headerValue);
 	void addHeader(const Header &header);
 	void removeHeader(const std::string &headerName);
 	std::list<Header>::const_iterator findHeader(const std::string &headerName) const;
+	const std::string &getCustomHeader(const std::string &headerName) const;
 
 	void setUserData(const Variant &userData);
 	Variant getUserData() const;
 
-protected:
-	explicit Content(ContentPrivate &p);
+	static SalBodyHandler *getBodyHandlerFromContent(const Content &content, bool parseMultipart = true);
 
+protected:
 	bool isFileEncrypted(const std::string &filePath) const;
 	const std::string exportPlainFileFromEncryptedFile(const std::string &filePath) const;
 
 private:
-	L_DECLARE_PRIVATE(Content);
+	std::vector<char> mBody;
+	ContentType mContentType;
+	ContentDisposition mContentDisposition;
+	std::string mContentEncoding;
+	std::list<Header> mHeaders;
+
+	void *mCryptoContext = nullptr; // Used to encrypt file for RCS file transfer.
+	bool mIsDirty = false;
+	SalBodyHandler *mBodyHandler = nullptr;
+
+	struct Cache {
+		std::string name;
+		std::string buffer;
+		std::string filePath;
+		std::string headerValue;
+	} mutable mCache;
+	mutable size_t mSize = 0;
 };
 
 LINPHONE_END_NAMESPACE
diff --git a/src/content/file-content.cpp b/src/content/file-content.cpp
index d25ce2bfec36a85fce463441b4017a3b621be83a..ba1a6dd35b2ffa21ce1d6c3a7ba0f1108a0ae8c0 100644
--- a/src/content/file-content.cpp
+++ b/src/content/file-content.cpp
@@ -18,15 +18,14 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-// TODO: Remove me later.
-#include "linphone/core.h"
-#include "linphone/utils/utils.h"
-
-#include "bctoolbox/charconv.h"
-#include "content-p.h"
 #include "file-content.h"
+
 #include <algorithm>
 
+#include "bctoolbox/charconv.h"
+
+#include "linphone/utils/utils.h"
+
 // =============================================================================
 
 using namespace std;
@@ -35,81 +34,59 @@ LINPHONE_BEGIN_NAMESPACE
 
 // -----------------------------------------------------------------------------
 
-class FileContentPrivate : public ContentPrivate {
-public:
-	string fileName;
-	string filePath;
-	size_t fileSize = 0;
-	int fileDuration = 0;
-};
-
-// -----------------------------------------------------------------------------
-
-FileContent::FileContent() : Content(*new FileContentPrivate) {
-}
-
-FileContent::FileContent(const FileContent &other) : Content(*new FileContentPrivate) {
-	L_D();
+FileContent::FileContent(const FileContent &other) : Content(other) {
 	Content::copy(other);
 	setFileName(other.getFileName());
 	setFilePath(other.getFilePath());
-	d->fileSize = other.getFileSize();
-	d->fileDuration = other.getFileDuration();
+	mFileSize = other.getFileSize();
+	mFileDuration = other.getFileDuration();
 }
 
-FileContent::FileContent(FileContent &&other) : Content(*new FileContentPrivate) {
-	L_D();
+FileContent::FileContent(FileContent &&other) noexcept : Content(other) {
 	Content::copy(other);
-	d->fileName = std::move(other.getPrivate()->fileName);
-	d->filePath = std::move(other.getPrivate()->filePath);
-	d->fileSize = std::move(other.getPrivate()->fileSize);
-	d->fileDuration = std::move(other.getPrivate()->fileDuration);
+	mFileName = std::move(other.mFileName);
+	mFilePath = std::move(other.mFilePath);
+	mFileSize = std::move(other.mFileSize);
+	mFileDuration = std::move(other.mFileDuration);
 }
 
 FileContent &FileContent::operator=(const FileContent &other) {
-	L_D();
 	Content::operator=(other);
 	setFileName(other.getFileName());
 	setFilePath(other.getFilePath());
-	d->fileSize = other.getFileSize();
-	d->fileDuration = other.getFileDuration();
+	mFileSize = other.getFileSize();
+	mFileDuration = other.getFileDuration();
 	return *this;
 }
 
 FileContent &FileContent::operator=(FileContent &&other) {
-	L_D();
 	Content::operator=(std::move(other));
-	d->fileName = std::move(other.getPrivate()->fileName);
-	d->filePath = std::move(other.getPrivate()->filePath);
-	d->fileSize = std::move(other.getPrivate()->fileSize);
-	d->fileDuration = std::move(other.getPrivate()->fileDuration);
+	mFileName = std::move(other.mFileName);
+	mFilePath = std::move(other.mFilePath);
+	mFileSize = std::move(other.mFileSize);
+	mFileDuration = std::move(other.mFileDuration);
 	return *this;
 }
 
 bool FileContent::operator==(const FileContent &other) const {
-	L_D();
 	return Content::operator==(other) && getFileName() == other.getFileName() && getFilePath() == other.getFilePath() &&
-	       d->fileSize == other.getFileSize() && d->fileDuration == other.getFileDuration();
+	       mFileSize == other.getFileSize() && mFileDuration == other.getFileDuration();
 }
 
 void FileContent::setFileSize(size_t size) {
-	L_D();
-	d->fileSize = size;
+	mFileSize = size;
 }
 
 size_t FileContent::getFileSize() const {
-	L_D();
-	return d->fileSize;
+	return mFileSize;
 }
 
 void FileContent::setFileName(const string &name) {
-	L_D();
-	d->fileName = Utils::normalizeFilename(name);
+	mFileName = Utils::normalizeFilename(name);
 }
 
 const string &FileContent::getFileName() const {
-	L_D();
-	return d->fileName;
+	return mFileName;
 }
 
 void FileContent::setFileNameSys(const string &name) {
@@ -129,13 +106,11 @@ string FileContent::getFileNameUtf8() const {
 }
 
 void FileContent::setFilePath(const string &path) {
-	L_D();
-	d->filePath = path;
+	mFilePath = path;
 }
 
 const string &FileContent::getFilePath() const {
-	L_D();
-	return d->filePath;
+	return mFilePath;
 }
 
 void FileContent::setFilePathSys(const string &path) {
@@ -155,13 +130,11 @@ string FileContent::getFilePathUtf8() const {
 }
 
 void FileContent::setFileDuration(int durationInSeconds) {
-	L_D();
-	d->fileDuration = durationInSeconds;
+	mFileDuration = durationInSeconds;
 }
 
 int FileContent::getFileDuration() const {
-	L_D();
-	return d->fileDuration;
+	return mFileDuration;
 }
 
 bool FileContent::isFile() const {
diff --git a/src/content/file-content.h b/src/content/file-content.h
index a7562c324707a62a87009afd0f6c972fc9d91f34..8b8ac26dc562649da263293b3a85db5cbdc18e80 100644
--- a/src/content/file-content.h
+++ b/src/content/file-content.h
@@ -27,13 +27,11 @@
 
 LINPHONE_BEGIN_NAMESPACE
 
-class FileContentPrivate;
-
 class LINPHONE_PUBLIC FileContent : public Content {
 public:
-	FileContent();
+	FileContent() = default;
 	FileContent(const FileContent &other);
-	FileContent(FileContent &&other);
+	FileContent(FileContent &&other) noexcept;
 
 	FileContent *clone() const override {
 		return new FileContent(*this);
@@ -56,8 +54,8 @@ public:
 	void setFileNameUtf8(const std::string &name); // UTF8
 	std::string getFileNameUtf8() const;
 
-	void setFilePath(const std::string &path); // App Locale
-	const std::string &getFilePath() const;
+	void setFilePath(const std::string &path) override; // App Locale
+	const std::string &getFilePath() const override;
 
 	void setFilePathSys(const std::string &path); // System Locale
 	std::string getFilePathSys() const;
@@ -80,7 +78,10 @@ public:
 	const std::string exportPlainFile() const;
 
 private:
-	L_DECLARE_PRIVATE(FileContent);
+	std::string mFileName;
+	std::string mFilePath;
+	size_t mFileSize = 0;
+	int mFileDuration = 0;
 };
 
 LINPHONE_END_NAMESPACE
diff --git a/src/content/file-transfer-content.cpp b/src/content/file-transfer-content.cpp
index d9f2bf5acdc42058f3c4afc765abde69d0d321bc..a563a2625c5efb2c4c6d160a82a3b2d15bd66067 100644
--- a/src/content/file-transfer-content.cpp
+++ b/src/content/file-transfer-content.cpp
@@ -18,17 +18,14 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-// TODO: Remove me later.
-#include "linphone/core.h"
-#include "linphone/utils/utils.h"
-
-#include "bctoolbox/charconv.h"
-#include "bctoolbox/crypto.h"
-#include "content-p.h"
 #include "file-transfer-content.h"
 
 #include <algorithm>
 
+#include "bctoolbox/charconv.h"
+
+#include "linphone/utils/utils.h"
+
 // =============================================================================
 
 using namespace std;
@@ -37,107 +34,80 @@ LINPHONE_BEGIN_NAMESPACE
 
 // -----------------------------------------------------------------------------
 
-class FileTransferContentPrivate : public ContentPrivate {
-public:
-	string fileName;
-	string fileUrl;
-	string filePath;
-	FileContent *fileContent = nullptr;
-	size_t fileSize = 0;
-	int fileDuration = 0;
-	std::vector<char> fileKey;
-	std::vector<char> fileAuthTag;
-	ContentType fileContentType;
-	~FileTransferContentPrivate() {
-		if (!fileKey.empty()) {
-			bctbx_clean(fileKey.data(), fileKey.size());
-		}
-	};
-};
-
-// -----------------------------------------------------------------------------
-
-FileTransferContent::FileTransferContent() : Content(*new FileTransferContentPrivate) {
+FileTransferContent::FileTransferContent() {
 	setContentType(ContentType::FileTransfer);
 }
 
-FileTransferContent::FileTransferContent(const FileTransferContent &other) : Content(*new FileTransferContentPrivate) {
-	L_D();
+FileTransferContent::FileTransferContent(const FileTransferContent &other) : Content(other) {
 	Content::copy(other);
 	setFileName(other.getFileName());
 	setFilePath(other.getFilePath());
-	d->fileUrl = other.getFileUrl();
-	d->fileContent = other.getFileContent();
-	d->fileSize = other.getFileSize();
-	d->fileKey = other.getFileKey();
-	d->fileAuthTag = other.getFileAuthTag();
-	d->fileContentType = other.getFileContentType();
-	d->fileDuration = other.getFileDuration();
+	mFileUrl = other.getFileUrl();
+	mFileContent = other.getFileContent();
+	mFileSize = other.getFileSize();
+	mFileKey = other.getFileKey();
+	mFileAuthTag = other.getFileAuthTag();
+	mFileContentType = other.getFileContentType();
+	mFileDuration = other.getFileDuration();
 }
 
-FileTransferContent::FileTransferContent(FileTransferContent &&other) : Content(*new FileTransferContentPrivate) {
-	L_D();
+FileTransferContent::FileTransferContent(FileTransferContent &&other) : Content(other) {
 	Content::copy(other);
-	d->fileName = std::move(other.getPrivate()->fileName);
-	d->fileUrl = std::move(other.getPrivate()->fileUrl);
-	d->filePath = std::move(other.getPrivate()->filePath);
-	d->fileContent = std::move(other.getPrivate()->fileContent);
-	d->fileSize = std::move(other.getPrivate()->fileSize);
-	d->fileKey = std::move(other.getPrivate()->fileKey);
-	d->fileAuthTag = std::move(other.getPrivate()->fileAuthTag);
-	d->fileContentType = std::move(other.getPrivate()->fileContentType);
-	d->fileDuration = std::move(other.getPrivate()->fileDuration);
+	mFileName = std::move(other.mFileName);
+	mFileUrl = std::move(other.mFileUrl);
+	mFilePath = std::move(other.mFilePath);
+	mFileContent = std::move(other.mFileContent);
+	mFileSize = std::move(other.mFileSize);
+	mFileKey = std::move(other.mFileKey);
+	mFileAuthTag = std::move(other.mFileAuthTag);
+	mFileContentType = std::move(other.mFileContentType);
+	mFileDuration = std::move(other.mFileDuration);
 }
 
 FileTransferContent &FileTransferContent::operator=(const FileTransferContent &other) {
-	L_D();
 	if (this != &other) {
 		Content::operator=(other);
 		setFileName(other.getFileName());
 		setFilePath(other.getFilePath());
-		d->fileUrl = other.getFileUrl();
-		d->fileContent = other.getFileContent();
-		d->fileSize = other.getFileSize();
-		d->fileKey = other.getFileKey();
-		d->fileAuthTag = other.getFileAuthTag();
-		d->fileContentType = other.getFileContentType();
-		d->fileDuration = other.getFileDuration();
+		mFileUrl = other.getFileUrl();
+		mFileContent = other.getFileContent();
+		mFileSize = other.getFileSize();
+		mFileKey = other.getFileKey();
+		mFileAuthTag = other.getFileAuthTag();
+		mFileContentType = other.getFileContentType();
+		mFileDuration = other.getFileDuration();
 	}
 
 	return *this;
 }
 
 FileTransferContent &FileTransferContent::operator=(FileTransferContent &&other) {
-	L_D();
 	Content::operator=(std::move(other));
-	d->fileName = std::move(other.getPrivate()->fileName);
-	d->fileUrl = std::move(other.getPrivate()->fileUrl);
-	d->filePath = std::move(other.getPrivate()->filePath);
-	d->fileContent = std::move(other.getPrivate()->fileContent);
-	d->fileSize = std::move(other.getPrivate()->fileSize);
-	d->fileKey = std::move(other.getPrivate()->fileKey);
-	d->fileAuthTag = std::move(other.getPrivate()->fileAuthTag);
-	d->fileContentType = std::move(other.getPrivate()->fileContentType);
-	d->fileDuration = std::move(other.getPrivate()->fileDuration);
+	mFileName = std::move(other.mFileName);
+	mFileUrl = std::move(other.mFileUrl);
+	mFilePath = std::move(other.mFilePath);
+	mFileContent = std::move(other.mFileContent);
+	mFileSize = std::move(other.mFileSize);
+	mFileKey = std::move(other.mFileKey);
+	mFileAuthTag = std::move(other.mFileAuthTag);
+	mFileContentType = std::move(other.mFileContentType);
+	mFileDuration = std::move(other.mFileDuration);
 
 	return *this;
 }
 
 bool FileTransferContent::operator==(const FileTransferContent &other) const {
-	L_D();
-	return Content::operator==(other) && getFileName() == other.getFileName() && d->fileUrl == other.getFileUrl() &&
-	       getFilePath() == other.getFilePath() && d->fileSize == other.getFileSize() &&
-	       d->fileContentType == other.getFileContentType() && d->fileDuration == other.getFileDuration();
+	return Content::operator==(other) && getFileName() == other.getFileName() && mFileUrl == other.getFileUrl() &&
+	       getFilePath() == other.getFilePath() && mFileSize == other.getFileSize() &&
+	       mFileContentType == other.getFileContentType() && mFileDuration == other.getFileDuration();
 }
 
 void FileTransferContent::setFileName(const string &name) {
-	L_D();
-	d->fileName = Utils::normalizeFilename(name);
+	mFileName = Utils::normalizeFilename(name);
 }
 
 const string &FileTransferContent::getFileName() const {
-	L_D();
-	return d->fileName;
+	return mFileName;
 }
 
 void FileTransferContent::setFileNameSys(const string &name) {
@@ -157,23 +127,19 @@ string FileTransferContent::getFileNameUtf8() const {
 }
 
 void FileTransferContent::setFileUrl(const string &url) {
-	L_D();
-	d->fileUrl = url;
+	mFileUrl = url;
 }
 
 const string &FileTransferContent::getFileUrl() const {
-	L_D();
-	return d->fileUrl;
+	return mFileUrl;
 }
 
 void FileTransferContent::setFilePath(const string &path) {
-	L_D();
-	d->filePath = path;
+	mFilePath = path;
 }
 
 const string &FileTransferContent::getFilePath() const {
-	L_D();
-	return d->filePath;
+	return mFilePath;
 }
 
 void FileTransferContent::setFilePathSys(const string &path) {
@@ -192,74 +158,60 @@ string FileTransferContent::getFilePathUtf8() const {
 	return Utils::localeToUtf8(getFilePath());
 }
 
-void FileTransferContent::setFileContent(FileContent *content) {
-	L_D();
-	d->fileContent = content;
+void FileTransferContent::setFileContent(std::shared_ptr<FileContent> content) {
+	mFileContent = content;
 }
 
-FileContent *FileTransferContent::getFileContent() const {
-	L_D();
-	return d->fileContent;
+std::shared_ptr<FileContent> FileTransferContent::getFileContent() const {
+	return mFileContent;
 }
 
 void FileTransferContent::setFileSize(size_t size) {
-	L_D();
-	d->fileSize = size;
+	mFileSize = size;
 }
 
 size_t FileTransferContent::getFileSize() const {
-	L_D();
-	return d->fileSize;
+	return mFileSize;
 }
 
 void FileTransferContent::setFileDuration(int durationInSeconds) {
-	L_D();
-	d->fileDuration = durationInSeconds;
+	mFileDuration = durationInSeconds;
 }
 
 int FileTransferContent::getFileDuration() const {
-	L_D();
-	return d->fileDuration;
+	return mFileDuration;
 }
 
 void FileTransferContent::setFileKey(const char *key, size_t size) {
-	L_D();
-	d->fileKey = vector<char>(key, key + size);
+	mFileKey = vector<char>(key, key + size);
 }
 
 const vector<char> &FileTransferContent::getFileKey() const {
-	L_D();
-	return d->fileKey;
+	return mFileKey;
 }
 
 size_t FileTransferContent::getFileKeySize() const {
-	L_D();
-	return d->fileKey.size();
+	return mFileKey.size();
 }
 
 void FileTransferContent::setFileAuthTag(const char *tag, size_t size) {
-	L_D();
-	d->fileAuthTag = vector<char>(tag, tag + size);
+	mFileAuthTag = vector<char>(tag, tag + size);
 }
 
 const vector<char> &FileTransferContent::getFileAuthTag() const {
-	L_D();
-	return d->fileAuthTag;
+	return mFileAuthTag;
 }
 
 size_t FileTransferContent::getFileAuthTagSize() const {
-	L_D();
-	return d->fileAuthTag.size();
+	return mFileAuthTag.size();
 }
 
 void FileTransferContent::setFileContentType(const ContentType &contentType) {
-	L_D();
-	d->fileContentType = contentType;
+	mFileContentType = contentType;
 }
 
 const ContentType &FileTransferContent::getFileContentType() const {
-	L_D();
-	return d->fileContentType;
+	return mFileContentType;
 }
 
 bool FileTransferContent::isFile() const {
diff --git a/src/content/file-transfer-content.h b/src/content/file-transfer-content.h
index aed8511b026ba2de7c27d4a83b2e000d248d7a47..c41bbe49d3b9aeb348c00fcff076d8f42c4fff4e 100644
--- a/src/content/file-transfer-content.h
+++ b/src/content/file-transfer-content.h
@@ -23,6 +23,8 @@
 
 #include <vector>
 
+#include "bctoolbox/crypto.h"
+
 #include "content.h"
 
 // =============================================================================
@@ -30,7 +32,7 @@
 LINPHONE_BEGIN_NAMESPACE
 
 class FileContent;
-class FileTransferContentPrivate;
+class ContentType;
 
 class LINPHONE_PUBLIC FileTransferContent : public Content {
 public:
@@ -59,8 +61,8 @@ public:
 	void setFileUrl(const std::string &url);
 	const std::string &getFileUrl() const;
 
-	void setFilePath(const std::string &path); // App Locale
-	const std::string &getFilePath() const;
+	void setFilePath(const std::string &path) override; // App Locale
+	const std::string &getFilePath() const override;
 
 	void setFilePathSys(const std::string &path); // System Locale
 	std::string getFilePathSys() const;
@@ -68,8 +70,8 @@ public:
 	void setFilePathUtf8(const std::string &path); // UTF8
 	std::string getFilePathUtf8() const;
 
-	void setFileContent(FileContent *content);
-	FileContent *getFileContent() const;
+	void setFileContent(std::shared_ptr<FileContent> content);
+	std::shared_ptr<FileContent> getFileContent() const;
 
 	void setFileSize(size_t size);
 	size_t getFileSize() const;
@@ -94,8 +96,23 @@ public:
 	bool isEncrypted() const;
 	const std::string exportPlainFile() const;
 
+protected:
+	~FileTransferContent() {
+		if (!mFileKey.empty()) {
+			bctbx_clean(mFileKey.data(), mFileKey.size());
+		}
+	};
+
 private:
-	L_DECLARE_PRIVATE(FileTransferContent);
+	std::string mFileName;
+	std::string mFileUrl;
+	std::string mFilePath;
+	std::shared_ptr<FileContent> mFileContent = nullptr;
+	size_t mFileSize = 0;
+	int mFileDuration = 0;
+	std::vector<char> mFileKey;
+	std::vector<char> mFileAuthTag;
+	ContentType mFileContentType;
 };
 
 LINPHONE_END_NAMESPACE
diff --git a/src/core/core-chat-room.cpp b/src/core/core-chat-room.cpp
index 8a0f78009518e6bc84129d1aef276533fafa553e..b664b2b0bf0e48421b02eccc1709ad2b18e632f1 100644
--- a/src/core/core-chat-room.cpp
+++ b/src/core/core-chat-room.cpp
@@ -700,10 +700,10 @@ list<shared_ptr<AbstractChatRoom>> Core::getChatRooms() const {
 		}
 
 		if (hideChatRoomsFromRemovedProxyConfig) {
-			const bctbx_list_t *it;
+			const bctbx_list_t *it2;
 			bool found = false;
-			for (it = linphone_core_get_proxy_config_list(lc); it != NULL; it = it->next) {
-				LinphoneProxyConfig *cfg = (LinphoneProxyConfig *)it->data;
+			for (it2 = linphone_core_get_proxy_config_list(lc); it2 != nullptr; it2 = it2->next) {
+				auto cfg = (LinphoneProxyConfig *)it2->data;
 				const LinphoneAddress *identityAddr = linphone_proxy_config_get_identity_address(cfg);
 				auto localAddress = Address::toCpp(const_cast<LinphoneAddress *>(identityAddr))->getSharedFromThis();
 				if (localAddress->weakEqual(*chatRoom->getLocalAddress())) {
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 2bb5c2128cce318cb6df3917b9f950307cb94e8e..7b8f70b85bc485fabe864b1f23f75a279bc05a1f 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -344,8 +344,11 @@ void CorePrivate::shutdown() {
 void CorePrivate::uninit() {
 	L_Q();
 
-	if (q->limeX3dhEnabled()) {
-		q->enableLimeX3dh(false);
+	// If we have an encryption engine, destroy it.
+	if (imee != nullptr) {
+		auto listener = dynamic_cast<CoreListener *>(q->getEncryptionEngine());
+		if (listener) unregisterListener(listener);
+		imee.reset();
 	}
 
 	const list<shared_ptr<AbstractChatRoom>> chatRooms = q->getChatRooms();
@@ -1804,12 +1807,12 @@ shared_ptr<CallSession> Core::createOrUpdateConferenceOnServer(const std::shared
 	addressesList.unique();
 
 	if (!addressesList.empty()) {
-		Content content;
-		content.setBodyFromUtf8(Utils::getResourceLists(addressesList));
-		content.setContentType(ContentType::ResourceLists);
-		content.setContentDisposition(ContentDisposition::RecipientList);
+		auto content = Content::create();
+		content->setBodyFromUtf8(Utils::getResourceLists(addressesList));
+		content->setContentType(ContentType::ResourceLists);
+		content->setContentDisposition(ContentDisposition::RecipientList);
 		if (linphone_core_content_encoding_supported(lc, "deflate")) {
-			content.setContentEncoding("deflate");
+			content->setContentEncoding("deflate");
 		}
 
 		L_GET_CPP_PTR_FROM_C_OBJECT(params)->addCustomContent(content);
diff --git a/src/db/main-db.cpp b/src/db/main-db.cpp
index 371ac81d796d56f082155cf515758352fe0d7e60..9668184ea16c4bb441349058239e2003701b2dd0 100644
--- a/src/db/main-db.cpp
+++ b/src/db/main-db.cpp
@@ -350,10 +350,11 @@ void MainDbPrivate::insertContent(long long chatMessageId, const Content &conten
 		    soci::use(chatMessageContentId), soci::use(name), soci::use(size), soci::use(path), soci::use(duration);
 	}
 
-	for (const auto &appData : content.getAppDataMap())
+	for (const auto &property : content.getProperties()) {
 		*session << "INSERT INTO chat_message_content_app_data (chat_message_content_id, name, data) VALUES"
 		            " (:chatMessageContentId, :name, :data)",
-		    soci::use(chatMessageContentId), soci::use(appData.first), soci::use(appData.second);
+		    soci::use(chatMessageContentId), soci::use(property.first), soci::use(property.second.getValue<string>());
+	}
 #endif
 }
 
@@ -1386,7 +1387,7 @@ long long MainDbPrivate::insertConferenceChatMessageEvent(const shared_ptr<Event
 		    soci::use(eventId), soci::use(ephemeralLifetime), soci::use(expireTime.first);
 	}
 
-	for (const Content *content : chatMessage->getContents())
+	for (const auto &content : chatMessage->getContents())
 		insertContent(eventId, *content);
 
 	shared_ptr<AbstractChatRoom> chatRoom(chatMessage->getChatRoom());
@@ -2783,7 +2784,7 @@ void MainDbPrivate::importLegacyHistory(DbSession &inDbSession) {
 
 			const string &text = getValueFromRow<string>(message, LegacyMessageColText, isNull);
 
-			unique_ptr<Content> content;
+			shared_ptr<Content> content;
 			if (contentType == ContentType::FileTransfer) {
 				const string appData = getValueFromRow<string>(message, LegacyMessageColAppData, isNull);
 				if (isNull) {
@@ -2797,12 +2798,12 @@ void MainDbPrivate::importLegacyHistory(DbSession &inDbSession) {
 					continue;
 				}
 				ContentType fileContentType(contentTypeString);
-				content.reset(new FileContent());
+				content = FileContent::create<FileContent>();
 				content->setContentType(fileContentType);
-				content->setAppData("legacy", appData);
+				content->setProperty("legacy", Variant{appData});
 				content->setBodyFromLocale(text);
 			} else {
-				content.reset(new Content());
+				content = Content::create();
 				content->setContentType(contentType);
 				if (contentType == ContentType::PlainText) {
 					if (isNull) {
@@ -4747,7 +4748,7 @@ static void fetchContentAppData(soci::session *session, Content &content, long l
 	soci::statement statement = (session->prepare << query, soci::use(contentId), soci::into(name), soci::into(data));
 	statement.execute();
 	while (statement.fetch())
-		content.setAppData(name, blobToString(data));
+		content.setProperty(name, Variant{blobToString(data)});
 }
 #endif
 
@@ -4771,14 +4772,14 @@ void MainDb::loadChatMessageContents(const shared_ptr<ChatMessage> &chatMessage)
 		for (const auto &row : rows) {
 			ContentType contentType(row.get<string>(2));
 			const long long &contentId = d->dbSession.resolveId(row, 0);
-			Content *content;
+			shared_ptr<Content> content;
 			int bodyEncodingType = row.get<int>(4);
 
 			if (contentType == ContentType::FileTransfer) {
 				hasFileTransferContent = true;
-				content = new FileTransferContent();
+				content = FileTransferContent::create<FileTransferContent>();
 			} else {
-				// 1.1 - Fetch contents' file informations if they exist
+				// 1.1 - Fetch contents' file information if they exist
 				string name;
 				int size;
 				string path;
@@ -4788,14 +4789,14 @@ void MainDb::loadChatMessageContents(const shared_ptr<ChatMessage> &chatMessage)
 				            " WHERE chat_message_content_id = :contentId",
 				    soci::into(name), soci::into(size), soci::into(path), soci::into(duration), soci::use(contentId);
 				if (session->got_data()) {
-					FileContent *fileContent = new FileContent();
+					auto fileContent = FileContent::create<FileContent>();
 					fileContent->setFileName(name);
 					fileContent->setFileSize(size_t(size));
 					fileContent->setFilePath(path);
 					fileContent->setFileDuration(duration);
 					content = fileContent;
 				} else {
-					content = new Content();
+					content = Content::create();
 				}
 			}
 
diff --git a/src/factory/factory.cpp b/src/factory/factory.cpp
index b82652c8a3981888147cff80f4c98a7b3b25cdd3..22e89a390bd81ed78c419fd505aace9b53bf492f 100644
--- a/src/factory/factory.cpp
+++ b/src/factory/factory.cpp
@@ -573,10 +573,10 @@ LinphoneContent *Factory::createContent() const {
 
 LinphoneContent *Factory::createContentFromFile(const std::string &file_path) const {
 	std::string file_name = file_path.substr(file_path.find_last_of("/\\") + 1);
-	FileContent *content = new FileContent();
-	content->setFilePath(file_path);
-	content->setFileName(file_name);
-	return L_GET_C_BACK_PTR(dynamic_cast<Content *>(content));
+	auto content = FileContent::createCObject<FileContent>();
+	linphone_content_set_file_path(content, file_path.c_str());
+	linphone_content_set_name(content, file_name.c_str());
+	return content;
 }
 
 LinphoneBuffer *Factory::createBuffer() const {
@@ -774,7 +774,7 @@ std::shared_ptr<ConferenceInfo> Factory::createConferenceInfo() const {
 #endif // _MSC_VER
 std::shared_ptr<ConferenceInfo> Factory::createConferenceInfoFromIcalendarContent(LinphoneContent *content) const {
 #ifdef HAVE_ADVANCED_IM
-	LinphonePrivate::ContentType contentType = L_GET_CPP_PTR_FROM_C_OBJECT(content)->getContentType();
+	LinphonePrivate::ContentType contentType = Content::toCpp(content)->getContentType();
 	if (!contentType.strongEqual(ContentType::Icalendar)) return nullptr;
 
 	std::string filepath = "";
diff --git a/src/object/app-data-container.cpp b/src/object/app-data-container.cpp
deleted file mode 100644
index 9a8eea95a724a2dd4852090ef32113708e6f679a..0000000000000000000000000000000000000000
--- a/src/object/app-data-container.cpp
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (c) 2010-2022 Belledonne Communications SARL.
- *
- * This file is part of Liblinphone
- * (see https://gitlab.linphone.org/BC/public/liblinphone).
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#include "linphone/utils/utils.h"
-
-#include "app-data-container.h"
-
-// =============================================================================
-
-using namespace std;
-
-LINPHONE_BEGIN_NAMESPACE
-
-// -----------------------------------------------------------------------------
-
-class AppDataContainerPrivate {
-public:
-	shared_ptr<unordered_map<string, string>> appData;
-};
-
-// -----------------------------------------------------------------------------
-
-AppDataContainer::AppDataContainer() : mPrivate(new AppDataContainerPrivate) {
-	L_D();
-	d->appData = make_shared<unordered_map<string, string>>();
-}
-
-AppDataContainer::AppDataContainer(const AppDataContainer &other) : mPrivate(new AppDataContainerPrivate) {
-	L_D();
-	d->appData = other.getPrivate()->appData;
-}
-
-AppDataContainer::~AppDataContainer() {
-	delete mPrivate;
-}
-
-AppDataContainer &AppDataContainer::operator=(const AppDataContainer &other) {
-	L_D();
-	if (this != &other) d->appData = other.getPrivate()->appData;
-	return *this;
-}
-
-const unordered_map<string, string> &AppDataContainer::getAppDataMap() const {
-	L_D();
-	return *d->appData.get();
-}
-
-const string &AppDataContainer::getAppData(const string &name) const {
-	L_D();
-	auto it = d->appData->find(name);
-	return it == d->appData->cend() ? Utils::getEmptyConstRefObject<string>() : it->second;
-}
-
-void AppDataContainer::setAppData(const string &name, const string &appData) {
-	L_D();
-	(*d->appData)[name] = appData;
-}
-
-void AppDataContainer::setAppData(const string &name, string &&appData) {
-	L_D();
-	(*d->appData)[name] = std::move(appData);
-}
-
-LINPHONE_END_NAMESPACE
diff --git a/src/object/app-data-container.h b/src/object/app-data-container.h
deleted file mode 100644
index 45f5d52ffc54dbb155dba4f9690921447b5b19ec..0000000000000000000000000000000000000000
--- a/src/object/app-data-container.h
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Copyright (c) 2010-2022 Belledonne Communications SARL.
- *
- * This file is part of Liblinphone
- * (see https://gitlab.linphone.org/BC/public/liblinphone).
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as
- * published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-
-#ifndef _L_APP_DATA_CONTAINER_H_
-#define _L_APP_DATA_CONTAINER_H_
-
-#include <string>
-#include <unordered_map>
-
-#include "linphone/utils/general.h"
-
-// =============================================================================
-
-LINPHONE_BEGIN_NAMESPACE
-
-class AppDataContainerPrivate;
-
-class LINPHONE_PUBLIC AppDataContainer {
-public:
-	AppDataContainer();
-	AppDataContainer(const AppDataContainer &other);
-	virtual ~AppDataContainer();
-
-	AppDataContainer &operator=(const AppDataContainer &other);
-
-	const std::unordered_map<std::string, std::string> &getAppDataMap() const;
-
-	const std::string &getAppData(const std::string &name) const;
-	void setAppData(const std::string &name, const std::string &appData);
-	void setAppData(const std::string &name, std::string &&appData);
-
-private:
-	AppDataContainerPrivate *mPrivate = nullptr;
-
-	L_DECLARE_PRIVATE(AppDataContainer);
-};
-
-LINPHONE_END_NAMESPACE
-
-#endif // ifndef _L_APP_DATA_CONTAINER_H_
diff --git a/src/object/property-container.cpp b/src/object/property-container.cpp
index b93d699f187a562632a72c34a583b14e86d205ad..92652d44bc3c7a3c86f0d11cc20029cdc5777962 100644
--- a/src/object/property-container.cpp
+++ b/src/object/property-container.cpp
@@ -18,10 +18,9 @@
  * along with this program. If not, see <http://www.gnu.org/licenses/>.
  */
 
-#include <map>
+#include "property-container.h"
 
 #include "bctoolbox/utils.hh"
-#include "property-container.h"
 
 // =============================================================================
 
@@ -74,6 +73,11 @@ void PropertyContainer::setProperty(const string &name, Variant &&value) {
 	mPrivate->properties[name] = std::move(value);
 }
 
+const std::map<std::string, Variant> &PropertyContainer::getProperties() const {
+	if (!mPrivate) return bctoolbox::Utils::getEmptyConstRefObject<std::map<std::string, Variant>>();
+	return mPrivate->properties;
+}
+
 int PropertyContainer::remove(const std::string &name) const {
 	if (mPrivate) {
 		auto it = mPrivate->properties.find(name);
@@ -96,6 +100,7 @@ bool PropertyContainer::hasKey(const std::string &name) const {
 	}
 	return false;
 }
+
 std::ostream &PropertyContainer::toStream(std::ostream &stream) const {
 	for (const auto &p : mPrivate->properties) {
 		stream << p.first << " : ";
@@ -104,4 +109,5 @@ std::ostream &PropertyContainer::toStream(std::ostream &stream) const {
 	}
 	return stream;
 }
+
 LINPHONE_END_NAMESPACE
diff --git a/src/object/property-container.h b/src/object/property-container.h
index eb9c6b0b9aac99cc59659c41481734f3119a22ad..60bf18c4cb1d065bd33d468405838ecbb90c9337 100644
--- a/src/object/property-container.h
+++ b/src/object/property-container.h
@@ -21,6 +21,8 @@
 #ifndef _L_PROPERTY_CONTAINER_H_
 #define _L_PROPERTY_CONTAINER_H_
 
+#include <map>
+
 #include "variant/variant.h"
 
 // =============================================================================
@@ -41,6 +43,8 @@ public:
 	void setProperty(const std::string &name, const Variant &value);
 	void setProperty(const std::string &name, Variant &&value);
 
+	const std::map<std::string, Variant> &getProperties() const;
+
 	int remove(const std::string &name) const;
 
 	void clear();
diff --git a/src/recorder/recorder.cpp b/src/recorder/recorder.cpp
index 4f80688a180dce4723733264ded9b4c1c21fcd21..4c6e4e79398bbccc38195881d42d9b27c7b828c3 100644
--- a/src/recorder/recorder.cpp
+++ b/src/recorder/recorder.cpp
@@ -126,14 +126,14 @@ float Recorder::getCaptureVolume() const {
 	return ms_media_recorder_get_capture_volume(mRecorder);
 }
 
-FileContent *Recorder::createContent() const {
+std::shared_ptr<FileContent> Recorder::createContent() const {
 	LinphoneRecorderState currentState = getState();
 	if (currentState != LinphoneRecorderClosed) {
 		lError() << "Cannot create Content from Recorder that isn't in Closed state, current state is " << currentState;
 		return nullptr;
 	}
 
-	FileContent *fileContent = new FileContent();
+	auto fileContent = FileContent::create<FileContent>();
 	fileContent->setFilePath(mFilePath);
 	fileContent->setContentType(ContentType::VoiceRecording);
 	fileContent->setFileDuration(getDuration());
diff --git a/src/recorder/recorder.h b/src/recorder/recorder.h
index 2055055e82b869cd8239ed941ef414edbd39f754..f121821f9498ce54ab192ecc4476a3c01cd1a873 100644
--- a/src/recorder/recorder.h
+++ b/src/recorder/recorder.h
@@ -54,7 +54,7 @@ public:
 	LinphoneRecorderState getState() const;
 	int getDuration() const;
 	float getCaptureVolume() const;
-	FileContent *createContent() const;
+	std::shared_ptr<FileContent> createContent() const;
 
 	void setParams(std::shared_ptr<RecorderParams> params);
 	std::shared_ptr<const RecorderParams> getParams() const;
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
index 314ca423155de67cd68da8a628b5cb6a2c8e75c0..4ebd0fdd2e6f9061291a655374362921b604d395 100644
--- a/src/utils/utils.cpp
+++ b/src/utils/utils.cpp
@@ -60,7 +60,9 @@ bool Utils::iequals(const string &a, const string &b) {
 
 #ifndef __ANDROID__
 #define TO_STRING_IMPL(TYPE)                                                                                           \
-	string Utils::toString(TYPE val) { return to_string(val); }
+	string Utils::toString(TYPE val) {                                                                                 \
+		return to_string(val);                                                                                         \
+	}
 #else
 #define TO_STRING_IMPL(TYPE)                                                                                           \
 	string Utils::toString(TYPE val) {                                                                                 \
@@ -91,7 +93,9 @@ string Utils::toString(const void *val) {
 // -----------------------------------------------------------------------------
 
 #define STRING_TO_NUMBER_IMPL(TYPE, SUFFIX)                                                                            \
-	TYPE Utils::sto##SUFFIX(const string &str, size_t *idx, int base) { return sto##SUFFIX(str.c_str(), idx, base); }  \
+	TYPE Utils::sto##SUFFIX(const string &str, size_t *idx, int base) {                                                \
+		return sto##SUFFIX(str.c_str(), idx, base);                                                                    \
+	}                                                                                                                  \
 	TYPE Utils::sto##SUFFIX(const char *str, size_t *idx, int base) {                                                  \
 		char *p;                                                                                                       \
 		TYPE v = strto##SUFFIX(str, &p, base);                                                                         \
@@ -100,7 +104,9 @@ string Utils::toString(const void *val) {
 	}
 
 #define STRING_TO_NUMBER_IMPL_BASE_LESS(TYPE, SUFFIX)                                                                  \
-	TYPE Utils::sto##SUFFIX(const string &str, size_t *idx) { return sto##SUFFIX(str.c_str(), idx); }                  \
+	TYPE Utils::sto##SUFFIX(const string &str, size_t *idx) {                                                          \
+		return sto##SUFFIX(str.c_str(), idx);                                                                          \
+	}                                                                                                                  \
 	TYPE Utils::sto##SUFFIX(const char *str, size_t *idx) {                                                            \
 		char *p;                                                                                                       \
 		TYPE v = strto##SUFFIX(str, &p);                                                                               \
diff --git a/src/variant/variant.h b/src/variant/variant.h
index 37e171118a315f84b717d21b54831f4517a5907f..3dcc34dd26beaa81e9f7dda5c6c904bb91526b70 100644
--- a/src/variant/variant.h
+++ b/src/variant/variant.h
@@ -75,7 +75,7 @@ public:
 		return bctoolbox::Utils::getEmptyConstRefObject<T>();
 	}
 
-	bool isValid() {
+	bool isValid() const {
 		return mImplBase != nullptr;
 	}
 	std::ostream &toStream(std::ostream &stream) const {
diff --git a/tester/call_single_tester.c b/tester/call_single_tester.c
index 0a15189419cb6592bfd44d4299d704faf4fd081e..af55b28f27394a7f1f899c065dc22aa13cccd3fb 100644
--- a/tester/call_single_tester.c
+++ b/tester/call_single_tester.c
@@ -4883,7 +4883,7 @@ void record_call(const char *filename, bool_t enableVideo, const char *video_cod
 	LinphoneCallParams *marieParams = NULL;
 	LinphoneCallParams *paulineParams = NULL;
 	LinphoneCall *callInst = NULL;
-	const char **formats, *format;
+	const char **formats, *file_format;
 	char *filepath;
 	int dummy = 0, i;
 	bool_t call_succeeded = FALSE;
@@ -4932,8 +4932,8 @@ void record_call(const char *filename, bool_t enableVideo, const char *video_cod
 
 	formats = linphone_core_get_supported_file_formats(marie->lc);
 
-	for (i = 0, format = formats[0]; format != NULL; i++, format = formats[i]) {
-		char *totalname = ms_strdup_printf("%s.%s", filename, format);
+	for (i = 0, file_format = formats[0]; file_format != NULL; i++, file_format = formats[i]) {
+		char *totalname = ms_strdup_printf("%s.%s", filename, file_format);
 		filepath = bc_tester_file(totalname);
 		ms_free(totalname);
 		remove(filepath);
@@ -4946,7 +4946,7 @@ void record_call(const char *filename, bool_t enableVideo, const char *video_cod
 				ms_message("call_recording(): start recording into %s", filepath);
 				linphone_call_start_recording(callInst);
 			}
-			if (strcmp(format, "mkv") == 0 && enableVideo) {
+			if (strcmp(file_format, "mkv") == 0 && enableVideo) {
 				VideoStream *pauline_vstream =
 				    (VideoStream *)linphone_call_get_stream(pauline_call, LinphoneStreamTypeVideo);
 				/* make sure that Pauline receives a RTCP FIR (Full Intra Request) requested by Marie's recorder.*/
diff --git a/tester/conference-event-tester.cpp b/tester/conference-event-tester.cpp
index f51ca3e651f8effc388c9f685f6e1b1c32a017ed..aa4c874bee3f17f97098e3e3c92905616828d854 100644
--- a/tester/conference-event-tester.cpp
+++ b/tester/conference-event-tester.cpp
@@ -1441,11 +1441,11 @@ void send_first_notify() {
 
 	LocalConferenceEventHandler *localHandler = (L_ATTR_GET(localConf.get(), eventHandler)).get();
 	localConf->setConferenceAddress(addr);
-	Content content = localHandler->createNotifyFullState(NULL);
+	auto content = localHandler->createNotifyFullState(NULL);
 
 	const_cast<ConferenceId &>(tester->handler->getConferenceId()).setPeerAddress(addr);
 
-	tester->handler->notifyReceived(content);
+	tester->handler->notifyReceived(*content);
 
 	BC_ASSERT_STRING_EQUAL(tester->confSubject.c_str(), "A random test subject");
 	BC_ASSERT_EQUAL((int)tester->participants.size(), 2, int, "%d");
@@ -2367,11 +2367,11 @@ void one_to_one_keyword() {
 	localConf->addParticipant(bobAddr);
 	LocalConferenceEventHandler *localHandler = (L_ATTR_GET(localConf.get(), eventHandler)).get();
 	localConf->setConferenceAddress(addr);
-	Content content = localHandler->createNotifyFullState(NULL);
+	auto content = localHandler->createNotifyFullState(NULL);
 
 	const_cast<ConferenceId &>(tester->handler->getConferenceId()).setPeerAddress(addr);
 
-	tester->handler->notifyReceived(content);
+	tester->handler->notifyReceived(*content);
 
 	BC_ASSERT_EQUAL((int)tester->participantDevices.size(), 1, int, "%d");
 	BC_ASSERT_TRUE(tester->participantDevices.find(bobAddr->toString()) != tester->participantDevices.end());
diff --git a/tester/cpim-tester.cpp b/tester/cpim-tester.cpp
index 9686b583e7577843a44fa4aabb26caf790133884..dba06afdd680e46820bd3c8fe2051261068e0180 100644
--- a/tester/cpim-tester.cpp
+++ b/tester/cpim-tester.cpp
@@ -286,7 +286,7 @@ static void cpim_chat_message_modifier_base(bool useMultipart) {
 	shared_ptr<ChatMessage> marieMessage = marieRoom->createChatMessageFromUtf8("Hello CPIM");
 	if (useMultipart) {
 		marieRoom->allowMultipart(true);
-		Content *content = new Content();
+		auto content = Content::create();
 		content->setContentType(ContentType::PlainText);
 		content->setBodyFromUtf8("Hello Part 2");
 		marieMessage->addContent(content);
diff --git a/tester/main-db-tester.cpp b/tester/main-db-tester.cpp
index e138ec49c184d33640789f4e77edfc0c4ecbcc66..dbc9867fa4679ab63201bdb5bbf5966eac7ab0ac 100644
--- a/tester/main-db-tester.cpp
+++ b/tester/main-db-tester.cpp
@@ -312,7 +312,7 @@ static void get_chat_rooms() {
 			BC_ASSERT_PTR_NOT_NULL(lastMessage);
 			BC_ASSERT_PTR_EQUAL(lastMessage, newMessage);
 			mainDb.loadChatMessageContents(lastMessage); // Force read Database
-			for (const Content *content : lastMessage->getContents()) {
+			for (const auto &content : lastMessage->getContents()) {
 				BC_ASSERT_EQUAL(content->getBodyAsUtf8String().compare(utf8Txt), 0, int, "%d");
 			}
 		}
@@ -346,7 +346,7 @@ static void get_chat_rooms() {
 			BC_ASSERT_PTR_EQUAL(lastMessage, newMessage);
 			BC_ASSERT_PTR_NOT_EQUAL(lastMessage, lastMessage2);
 			mainDb.loadChatMessageContents(lastMessage); // Force read Database
-			for (const Content *content : lastMessage->getContents()) {
+			for (const auto &content : lastMessage->getContents()) {
 				BC_ASSERT_EQUAL(content->getBodyAsUtf8String().compare(utf8Txt), 0, int, "%d");
 			}
 		}
diff --git a/tester/message_tester.c b/tester/message_tester.c
index ccf7be1e3ce7f0261a72d102fc5f4cf8a386e263..46adba8750be337ac59ef4f232fccabc81554dec 100644
--- a/tester/message_tester.c
+++ b/tester/message_tester.c
@@ -411,60 +411,6 @@ void text_message_base_with_text_and_forward(LinphoneCoreManager *marie,
 	linphone_chat_message_unref(msg);
 }
 
-void check_reactions(LinphoneChatMessage *message,
-                     size_t expected_reactions_count,
-                     const bctbx_list_t *expected_reactions,
-                     const bctbx_list_t *expected_reactions_from) {
-	bctbx_list_t *reactions = linphone_chat_message_get_reactions(message);
-	bctbx_list_t *reactions_it = reactions;
-	bctbx_list_t *expected_reactions_it = (bctbx_list_t *)expected_reactions;
-	bctbx_list_t *expected_reactions_from_it = (bctbx_list_t *)expected_reactions_from;
-	BC_ASSERT_PTR_NOT_NULL(reactions);
-
-	if (reactions_it) {
-		size_t count = bctbx_list_size(reactions);
-		BC_ASSERT_EQUAL(count, expected_reactions_count, size_t, "%zu");
-		for (size_t i = 0; i < count; i++) {
-			const LinphoneChatMessageReaction *reaction =
-			    (const LinphoneChatMessageReaction *)bctbx_list_get_data(reactions_it);
-			reactions_it = bctbx_list_next(reactions_it);
-
-			const char *expected_reaction = (const char *)bctbx_list_get_data(expected_reactions_it);
-			expected_reactions_it = bctbx_list_next(expected_reactions_it);
-
-			const char *expected_reaction_from = (const char *)bctbx_list_get_data(expected_reactions_from_it);
-			expected_reactions_from_it = bctbx_list_next(expected_reactions_from_it);
-
-			const char *reaction_body = linphone_chat_message_reaction_get_body(reaction);
-			BC_ASSERT_STRING_EQUAL(reaction_body, expected_reaction);
-
-			const LinphoneAddress *from = linphone_chat_message_reaction_get_from_address(reaction);
-			char *address_as_string = linphone_address_as_string_uri_only(from);
-			BC_ASSERT_STRING_EQUAL(address_as_string, expected_reaction_from);
-			bctbx_free(address_as_string);
-		}
-	}
-	bctbx_list_free_with_data(reactions, (bctbx_list_free_func)linphone_chat_message_reaction_unref);
-}
-
-void liblinphone_tester_chat_message_reaction_received(LinphoneChatMessage *msg,
-                                                       const LinphoneChatMessageReaction *reaction) {
-	BC_ASSERT_PTR_NOT_NULL(msg);
-	BC_ASSERT_PTR_NOT_NULL(reaction);
-
-	const LinphoneAddress *address = linphone_chat_message_reaction_get_from_address(reaction);
-	BC_ASSERT_PTR_NOT_NULL(address);
-	const char *body = linphone_chat_message_reaction_get_body(reaction);
-	BC_ASSERT_STRING_EQUAL(body, "👍");
-
-	bctbx_list_t *expected_reaction = bctbx_list_append(NULL, "👍");
-	bctbx_list_t *expected_reaction_from =
-	    bctbx_list_append(NULL, ms_strdup(linphone_address_as_string_uri_only(address)));
-	check_reactions(msg, 1, expected_reaction, expected_reaction_from);
-	bctbx_list_free(expected_reaction);
-	bctbx_list_free_with_data(expected_reaction_from, (bctbx_list_free_func)ms_free);
-}
-
 void text_message_base_with_text(LinphoneCoreManager *marie,
                                  LinphoneCoreManager *pauline,
                                  const char *text,
diff --git a/tester/multipart-tester.cpp b/tester/multipart-tester.cpp
index e8fce5ae7b8f29f8ce87d22464183fc19a5786c8..61f349f8419dd1139fc0d93a0ff2f2cf63389141 100644
--- a/tester/multipart-tester.cpp
+++ b/tester/multipart-tester.cpp
@@ -155,14 +155,14 @@ static void chat_message_multipart_modifier_base(bool first_file_transfer,
 	shared_ptr<ChatMessage> marieMessage = marieRoom->createChatMessage();
 	if (first_file_transfer) {
 		char *send_filepath = bc_tester_res("sounds/sintel_trailer_opus_h264.mkv");
-		FileContent *content = new FileContent();
+		auto content = FileContent::create<FileContent>();
 		content->setContentType(ContentType("video/mkv"));
 		content->setFilePath(send_filepath);
 		content->setFileName("sintel_trailer_opus_h264.mkv");
 		marieMessage->addContent(content);
 		bc_free(send_filepath);
 	} else {
-		Content *content = new Content();
+		auto content = Content::create();
 		content->setContentType(ContentType::PlainText);
 		content->setBodyFromUtf8("Hello part 1");
 		marieMessage->addContent(content);
@@ -170,21 +170,21 @@ static void chat_message_multipart_modifier_base(bool first_file_transfer,
 
 	if (second_file_transfer) {
 		char *send_filepath = bc_tester_res("vcards/vcards.vcf");
-		FileContent *content = new FileContent();
+		auto content = FileContent::create<FileContent>();
 		content->setContentType(ContentType("file/vcf"));
 		content->setFilePath(send_filepath);
 		content->setFileName("vcards.vcf");
 		marieMessage->addContent(content);
 		bc_free(send_filepath);
 	} else {
-		Content *content = new Content();
+		auto content = Content::create();
 		content->setContentType(ContentType::PlainText);
 		content->setBodyFromUtf8("Hello part 2");
 		marieMessage->addContent(content);
 	}
 
 	if (third_content) {
-		Content *content = new Content();
+		auto content = Content::create();
 		content->setContentType(ContentType::PlainText);
 		content->setBodyFromUtf8("Hello part 3");
 		marieMessage->addContent(content);