chat.c 63.2 KB
Newer Older
aymeric's avatar
aymeric committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
/***************************************************************************
 *            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.
 */
24

25
#include "linphone/core.h"
26
#include "private.h"
27
#include "linphone/lpconfig.h"
28
#include "belle-sip/belle-sip.h"
29
#include "ortp/b64.h"
30
#include "linphone/wrapper_utils.h"
31

Ghislain MARY's avatar
Ghislain MARY committed
32
#include <libxml/parser.h>
33
#include <libxml/tree.h>
34 35 36 37 38 39
#include <libxml/xmlwriter.h>

#define COMPOSING_DEFAULT_IDLE_TIMEOUT 15
#define COMPOSING_DEFAULT_REFRESH_TIMEOUT 60
#define COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT 120

Simon Morlat's avatar
Simon Morlat committed
40
static void linphone_chat_message_release(LinphoneChatMessage *msg);
41 42 43 44
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);
Ghislain MARY's avatar
Ghislain MARY committed
45 46
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);
47
static void linphone_chat_message_deactivate(LinphoneChatMessage *msg);
48 49 50 51

BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessageCbs);

BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatMessageCbs, belle_sip_object_t,
52 53 54 55 56
						   NULL, // destroy
						   NULL, // clone
						   NULL, // marshal
						   FALSE);

57 58
LinphoneChatMessageCbs *linphone_chat_message_cbs_new(void) {
	return belle_sip_object_new(LinphoneChatMessageCbs);
59
}
60

61
LinphoneChatMessageCbs *linphone_chat_message_cbs_ref(LinphoneChatMessageCbs *cbs) {
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
	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;
}

78 79
LinphoneChatMessageCbsMsgStateChangedCb
linphone_chat_message_cbs_get_msg_state_changed(const LinphoneChatMessageCbs *cbs) {
80 81 82
	return cbs->msg_state_changed;
}

83 84
void linphone_chat_message_cbs_set_msg_state_changed(LinphoneChatMessageCbs *cbs,
													 LinphoneChatMessageCbsMsgStateChangedCb cb) {
85 86 87
	cbs->msg_state_changed = cb;
}

88
LinphoneChatMessageCbsFileTransferRecvCb linphone_chat_message_cbs_get_file_transfer_recv(const LinphoneChatMessageCbs *cbs) {
89 90 91
	return cbs->file_transfer_recv;
}

92 93
void linphone_chat_message_cbs_set_file_transfer_recv(LinphoneChatMessageCbs *cbs,
													  LinphoneChatMessageCbsFileTransferRecvCb cb) {
94 95 96
	cbs->file_transfer_recv = cb;
}

97
LinphoneChatMessageCbsFileTransferSendCb linphone_chat_message_cbs_get_file_transfer_send(const LinphoneChatMessageCbs *cbs) {
98 99 100
	return cbs->file_transfer_send;
}

101 102
void linphone_chat_message_cbs_set_file_transfer_send(LinphoneChatMessageCbs *cbs,
													  LinphoneChatMessageCbsFileTransferSendCb cb) {
103 104 105
	cbs->file_transfer_send = cb;
}

106 107
LinphoneChatMessageCbsFileTransferProgressIndicationCb
linphone_chat_message_cbs_get_file_transfer_progress_indication(const LinphoneChatMessageCbs *cbs) {
108 109 110
	return cbs->file_transfer_progress_indication;
}

111 112
void linphone_chat_message_cbs_set_file_transfer_progress_indication(
	LinphoneChatMessageCbs *cbs, LinphoneChatMessageCbsFileTransferProgressIndicationCb cb) {
113 114 115
	cbs->file_transfer_progress_indication = cb;
}

116

117
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessage);
118

119
static void _linphone_chat_room_destroy(LinphoneChatRoom *cr) {
120
	bctbx_list_free_with_data(cr->transient_messages, (void (*)(void *))linphone_chat_message_release);
121 122 123
	if (cr->received_rtt_characters) {
		cr->received_rtt_characters = bctbx_list_free_with_data(cr->received_rtt_characters, (void (*)(void *))ms_free);
	}
124 125 126 127
	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) {
128
		if (bctbx_list_find(cr->lc->chatrooms, cr)) {
129 130 131 132 133
			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);
134
			cr->lc->chatrooms = bctbx_list_remove(cr->lc->chatrooms, cr);
135
		}
136
	}
Simon Morlat's avatar
Simon Morlat committed
137
	linphone_address_unref(cr->peer_url);
138 139 140
	if (cr->pending_message)
		linphone_chat_message_destroy(cr->pending_message);
	ms_free(cr->peer);
