chat.c 41.3 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 26 27
#include "linphonecore.h"
#include "private.h"
#include "lpconfig.h"
28
#include "belle-sip/belle-sip.h"
29
#include "ortp/b64.h"
30

Ghislain MARY's avatar
Ghislain MARY committed
31
#include <libxml/parser.h>
32
#include <libxml/tree.h>
33 34 35 36 37 38
#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
39
static void linphone_chat_message_release(LinphoneChatMessage *msg);
40 41 42 43
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);
44 45 46 47

BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessageCbs);

BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatMessageCbs, belle_sip_object_t,
48 49 50 51 52
						   NULL, // destroy
						   NULL, // clone
						   NULL, // marshal
						   FALSE);

53 54
LinphoneChatMessageCbs *linphone_chat_message_cbs_new(void) {
	return belle_sip_object_new(LinphoneChatMessageCbs);
55
}
56

57
LinphoneChatMessageCbs *linphone_chat_message_cbs_ref(LinphoneChatMessageCbs *cbs) {
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
	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;
}

74 75
LinphoneChatMessageCbsMsgStateChangedCb
linphone_chat_message_cbs_get_msg_state_changed(const LinphoneChatMessageCbs *cbs) {
76 77 78
	return cbs->msg_state_changed;
}

79 80
void linphone_chat_message_cbs_set_msg_state_changed(LinphoneChatMessageCbs *cbs,
													 LinphoneChatMessageCbsMsgStateChangedCb cb) {
81 82 83
	cbs->msg_state_changed = cb;
}

84
LinphoneChatMessageCbsFileTransferRecvCb linphone_chat_message_cbs_get_file_transfer_recv(const LinphoneChatMessageCbs *cbs) {
85 86 87
	return cbs->file_transfer_recv;
}

88 89
void linphone_chat_message_cbs_set_file_transfer_recv(LinphoneChatMessageCbs *cbs,
													  LinphoneChatMessageCbsFileTransferRecvCb cb) {
90 91 92
	cbs->file_transfer_recv = cb;
}

93
LinphoneChatMessageCbsFileTransferSendCb linphone_chat_message_cbs_get_file_transfer_send(const LinphoneChatMessageCbs *cbs) {
94 95 96
	return cbs->file_transfer_send;
}

97 98
void linphone_chat_message_cbs_set_file_transfer_send(LinphoneChatMessageCbs *cbs,
													  LinphoneChatMessageCbsFileTransferSendCb cb) {
99 100 101
	cbs->file_transfer_send = cb;
}

102 103
LinphoneChatMessageCbsFileTransferProgressIndicationCb
linphone_chat_message_cbs_get_file_transfer_progress_indication(const LinphoneChatMessageCbs *cbs) {
104 105 106
	return cbs->file_transfer_progress_indication;
}

107 108
void linphone_chat_message_cbs_set_file_transfer_progress_indication(
	LinphoneChatMessageCbs *cbs, LinphoneChatMessageCbsFileTransferProgressIndicationCb cb) {
109 110 111
	cbs->file_transfer_progress_indication = cb;
}

112

113
BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessage);
114

115 116 117 118 119 120 121 122 123 124 125 126 127
static void _linphone_chat_room_destroy(LinphoneChatRoom *cr) {
	ms_list_free_with_data(cr->transient_messages, (void (*)(void *))linphone_chat_message_release);
	linphone_chat_room_delete_composing_idle_timer(cr);
	linphone_chat_room_delete_composing_refresh_timer(cr);
	linphone_chat_room_delete_remote_composing_refresh_timer(cr);
	if (cr->lc != NULL) {
		if (ms_list_find(cr->lc->chatrooms, cr)) {
			ms_error("LinphoneChatRoom[%p] is destroyed while still being used by the LinphoneCore. This is abnormal."
					 " linphone_core_get_chat_room() doesn't give a reference, there is no need to call "
					 "linphone_chat_room_unref(). "
					 "In order to remove a chat room from the core, use linphone_core_delete_chat_room().",
					 cr);
			cr->lc->chatrooms = ms_list_remove(cr->lc->chatrooms, cr);
128
		}
129
	}
130 131 132 133
	linphone_address_destroy(cr->peer_url);
	if (cr->pending_message)
		linphone_chat_message_destroy(cr->pending_message);
	ms_free(cr->peer);
134 135
}

136
void linphone_chat_message_set_state(LinphoneChatMessage *msg, LinphoneChatMessageState state) {
137 138
	/* do not invoke callbacks on orphan messages */
	if (state != msg->state && msg->chat_room != NULL) {
139 140 141 142 143 144 145 146
		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);
147 148 149 150
		}
	}
}

151 152 153 154 155
BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatMessage, belle_sip_object_t,
						   (belle_sip_object_destroy_t)_linphone_chat_message_destroy,
						   NULL, // clone
						   NULL, // marshal
						   FALSE);
156

157 158
void linphone_core_disable_chat(LinphoneCore *lc, LinphoneReason deny_reason) {
	lc->chat_deny_code = deny_reason;
159 160
}

161 162
void linphone_core_enable_chat(LinphoneCore *lc) {
	lc->chat_deny_code = LinphoneReasonNone;
163 164
}

165 166
bool_t linphone_core_chat_enabled(const LinphoneCore *lc) {
	return lc->chat_deny_code != LinphoneReasonNone;
167
}
Simon Morlat's avatar
Simon Morlat committed
168

