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