141
	if (cr->weak_messages != NULL) bctbx_list_free(cr->weak_messages);
142 143
}

144
void linphone_chat_message_set_state(LinphoneChatMessage *msg, LinphoneChatMessageState state) {
145 146
	/* do not invoke callbacks on orphan messages */
	if (state != msg->state && msg->chat_room != NULL) {
147 148 149
		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. */
Ghislain MARY's avatar
Ghislain MARY committed
150 151
			return;
		}
152 153 154 155 156 157 158 159
		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);
160 161 162 163
		}
	}
}

164 165 166 167 168
BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatMessage, belle_sip_object_t,
						   (belle_sip_object_destroy_t)_linphone_chat_message_destroy,
						   NULL, // clone
						   NULL, // marshal
						   FALSE);
169

170 171
void linphone_core_disable_chat(LinphoneCore *lc, LinphoneReason deny_reason) {
	lc->chat_deny_code = deny_reason;
172 173
}

174 175
void linphone_core_enable_chat(LinphoneCore *lc) {
	lc->chat_deny_code = LinphoneReasonNone;
176 177
}

178 179
bool_t linphone_core_chat_enabled(const LinphoneCore *lc) {
	return lc->chat_deny_code != LinphoneReasonNone;
180
}
Simon Morlat's avatar
Simon Morlat committed
181

182
const bctbx_list_t *linphone_core_get_chat_rooms(LinphoneCore *lc) {
183 184 185
	return lc->chatrooms;
}

186 187
static bool_t linphone_chat_room_matches(LinphoneChatRoom *cr, const LinphoneAddress *from) {
	return linphone_address_weak_equal(cr->peer_url, from);
188 189 190 191 192
}

BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatRoom);

BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatRoom, belle_sip_object_t,
193 194 195 196
						   (belle_sip_object_destroy_t)_linphone_chat_room_destroy,
						   NULL, // clone
						   NULL, // marshal
						   FALSE);
197

198
static LinphoneChatRoom *_linphone_core_create_chat_room_base(LinphoneCore *lc, LinphoneAddress *addr){
199 200 201 202
	LinphoneChatRoom *cr = belle_sip_object_new(LinphoneChatRoom);
	cr->lc = lc;
	cr->peer = linphone_address_as_string(addr);
	cr->peer_url = addr;
203
	cr->unread_count = -1;
204
	cr->received_rtt_characters = NULL;
205 206 207 208 209
	return cr;
}

static LinphoneChatRoom *_linphone_core_create_chat_room(LinphoneCore *lc, LinphoneAddress *addr) {
	LinphoneChatRoom *cr = _linphone_core_create_chat_room_base(lc, addr);
210
	lc->chatrooms = bctbx_list_append(lc->chatrooms, (void *)cr);
211
	return cr;
212 213
}

214
LinphoneChatRoom *_linphone_core_create_chat_room_from_call(LinphoneCall *call){
215
	LinphoneChatRoom *cr = _linphone_core_create_chat_room_base(call->core,
216 217 218 219 220
		linphone_address_clone(linphone_call_get_remote_address(call)));
	cr->call = call;
	return cr;
}

221
static LinphoneChatRoom *_linphone_core_create_chat_room_from_url(LinphoneCore *lc, const char *to) {
222 223 224
	LinphoneAddress *parsed_url = NULL;
	if ((parsed_url = linphone_core_interpret_url(lc, to)) != NULL) {
		return _linphone_core_create_chat_room(lc, parsed_url);
aymeric's avatar
aymeric committed
225 226
	}
	return NULL;
227
}
228

229 230
LinphoneChatRoom *_linphone_core_get_chat_room(LinphoneCore *lc, const LinphoneAddress *addr) {
	LinphoneChatRoom *cr = NULL;
231 232
	bctbx_list_t *elem;
	for (elem = lc->chatrooms; elem != NULL; elem = bctbx_list_next(elem)) {
233 234
		cr = (LinphoneChatRoom *)elem->data;
		if (linphone_chat_room_matches(cr, addr)) {
235 236
			break;
		}
237
		cr = NULL;
238 239
	}
	return cr;
240 241
}