169
const MSList *linphone_core_get_chat_rooms(LinphoneCore *lc) {
170 171 172
	return lc->chatrooms;
}

173 174
static bool_t linphone_chat_room_matches(LinphoneChatRoom *cr, const LinphoneAddress *from) {
	return linphone_address_weak_equal(cr->peer_url, from);
175 176 177 178 179
}

BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatRoom);

BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatRoom, belle_sip_object_t,
180 181 182 183
						   (belle_sip_object_destroy_t)_linphone_chat_room_destroy,
						   NULL, // clone
						   NULL, // marshal
						   FALSE);
184

185
static LinphoneChatRoom *_linphone_core_create_chat_room_base(LinphoneCore *lc, LinphoneAddress *addr){
186 187 188 189
	LinphoneChatRoom *cr = belle_sip_object_new(LinphoneChatRoom);
	cr->lc = lc;
	cr->peer = linphone_address_as_string(addr);
	cr->peer_url = addr;
190
	cr->unread_count = -1;
191
	cr->received_rtt_characters = NULL;
192 193 194 195 196
	return cr;
}

static LinphoneChatRoom *_linphone_core_create_chat_room(LinphoneCore *lc, LinphoneAddress *addr) {
	LinphoneChatRoom *cr = _linphone_core_create_chat_room_base(lc, addr);
197 198
	lc->chatrooms = ms_list_append(lc->chatrooms, (void *)cr);
	return cr;
199 200
}

201
LinphoneChatRoom *_linphone_core_create_chat_room_from_call(LinphoneCall *call){
202
	LinphoneChatRoom *cr = _linphone_core_create_chat_room_base(call->core,
203 204 205 206 207
		linphone_address_clone(linphone_call_get_remote_address(call)));
	cr->call = call;
	return cr;
}

208
static LinphoneChatRoom *_linphone_core_create_chat_room_from_url(LinphoneCore *lc, const char *to) {
209 210 211
	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
212 213
	}
	return NULL;
214
}
215

216 217
LinphoneChatRoom *_linphone_core_get_chat_room(LinphoneCore *lc, const LinphoneAddress *addr) {
	LinphoneChatRoom *cr = NULL;
218
	MSList *elem;
219 220 221
	for (elem = lc->chatrooms; elem != NULL; elem = ms_list_next(elem)) {
		cr = (LinphoneChatRoom *)elem->data;
		if (linphone_chat_room_matches(cr, addr)) {
222 223
			break;
		}
224
		cr = NULL;
225 226
	}
	return cr;
227 228
}

229 230
static LinphoneChatRoom *_linphone_core_get_or_create_chat_room(LinphoneCore *lc, const char *to) {
	LinphoneAddress *to_addr = linphone_core_interpret_url(lc, to);
231
	LinphoneChatRoom *ret;
232

233 234
	if (to_addr == NULL) {
		ms_error("linphone_core_get_or_create_chat_room(): Cannot make a valid address with %s", to);
235 236
		return NULL;
	}
237
	ret = _linphone_core_get_chat_room(lc, to_addr);
238
	linphone_address_destroy(to_addr);
239 240
	if (!ret) {
		ret = _linphone_core_create_chat_room_from_url(lc, to);
241 242
	}
	return ret;
243 244
}

245
LinphoneChatRoom *linphone_core_get_chat_room(LinphoneCore *lc, const LinphoneAddress *addr) {
246 247 248
	LinphoneChatRoom *ret = _linphone_core_get_chat_room(lc, addr);
	if (!ret) {
		ret = _linphone_core_create_chat_room(lc, linphone_address_clone(addr));
249
	}
250
	return ret;
251
}
252

253 254
void linphone_core_delete_chat_room(LinphoneCore *lc, LinphoneChatRoom *cr) {
	if (ms_list_find(lc->chatrooms, cr)) {
Simon Morlat's avatar
Simon Morlat committed
255
		lc->chatrooms = ms_list_remove(cr->lc->chatrooms, cr);
Simon Morlat's avatar
Simon Morlat committed
256
		linphone_chat_room_delete_history(cr);
Simon Morlat's avatar
Simon Morlat committed
257
		linphone_chat_room_unref(cr);
258 259
	} else {
		ms_error("linphone_core_delete_chat_room(): chatroom [%p] isn't part of LinphoneCore.", cr);
Simon Morlat's avatar
Simon Morlat committed
260 261 262
	}
}

263
LinphoneChatRoom *linphone_core_get_chat_room_from_uri(LinphoneCore *lc, const char *to) {
264
	return _linphone_core_get_or_create_chat_room(lc, to);
265
}
266 267 268

static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) {
	if (cr->composing_idle_timer) {
269
		if (cr->lc && cr->lc->sal)
270
			sal_cancel_timer(cr->lc->sal, cr->composing_idle_timer);
271 272 273 274 275 276 277
		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) {
278
		if (cr->lc && cr->lc->sal)
279
			sal_cancel_timer(cr->lc->sal, cr->composing_refresh_timer);
280 281 282 283 284 285 286
		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) {
287
		if (cr->lc && cr->lc->sal)
288
			sal_cancel_timer(cr->lc->sal, cr->remote_composing_refresh_timer);
289 290 291 292 293
		belle_sip_object_unref(cr->remote_composing_refresh_timer);
		cr->remote_composing_refresh_timer = NULL;
	}
}

