1 /***************************************************************************
2  *            chat.c
3  *
4  *  Sun Jun  5 19:34:18 2005
5  *  Copyright  2005  Simon Morlat
6  *  Email simon dot morlat at linphone dot org
7  ****************************************************************************/
8 
9 /*
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23  */
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 
32 #include <libxml/parser.h>
33 #include <libxml/tree.h>
34 #include <libxml/xmlwriter.h>
35 
36 #define COMPOSING_DEFAULT_IDLE_TIMEOUT 15
37 #define COMPOSING_DEFAULT_REFRESH_TIMEOUT 60
38 #define COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT 120
39 
40 static void linphone_chat_message_release(LinphoneChatMessage *msg);
41 static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr);
42 static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom *cr);
43 static void linphone_chat_room_delete_remote_composing_refresh_timer(LinphoneChatRoom *cr);
44 static void _linphone_chat_message_destroy(LinphoneChatMessage *msg);
45 static void linphone_chat_room_notify_is_composing(LinphoneChatRoom *cr, const char *text);
46 static void linphone_chat_room_notify_imdn(LinphoneChatRoom *cr, const char *text);
47 static void linphone_chat_message_deactivate(LinphoneChatMessage *msg);
48 
49 BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessageCbs);
50 
51 BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatMessageCbs, belle_sip_object_t,
52 						   NULL, // destroy
53 						   NULL, // clone
54 						   NULL, // marshal
55 						   FALSE);
56 
linphone_chat_message_cbs_new(void)57 LinphoneChatMessageCbs *linphone_chat_message_cbs_new(void) {
58 	return belle_sip_object_new(LinphoneChatMessageCbs);
59 }
60 
linphone_chat_message_cbs_ref(LinphoneChatMessageCbs * cbs)61 LinphoneChatMessageCbs *linphone_chat_message_cbs_ref(LinphoneChatMessageCbs *cbs) {
62 	belle_sip_object_ref(cbs);
63 	return cbs;
64 }
65 
linphone_chat_message_cbs_unref(LinphoneChatMessageCbs * cbs)66 void linphone_chat_message_cbs_unref(LinphoneChatMessageCbs *cbs) {
67 	belle_sip_object_unref(cbs);
68 }
69 
linphone_chat_message_cbs_get_user_data(const LinphoneChatMessageCbs * cbs)70 void *linphone_chat_message_cbs_get_user_data(const LinphoneChatMessageCbs *cbs) {
71 	return cbs->user_data;
72 }
73 
linphone_chat_message_cbs_set_user_data(LinphoneChatMessageCbs * cbs,void * ud)74 void linphone_chat_message_cbs_set_user_data(LinphoneChatMessageCbs *cbs, void *ud) {
75 	cbs->user_data = ud;
76 }
77 
78 LinphoneChatMessageCbsMsgStateChangedCb
linphone_chat_message_cbs_get_msg_state_changed(const LinphoneChatMessageCbs * cbs)79 linphone_chat_message_cbs_get_msg_state_changed(const LinphoneChatMessageCbs *cbs) {
80 	return cbs->msg_state_changed;
81 }
82 
linphone_chat_message_cbs_set_msg_state_changed(LinphoneChatMessageCbs * cbs,LinphoneChatMessageCbsMsgStateChangedCb cb)83 void linphone_chat_message_cbs_set_msg_state_changed(LinphoneChatMessageCbs *cbs,
84 													 LinphoneChatMessageCbsMsgStateChangedCb cb) {
85 	cbs->msg_state_changed = cb;
86 }
87 
linphone_chat_message_cbs_get_file_transfer_recv(const LinphoneChatMessageCbs * cbs)88 LinphoneChatMessageCbsFileTransferRecvCb linphone_chat_message_cbs_get_file_transfer_recv(const LinphoneChatMessageCbs *cbs) {
89 	return cbs->file_transfer_recv;
90 }
91 
linphone_chat_message_cbs_set_file_transfer_recv(LinphoneChatMessageCbs * cbs,LinphoneChatMessageCbsFileTransferRecvCb cb)92 void linphone_chat_message_cbs_set_file_transfer_recv(LinphoneChatMessageCbs *cbs,
93 													  LinphoneChatMessageCbsFileTransferRecvCb cb) {
94 	cbs->file_transfer_recv = cb;
95 }
96 
linphone_chat_message_cbs_get_file_transfer_send(const LinphoneChatMessageCbs * cbs)97 LinphoneChatMessageCbsFileTransferSendCb linphone_chat_message_cbs_get_file_transfer_send(const LinphoneChatMessageCbs *cbs) {
98 	return cbs->file_transfer_send;
99 }
100 
linphone_chat_message_cbs_set_file_transfer_send(LinphoneChatMessageCbs * cbs,LinphoneChatMessageCbsFileTransferSendCb cb)101 void linphone_chat_message_cbs_set_file_transfer_send(LinphoneChatMessageCbs *cbs,
102 													  LinphoneChatMessageCbsFileTransferSendCb cb) {
103 	cbs->file_transfer_send = cb;
104 }
105 
106 LinphoneChatMessageCbsFileTransferProgressIndicationCb
linphone_chat_message_cbs_get_file_transfer_progress_indication(const LinphoneChatMessageCbs * cbs)107 linphone_chat_message_cbs_get_file_transfer_progress_indication(const LinphoneChatMessageCbs *cbs) {
108 	return cbs->file_transfer_progress_indication;
109 }
110 
linphone_chat_message_cbs_set_file_transfer_progress_indication(LinphoneChatMessageCbs * cbs,LinphoneChatMessageCbsFileTransferProgressIndicationCb cb)111 void linphone_chat_message_cbs_set_file_transfer_progress_indication(
112 	LinphoneChatMessageCbs *cbs, LinphoneChatMessageCbsFileTransferProgressIndicationCb cb) {
113 	cbs->file_transfer_progress_indication = cb;
114 }
115 
116 
117 BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessage);
118 
_linphone_chat_room_destroy(LinphoneChatRoom * cr)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 	if (cr->received_rtt_characters) {
122 		cr->received_rtt_characters = bctbx_list_free_with_data(cr->received_rtt_characters, (void (*)(void *))ms_free);
123 	}
124 	linphone_chat_room_delete_composing_idle_timer(cr);
125 	linphone_chat_room_delete_composing_refresh_timer(cr);
126 	linphone_chat_room_delete_remote_composing_refresh_timer(cr);
127 	if (cr->lc != NULL) {
128 		if (bctbx_list_find(cr->lc->chatrooms, cr)) {
129 			ms_error("LinphoneChatRoom[%p] is destroyed while still being used by the LinphoneCore. This is abnormal."
130 					 " linphone_core_get_chat_room() doesn't give a reference, there is no need to call "
131 					 "linphone_chat_room_unref(). "
132 					 "In order to remove a chat room from the core, use linphone_core_delete_chat_room().",
133 					 cr);
134 			cr->lc->chatrooms = bctbx_list_remove(cr->lc->chatrooms, cr);
135 		}
136 	}
137 	linphone_address_unref(cr->peer_url);
138 	if (cr->pending_message)
139 		linphone_chat_message_destroy(cr->pending_message);
140 	ms_free(cr->peer);
141 	if (cr->weak_messages != NULL) bctbx_list_free(cr->weak_messages);
142 }
143 
linphone_chat_message_set_state(LinphoneChatMessage * msg,LinphoneChatMessageState state)144 void linphone_chat_message_set_state(LinphoneChatMessage *msg, LinphoneChatMessageState state) {
145 	/* do not invoke callbacks on orphan messages */
146 	if (state != msg->state && msg->chat_room != NULL) {
147 		if (((msg->state == LinphoneChatMessageStateDisplayed) || (msg->state == LinphoneChatMessageStateDeliveredToUser))
148 			&& ((state == LinphoneChatMessageStateDeliveredToUser) || (state == LinphoneChatMessageStateDelivered) || (state == LinphoneChatMessageStateNotDelivered))) {
149 			/* If the message has been displayed or delivered to user we must not go back to the delivered or not delivered state. */
150 			return;
151 		}
152 		ms_message("Chat message %p: moving from state %s to %s", msg, linphone_chat_message_state_to_string(msg->state),
153 				   linphone_chat_message_state_to_string(state));
154 		msg->state = state;
155 		if (msg->message_state_changed_cb) {
156 			msg->message_state_changed_cb(msg, msg->state, msg->message_state_changed_user_data);
157 		}
158 		if (linphone_chat_message_cbs_get_msg_state_changed(msg->callbacks)) {
159 			linphone_chat_message_cbs_get_msg_state_changed(msg->callbacks)(msg, msg->state);
160 		}
161 	}
162 }
163 
164 BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatMessage, belle_sip_object_t,
165 						   (belle_sip_object_destroy_t)_linphone_chat_message_destroy,
166 						   NULL, // clone
167 						   NULL, // marshal
168 						   FALSE);
169 
linphone_core_disable_chat(LinphoneCore * lc,LinphoneReason deny_reason)170 void linphone_core_disable_chat(LinphoneCore *lc, LinphoneReason deny_reason) {
171 	lc->chat_deny_code = deny_reason;
172 }
173 
linphone_core_enable_chat(LinphoneCore * lc)174 void linphone_core_enable_chat(LinphoneCore *lc) {
175 	lc->chat_deny_code = LinphoneReasonNone;
176 }
177 
linphone_core_chat_enabled(const LinphoneCore * lc)178 bool_t linphone_core_chat_enabled(const LinphoneCore *lc) {
179 	return lc->chat_deny_code != LinphoneReasonNone;
180 }
181 
linphone_core_get_chat_rooms(LinphoneCore * lc)182 const bctbx_list_t *linphone_core_get_chat_rooms(LinphoneCore *lc) {
183 	return lc->chatrooms;
184 }
185 
linphone_chat_room_matches(LinphoneChatRoom * cr,const LinphoneAddress * from)186 static bool_t linphone_chat_room_matches(LinphoneChatRoom *cr, const LinphoneAddress *from) {
187 	return linphone_address_weak_equal(cr->peer_url, from);
188 }
189 
190 BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatRoom);
191 
192 BELLE_SIP_INSTANCIATE_VPTR(LinphoneChatRoom, belle_sip_object_t,
193 						   (belle_sip_object_destroy_t)_linphone_chat_room_destroy,
194 						   NULL, // clone
195 						   NULL, // marshal
196 						   FALSE);
197 
_linphone_core_create_chat_room_base(LinphoneCore * lc,LinphoneAddress * addr)198 static LinphoneChatRoom *_linphone_core_create_chat_room_base(LinphoneCore *lc, LinphoneAddress *addr){
199 	LinphoneChatRoom *cr = belle_sip_object_new(LinphoneChatRoom);
200 	cr->lc = lc;
201 	cr->peer = linphone_address_as_string(addr);
202 	cr->peer_url = addr;
203 	cr->unread_count = -1;
204 	cr->received_rtt_characters = NULL;
205 	return cr;
206 }
207 
_linphone_core_create_chat_room(LinphoneCore * lc,LinphoneAddress * addr)208 static LinphoneChatRoom *_linphone_core_create_chat_room(LinphoneCore *lc, LinphoneAddress *addr) {
209 	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 
_linphone_core_create_chat_room_from_call(LinphoneCall * call)214 LinphoneChatRoom *_linphone_core_create_chat_room_from_call(LinphoneCall *call){
215 	LinphoneChatRoom *cr = _linphone_core_create_chat_room_base(call->core,
216 		linphone_address_clone(linphone_call_get_remote_address(call)));
217 	cr->call = call;
218 	return cr;
219 }
220 
_linphone_core_create_chat_room_from_url(LinphoneCore * lc,const char * to)221 static LinphoneChatRoom *_linphone_core_create_chat_room_from_url(LinphoneCore *lc, const char *to) {
222 	LinphoneAddress *parsed_url = NULL;
223 	if ((parsed_url = linphone_core_interpret_url(lc, to)) != NULL) {
224 		return _linphone_core_create_chat_room(lc, parsed_url);
225 	}
226 	return NULL;
227 }
228 
_linphone_core_get_chat_room(LinphoneCore * lc,const LinphoneAddress * addr)229 LinphoneChatRoom *_linphone_core_get_chat_room(LinphoneCore *lc, const LinphoneAddress *addr) {
230 	LinphoneChatRoom *cr = NULL;
231 	bctbx_list_t *elem;
232 	for (elem = lc->chatrooms; elem != NULL; elem = bctbx_list_next(elem)) {
233 		cr = (LinphoneChatRoom *)elem->data;
234 		if (linphone_chat_room_matches(cr, addr)) {
235 			break;
236 		}
237 		cr = NULL;
238 	}
239 	return cr;
240 }
241 
_linphone_core_get_or_create_chat_room(LinphoneCore * lc,const char * to)242 static LinphoneChatRoom *_linphone_core_get_or_create_chat_room(LinphoneCore *lc, const char *to) {
243 	LinphoneAddress *to_addr = linphone_core_interpret_url(lc, to);
244 	LinphoneChatRoom *ret;
245 
246 	if (to_addr == NULL) {
247 		ms_error("linphone_core_get_or_create_chat_room(): Cannot make a valid address with %s", to);
248 		return NULL;
249 	}
250 	ret = _linphone_core_get_chat_room(lc, to_addr);
251 	linphone_address_unref(to_addr);
252 	if (!ret) {
253 		ret = _linphone_core_create_chat_room_from_url(lc, to);
254 	}
255 	return ret;
256 }
257 
linphone_core_get_chat_room(LinphoneCore * lc,const LinphoneAddress * addr)258 LinphoneChatRoom *linphone_core_get_chat_room(LinphoneCore *lc, const LinphoneAddress *addr) {
259 	LinphoneChatRoom *ret = _linphone_core_get_chat_room(lc, addr);
260 	if (!ret) {
261 		ret = _linphone_core_create_chat_room(lc, linphone_address_clone(addr));
262 	}
263 	return ret;
264 }
265 
linphone_core_delete_chat_room(LinphoneCore * lc,LinphoneChatRoom * cr)266 void linphone_core_delete_chat_room(LinphoneCore *lc, LinphoneChatRoom *cr) {
267 	if (bctbx_list_find(lc->chatrooms, cr)) {
268 		lc->chatrooms = bctbx_list_remove(cr->lc->chatrooms, cr);
269 		linphone_chat_room_delete_history(cr);
270 		linphone_chat_room_unref(cr);
271 	} else {
272 		ms_error("linphone_core_delete_chat_room(): chatroom [%p] isn't part of LinphoneCore.", cr);
273 	}
274 }
275 
linphone_core_get_chat_room_from_uri(LinphoneCore * lc,const char * to)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 
linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom * cr)280 static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) {
281 	if (cr->composing_idle_timer) {
282 		if (cr->lc && cr->lc->sal)
283 			sal_cancel_timer(cr->lc->sal, cr->composing_idle_timer);
284 		belle_sip_object_unref(cr->composing_idle_timer);
285 		cr->composing_idle_timer = NULL;
286 	}
287 }
288 
linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom * cr)289 static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom *cr) {
290 	if (cr->composing_refresh_timer) {
291 		if (cr->lc && cr->lc->sal)
292 			sal_cancel_timer(cr->lc->sal, cr->composing_refresh_timer);
293 		belle_sip_object_unref(cr->composing_refresh_timer);
294 		cr->composing_refresh_timer = NULL;
295 	}
296 }
297 
linphone_chat_room_delete_remote_composing_refresh_timer(LinphoneChatRoom * cr)298 static void linphone_chat_room_delete_remote_composing_refresh_timer(LinphoneChatRoom *cr) {
299 	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 		belle_sip_object_unref(cr->remote_composing_refresh_timer);
303 		cr->remote_composing_refresh_timer = NULL;
304 	}
305 }
306 
linphone_chat_room_destroy(LinphoneChatRoom * cr)307 void linphone_chat_room_destroy(LinphoneChatRoom *cr) {
308 	linphone_chat_room_unref(cr);
309 }
310 
linphone_chat_room_release(LinphoneChatRoom * cr)311 void linphone_chat_room_release(LinphoneChatRoom *cr) {
312 	linphone_chat_room_delete_composing_idle_timer(cr);
313 	linphone_chat_room_delete_composing_refresh_timer(cr);
314 	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 	cr->lc = NULL;
317 	linphone_chat_room_unref(cr);
318 }
319 
on_weak_message_destroy(void * obj,belle_sip_object_t * message_being_destroyed)320 static void on_weak_message_destroy(void *obj, belle_sip_object_t *message_being_destroyed) {
321 	LinphoneChatRoom *cr = (LinphoneChatRoom *)obj;
322 	cr->weak_messages = bctbx_list_remove(cr->weak_messages, message_being_destroyed);
323 }
324 
linphone_chat_room_add_weak_message(LinphoneChatRoom * cr,LinphoneChatMessage * cm)325 void linphone_chat_room_add_weak_message(LinphoneChatRoom *cr, LinphoneChatMessage *cm) {
326 	bctbx_list_t *item = bctbx_list_find(cr->weak_messages, cm);
327 	if (item == NULL) {
328 		cr->weak_messages = bctbx_list_append(cr->weak_messages, belle_sip_object_weak_ref(cm, on_weak_message_destroy, cr));
329 	}
330 }
331 
linphone_chat_room_ref(LinphoneChatRoom * cr)332 LinphoneChatRoom *linphone_chat_room_ref(LinphoneChatRoom *cr) {
333 	belle_sip_object_ref(cr);
334 	return cr;
335 }
336 
linphone_chat_room_unref(LinphoneChatRoom * cr)337 void linphone_chat_room_unref(LinphoneChatRoom *cr) {
338 	belle_sip_object_unref(cr);
339 }
340 
linphone_chat_room_get_user_data(const LinphoneChatRoom * cr)341 void *linphone_chat_room_get_user_data(const LinphoneChatRoom *cr) {
342 	return cr->user_data;
343 }
344 
linphone_chat_room_set_user_data(LinphoneChatRoom * cr,void * ud)345 void linphone_chat_room_set_user_data(LinphoneChatRoom *cr, void *ud) {
346 	cr->user_data = ud;
347 }
348 
linphone_chat_room_add_transient_message(LinphoneChatRoom * cr,LinphoneChatMessage * msg)349 void linphone_chat_room_add_transient_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
350 	if (bctbx_list_find(msg->chat_room->transient_messages, msg) == NULL) {
351 		cr->transient_messages = bctbx_list_append(cr->transient_messages, linphone_chat_message_ref(msg));
352 	}
353 }
354 
linphone_chat_room_remove_transient_message(LinphoneChatRoom * cr,LinphoneChatMessage * msg)355 void linphone_chat_room_remove_transient_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
356 	if (bctbx_list_find(msg->chat_room->transient_messages, msg) != NULL) {
357 		cr->transient_messages = bctbx_list_remove(cr->transient_messages, msg);
358 		linphone_chat_message_unref(msg);
359 	}
360 }
361 
store_or_update_chat_message(LinphoneChatMessage * msg)362 static void store_or_update_chat_message(LinphoneChatMessage *msg) {
363 	if (msg->storage_id != 0) {
364 		/* The message has already been stored (probably because of file transfer), update it */
365 		linphone_chat_message_store_update(msg);
366 	} else {
367 		/* Store the new message */
368 		msg->storage_id = linphone_chat_message_store(msg);
369 	}
370 }
371 
_linphone_chat_room_send_message(LinphoneChatRoom * cr,LinphoneChatMessage * msg)372 void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
373 	int retval = -1;
374 	LinphoneCore *lc = cr->lc;
375 	LinphoneImEncryptionEngine *imee = lc->im_encryption_engine;
376 
377 	/*stubed rtt text*/
378 	if (cr->call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(cr->call))) {
379 		uint32_t new_line = 0x2028;
380 		linphone_chat_message_put_char(msg, new_line); // New Line
381 		linphone_chat_message_unref(msg);
382 		return;
383 	}
384 
385 	msg->dir = LinphoneChatMessageOutgoing;
386 
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 			/* Add to transient list only if message is going out */
392 			linphone_chat_room_add_transient_message(cr, msg);
393 			/* Store the message so that even if the upload is stopped, it can be done again */
394 			msg->storage_id = linphone_chat_message_store(msg);
395 		} else {
396 			linphone_chat_message_unref(msg);
397 			return;
398 		}
399 	} else {
400 		SalOp *op = msg->op;
401 		LinphoneCall *call=NULL;
402 		char *content_type;
403 		const char *identity = NULL;
404 		char *clear_text_message = NULL;
405 		char *clear_text_content_type = NULL;
406 
407 		if (msg->message) {
408 			clear_text_message = ms_strdup(msg->message);
409 		}
410 		if (msg->content_type) {
411 			clear_text_content_type = ms_strdup(msg->content_type);
412 		}
413 
414 		/* Add to transient list */
415 		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 			if ((call = linphone_core_get_call_by_remote_address(cr->lc, cr->peer)) != NULL) {
419 				if (call->state == LinphoneCallConnected || call->state == LinphoneCallStreamsRunning ||
420 					call->state == LinphoneCallPaused || call->state == LinphoneCallPausing ||
421 					call->state == LinphoneCallPausedByRemote) {
422 					ms_message("send SIP msg through the existing call.");
423 					op = call->op;
424 					identity = linphone_core_find_best_identity(cr->lc, linphone_call_get_remote_address(call));
425 				}
426 			}
427 		}
428 
429 		if (!identity) {
430 			LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(cr->lc, cr->peer_url);
431 			if (proxy) {
432 				identity = linphone_proxy_config_get_identity(proxy);
433 			} else {
434 				identity = linphone_core_get_primary_contact(cr->lc);
435 			}
436 		}
437 		if (msg->from){
438 			/*
439 			 * BUG
440 			 * the file transfer message constructor sets the from, but doesn't do it as well as here.
441 			 */
442 			linphone_address_unref(msg->from);
443 		}
444 		msg->from = linphone_address_new(identity);
445 
446 		if (imee) {
447 			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 				if(retval == 0) {
452 					msg->is_secured = TRUE;
453 				}
454 			}
455 		}
456 
457 		if (op == NULL) {
458 			/*sending out of calls*/
459 			msg->op = op = sal_op_new(cr->lc->sal);
460 			linphone_configure_op(cr->lc, op, cr->peer_url, msg->custom_headers,
461 								  lp_config_get_int(cr->lc->config, "sip", "chat_msg_with_contact", 0));
462 			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 			linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered);
469 			linphone_chat_message_unref(msg);
470 			return;
471 		}
472 
473 		if (msg->external_body_url) {
474 			content_type = ms_strdup_printf("message/external-body; access-type=URL; URL=\"%s\"", msg->external_body_url);
475 			sal_message_send(op, identity, cr->peer, content_type, NULL, NULL);
476 			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 			if (content_type == NULL) {
481 				sal_text_send(op, identity, cr->peer, msg->message);
482 			} else {
483 				sal_message_send(op, identity, cr->peer, content_type, msg->message, peer_uri);
484 			}
485 			ms_free(peer_uri);
486 		}
487 
488 		if (msg->message && clear_text_message && strcmp(msg->message, clear_text_message) != 0) {
489 			// We replace the encrypted message by the original one so it can be correctly stored and displayed by the application
490 			ms_free(msg->message);
491 			msg->message = ms_strdup(clear_text_message);
492 		}
493 		if (msg->content_type && clear_text_content_type && (strcmp(msg->content_type, clear_text_content_type) != 0)) {
494 			/* We replace the encrypted content type by the original one */
495 			ms_free(msg->content_type);
496 			msg->content_type = ms_strdup(clear_text_content_type);
497 		}
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 		if (cr->is_composing == LinphoneIsComposingActive) {
502 			cr->is_composing = LinphoneIsComposingIdle;
503 		}
504 		linphone_chat_room_delete_composing_idle_timer(cr);
505 		linphone_chat_room_delete_composing_refresh_timer(cr);
506 
507 		if (clear_text_message) {
508 			ms_free(clear_text_message);
509 		}
510 		if (clear_text_content_type) {
511 			ms_free(clear_text_content_type);
512 		}
513 
514 		if (call && call->op == op) {
515 			/*In this case, chat delivery status is not notified, so unrefing chat message right now*/
516 			/*Might be better fixed by delivering status, but too costly for now*/
517 			linphone_chat_room_remove_transient_message(msg->chat_room, msg);
518 			linphone_chat_message_unref(msg);
519 			return;
520 		}
521 	}
522 	// if operation failed, we should not change message state
523 	if (msg->dir == LinphoneChatMessageOutgoing) {
524 		linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress);
525 	}
526 }
527 
linphone_chat_message_update_state(LinphoneChatMessage * msg,LinphoneChatMessageState new_state)528 void linphone_chat_message_update_state(LinphoneChatMessage *msg, LinphoneChatMessageState new_state) {
529 	linphone_chat_message_set_state(msg, new_state);
530 	linphone_chat_message_store_state(msg);
531 
532 	if (msg->state == LinphoneChatMessageStateDelivered || msg->state == LinphoneChatMessageStateNotDelivered) {
533 		if (bctbx_list_find(msg->chat_room->transient_messages, msg) != NULL) {
534 			// msg is not transient anymore, we can remove it from our transient list and unref it
535 			linphone_chat_room_add_weak_message(msg->chat_room, msg);
536 			linphone_chat_room_remove_transient_message(msg->chat_room, msg);
537 		} else {
538 			// msg has already been removed from the transient messages, do nothing. */
539 		}
540 	}
541 }
542 
linphone_chat_room_send_message(LinphoneChatRoom * cr,const char * msg)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 
is_file_transfer(const char * content_type)547 static bool_t is_file_transfer(const char *content_type) {
548 	return (strcmp("application/vnd.gsma.rcs-ft-http+xml", content_type) == 0);
549 }
550 
is_im_iscomposing(const char * content_type)551 static bool_t is_im_iscomposing(const char* content_type) {
552 	return (strcmp("application/im-iscomposing+xml", content_type) == 0);
553 }
554 
is_imdn(const char * content_type)555 static bool_t is_imdn(const char *content_type) {
556 	return (strcmp("message/imdn+xml", content_type) == 0);
557 }
558 
is_text(const char * content_type)559 static bool_t is_text(const char *content_type) {
560 	return (strcmp("text/plain", content_type) == 0);
561 }
562 
linphone_chat_room_message_received(LinphoneChatRoom * cr,LinphoneCore * lc,LinphoneChatMessage * msg)563 void linphone_chat_room_message_received(LinphoneChatRoom *cr, LinphoneCore *lc, LinphoneChatMessage *msg) {
564 	if (msg->message) {
565 		/*legacy API*/
566 		linphone_core_notify_text_message_received(lc, cr, msg->from, msg->message);
567 	}
568 	linphone_core_notify_message_received(lc, cr, msg);
569 	if(!is_imdn(msg->content_type) && !is_im_iscomposing(msg->content_type)) {
570 		cr->remote_is_composing = LinphoneIsComposingIdle;
571 		linphone_core_notify_is_composing_received(cr->lc, cr);
572 		linphone_chat_message_send_delivery_notification(msg, LinphoneReasonNone);
573 	}
574 }
575 
create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(LinphoneChatMessage * msg)576 static void create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(LinphoneChatMessage *msg) {
577 	xmlChar *file_url = NULL;
578 	xmlDocPtr xmlMessageBody;
579 	xmlNodePtr cur;
580 	/* parse the msg body to get all informations from it */
581 	xmlMessageBody = xmlParseDoc((const xmlChar *)msg->message);
582 	msg->file_transfer_information = linphone_content_new();
583 
584 	cur = xmlDocGetRootElement(xmlMessageBody);
585 	if (cur != NULL) {
586 		cur = cur->xmlChildrenNode;
587 		while (cur != NULL) {
588 			if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) {
589 				/* we found a file info node, check if it has a type="file" attribute */
590 				xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type");
591 				if (!xmlStrcmp(typeAttribute, (const xmlChar *)"file")) { /* this is the node we are looking for */
592 					cur = cur->xmlChildrenNode; /* now loop on the content of the file-info node */
593 					while (cur != NULL) {
594 						if (!xmlStrcmp(cur->name, (const xmlChar *)"file-size")) {
595 							xmlChar *fileSizeString = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
596 							linphone_content_set_size(msg->file_transfer_information, strtol((const char *)fileSizeString, NULL, 10));
597 							xmlFree(fileSizeString);
598 						}
599 
600 						if (!xmlStrcmp(cur->name, (const xmlChar *)"file-name")) {
601 							xmlChar *filename = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
602 							linphone_content_set_name(msg->file_transfer_information, (char *)filename);
603 							xmlFree(filename);
604 						}
605 						if (!xmlStrcmp(cur->name, (const xmlChar *)"content-type")) {
606 							xmlChar *contentType = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
607 							int contentTypeIndex = 0;
608 							char *type;
609 							char *subtype;
610 							while (contentType[contentTypeIndex] != '/' && contentType[contentTypeIndex] != '\0') {
611 								contentTypeIndex++;
612 							}
613 							type = ms_strndup((char *)contentType, contentTypeIndex);
614 							subtype = ms_strdup(((char *)contentType + contentTypeIndex + 1));
615 							linphone_content_set_type(msg->file_transfer_information, type);
616 							linphone_content_set_subtype(msg->file_transfer_information, subtype);
617 							ms_free(subtype);
618 							ms_free(type);
619 							xmlFree(contentType);
620 						}
621 						if (!xmlStrcmp(cur->name, (const xmlChar *)"data")) {
622 							file_url = xmlGetProp(cur, (const xmlChar *)"url");
623 						}
624 
625 						if (!xmlStrcmp(cur->name, (const xmlChar *)"file-key")) {
626 							/* there is a key in the msg: file has been encrypted */
627 							/* convert the key from base 64 */
628 							xmlChar *keyb64 = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
629 							size_t keyLength = b64_decode((char *)keyb64, strlen((char *)keyb64), NULL, 0);
630 							uint8_t *keyBuffer = (uint8_t *)malloc(keyLength);
631 							/* decode the key into local key buffer */
632 							b64_decode((char *)keyb64, strlen((char *)keyb64), keyBuffer, keyLength);
633 							linphone_content_set_key(msg->file_transfer_information, (char *)keyBuffer, keyLength);
634 							/* duplicate key value into the linphone content private structure */
635 							xmlFree(keyb64);
636 							free(keyBuffer);
637 						}
638 
639 						cur = cur->next;
640 					}
641 					xmlFree(typeAttribute);
642 					break;
643 				}
644 				xmlFree(typeAttribute);
645 			}
646 			cur = cur->next;
647 		}
648 	}
649 	xmlFreeDoc(xmlMessageBody);
650 
651 	linphone_chat_message_set_external_body_url(msg, (const char *)file_url);
652 	xmlFree(file_url);
653 }
654 
linphone_core_message_received(LinphoneCore * lc,SalOp * op,const SalMessage * sal_msg)655 LinphoneReason linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *sal_msg) {
656 	LinphoneChatRoom *cr = NULL;
657 	LinphoneAddress *addr;
658 	LinphoneAddress *to;
659 	LinphoneChatMessage *msg = NULL;
660 	LinphoneImEncryptionEngine *imee = lc->im_encryption_engine;
661 	const SalCustomHeader *ch;
662 	LinphoneReason reason = LinphoneReasonNone;
663 	int retval = -1;
664 	bool_t increase_msg_count = TRUE;
665 
666 	addr = linphone_address_new(sal_msg->from);
667 	linphone_address_clean(addr);
668 	cr = linphone_core_get_chat_room(lc, addr);
669 
670 	/* Check if this is a duplicate message */
671 	if ((msg = linphone_chat_room_find_message_with_dir(cr, sal_op_get_call_id(op), LinphoneChatMessageIncoming))) {
672 		reason = lc->chat_deny_code;
673 		goto end;
674 	}
675 
676 	msg = linphone_chat_room_create_message(cr, sal_msg->text);
677 	linphone_chat_message_set_content_type(msg, sal_msg->content_type);
678 	linphone_chat_message_set_from(msg, cr->peer_url);
679 
680 	to = sal_op_get_to(op) ? linphone_address_new(sal_op_get_to(op)) : linphone_address_new(linphone_core_get_identity(lc));
681 	msg->to = to;
682 
683 	msg->time = sal_msg->time;
684 	msg->state = LinphoneChatMessageStateDelivered;
685 	msg->dir = LinphoneChatMessageIncoming;
686 	msg->message_id = ms_strdup(sal_op_get_call_id(op));
687 
688 	ch = sal_op_get_recv_custom_header(op);
689 	if (ch) {
690 		msg->custom_headers = sal_custom_header_clone(ch);
691 	}
692 
693 	if (sal_msg->url) {
694 		linphone_chat_message_set_external_body_url(msg, sal_msg->url);
695 	}
696 
697 	if (imee) {
698 		LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
699 		LinphoneImEncryptionEngineCbsIncomingMessageCb cb_process_incoming_message = linphone_im_encryption_engine_cbs_get_process_incoming_message(imee_cbs);
700 		if (cb_process_incoming_message) {
701 			retval = cb_process_incoming_message(imee, cr, msg);
702 			if(retval == 0) {
703 				msg->is_secured = TRUE;
704 			} else if(retval > 0) {
705 				// Unable to decrypt message
706 				linphone_core_notify_message_received_unable_decrypt(cr->lc, cr, msg);
707 				reason = linphone_error_code_to_reason(retval);
708 				linphone_chat_message_send_delivery_notification(msg, reason);
709 				// return LinphoneReasonNone to avoid flexisip resending us a message we can't decrypt
710 				reason = LinphoneReasonNone;
711 				goto end;
712 			}
713 		}
714 	}
715 
716 	if ((retval <= 0) && (linphone_core_is_content_type_supported(lc, msg->content_type) == FALSE)) {
717 		retval = 415;
718 		ms_error("Unsupported MESSAGE (content-type %s not recognized)", msg->content_type);
719 	}
720 
721 	if (retval > 0) {
722 		reason = linphone_error_code_to_reason(retval);
723 		linphone_chat_message_send_delivery_notification(msg, reason);
724 		goto end;
725 	}
726 
727 	if (is_file_transfer(msg->content_type)) {
728 		create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(msg);
729 		linphone_chat_message_set_to_be_stored(msg, TRUE);
730 	} else if (is_im_iscomposing(msg->content_type)) {
731 		linphone_chat_room_notify_is_composing(cr, msg->message);
732 		linphone_chat_message_set_to_be_stored(msg, FALSE);
733 		increase_msg_count = FALSE;
734 		if(lp_config_get_int(cr->lc->config, "sip", "deliver_imdn", 0) != 1) {
735 			goto end;
736 		}
737 	} else if (is_imdn(msg->content_type)) {
738 		linphone_chat_room_notify_imdn(cr, msg->message);
739 		linphone_chat_message_set_to_be_stored(msg, FALSE);
740 		increase_msg_count = FALSE;
741 		if(lp_config_get_int(cr->lc->config, "sip", "deliver_imdn", 0) != 1) {
742 			goto end;
743 		}
744 	} else if (is_text(msg->content_type)) {
745 		linphone_chat_message_set_to_be_stored(msg, TRUE);
746 	}
747 
748 	if (increase_msg_count == TRUE) {
749 		if (cr->unread_count < 0)
750 			cr->unread_count = 1;
751 		else
752 			cr->unread_count++;
753 		/* Mark the message as pending so that if linphone_core_chat_room_mark_as_read() is called
754 		   in the linphone_chat_room_message_received() callback, it will effectively be marked as
755 		   being read before being stored. */
756 		cr->pending_message = msg;
757 	}
758 
759 	linphone_chat_room_message_received(cr, lc, msg);
760 
761 	if(linphone_chat_message_get_to_be_stored(msg)) {
762 		msg->storage_id = linphone_chat_message_store(msg);
763 	}
764 
765 	cr->pending_message = NULL;
766 
767 end:
768 	linphone_address_unref(addr);
769 	if (msg != NULL) linphone_chat_message_unref(msg);
770 	return reason;
771 }
772 
linphone_chat_room_remote_refresh_composing_expired(void * data,unsigned int revents)773 static int linphone_chat_room_remote_refresh_composing_expired(void *data, unsigned int revents) {
774 	LinphoneChatRoom *cr = (LinphoneChatRoom *)data;
775 	belle_sip_object_unref(cr->remote_composing_refresh_timer);
776 	cr->remote_composing_refresh_timer = NULL;
777 	cr->remote_is_composing = LinphoneIsComposingIdle;
778 	linphone_core_notify_is_composing_received(cr->lc, cr);
779 	return BELLE_SIP_STOP;
780 }
781 
782 static const char *iscomposing_prefix = "/xsi:isComposing";
783 
process_im_is_composing_notification(LinphoneChatRoom * cr,xmlparsing_context_t * xml_ctx)784 static void process_im_is_composing_notification(LinphoneChatRoom *cr, xmlparsing_context_t *xml_ctx) {
785 	char xpath_str[MAX_XPATH_LENGTH];
786 	xmlXPathObjectPtr iscomposing_object;
787 	const char *state_str = NULL;
788 	const char *refresh_str = NULL;
789 	int refresh_duration = lp_config_get_int(cr->lc->config, "sip", "composing_remote_refresh_timeout",
790 											 COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT);
791 	int i;
792 	LinphoneIsComposingState state = LinphoneIsComposingIdle;
793 
794 	if (linphone_create_xml_xpath_context(xml_ctx) < 0)
795 		return;
796 
797 	xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"xsi",
798 					   (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing");
799 	iscomposing_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, iscomposing_prefix);
800 	if (iscomposing_object != NULL) {
801 		if (iscomposing_object->nodesetval != NULL) {
802 			for (i = 1; i <= iscomposing_object->nodesetval->nodeNr; i++) {
803 				snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/xsi:state", iscomposing_prefix, i);
804 				state_str = linphone_get_xml_text_content(xml_ctx, xpath_str);
805 				if (state_str == NULL)
806 					continue;
807 				snprintf(xpath_str, sizeof(xpath_str), "%s[%i]/xsi:refresh", iscomposing_prefix, i);
808 				refresh_str = linphone_get_xml_text_content(xml_ctx, xpath_str);
809 			}
810 		}
811 		xmlXPathFreeObject(iscomposing_object);
812 	}
813 
814 	if (state_str != NULL) {
815 		if (strcmp(state_str, "active") == 0) {
816 			state = LinphoneIsComposingActive;
817 			if (refresh_str != NULL) {
818 				refresh_duration = atoi(refresh_str);
819 			}
820 			if (!cr->remote_composing_refresh_timer) {
821 				cr->remote_composing_refresh_timer =
822 					sal_create_timer(cr->lc->sal, linphone_chat_room_remote_refresh_composing_expired, cr,
823 									 refresh_duration * 1000, "composing remote refresh timeout");
824 			} else {
825 				belle_sip_source_set_timeout(cr->remote_composing_refresh_timer, refresh_duration * 1000);
826 			}
827 		} else {
828 			linphone_chat_room_delete_remote_composing_refresh_timer(cr);
829 		}
830 
831 		cr->remote_is_composing = state;
832 		linphone_core_notify_is_composing_received(cr->lc, cr);
833 		linphone_free_xml_text_content(state_str);
834 	}
835 	if (refresh_str != NULL) {
836 		linphone_free_xml_text_content(refresh_str);
837 	}
838 }
839 
linphone_chat_room_notify_is_composing(LinphoneChatRoom * cr,const char * text)840 static void linphone_chat_room_notify_is_composing(LinphoneChatRoom *cr, const char *text) {
841 	xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new();
842 	xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
843 	xml_ctx->doc = xmlReadDoc((const unsigned char *)text, 0, NULL, 0);
844 	if (xml_ctx->doc != NULL) {
845 		process_im_is_composing_notification(cr, xml_ctx);
846 	} else {
847 		ms_warning("Wrongly formatted presence XML: %s", xml_ctx->errorBuffer);
848 	}
849 	linphone_xmlparsing_context_destroy(xml_ctx);
850 }
851 
linphone_chat_room_is_remote_composing(const LinphoneChatRoom * cr)852 bool_t linphone_chat_room_is_remote_composing(const LinphoneChatRoom *cr) {
853 	return (cr->remote_is_composing == LinphoneIsComposingActive) ? TRUE : FALSE;
854 }
855 
856 static const char *imdn_prefix = "/imdn:imdn";
857 
process_imdn(LinphoneChatRoom * cr,xmlparsing_context_t * xml_ctx)858 static void process_imdn(LinphoneChatRoom *cr, xmlparsing_context_t *xml_ctx) {
859 	char xpath_str[MAX_XPATH_LENGTH];
860 	xmlXPathObjectPtr imdn_object;
861 	xmlXPathObjectPtr delivery_status_object;
862 	xmlXPathObjectPtr display_status_object;
863 	const char *message_id_str = NULL;
864 	const char *datetime_str = NULL;
865 	LinphoneCore *lc = linphone_chat_room_get_core(cr);
866 	LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc);
867 
868 	if (linphone_create_xml_xpath_context(xml_ctx) < 0)
869 		return;
870 
871 	xmlXPathRegisterNs(xml_ctx->xpath_ctx, (const xmlChar *)"imdn",
872 					   (const xmlChar *)"urn:ietf:params:xml:ns:imdn");
873 	imdn_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, imdn_prefix);
874 	if (imdn_object != NULL) {
875 		if ((imdn_object->nodesetval != NULL) && (imdn_object->nodesetval->nodeNr >= 1)) {
876 			snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:message-id", imdn_prefix);
877 			message_id_str = linphone_get_xml_text_content(xml_ctx, xpath_str);
878 			snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:datetime", imdn_prefix);
879 			datetime_str = linphone_get_xml_text_content(xml_ctx, xpath_str);
880 		}
881 		xmlXPathFreeObject(imdn_object);
882 	}
883 
884 	if ((message_id_str != NULL) && (datetime_str != NULL)) {
885 		LinphoneChatMessage *cm = linphone_chat_room_find_message_with_dir(cr, message_id_str, LinphoneChatMessageOutgoing);
886 		if (cm == NULL) {
887 			ms_warning("Received IMDN for unknown message %s", message_id_str);
888 		} else {
889 			snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:delivery-notification/imdn:status", imdn_prefix);
890 			delivery_status_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, xpath_str);
891 			snprintf(xpath_str, sizeof(xpath_str), "%s[1]/imdn:display-notification/imdn:status", imdn_prefix);
892 			display_status_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, xpath_str);
893 			if ((delivery_status_object != NULL) && (linphone_im_notif_policy_get_recv_imdn_delivered(policy) == TRUE)) {
894 				if ((delivery_status_object->nodesetval != NULL) && (delivery_status_object->nodesetval->nodeNr >= 1)) {
895 					xmlNodePtr node = delivery_status_object->nodesetval->nodeTab[0];
896 					if ((node->children != NULL) && (node->children->name != NULL)) {
897 						if (strcmp((const char *)node->children->name, "delivered") == 0) {
898 							linphone_chat_message_update_state(cm, LinphoneChatMessageStateDeliveredToUser);
899 						} else if (strcmp((const char *)node->children->name, "error") == 0) {
900 							linphone_chat_message_update_state(cm, LinphoneChatMessageStateNotDelivered);
901 						}
902 					}
903 				}
904 				xmlXPathFreeObject(delivery_status_object);
905 			}
906 			if ((display_status_object != NULL) && (linphone_im_notif_policy_get_recv_imdn_displayed(policy) == TRUE)) {
907 				if ((display_status_object->nodesetval != NULL) && (display_status_object->nodesetval->nodeNr >= 1)) {
908 					xmlNodePtr node = display_status_object->nodesetval->nodeTab[0];
909 					if ((node->children != NULL) && (node->children->name != NULL)) {
910 						if (strcmp((const char *)node->children->name, "displayed") == 0) {
911 							linphone_chat_message_update_state(cm, LinphoneChatMessageStateDisplayed);
912 						}
913 					}
914 				}
915 				xmlXPathFreeObject(display_status_object);
916 			}
917 			linphone_chat_message_unref(cm);
918 		}
919 	}
920 	if (message_id_str != NULL) linphone_free_xml_text_content(message_id_str);
921 	if (datetime_str != NULL) linphone_free_xml_text_content(datetime_str);
922 }
923 
linphone_chat_room_notify_imdn(LinphoneChatRoom * cr,const char * text)924 static void linphone_chat_room_notify_imdn(LinphoneChatRoom *cr, const char *text) {
925 	xmlparsing_context_t *xml_ctx = linphone_xmlparsing_context_new();
926 	xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error);
927 	xml_ctx->doc = xmlReadDoc((const unsigned char *)text, 0, NULL, 0);
928 	if (xml_ctx->doc != NULL) {
929 		process_imdn(cr, xml_ctx);
930 	} else {
931 		ms_warning("Wrongly formatted IMDN XML: %s", xml_ctx->errorBuffer);
932 	}
933 	linphone_xmlparsing_context_destroy(xml_ctx);
934 }
935 
linphone_chat_room_get_lc(LinphoneChatRoom * cr)936 LinphoneCore *linphone_chat_room_get_lc(LinphoneChatRoom *cr) {
937 	return linphone_chat_room_get_core(cr);
938 }
939 
linphone_chat_room_get_core(LinphoneChatRoom * cr)940 LinphoneCore *linphone_chat_room_get_core(LinphoneChatRoom *cr) {
941 	return cr->lc;
942 }
943 
linphone_chat_room_get_peer_address(LinphoneChatRoom * cr)944 const LinphoneAddress *linphone_chat_room_get_peer_address(LinphoneChatRoom *cr) {
945 	return cr->peer_url;
946 }
947 
linphone_chat_room_create_message(LinphoneChatRoom * cr,const char * message)948 LinphoneChatMessage *linphone_chat_room_create_message(LinphoneChatRoom *cr, const char *message) {
949 	LinphoneChatMessage *msg = belle_sip_object_new(LinphoneChatMessage);
950 	msg->state = LinphoneChatMessageStateIdle;
951 	msg->callbacks = linphone_chat_message_cbs_new();
952 	msg->chat_room = (LinphoneChatRoom *)cr;
953 	msg->message = message ? ms_strdup(message) : NULL;
954 	msg->content_type = ms_strdup("text/plain");
955 	msg->file_transfer_information = NULL; /* this property is used only when transfering file */
956 	msg->http_request = NULL;
957 	msg->time = ms_time(0);
958 	msg->is_secured = FALSE;
959 	return msg;
960 }
961 
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)962 LinphoneChatMessage *linphone_chat_room_create_message_2(LinphoneChatRoom *cr, const char *message,
963 														 const char *external_body_url, LinphoneChatMessageState state,
964 														 time_t time, bool_t is_read, bool_t is_incoming) {
965 	LinphoneChatMessage *msg = linphone_chat_room_create_message(cr, message);
966 	LinphoneCore *lc = linphone_chat_room_get_core(cr);
967 	msg->external_body_url = external_body_url ? ms_strdup(external_body_url) : NULL;
968 	msg->time = time;
969 	msg->is_secured = FALSE;
970 	linphone_chat_message_set_state(msg, state);
971 	if (is_incoming) {
972 		msg->dir = LinphoneChatMessageIncoming;
973 		linphone_chat_message_set_from(msg, linphone_chat_room_get_peer_address(cr));
974 		msg->to = linphone_address_new(linphone_core_get_identity(lc)); /*direct assignment*/
975 	} else {
976 		msg->dir = LinphoneChatMessageOutgoing;
977 		linphone_chat_message_set_to(msg, linphone_chat_room_get_peer_address(cr));
978 		msg->from = linphone_address_new(linphone_core_get_identity(lc));/*direct assignment*/
979 	}
980 	return msg;
981 }
982 
linphone_chat_room_send_message2(LinphoneChatRoom * cr,LinphoneChatMessage * msg,LinphoneChatMessageStateChangedCb status_cb,void * ud)983 void linphone_chat_room_send_message2(LinphoneChatRoom *cr, LinphoneChatMessage *msg,
984 									  LinphoneChatMessageStateChangedCb status_cb, void *ud) {
985 	msg->message_state_changed_cb = status_cb;
986 	msg->message_state_changed_user_data = ud;
987 	_linphone_chat_room_send_message(cr, msg);
988 }
989 
linphone_chat_room_send_chat_message_2(LinphoneChatRoom * cr,LinphoneChatMessage * msg)990 void linphone_chat_room_send_chat_message_2(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
991 	linphone_chat_message_ref(msg);
992 	_linphone_chat_room_send_message(cr, msg);
993 }
994 
linphone_chat_room_send_chat_message(LinphoneChatRoom * cr,LinphoneChatMessage * msg)995 void linphone_chat_room_send_chat_message(LinphoneChatRoom *cr, LinphoneChatMessage *msg) {
996 	_linphone_chat_room_send_message(cr, msg);
997 }
998 
_linphone_chat_message_resend(LinphoneChatMessage * msg,bool_t ref_msg)999 void _linphone_chat_message_resend(LinphoneChatMessage *msg, bool_t ref_msg) {
1000 	LinphoneChatMessageState state = linphone_chat_message_get_state(msg);
1001 	LinphoneChatRoom *cr;
1002 
1003 	if (state != LinphoneChatMessageStateNotDelivered) {
1004 		ms_warning("Cannot resend chat message in state %s", linphone_chat_message_state_to_string(state));
1005 		return;
1006 	}
1007 
1008 	cr = linphone_chat_message_get_chat_room(msg);
1009 	if (ref_msg) linphone_chat_message_ref(msg);
1010 	_linphone_chat_room_send_message(cr, msg);
1011 }
1012 
linphone_chat_message_resend(LinphoneChatMessage * msg)1013 void linphone_chat_message_resend(LinphoneChatMessage *msg) {
1014 	_linphone_chat_message_resend(msg, FALSE);
1015 }
1016 
linphone_chat_message_resend_2(LinphoneChatMessage * msg)1017 void linphone_chat_message_resend_2(LinphoneChatMessage *msg) {
1018 	_linphone_chat_message_resend(msg, TRUE);
1019 }
1020 
linphone_chat_room_create_is_composing_xml(LinphoneChatRoom * cr)1021 static char *linphone_chat_room_create_is_composing_xml(LinphoneChatRoom *cr) {
1022 	xmlBufferPtr buf;
1023 	xmlTextWriterPtr writer;
1024 	int err;
1025 	char *content = NULL;
1026 
1027 	buf = xmlBufferCreate();
1028 	if (buf == NULL) {
1029 		ms_error("Error creating the XML buffer");
1030 		return content;
1031 	}
1032 	writer = xmlNewTextWriterMemory(buf, 0);
1033 	if (writer == NULL) {
1034 		ms_error("Error creating the XML writer");
1035 		return content;
1036 	}
1037 
1038 	err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL);
1039 	if (err >= 0) {
1040 		err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"isComposing",
1041 										  (const xmlChar *)"urn:ietf:params:xml:ns:im-iscomposing");
1042 	}
1043 	if (err >= 0) {
1044 		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"xsi", NULL,
1045 											(const xmlChar *)"http://www.w3.org/2001/XMLSchema-instance");
1046 	}
1047 	if (err >= 0) {
1048 		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xsi", (const xmlChar *)"schemaLocation", NULL,
1049 											(const xmlChar *)"urn:ietf:params:xml:ns:im-composing iscomposing.xsd");
1050 	}
1051 	if (err >= 0) {
1052 		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"state",
1053 										(cr->is_composing == LinphoneIsComposingActive) ? (const xmlChar *)"active"
1054 																						: (const xmlChar *)"idle");
1055 	}
1056 	if ((err >= 0) && (cr->is_composing == LinphoneIsComposingActive)) {
1057 		char refresh_str[4] = {0};
1058 		int refresh_timeout =
1059 			lp_config_get_int(cr->lc->config, "sip", "composing_refresh_timeout", COMPOSING_DEFAULT_REFRESH_TIMEOUT);
1060 		snprintf(refresh_str, sizeof(refresh_str), "%u", refresh_timeout);
1061 		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"refresh", (const xmlChar *)refresh_str);
1062 	}
1063 	if (err >= 0) {
1064 		/* Close the "isComposing" element. */
1065 		err = xmlTextWriterEndElement(writer);
1066 	}
1067 	if (err >= 0) {
1068 		err = xmlTextWriterEndDocument(writer);
1069 	}
1070 	if (err > 0) {
1071 		/* xmlTextWriterEndDocument returns the size of the content. */
1072 		content = ms_strdup((char *)buf->content);
1073 	}
1074 	xmlFreeTextWriter(writer);
1075 	xmlBufferFree(buf);
1076 	return content;
1077 }
1078 
linphone_chat_room_send_is_composing_notification(LinphoneChatRoom * cr)1079 static void linphone_chat_room_send_is_composing_notification(LinphoneChatRoom *cr) {
1080 	SalOp *op = NULL;
1081 	const char *identity = NULL;
1082 	char *content = NULL;
1083 	LinphoneCore *lc = linphone_chat_room_get_core(cr);
1084 	LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc);
1085 	if (linphone_im_notif_policy_get_send_is_composing(policy) == TRUE) {
1086 		LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(lc, cr->peer_url);
1087 		LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc);
1088 		LinphoneChatMessage *msg = NULL;
1089 
1090 		if (proxy)
1091 			identity = linphone_proxy_config_get_identity(proxy);
1092 		else
1093 			identity = linphone_core_get_primary_contact(lc);
1094 		/*sending out of calls*/
1095 		op = sal_op_new(lc->sal);
1096 		linphone_configure_op(lc, op, cr->peer_url, NULL,
1097 							lp_config_get_int(lc->config, "sip", "chat_msg_with_contact", 0));
1098 
1099 		content = linphone_chat_room_create_is_composing_xml(cr);
1100 		if (content != NULL) {
1101 			int retval = -1;
1102 			LinphoneAddress *from_addr = linphone_address_new(identity);
1103 			LinphoneAddress *to_addr = linphone_address_new(cr->peer);
1104 			msg = linphone_chat_room_create_message(cr, content);
1105 			linphone_chat_message_set_from_address(msg, from_addr);
1106 			linphone_chat_message_set_to_address(msg, to_addr);
1107 			linphone_chat_message_set_content_type(msg, "application/im-iscomposing+xml");
1108 
1109 			if (imee) {
1110 				LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
1111 				LinphoneImEncryptionEngineCbsOutgoingMessageCb cb_process_outgoing_message = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imee_cbs);
1112 				if (cb_process_outgoing_message) {
1113 					retval = cb_process_outgoing_message(imee, cr, msg);
1114 				}
1115 			}
1116 
1117 			if (retval <= 0) {
1118 				sal_message_send(op, identity, cr->peer, msg->content_type, msg->message, NULL);
1119 			}
1120 
1121 			linphone_chat_message_unref(msg);
1122 			linphone_address_unref(from_addr);
1123 			linphone_address_unref(to_addr);
1124 			ms_free(content);
1125 			sal_op_unref(op);
1126 		}
1127 	}
1128 }
1129 
1130 enum ImdnType {
1131 	ImdnTypeDelivery,
1132 	ImdnTypeDisplay
1133 };
1134 
linphone_chat_message_create_imdn_xml(LinphoneChatMessage * cm,enum ImdnType imdn_type,LinphoneReason reason)1135 static char *linphone_chat_message_create_imdn_xml(LinphoneChatMessage *cm, enum ImdnType imdn_type, LinphoneReason reason) {
1136 	xmlBufferPtr buf;
1137 	xmlTextWriterPtr writer;
1138 	int err;
1139 	char *content = NULL;
1140 	char *datetime = NULL;
1141 	const char *message_id;
1142 
1143 	/* Check that the chat message has a message id */
1144 	message_id = linphone_chat_message_get_message_id(cm);
1145 	if (message_id == NULL) return NULL;
1146 
1147 	buf = xmlBufferCreate();
1148 	if (buf == NULL) {
1149 		ms_error("Error creating the XML buffer");
1150 		return content;
1151 	}
1152 	writer = xmlNewTextWriterMemory(buf, 0);
1153 	if (writer == NULL) {
1154 		ms_error("Error creating the XML writer");
1155 		return content;
1156 	}
1157 
1158 	datetime = linphone_timestamp_to_rfc3339_string(linphone_chat_message_get_time(cm));
1159 	err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL);
1160 	if (err >= 0) {
1161 		err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"imdn",
1162 										  (const xmlChar *)"urn:ietf:params:xml:ns:imdn");
1163 	}
1164 	if ((err >= 0) && (reason != LinphoneReasonNone)) {
1165 		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"linphoneimdn", NULL, (const xmlChar *)"http://www.linphone.org/xsds/imdn.xsd");
1166 	}
1167 	if (err >= 0) {
1168 		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"message-id", (const xmlChar *)message_id);
1169 	}
1170 	if (err >= 0) {
1171 		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"datetime", (const xmlChar *)datetime);
1172 	}
1173 	if (err >= 0) {
1174 		if (imdn_type == ImdnTypeDelivery) {
1175 			err = xmlTextWriterStartElement(writer, (const xmlChar *)"delivery-notification");
1176 		} else {
1177 			err = xmlTextWriterStartElement(writer, (const xmlChar *)"display-notification");
1178 		}
1179 	}
1180 	if (err >= 0) {
1181 		err = xmlTextWriterStartElement(writer, (const xmlChar *)"status");
1182 	}
1183 	if (err >= 0) {
1184 		if (reason == LinphoneReasonNone) {
1185 			if (imdn_type == ImdnTypeDelivery) {
1186 				err = xmlTextWriterStartElement(writer, (const xmlChar *)"delivered");
1187 			} else {
1188 				err = xmlTextWriterStartElement(writer, (const xmlChar *)"displayed");
1189 			}
1190 		} else {
1191 			err = xmlTextWriterStartElement(writer, (const xmlChar *)"error");
1192 		}
1193 	}
1194 	if (err >= 0) {
1195 		/* Close the "delivered", "displayed" or "error" element. */
1196 		err = xmlTextWriterEndElement(writer);
1197 	}
1198 	if ((err >= 0) && (reason != LinphoneReasonNone)) {
1199 		err = xmlTextWriterStartElementNS(writer, (const xmlChar *)"linphoneimdn", (const xmlChar *)"reason", NULL);
1200 		if (err >= 0) {
1201 			char codestr[16];
1202 			snprintf(codestr, 16, "%d", linphone_reason_to_error_code(reason));
1203 			err = xmlTextWriterWriteAttribute(writer, (const xmlChar *)"code", (const xmlChar *)codestr);
1204 		}
1205 		if (err >= 0) {
1206 			err = xmlTextWriterWriteString(writer, (const xmlChar *)linphone_reason_to_string(reason));
1207 		}
1208 		if (err >= 0) {
1209 			err = xmlTextWriterEndElement(writer);
1210 		}
1211 	}
1212 	if (err >= 0) {
1213 		/* Close the "status" element. */
1214 		err = xmlTextWriterEndElement(writer);
1215 	}
1216 	if (err >= 0) {
1217 		/* Close the "delivery-notification" or "display-notification" element. */
1218 		err = xmlTextWriterEndElement(writer);
1219 	}
1220 	if (err >= 0) {
1221 		/* Close the "imdn" element. */
1222 		err = xmlTextWriterEndElement(writer);
1223 	}
1224 	if (err >= 0) {
1225 		err = xmlTextWriterEndDocument(writer);
1226 	}
1227 	if (err > 0) {
1228 		/* xmlTextWriterEndDocument returns the size of the content. */
1229 		content = ms_strdup((char *)buf->content);
1230 	}
1231 	xmlFreeTextWriter(writer);
1232 	xmlBufferFree(buf);
1233 	ms_free(datetime);
1234 	return content;
1235 }
1236 
linphone_chat_message_send_imdn(LinphoneChatMessage * cm,enum ImdnType imdn_type,LinphoneReason reason)1237 static void linphone_chat_message_send_imdn(LinphoneChatMessage *cm, enum ImdnType imdn_type, LinphoneReason reason) {
1238 	SalOp *op = NULL;
1239 	const char *identity = NULL;
1240 	char *content = NULL;
1241 	LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(cm);
1242 	LinphoneProxyConfig *proxy = linphone_core_lookup_known_proxy(cr->lc, cr->peer_url);
1243 	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(cr->lc);
1244 	LinphoneChatMessage *msg;
1245 
1246 	if (proxy)
1247 		identity = linphone_proxy_config_get_identity(proxy);
1248 	else
1249 		identity = linphone_core_get_primary_contact(cr->lc);
1250 	/* Sending out of calls */
1251 	op = sal_op_new(cr->lc->sal);
1252 	linphone_configure_op(cr->lc, op, cr->peer_url, NULL,
1253 		lp_config_get_int(cr->lc->config, "sip", "chat_msg_with_contact", 0));
1254 
1255 	content = linphone_chat_message_create_imdn_xml(cm, imdn_type, reason);
1256 	if (content != NULL) {
1257 		int retval = -1;
1258 		LinphoneAddress *from_addr = linphone_address_new(identity);
1259 		LinphoneAddress *to_addr = linphone_address_new(cr->peer);
1260 		msg = linphone_chat_room_create_message(cr, content);
1261 		linphone_chat_message_set_from_address(msg, from_addr);
1262 		linphone_chat_message_set_to_address(msg, to_addr);
1263 		linphone_chat_message_set_content_type(msg, "message/imdn+xml");
1264 
1265 		/* Do not try to encrypt the notification when it is reporting an error (maybe it should be bypassed only for some reasons). */
1266 		if (imee && (reason == LinphoneReasonNone)) {
1267 			LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
1268 			LinphoneImEncryptionEngineCbsOutgoingMessageCb cb_process_outgoing_message = linphone_im_encryption_engine_cbs_get_process_outgoing_message(imee_cbs);
1269 			if (cb_process_outgoing_message) {
1270 				retval = cb_process_outgoing_message(imee, cr, msg);
1271 			}
1272 		}
1273 
1274 		if (retval <= 0) {
1275 			sal_message_send(op, identity, cr->peer, msg->content_type, msg->message, NULL);
1276 		}
1277 
1278 		linphone_chat_message_unref(msg);
1279 		linphone_address_unref(from_addr);
1280 		linphone_address_unref(to_addr);
1281 		ms_free(content);
1282 	}
1283 	sal_op_unref(op);
1284 }
1285 
linphone_chat_message_send_delivery_notification(LinphoneChatMessage * cm,LinphoneReason reason)1286 void linphone_chat_message_send_delivery_notification(LinphoneChatMessage *cm, LinphoneReason reason) {
1287 	LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(cm);
1288 	LinphoneCore *lc = linphone_chat_room_get_core(cr);
1289 	LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc);
1290 	if (linphone_im_notif_policy_get_send_imdn_delivered(policy) == TRUE) {
1291 		linphone_chat_message_send_imdn(cm, ImdnTypeDelivery, reason);
1292 	}
1293 }
1294 
linphone_chat_message_send_display_notification(LinphoneChatMessage * cm)1295 void linphone_chat_message_send_display_notification(LinphoneChatMessage *cm) {
1296 	LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(cm);
1297 	LinphoneCore *lc = linphone_chat_room_get_core(cr);
1298 	LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc);
1299 	if (linphone_im_notif_policy_get_send_imdn_displayed(policy) == TRUE) {
1300 		linphone_chat_message_send_imdn(cm, ImdnTypeDisplay, LinphoneReasonNone);
1301 	}
1302 }
1303 
utf8_to_char(uint32_t ic)1304 static char* utf8_to_char(uint32_t ic) {
1305 	char *result = ms_malloc(sizeof(char) * 5);
1306 	int size = 0;
1307 	if (ic < 0x80) {
1308 		result[0] = ic;
1309 		size = 1;
1310 	} else if (ic < 0x800) {
1311 		result[1] = 0x80 + ((ic & 0x3F));
1312 		result[0] = 0xC0 + ((ic >> 6) & 0x1F);
1313 		size = 2;
1314 	} else if (ic < 0x100000) {
1315 		result[2] = 0x80 + (ic & 0x3F);
1316 		result[1] = 0x80 + ((ic >> 6) & 0x3F);
1317 		result[0] = 0xE0 + ((ic >> 12) & 0xF);
1318 		size = 3;
1319 	} else if (ic < 0x110000) {
1320 		result[3] = 0x80 + (ic & 0x3F);
1321 		result[2] = 0x80 + ((ic >> 6) & 0x3F);
1322 		result[1] = 0x80 + ((ic >> 12) & 0x3F);
1323 		result[0] = 0xF0 + ((ic >> 18) & 0x7);
1324 		size = 4;
1325 	}
1326 	result[size] = '\0';
1327 	return result;
1328 }
1329 
linphone_core_real_time_text_received(LinphoneCore * lc,LinphoneChatRoom * cr,uint32_t character,LinphoneCall * call)1330 void linphone_core_real_time_text_received(LinphoneCore *lc, LinphoneChatRoom *cr, uint32_t character, LinphoneCall *call) {
1331 	uint32_t new_line = 0x2028;
1332 	uint32_t crlf = 0x0D0A;
1333 	uint32_t lf = 0x0A;
1334 
1335 	if (call && linphone_call_params_realtime_text_enabled(linphone_call_get_current_params(call))) {
1336 		LinphoneChatMessageCharacter *cmc = ms_new0(LinphoneChatMessageCharacter, 1);
1337 
1338 		if (cr->pending_message == NULL) {
1339 			cr->pending_message = linphone_chat_room_create_message(cr, "");
1340 		}
1341 
1342 		cmc->value = character;
1343 		cmc->has_been_read = FALSE;
1344 		cr->received_rtt_characters = bctbx_list_append(cr->received_rtt_characters, (void *)cmc);
1345 
1346 		cr->remote_is_composing = LinphoneIsComposingActive;
1347 		linphone_core_notify_is_composing_received(cr->lc, cr);
1348 
1349 		if (character == new_line || character == crlf || character == lf) {
1350 			// End of message
1351 			LinphoneChatMessage *msg = cr->pending_message;
1352 			ms_debug("New line received, forge a message with content %s", cr->pending_message->message);
1353 
1354 			linphone_chat_message_set_from(msg, cr->peer_url);
1355 			if (msg->to)
1356 				linphone_address_unref(msg->to);
1357 			msg->to = call->dest_proxy ? linphone_address_clone(call->dest_proxy->identity_address) :
1358 					linphone_address_new(linphone_core_get_identity(lc));
1359 			msg->time = ms_time(0);
1360 			msg->state = LinphoneChatMessageStateDelivered;
1361 			msg->dir = LinphoneChatMessageIncoming;
1362 
1363 			if (lp_config_get_int(lc->config, "misc", "store_rtt_messages", 1) == 1) {
1364 				msg->storage_id = linphone_chat_message_store(msg);
1365 			}
1366 
1367 			if (cr->unread_count < 0) cr->unread_count = 1;
1368 			else cr->unread_count++;
1369 
1370 			linphone_chat_room_message_received(cr, lc, msg);
1371 			linphone_chat_message_unref(msg);
1372 			cr->pending_message = NULL;
1373 			cr->received_rtt_characters = bctbx_list_free_with_data(cr->received_rtt_characters, (void (*)(void *))ms_free);
1374 		} else {
1375 			char *value = utf8_to_char(character);
1376 			cr->pending_message->message = ms_strcat_printf(cr->pending_message->message, value);
1377 			ms_debug("Received RTT character: %s (%lu), pending text is %s", value, (unsigned long)character, cr->pending_message->message);
1378 			ms_free(value);
1379 		}
1380 	}
1381 }
1382 
linphone_chat_room_get_char(const LinphoneChatRoom * cr)1383 uint32_t linphone_chat_room_get_char(const LinphoneChatRoom *cr) {
1384 	if (cr && cr->received_rtt_characters) {
1385 		bctbx_list_t *characters = cr->received_rtt_characters;
1386 		while (characters != NULL) {
1387 			LinphoneChatMessageCharacter *cmc = (LinphoneChatMessageCharacter *)characters->data;
1388 			if (!cmc->has_been_read) {
1389 				cmc->has_been_read = TRUE;
1390 				return cmc->value;
1391 			}
1392 			characters = bctbx_list_next(characters);
1393 		}
1394 	}
1395 	return 0;
1396 }
1397 
linphone_chat_message_put_char(LinphoneChatMessage * msg,uint32_t character)1398 LinphoneStatus linphone_chat_message_put_char(LinphoneChatMessage *msg, uint32_t character) {
1399 	LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(msg);
1400 	LinphoneCall *call = cr->call;
1401 	LinphoneCore *lc = cr->lc;
1402 	uint32_t new_line = 0x2028;
1403 	uint32_t crlf = 0x0D0A;
1404 	uint32_t lf = 0x0A;
1405 
1406 	if (!call || !call->textstream) {
1407 		return -1;
1408 	}
1409 
1410 	if (character == new_line || character == crlf || character == lf) {
1411 		if (lc && lp_config_get_int(lc->config, "misc", "store_rtt_messages", 1) == 1) {
1412 			ms_debug("New line sent, forge a message with content %s", msg->message);
1413 			msg->time = ms_time(0);
1414 			msg->state = LinphoneChatMessageStateDisplayed;
1415 			msg->dir = LinphoneChatMessageOutgoing;
1416 			if (msg->from) linphone_address_unref(msg->from);
1417 			msg->from = linphone_address_new(linphone_core_get_identity(lc));
1418 			msg->storage_id = linphone_chat_message_store(msg);
1419 			ms_free(msg->message);
1420 			msg->message = NULL;
1421 		}
1422 	} else {
1423 		char *value = utf8_to_char(character);
1424 		msg->message = ms_strcat_printf(msg->message, value);
1425 		ms_debug("Sent RTT character: %s (%lu), pending text is %s", value, (unsigned long)character, msg->message);
1426 		ms_free(value);
1427 	}
1428 
1429 	text_stream_putchar32(call->textstream, character);
1430 	return 0;
1431 }
1432 
linphone_chat_message_get_message_id(const LinphoneChatMessage * cm)1433 const char* linphone_chat_message_get_message_id(const LinphoneChatMessage *cm) {
1434 	return cm->message_id;
1435 }
1436 
linphone_chat_room_stop_composing(void * data,unsigned int revents)1437 static int linphone_chat_room_stop_composing(void *data, unsigned int revents) {
1438 	LinphoneChatRoom *cr = (LinphoneChatRoom *)data;
1439 	cr->is_composing = LinphoneIsComposingIdle;
1440 	linphone_chat_room_send_is_composing_notification(cr);
1441 	linphone_chat_room_delete_composing_refresh_timer(cr);
1442 	belle_sip_object_unref(cr->composing_idle_timer);
1443 	cr->composing_idle_timer = NULL;
1444 	return BELLE_SIP_STOP;
1445 }
1446 
linphone_chat_room_refresh_composing(void * data,unsigned int revents)1447 static int linphone_chat_room_refresh_composing(void *data, unsigned int revents) {
1448 	LinphoneChatRoom *cr = (LinphoneChatRoom *)data;
1449 	linphone_chat_room_send_is_composing_notification(cr);
1450 	return BELLE_SIP_CONTINUE;
1451 }
1452 
linphone_chat_room_compose(LinphoneChatRoom * cr)1453 void linphone_chat_room_compose(LinphoneChatRoom *cr) {
1454 	int idle_timeout =
1455 		lp_config_get_int(cr->lc->config, "sip", "composing_idle_timeout", COMPOSING_DEFAULT_IDLE_TIMEOUT);
1456 	int refresh_timeout =
1457 		lp_config_get_int(cr->lc->config, "sip", "composing_refresh_timeout", COMPOSING_DEFAULT_REFRESH_TIMEOUT);
1458 	if (cr->is_composing == LinphoneIsComposingIdle) {
1459 		cr->is_composing = LinphoneIsComposingActive;
1460 		linphone_chat_room_send_is_composing_notification(cr);
1461 		if (!cr->composing_refresh_timer) {
1462 			cr->composing_refresh_timer = sal_create_timer(cr->lc->sal, linphone_chat_room_refresh_composing, cr,
1463 														   refresh_timeout * 1000, "composing refresh timeout");
1464 		} else {
1465 			belle_sip_source_set_timeout(cr->composing_refresh_timer, refresh_timeout * 1000);
1466 		}
1467 		if (!cr->composing_idle_timer) {
1468 			cr->composing_idle_timer = sal_create_timer(cr->lc->sal, linphone_chat_room_stop_composing, cr,
1469 														idle_timeout * 1000, "composing idle timeout");
1470 		}
1471 	}
1472 	belle_sip_source_set_timeout(cr->composing_idle_timer, idle_timeout * 1000);
1473 }
1474 
linphone_chat_message_state_to_string(const LinphoneChatMessageState state)1475 const char *linphone_chat_message_state_to_string(const LinphoneChatMessageState state) {
1476 	switch (state) {
1477 	case LinphoneChatMessageStateIdle:
1478 		return "LinphoneChatMessageStateIdle";
1479 	case LinphoneChatMessageStateInProgress:
1480 		return "LinphoneChatMessageStateInProgress";
1481 	case LinphoneChatMessageStateDelivered:
1482 		return "LinphoneChatMessageStateDelivered";
1483 	case LinphoneChatMessageStateNotDelivered:
1484 		return "LinphoneChatMessageStateNotDelivered";
1485 	case LinphoneChatMessageStateFileTransferError:
1486 		return "LinphoneChatMessageStateFileTransferError";
1487 	case LinphoneChatMessageStateFileTransferDone:
1488 		return "LinphoneChatMessageStateFileTransferDone ";
1489 	case LinphoneChatMessageStateDeliveredToUser:
1490 		return "LinphoneChatMessageStateDeliveredToUser";
1491 	case LinphoneChatMessageStateDisplayed:
1492 		return "LinphoneChatMessageStateDisplayed";
1493 	}
1494 	return NULL;
1495 }
1496 
linphone_chat_message_get_chat_room(LinphoneChatMessage * msg)1497 LinphoneChatRoom *linphone_chat_message_get_chat_room(LinphoneChatMessage *msg) {
1498 	return msg->chat_room;
1499 }
1500 
linphone_chat_message_get_peer_address(LinphoneChatMessage * msg)1501 const LinphoneAddress *linphone_chat_message_get_peer_address(LinphoneChatMessage *msg) {
1502 	return linphone_chat_room_get_peer_address(msg->chat_room);
1503 }
1504 
linphone_chat_message_set_user_data(LinphoneChatMessage * msg,void * ud)1505 void linphone_chat_message_set_user_data(LinphoneChatMessage *msg, void *ud) {
1506 	msg->message_userdata = ud;
1507 }
1508 
linphone_chat_message_get_user_data(const LinphoneChatMessage * msg)1509 void *linphone_chat_message_get_user_data(const LinphoneChatMessage *msg) {
1510 	return msg->message_userdata;
1511 }
1512 
linphone_chat_message_get_external_body_url(const LinphoneChatMessage * msg)1513 const char *linphone_chat_message_get_external_body_url(const LinphoneChatMessage *msg) {
1514 	return msg->external_body_url;
1515 }
1516 
linphone_chat_message_set_external_body_url(LinphoneChatMessage * msg,const char * url)1517 void linphone_chat_message_set_external_body_url(LinphoneChatMessage *msg, const char *url) {
1518 	if (msg->external_body_url) {
1519 		ms_free(msg->external_body_url);
1520 	}
1521 	msg->external_body_url = url ? ms_strdup(url) : NULL;
1522 }
1523 
linphone_chat_message_get_content_type(const LinphoneChatMessage * msg)1524 const char * linphone_chat_message_get_content_type(const LinphoneChatMessage *msg) {
1525 	return msg->content_type;
1526 }
1527 
linphone_chat_message_set_content_type(LinphoneChatMessage * msg,const char * content_type)1528 void linphone_chat_message_set_content_type(LinphoneChatMessage *msg, const char *content_type) {
1529 	if (msg->content_type) {
1530 		ms_free(msg->content_type);
1531 	}
1532 	msg->content_type = content_type ? ms_strdup(content_type) : NULL;
1533 }
1534 
linphone_chat_message_is_file_transfer(const LinphoneChatMessage * msg)1535 bool_t linphone_chat_message_is_file_transfer(const LinphoneChatMessage *msg) {
1536 	return is_file_transfer(msg->content_type);
1537 }
1538 
linphone_chat_message_is_text(const LinphoneChatMessage * msg)1539 bool_t linphone_chat_message_is_text(const LinphoneChatMessage *msg) {
1540 	return is_text(msg->content_type);
1541 }
1542 
linphone_chat_message_get_to_be_stored(const LinphoneChatMessage * msg)1543 bool_t linphone_chat_message_get_to_be_stored(const LinphoneChatMessage *msg) {
1544 	return msg->to_be_stored;
1545 }
1546 
linphone_chat_message_set_to_be_stored(LinphoneChatMessage * msg,bool_t to_be_stored)1547 void linphone_chat_message_set_to_be_stored(LinphoneChatMessage *msg, bool_t to_be_stored) {
1548 	msg->to_be_stored = to_be_stored;
1549 }
1550 
linphone_chat_message_get_appdata(const LinphoneChatMessage * msg)1551 const char *linphone_chat_message_get_appdata(const LinphoneChatMessage *msg) {
1552 	return msg->appdata;
1553 }
1554 
linphone_chat_message_set_appdata(LinphoneChatMessage * msg,const char * data)1555 void linphone_chat_message_set_appdata(LinphoneChatMessage *msg, const char *data) {
1556 	if (msg->appdata) {
1557 		ms_free(msg->appdata);
1558 	}
1559 	msg->appdata = data ? ms_strdup(data) : NULL;
1560 	linphone_chat_message_store_appdata(msg);
1561 }
1562 
linphone_chat_message_set_from_address(LinphoneChatMessage * msg,const LinphoneAddress * from)1563 void linphone_chat_message_set_from_address(LinphoneChatMessage *msg, const LinphoneAddress *from) {
1564 	if (msg->from)
1565 		linphone_address_unref(msg->from);
1566 	msg->from = linphone_address_clone(from);
1567 }
1568 
linphone_chat_message_get_from_address(const LinphoneChatMessage * msg)1569 const LinphoneAddress *linphone_chat_message_get_from_address(const LinphoneChatMessage *msg) {
1570 	return msg->from;
1571 }
1572 
linphone_chat_message_set_to_address(LinphoneChatMessage * msg,const LinphoneAddress * to)1573 void linphone_chat_message_set_to_address(LinphoneChatMessage *msg, const LinphoneAddress *to) {
1574 	if (msg->to)
1575 		linphone_address_unref(msg->to);
1576 	msg->to = linphone_address_clone(to);
1577 }
1578 
linphone_chat_message_get_to_address(const LinphoneChatMessage * msg)1579 const LinphoneAddress *linphone_chat_message_get_to_address(const LinphoneChatMessage *msg) {
1580 	if (msg->to)
1581 		return msg->to;
1582 	if (msg->dir == LinphoneChatMessageOutgoing) {
1583 		return msg->chat_room->peer_url;
1584 	}
1585 	return NULL;
1586 }
1587 
linphone_chat_message_set_is_secured(LinphoneChatMessage * msg,bool_t secured)1588 void linphone_chat_message_set_is_secured(LinphoneChatMessage *msg, bool_t secured) {
1589 	msg->is_secured = secured;
1590 }
1591 
linphone_chat_message_is_secured(LinphoneChatMessage * msg)1592 bool_t linphone_chat_message_is_secured(LinphoneChatMessage *msg) {
1593 	return msg->is_secured;
1594 }
1595 
linphone_chat_message_get_local_address(const LinphoneChatMessage * msg)1596 LinphoneAddress *linphone_chat_message_get_local_address(const LinphoneChatMessage *msg) {
1597 	return msg->dir == LinphoneChatMessageOutgoing ? msg->from : msg->to;
1598 }
1599 
linphone_chat_message_get_time(const LinphoneChatMessage * msg)1600 time_t linphone_chat_message_get_time(const LinphoneChatMessage *msg) {
1601 	return msg->time;
1602 }
1603 
linphone_chat_message_get_state(const LinphoneChatMessage * msg)1604 LinphoneChatMessageState linphone_chat_message_get_state(const LinphoneChatMessage *msg) {
1605 	return msg->state;
1606 }
1607 
linphone_chat_message_get_text(const LinphoneChatMessage * msg)1608 const char *linphone_chat_message_get_text(const LinphoneChatMessage *msg) {
1609 	return msg->message;
1610 }
1611 
linphone_chat_message_set_text(LinphoneChatMessage * msg,const char * text)1612 int linphone_chat_message_set_text(LinphoneChatMessage *msg, const char* text) {
1613 	if (msg->message)
1614 		ms_free(msg->message);
1615 	if (text)
1616 		msg->message = ms_strdup(text);
1617 	else
1618 		msg->message = NULL;
1619 
1620 	return 0;
1621 }
1622 
linphone_chat_message_add_custom_header(LinphoneChatMessage * msg,const char * header_name,const char * header_value)1623 void linphone_chat_message_add_custom_header(LinphoneChatMessage *msg, const char *header_name,
1624 											 const char *header_value) {
1625 	msg->custom_headers = sal_custom_header_append(msg->custom_headers, header_name, header_value);
1626 }
1627 
linphone_chat_message_get_custom_header(LinphoneChatMessage * msg,const char * header_name)1628 const char *linphone_chat_message_get_custom_header(LinphoneChatMessage *msg, const char *header_name) {
1629 	return sal_custom_header_find(msg->custom_headers, header_name);
1630 }
1631 
linphone_chat_message_remove_custom_header(LinphoneChatMessage * msg,const char * header_name)1632 void linphone_chat_message_remove_custom_header(LinphoneChatMessage *msg, const char *header_name) {
1633 	msg->custom_headers = sal_custom_header_remove(msg->custom_headers, header_name);
1634 }
1635 
linphone_chat_message_is_read(LinphoneChatMessage * msg)1636 bool_t linphone_chat_message_is_read(LinphoneChatMessage *msg) {
1637 	LinphoneChatRoom *cr = linphone_chat_message_get_chat_room(msg);
1638 	LinphoneCore *lc = linphone_chat_room_get_core(cr);
1639 	LinphoneImNotifPolicy *policy = linphone_core_get_im_notif_policy(lc);
1640 	if ((linphone_im_notif_policy_get_recv_imdn_displayed(policy) == TRUE) && (msg->state == LinphoneChatMessageStateDisplayed)) return TRUE;
1641 	if ((linphone_im_notif_policy_get_recv_imdn_delivered(policy) == TRUE) && (msg->state == LinphoneChatMessageStateDeliveredToUser || msg->state == LinphoneChatMessageStateDisplayed)) return TRUE;
1642 	return (msg->state == LinphoneChatMessageStateDelivered || msg->state == LinphoneChatMessageStateDisplayed || msg->state == LinphoneChatMessageStateDeliveredToUser);
1643 }
1644 
linphone_chat_message_is_outgoing(LinphoneChatMessage * msg)1645 bool_t linphone_chat_message_is_outgoing(LinphoneChatMessage *msg) {
1646 	return msg->dir == LinphoneChatMessageOutgoing;
1647 }
1648 
linphone_chat_message_get_storage_id(LinphoneChatMessage * msg)1649 unsigned int linphone_chat_message_get_storage_id(LinphoneChatMessage *msg) {
1650 	return msg->storage_id;
1651 }
1652 
linphone_chat_message_clone(const LinphoneChatMessage * msg)1653 LinphoneChatMessage *linphone_chat_message_clone(const LinphoneChatMessage *msg) {
1654 	LinphoneChatMessage *new_message = linphone_chat_room_create_message(msg->chat_room, msg->message);
1655 	if (msg->external_body_url)
1656 		new_message->external_body_url = ms_strdup(msg->external_body_url);
1657 	if (msg->appdata)
1658 		new_message->appdata = ms_strdup(msg->appdata);
1659 	new_message->message_state_changed_cb = msg->message_state_changed_cb;
1660 	new_message->message_state_changed_user_data = msg->message_state_changed_user_data;
1661 	new_message->message_userdata = msg->message_userdata;
1662 	new_message->time = msg->time;
1663 	new_message->state = msg->state;
1664 	new_message->storage_id = msg->storage_id;
1665 	if (msg->from)
1666 		new_message->from = linphone_address_clone(msg->from);
1667 	if (msg->file_transfer_filepath)
1668 		new_message->file_transfer_filepath = ms_strdup(msg->file_transfer_filepath);
1669 	if (msg->file_transfer_information)
1670 		new_message->file_transfer_information = linphone_content_copy(msg->file_transfer_information);
1671 	return new_message;
1672 }
1673 
linphone_chat_message_destroy(LinphoneChatMessage * msg)1674 void linphone_chat_message_destroy(LinphoneChatMessage *msg) {
1675 	belle_sip_object_unref(msg);
1676 }
1677 
_linphone_chat_message_destroy(LinphoneChatMessage * msg)1678 static void _linphone_chat_message_destroy(LinphoneChatMessage *msg) {
1679 	if (msg->op)
1680 		sal_op_release(msg->op);
1681 	if (msg->ei)
1682 		linphone_error_info_unref(msg->ei);
1683 	if (msg->message)
1684 		ms_free(msg->message);
1685 	if (msg->external_body_url)
1686 		ms_free(msg->external_body_url);
1687 	if (msg->appdata)
1688 		ms_free(msg->appdata);
1689 	if (msg->from)
1690 		linphone_address_unref(msg->from);
1691 	if (msg->to)
1692 		linphone_address_unref(msg->to);
1693 	if (msg->message_id)
1694 		ms_free(msg->message_id);
1695 	if (msg->custom_headers)
1696 		sal_custom_header_free(msg->custom_headers);
1697 	if (msg->content_type)
1698 		ms_free(msg->content_type);
1699 	if (msg->file_transfer_information) {
1700 		linphone_content_unref(msg->file_transfer_information);
1701 	}
1702 	if (msg->file_transfer_filepath != NULL) {
1703 		ms_free(msg->file_transfer_filepath);
1704 	}
1705 	if (msg->callbacks) {
1706 		linphone_chat_message_cbs_unref(msg->callbacks);
1707 	}
1708 }
1709 
linphone_chat_message_ref(LinphoneChatMessage * msg)1710 LinphoneChatMessage *linphone_chat_message_ref(LinphoneChatMessage *msg) {
1711 	belle_sip_object_ref(msg);
1712 	return msg;
1713 }
1714 
linphone_chat_message_unref(LinphoneChatMessage * msg)1715 void linphone_chat_message_unref(LinphoneChatMessage *msg) {
1716 	belle_sip_object_unref(msg);
1717 }
1718 
linphone_chat_message_deactivate(LinphoneChatMessage * msg)1719 static void linphone_chat_message_deactivate(LinphoneChatMessage *msg){
1720 	/*mark the chat msg as orphan (it has no chat room anymore)*/
1721 	msg->chat_room = NULL;
1722 	if (msg->file_transfer_information != NULL) {
1723 		linphone_chat_message_cancel_file_transfer(msg);
1724 	}
1725 }
1726 
linphone_chat_message_release(LinphoneChatMessage * msg)1727 static void linphone_chat_message_release(LinphoneChatMessage *msg) {
1728 	linphone_chat_message_deactivate(msg);
1729 	linphone_chat_message_unref(msg);
1730 }
1731 
linphone_chat_message_get_error_info(const LinphoneChatMessage * msg)1732 const LinphoneErrorInfo *linphone_chat_message_get_error_info(const LinphoneChatMessage *msg) {
1733 	if (!msg->ei) ((LinphoneChatMessage*)msg)->ei = linphone_error_info_new(); /*let's do it mutable*/
1734 	linphone_error_info_from_sal_op(msg->ei, msg->op);
1735 	return msg->ei;
1736 }
1737 
linphone_chat_message_get_reason(LinphoneChatMessage * msg)1738 LinphoneReason linphone_chat_message_get_reason(LinphoneChatMessage *msg) {
1739 	return linphone_error_info_get_reason(linphone_chat_message_get_error_info(msg));
1740 }
1741 
linphone_chat_message_get_callbacks(const LinphoneChatMessage * msg)1742 LinphoneChatMessageCbs *linphone_chat_message_get_callbacks(const LinphoneChatMessage *msg) {
1743 	return msg->callbacks;
1744 }
1745 
linphone_chat_room_get_call(const LinphoneChatRoom * room)1746 LinphoneCall *linphone_chat_room_get_call(const LinphoneChatRoom *room) {
1747 	return room->call;
1748 }
1749