242 243
static LinphoneChatRoom *_linphone_core_get_or_create_chat_room(LinphoneCore *lc, const char *to) {
	LinphoneAddress *to_addr = linphone_core_interpret_url(lc, to);
244
	LinphoneChatRoom *ret;
245

246 247
	if (to_addr == NULL) {
		ms_error("linphone_core_get_or_create_chat_room(): Cannot make a valid address with %s", to);
248 249
		return NULL;
	}
250
	ret = _linphone_core_get_chat_room(lc, to_addr);
Simon Morlat's avatar
Simon Morlat committed
251
	linphone_address_unref(to_addr);
252 253
	if (!ret) {
		ret = _linphone_core_create_chat_room_from_url(lc, to);
254 255
	}
	return ret;
256 257
}

258
LinphoneChatRoom *linphone_core_get_chat_room(LinphoneCore *lc, const LinphoneAddress *addr) {
259 260 261
	LinphoneChatRoom *ret = _linphone_core_get_chat_room(lc, addr);
	if (!ret) {
		ret = _linphone_core_create_chat_room(lc, linphone_address_clone(addr));
262
	}
263
	return ret;
264
}
265

266
void linphone_core_delete_chat_room(LinphoneCore *lc, LinphoneChatRoom *cr) {
267 268
	if (bctbx_list_find(lc->chatrooms, cr)) {
		lc->chatrooms = bctbx_list_remove(cr->lc->chatrooms, cr);
Simon Morlat's avatar
Simon Morlat committed
269
		linphone_chat_room_delete_history(cr);
Simon Morlat's avatar
Simon Morlat committed
270
		linphone_chat_room_unref(cr);
271 272
	} else {
		ms_error("linphone_core_delete_chat_room(): chatroom [%p] isn't part of LinphoneCore.", cr);
Simon Morlat's avatar
Simon Morlat committed
273 274 275
	}
}

276
LinphoneChatRoom *linphone_core_get_chat_room_from_uri(LinphoneCore *lc, const char *to) {
277
	return _linphone_core_get_or_create_chat_room(lc, to);
278
}
279 280 281

static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) {
	if (cr->composing_idle_timer) {
282
		if (cr->lc && cr->lc->sal)
283
			sal_cancel_timer(cr->lc->sal, cr->composing_idle_timer);
284 285 286 287 288 289 290
		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) {
291
		if (cr->lc && cr->lc->sal)
292
			sal_cancel_timer(cr->lc->sal, cr->composing_refresh_timer);
293 294 295 296 297 298 299
		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) {
300
		if (cr->lc && cr->lc->sal)
301
			sal_cancel_timer(cr->lc->sal, cr->remote_composing_refresh_timer);
302 303 304 305 306
		belle_sip_object_unref(cr->remote_composing_refresh_timer);
		cr->remote_composing_refresh_timer = NULL;
	}
}

307
void linphone_chat_room_destroy(LinphoneChatRoom *cr) {
308 309 310 311
	linphone_chat_room_unref(cr);
}

void linphone_chat_room_release(LinphoneChatRoom *cr) {
312 313 314
	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);
315
	bctbx_list_for_each(cr->weak_messages, (bctbx_list_iterate_func)linphone_chat_message_deactivate);
316 317
	cr->lc = NULL;
	linphone_chat_room_unref(cr);
318 319
}

320 321 322 323 324 325 326 327 328 329 330 331
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));
	}
}

332
LinphoneChatRoom *linphone_chat_room_ref(LinphoneChatRoom *cr) {
333 334
	belle_sip_object_ref(cr);
	return cr;
335 336 337
}

void linphone_chat_room_unref(LinphoneChatRoom *cr) {
338
	belle_sip_object_unref(cr);
339 340
}

341
void *linphone_chat_room_get_user_data(const LinphoneChatRoom *cr) {
342
	return cr->user_data;
343 344 345
}

void linphone_chat_room_set_user_data(LinphoneChatRoom *cr, void *ud) {
346
	cr->user_data = ud;
347
}
Margaux Clerc's avatar
Margaux Clerc committed
348

349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
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);
	}
}

372
void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
373 374 375 376
	int retval = -1;
	LinphoneCore *lc = cr->lc;
	LinphoneImEncryptionEngine *imee = lc->im_encryption_engine;
	
377 378
	/*stubed rtt text*/
	if (cr->call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(cr->call))) {
379 380
		uint32_t new_line = 0x2028;
		linphone_chat_message_put_char(msg, new_line); // New Line
381
		linphone_chat_message_unref(msg);
382
		return;
383
	}
384

385 386
	msg->dir = LinphoneChatMessageOutgoing;

387
	/* Check if we shall upload a file to a server */