294
void linphone_chat_room_destroy(LinphoneChatRoom *cr) {
295 296 297
	if (cr->received_rtt_characters) {
		cr->received_rtt_characters = ms_list_free(cr->received_rtt_characters);
	}
298 299 300 301 302 303
	linphone_chat_room_unref(cr);
}

void linphone_chat_room_release(LinphoneChatRoom *cr) {
	cr->lc = NULL;
	linphone_chat_room_unref(cr);
304 305
}

306
LinphoneChatRoom *linphone_chat_room_ref(LinphoneChatRoom *cr) {
307 308
	belle_sip_object_ref(cr);
	return cr;
309 310 311
}

void linphone_chat_room_unref(LinphoneChatRoom *cr) {
312
	belle_sip_object_unref(cr);
313 314
}

315
void *linphone_chat_room_get_user_data(const LinphoneChatRoom *cr) {
316
	return cr->user_data;
317 318 319
}

void linphone_chat_room_set_user_data(LinphoneChatRoom *cr, void *ud) {
320
	cr->user_data = ud;
321
}
Margaux Clerc's avatar
Margaux Clerc committed
322

323
void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
324 325
	/*stubed rtt text*/
	if (cr->call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(cr->call))) {
326 327
		uint32_t new_line = 0x2028;
		linphone_chat_message_put_char(msg, new_line); // New Line
328
		linphone_chat_message_set_state(msg, LinphoneChatMessageStateDelivered);
329
		linphone_chat_message_unref(msg);
330
		return;
331
	}
332

333 334
	msg->dir = LinphoneChatMessageOutgoing;

335 336
	// add to transient list
	cr->transient_messages = ms_list_append(cr->transient_messages, linphone_chat_message_ref(msg));
337

338
	/* Check if we shall upload a file to a server */
339
	if (msg->file_transfer_information != NULL && msg->content_type == NULL) {
340
		/* open a transaction with the server and send an empty request(RCS5.1 section 3.5.4.8.3.1) */
341
		linphone_chat_room_upload_file(msg);
342
	} else {
343 344 345 346
		SalOp *op = NULL;
		LinphoneCall *call;
		char *content_type;
		const char *identity = NULL;
347
		msg->time = ms_time(0);
348
		if (lp_config_get_int(cr->lc->config, "sip", "chat_use_call_dialogs", 0) != 0) {
349 350 351 352 353
			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.");
354
					op = call->op;
355 356
					identity = linphone_core_find_best_identity(cr->lc, linphone_call_get_remote_address(call));
				}
357
			}
358 359 360 361 362 363 364 365 366 367 368 369
		}
		if (op == NULL) {
			LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(cr->lc, cr->peer_url);
			if (proxy) {
				identity = linphone_proxy_config_get_identity(proxy);
			} else
				identity = linphone_core_get_primary_contact(cr->lc);
			/*sending out of calls*/
			msg->op = op = sal_op_new(cr->lc->sal);
			linphone_configure_op(cr->lc, op, cr->peer_url, msg->custom_headers,
								  lp_config_get_int(cr->lc->config, "sip", "chat_msg_with_contact", 0));
			sal_op_set_user_pointer(op, msg); /*if out of call, directly store msg*/
370 371
		}

372 373 374 375
		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);
376
		} else {
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
			char *peer_uri = linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(cr));
			const char *content_type;

			if (linphone_core_lime_enabled(cr->lc)) {
				/* ref the msg or it may be destroyed by callback if the encryption failed */
				if (msg->content_type && strcmp(msg->content_type, "application/vnd.gsma.rcs-ft-http+xml") == 0) {
					/* it's a file transfer, content type shall be set to
					application/cipher.vnd.gsma.rcs-ft-http+xml*/
					content_type = "application/cipher.vnd.gsma.rcs-ft-http+xml";
				} else {
					content_type = "xml/cipher";
				}
			} else {
				content_type = msg->content_type;
			}

			if (content_type == NULL) {
				sal_text_send(op, identity, cr->peer, msg->message);
			} else {
				sal_message_send(op, identity, cr->peer, content_type, msg->message, peer_uri);
			}
			ms_free(peer_uri);
399
		}
400

401 402 403 404 405 406 407
		if (msg->from){
			/*
			 * BUG
			 * the file transfer message constructor sets the from, but doesn't do it as well as here.
			 */
			linphone_address_destroy(msg->from);
		}
408 409
		msg->from = linphone_address_new(identity);
		msg->storage_id = linphone_chat_message_store(msg);
410

411 412
		if (cr->unread_count >= 0 && !msg->is_read)
			cr->unread_count++;
413

414 415 416 417 418
		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);
419

420
	}
421
	linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress);
aymeric's avatar
aymeric committed
422 423
}

424 425 426
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);
427

428
	if (msg->state == LinphoneChatMessageStateDelivered || msg->state == LinphoneChatMessageStateNotDelivered) {
429
		// msg is not transient anymore, we can remove it from our transient list and unref it
430 431
		msg->chat_room->transient_messages = ms_list_remove(msg->chat_room->transient_messages, msg);
		linphone_chat_message_unref(msg);
432 433 434
	}
}

435
void linphone_chat_room_send_message(LinphoneChatRoom *cr, const char *msg) {
436
	_linphone_chat_room_send_message(cr, linphone_chat_room_create_message(cr, msg));
437
}
438

