forked from mirrors/linphone-iphone
1726 lines
63 KiB
C
1726 lines
63 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 "linphone/core.h"
|
|
#include "private.h"
|
|
#include "linphone/lpconfig.h"
|
|
#include "belle-sip/belle-sip.h"
|
|
#include "ortp/b64.h"
|
|
#include "linphone/wrapper_utils.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
|
|
|
|
static void linphone_chat_message_release(LinphoneChatMessage *msg);
|
|
static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr);
|
|
static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom *cr);
|
|
static void linphone_chat_room_delete_remote_composing_refresh_timer(LinphoneChatRoom *cr);
|
|
static void _linphone_chat_message_destroy(LinphoneChatMessage *msg);
|
|
static void linphone_chat_room_notify_is_composing(LinphoneChatRoom *cr, const char *text);
|
|
static void linphone_chat_room_notify_imdn(LinphoneChatRoom *cr, const char *text);
|
|
static void linphone_chat_message_deactivate(LinphoneChatMessage *msg);
|
|
|
|
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessageCbs);
|
|
|
|
BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatMessageCbs, belle_sip_object_t,
|
|
NULL, // destroy
|
|
NULL, // clone
|
|
NULL, // marshal
|
|
FALSE);
|
|
|
|
LinphoneChatMessageCbs *linphone_chat_message_cbs_new(void) {
|
|
return belle_sip_object_new(LinphoneChatMessageCbs);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessage);
|
|
|
|
static void _linphone_chat_room_destroy(LinphoneChatRoom *cr) {
|
|
bctbx_list_free_with_data(cr->transient_messages, (void (*)(void *))linphone_chat_message_release);
|
|
if (cr->received_rtt_characters) {
|
|
cr->received_rtt_characters = bctbx_list_free_with_data(cr->received_rtt_characters, (void (*)(void *))ms_free);
|
|
}
|
|
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 (bctbx_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 = bctbx_list_remove(cr->lc->chatrooms, cr);
|
|
}
|
|
}
|
|
linphone_address_unref(cr->peer_url);
|
|
if (cr->pending_message)
|
|
linphone_chat_message_destroy(cr->pending_message);
|
|
ms_free(cr->peer);
|
|
if (cr->weak_messages != NULL) bctbx_list_free(cr->weak_messages);
|
|
}
|
|
|
|
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->callbacks)) {
|
|
linphone_chat_message_cbs_get_msg_state_changed(msg->callbacks)(msg, msg->state);
|
|
}
|
|
}
|
|
}
|
|
|
|
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 bctbx_list_t *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);
|
|
}
|
|
|
|
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_base(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;
|
|
cr->received_rtt_characters = NULL;
|
|
return cr;
|
|
}
|
|
|
|
static LinphoneChatRoom *_linphone_core_create_chat_room(LinphoneCore *lc, LinphoneAddress *addr) {
|
|
LinphoneChatRoom *cr = _linphone_core_create_chat_room_base(lc, addr);
|
|
lc->chatrooms = bctbx_list_append(lc->chatrooms, (void *)cr);
|
|
return cr;
|
|
}
|
|
|
|
LinphoneChatRoom *_linphone_core_create_chat_room_from_call(LinphoneCall *call){
|
|
LinphoneChatRoom *cr = _linphone_core_create_chat_room_base(call->core,
|
|
linphone_address_clone(linphone_call_get_remote_address(call)));
|
|
cr->call = call;
|
|
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;
|
|
bctbx_list_t *elem;
|
|
for (elem = lc->chatrooms; elem != NULL; elem = bctbx_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_unref(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 (bctbx_list_find(lc->chatrooms, cr)) {
|
|
lc->chatrooms = bctbx_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;
|
|
}
|
|
}
|
|
|
|
void linphone_chat_room_destroy(LinphoneChatRoom *cr) {
|
|
linphone_chat_room_unref(cr);
|
|
}
|
|
|
|
void linphone_chat_room_release(LinphoneChatRoom *cr) {
|
|
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);
|
|
bctbx_list_for_each(cr->weak_messages, (bctbx_list_iterate_func)linphone_chat_message_deactivate);
|
|
cr->lc = NULL;
|
|
linphone_chat_room_unref(cr);
|
|
}
|
|
|
|
static void on_weak_message_destroy(void *obj, belle_sip_object_t *message_being_destroyed) {
|
|
LinphoneChatRoom *cr = (LinphoneChatRoom *)obj;
|
|
cr->weak_messages = bctbx_list_remove(cr->weak_messages, message_being_destroyed);
|
|
}
|
|
|
|
void linphone_chat_room_add_weak_message(LinphoneChatRoom *cr, LinphoneChatMessage *cm) {
|
|
bctbx_list_t *item = bctbx_list_find(cr->weak_messages, cm);
|
|
if (item == NULL) {
|
|
cr->weak_messages = bctbx_list_append(cr->weak_messages, belle_sip_object_weak_ref(cm, on_weak_message_destroy, 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;
|
|
}
|
|
|
|
void linphone_chat_room_add_transient_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
|
|
if (bctbx_list_find(msg->chat_room->transient_messages, msg) == NULL) {
|
|
cr->transient_messages = bctbx_list_append(cr->transient_messages, linphone_chat_message_ref(msg));
|
|
}
|
|
}
|
|
|
|
void linphone_chat_room_remove_transient_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
|
|
if (bctbx_list_find(msg->chat_room->transient_messages, msg) != NULL) {
|
|
cr->transient_messages = bctbx_list_remove(cr->transient_messages, msg);
|
|
linphone_chat_message_unref(msg);
|
|
}
|
|
}
|
|
|
|
static void store_or_update_chat_message(LinphoneChatMessage *msg) {
|
|
if (msg->storage_id != 0) {
|
|
/* The message has already been stored (probably because of file transfer), update it */
|
|
linphone_chat_message_store_update(msg);
|
|
} else {
|
|
/* Store the new message */
|
|
msg->storage_id = linphone_chat_message_store(msg);
|
|
}
|
|
}
|
|
|
|
void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
|
|
int retval = -1;
|
|
LinphoneCore *lc = cr->lc;
|
|
LinphoneImEncryptionEngine *imee = lc->im_encryption_engine;
|
|
|
|
/*stubed rtt text*/
|
|
if (cr->call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(cr->call))) {
|
|
uint32_t new_line = 0x2028;
|
|
linphone_chat_message_put_char(msg, new_line); // New Line
|
|
linphone_chat_message_unref(msg);
|
|
return;
|
|
}
|
|
|
|
msg->dir = LinphoneChatMessageOutgoing;
|
|
|
|
/* 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) */
|
|
if (linphone_chat_room_upload_file(msg) == 0) {
|
|
/* Add to transient list only if message is going out */
|
|
linphone_chat_room_add_transient_message(cr, msg);
|
|
/* Store the message so that even if the upload is stopped, it can be done again */
|
|
msg->storage_id = linphone_chat_message_store(msg);
|
|
} else {
|
|
linphone_chat_message_unref(msg);
|
|
return;
|
|
}
|
|
} else {
|
|
SalOp *op = msg->op;
|
|
LinphoneCall *call=NULL;
|
|
char *content_type;
|
|
const char *identity = NULL;
|
|
char *clear_text_message = NULL;
|
|
char *clear_text_content_type = NULL;
|
|
|
|
if (msg->message) {
|
|
clear_text_message = ms_strdup(msg->message);
|
|
}
|
|
if (msg->content_type) {
|
|
clear_text_content_type = ms_strdup(msg->content_type);
|
|
}
|
|
|
|
/* Add to transient list */
|
|
linphone_chat_room_add_transient_message(cr, msg);
|
|
msg->time = ms_time(0);
|
|
if (lp_config_get_int(cr->lc->config, "sip", "chat_use_call_dialogs", 0) != 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));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!identity) {
|
|
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);
|
|
}
|
|
}
|
|
if (msg->from){
|
|
/*
|
|
* BUG
|
|
* the file transfer message constructor sets the from, but doesn't do it as well as here.
|
|
*/
|
|
linphone_address_unref(msg->from);
|
|
}
|
|
msg->from = linphone_address_new(identity);
|
|
|
|
if (imee) {
|
|
LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
|
|
LinphoneImEncryptionEngineCbsOutgoingMessageCb cb_process_outgoing_message = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imee_cbs);
|
|
if (cb_process_outgoing_message) {
|
|
retval = cb_process_outgoing_message(imee, cr, msg);
|
|
if(retval == 0) {
|
|
msg->is_secured = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (op == NULL) {
|
|
/*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 (retval > 0) {
|
|
sal_error_info_set((SalErrorInfo *)sal_op_get_error_info(op), SalReasonNotAcceptable, "SIP", retval, "Unable to encrypt IM", NULL);
|
|
store_or_update_chat_message(msg);
|
|
linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered);
|
|
linphone_chat_message_unref(msg);
|
|
return;
|
|
}
|
|
|
|
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 = 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);
|
|
}
|
|
|
|
if (msg->message && clear_text_message && strcmp(msg->message, clear_text_message) != 0) {
|
|
// We replace the encrypted message by the original one so it can be correctly stored and displayed by the application
|
|
ms_free(msg->message);
|
|
msg->message = ms_strdup(clear_text_message);
|
|
}
|
|
if (msg->content_type && clear_text_content_type && (strcmp(msg->content_type, clear_text_content_type) != 0)) {
|
|
/* We replace the encrypted content type by the original one */
|
|
ms_free(msg->content_type);
|
|
msg->content_type = ms_strdup(clear_text_content_type);
|
|
}
|
|
msg->message_id = ms_strdup(sal_op_get_call_id(op)); /* must be known at that time */
|
|
store_or_update_chat_message(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);
|
|
|
|
if (clear_text_message) {
|
|
ms_free(clear_text_message);
|
|
}
|
|
if (clear_text_content_type) {
|
|
ms_free(clear_text_content_type);
|
|
}
|
|
|
|
if (call && call->op == op) {
|
|
/*In this case, chat delivery status is not notified, so unrefing chat message right now*/
|
|
/*Might be better fixed by delivering status, but too costly for now*/
|
|
linphone_chat_room_remove_transient_message(msg->chat_room, msg);
|
|
linphone_chat_message_unref(msg);
|
|
return;
|
|
}
|
|
}
|
|
// if operation failed, we should not change message state
|
|
if (msg->dir == LinphoneChatMessageOutgoing) {
|
|
linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress);
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (bctbx_list_find(msg->chat_room->transient_messages, msg) != NULL) {
|
|
// msg is not transient anymore, we can remove it from our transient list and unref it
|
|
linphone_chat_room_add_weak_message(msg->chat_room, msg);
|
|
linphone_chat_room_remove_transient_message(msg->chat_room, msg);
|
|
} else {
|
|
// msg has already been removed from the transient messages, do nothing. */
|
|
}
|
|
}
|
|
}
|
|
|
|
void linphone_chat_room_send_message(LinphoneChatRoom *cr, const char *msg) {
|
|
_linphone_chat_room_send_message(cr, linphone_chat_room_create_message(cr, msg));
|
|
}
|
|
|
|
static bool_t is_file_transfer(const char *content_type) {
|
|
return (strcmp("application/vnd.gsma.rcs-ft-http+xml", content_type) == 0);
|
|
}
|
|
|
|
static bool_t is_im_iscomposing(const char* content_type) {
|
|
return (strcmp("application/im-iscomposing+xml", content_type) == 0);
|
|
}
|
|
|
|
static bool_t is_imdn(const char *content_type) {
|
|
return (strcmp("message/imdn+xml", content_type) == 0);
|
|
}
|
|
|
|
static bool_t is_text(const char *content_type) {
|
|
return (strcmp("text/plain", content_type) == 0);
|
|
}
|
|
|
|
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);
|
|
if(!is_imdn(msg->content_type) && !is_im_iscomposing(msg->content_type)) {
|
|
cr->remote_is_composing = LinphoneIsComposingIdle;
|
|
linphone_core_notify_is_composing_received(cr->lc, cr);
|
|
linphone_chat_message_send_delivery_notification(msg, LinphoneReasonNone);
|
|
}
|
|
}
|
|
|
|
static void create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(LinphoneChatMessage *msg) {
|
|
xmlChar *file_url = NULL;
|
|
xmlDocPtr xmlMessageBody;
|
|
xmlNodePtr cur;
|
|
/* parse the msg body to get all informations from it */
|
|
xmlMessageBody = xmlParseDoc((const xmlChar *)msg->message);
|
|
msg->file_transfer_information = linphone_content_new();
|
|
|
|
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 if 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")) {
|
|
xmlChar *filename = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
|
|
linphone_content_set_name(msg->file_transfer_information, (char *)filename);
|
|
xmlFree(filename);
|
|
}
|
|
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);
|
|
size_t 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);
|
|
}
|
|
|
|
LinphoneReason linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *sal_msg) {
|
|
LinphoneChatRoom *cr = NULL;
|
|
LinphoneAddress *addr;
|
|
LinphoneAddress *to;
|
|
LinphoneChatMessage *msg = NULL;
|
|
LinphoneImEncryptionEngine *imee = lc->im_encryption_engine;
|
|
const SalCustomHeader *ch;
|
|
LinphoneReason reason = LinphoneReasonNone;
|
|
int retval = -1;
|
|
|
|
addr = linphone_address_new(sal_msg->from);
|
|
linphone_address_clean(addr);
|
|
cr = linphone_core_get_chat_room(lc, addr);
|
|
|
|
/* Check if this is a duplicate message */
|
|
if (linphone_chat_room_find_message(cr, sal_op_get_call_id(op)) != NULL) {
|
|
reason = lc->chat_deny_code;
|
|
goto end;
|
|
}
|
|
|
|
msg = linphone_chat_room_create_message(cr, sal_msg->text);
|
|
linphone_chat_message_set_content_type(msg, sal_msg->content_type);
|
|
linphone_chat_message_set_from(msg, cr->peer_url);
|
|
|
|
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;
|
|
msg->state = LinphoneChatMessageStateDelivered;
|
|
msg->dir = LinphoneChatMessageIncoming;
|
|
msg->message_id = ms_strdup(sal_op_get_call_id(op));
|
|
|
|
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);
|
|
}
|
|
|
|
if (imee) {
|
|
LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
|
|
LinphoneImEncryptionEngineCbsIncomingMessageCb cb_process_incoming_message = linphone_im_encryption_engine_cbs_get_process_incoming_message(imee_cbs);
|
|
if (cb_process_incoming_message) {
|
|
retval = cb_process_incoming_message(imee, cr, msg);
|
|
if(retval == 0) {
|
|
msg->is_secured = TRUE;
|
|
} else if(retval > 0) {
|
|
// Unable to decrypt message
|
|
linphone_core_notify_message_received_unable_decrypt(cr->lc, cr, msg);
|
|
reason = linphone_error_code_to_reason(retval);
|
|
linphone_chat_message_send_delivery_notification(msg, reason);
|
|
// return LinphoneReasonNone to avoid flexisip resending us a message we can't decrypt
|
|
reason = LinphoneReasonNone;
|
|
goto end;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((retval <= 0) && (linphone_core_is_content_type_supported(lc, msg->content_type) == FALSE)) {
|
|
retval = 415;
|
|
ms_error("Unsupported MESSAGE (content-type %s not recognized)", msg->content_type);
|
|
}
|
|
|
|
if (retval > 0) {
|
|
reason = linphone_error_code_to_reason(retval);
|
|
linphone_chat_message_send_delivery_notification(msg, reason);
|
|
goto end;
|
|
}
|
|
|
|
if (is_file_transfer(msg->content_type)) {
|
|
create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(msg);
|
|
linphone_chat_message_set_to_be_stored(msg, TRUE);
|
|
} else if (is_im_iscomposing(msg->content_type)) {
|
|
linphone_chat_room_notify_is_composing(cr, msg->message);
|
|
linphone_chat_message_set_to_be_stored(msg, FALSE);
|
|
if(lp_config_get_int(cr->lc->config, "sip", "deliver_imdn", 0) != 1) {
|
|
goto end;
|
|
}
|
|
} else if (is_imdn(msg->content_type)) {
|
|
linphone_chat_room_notify_imdn(cr, msg->message);
|
|
linphone_chat_message_set_to_be_stored(msg, FALSE);
|
|
if(lp_config_get_int(cr->lc->config, "sip", "deliver_imdn", 0) != 1) {
|
|
goto end;
|
|
}
|
|
} else if (is_text(msg->content_type)) {
|
|
linphone_chat_message_set_to_be_stored(msg, TRUE);
|
|
}
|
|
|
|
linphone_chat_room_message_received(cr, lc, msg);
|
|
|
|
if(linphone_chat_message_get_to_be_stored(msg)) {
|
|
msg->storage_id = linphone_chat_message_store(msg);
|
|
|
|
if (cr->unread_count < 0)
|
|
cr->unread_count = 1;
|
|
else
|
|
cr->unread_count++;
|
|
}
|
|
|
|
end:
|
|
linphone_address_unref(addr);
|
|
if (msg != NULL) linphone_chat_message_unref(msg);
|
|
return reason;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
bool_t linphone_chat_room_is_remote_composing(const LinphoneChatRoom *cr) {
|
|
return (cr->remote_is_composing == LinphoneIsComposingActive) ? TRUE : FALSE;
|
|
}
|
|
|
|
static const char *imdn_prefix = "/imdn:imdn";
|
|
|
|
static void process_imdn(LinphoneChatRoom *cr, xmlparsing_context_t *xml_ctx) {
|
|
char xpath_str[MAX_XPATH_LENGTH];
|
|
xmlXPathObjectPtr imdn_object;
|
|
xmlXPathObjectPtr delivery_status_object;
|
|
xmlXPathObjectPtr display_status_object;
|
|
const char *message_id_str = NULL;
|
|
const char *datetime_str = NULL;
|
|
LinphoneCore *lc = linphone_chat_room_get_core(cr);
|
|
LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc);
|
|
|
|
if (linphone_create_xml_xpath_context(xml_ctx) < 0)
|
|
return;
|
|
|
|
xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"imdn",
|
|
(const xmlChar *)"urn:ietf:params:xml:ns:imdn");
|
|
imdn_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, imdn_prefix);
|
|
if (imdn_object != NULL) {
|
|
if ((imdn_object->nodesetval != NULL) && (imdn_object->nodesetval->nodeNr >= 1)) {
|
|
snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:message-id", imdn_prefix);
|
|
message_id_str = linphone_get_xml_text_content(xml_ctx, xpath_str);
|
|
snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:datetime", imdn_prefix);
|
|
datetime_str = linphone_get_xml_text_content(xml_ctx, xpath_str);
|
|
}
|
|
xmlXPathFreeObject(imdn_object);
|
|
}
|
|
|
|
if ((message_id_str != NULL) && (datetime_str != NULL)) {
|
|
LinphoneChatMessage *cm = linphone_chat_room_find_message(cr, message_id_str);
|
|
if (cm == NULL) {
|
|
ms_warning("Received IMDN for unknown message %s", message_id_str);
|
|
} else {
|
|
snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:delivery-notification/imdn:status", imdn_prefix);
|
|
delivery_status_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, xpath_str);
|
|
snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:display-notification/imdn:status", imdn_prefix);
|
|
display_status_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, xpath_str);
|
|
if ((delivery_status_object != NULL) && (linphone_im_notif_policy_get_recv_imdn_delivered(policy) == TRUE)) {
|
|
if ((delivery_status_object->nodesetval != NULL) && (delivery_status_object->nodesetval->nodeNr >= 1)) {
|
|
xmlNodePtr node = delivery_status_object->nodesetval->nodeTab[0];
|
|
if ((node->children != NULL) && (node->children->name != NULL)) {
|
|
if (strcmp((const char *)node->children->name, "delivered") == 0) {
|
|
linphone_chat_message_update_state(cm, LinphoneChatMessageStateDeliveredToUser);
|
|
} else if (strcmp((const char *)node->children->name, "error") == 0) {
|
|
linphone_chat_message_update_state(cm, LinphoneChatMessageStateNotDelivered);
|
|
}
|
|
}
|
|
}
|
|
xmlXPathFreeObject(delivery_status_object);
|
|
}
|
|
if ((display_status_object != NULL) && (linphone_im_notif_policy_get_recv_imdn_displayed(policy) == TRUE)) {
|
|
if ((display_status_object->nodesetval != NULL) && (display_status_object->nodesetval->nodeNr >= 1)) {
|
|
xmlNodePtr node = display_status_object->nodesetval->nodeTab[0];
|
|
if ((node->children != NULL) && (node->children->name != NULL)) {
|
|
if (strcmp((const char *)node->children->name, "displayed") == 0) {
|
|
linphone_chat_message_update_state(cm, LinphoneChatMessageStateDisplayed);
|
|
}
|
|
}
|
|
}
|
|
xmlXPathFreeObject(display_status_object);
|
|
}
|
|
linphone_chat_message_unref(cm);
|
|
}
|
|
}
|
|
if (message_id_str != NULL) linphone_free_xml_text_content(message_id_str);
|
|
if (datetime_str != NULL) linphone_free_xml_text_content(datetime_str);
|
|
}
|
|
|
|
static void linphone_chat_room_notify_imdn(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_imdn(cr, xml_ctx);
|
|
} else {
|
|
ms_warning("Wrongly formatted IMDN XML: %s", xml_ctx->errorBuffer);
|
|
}
|
|
linphone_xmlparsing_context_destroy(xml_ctx);
|
|
}
|
|
|
|
LinphoneCore *linphone_chat_room_get_lc(LinphoneChatRoom *cr) {
|
|
return linphone_chat_room_get_core(cr);
|
|
}
|
|
|
|
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->state = LinphoneChatMessageStateIdle;
|
|
msg->callbacks = linphone_chat_message_cbs_new();
|
|
msg->chat_room = (LinphoneChatRoom *)cr;
|
|
msg->message = message ? ms_strdup(message) : NULL;
|
|
msg->content_type = ms_strdup("text/plain");
|
|
msg->file_transfer_information = NULL; /* this property is used only when transfering file */
|
|
msg->http_request = NULL;
|
|
msg->time = ms_time(0);
|
|
msg->is_secured = FALSE;
|
|
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) {
|
|
LinphoneChatMessage *msg = linphone_chat_room_create_message(cr, message);
|
|
LinphoneCore *lc = linphone_chat_room_get_core(cr);
|
|
msg->external_body_url = external_body_url ? ms_strdup(external_body_url) : NULL;
|
|
msg->time = time;
|
|
msg->is_secured = FALSE;
|
|
linphone_chat_message_set_state(msg, state);
|
|
if (is_incoming) {
|
|
msg->dir = LinphoneChatMessageIncoming;
|
|
linphone_chat_message_set_from(msg, linphone_chat_room_get_peer_address(cr));
|
|
msg->to = linphone_address_new(linphone_core_get_identity(lc)); /*direct assignment*/
|
|
} else {
|
|
msg->dir = LinphoneChatMessageOutgoing;
|
|
linphone_chat_message_set_to(msg, linphone_chat_room_get_peer_address(cr));
|
|
msg->from = linphone_address_new(linphone_core_get_identity(lc));/*direct assignment*/
|
|
}
|
|
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_room_send_message(cr, msg);
|
|
}
|
|
|
|
void linphone_chat_room_send_chat_message_2(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
|
|
linphone_chat_message_ref(msg);
|
|
_linphone_chat_room_send_message(cr, msg);
|
|
}
|
|
|
|
void linphone_chat_room_send_chat_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
|
|
_linphone_chat_room_send_message(cr, 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);
|
|
_linphone_chat_room_send_message(cr, 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_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;
|
|
LinphoneCore *lc = linphone_chat_room_get_core(cr);
|
|
LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc);
|
|
if (linphone_im_notif_policy_get_send_is_composing(policy) == TRUE) {
|
|
LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(lc, cr->peer_url);
|
|
LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc);
|
|
LinphoneChatMessage *msg = NULL;
|
|
|
|
if (proxy)
|
|
identity = linphone_proxy_config_get_identity(proxy);
|
|
else
|
|
identity = linphone_core_get_primary_contact(lc);
|
|
/*sending out of calls*/
|
|
op = sal_op_new(lc->sal);
|
|
linphone_configure_op(lc, op, cr->peer_url, NULL,
|
|
lp_config_get_int(lc->config, "sip", "chat_msg_with_contact", 0));
|
|
|
|
content = linphone_chat_room_create_is_composing_xml(cr);
|
|
if (content != NULL) {
|
|
int retval = -1;
|
|
LinphoneAddress *from_addr = linphone_address_new(identity);
|
|
LinphoneAddress *to_addr = linphone_address_new(cr->peer);
|
|
msg = linphone_chat_room_create_message(cr, content);
|
|
linphone_chat_message_set_from_address(msg, from_addr);
|
|
linphone_chat_message_set_to_address(msg, to_addr);
|
|
msg->content_type = ms_strdup("application/im-iscomposing+xml");
|
|
|
|
if (imee) {
|
|
LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
|
|
LinphoneImEncryptionEngineCbsOutgoingMessageCb cb_process_outgoing_message = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imee_cbs);
|
|
if (cb_process_outgoing_message) {
|
|
retval = cb_process_outgoing_message(imee, cr, msg);
|
|
}
|
|
}
|
|
|
|
if (retval <= 0) {
|
|
sal_message_send(op, identity, cr->peer, msg->content_type, msg->message, NULL);
|
|
}
|
|
|
|
linphone_chat_message_unref(msg);
|
|
linphone_address_unref(from_addr);
|
|
linphone_address_unref(to_addr);
|
|
ms_free(content);
|
|
sal_op_unref(op);
|
|
}
|
|
}
|
|
}
|
|
|
|
enum ImdnType {
|
|
ImdnTypeDelivery,
|
|
ImdnTypeDisplay
|
|
};
|
|
|
|
static char *linphone_chat_message_create_imdn_xml(LinphoneChatMessage *cm, enum 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;
|
|
}
|
|
|
|
static void linphone_chat_message_send_imdn(LinphoneChatMessage *cm, enum ImdnType imdn_type, LinphoneReason reason) {
|
|
SalOp *op = NULL;
|
|
const char *identity = NULL;
|
|
char *content = NULL;
|
|
LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(cm);
|
|
LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(cr->lc, cr->peer_url);
|
|
LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(cr->lc);
|
|
LinphoneChatMessage *msg;
|
|
|
|
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_message_create_imdn_xml(cm, imdn_type, reason);
|
|
if (content != NULL) {
|
|
int retval = -1;
|
|
LinphoneAddress *from_addr = linphone_address_new(identity);
|
|
LinphoneAddress *to_addr = linphone_address_new(cr->peer);
|
|
msg = linphone_chat_room_create_message(cr, content);
|
|
linphone_chat_message_set_from_address(msg, from_addr);
|
|
linphone_chat_message_set_to_address(msg, to_addr);
|
|
linphone_chat_message_set_content_type(msg, "message/imdn+xml");
|
|
|
|
/* Do not try to encrypt the notification when it is reporting an error (maybe it should be bypassed only for some reasons). */
|
|
if (imee && (reason == LinphoneReasonNone)) {
|
|
LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
|
|
LinphoneImEncryptionEngineCbsOutgoingMessageCb cb_process_outgoing_message = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imee_cbs);
|
|
if (cb_process_outgoing_message) {
|
|
retval = cb_process_outgoing_message(imee, cr, msg);
|
|
}
|
|
}
|
|
|
|
if (retval <= 0) {
|
|
sal_message_send(op, identity, cr->peer, msg->content_type, msg->message, NULL);
|
|
}
|
|
|
|
linphone_chat_message_unref(msg);
|
|
linphone_address_unref(from_addr);
|
|
linphone_address_unref(to_addr);
|
|
ms_free(content);
|
|
}
|
|
sal_op_unref(op);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
static char* utf8_to_char(uint32_t ic) {
|
|
char *result = ms_malloc(sizeof(char) * 5);
|
|
int size = 0;
|
|
if (ic < 0x80) {
|
|
result[0] = ic;
|
|
size = 1;
|
|
} else if (ic < 0x800) {
|
|
result[1] = 0x80 + ((ic & 0x3F));
|
|
result[0] = 0xC0 + ((ic >> 6) & 0x1F);
|
|
size = 2;
|
|
} else if (ic < 0x100000) {
|
|
result[2] = 0x80 + (ic & 0x3F);
|
|
result[1] = 0x80 + ((ic >> 6) & 0x3F);
|
|
result[0] = 0xE0 + ((ic >> 12) & 0xF);
|
|
size = 3;
|
|
} else if (ic < 0x110000) {
|
|
result[3] = 0x80 + (ic & 0x3F);
|
|
result[2] = 0x80 + ((ic >> 6) & 0x3F);
|
|
result[1] = 0x80 + ((ic >> 12) & 0x3F);
|
|
result[0] = 0xF0 + ((ic >> 18) & 0x7);
|
|
size = 4;
|
|
}
|
|
result[size] = '\0';
|
|
return result;
|
|
}
|
|
|
|
void linphone_core_real_time_text_received(LinphoneCore *lc, LinphoneChatRoom *cr, uint32_t character, LinphoneCall *call) {
|
|
uint32_t new_line = 0x2028;
|
|
uint32_t crlf = 0x0D0A;
|
|
uint32_t lf = 0x0A;
|
|
|
|
if (call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(call))) {
|
|
LinphoneChatMessageCharacter *cmc = ms_new0(LinphoneChatMessageCharacter, 1);
|
|
|
|
if (cr->pending_message == NULL) {
|
|
cr->pending_message = linphone_chat_room_create_message(cr, "");
|
|
}
|
|
|
|
cmc->value = character;
|
|
cmc->has_been_read = FALSE;
|
|
cr->received_rtt_characters = bctbx_list_append(cr->received_rtt_characters, (void *)cmc);
|
|
|
|
cr->remote_is_composing = LinphoneIsComposingActive;
|
|
linphone_core_notify_is_composing_received(cr->lc, cr);
|
|
|
|
if (character == new_line || character == crlf || character == lf) {
|
|
// End of message
|
|
LinphoneChatMessage *msg = cr->pending_message;
|
|
ms_debug("New line received, forge a message with content %s", cr->pending_message->message);
|
|
|
|
linphone_chat_message_set_from(msg, cr->peer_url);
|
|
if (msg->to)
|
|
linphone_address_unref(msg->to);
|
|
msg->to = call->dest_proxy ? linphone_address_clone(call->dest_proxy->identity_address) :
|
|
linphone_address_new(linphone_core_get_identity(lc));
|
|
msg->time = ms_time(0);
|
|
msg->state = LinphoneChatMessageStateDelivered;
|
|
msg->dir = LinphoneChatMessageIncoming;
|
|
|
|
if (lp_config_get_int(lc->config, "misc", "store_rtt_messages", 1) == 1) {
|
|
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);
|
|
cr->pending_message = NULL;
|
|
cr->received_rtt_characters = bctbx_list_free_with_data(cr->received_rtt_characters, (void (*)(void *))ms_free);
|
|
} else {
|
|
char *value = utf8_to_char(character);
|
|
cr->pending_message->message = ms_strcat_printf(cr->pending_message->message, value);
|
|
ms_debug("Received RTT character: %s (%lu), pending text is %s", value, (unsigned long)character, cr->pending_message->message);
|
|
ms_free(value);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t linphone_chat_room_get_char(const LinphoneChatRoom *cr) {
|
|
if (cr && cr->received_rtt_characters) {
|
|
bctbx_list_t *characters = cr->received_rtt_characters;
|
|
while (characters != NULL) {
|
|
LinphoneChatMessageCharacter *cmc = (LinphoneChatMessageCharacter *)characters->data;
|
|
if (!cmc->has_been_read) {
|
|
cmc->has_been_read = TRUE;
|
|
return cmc->value;
|
|
}
|
|
characters = bctbx_list_next(characters);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int linphone_chat_message_put_char(LinphoneChatMessage *msg, uint32_t character) {
|
|
LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(msg);
|
|
LinphoneCall *call = cr->call;
|
|
LinphoneCore *lc = cr->lc;
|
|
uint32_t new_line = 0x2028;
|
|
uint32_t crlf = 0x0D0A;
|
|
uint32_t lf = 0x0A;
|
|
|
|
if (!call || !call->textstream) {
|
|
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 = utf8_to_char(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);
|
|
ms_free(value);
|
|
}
|
|
|
|
text_stream_putchar32(call->textstream, character);
|
|
return 0;
|
|
}
|
|
|
|
const char* linphone_chat_message_get_message_id(const LinphoneChatMessage *cm) {
|
|
return cm->message_id;
|
|
}
|
|
|
|
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 ";
|
|
case LinphoneChatMessageStateDeliveredToUser:
|
|
return "LinphoneChatMessageStateDeliveredToUser";
|
|
case LinphoneChatMessageStateDisplayed:
|
|
return "LinphoneChatMessageStateDisplayed";
|
|
}
|
|
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_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;
|
|
}
|
|
|
|
bool_t linphone_chat_message_is_file_transfer(const LinphoneChatMessage *msg) {
|
|
return is_file_transfer(msg->content_type);
|
|
}
|
|
|
|
bool_t linphone_chat_message_is_text(const LinphoneChatMessage *msg) {
|
|
return is_text(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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 msg->chat_room->peer_url;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void linphone_chat_message_remove_custom_header(LinphoneChatMessage *msg, const char *header_name) {
|
|
msg->custom_headers = sal_custom_header_remove(msg->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);
|
|
}
|
|
|
|
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) {
|
|
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->ei)
|
|
linphone_error_info_unref(msg->ei);
|
|
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_unref(msg->from);
|
|
if (msg->to)
|
|
linphone_address_unref(msg->to);
|
|
if (msg->message_id)
|
|
ms_free(msg->message_id);
|
|
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);
|
|
}
|
|
if (msg->callbacks) {
|
|
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_deactivate(LinphoneChatMessage *msg){
|
|
/*mark the chat msg as orphan (it has no chat room anymore)*/
|
|
msg->chat_room = NULL;
|
|
if (msg->file_transfer_information != NULL) {
|
|
linphone_chat_message_cancel_file_transfer(msg);
|
|
}
|
|
}
|
|
|
|
static 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->callbacks;
|
|
}
|
|
|
|
LinphoneCall *linphone_chat_room_get_call(const LinphoneChatRoom *room) {
|
|
return room->call;
|
|
}
|