388
	if (msg->file_transfer_information != NULL && msg->content_type == NULL) {
389
		/* open a transaction with the server and send an empty request(RCS5.1 section 3.5.4.8.3.1) */
390
		if (linphone_chat_room_upload_file(msg) == 0) {
391 392 393 394
			/* 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);
395 396 397 398
		} else {
			linphone_chat_message_unref(msg);
			return;
		}
399
	} else {
400
		SalOp *op = msg->op;
401
		LinphoneCall *call=NULL;
402 403
		char *content_type;
		const char *identity = NULL;
404 405 406
		char *clear_text_message = NULL;
		char *clear_text_content_type = NULL;

407
		if (msg->message) {
408 409 410 411
			clear_text_message = ms_strdup(msg->message);
		}
		if (msg->content_type) {
			clear_text_content_type = ms_strdup(msg->content_type);
412
		}
413

414 415
		/* Add to transient list */
		linphone_chat_room_add_transient_message(cr, msg);
416
		msg->time = ms_time(0);
417
		if (lp_config_get_int(cr->lc->config, "sip", "chat_use_call_dialogs", 0) != 0) {
418 419 420 421 422
			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.");
423
					op = call->op;
424 425
					identity = linphone_core_find_best_identity(cr->lc, linphone_call_get_remote_address(call));
				}
426
			}
427
		}
428

429 430 431 432 433 434 435 436 437 438 439 440 441
		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.
			 */
Simon Morlat's avatar
Simon Morlat committed
442
			linphone_address_unref(msg->from);
443 444
		}
		msg->from = linphone_address_new(identity);
445

446 447
		if (imee) {
			LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
448
			LinphoneImEncryptionEngineCbsOutgoingMessageCb cb_process_outgoing_message = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imee_cbs);
449
			if (cb_process_outgoing_message) {
450
				retval = cb_process_outgoing_message(imee, cr, msg);
451 452 453
				if(retval == 0) {
					msg->is_secured = TRUE;
				}
454 455
			}
		}
456

457 458 459 460 461 462
		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*/
463
		}
464

465
		if (retval > 0) {
466
			sal_error_info_set((SalErrorInfo *)sal_op_get_error_info(op), SalReasonNotAcceptable, "SIP", retval, "Unable to encrypt IM", NULL);
467
			store_or_update_chat_message(msg);
468 469 470 471
			linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered);
			linphone_chat_message_unref(msg);
			return;
		}
472

473 474 475 476
		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);
477
		} else {
478
			char *peer_uri = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
479
			const char *content_type = msg->content_type;
480 481 482 483 484 485
			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);
486
		}
487

488
		if (msg->message && clear_text_message && strcmp(msg->message, clear_text_message) != 0) {
489 490
			// We replace the encrypted message by the original one so it can be correctly stored and displayed by the application
			ms_free(msg->message);
491 492 493 494 495 496
			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);
497
		}
Ghislain MARY's avatar
Ghislain MARY committed
498
		msg->message_id = ms_strdup(sal_op_get_call_id(op)); /* must be known at that time */
499
		store_or_update_chat_message(msg);
500

501 502 503 504 505
		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);
Gautier Pelloux-Prayer's avatar
Gautier Pelloux-Prayer committed
506

507 508 509 510 511
		if (clear_text_message) {
			ms_free(clear_text_message);
		}
		if (clear_text_content_type) {
			ms_free(clear_text_content_type);
512 513
		}

514 515 516
		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*/
517
			linphone_chat_room_remove_transient_message(msg->chat_room, msg);
518 519 520
			linphone_chat_message_unref(msg);
			return;
		}
521
	}
522 523 524 525
	// if operation failed, we should not change message state
	if (msg->dir == LinphoneChatMessageOutgoing) {
		linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress);
	}
aymeric's avatar
aymeric committed
526 527
}

528 529 530
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);
531

532
	if (msg->state == LinphoneChatMessageStateDelivered || msg->state == LinphoneChatMessageStateNotDelivered) {
533 534 535
		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);
536
			linphone_chat_room_remove_transient_message(msg->chat_room, msg);
537 538 539
		} else {
			// msg has already been removed from the transient messages, do nothing. */
		}
540 541 542
	}
}

543
void linphone_chat_room_send_message(LinphoneChatRoom *cr, const char *msg) {
544
	_linphone_chat_room_send_message(cr, linphone_chat_room_create_message(cr, msg));
545
}
546

Ghislain MARY's avatar
Ghislain MARY committed
547 548 549 550 551 552 553 554 555 556 557 558
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);
}

559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575
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);
	}
}

Ghislain MARY's avatar
Ghislain MARY committed
576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
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);
}