439 440
void linphone_chat_room_message_received(LinphoneChatRoom *cr, LinphoneCore *lc, LinphoneChatMessage *msg) {
	if (msg->message) {
Simon Morlat's avatar
Simon Morlat committed
441
		/*legacy API*/
442
		linphone_core_notify_text_message_received(lc, cr, msg->from, msg->message);
Simon Morlat's avatar
Simon Morlat committed
443
	}
444
	linphone_core_notify_message_received(lc, cr, msg);
445 446
	cr->remote_is_composing = LinphoneIsComposingIdle;
	linphone_core_notify_is_composing_received(cr->lc, cr);
Simon Morlat's avatar
Simon Morlat committed
447 448
}

449 450
void linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *sal_msg) {
	LinphoneChatRoom *cr = NULL;
451
	LinphoneAddress *addr;
452
	LinphoneChatMessage *msg;
453
	const SalCustomHeader *ch;
454

455
	addr = linphone_address_new(sal_msg->from);
456
	linphone_address_clean(addr);
457
	cr = linphone_core_get_chat_room(lc, addr);
458

459 460 461
	if (sal_msg->content_type !=
		NULL) { /* content_type field is, for now, used only for rcs file transfer but we shall strcmp it with
				   "application/vnd.gsma.rcs-ft-http+xml" */
462 463 464 465
		xmlChar *file_url = NULL;
		xmlDocPtr xmlMessageBody;
		xmlNodePtr cur;

466 467 468
		msg = linphone_chat_room_create_message(cr, NULL); /* create a msg with empty body */
		msg->content_type =
			ms_strdup(sal_msg->content_type); /* add the content_type "application/vnd.gsma.rcs-ft-http+xml" */
469
		msg->file_transfer_information = linphone_content_new();
470

471
		/* parse the msg body to get all informations from it */
472
		xmlMessageBody = xmlParseDoc((const xmlChar *)sal_msg->text);
473

474
		cur = xmlDocGetRootElement(xmlMessageBody);
475 476
		if (cur != NULL) {
			cur = cur->xmlChildrenNode;
477 478 479 480
			while (cur != NULL) {
				if (!xmlStrcmp(
						cur->name, (const xmlChar *)"file-info")) { /* we found a file info node, check it has a
																	   type="file" attribute */
481
					xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type");
482
					if (!xmlStrcmp(typeAttribute, (const xmlChar *)"file")) { /* this is the node we are looking for */
483
						cur = cur->xmlChildrenNode; /* now loop on the content of the file-info node */
484
						while (cur != NULL) {
485
							if (!xmlStrcmp(cur->name, (const xmlChar *)"file-size")) {
486
								xmlChar *fileSizeString = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
487 488
								linphone_content_set_size(msg->file_transfer_information,
														  strtol((const char *)fileSizeString, NULL, 10));
489 490 491
								xmlFree(fileSizeString);
							}

492
							if (!xmlStrcmp(cur->name, (const xmlChar *)"file-name")) {
493
								xmlChar *filename = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
494 495
								linphone_content_set_name(
									msg->file_transfer_information,
496 497
									(char *)filename);
								xmlFree(filename);
498
							}
499
							if (!xmlStrcmp(cur->name, (const xmlChar *)"content-type")) {
500 501
								xmlChar *contentType = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
								int contentTypeIndex = 0;
502 503
								char *type;
								char *subtype;
504
								while (contentType[contentTypeIndex] != '/' && contentType[contentTypeIndex] != '\0') {
505 506
									contentTypeIndex++;
								}
507
								type = ms_strndup((char *)contentType, contentTypeIndex);
508
								subtype = ms_strdup(((char *)contentType + contentTypeIndex + 1));
509 510 511 512
								linphone_content_set_type(msg->file_transfer_information, type);
								linphone_content_set_subtype(msg->file_transfer_information, subtype);
								ms_free(subtype);
								ms_free(type);
513 514
								xmlFree(contentType);
							}
515
							if (!xmlStrcmp(cur->name, (const xmlChar *)"data")) {
516
								file_url = xmlGetProp(cur, (const xmlChar *)"url");
517 518
							}

519 520 521
							if (!xmlStrcmp(cur->name,
										   (const xmlChar *)"file-key")) { /* there is a key in the msg: file has
																			  been encrypted */
522 523
								/* convert the key from base 64 */
								xmlChar *keyb64 = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
524
								size_t keyLength = b64_decode((char *)keyb64, strlen((char *)keyb64), NULL, 0);
525 526 527
								uint8_t *keyBuffer = (uint8_t *)malloc(keyLength);
								/* decode the key into local key buffer */
								b64_decode((char *)keyb64, strlen((char *)keyb64), keyBuffer, keyLength);
528 529 530
								linphone_content_set_key(
									msg->file_transfer_information, (char *)keyBuffer,
									keyLength); /* duplicate key value into the linphone content private structure */
531
								xmlFree(keyb64);
532
								free(keyBuffer);
533 534
							}

535
							cur = cur->next;
536 537 538 539 540 541 542 543 544 545
						}
						xmlFree(typeAttribute);
						break;
					}
					xmlFree(typeAttribute);
				}
				cur = cur->next;
			}
		}
		xmlFreeDoc(xmlMessageBody);
546

547 548
		linphone_chat_message_set_external_body_url(msg, (const char *)file_url);
		xmlFree(file_url);
549
	} else { /* msg is not rcs file transfer, create it with provided sal_msg->text as ->msg */
550 551
		msg = linphone_chat_room_create_message(cr, sal_msg->text);
	}
552
	linphone_chat_message_set_from(msg, cr->peer_url);
553

Simon Morlat's avatar
Simon Morlat committed
554 555
	{
		LinphoneAddress *to;
556 557 558
		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;
Simon Morlat's avatar
Simon Morlat committed
559
	}
560

561
	msg->time = sal_msg->time;
562
	msg->state = LinphoneChatMessageStateDelivered;
563 564 565 566 567
	msg->is_read = FALSE;
	msg->dir = LinphoneChatMessageIncoming;
	ch = sal_op_get_recv_custom_header(op);
	if (ch)
		msg->custom_headers = sal_custom_header_clone(ch);
568

569 570
	if (sal_msg->url) {
		linphone_chat_message_set_external_body_url(msg, sal_msg->url);
571
	}
572

573
	linphone_address_destroy(addr);
574
	msg->storage_id = linphone_chat_message_store(msg);
575

576 577 578 579
	if (cr->unread_count < 0)
		cr->unread_count = 1;
	else
		cr->unread_count++;
580

581
	linphone_chat_room_message_received(cr, lc, msg);
582
	linphone_chat_message_unref(msg);
aymeric's avatar
aymeric committed
583 584
}

