linphone-ios/coreapi/chat.c
2015-09-17 17:16:29 +02:00

1561 lines
61 KiB
C

/***************************************************************************
* chat.c
*
* Sun Jun 5 19:34:18 2005
* Copyright 2005 Simon Morlat
* Email simon dot morlat at linphone dot org
****************************************************************************/
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "linphonecore.h"
#include "private.h"
#include "lpconfig.h"
#include "belle-sip/belle-sip.h"
#include "lime.h"
#include "ortp/b64.h"
#include <libxml/parser.h>
#include <libxml/tree.h>
#include <libxml/xmlwriter.h>
#define COMPOSING_DEFAULT_IDLE_TIMEOUT 15
#define COMPOSING_DEFAULT_REFRESH_TIMEOUT 60
#define COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT 120
#define FILE_TRANSFER_KEY_SIZE 32
static void linphone_chat_message_release(LinphoneChatMessage *msg);
static LinphoneChatMessageCbs *linphone_chat_message_cbs_new(void) {
return belle_sip_object_new(LinphoneChatMessageCbs);
}
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessageCbs);
BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatMessageCbs, belle_sip_object_t,
NULL, // destroy
NULL, // clone
NULL, // marshal
FALSE);
static void linphone_chat_message_set_state(LinphoneChatMessage *msg, LinphoneChatMessageState state) {
if (state != msg->state) {
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->callbacks)) {
linphone_chat_message_cbs_get_msg_state_changed(msg->callbacks)(msg, msg->state);
}
}
}
LinphoneChatMessageCbs *linphone_chat_message_cbs_ref(LinphoneChatMessageCbs *cbs) {
belle_sip_object_ref(cbs);
return cbs;
}
void linphone_chat_message_cbs_unref(LinphoneChatMessageCbs *cbs) {
belle_sip_object_unref(cbs);
}
void *linphone_chat_message_cbs_get_user_data(const LinphoneChatMessageCbs *cbs) {
return cbs->user_data;
}
void linphone_chat_message_cbs_set_user_data(LinphoneChatMessageCbs *cbs, void *ud) {
cbs->user_data = ud;
}
LinphoneChatMessageCbsMsgStateChangedCb
linphone_chat_message_cbs_get_msg_state_changed(const LinphoneChatMessageCbs *cbs) {
return cbs->msg_state_changed;
}
void linphone_chat_message_cbs_set_msg_state_changed(LinphoneChatMessageCbs *cbs,
LinphoneChatMessageCbsMsgStateChangedCb cb) {
cbs->msg_state_changed = cb;
}
LinphoneChatMessageCbsFileTransferRecvCb
linphone_chat_message_cbs_get_file_transfer_recv(const LinphoneChatMessageCbs *cbs) {
return cbs->file_transfer_recv;
}
void linphone_chat_message_cbs_set_file_transfer_recv(LinphoneChatMessageCbs *cbs,
LinphoneChatMessageCbsFileTransferRecvCb cb) {
cbs->file_transfer_recv = cb;
}
LinphoneChatMessageCbsFileTransferSendCb
linphone_chat_message_cbs_get_file_transfer_send(const LinphoneChatMessageCbs *cbs) {
return cbs->file_transfer_send;
}
void linphone_chat_message_cbs_set_file_transfer_send(LinphoneChatMessageCbs *cbs,
LinphoneChatMessageCbsFileTransferSendCb cb) {
cbs->file_transfer_send = cb;
}
LinphoneChatMessageCbsFileTransferProgressIndicationCb
linphone_chat_message_cbs_get_file_transfer_progress_indication(const LinphoneChatMessageCbs *cbs) {
return cbs->file_transfer_progress_indication;
}
void linphone_chat_message_cbs_set_file_transfer_progress_indication(
LinphoneChatMessageCbs *cbs, LinphoneChatMessageCbsFileTransferProgressIndicationCb cb) {
cbs->file_transfer_progress_indication = cb;
}
static void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg);
static void 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 to %s - msg [%p] chat room[%p]",
linphone_core_get_file_transfer_server(msg->chat_room->lc), msg, msg->chat_room);
linphone_chat_message_cancel_file_transfer(msg);
}
static void process_auth_requested_upload(void *data, belle_sip_auth_event_t *event) {
LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
ms_error("Error during file upload: auth requested to connect %s - msg [%p] chat room[%p]",
linphone_core_get_file_transfer_server(msg->chat_room->lc), msg, msg->chat_room);
linphone_chat_message_cancel_file_transfer(msg);
}
static void 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 %s - msg [%p] chat room[%p]", msg->external_body_url, msg, msg->chat_room);
linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferError);
}
static void process_auth_requested_download(void *data, belle_sip_auth_event_t *event) {
LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferError);
ms_error("Error during file download : auth requested to get %s - msg [%p] chat room[%p]", msg->external_body_url,
msg, msg->chat_room);
}
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 (!msg->http_request || belle_http_request_is_cancelled(msg->http_request)) {
ms_warning("Cancelled request for msg [%p], ignoring %s", msg, __FUNCTION__);
return;
}
if (linphone_chat_message_cbs_get_file_transfer_progress_indication(msg->callbacks)) {
linphone_chat_message_cbs_get_file_transfer_progress_indication(msg->callbacks)(
msg, msg->file_transfer_information, offset, total);
} else {
/* Legacy: call back given by application level */
linphone_core_notify_file_transfer_progress_indication(msg->chat_room->lc, msg, msg->file_transfer_information,
offset, total);
}
}
static int linphone_chat_message_file_transfer_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 = msg->chat_room->lc;
char *buf = (char *)buffer;
if (!msg->http_request || belle_http_request_is_cancelled(msg->http_request)) {
ms_warning("Cancelled request for msg [%p], ignoring %s", msg, __FUNCTION__);
return BELLE_SIP_STOP;
}
/* if we've not reach the end of file yet, ask for more data*/
if (offset < linphone_content_get_size(msg->file_transfer_information)) {
char *plainBuffer = NULL;
if (linphone_content_get_key(msg->file_transfer_information) !=
NULL) { /* if we have a key to cipher the msg, use it! */
/* if this chunk is not the last one, the lenght must be a multiple of block cipher size(16 bytes)*/
if (offset + *size < linphone_content_get_size(msg->file_transfer_information)) {
*size -= (*size % 16);
}
plainBuffer = (char *)malloc(*size);
}
/* get data from call back */
if (linphone_chat_message_cbs_get_file_transfer_send(msg->callbacks)) {
LinphoneBuffer *lb = linphone_chat_message_cbs_get_file_transfer_send(msg->callbacks)(
msg, msg->file_transfer_information, offset, *size);
if (lb == NULL) {
*size = 0;
} else {
*size = linphone_buffer_get_size(lb);
memcpy(plainBuffer ? plainBuffer : buf, linphone_buffer_get_content(lb), *size);
linphone_buffer_unref(lb);
}
} else {
/* Legacy */
linphone_core_notify_file_transfer_send(lc, msg, msg->file_transfer_information,
plainBuffer ? plainBuffer : buf, size);
}
if (linphone_content_get_key(msg->file_transfer_information) !=
NULL) { /* if we have a key to cipher the msg, use it! */
lime_encryptFile(linphone_content_get_cryptoContext_address(msg->file_transfer_information),
(unsigned char *)linphone_content_get_key(msg->file_transfer_information), *size,
plainBuffer, (char *)buffer);
free(plainBuffer);
/* check if we reach the end of file */
if (offset + *size >= linphone_content_get_size(msg->file_transfer_information)) {
/* conclude file ciphering by calling it context with a zero size */
lime_encryptFile(linphone_content_get_cryptoContext_address(msg->file_transfer_information), NULL, 0,
NULL, NULL);
}
}
}
return BELLE_SIP_CONTINUE;
}
static void linphone_chat_message_process_response_from_post_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 == 204) { /* this is the reply to the first post to the server - an empty msg */
/* start uploading the file */
belle_http_request_listener_callbacks_t cbs = {0};
belle_http_request_listener_t *l;
belle_generic_uri_t *uri;
belle_sip_multipart_body_handler_t *bh;
char *ua;
char *first_part_header;
belle_sip_body_handler_t *first_part_bh;
/* shall we encrypt the file */
if (linphone_core_lime_for_file_sharing_enabled(msg->chat_room->lc)) {
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 field of the msg */
sal_get_random_bytes((unsigned char *)keyBuffer, FILE_TRANSFER_KEY_SIZE);
linphone_content_set_key(
msg->file_transfer_information, keyBuffer,
FILE_TRANSFER_KEY_SIZE); /* key is duplicated in the content private structure */
/* 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 */
if (msg->file_transfer_filepath != NULL) {
first_part_bh =
(belle_sip_body_handler_t *)belle_sip_file_body_handler_new(msg->file_transfer_filepath, NULL, msg);
} 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), NULL, msg);
} else {
first_part_bh = (belle_sip_body_handler_t *)belle_sip_user_body_handler_new(
linphone_content_get_size(msg->file_transfer_information), NULL, NULL,
linphone_chat_message_file_transfer_on_send_body, 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);
/* create the http request: do not include the msg header at this point, it is done by bellesip when
* setting the multipart body handler in the msg */
ua = ms_strdup_printf("%s/%s", linphone_core_get_user_agent_name(), linphone_core_get_user_agent_version());
uri = belle_generic_uri_parse(linphone_core_get_file_transfer_server(msg->chat_room->lc));
if (msg->http_request)
belle_sip_object_unref(msg->http_request);
msg->http_request = belle_http_request_create("POST", uri, belle_sip_header_create("User-Agent", ua), NULL);
belle_sip_object_ref(
msg->http_request); /* keep a reference to the http request to be able to cancel it during upload */
ms_free(ua);
belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(msg->http_request), BELLE_SIP_BODY_HANDLER(bh));
cbs.process_response = linphone_chat_message_process_response_from_post_file;
cbs.process_io_error = process_io_error_upload;
cbs.process_auth_requested = process_auth_requested_upload;
l = belle_http_request_listener_create_from_callbacks(&cbs, msg);
belle_http_provider_send_request(msg->chat_room->lc->http_provider, msg->http_request, l);
} else if (code == 200) { /* file has been uplaoded correctly, get server reply and send it */
const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response);
belle_sip_object_unref(msg->http_request);
msg->http_request = NULL;
/* if we have an encryption key for the file, we must insert it into the msg and restore the correct
* filename */
if (linphone_content_get_key(msg->file_transfer_information) != 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 */
int b64Size = b64_encode(NULL, FILE_TRANSFER_KEY_SIZE, NULL, 0);
char *keyb64 = (char *)malloc(b64Size + 1);
int xmlStringLength;
b64Size = b64_encode(linphone_content_get_key(msg->file_transfer_information),
FILE_TRANSFER_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);
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);
}
msg->content_type = ms_strdup("application/vnd.gsma.rcs-ft-http+xml");
linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferDone);
_linphone_chat_room_send_message(msg->chat_room, msg);
} else {
ms_warning("Unhandled HTTP code response %d for file transfer", code);
linphone_chat_message_set_state(msg, LinphoneChatMessageStateNotDelivered);
}
}
}
static void _linphone_chat_message_destroy(LinphoneChatMessage *msg);
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessage);
BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatMessage, belle_sip_object_t,
(belle_sip_object_destroy_t)_linphone_chat_message_destroy,
NULL, // clone
NULL, // marshal
FALSE);
void linphone_core_disable_chat(LinphoneCore *lc, LinphoneReason deny_reason) {
lc->chat_deny_code = deny_reason;
}
void linphone_core_enable_chat(LinphoneCore *lc) {
lc->chat_deny_code = LinphoneReasonNone;
}
bool_t linphone_core_chat_enabled(const LinphoneCore *lc) {
return lc->chat_deny_code != LinphoneReasonNone;
}
const MSList *linphone_core_get_chat_rooms(LinphoneCore *lc) {
return lc->chatrooms;
}
static bool_t linphone_chat_room_matches(LinphoneChatRoom *cr, const LinphoneAddress *from) {
return linphone_address_weak_equal(cr->peer_url, from);
}
static void _linphone_chat_room_destroy(LinphoneChatRoom *obj);
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatRoom);
BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatRoom, belle_sip_object_t,
(belle_sip_object_destroy_t)_linphone_chat_room_destroy,
NULL, // clone
NULL, // marshal
FALSE);
static LinphoneChatRoom *_linphone_core_create_chat_room(LinphoneCore *lc, LinphoneAddress *addr) {
LinphoneChatRoom *cr = belle_sip_object_new(LinphoneChatRoom);
cr->lc = lc;
cr->peer = linphone_address_as_string(addr);
cr->peer_url = addr;
cr->unread_count = -1;
lc->chatrooms = ms_list_append(lc->chatrooms, (void *)cr);
return cr;
}
static LinphoneChatRoom *_linphone_core_create_chat_room_from_url(LinphoneCore *lc, const char *to) {
LinphoneAddress *parsed_url = NULL;
if ((parsed_url = linphone_core_interpret_url(lc, to)) != NULL) {
return _linphone_core_create_chat_room(lc, parsed_url);
}
return NULL;
}
LinphoneChatRoom *_linphone_core_get_chat_room(LinphoneCore *lc, const LinphoneAddress *addr) {
LinphoneChatRoom *cr = NULL;
MSList *elem;
for (elem = lc->chatrooms; elem != NULL; elem = ms_list_next(elem)) {
cr = (LinphoneChatRoom *)elem->data;
if (linphone_chat_room_matches(cr, addr)) {
break;
}
cr = NULL;
}
return cr;
}
static LinphoneChatRoom *_linphone_core_get_or_create_chat_room(LinphoneCore *lc, const char *to) {
LinphoneAddress *to_addr = linphone_core_interpret_url(lc, to);
LinphoneChatRoom *ret;
if (to_addr == NULL) {
ms_error("linphone_core_get_or_create_chat_room(): Cannot make a valid address with %s", to);
return NULL;
}
ret = _linphone_core_get_chat_room(lc, to_addr);
linphone_address_destroy(to_addr);
if (!ret) {
ret = _linphone_core_create_chat_room_from_url(lc, to);
}
return ret;
}
LinphoneChatRoom *linphone_core_get_chat_room(LinphoneCore *lc, const LinphoneAddress *addr) {
LinphoneChatRoom *ret = _linphone_core_get_chat_room(lc, addr);
if (!ret) {
ret = _linphone_core_create_chat_room(lc, linphone_address_clone(addr));
}
return ret;
}
void linphone_core_delete_chat_room(LinphoneCore *lc, LinphoneChatRoom *cr) {
if (ms_list_find(lc->chatrooms, cr)) {
lc->chatrooms = ms_list_remove(cr->lc->chatrooms, cr);
linphone_chat_room_delete_history(cr);
linphone_chat_room_unref(cr);
} else {
ms_error("linphone_core_delete_chat_room(): chatroom [%p] isn't part of LinphoneCore.", cr);
}
}
LinphoneChatRoom *linphone_core_get_chat_room_from_uri(LinphoneCore *lc, const char *to) {
return _linphone_core_get_or_create_chat_room(lc, to);
}
static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) {
if (cr->composing_idle_timer) {
if (cr->lc && cr->lc->sal)
sal_cancel_timer(cr->lc->sal, cr->composing_idle_timer);
belle_sip_object_unref(cr->composing_idle_timer);
cr->composing_idle_timer = NULL;
}
}
static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom *cr) {
if (cr->composing_refresh_timer) {
if (cr->lc && cr->lc->sal)
sal_cancel_timer(cr->lc->sal, cr->composing_refresh_timer);
belle_sip_object_unref(cr->composing_refresh_timer);
cr->composing_refresh_timer = NULL;
}
}
static void linphone_chat_room_delete_remote_composing_refresh_timer(LinphoneChatRoom *cr) {
if (cr->remote_composing_refresh_timer) {
if (cr->lc && cr->lc->sal)
sal_cancel_timer(cr->lc->sal, cr->remote_composing_refresh_timer);
belle_sip_object_unref(cr->remote_composing_refresh_timer);
cr->remote_composing_refresh_timer = NULL;
}
}
static void _linphone_chat_room_destroy(LinphoneChatRoom *cr) {
ms_list_free_with_data(cr->transient_messages, (void (*)(void *))linphone_chat_message_release);
linphone_chat_room_delete_composing_idle_timer(cr);
linphone_chat_room_delete_composing_refresh_timer(cr);
linphone_chat_room_delete_remote_composing_refresh_timer(cr);
if (cr->lc != NULL) {
if (ms_list_find(cr->lc->chatrooms, cr)) {
ms_error("LinphoneChatRoom[%p] is destroyed while still being used by the LinphoneCore. This is abnormal."
" linphone_core_get_chat_room() doesn't give a reference, there is no need to call "
"linphone_chat_room_unref(). "
"In order to remove a chat room from the core, use linphone_core_delete_chat_room().",
cr);
cr->lc->chatrooms = ms_list_remove(cr->lc->chatrooms, cr);
}
}
linphone_address_destroy(cr->peer_url);
ms_free(cr->peer);
}
void linphone_chat_room_destroy(LinphoneChatRoom *cr) {
linphone_chat_room_unref(cr);
}
void linphone_chat_room_release(LinphoneChatRoom *cr) {
cr->lc = NULL;
linphone_chat_room_unref(cr);
}
LinphoneChatRoom *linphone_chat_room_ref(LinphoneChatRoom *cr) {
belle_sip_object_ref(cr);
return cr;
}
void linphone_chat_room_unref(LinphoneChatRoom *cr) {
belle_sip_object_unref(cr);
}
void *linphone_chat_room_get_user_data(const LinphoneChatRoom *cr) {
return cr->user_data;
}
void linphone_chat_room_set_user_data(LinphoneChatRoom *cr, void *ud) {
cr->user_data = ud;
}
static void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
SalOp *op = NULL;
LinphoneCall *call;
char *content_type;
const char *identity = NULL;
time_t t = time(NULL);
linphone_chat_message_ref(msg);
/* Check if we shall upload a file to a server */
if (msg->file_transfer_information != NULL && msg->content_type == NULL) {
/* open a transaction with the server and send an empty request(RCS5.1 section 3.5.4.8.3.1) */
belle_http_request_listener_callbacks_t cbs = {0};
belle_http_request_listener_t *l;
belle_generic_uri_t *uri = NULL;
const char *transfer_server = linphone_core_get_file_transfer_server(cr->lc);
if (transfer_server == NULL) {
ms_warning("Cannot send file transfer msg: no file transfer server configured.");
goto error;
}
uri = belle_generic_uri_parse(transfer_server);
if (uri == NULL) {
ms_warning("Cannot send file transfer msg: incorrect file transfer server configured '%s'.", transfer_server);
goto error;
}
msg->http_request = belle_http_request_create("POST", uri, NULL, NULL, NULL);
if (msg->http_request == NULL) {
ms_warning("Could not create http request for uri %s", transfer_server);
goto error;
}
/* keep a reference on the request to be able to cancel it */
belle_sip_object_ref(msg->http_request);
cbs.process_response = linphone_chat_message_process_response_from_post_file;
cbs.process_io_error = process_io_error_upload;
cbs.process_auth_requested = process_auth_requested_upload;
/* give msg to listener to be able to start the actual file upload when server answer a 204 No content */
l = belle_http_request_listener_create_from_callbacks(&cbs, msg);
belle_http_provider_send_request(cr->lc->http_provider, msg->http_request, l);
linphone_chat_message_unref(msg);
return;
error:
if (uri) {
belle_sip_object_unref(uri);
}
linphone_chat_message_set_state(msg, LinphoneChatMessageStateNotDelivered);
linphone_chat_message_unref(msg);
} else {
if (lp_config_get_int(cr->lc->config, "sip", "chat_use_call_dialogs", 0)) {
if ((call = linphone_core_get_call_by_remote_address(cr->lc, cr->peer)) != NULL) {
if (call->state == LinphoneCallConnected || call->state == LinphoneCallStreamsRunning ||
call->state == LinphoneCallPaused || call->state == LinphoneCallPausing ||
call->state == LinphoneCallPausedByRemote) {
ms_message("send SIP msg through the existing call.");
op = call->op;
identity = linphone_core_find_best_identity(cr->lc, linphone_call_get_remote_address(call));
}
}
}
msg->time = t;
if (op == NULL) {
LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(cr->lc, cr->peer_url);
if (proxy) {
identity = linphone_proxy_config_get_identity(proxy);
} else
identity = linphone_core_get_primary_contact(cr->lc);
/*sending out of calls*/
msg->op = op = sal_op_new(cr->lc->sal);
linphone_configure_op(cr->lc, op, cr->peer_url, msg->custom_headers,
lp_config_get_int(cr->lc->config, "sip", "chat_msg_with_contact", 0));
sal_op_set_user_pointer(op, msg); /*if out of call, directly store msg*/
}
if (msg->external_body_url) {
content_type = ms_strdup_printf("message/external-body; access-type=URL; URL=\"%s\"", msg->external_body_url);
sal_message_send(op, identity, cr->peer, content_type, NULL, NULL);
ms_free(content_type);
} else {
char *peer_uri = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
const char *content_type;
if (linphone_core_lime_enabled(cr->lc)) {
/* ref the msg or it may be destroyed by callback if the encryption failed */
linphone_chat_message_ref(msg);
if (msg->content_type && strcmp(msg->content_type, "application/vnd.gsma.rcs-ft-http+xml") == 0) {
/* it's a file transfer, content type shall be set to
application/cipher.vnd.gsma.rcs-ft-http+xml*/
content_type = "application/cipher.vnd.gsma.rcs-ft-http+xml";
} else {
content_type = "xml/cipher";
}
} else {
content_type = msg->content_type;
}
if (content_type == NULL) {
sal_text_send(op, identity, cr->peer, msg->message);
} else {
sal_message_send(op, identity, cr->peer, content_type, msg->message, peer_uri);
}
ms_free(peer_uri);
}
msg->dir = LinphoneChatMessageOutgoing;
msg->from = linphone_address_new(identity);
msg->storage_id = linphone_chat_message_store(msg);
if (cr->unread_count >= 0 && !msg->is_read)
cr->unread_count++;
// add to transient list
cr->transient_messages = ms_list_append(cr->transient_messages, linphone_chat_message_ref(msg));
if (cr->is_composing == LinphoneIsComposingActive) {
cr->is_composing = LinphoneIsComposingIdle;
}
linphone_chat_room_delete_composing_idle_timer(cr);
linphone_chat_room_delete_composing_refresh_timer(cr);
linphone_chat_message_unref(msg);
}
}
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) {
// msg is not transient anymore, we can remove it from our transient list and unref it :
msg->chat_room->transient_messages = ms_list_remove(msg->chat_room->transient_messages, msg);
linphone_chat_message_unref(msg);
}
}
/**
* Send a msg to peer member of this chat room.
* @deprecated linphone_chat_room_send_message2() gives more control on the msg expedition.
* @param cr #LinphoneChatRoom object
* @param msg msg to be sent
*/
void linphone_chat_room_send_message(LinphoneChatRoom *cr, const char *msg) {
_linphone_chat_room_send_message(cr, linphone_chat_room_create_message(cr, msg));
}
void linphone_chat_room_message_received(LinphoneChatRoom *cr, LinphoneCore *lc, LinphoneChatMessage *msg) {
if (msg->message) {
/*legacy API*/
linphone_core_notify_text_message_received(lc, cr, msg->from, msg->message);
}
linphone_core_notify_message_received(lc, cr, msg);
cr->remote_is_composing = LinphoneIsComposingIdle;
linphone_core_notify_is_composing_received(cr->lc, cr);
}
void linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *sal_msg) {
LinphoneChatRoom *cr = NULL;
LinphoneAddress *addr;
LinphoneChatMessage *msg;
const SalCustomHeader *ch;
addr = linphone_address_new(sal_msg->from);
linphone_address_clean(addr);
cr = linphone_core_get_chat_room(lc, addr);
if (sal_msg->content_type !=
NULL) { /* content_type field is, for now, used only for rcs file transfer but we shall strcmp it with
"application/vnd.gsma.rcs-ft-http+xml" */
xmlChar *file_url = NULL;
xmlDocPtr xmlMessageBody;
xmlNodePtr cur;
msg = linphone_chat_room_create_message(cr, NULL); /* create a msg with empty body */
msg->content_type =
ms_strdup(sal_msg->content_type); /* add the content_type "application/vnd.gsma.rcs-ft-http+xml" */
msg->file_transfer_information = linphone_content_new();
/* parse the msg body to get all informations from it */
xmlMessageBody = xmlParseDoc((const xmlChar *)sal_msg->text);
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 */
cur = cur->xmlChildrenNode; /* now loop on the content of the file-info node */
while (cur != NULL) {
if (!xmlStrcmp(cur->name, (const xmlChar *)"file-size")) {
xmlChar *fileSizeString = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
linphone_content_set_size(msg->file_transfer_information,
strtol((const char *)fileSizeString, NULL, 10));
xmlFree(fileSizeString);
}
if (!xmlStrcmp(cur->name, (const xmlChar *)"file-name")) {
linphone_content_set_name(
msg->file_transfer_information,
(const char *)xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1));
}
if (!xmlStrcmp(cur->name, (const xmlChar *)"content-type")) {
xmlChar *contentType = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
int contentTypeIndex = 0;
char *type;
char *subtype;
while (contentType[contentTypeIndex] != '/' && contentType[contentTypeIndex] != '\0') {
contentTypeIndex++;
}
type = ms_strndup((char *)contentType, contentTypeIndex);
subtype = ms_strdup(((char *)contentType + contentTypeIndex + 1));
linphone_content_set_type(msg->file_transfer_information, type);
linphone_content_set_subtype(msg->file_transfer_information, subtype);
ms_free(subtype);
ms_free(type);
xmlFree(contentType);
}
if (!xmlStrcmp(cur->name, (const xmlChar *)"data")) {
file_url = xmlGetProp(cur, (const xmlChar *)"url");
}
if (!xmlStrcmp(cur->name,
(const xmlChar *)"file-key")) { /* there is a key in the msg: file has
been encrypted */
/* convert the key from base 64 */
xmlChar *keyb64 = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
int keyLength = b64_decode((char *)keyb64, strlen((char *)keyb64), NULL, 0);
uint8_t *keyBuffer = (uint8_t *)malloc(keyLength);
/* decode the key into local key buffer */
b64_decode((char *)keyb64, strlen((char *)keyb64), keyBuffer, keyLength);
linphone_content_set_key(
msg->file_transfer_information, (char *)keyBuffer,
keyLength); /* duplicate key value into the linphone content private structure */
xmlFree(keyb64);
free(keyBuffer);
}
cur = cur->next;
}
xmlFree(typeAttribute);
break;
}
xmlFree(typeAttribute);
}
cur = cur->next;
}
}
xmlFreeDoc(xmlMessageBody);
linphone_chat_message_set_external_body_url(msg, (const char *)file_url);
xmlFree(file_url);
} else { /* msg is not rcs file transfer, create it with provided sal_msg->text as ->msg */
msg = linphone_chat_room_create_message(cr, sal_msg->text);
}
linphone_chat_message_set_from(msg, cr->peer_url);
{
LinphoneAddress *to;
to = sal_op_get_to(op) ? linphone_address_new(sal_op_get_to(op))
: linphone_address_new(linphone_core_get_identity(lc));
msg->to = to;
}
msg->time = sal_msg->time;
linphone_chat_message_set_state(msg, LinphoneChatMessageStateDelivered);
msg->is_read = FALSE;
msg->dir = LinphoneChatMessageIncoming;
ch = sal_op_get_recv_custom_header(op);
if (ch)
msg->custom_headers = sal_custom_header_clone(ch);
if (sal_msg->url) {
linphone_chat_message_set_external_body_url(msg, sal_msg->url);
}
linphone_address_destroy(addr);
msg->storage_id = linphone_chat_message_store(msg);
if (cr->unread_count < 0)
cr->unread_count = 1;
else
cr->unread_count++;
linphone_chat_room_message_received(cr, lc, msg);
linphone_chat_message_unref(msg);
}
static int linphone_chat_room_remote_refresh_composing_expired(void *data, unsigned int revents) {
LinphoneChatRoom *cr = (LinphoneChatRoom *)data;
belle_sip_object_unref(cr->remote_composing_refresh_timer);
cr->remote_composing_refresh_timer = NULL;
cr->remote_is_composing = LinphoneIsComposingIdle;
linphone_core_notify_is_composing_received(cr->lc, cr);
return BELLE_SIP_STOP;
}
static const char *iscomposing_prefix = "/xsi:isComposing";
static void process_im_is_composing_notification(LinphoneChatRoom *cr, xmlparsing_context_t *xml_ctx) {
char xpath_str[MAX_XPATH_LENGTH];
xmlXPathObjectPtr iscomposing_object;
const char *state_str = NULL;
const char *refresh_str = NULL;
int refresh_duration = lp_config_get_int(cr->lc->config, "sip", "composing_remote_refresh_timeout",
COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT);
int i;
LinphoneIsComposingState state = LinphoneIsComposingIdle;
if (linphone_create_xml_xpath_context(xml_ctx) < 0)
return;
xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"xsi",
(const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing");
iscomposing_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, iscomposing_prefix);
if (iscomposing_object != NULL) {
if (iscomposing_object->nodesetval != NULL) {
for (i = 1; i <= iscomposing_object->nodesetval->nodeNr; i++) {
snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/xsi:state", iscomposing_prefix, i);
state_str = linphone_get_xml_text_content(xml_ctx, xpath_str);
if (state_str == NULL)
continue;
snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/xsi:refresh", iscomposing_prefix, i);
refresh_str = linphone_get_xml_text_content(xml_ctx, xpath_str);
}
}
xmlXPathFreeObject(iscomposing_object);
}
if (state_str != NULL) {
if (strcmp(state_str, "active") == 0) {
state = LinphoneIsComposingActive;
if (refresh_str != NULL) {
refresh_duration = atoi(refresh_str);
}
if (!cr->remote_composing_refresh_timer) {
cr->remote_composing_refresh_timer =
sal_create_timer(cr->lc->sal, linphone_chat_room_remote_refresh_composing_expired, cr,
refresh_duration * 1000, "composing remote refresh timeout");
} else {
belle_sip_source_set_timeout(cr->remote_composing_refresh_timer, refresh_duration * 1000);
}
} else {
linphone_chat_room_delete_remote_composing_refresh_timer(cr);
}
cr->remote_is_composing = state;
linphone_core_notify_is_composing_received(cr->lc, cr);
linphone_free_xml_text_content(state_str);
}
if (refresh_str != NULL) {
linphone_free_xml_text_content(refresh_str);
}
}
static void linphone_chat_room_notify_is_composing(LinphoneChatRoom *cr, const char *text) {
xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new();
xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
xml_ctx->doc = xmlReadDoc((const unsigned char *)text, 0, NULL, 0);
if (xml_ctx->doc != NULL) {
process_im_is_composing_notification(cr, xml_ctx);
} else {
ms_warning("Wrongly formatted presence XML: %s", xml_ctx->errorBuffer);
}
linphone_xmlparsing_context_destroy(xml_ctx);
}
void linphone_core_is_composing_received(LinphoneCore *lc, SalOp *op, const SalIsComposing *is_composing) {
LinphoneAddress *addr = linphone_address_new(is_composing->from);
LinphoneChatRoom *cr = _linphone_core_get_chat_room(lc, addr);
if (cr != NULL) {
linphone_chat_room_notify_is_composing(cr, is_composing->text);
}
linphone_address_destroy(addr);
}
bool_t linphone_chat_room_is_remote_composing(const LinphoneChatRoom *cr) {
return (cr->remote_is_composing == LinphoneIsComposingActive) ? TRUE : FALSE;
}
LinphoneCore *linphone_chat_room_get_lc(LinphoneChatRoom *cr) {
return cr->lc;
}
LinphoneCore *linphone_chat_room_get_core(LinphoneChatRoom *cr) {
return cr->lc;
}
const LinphoneAddress *linphone_chat_room_get_peer_address(LinphoneChatRoom *cr) {
return cr->peer_url;
}
LinphoneChatMessage *linphone_chat_room_create_message(LinphoneChatRoom *cr, const char *message) {
LinphoneChatMessage *msg = belle_sip_object_new(LinphoneChatMessage);
msg->callbacks = linphone_chat_message_cbs_new();
msg->chat_room = (LinphoneChatRoom *)cr;
msg->message = message ? ms_strdup(message) : NULL;
msg->is_read = TRUE;
msg->content_type = NULL; /* this property is used only when transfering file */
msg->file_transfer_information = NULL; /* this property is used only when transfering file */
msg->http_request = NULL;
return msg;
}
LinphoneChatMessage *linphone_chat_room_create_message_2(LinphoneChatRoom *cr, const char *message,
const char *external_body_url, LinphoneChatMessageState state,
time_t time, bool_t is_read, bool_t is_incoming) {
LinphoneCore *lc = linphone_chat_room_get_lc(cr);
LinphoneChatMessage *msg = belle_sip_object_new(LinphoneChatMessage);
msg->callbacks = linphone_chat_message_cbs_new();
msg->chat_room = (LinphoneChatRoom *)cr;
msg->message = message ? ms_strdup(message) : NULL;
msg->external_body_url = external_body_url ? ms_strdup(external_body_url) : NULL;
msg->time = time;
linphone_chat_message_set_state(msg, state);
msg->is_read = is_read;
msg->content_type = NULL; /* this property is used only when transfering file */
msg->file_transfer_information = NULL; /* this property is used only when transfering file */
if (is_incoming) {
msg->dir = LinphoneChatMessageIncoming;
linphone_chat_message_set_from(msg, linphone_chat_room_get_peer_address(cr));
linphone_chat_message_set_to(msg, linphone_address_new(linphone_core_get_identity(lc)));
} else {
msg->dir = LinphoneChatMessageOutgoing;
linphone_chat_message_set_to(msg, linphone_chat_room_get_peer_address(cr));
linphone_chat_message_set_from(msg, linphone_address_new(linphone_core_get_identity(lc)));
}
return msg;
}
void linphone_chat_room_send_message2(LinphoneChatRoom *cr, LinphoneChatMessage *msg,
LinphoneChatMessageStateChangedCb status_cb, void *ud) {
msg->message_state_changed_cb = status_cb;
msg->message_state_changed_user_data = ud;
linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress);
_linphone_chat_room_send_message(cr, msg);
}
void linphone_chat_room_send_chat_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress);
_linphone_chat_room_send_message(cr, msg);
}
static char *linphone_chat_room_create_is_composing_xml(LinphoneChatRoom *cr) {
xmlBufferPtr buf;
xmlTextWriterPtr writer;
int err;
char *content = 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;
}
err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL);
if (err >= 0) {
err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"isComposing",
(const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing");
}
if (err >= 0) {
err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"xsi", NULL,
(const xmlChar *)"http://www.w3.org/2001/XMLSchema-instance");
}
if (err >= 0) {
err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xsi", (const xmlChar *)"schemaLocation", NULL,
(const xmlChar *)"urn:ietf:params:xml:ns:im-composing iscomposing.xsd");
}
if (err >= 0) {
err = xmlTextWriterWriteElement(writer, (const xmlChar *)"state",
(cr->is_composing == LinphoneIsComposingActive) ? (const xmlChar *)"active"
: (const xmlChar *)"idle");
}
if ((err >= 0) && (cr->is_composing == LinphoneIsComposingActive)) {
char refresh_str[4] = {0};
int refresh_timeout =
lp_config_get_int(cr->lc->config, "sip", "composing_refresh_timeout", COMPOSING_DEFAULT_REFRESH_TIMEOUT);
snprintf(refresh_str, sizeof(refresh_str), "%u", refresh_timeout);
err = xmlTextWriterWriteElement(writer, (const xmlChar *)"refresh", (const xmlChar *)refresh_str);
}
if (err >= 0) {
/* Close the "isComposing" 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);
return content;
}
static void linphone_chat_room_send_is_composing_notification(LinphoneChatRoom *cr) {
SalOp *op = NULL;
const char *identity = NULL;
char *content = NULL;
LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(cr->lc, cr->peer_url);
if (proxy)
identity = linphone_proxy_config_get_identity(proxy);
else
identity = linphone_core_get_primary_contact(cr->lc);
/*sending out of calls*/
op = sal_op_new(cr->lc->sal);
linphone_configure_op(cr->lc, op, cr->peer_url, NULL,
lp_config_get_int(cr->lc->config, "sip", "chat_msg_with_contact", 0));
content = linphone_chat_room_create_is_composing_xml(cr);
if (content != NULL) {
sal_message_send(op, identity, cr->peer, "application/im-iscomposing+xml", content, NULL);
ms_free(content);
sal_op_unref(op);
}
}
static int linphone_chat_room_stop_composing(void *data, unsigned int revents) {
LinphoneChatRoom *cr = (LinphoneChatRoom *)data;
cr->is_composing = LinphoneIsComposingIdle;
linphone_chat_room_send_is_composing_notification(cr);
linphone_chat_room_delete_composing_refresh_timer(cr);
belle_sip_object_unref(cr->composing_idle_timer);
cr->composing_idle_timer = NULL;
return BELLE_SIP_STOP;
}
static int linphone_chat_room_refresh_composing(void *data, unsigned int revents) {
LinphoneChatRoom *cr = (LinphoneChatRoom *)data;
linphone_chat_room_send_is_composing_notification(cr);
return BELLE_SIP_CONTINUE;
}
void linphone_chat_room_compose(LinphoneChatRoom *cr) {
int idle_timeout =
lp_config_get_int(cr->lc->config, "sip", "composing_idle_timeout", COMPOSING_DEFAULT_IDLE_TIMEOUT);
int refresh_timeout =
lp_config_get_int(cr->lc->config, "sip", "composing_refresh_timeout", COMPOSING_DEFAULT_REFRESH_TIMEOUT);
if (cr->is_composing == LinphoneIsComposingIdle) {
cr->is_composing = LinphoneIsComposingActive;
linphone_chat_room_send_is_composing_notification(cr);
if (!cr->composing_refresh_timer) {
cr->composing_refresh_timer = sal_create_timer(cr->lc->sal, linphone_chat_room_refresh_composing, cr,
refresh_timeout * 1000, "composing refresh timeout");
} else {
belle_sip_source_set_timeout(cr->composing_refresh_timer, refresh_timeout * 1000);
}
if (!cr->composing_idle_timer) {
cr->composing_idle_timer = sal_create_timer(cr->lc->sal, linphone_chat_room_stop_composing, cr,
idle_timeout * 1000, "composing idle timeout");
}
}
belle_sip_source_set_timeout(cr->composing_idle_timer, idle_timeout * 1000);
}
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 ";
}
return NULL;
}
LinphoneChatRoom *linphone_chat_message_get_chat_room(LinphoneChatMessage *msg) {
return msg->chat_room;
}
const LinphoneAddress *linphone_chat_message_get_peer_address(LinphoneChatMessage *msg) {
return linphone_chat_room_get_peer_address(msg->chat_room);
}
void linphone_chat_message_set_user_data(LinphoneChatMessage *msg, void *ud) {
msg->message_userdata = ud;
}
void *linphone_chat_message_get_user_data(const LinphoneChatMessage *msg) {
return msg->message_userdata;
}
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;
}
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);
}
const LinphoneContent *linphone_chat_message_get_file_transfer_information(const 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,
const uint8_t *buffer, size_t size) {
LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
LinphoneCore *lc = msg->chat_room->lc;
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;
}
if (linphone_content_get_key(msg->file_transfer_information) !=
NULL) { /* we have a key, we must decrypt the file */
/* get data from callback to a plainBuffer */
char *plainBuffer = (char *)malloc(size);
lime_decryptFile(linphone_content_get_cryptoContext_address(msg->file_transfer_information),
(unsigned char *)linphone_content_get_key(msg->file_transfer_information), size, plainBuffer,
(char *)buffer);
if (linphone_chat_message_cbs_get_file_transfer_recv(msg->callbacks)) {
LinphoneBuffer *lb = linphone_buffer_new_from_data((unsigned char *)plainBuffer, size);
linphone_chat_message_cbs_get_file_transfer_recv(msg->callbacks)(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, plainBuffer, size);
}
free(plainBuffer);
} else { /* regular file, no deciphering */
if (linphone_chat_message_cbs_get_file_transfer_recv(msg->callbacks)) {
LinphoneBuffer *lb = linphone_buffer_new_from_data(buffer, size);
linphone_chat_message_cbs_get_file_transfer_recv(msg->callbacks)(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, (char *)buffer, size);
}
}
return;
}
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) {
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*/
LinphoneChatMessage *msg =
(LinphoneChatMessage *)belle_sip_object_data_get(BELLE_SIP_OBJECT(event->request), "msg");
belle_sip_message_t *response = BELLE_SIP_MESSAGE(event->response);
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);
}
if (msg->file_transfer_filepath == NULL) {
belle_sip_message_set_body_handler(
(belle_sip_message_t *)event->response,
(belle_sip_body_handler_t *)belle_sip_user_body_handler_new(
body_size, linphone_chat_message_file_transfer_on_progress, on_recv_body, NULL, msg));
} else {
belle_sip_body_handler_t *bh = (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(bh) == 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(bh, body_size);
}
belle_sip_message_set_body_handler((belle_sip_message_t *)event->response, bh);
}
}
}
static void linphone_chat_process_response_from_get_file(void *data, const belle_http_response_event_t *event) {
/* check the answer code */
if (event->response) {
int code = belle_http_response_get_status_code(event->response);
if (code == 200) {
LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
LinphoneCore *lc = msg->chat_room->lc;
/* if the file was encrypted, finish the decryption and free context */
if (linphone_content_get_key(msg->file_transfer_information) != NULL) {
lime_decryptFile(linphone_content_get_cryptoContext_address(msg->file_transfer_information), NULL, 0,
NULL, NULL);
}
/* file downloaded succesfully, call again the callback with size at zero */
if (linphone_chat_message_cbs_get_file_transfer_recv(msg->callbacks)) {
LinphoneBuffer *lb = linphone_buffer_new();
linphone_chat_message_cbs_get_file_transfer_recv(msg->callbacks)(msg, msg->file_transfer_information,
lb);
linphone_buffer_unref(lb);
} else {
linphone_core_notify_file_transfer_recv(lc, msg, msg->file_transfer_information, NULL, 0);
}
linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferDone);
}
}
}
void linphone_chat_message_download_file(LinphoneChatMessage *msg) {
belle_http_request_listener_callbacks_t cbs = {0};
belle_http_request_listener_t *l;
belle_generic_uri_t *uri;
const char *url = msg->external_body_url;
char *ua;
if (url == NULL) {
ms_error("Cannot download file from chat msg [%p] because url is NULL", msg);
return;
}
ua = ms_strdup_printf("%s/%s", linphone_core_get_user_agent_name(), linphone_core_get_user_agent_version());
uri = belle_generic_uri_parse(url);
msg->http_request = belle_http_request_create("GET", uri, belle_sip_header_create("User-Agent", ua), NULL);
belle_sip_object_ref(msg->http_request); /* keep a reference on the request to be able to cancel the download */
ms_free(ua);
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 = process_io_error_download;
cbs.process_auth_requested = process_auth_requested_download;
l = belle_http_request_listener_create_from_callbacks(&cbs, (void *)msg);
belle_sip_object_data_set(BELLE_SIP_OBJECT(msg->http_request), "msg", (void *)msg, NULL);
linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress); /* start the download, status is In Progress */
belle_http_provider_send_request(msg->chat_room->lc->http_provider, msg->http_request, l);
}
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) {
if (msg->http_request) {
if (!belle_http_request_is_cancelled(msg->http_request)) {
ms_message("Cancelling file transfer %s - msg [%p] chat room[%p]",
(msg->external_body_url == NULL) ? linphone_core_get_file_transfer_server(msg->chat_room->lc)
: msg->external_body_url,
msg, msg->chat_room);
belle_http_provider_cancel_request(msg->chat_room->lc->http_provider, msg->http_request);
belle_sip_object_unref(msg->http_request);
msg->http_request = NULL;
linphone_chat_message_set_state(msg, LinphoneChatMessageStateNotDelivered);
}
} else {
ms_message("No existing file transfer - nothing to cancel");
}
}
void linphone_chat_message_set_from_address(LinphoneChatMessage *msg, const LinphoneAddress *from) {
if (msg->from)
linphone_address_destroy(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_destroy(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 msg->chat_room->peer_url;
}
return NULL;
}
LinphoneAddress *linphone_chat_message_get_local_address(const LinphoneChatMessage *msg) {
return msg->dir == LinphoneChatMessageOutgoing ? msg->from : msg->to;
}
time_t linphone_chat_message_get_time(const LinphoneChatMessage *msg) {
return msg->time;
}
LinphoneChatMessageState linphone_chat_message_get_state(const LinphoneChatMessage *msg) {
return msg->state;
}
const char *linphone_chat_message_get_text(const LinphoneChatMessage *msg) {
return msg->message;
}
void linphone_chat_message_add_custom_header(LinphoneChatMessage *msg, const char *header_name,
const char *header_value) {
msg->custom_headers = sal_custom_header_append(msg->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->custom_headers, header_name);
}
bool_t linphone_chat_message_is_read(LinphoneChatMessage *msg) {
return msg->is_read;
}
bool_t linphone_chat_message_is_outgoing(LinphoneChatMessage *msg) {
return msg->dir == LinphoneChatMessageOutgoing;
}
unsigned int linphone_chat_message_get_storage_id(LinphoneChatMessage *msg) {
return msg->storage_id;
}
LinphoneChatMessage *linphone_chat_message_clone(const LinphoneChatMessage *msg) {
/*struct _LinphoneChatMessage {
char* msg;
LinphoneChatRoom* chat_room;
LinphoneChatMessageStateChangeCb cb;
void* cb_ud;
void* message_userdata;
char* external_body_url;
LinphoneAddress* from;
time_t time;
SalCustomHeader *custom_headers;
LinphoneChatMessageState state;
};*/
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_destroy(LinphoneChatMessage *msg) {
belle_sip_object_unref(msg);
}
static void _linphone_chat_message_destroy(LinphoneChatMessage *msg) {
if (msg->op)
sal_op_release(msg->op);
if (msg->message)
ms_free(msg->message);
if (msg->external_body_url)
ms_free(msg->external_body_url);
if (msg->appdata)
ms_free(msg->appdata);
if (msg->from)
linphone_address_destroy(msg->from);
if (msg->to)
linphone_address_destroy(msg->to);
if (msg->custom_headers)
sal_custom_header_free(msg->custom_headers);
if (msg->content_type)
ms_free(msg->content_type);
if (msg->file_transfer_information) {
linphone_content_unref(msg->file_transfer_information);
}
if (msg->file_transfer_filepath != NULL) {
ms_free(msg->file_transfer_filepath);
}
linphone_chat_message_cbs_unref(msg->callbacks);
}
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);
}
static void linphone_chat_message_release(LinphoneChatMessage *msg) {
/*mark the chat msg as orphan (it has no chat room anymore), and unref it*/
msg->chat_room = NULL;
linphone_chat_message_unref(msg);
}
const LinphoneErrorInfo *linphone_chat_message_get_error_info(const LinphoneChatMessage *msg) {
return linphone_error_info_from_sal_op(msg->op);
}
LinphoneReason linphone_chat_message_get_reason(LinphoneChatMessage *msg) {
return linphone_error_info_get_reason(linphone_chat_message_get_error_info(msg));
}
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);
}
const char *linphone_chat_message_get_file_transfer_filepath(LinphoneChatMessage *msg) {
return msg->file_transfer_filepath;
}
LinphoneChatMessageCbs *linphone_chat_message_get_callbacks(const LinphoneChatMessage *msg) {
return msg->callbacks;
}
LinphoneChatMessage *linphone_chat_room_create_file_transfer_message(LinphoneChatRoom *cr,
const LinphoneContent *initial_content) {
LinphoneChatMessage *msg = belle_sip_object_new(LinphoneChatMessage);
msg->callbacks = linphone_chat_message_cbs_new();
msg->chat_room = (LinphoneChatRoom *)cr;
msg->message = NULL;
msg->is_read = TRUE;
msg->file_transfer_information = linphone_content_copy(initial_content);
msg->dir = LinphoneChatMessageOutgoing;
linphone_chat_message_set_to(msg, linphone_chat_room_get_peer_address(cr));
linphone_chat_message_set_from(msg, linphone_address_new(linphone_core_get_identity(cr->lc)));
msg->content_type =
NULL; /* this will be set to application/vnd.gsma.rcs-ft-http+xml when we will transfer the xml reply from
server to the peers */
msg->http_request = NULL; /* this will store the http request during file upload to the server */
return msg;
}