655
LinphoneReason linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *sal_msg) {
656
	LinphoneChatRoom *cr = NULL;
657
	LinphoneAddress *addr;
658
	LinphoneAddress *to;
Ghislain MARY's avatar
Ghislain MARY committed
659
	LinphoneChatMessage *msg = NULL;
660
	LinphoneImEncryptionEngine *imee = lc->im_encryption_engine;
661
	const SalCustomHeader *ch;
662 663
	LinphoneReason reason = LinphoneReasonNone;
	int retval = -1;
664

665
	addr = linphone_address_new(sal_msg->from);
666
	linphone_address_clean(addr);
667
	cr = linphone_core_get_chat_room(lc, addr);
668

669 670 671 672 673 674
	/* 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;
	}

Ghislain MARY's avatar
Ghislain MARY committed
675
	msg = linphone_chat_room_create_message(cr, sal_msg->text);
Simon Morlat's avatar
Simon Morlat committed
676
	linphone_chat_message_set_content_type(msg, sal_msg->content_type);
677 678 679 680
	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;
Ghislain MARY's avatar
Ghislain MARY committed
681

682 683 684
	msg->time = sal_msg->time;
	msg->state = LinphoneChatMessageStateDelivered;
	msg->dir = LinphoneChatMessageIncoming;
Ghislain MARY's avatar
Ghislain MARY committed
685
	msg->message_id = ms_strdup(sal_op_get_call_id(op));
Ghislain MARY's avatar
Ghislain MARY committed
686

687 688 689 690 691 692 693 694
	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);
	}
Ghislain MARY's avatar
Ghislain MARY committed
695

696 697
	if (imee) {
		LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
698
		LinphoneImEncryptionEngineCbsIncomingMessageCb cb_process_incoming_message = linphone_im_encryption_engine_cbs_get_process_incoming_message(imee_cbs);
699
		if (cb_process_incoming_message) {
700
			retval = cb_process_incoming_message(imee, cr, msg);
701 702
			if(retval == 0) {
				msg->is_secured = TRUE;
703 704 705
			} else if(retval > 0) {
				// Unable to decrypt message
				linphone_core_notify_message_received_unable_decrypt(cr->lc, cr, msg);
706 707 708 709 710
				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;
711
			}
712 713
		}
	}
Ghislain MARY's avatar
Ghislain MARY committed
714 715

	if ((retval <= 0) && (linphone_core_is_content_type_supported(lc, msg->content_type) == FALSE)) {
716 717 718
		retval = 415;
		ms_error("Unsupported MESSAGE (content-type %s not recognized)", msg->content_type);
	}
Ghislain MARY's avatar
Ghislain MARY committed
719

720 721
	if (retval > 0) {
		reason = linphone_error_code_to_reason(retval);
722
		linphone_chat_message_send_delivery_notification(msg, reason);
723 724 725
		goto end;
	}

Ghislain MARY's avatar
Ghislain MARY committed
726 727
	if (is_file_transfer(msg->content_type)) {
		create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(msg);
728
		linphone_chat_message_set_to_be_stored(msg, TRUE);
Ghislain MARY's avatar
Ghislain MARY committed
729 730
	} else if (is_im_iscomposing(msg->content_type)) {
		linphone_chat_room_notify_is_composing(cr, msg->message);
731
		linphone_chat_message_set_to_be_stored(msg, FALSE);
732 733 734
		if(lp_config_get_int(cr->lc->config, "sip", "deliver_imdn", 0) != 1) {
			goto end;
		}
Ghislain MARY's avatar
Ghislain MARY committed
735 736
	} else if (is_imdn(msg->content_type)) {
		linphone_chat_room_notify_imdn(cr, msg->message);
737
		linphone_chat_message_set_to_be_stored(msg, FALSE);
738 739 740
		if(lp_config_get_int(cr->lc->config, "sip", "deliver_imdn", 0) != 1) {
			goto end;
		}
741 742
	} else if (is_text(msg->content_type)) {
		linphone_chat_message_set_to_be_stored(msg, TRUE);
743
	}
744

745
	linphone_chat_room_message_received(cr, lc, msg);
746

747 748 749 750 751 752 753 754
	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++;
	}
755

756
end:
Simon Morlat's avatar
Simon Morlat committed
757
	linphone_address_unref(addr);
Ghislain MARY's avatar
Ghislain MARY committed
758
	if (msg != NULL) linphone_chat_message_unref(msg);
759
	return reason;
aymeric's avatar
aymeric committed
760 761
}

762 763 764 765 766
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;
767
	linphone_core_notify_is_composing_received(cr->lc, cr);
768 769 770 771 772 773 774 775 776 777
	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;
778 779
	int refresh_duration = lp_config_get_int(cr->lc->config, "sip", "composing_remote_refresh_timeout",
											 COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT);
780 781 782
	int i;
	LinphoneIsComposingState state = LinphoneIsComposingIdle;

783 784
	if (linphone_create_xml_xpath_context(xml_ctx) < 0)
		return;
785

786 787
	xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"xsi",
					   (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing");
788
	iscomposing_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, iscomposing_prefix);
789 790
	if (iscomposing_object != NULL) {
		if (iscomposing_object->nodesetval != NULL) {
Simon Morlat's avatar
Simon Morlat committed
791 792 793
			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);
794 795
				if (state_str == NULL)
					continue;
Simon Morlat's avatar
Simon Morlat committed
796 797 798
				snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/xsi:refresh", iscomposing_prefix, i);
				refresh_str = linphone_get_xml_text_content(xml_ctx, xpath_str);
			}
799
		}
Simon Morlat's avatar
Simon Morlat committed
800
		xmlXPathFreeObject(iscomposing_object);
801 802 803 804 805 806 807 808 809
	}

	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) {
810 811 812
				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");
813 814 815 816 817 818 819 820
			} 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;
821
		linphone_core_notify_is_composing_received(cr->lc, cr);
822 823 824 825
		linphone_free_xml_text_content(state_str);
	}
	if (refresh_str != NULL) {
		linphone_free_xml_text_content(refresh_str);
826 827 828 829 830 831
	}
}

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);
832
	xml_ctx->doc = xmlReadDoc((const unsigned char *)text, 0, NULL, 0);
833 834 835 836 837 838 839 840 841 842 843 844
	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;
}

Ghislain MARY's avatar
Ghislain MARY committed
845 846 847 848 849 850 851 852 853
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;
854 855
	LinphoneCore *lc = linphone_chat_room_get_core(cr);
	LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc);
Ghislain MARY's avatar
Ghislain MARY committed
856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881

	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);
882
			if ((delivery_status_object != NULL) && (linphone_im_notif_policy_get_recv_imdn_delivered(policy) == TRUE)) {
Ghislain MARY's avatar
Ghislain MARY committed
883 884
				if ((delivery_status_object->nodesetval != NULL) && (delivery_status_object->nodesetval->nodeNr >= 1)) {
					xmlNodePtr node = delivery_status_object->nodesetval->nodeTab[0];
885 886
					if ((node->children != NULL) && (node->children->name != NULL)) {
						if (strcmp((const char *)node->children->name, "delivered") == 0) {
887
							linphone_chat_message_update_state(cm, LinphoneChatMessageStateDeliveredToUser);
888 889
						} else if (strcmp((const char *)node->children->name, "error") == 0) {
							linphone_chat_message_update_state(cm, LinphoneChatMessageStateNotDelivered);
890
						}
Ghislain MARY's avatar
Ghislain MARY committed
891 892 893 894
					}
				}
				xmlXPathFreeObject(delivery_status_object);
			}
895
			if ((display_status_object != NULL) && (linphone_im_notif_policy_get_recv_imdn_displayed(policy) == TRUE)) {
Ghislain MARY's avatar
Ghislain MARY committed
896 897
				if ((display_status_object->nodesetval != NULL) && (display_status_object->nodesetval->nodeNr >= 1)) {
					xmlNodePtr node = display_status_object->nodesetval->nodeTab[0];
898 899
					if ((node->children != NULL) && (node->children->name != NULL)) {
						if (strcmp((const char *)node->children->name, "displayed") == 0) {
900 901
							linphone_chat_message_update_state(cm, LinphoneChatMessageStateDisplayed);
						}
Ghislain MARY's avatar
Ghislain MARY committed
902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924
					}
				}
				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);
}

925
LinphoneCore *linphone_chat_room_get_lc(LinphoneChatRoom *cr) {
jehan's avatar
jehan committed
926
	return linphone_chat_room_get_core(cr);
927 928
}

929
LinphoneCore *linphone_chat_room_get_core(LinphoneChatRoom *cr) {
930 931 932
	return cr->lc;
}

933
const LinphoneAddress *linphone_chat_room_get_peer_address(LinphoneChatRoom *cr) {
jehan's avatar
jehan committed
934 935
	return cr->peer_url;
}
936

937 938
LinphoneChatMessage *linphone_chat_room_create_message(LinphoneChatRoom *cr, const char *message) {
	LinphoneChatMessage *msg = belle_sip_object_new(LinphoneChatMessage);
939
	msg->state = LinphoneChatMessageStateIdle;
940 941 942
	msg->callbacks = linphone_chat_message_cbs_new();
	msg->chat_room = (LinphoneChatRoom *)cr;
	msg->message = message ? ms_strdup(message) : NULL;
Ghislain MARY's avatar
Ghislain MARY committed
943
	msg->content_type = ms_strdup("text/plain");
johan's avatar
johan committed
944
	msg->file_transfer_information = NULL; /* this property is used only when transfering file */
945
	msg->http_request = NULL;
946
	msg->time = ms_time(0);
947
	msg->is_secured = FALSE;
948 949
	return msg;
}
950