585 586 587 588 589
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;
590
	linphone_core_notify_is_composing_received(cr->lc, cr);
591 592 593 594 595 596 597 598 599 600
	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;
601 602
	int refresh_duration = lp_config_get_int(cr->lc->config, "sip", "composing_remote_refresh_timeout",
											 COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT);
603 604 605
	int i;
	LinphoneIsComposingState state = LinphoneIsComposingIdle;

606 607
	if (linphone_create_xml_xpath_context(xml_ctx) < 0)
		return;
608

609 610
	xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"xsi",
					   (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing");
611
	iscomposing_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, iscomposing_prefix);
612 613
	if (iscomposing_object != NULL) {
		if (iscomposing_object->nodesetval != NULL) {
Simon Morlat's avatar
Simon Morlat committed
614 615 616
			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);
617 618
				if (state_str == NULL)
					continue;
Simon Morlat's avatar
Simon Morlat committed
619 620 621
				snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/xsi:refresh", iscomposing_prefix, i);
				refresh_str = linphone_get_xml_text_content(xml_ctx, xpath_str);
			}
622
		}
Simon Morlat's avatar
Simon Morlat committed
623
		xmlXPathFreeObject(iscomposing_object);
624 625 626 627 628 629 630 631 632
	}

	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) {
633 634 635
				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");
636 637 638 639 640 641 642 643
			} 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;
644
		linphone_core_notify_is_composing_received(cr->lc, cr);
645 646 647 648
		linphone_free_xml_text_content(state_str);
	}
	if (refresh_str != NULL) {
		linphone_free_xml_text_content(refresh_str);
649 650 651 652 653 654
	}
}

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);
655
	xml_ctx->doc = xmlReadDoc((const unsigned char *)text, 0, NULL, 0);
656 657 658 659 660 661 662 663 664
	if (xml_ctx->doc != NULL) {
		process_im_is_composing_notification(cr, xml_ctx);
	} else {
		ms_warning("Wrongly formatted presence XML: %s", xml_ctx->errorBuffer);
	}
	linphone_xmlparsing_context_destroy(xml_ctx);
}

void linphone_core_is_composing_received(LinphoneCore *lc, SalOp *op, const SalIsComposing *is_composing) {
Simon Morlat's avatar
Simon Morlat committed
665 666
	LinphoneAddress *addr = linphone_address_new(is_composing->from);
	LinphoneChatRoom *cr = _linphone_core_get_chat_room(lc, addr);
667 668 669
	if (cr != NULL) {
		linphone_chat_room_notify_is_composing(cr, is_composing->text);
	}
Simon Morlat's avatar
Simon Morlat committed
670
	linphone_address_destroy(addr);
671 672 673 674 675 676
}

bool_t linphone_chat_room_is_remote_composing(const LinphoneChatRoom *cr) {
	return (cr->remote_is_composing == LinphoneIsComposingActive) ? TRUE : FALSE;
}

677
LinphoneCore *linphone_chat_room_get_lc(LinphoneChatRoom *cr) {
678 679 680
	return cr->lc;
}

681
LinphoneCore *linphone_chat_room_get_core(LinphoneChatRoom *cr) {
682 683 684
	return cr->lc;
}

685
const LinphoneAddress *linphone_chat_room_get_peer_address(LinphoneChatRoom *cr) {
jehan's avatar
jehan committed
686 687
	return cr->peer_url;
}
688

689 690
LinphoneChatMessage *linphone_chat_room_create_message(LinphoneChatRoom *cr, const char *message) {
	LinphoneChatMessage *msg = belle_sip_object_new(LinphoneChatMessage);
691
	msg->state = LinphoneChatMessageStateIdle;
692 693 694 695 696
	msg->callbacks = linphone_chat_message_cbs_new();
	msg->chat_room = (LinphoneChatRoom *)cr;
	msg->message = message ? ms_strdup(message) : NULL;
	msg->is_read = TRUE;
	msg->content_type = NULL;			   /* this property is used only when transfering file */
johan's avatar
johan committed
697
	msg->file_transfer_information = NULL; /* this property is used only when transfering file */
698
	msg->http_request = NULL;
699
	msg->time = ms_time(0);
700 701
	return msg;
}
702

