/* * c-chat-message.cpp * Copyright (C) 2017 Belledonne Communications SARL * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "linphone/chat.h" #include "linphone/wrapper_utils.h" #include "linphone/utils/utils.h" #include "ortp/b64.h" // TODO: Remove me later. #include "private.h" #include "chat/chat-room.h" #include "chat/chat-room-p.h" #include "chat/real-time-text-chat-room-p.h" #include "content/content-type.h" #include "c-wrapper/c-tools.h" #include "chat/chat-message.h" #include "chat/chat-message-p.h" // ============================================================================= #define GET_CPP_PTR(obj) L_GET_CPP_PTR_FROM_C_STRUCT(obj, ChatMessage, ChatMessage) #define GET_CPP_PRIVATE_PTR(obj) L_GET_PRIVATE_FROM_C_STRUCT(obj, ChatMessage, ChatMessage) using namespace std; static void _linphone_chat_message_constructor (LinphoneChatMessage *msg); static void _linphone_chat_message_destructor (LinphoneChatMessage *msg); L_DECLARE_C_STRUCT_IMPL_WITH_XTORS(ChatMessage, ChatMessage, chat_message, _linphone_chat_message_constructor, _linphone_chat_message_destructor, LinphoneChatMessageCbs *cbs; LinphoneChatRoom* chat_room; LinphoneErrorInfo *ei; LinphoneChatMessageDir dir; char* message; void* message_userdata; char* appdata; char* external_body_url; LinphoneAddress *from; LinphoneAddress *to; time_t time; SalCustomHeader *sal_custom_headers; LinphoneChatMessageState state; bool_t is_read; unsigned int storage_id; char *message_id; SalOp *op; LinphoneContent *file_transfer_information; //< used to store file transfer information when the message is of file transfer type char *content_type; //< is used to specified the type of message to be sent, used only for file transfer message bool_t to_be_stored; belle_http_request_t *http_request; //< keep a reference to the http_request in case of file transfer in order to be able to cancel the transfer belle_http_request_listener_t *http_listener; // our listener, only owned by us char *file_transfer_filepath; unsigned long bg_task_id; bool_t is_secured; LinphoneChatMessageStateChangedCb message_state_changed_cb; void* message_state_changed_user_data; ) static void _linphone_chat_message_constructor (LinphoneChatMessage *msg) { msg->cbs = linphone_chat_message_cbs_new(); } static void _linphone_chat_message_destructor (LinphoneChatMessage *msg) { linphone_chat_message_cbs_unref(msg->cbs); msg->cbs = nullptr; } // ============================================================================= // Reference and user data handling functions. // ============================================================================= LinphoneChatMessage *linphone_chat_message_ref (LinphoneChatMessage *msg) { belle_sip_object_ref(msg); return msg; } void linphone_chat_message_unref (LinphoneChatMessage *msg) { belle_sip_object_unref(msg); } void * linphone_chat_message_get_user_data (const LinphoneChatMessage *msg) { return L_GET_USER_DATA_FROM_C_STRUCT(msg, ChatMessage); } void linphone_chat_message_set_user_data (LinphoneChatMessage *msg, void *ud) { L_SET_USER_DATA_FROM_C_STRUCT(msg, ud, ChatMessage); } // ============================================================================= // Getter and setters // ============================================================================= const char *linphone_chat_message_get_external_body_url(const LinphoneChatMessage *msg) { return msg->external_body_url; } void linphone_chat_message_set_external_body_url(LinphoneChatMessage *msg, const char *url) { if (msg->external_body_url) { ms_free(msg->external_body_url); } msg->external_body_url = url ? ms_strdup(url) : NULL; } time_t linphone_chat_message_get_time(const LinphoneChatMessage *msg) { return msg->time; } void linphone_chat_message_set_time(LinphoneChatMessage *msg, time_t time) { msg->time = time; } void linphone_chat_message_set_is_secured(LinphoneChatMessage *msg, bool_t secured) { msg->is_secured = secured; } bool_t linphone_chat_message_is_secured(LinphoneChatMessage *msg) { return msg->is_secured; } bool_t linphone_chat_message_is_outgoing(LinphoneChatMessage *msg) { return msg->dir == LinphoneChatMessageOutgoing; } void linphone_chat_message_set_incoming(LinphoneChatMessage *msg) { msg->dir = LinphoneChatMessageIncoming; } void linphone_chat_message_set_outgoing(LinphoneChatMessage *msg) { msg->dir = LinphoneChatMessageOutgoing; } void linphone_chat_message_set_from_address(LinphoneChatMessage *msg, const LinphoneAddress *from) { if (msg->from) linphone_address_unref(msg->from); msg->from = linphone_address_clone(from); } const LinphoneAddress *linphone_chat_message_get_from_address(const LinphoneChatMessage *msg) { return msg->from; } void linphone_chat_message_set_to_address(LinphoneChatMessage *msg, const LinphoneAddress *to) { if (msg->to) linphone_address_unref(msg->to); msg->to = linphone_address_clone(to); } const LinphoneAddress *linphone_chat_message_get_to_address(const LinphoneChatMessage *msg) { if (msg->to) return msg->to; if (msg->dir == LinphoneChatMessageOutgoing) { return linphone_chat_room_get_peer_address(msg->chat_room); } return NULL; } void linphone_chat_message_set_message_state_changed_cb(LinphoneChatMessage* msg, LinphoneChatMessageStateChangedCb cb) { msg->message_state_changed_cb = cb; } void linphone_chat_message_set_message_state_changed_cb_user_data(LinphoneChatMessage* msg, void *user_data) { msg->message_state_changed_user_data = user_data; } void * linphone_chat_message_get_message_state_changed_cb_user_data(LinphoneChatMessage* msg) { return msg->message_state_changed_user_data; } const char * linphone_chat_message_get_content_type(const LinphoneChatMessage *msg) { return msg->content_type; } void linphone_chat_message_set_content_type(LinphoneChatMessage *msg, const char *content_type) { if (msg->content_type) { ms_free(msg->content_type); } msg->content_type = content_type ? ms_strdup(content_type) : NULL; } const char *linphone_chat_message_get_text(const LinphoneChatMessage *msg) { return msg->message; } int linphone_chat_message_set_text(LinphoneChatMessage *msg, const char* text) { if (msg->message) ms_free(msg->message); if (text) msg->message = ms_strdup(text); else msg->message = NULL; return 0; } unsigned int linphone_chat_message_get_storage_id(LinphoneChatMessage *msg) { return msg->storage_id; } void linphone_chat_message_set_state(LinphoneChatMessage *msg, LinphoneChatMessageState state) { /* do not invoke callbacks on orphan messages */ if (state != msg->state && msg->chat_room != NULL) { if (((msg->state == LinphoneChatMessageStateDisplayed) || (msg->state == LinphoneChatMessageStateDeliveredToUser)) && ((state == LinphoneChatMessageStateDeliveredToUser) || (state == LinphoneChatMessageStateDelivered) || (state == LinphoneChatMessageStateNotDelivered))) { /* If the message has been displayed or delivered to user we must not go back to the delivered or not delivered state. */ return; } ms_message("Chat message %p: moving from state %s to %s", msg, linphone_chat_message_state_to_string(msg->state), linphone_chat_message_state_to_string(state)); msg->state = state; if (msg->message_state_changed_cb) { msg->message_state_changed_cb(msg, msg->state, msg->message_state_changed_user_data); } if (linphone_chat_message_cbs_get_msg_state_changed(msg->cbs)) { linphone_chat_message_cbs_get_msg_state_changed(msg->cbs)(msg, msg->state); } } } const char* linphone_chat_message_get_message_id(const LinphoneChatMessage *msg) { return msg->message_id; } void linphone_chat_message_set_message_id(LinphoneChatMessage *msg, char *id) { msg->message_id = id; } void linphone_chat_message_set_is_read(LinphoneChatMessage *msg, bool_t is_read) { msg->is_read = is_read; } void linphone_chat_message_set_storage_id(LinphoneChatMessage *msg, unsigned int id) { msg->storage_id = id; } const char *linphone_chat_message_get_appdata(const LinphoneChatMessage *msg) { return msg->appdata; } void linphone_chat_message_set_appdata(LinphoneChatMessage *msg, const char *data) { if (msg->appdata) { ms_free(msg->appdata); } msg->appdata = data ? ms_strdup(data) : NULL; linphone_chat_message_store_appdata(msg); } SalCustomHeader * linphone_chat_message_get_sal_custom_headers(const LinphoneChatMessage *msg) { return msg->sal_custom_headers; } void linphone_chat_message_set_sal_custom_headers(LinphoneChatMessage *msg, SalCustomHeader *header) { msg->sal_custom_headers = header; } belle_http_request_t * linphone_chat_message_get_http_request(LinphoneChatMessage *msg) { return msg->http_request; } void linphone_chat_message_set_http_request(LinphoneChatMessage *msg, belle_http_request_t *request) { msg->http_request = request; } void linphone_chat_message_set_file_transfer_information(LinphoneChatMessage *msg, LinphoneContent *content) { msg->file_transfer_information = content; } LinphoneChatMessageDir linphone_chat_message_get_direction(const LinphoneChatMessage *msg) { return msg->dir; } LinphoneChatRoom *linphone_chat_message_get_chat_room(const LinphoneChatMessage *msg) { return msg->chat_room; } SalOp * linphone_chat_message_get_sal_op(const LinphoneChatMessage *msg) { return msg->op; } void linphone_chat_message_set_sal_op(LinphoneChatMessage *msg, SalOp *op) { msg->op = op; } void linphone_chat_message_set_chat_room(LinphoneChatMessage *msg, LinphoneChatRoom *room) { msg->chat_room = room; } const char *linphone_chat_message_get_file_transfer_filepath(LinphoneChatMessage *msg) { return msg->file_transfer_filepath; } // ============================================================================= void linphone_chat_message_update_state(LinphoneChatMessage *msg, LinphoneChatMessageState new_state) { linphone_chat_message_set_state(msg, new_state); linphone_chat_message_store_state(msg); if (msg->state == LinphoneChatMessageStateDelivered || msg->state == LinphoneChatMessageStateNotDelivered) { L_GET_PRIVATE_FROM_C_STRUCT(msg->chat_room, ChatRoom)->moveTransientMessageToWeakMessages(msg); } } void _linphone_chat_message_resend(LinphoneChatMessage *msg, bool_t ref_msg) { LinphoneChatMessageState state = linphone_chat_message_get_state(msg); LinphoneChatRoom *cr; if (state != LinphoneChatMessageStateNotDelivered) { ms_warning("Cannot resend chat message in state %s", linphone_chat_message_state_to_string(state)); return; } cr = linphone_chat_message_get_chat_room(msg); if (ref_msg) linphone_chat_message_ref(msg); L_GET_CPP_PTR_FROM_C_STRUCT(cr, ChatRoom)->sendMessage(msg); } void linphone_chat_message_resend(LinphoneChatMessage *msg) { _linphone_chat_message_resend(msg, FALSE); } void linphone_chat_message_resend_2(LinphoneChatMessage *msg) { _linphone_chat_message_resend(msg, TRUE); } static char *linphone_chat_message_create_imdn_xml(LinphoneChatMessage *cm, ImdnType imdn_type, LinphoneReason reason) { xmlBufferPtr buf; xmlTextWriterPtr writer; int err; char *content = NULL; char *datetime = NULL; const char *message_id; /* Check that the chat message has a message id */ message_id = linphone_chat_message_get_message_id(cm); if (message_id == NULL) return NULL; buf = xmlBufferCreate(); if (buf == NULL) { ms_error("Error creating the XML buffer"); return content; } writer = xmlNewTextWriterMemory(buf, 0); if (writer == NULL) { ms_error("Error creating the XML writer"); return content; } datetime = linphone_timestamp_to_rfc3339_string(linphone_chat_message_get_time(cm)); err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL); if (err >= 0) { err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"imdn", (const xmlChar *)"urn:ietf:params:xml:ns:imdn"); } if ((err >= 0) && (reason != LinphoneReasonNone)) { err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"linphoneimdn", NULL, (const xmlChar *)"http://www.linphone.org/xsds/imdn.xsd"); } if (err >= 0) { err = xmlTextWriterWriteElement(writer, (const xmlChar *)"message-id", (const xmlChar *)message_id); } if (err >= 0) { err = xmlTextWriterWriteElement(writer, (const xmlChar *)"datetime", (const xmlChar *)datetime); } if (err >= 0) { if (imdn_type == ImdnTypeDelivery) { err = xmlTextWriterStartElement(writer, (const xmlChar *)"delivery-notification"); } else { err = xmlTextWriterStartElement(writer, (const xmlChar *)"display-notification"); } } if (err >= 0) { err = xmlTextWriterStartElement(writer, (const xmlChar *)"status"); } if (err >= 0) { if (reason == LinphoneReasonNone) { if (imdn_type == ImdnTypeDelivery) { err = xmlTextWriterStartElement(writer, (const xmlChar *)"delivered"); } else { err = xmlTextWriterStartElement(writer, (const xmlChar *)"displayed"); } } else { err = xmlTextWriterStartElement(writer, (const xmlChar *)"error"); } } if (err >= 0) { /* Close the "delivered", "displayed" or "error" element. */ err = xmlTextWriterEndElement(writer); } if ((err >= 0) && (reason != LinphoneReasonNone)) { err = xmlTextWriterStartElementNS(writer, (const xmlChar *)"linphoneimdn", (const xmlChar *)"reason", NULL); if (err >= 0) { char codestr[16]; snprintf(codestr, 16, "%d", linphone_reason_to_error_code(reason)); err = xmlTextWriterWriteAttribute(writer, (const xmlChar *)"code", (const xmlChar *)codestr); } if (err >= 0) { err = xmlTextWriterWriteString(writer, (const xmlChar *)linphone_reason_to_string(reason)); } if (err >= 0) { err = xmlTextWriterEndElement(writer); } } if (err >= 0) { /* Close the "status" element. */ err = xmlTextWriterEndElement(writer); } if (err >= 0) { /* Close the "delivery-notification" or "display-notification" element. */ err = xmlTextWriterEndElement(writer); } if (err >= 0) { /* Close the "imdn" element. */ err = xmlTextWriterEndElement(writer); } if (err >= 0) { err = xmlTextWriterEndDocument(writer); } if (err > 0) { /* xmlTextWriterEndDocument returns the size of the content. */ content = ms_strdup((char *)buf->content); } xmlFreeTextWriter(writer); xmlBufferFree(buf); ms_free(datetime); return content; } void linphone_chat_message_send_imdn(LinphoneChatMessage *cm, ImdnType imdn_type, LinphoneReason reason) { char *content = linphone_chat_message_create_imdn_xml(cm, imdn_type, reason); if (content) { L_GET_PRIVATE_FROM_C_STRUCT(linphone_chat_message_get_chat_room(cm), ChatRoom)->sendImdn(content, reason); ms_free(content); } } void linphone_chat_message_send_delivery_notification(LinphoneChatMessage *cm, LinphoneReason reason) { LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(cm); LinphoneCore *lc = linphone_chat_room_get_core(cr); LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc); if (linphone_im_notif_policy_get_send_imdn_delivered(policy) == TRUE) { linphone_chat_message_send_imdn(cm, ImdnTypeDelivery, reason); } } void linphone_chat_message_send_display_notification(LinphoneChatMessage *cm) { LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(cm); LinphoneCore *lc = linphone_chat_room_get_core(cr); LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc); if (linphone_im_notif_policy_get_send_imdn_displayed(policy) == TRUE) { linphone_chat_message_send_imdn(cm, ImdnTypeDisplay, LinphoneReasonNone); } }LinphoneStatus linphone_chat_message_put_char(LinphoneChatMessage *msg, uint32_t character) { LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(msg); if (linphone_core_realtime_text_enabled(linphone_chat_room_get_core(cr))) { std::shared_ptr rttcr = std::static_pointer_cast(L_GET_CPP_PTR_FROM_C_STRUCT(cr, ChatRoom)); LinphoneCall *call = rttcr->getCall(); LinphoneCore *lc = rttcr->getCore(); const uint32_t new_line = 0x2028; const uint32_t crlf = 0x0D0A; const uint32_t lf = 0x0A; if (!call || !linphone_call_get_stream(call, LinphoneStreamTypeText)) { return -1; } if (character == new_line || character == crlf || character == lf) { if (lc && lp_config_get_int(lc->config, "misc", "store_rtt_messages", 1) == 1) { ms_debug("New line sent, forge a message with content %s", msg->message); msg->time = ms_time(0); msg->state = LinphoneChatMessageStateDisplayed; msg->dir = LinphoneChatMessageOutgoing; if (msg->from) linphone_address_unref(msg->from); msg->from = linphone_address_new(linphone_core_get_identity(lc)); msg->storage_id = linphone_chat_message_store(msg); ms_free(msg->message); msg->message = NULL; } } else { char *value = LinphonePrivate::Utils::utf8ToChar(character); msg->message = ms_strcat_printf(msg->message, value); ms_debug("Sent RTT character: %s (%lu), pending text is %s", value, (unsigned long)character, msg->message); delete value; } text_stream_putchar32(reinterpret_cast(linphone_call_get_stream(call, LinphoneStreamTypeText)), character); return 0; } return -1; } const char *linphone_chat_message_state_to_string(const LinphoneChatMessageState state) { switch (state) { case LinphoneChatMessageStateIdle: return "LinphoneChatMessageStateIdle"; case LinphoneChatMessageStateInProgress: return "LinphoneChatMessageStateInProgress"; case LinphoneChatMessageStateDelivered: return "LinphoneChatMessageStateDelivered"; case LinphoneChatMessageStateNotDelivered: return "LinphoneChatMessageStateNotDelivered"; case LinphoneChatMessageStateFileTransferError: return "LinphoneChatMessageStateFileTransferError"; case LinphoneChatMessageStateFileTransferDone: return "LinphoneChatMessageStateFileTransferDone "; case LinphoneChatMessageStateDeliveredToUser: return "LinphoneChatMessageStateDeliveredToUser"; case LinphoneChatMessageStateDisplayed: return "LinphoneChatMessageStateDisplayed"; } return NULL; } const LinphoneAddress *linphone_chat_message_get_peer_address(LinphoneChatMessage *msg) { return linphone_chat_room_get_peer_address(msg->chat_room); } bool_t linphone_chat_message_is_file_transfer(const LinphoneChatMessage *msg) { return LinphonePrivate::ContentType::isFileTransfer(msg->content_type); } bool_t linphone_chat_message_is_text(const LinphoneChatMessage *msg) { return LinphonePrivate::ContentType::isText(msg->content_type); } bool_t linphone_chat_message_get_to_be_stored(const LinphoneChatMessage *msg) { return msg->to_be_stored; } void linphone_chat_message_set_to_be_stored(LinphoneChatMessage *msg, bool_t to_be_stored) { msg->to_be_stored = to_be_stored; } LinphoneAddress *linphone_chat_message_get_local_address(const LinphoneChatMessage *msg) { return msg->dir == LinphoneChatMessageOutgoing ? msg->from : msg->to; } LinphoneChatMessageState linphone_chat_message_get_state(const LinphoneChatMessage *msg) { return msg->state; } void linphone_chat_message_add_custom_header(LinphoneChatMessage *msg, const char *header_name, const char *header_value) { msg->sal_custom_headers = sal_custom_header_append(msg->sal_custom_headers, header_name, header_value); } const char *linphone_chat_message_get_custom_header(LinphoneChatMessage *msg, const char *header_name) { return sal_custom_header_find(msg->sal_custom_headers, header_name); } void linphone_chat_message_remove_custom_header(LinphoneChatMessage *msg, const char *header_name) { msg->sal_custom_headers = sal_custom_header_remove(msg->sal_custom_headers, header_name); } bool_t linphone_chat_message_is_read(LinphoneChatMessage *msg) { LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(msg); LinphoneCore *lc = linphone_chat_room_get_core(cr); LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc); if ((linphone_im_notif_policy_get_recv_imdn_displayed(policy) == TRUE) && (msg->state == LinphoneChatMessageStateDisplayed)) return TRUE; if ((linphone_im_notif_policy_get_recv_imdn_delivered(policy) == TRUE) && (msg->state == LinphoneChatMessageStateDeliveredToUser || msg->state == LinphoneChatMessageStateDisplayed)) return TRUE; return (msg->state == LinphoneChatMessageStateDelivered || msg->state == LinphoneChatMessageStateDisplayed || msg->state == LinphoneChatMessageStateDeliveredToUser); } LinphoneChatMessage *linphone_chat_message_clone(const LinphoneChatMessage *msg) { LinphoneChatMessage *new_message = linphone_chat_room_create_message(msg->chat_room, msg->message); if (msg->external_body_url) new_message->external_body_url = ms_strdup(msg->external_body_url); if (msg->appdata) new_message->appdata = ms_strdup(msg->appdata); new_message->message_state_changed_cb = msg->message_state_changed_cb; new_message->message_state_changed_user_data = msg->message_state_changed_user_data; new_message->message_userdata = msg->message_userdata; new_message->time = msg->time; new_message->state = msg->state; new_message->storage_id = msg->storage_id; if (msg->from) new_message->from = linphone_address_clone(msg->from); if (msg->file_transfer_filepath) new_message->file_transfer_filepath = ms_strdup(msg->file_transfer_filepath); if (msg->file_transfer_information) new_message->file_transfer_information = linphone_content_copy(msg->file_transfer_information); return new_message; } void linphone_chat_message_deactivate(LinphoneChatMessage *msg){ if (msg->file_transfer_information != NULL) { _linphone_chat_message_cancel_file_transfer(msg, FALSE); } /*mark the chat msg as orphan (it has no chat room anymore)*/ msg->chat_room = NULL; } void linphone_chat_message_release(LinphoneChatMessage *msg) { linphone_chat_message_deactivate(msg); linphone_chat_message_unref(msg); } const LinphoneErrorInfo *linphone_chat_message_get_error_info(const LinphoneChatMessage *msg) { if (!msg->ei) ((LinphoneChatMessage*)msg)->ei = linphone_error_info_new(); /*let's do it mutable*/ linphone_error_info_from_sal_op(msg->ei, msg->op); return msg->ei; } LinphoneReason linphone_chat_message_get_reason(LinphoneChatMessage *msg) { return linphone_error_info_get_reason(linphone_chat_message_get_error_info(msg)); } LinphoneChatMessageCbs *linphone_chat_message_get_callbacks(const LinphoneChatMessage *msg) { return msg->cbs; } static bool_t file_transfer_in_progress_and_valid(LinphoneChatMessage* msg) { return (linphone_chat_message_get_chat_room(msg) && linphone_chat_room_get_core(linphone_chat_message_get_chat_room(msg)) && linphone_chat_message_get_http_request(msg) && !belle_http_request_is_cancelled(linphone_chat_message_get_http_request(msg))); } static void _release_http_request(LinphoneChatMessage* msg) { if (linphone_chat_message_get_http_request(msg)) { belle_sip_object_unref(msg->http_request); msg->http_request = NULL; if (msg->http_listener){ belle_sip_object_unref(msg->http_listener); msg->http_listener = NULL; // unhold the reference that the listener was holding on the message linphone_chat_message_unref(msg); } } } static void linphone_chat_message_process_io_error_upload(void *data, const belle_sip_io_error_event_t *event) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; ms_error("I/O Error during file upload of msg [%p]", msg); linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered); _release_http_request(msg); linphone_chat_room_remove_transient_message(msg->chat_room, msg); linphone_chat_message_unref(msg); } static void linphone_chat_message_process_auth_requested_upload(void *data, belle_sip_auth_event_t *event) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; ms_error("Error during file upload: auth requested for msg [%p]", msg); linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered); _release_http_request(msg); linphone_chat_room_remove_transient_message(msg->chat_room, msg); linphone_chat_message_unref(msg); } static void linphone_chat_message_process_io_error_download(void *data, const belle_sip_io_error_event_t *event) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; ms_error("I/O Error during file download msg [%p]", msg); linphone_chat_message_update_state(msg, LinphoneChatMessageStateFileTransferError); _release_http_request(msg); } static void linphone_chat_message_process_auth_requested_download(void *data, belle_sip_auth_event_t *event) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; ms_error("Error during file download : auth requested for msg [%p]", msg); linphone_chat_message_update_state(msg, LinphoneChatMessageStateFileTransferError); _release_http_request(msg); } static void linphone_chat_message_file_transfer_on_progress(belle_sip_body_handler_t *bh, belle_sip_message_t *m, void *data, size_t offset, size_t total) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; if (!file_transfer_in_progress_and_valid(msg)) { ms_warning("Cancelled request for %s msg [%p], ignoring %s", msg->chat_room?"":"ORPHAN", msg, __FUNCTION__); _release_http_request(msg); return; } if (linphone_chat_message_cbs_get_file_transfer_progress_indication(msg->cbs)) { linphone_chat_message_cbs_get_file_transfer_progress_indication(msg->cbs)( msg, msg->file_transfer_information, offset, total); } else { /* Legacy: call back given by application level */ linphone_core_notify_file_transfer_progress_indication(linphone_chat_room_get_core(msg->chat_room), msg, msg->file_transfer_information, offset, total); } } static int on_send_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t *m, void *data, size_t offset, uint8_t *buffer, size_t *size) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; LinphoneCore *lc = NULL; LinphoneImEncryptionEngine *imee = NULL; int retval = -1; if (!file_transfer_in_progress_and_valid(msg)) { if (msg->http_request) { ms_warning("Cancelled request for %s msg [%p], ignoring %s", msg->chat_room?"":"ORPHAN", msg, __FUNCTION__); _release_http_request(msg); } return BELLE_SIP_STOP; } lc = linphone_chat_room_get_core(msg->chat_room); /* if we've not reach the end of file yet, ask for more data */ /* in case of file body handler, won't be called */ if (msg->file_transfer_filepath == NULL && offset < linphone_content_get_size(msg->file_transfer_information)) { /* get data from call back */ LinphoneChatMessageCbsFileTransferSendCb file_transfer_send_cb = linphone_chat_message_cbs_get_file_transfer_send(msg->cbs); if (file_transfer_send_cb) { LinphoneBuffer *lb = file_transfer_send_cb(msg, msg->file_transfer_information, offset, *size); if (lb == NULL) { *size = 0; } else { *size = linphone_buffer_get_size(lb); memcpy(buffer, linphone_buffer_get_content(lb), *size); linphone_buffer_unref(lb); } } else { /* Legacy */ linphone_core_notify_file_transfer_send(lc, msg, msg->file_transfer_information, (char *)buffer, size); } } imee = linphone_core_get_im_encryption_engine(lc); if (imee) { LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); LinphoneImEncryptionEngineCbsUploadingFileCb cb_process_uploading_file = linphone_im_encryption_engine_cbs_get_process_uploading_file(imee_cbs); if (cb_process_uploading_file) { size_t max_size = *size; uint8_t *encrypted_buffer = (uint8_t *)ms_malloc0(max_size); retval = cb_process_uploading_file(imee, msg, offset, (const uint8_t *)buffer, size, encrypted_buffer); if (retval == 0) { if (*size > max_size) { ms_error("IM encryption engine process upload file callback returned a size bigger than the size of the buffer, so it will be truncated !"); *size = max_size; } memcpy(buffer, encrypted_buffer, *size); } ms_free(encrypted_buffer); } } return retval <= 0 ? BELLE_SIP_CONTINUE : BELLE_SIP_STOP; } static void on_send_end(belle_sip_user_body_handler_t *bh, void *data) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; LinphoneCore *lc = linphone_chat_room_get_core(msg->chat_room); LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc); if (imee) { LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); LinphoneImEncryptionEngineCbsUploadingFileCb cb_process_uploading_file = linphone_im_encryption_engine_cbs_get_process_uploading_file(imee_cbs); if (cb_process_uploading_file) { cb_process_uploading_file(imee, msg, 0, NULL, NULL, NULL); } } } static void file_upload_end_background_task(LinphoneChatMessage *obj){ if (obj->bg_task_id){ ms_message("channel [%p]: ending file upload background task with id=[%lx].",obj,obj->bg_task_id); sal_end_background_task(obj->bg_task_id); obj->bg_task_id=0; } } static void file_upload_background_task_ended(LinphoneChatMessage *obj){ ms_warning("channel [%p]: file upload background task has to be ended now, but work isn't finished.",obj); file_upload_end_background_task(obj); } static void file_upload_begin_background_task(LinphoneChatMessage *obj){ if (obj->bg_task_id==0){ obj->bg_task_id=sal_begin_background_task("file transfer upload",(void (*)(void*))file_upload_background_task_ended, obj); if (obj->bg_task_id) ms_message("channel [%p]: starting file upload background task with id=[%lx].",obj,obj->bg_task_id); } } static void linphone_chat_message_process_response_from_post_file(void *data, const belle_http_response_event_t *event) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; if (msg->http_request && !file_transfer_in_progress_and_valid(msg)) { ms_warning("Cancelled request for %s msg [%p], ignoring %s", msg->chat_room?"":"ORPHAN", msg, __FUNCTION__); _release_http_request(msg); return; } /* check the answer code */ if (event->response) { int code = belle_http_response_get_status_code(event->response); if (code == 204) { /* this is the reply to the first post to the server - an empty msg */ /* start uploading the file */ belle_sip_multipart_body_handler_t *bh; char *first_part_header; belle_sip_body_handler_t *first_part_bh; bool_t is_file_encryption_enabled = FALSE; LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(linphone_chat_room_get_core(msg->chat_room)); if (imee) { LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); LinphoneImEncryptionEngineCbsIsEncryptionEnabledForFileTransferCb is_encryption_enabled_for_file_transfer_cb = linphone_im_encryption_engine_cbs_get_is_encryption_enabled_for_file_transfer(imee_cbs); if (is_encryption_enabled_for_file_transfer_cb) { is_file_encryption_enabled = is_encryption_enabled_for_file_transfer_cb(imee, msg->chat_room); } } /* shall we encrypt the file */ if (is_file_encryption_enabled) { LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); LinphoneImEncryptionEngineCbsGenerateFileTransferKeyCb generate_file_transfer_key_cb = linphone_im_encryption_engine_cbs_get_generate_file_transfer_key(imee_cbs); if (generate_file_transfer_key_cb) { generate_file_transfer_key_cb(imee, msg->chat_room, msg); } /* temporary storage for the Content-disposition header value : use a generic filename to not leak it * Actual filename stored in msg->file_transfer_information->name will be set in encrypted msg * sended to the */ first_part_header = belle_sip_strdup_printf("form-data; name=\"File\"; filename=\"filename.txt\""); } else { /* temporary storage for the Content-disposition header value */ first_part_header = belle_sip_strdup_printf("form-data; name=\"File\"; filename=\"%s\"", linphone_content_get_name(msg->file_transfer_information)); } /* create a user body handler to take care of the file and add the content disposition and content-type * headers */ first_part_bh = (belle_sip_body_handler_t *)belle_sip_user_body_handler_new( linphone_content_get_size(msg->file_transfer_information), linphone_chat_message_file_transfer_on_progress, NULL, NULL, on_send_body, on_send_end, msg); if (msg->file_transfer_filepath != NULL) { belle_sip_user_body_handler_t *body_handler = (belle_sip_user_body_handler_t *)first_part_bh; first_part_bh = (belle_sip_body_handler_t *)belle_sip_file_body_handler_new(msg->file_transfer_filepath, NULL, msg); // No need to add again the callback for progression, otherwise it will be called twice linphone_content_set_size(msg->file_transfer_information, belle_sip_file_body_handler_get_file_size((belle_sip_file_body_handler_t *)first_part_bh)); belle_sip_file_body_handler_set_user_body_handler((belle_sip_file_body_handler_t *)first_part_bh, body_handler); } else if (linphone_content_get_buffer(msg->file_transfer_information) != NULL) { first_part_bh = (belle_sip_body_handler_t *)belle_sip_memory_body_handler_new_from_buffer( linphone_content_get_buffer(msg->file_transfer_information), linphone_content_get_size(msg->file_transfer_information), linphone_chat_message_file_transfer_on_progress, msg); } belle_sip_body_handler_add_header(first_part_bh, belle_sip_header_create("Content-disposition", first_part_header)); belle_sip_free(first_part_header); belle_sip_body_handler_add_header(first_part_bh, (belle_sip_header_t *)belle_sip_header_content_type_create( linphone_content_get_type(msg->file_transfer_information), linphone_content_get_subtype(msg->file_transfer_information))); /* insert it in a multipart body handler which will manage the boundaries of multipart msg */ bh = belle_sip_multipart_body_handler_new(linphone_chat_message_file_transfer_on_progress, msg, first_part_bh, NULL); linphone_chat_message_ref(msg); _release_http_request(msg); file_upload_begin_background_task(msg); linphone_chat_room_upload_file(msg); belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(msg->http_request), BELLE_SIP_BODY_HANDLER(bh)); linphone_chat_message_unref(msg); } 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) { /* if we have an encryption key for the file, we must insert it into the msg and restore the correct * filename */ const char *content_key = linphone_content_get_key(msg->file_transfer_information); size_t content_key_size = linphone_content_get_key_size(msg->file_transfer_information); if (content_key != NULL) { /* parse the msg body */ xmlDocPtr xmlMessageBody = xmlParseDoc((const xmlChar *)body); xmlNodePtr cur = xmlDocGetRootElement(xmlMessageBody); if (cur != NULL) { cur = cur->xmlChildrenNode; while (cur != NULL) { if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) { /* we found a file info node, check it has a type="file" attribute */ xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type"); if (!xmlStrcmp(typeAttribute, (const xmlChar *)"file")) { /* this is the node we are looking for : add a file-key children node */ xmlNodePtr fileInfoNodeChildren = cur ->xmlChildrenNode; /* need to parse the children node to update the file-name one */ /* convert key to base64 */ size_t b64Size = b64::b64_encode(NULL, content_key_size, NULL, 0); char *keyb64 = (char *)ms_malloc0(b64Size + 1); int xmlStringLength; b64Size = b64::b64_encode(content_key, content_key_size, keyb64, b64Size); keyb64[b64Size] = '\0'; /* libxml need a null terminated string */ /* add the node containing the key to the file-info node */ xmlNewTextChild(cur, NULL, (const xmlChar *)"file-key", (const xmlChar *)keyb64); xmlFree(typeAttribute); ms_free(keyb64); /* look for the file-name node and update its content */ while (fileInfoNodeChildren != NULL) { if (!xmlStrcmp( fileInfoNodeChildren->name, (const xmlChar *)"file-name")) { /* we found a the file-name node, update its content with the real filename */ /* update node content */ xmlNodeSetContent(fileInfoNodeChildren, (const xmlChar *)(linphone_content_get_name( msg->file_transfer_information))); break; } fileInfoNodeChildren = fileInfoNodeChildren->next; } /* dump the xml into msg->message */ xmlDocDumpFormatMemoryEnc(xmlMessageBody, (xmlChar **)&msg->message, &xmlStringLength, "UTF-8", 0); break; } xmlFree(typeAttribute); } cur = cur->next; } } xmlFreeDoc(xmlMessageBody); } else { /* no encryption key, transfer in plain, just copy the msg sent by server */ msg->message = ms_strdup(body); } linphone_chat_message_set_content_type(msg, "application/vnd.gsma.rcs-ft-http+xml"); linphone_chat_message_ref(msg); linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferDone); _release_http_request(msg); L_GET_CPP_PTR_FROM_C_STRUCT(msg->chat_room, ChatRoom)->sendMessage(msg); file_upload_end_background_task(msg); linphone_chat_message_unref(msg); } else { ms_warning("Received empty response from server, file transfer failed"); linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered); _release_http_request(msg); file_upload_end_background_task(msg); linphone_chat_message_unref(msg); } } else { ms_warning("Unhandled HTTP code response %d for file transfer", code); linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered); _release_http_request(msg); file_upload_end_background_task(msg); linphone_chat_message_unref(msg); } } } LinphoneContent *linphone_chat_message_get_file_transfer_information(LinphoneChatMessage *msg) { return msg->file_transfer_information; } static void on_recv_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t *m, void *data, size_t offset, uint8_t *buffer, size_t size) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; LinphoneCore *lc = NULL; LinphoneImEncryptionEngine *imee = NULL; int retval = -1; uint8_t *decrypted_buffer = NULL; if (!msg->chat_room) { linphone_chat_message_cancel_file_transfer(msg); return; } lc = linphone_chat_room_get_core(msg->chat_room); if (lc == NULL){ return; /*might happen during linphone_core_destroy()*/ } if (!msg->http_request || belle_http_request_is_cancelled(msg->http_request)) { ms_warning("Cancelled request for msg [%p], ignoring %s", msg, __FUNCTION__); return; } /* first call may be with a zero size, ignore it */ if (size == 0) { return; } decrypted_buffer = (uint8_t *)ms_malloc0(size); imee = linphone_core_get_im_encryption_engine(lc); if (imee) { LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); LinphoneImEncryptionEngineCbsDownloadingFileCb cb_process_downloading_file = linphone_im_encryption_engine_cbs_get_process_downloading_file(imee_cbs); if (cb_process_downloading_file) { retval = cb_process_downloading_file(imee, msg, offset, (const uint8_t *)buffer, size, decrypted_buffer); if (retval == 0) { memcpy(buffer, decrypted_buffer, size); } } } ms_free(decrypted_buffer); if (retval <= 0) { if (msg->file_transfer_filepath == NULL) { if (linphone_chat_message_cbs_get_file_transfer_recv(msg->cbs)) { LinphoneBuffer *lb = linphone_buffer_new_from_data(buffer, size); linphone_chat_message_cbs_get_file_transfer_recv(msg->cbs)(msg, msg->file_transfer_information, lb); linphone_buffer_unref(lb); } else { /* Legacy: call back given by application level */ linphone_core_notify_file_transfer_recv(lc, msg, msg->file_transfer_information, (const char *)buffer, size); } } } else { ms_warning("File transfer decrypt failed with code %d", (int)retval); linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferError); } return; } static void on_recv_end(belle_sip_user_body_handler_t *bh, void *data) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; LinphoneCore *lc = linphone_chat_room_get_core(msg->chat_room); LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc); int retval = -1; if (imee) { LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); LinphoneImEncryptionEngineCbsDownloadingFileCb cb_process_downloading_file = linphone_im_encryption_engine_cbs_get_process_downloading_file(imee_cbs); if (cb_process_downloading_file) { retval = cb_process_downloading_file(imee, msg, 0, NULL, 0, NULL); } } if (retval <= 0) { if (msg->file_transfer_filepath == NULL) { if (linphone_chat_message_cbs_get_file_transfer_recv(msg->cbs)) { LinphoneBuffer *lb = linphone_buffer_new(); linphone_chat_message_cbs_get_file_transfer_recv(msg->cbs)(msg, msg->file_transfer_information, lb); linphone_buffer_unref(lb); } else { /* Legacy: call back given by application level */ linphone_core_notify_file_transfer_recv(lc, msg, msg->file_transfer_information, NULL, 0); } } } if (retval <= 0 && linphone_chat_message_get_state(msg) != LinphoneChatMessageStateFileTransferError) { linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferDone); } } static LinphoneContent *linphone_chat_create_file_transfer_information_from_headers(const belle_sip_message_t *m) { LinphoneContent *content = linphone_content_new(); belle_sip_header_content_length_t *content_length_hdr = BELLE_SIP_HEADER_CONTENT_LENGTH(belle_sip_message_get_header(m, "Content-Length")); belle_sip_header_content_type_t *content_type_hdr = BELLE_SIP_HEADER_CONTENT_TYPE(belle_sip_message_get_header(m, "Content-Type")); const char *type = NULL, *subtype = NULL; linphone_content_set_name(content, ""); if (content_type_hdr) { type = belle_sip_header_content_type_get_type(content_type_hdr); subtype = belle_sip_header_content_type_get_subtype(content_type_hdr); ms_message("Extracted content type %s / %s from header", type ? type : "", subtype ? subtype : ""); if (type) linphone_content_set_type(content, type); if (subtype) linphone_content_set_subtype(content, subtype); } if (content_length_hdr) { linphone_content_set_size(content, belle_sip_header_content_length_get_content_length(content_length_hdr)); ms_message("Extracted content length %i from header", (int)linphone_content_get_size(content)); } return content; } static void linphone_chat_process_response_headers_from_get_file(void *data, const belle_http_response_event_t *event) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; if (event->response) { /*we are receiving a response, set a specific body handler to acquire the response. * if not done, belle-sip will create a memory body handler, the default*/ belle_sip_message_t *response = BELLE_SIP_MESSAGE(event->response); belle_sip_body_handler_t *body_handler = NULL; size_t body_size = 0; if (msg->file_transfer_information == NULL) { ms_warning("No file transfer information for msg %p: creating...", msg); msg->file_transfer_information = linphone_chat_create_file_transfer_information_from_headers(response); } if (msg->file_transfer_information) { body_size = linphone_content_get_size(msg->file_transfer_information); } body_handler = (belle_sip_body_handler_t *)belle_sip_user_body_handler_new(body_size, linphone_chat_message_file_transfer_on_progress, NULL, on_recv_body, NULL, on_recv_end, msg); if (msg->file_transfer_filepath != NULL) { belle_sip_user_body_handler_t *bh = (belle_sip_user_body_handler_t *)body_handler; body_handler = (belle_sip_body_handler_t *)belle_sip_file_body_handler_new( msg->file_transfer_filepath, linphone_chat_message_file_transfer_on_progress, msg); if (belle_sip_body_handler_get_size((belle_sip_body_handler_t *)body_handler) == 0) { /* If the size of the body has not been initialized from the file stat, use the one from the * file_transfer_information. */ belle_sip_body_handler_set_size((belle_sip_body_handler_t *)body_handler, body_size); } belle_sip_file_body_handler_set_user_body_handler((belle_sip_file_body_handler_t *)body_handler, bh); } belle_sip_message_set_body_handler((belle_sip_message_t *)event->response, body_handler); } } static void linphone_chat_process_response_from_get_file(void *data, const belle_http_response_event_t *event) { LinphoneChatMessage *msg = (LinphoneChatMessage *)data; /* check the answer code */ if (event->response) { int code = belle_http_response_get_status_code(event->response); if (code >= 400 && code < 500) { ms_warning("File transfer failed with code %d", code); linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferError); } else if (code != 200) { ms_warning("Unhandled HTTP code response %d for file transfer", code); } _release_http_request(msg); } } int _linphone_chat_room_start_http_transfer(LinphoneChatMessage *msg, const char* url, const char* action, const belle_http_request_listener_callbacks_t *cbs) { belle_generic_uri_t *uri = NULL; const char* ua = linphone_core_get_user_agent(linphone_chat_room_get_core(msg->chat_room)); if (url == NULL) { ms_warning("Cannot process file transfer msg: no file remote URI configured."); goto error; } uri = belle_generic_uri_parse(url); if (uri == NULL || belle_generic_uri_get_host(uri)==NULL) { ms_warning("Cannot process file transfer msg: incorrect file remote URI configured '%s'.", url); goto error; } msg->http_request = belle_http_request_create(action, uri, belle_sip_header_create("User-Agent", ua), NULL); if (msg->http_request == NULL) { ms_warning("Could not create http request for uri %s", url); goto error; } /* keep a reference to the http request to be able to cancel it during upload */ belle_sip_object_ref(msg->http_request); /* give msg to listener to be able to start the actual file upload when server answer a 204 No content */ msg->http_listener = belle_http_request_listener_create_from_callbacks(cbs, linphone_chat_message_ref(msg)); belle_http_provider_send_request(linphone_chat_room_get_core(msg->chat_room)->http_provider, msg->http_request, msg->http_listener); return 0; error: if (uri) { belle_sip_object_unref(uri); } return -1; } int linphone_chat_room_upload_file(LinphoneChatMessage *msg) { belle_http_request_listener_callbacks_t cbs = {0}; int err; if (msg->http_request){ ms_error("linphone_chat_room_upload_file(): there is already an upload in progress."); return -1; } cbs.process_response = linphone_chat_message_process_response_from_post_file; cbs.process_io_error = linphone_chat_message_process_io_error_upload; cbs.process_auth_requested = linphone_chat_message_process_auth_requested_upload; err = _linphone_chat_room_start_http_transfer(msg, linphone_core_get_file_transfer_server(linphone_chat_room_get_core(msg->chat_room)), "POST", &cbs); if (err == -1){ linphone_chat_message_set_state(msg, LinphoneChatMessageStateNotDelivered); } return err; } LinphoneStatus linphone_chat_message_download_file(LinphoneChatMessage *msg) { belle_http_request_listener_callbacks_t cbs = {0}; int err; if (msg->http_request){ ms_error("linphone_chat_message_download_file(): there is already a download in progress"); return -1; } cbs.process_response_headers = linphone_chat_process_response_headers_from_get_file; cbs.process_response = linphone_chat_process_response_from_get_file; cbs.process_io_error = linphone_chat_message_process_io_error_download; cbs.process_auth_requested = linphone_chat_message_process_auth_requested_download; err = _linphone_chat_room_start_http_transfer(msg, msg->external_body_url, "GET", &cbs); if (err == -1) return -1; /* start the download, status is In Progress */ linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress); return 0; } void linphone_chat_message_start_file_download(LinphoneChatMessage *msg, LinphoneChatMessageStateChangedCb status_cb, void *ud) { msg->message_state_changed_cb = status_cb; msg->message_state_changed_user_data = ud; linphone_chat_message_download_file(msg); } void _linphone_chat_message_cancel_file_transfer(LinphoneChatMessage *msg, bool_t unref) { if (msg->http_request) { if (msg->state == LinphoneChatMessageStateInProgress) { linphone_chat_message_set_state(msg, LinphoneChatMessageStateNotDelivered); } if (!belle_http_request_is_cancelled(msg->http_request)) { if (msg->chat_room) { ms_message("Canceling file transfer %s - msg [%p] chat room[%p]" , (msg->external_body_url == NULL) ? linphone_core_get_file_transfer_server(linphone_chat_room_get_core(msg->chat_room)) : msg->external_body_url , msg , msg->chat_room); belle_http_provider_cancel_request(linphone_chat_room_get_core(msg->chat_room)->http_provider, msg->http_request); if ((msg->dir == LinphoneChatMessageOutgoing) && unref) { // must release it linphone_chat_message_unref(msg); } } else { ms_message("Warning: http request still running for ORPHAN msg [%p]: this is a memory leak", msg); } } _release_http_request(msg); } else { ms_message("No existing file transfer - nothing to cancel"); } } void linphone_chat_message_cancel_file_transfer(LinphoneChatMessage *msg) { _linphone_chat_message_cancel_file_transfer(msg, TRUE); } void linphone_chat_message_set_file_transfer_filepath(LinphoneChatMessage *msg, const char *filepath) { if (msg->file_transfer_filepath != NULL) { ms_free(msg->file_transfer_filepath); } msg->file_transfer_filepath = ms_strdup(filepath); }