951 952 953
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) {
954
	LinphoneChatMessage *msg = linphone_chat_room_create_message(cr, message);
jehan's avatar
jehan committed
955
	LinphoneCore *lc = linphone_chat_room_get_core(cr);
956 957
	msg->external_body_url = external_body_url ? ms_strdup(external_body_url) : NULL;
	msg->time = time;
958
	msg->is_secured = FALSE;
959
	linphone_chat_message_set_state(msg, state);
960
	if (is_incoming) {
961
		msg->dir = LinphoneChatMessageIncoming;
962
		linphone_chat_message_set_from(msg, linphone_chat_room_get_peer_address(cr));
963
		msg->to = linphone_address_new(linphone_core_get_identity(lc)); /*direct assignment*/
964
	} else {
965
		msg->dir = LinphoneChatMessageOutgoing;
966
		linphone_chat_message_set_to(msg, linphone_chat_room_get_peer_address(cr));
967
		msg->from = linphone_address_new(linphone_core_get_identity(lc));/*direct assignment*/
968 969 970 971
	}
	return msg;
}

972 973 974 975
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;
976 977 978
	_linphone_chat_room_send_message(cr, msg);
}

979
void linphone_chat_room_send_chat_message_2(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
980 981 982 983
	linphone_chat_message_ref(msg);
	_linphone_chat_room_send_message(cr, msg);
}