703 704 705
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) {
706
	LinphoneChatMessage *msg = linphone_chat_room_create_message(cr, message);
707 708 709 710
	LinphoneCore *lc = linphone_chat_room_get_lc(cr);
	msg->external_body_url = external_body_url ? ms_strdup(external_body_url) : NULL;
	msg->time = time;
	msg->is_read = is_read;
711
	linphone_chat_message_set_state(msg, state);
712
	if (is_incoming) {
713
		msg->dir = LinphoneChatMessageIncoming;
714
		linphone_chat_message_set_from(msg, linphone_chat_room_get_peer_address(cr));
715
		msg->to = linphone_address_new(linphone_core_get_identity(lc)); /*direct assignment*/
716
	} else {
717
		msg->dir = LinphoneChatMessageOutgoing;
718
		linphone_chat_message_set_to(msg, linphone_chat_room_get_peer_address(cr));
719
		msg->from = linphone_address_new(linphone_core_get_identity(lc));/*direct assignment*/
720 721 722 723
	}
	return msg;
}

724 725 726 727
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;
728 729 730
	_linphone_chat_room_send_message(cr, msg);
}

731 732 733 734
void linphone_chat_room_send_chat_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
	_linphone_chat_room_send_message(cr, msg);
}

735
static char *linphone_chat_room_create_is_composing_xml(LinphoneChatRoom *cr) {
736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
	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) {
754 755
		err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"isComposing",
										  (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing");
756 757
	}
	if (err >= 0) {
758 759
		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"xsi", NULL,
											(const xmlChar *)"http://www.w3.org/2001/XMLSchema-instance");
760 761
	}
	if (err >= 0) {
762 763
		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xsi", (const xmlChar *)"schemaLocation", NULL,
											(const xmlChar *)"urn:ietf:params:xml:ns:im-composing iscomposing.xsd");
764 765 766
	}
	if (err >= 0) {
		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"state",
767 768
										(cr->is_composing == LinphoneIsComposingActive) ? (const xmlChar *)"active"
																						: (const xmlChar *)"idle");
769 770
	}
	if ((err >= 0) && (cr->is_composing == LinphoneIsComposingActive)) {
771 772 773
		char refresh_str[4] = {0};
		int refresh_timeout =
			lp_config_get_int(cr->lc->config, "sip", "composing_refresh_timeout", COMPOSING_DEFAULT_REFRESH_TIMEOUT);
774
		snprintf(refresh_str, sizeof(refresh_str), "%u", refresh_timeout);
775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796
		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;
Simon Morlat's avatar
Simon Morlat committed
797 798 799 800 801 802 803
	LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(cr->lc, cr->peer_url);
	if (proxy)
		identity = linphone_proxy_config_get_identity(proxy);
	else
		identity = linphone_core_get_primary_contact(cr->lc);
	/*sending out of calls*/
	op = sal_op_new(cr->lc->sal);
804 805
	linphone_configure_op(cr->lc, op, cr->peer_url, NULL,
						  lp_config_get_int(cr->lc->config, "sip", "chat_msg_with_contact", 0));
806 807 808

	content = linphone_chat_room_create_is_composing_xml(cr);
	if (content != NULL) {
809
		sal_message_send(op, identity, cr->peer, "application/im-iscomposing+xml", content, NULL);
810
		ms_free(content);
Simon Morlat's avatar
Simon Morlat committed
811
		sal_op_unref(op);
812 813 814
	}
}

815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840
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
841
void linphone_core_real_time_text_received(LinphoneCore *lc, LinphoneChatRoom *cr, uint32_t character, LinphoneCall *call) {
842
	uint32_t new_line = 0x2028;
843 844 845
	uint32_t crlf = 0x0D0A;
	uint32_t lf = 0x0A;
	
Sylvain Berfini's avatar
Sylvain Berfini committed
846
	if (call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(call))) {
847 848
		LinphoneChatMessageCharacter *cmc = ms_new0(LinphoneChatMessageCharacter, 1);
		
Sylvain Berfini's avatar
Sylvain Berfini committed
849 850 851
		if (cr->pending_message == NULL) {
			cr->pending_message = linphone_chat_room_create_message(cr, "");
		}
852

853 854 855 856 857 858 859 860
		cmc->value = character;
		cmc->has_been_read = FALSE;
		cr->received_rtt_characters = ms_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) {
Sylvain Berfini's avatar
Sylvain Berfini committed
861 862
			// End of message
			LinphoneChatMessage *msg = cr->pending_message;
863
			ms_message("New line received, forge a message with content %s", cr->pending_message->message);
864

Sylvain Berfini's avatar
Sylvain Berfini committed
865
			linphone_chat_message_set_from(msg, cr->peer_url);
866 867
			if (msg->to)
				linphone_address_destroy(msg->to);
868
			msg->to = call->dest_proxy ? linphone_address_clone(call->dest_proxy->identity_address) :
869
					linphone_address_new(linphone_core_get_identity(lc));
Sylvain Berfini's avatar
Sylvain Berfini committed
870 871 872 873 874 875 876 877
			msg->time = ms_time(0);
			msg->state = LinphoneChatMessageStateDelivered;
			msg->is_read = FALSE;
			msg->dir = LinphoneChatMessageIncoming;
			msg->storage_id = linphone_chat_message_store(msg);

			if (cr->unread_count < 0) cr->unread_count = 1;
			else cr->unread_count++;
878

Sylvain Berfini's avatar
Sylvain Berfini committed
879 880 881
			linphone_chat_room_message_received(cr, lc, msg);
			linphone_chat_message_unref(msg);
			cr->pending_message = NULL;
882
			cr->received_rtt_characters = ms_list_free(cr->received_rtt_characters);
883 884 885 886 887
		} else {
			char *value = utf8_to_char(character);
			cr->pending_message->message = ms_strcat_printf(cr->pending_message->message, value);
			ms_message("Received RTT character: %s (%lu), pending text is %s", value, (unsigned long)character, cr->pending_message->message);
			ms_free(value);
Sylvain Berfini's avatar
Sylvain Berfini committed
888 889 890 891
		}
	}
}