984 985 986 987
void linphone_chat_room_send_chat_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
	_linphone_chat_room_send_message(cr, msg);
}

988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009
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);
}

1010
static char *linphone_chat_room_create_is_composing_xml(LinphoneChatRoom *cr) {
1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
	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) {
1029 1030
		err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"isComposing",
										  (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing");
1031 1032
	}
	if (err >= 0) {
1033 1034
		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"xsi", NULL,
											(const xmlChar *)"http://www.w3.org/2001/XMLSchema-instance");
1035 1036
	}
	if (err >= 0) {
1037 1038
		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xsi", (const xmlChar *)"schemaLocation", NULL,
											(const xmlChar *)"urn:ietf:params:xml:ns:im-composing iscomposing.xsd");
1039 1040 1041
	}
	if (err >= 0) {
		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"state",
1042 1043
										(cr->is_composing == LinphoneIsComposingActive) ? (const xmlChar *)"active"
																						: (const xmlChar *)"idle");
1044 1045
	}
	if ((err >= 0) && (cr->is_composing == LinphoneIsComposingActive)) {
1046 1047 1048
		char refresh_str[4] = {0};
		int refresh_timeout =
			lp_config_get_int(cr->lc->config, "sip", "composing_refresh_timeout", COMPOSING_DEFAULT_REFRESH_TIMEOUT);
1049
		snprintf(refresh_str, sizeof(refresh_str), "%u", refresh_timeout);
1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071
		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;
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104
	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);
				}
			}
1105

1106 1107
			if (retval <= 0) {
				sal_message_send(op, identity, cr->peer, msg->content_type, msg->message, NULL);
1108 1109
			}

1110
			linphone_chat_message_unref(msg);
Simon Morlat's avatar
Simon Morlat committed
1111 1112
			linphone_address_unref(from_addr);
			linphone_address_unref(to_addr);
1113 1114
			ms_free(content);
			sal_op_unref(op);
1115
		}
1116 1117 1118
	}
}

Ghislain MARY's avatar
Ghislain MARY committed
1119 1120 1121 1122 1123
enum ImdnType {
	ImdnTypeDelivery,
	ImdnTypeDisplay
};

1124
static char *linphone_chat_message_create_imdn_xml(LinphoneChatMessage *cm, enum ImdnType imdn_type, LinphoneReason reason) {
Ghislain MARY's avatar
Ghislain MARY committed
1125 1126 1127 1128 1129
	xmlBufferPtr buf;
	xmlTextWriterPtr writer;
	int err;
	char *content = NULL;
	char *datetime = NULL;
1130 1131 1132 1133 1134
	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;
Ghislain MARY's avatar
Ghislain MARY committed
1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152

	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");
	}
1153 1154 1155
	if ((err >= 0) && (reason != LinphoneReasonNone)) {
		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"linphoneimdn", NULL, (const xmlChar *)"http://www.linphone.org/xsds/imdn.xsd");
	}
Ghislain MARY's avatar
Ghislain MARY committed
1156
	if (err >= 0) {
1157
		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"message-id", (const xmlChar *)message_id);
Ghislain MARY's avatar
Ghislain MARY committed
1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172
	}
	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) {
1173 1174 1175 1176 1177 1178
		if (reason == LinphoneReasonNone) {
			if (imdn_type == ImdnTypeDelivery) {
				err = xmlTextWriterStartElement(writer, (const xmlChar *)"delivered");
			} else {
				err = xmlTextWriterStartElement(writer, (const xmlChar *)"displayed");
			}
Ghislain MARY's avatar
Ghislain MARY committed
1179
		} else {
1180
			err = xmlTextWriterStartElement(writer, (const xmlChar *)"error");
Ghislain MARY's avatar
Ghislain MARY committed
1181 1182 1183
		}
	}
	if (err >= 0) {
1184
		/* Close the "delivered", "displayed" or "error" element. */
Ghislain MARY's avatar
Ghislain MARY committed
1185 1186
		err = xmlTextWriterEndElement(writer);
	}
1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200
	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);
		}
	}
Ghislain MARY's avatar
Ghislain MARY committed
1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225
	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;
}

1226
static void linphone_chat_message_send_imdn(LinphoneChatMessage *cm, enum ImdnType imdn_type, LinphoneReason reason) {
Ghislain MARY's avatar
Ghislain MARY committed
1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243
	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));

1244
	content = linphone_chat_message_create_imdn_xml(cm, imdn_type, reason);
Ghislain MARY's avatar
Ghislain MARY committed
1245 1246 1247 1248 1249 1250 1251
	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);
Simon Morlat's avatar
Simon Morlat committed
1252
		linphone_chat_message_set_content_type(msg, "message/imdn+xml");
Ghislain MARY's avatar
Ghislain MARY committed
1253

1254 1255
		/* 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)) {
Ghislain MARY's avatar
Ghislain MARY committed
1256
			LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
1257
			LinphoneImEncryptionEngineCbsOutgoingMessageCb cb_process_outgoing_message = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imee_cbs);
Ghislain MARY's avatar
Ghislain MARY committed
1258
			if (cb_process_outgoing_message) {
1259
				retval = cb_process_outgoing_message(imee, cr, msg);
Ghislain MARY's avatar
Ghislain MARY committed
1260 1261 1262 1263 1264 1265 1266 1267
			}
		}

		if (retval <= 0) {
			sal_message_send(op, identity, cr->peer, msg->content_type, msg->message, NULL);
		}
		
		linphone_chat_message_unref(msg);
Simon Morlat's avatar
Simon Morlat committed
1268 1269
		linphone_address_unref(from_addr);
		linphone_address_unref(to_addr);
Ghislain MARY's avatar
Ghislain MARY committed
1270 1271 1272 1273 1274
		ms_free(content);
	}
	sal_op_unref(op);
}

1275
void linphone_chat_message_send_delivery_notification(LinphoneChatMessage *cm, LinphoneReason reason) {
1276 1277 1278 1279
	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) {
1280
		linphone_chat_message_send_imdn(cm, ImdnTypeDelivery, reason);
1281
	}
Ghislain MARY's avatar
Ghislain MARY committed
1282 1283 1284
}

void linphone_chat_message_send_display_notification(LinphoneChatMessage *cm) {
1285 1286 1287 1288
	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) {
1289
		linphone_chat_message_send_imdn(cm, ImdnTypeDisplay, LinphoneReasonNone);
1290
	}
Ghislain MARY's avatar
Ghislain MARY committed
1291 1292
}

1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318
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;
}

Sylvain Berfini's avatar
Sylvain Berfini committed
1319
void linphone_core_real_time_text_received(LinphoneCore *lc, LinphoneChatRoom *cr, uint32_t character, LinphoneCall *call) {
1320
	uint32_t new_line = 0x2028;
1321 1322
	uint32_t crlf = 0x0D0A;
	uint32_t lf = 0x0A;
1323

Sylvain Berfini's avatar
Sylvain Berfini committed
1324
	if (call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(call))) {
1325
		LinphoneChatMessageCharacter *cmc = ms_new0(LinphoneChatMessageCharacter, 1);