892
uint32_t linphone_chat_room_get_char(const LinphoneChatRoom *cr) {
893 894 895 896 897 898 899 900 901 902
	if (cr && cr->received_rtt_characters) {
		MSList *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 = ms_list_next(characters);
		}
Sylvain Berfini's avatar
Sylvain Berfini committed
903
	}
904 905 906 907 908
	return 0;
}

int linphone_chat_message_put_char(LinphoneChatMessage *msg, uint32_t charater) {
	LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(msg);
909
	LinphoneCall *call = cr->call;
910

911 912
	if (!call || !call->textstream) {
		return -1;
913
	}
914

915
	text_stream_putchar32(call->textstream, charater);
916 917
	return 0;
}
918

919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935
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) {
936 937 938 939
	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);
940 941 942 943
	if (cr->is_composing == LinphoneIsComposingIdle) {
		cr->is_composing = LinphoneIsComposingActive;
		linphone_chat_room_send_is_composing_notification(cr);
		if (!cr->composing_refresh_timer) {
944 945
			cr->composing_refresh_timer = sal_create_timer(cr->lc->sal, linphone_chat_room_refresh_composing, cr,
														   refresh_timeout * 1000, "composing refresh timeout");
946
		} else {
947
			belle_sip_source_set_timeout(cr->composing_refresh_timer, refresh_timeout * 1000);
948 949
		}
		if (!cr->composing_idle_timer) {
950 951
			cr->composing_idle_timer = sal_create_timer(cr->lc->sal, linphone_chat_room_stop_composing, cr,
														idle_timeout * 1000, "composing idle timeout");
952 953
		}
	}
954
	belle_sip_source_set_timeout(cr->composing_idle_timer, idle_timeout * 1000);
955 956
}

957
const char *linphone_chat_message_state_to_string(const LinphoneChatMessageState state) {
958
	switch (state) {
959 960 961 962 963 964 965 966 967 968 969 970
	case LinphoneChatMessageStateIdle:
		return "LinphoneChatMessageStateIdle";
	case LinphoneChatMessageStateInProgress:
		return "LinphoneChatMessageStateInProgress";
	case LinphoneChatMessageStateDelivered:
		return "LinphoneChatMessageStateDelivered";
	case LinphoneChatMessageStateNotDelivered:
		return "LinphoneChatMessageStateNotDelivered";
	case LinphoneChatMessageStateFileTransferError:
		return "LinphoneChatMessageStateFileTransferError";
	case LinphoneChatMessageStateFileTransferDone:
		return "LinphoneChatMessageStateFileTransferDone ";
971
	}
Ghislain MARY's avatar
Ghislain MARY committed
972
	return NULL;
973
}
974

975
LinphoneChatRoom *linphone_chat_message_get_chat_room(LinphoneChatMessage *msg) {
976
	return msg->chat_room;
977 978
}

979
const LinphoneAddress *linphone_chat_message_get_peer_address(LinphoneChatMessage *msg) {
980 981 982
	return linphone_chat_room_get_peer_address(msg->chat_room);
}

983 984
void linphone_chat_message_set_user_data(LinphoneChatMessage *msg, void *ud) {
	msg->message_userdata = ud;
985
}
986

987 988
void *linphone_chat_message_get_user_data(const LinphoneChatMessage *msg) {
	return msg->message_userdata;
989
}
990

991 992
const char *linphone_chat_message_get_external_body_url(const LinphoneChatMessage *msg) {
	return msg->external_body_url;
993 994
}

995 996 997
void linphone_chat_message_set_external_body_url(LinphoneChatMessage *msg, const char *url) {
	if (msg->external_body_url) {
		ms_free(msg->external_body_url);
998
	}
999
	msg->external_body_url = url ? ms_strdup(url) : NULL;
1000
}
1001

1002 1003
const char *linphone_chat_message_get_appdata(const LinphoneChatMessage *msg) {
	return msg->appdata;
1004 1005
}

1006 1007 1008
void linphone_chat_message_set_appdata(LinphoneChatMessage *msg, const char *data) {
	if (msg->appdata) {
		ms_free(msg->appdata);
1009
	}
1010 1011
	msg->appdata = data ? ms_strdup(data) : NULL;
	linphone_chat_message_store_appdata(msg);
1012 1013
}

1014 1015 1016 1017
void linphone_chat_message_set_from_address(LinphoneChatMessage *msg, const LinphoneAddress *from) {
	if (msg->from)
		linphone_address_destroy(msg->from);
	msg->from = linphone_address_clone(from);
1018
}
1019

1020 1021
const LinphoneAddress *linphone_chat_message_get_from_address(const LinphoneChatMessage *msg) {
	return msg->from;
1022
}
1023

1024 1025 1026 1027
void linphone_chat_message_set_to_address(LinphoneChatMessage *msg, const LinphoneAddress *to) {
	if (msg->to)
		linphone_address_destroy(msg->to);
	msg->to = linphone_address_clone(to);
1028 1029
}

1030 1031 1032 1033 1034
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;
Simon Morlat's avatar
Simon Morlat committed
1035 1036 1037 1038
	}
	return NULL;
}

1039 1040
LinphoneAddress *linphone_chat_message_get_local_address(const LinphoneChatMessage *msg) {
	return msg->dir == LinphoneChatMessageOutgoing ? msg->from : msg->to;
Simon Morlat's avatar
Simon Morlat committed
1041 1042
}

1043 1044
time_t linphone_chat_message_get_time(const LinphoneChatMessage *msg) {
	return msg->time;
1045 1046
}

1047 1048
LinphoneChatMessageState linphone_chat_message_get_state(const LinphoneChatMessage *msg) {
	return msg->state;
Margaux Clerc's avatar
Margaux Clerc committed
1049 1050
}

1051 1052
const char *linphone_chat_message_get_text(const LinphoneChatMessage *msg) {
	return msg->message;
1053
}
1054

1055 1056 1057
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);
1058 1059
}

1060 1061
const char *linphone_chat_message_get_custom_header(LinphoneChatMessage *msg, const char *header_name) {
	return sal_custom_header_find(msg->custom_headers, header_name);
1062 1063
}

1064 1065
bool_t linphone_chat_message_is_read(LinphoneChatMessage *msg) {
	return msg->is_read;
1066 1067
}

1068 1069
bool_t linphone_chat_message_is_outgoing(LinphoneChatMessage *msg) {
	return msg->dir == LinphoneChatMessageOutgoing;
1070 1071
}

1072 1073
unsigned int linphone_chat_message_get_storage_id(LinphoneChatMessage *msg) {
	return msg->storage_id;
1074 1075
}

1076
LinphoneChatMessage *linphone_chat_message_clone(const LinphoneChatMessage *msg) {
1077
	/*struct _LinphoneChatMessage {
1078
	 char* msg;
1079 1080 1081 1082 1083 1084
	 LinphoneChatRoom* chat_room;
	 LinphoneChatMessageStateChangeCb cb;
	 void* cb_ud;
	 void* message_userdata;
	 char* external_body_url;
	 LinphoneAddress* from;
Margaux Clerc's avatar
Margaux Clerc committed
1085 1086 1087
	 time_t time;
	 SalCustomHeader *custom_headers;
	 LinphoneChatMessageState state;
1088
	 };*/
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105
	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);
1106
	return new_message;
1107
}
1108

1109
void linphone_chat_message_destroy(LinphoneChatMessage *msg) {
1110 1111 1112
	belle_sip_object_unref(msg);
}

1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129
static void _linphone_chat_message_destroy(LinphoneChatMessage *msg) {
	if (msg->op)
		sal_op_release(msg->op);
	if (msg->message)
		ms_free(msg->message);
	if (msg->external_body_url)
		ms_free(msg->external_body_url);
	if (msg->appdata)
		ms_free(msg->appdata);
	if (msg->from)
		linphone_address_destroy(msg->from);
	if (msg->to)
		linphone_address_destroy(msg->to);
	if (msg->custom_headers)
		sal_custom_header_free(msg->custom_headers);
	if (msg->content_type)
		ms_free(msg->content_type);
johan's avatar
johan committed
1130
	if (msg->file_transfer_information) {
1131
		linphone_content_unref(msg->file_transfer_information);
johan's avatar
johan committed
1132
	}
1133 1134 1135
	if (msg->file_transfer_filepath != NULL) {
		ms_free(msg->file_transfer_filepath);
	}
1136 1137 1138
	if (msg->callbacks) {
		linphone_chat_message_cbs_unref(msg->callbacks);
	}
1139 1140
}

1141
LinphoneChatMessage *linphone_chat_message_ref(LinphoneChatMessage *msg) {
1142 1143 1144 1145
	belle_sip_object_ref(msg);
	return msg;
}

1146
void linphone_chat_message_unref(LinphoneChatMessage *msg) {
1147
	belle_sip_object_unref(msg);
1148 1149
}

1150
static void linphone_chat_message_release(LinphoneChatMessage *msg) {
1151 1152
	/*mark the chat msg as orphan (it has no chat room anymore), and unref it*/
	msg->chat_room = NULL;
1153 1154 1155
	if (msg->file_transfer_information != NULL) {
		linphone_chat_message_cancel_file_transfer(msg);
	}
Simon Morlat's avatar
Simon Morlat committed
1156 1157 1158
	linphone_chat_message_unref(msg);
}

1159
const LinphoneErrorInfo *linphone_chat_message_get_error_info(const LinphoneChatMessage *msg) {
1160 1161
	return linphone_error_info_from_sal_op(msg->op);
}
1162

1163
LinphoneReason linphone_chat_message_get_reason(LinphoneChatMessage *msg) {
1164
	return linphone_error_info_get_reason(linphone_chat_message_get_error_info(msg));
1165 1166
}

1167
LinphoneChatMessageCbs *linphone_chat_message_get_callbacks(const LinphoneChatMessage *msg) {
1168 1169 1170
	return msg->callbacks;
}

1171 1172 1173
LinphoneCall *linphone_chat_room_get_call(const LinphoneChatRoom *room) {
	return room->call;
}