chat-message.cpp 54.7 KB
Newer Older
1
/*
2
 * chat-message.cpp
Ghislain MARY's avatar
Ghislain MARY committed
3
 * Copyright (C) 2010-2017 Belledonne Communications SARL
4
 *
Ghislain MARY's avatar
Ghislain MARY committed
5 6 7 8
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
9 10 11 12 13 14 15
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
Ghislain MARY's avatar
Ghislain MARY committed
16 17
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18
 */
Ronan's avatar
Ronan committed
19 20 21 22

#include "db/events-db.h"
#include "object/object-p.h"

23 24
#include "linphone/core.h"
#include "linphone/lpconfig.h"
Sylvain Berfini's avatar
Sylvain Berfini committed
25
#include "c-wrapper/c-wrapper.h"
26
#include "address/address.h"
27

Ronan's avatar
Ronan committed
28
#include "chat/chat-message/chat-message-p.h"
Ronan's avatar
Ronan committed
29

30
#include "content/content.h"
Ronan's avatar
Ronan committed
31 32 33 34 35
#include "chat/chat-room/chat-room-p.h"
#include "chat/chat-room/real-time-text-chat-room.h"
#include "chat/modifier/cpim-chat-message-modifier.h"
#include "chat/modifier/encryption-chat-message-modifier.h"
#include "chat/modifier/multipart-chat-message-modifier.h"
36

37
#include "logger/logger.h"
38
#include "ortp/b64.h"
39

Ronan's avatar
Ronan committed
40 41 42 43
// =============================================================================

LINPHONE_BEGIN_NAMESPACE

44
using namespace B64_NAMESPACE;
Ronan's avatar
Ronan committed
45 46
using namespace std;

47 48 49
// =============================================================================
// ChatMessagePrivate
// =============================================================================
Ronan's avatar
Ronan committed
50

Ronan's avatar
Ronan committed
51
ChatMessagePrivate::ChatMessagePrivate (const shared_ptr<ChatRoom> &room)
52 53
: chatRoom(room) {
}
Sylvain Berfini's avatar
Sylvain Berfini committed
54 55

ChatMessagePrivate::~ChatMessagePrivate () {}
56

57 58
// -----------------------------------------------------------------------------

59 60 61 62
void ChatMessagePrivate::setChatRoom (shared_ptr<ChatRoom> cr) {
	chatRoom = cr;
}

63 64 65 66
void ChatMessagePrivate::setDirection (ChatMessage::Direction dir) {
	direction = dir;
}

67 68 69 70
void ChatMessagePrivate::setTime(time_t t) {
	time = t;
}

71 72 73 74
void ChatMessagePrivate::setIsReadOnly(bool readOnly) {
	isReadOnly = readOnly;
}

75 76 77 78 79 80 81
void ChatMessagePrivate::setState(ChatMessage::State s) {
	L_Q();
	if (s != state && chatRoom) {
		if (((state == ChatMessage::State::Displayed) || (state == ChatMessage::State::DeliveredToUser))
			&& ((s == ChatMessage::State::DeliveredToUser) || (s == ChatMessage::State::Delivered) || (s == ChatMessage::State::NotDelivered))) {
			return;
		}
82
		lInfo() << "Chat message " << this << ": moving from state " << linphone_chat_message_state_to_string((LinphoneChatMessageState)state) << " to " << linphone_chat_message_state_to_string((LinphoneChatMessageState)s);
83 84 85 86 87 88 89
		state = s;

		LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q);
		if (linphone_chat_message_get_message_state_changed_cb(msg)) {
			linphone_chat_message_get_message_state_changed_cb(msg)(msg, (LinphoneChatMessageState)state, linphone_chat_message_get_message_state_changed_cb_user_data(msg));
		}
		LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
90
		if (cbs && linphone_chat_message_cbs_get_msg_state_changed(cbs)) {
91 92 93 94 95
			linphone_chat_message_cbs_get_msg_state_changed(cbs)(msg, linphone_chat_message_get_state(msg));
		}
	}
}

96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
unsigned int ChatMessagePrivate::getStorageId() const {
	return storageId;
}

void ChatMessagePrivate::setStorageId(unsigned int id) {
	storageId = id;
}

belle_http_request_t *ChatMessagePrivate::getHttpRequest() const {
	return httpRequest;
}

void ChatMessagePrivate::setHttpRequest(belle_http_request_t *request) {
	httpRequest = request;
}

SalOp *ChatMessagePrivate::getSalOp() const {
	return salOp;
}

void ChatMessagePrivate::setSalOp(SalOp *op) {
	salOp = op;
}

SalCustomHeader *ChatMessagePrivate::getSalCustomHeaders() const {
	return salCustomHeaders;
}

void ChatMessagePrivate::setSalCustomHeaders(SalCustomHeader *headers) {
	salCustomHeaders = headers;
}

128
void ChatMessagePrivate::addSalCustomHeader(const string& name, const string& value) {
129 130 131
	salCustomHeaders = sal_custom_header_append(salCustomHeaders, name.c_str(), value.c_str());
}

132
void ChatMessagePrivate::removeSalCustomHeader(const string& name) {
133 134 135
	salCustomHeaders = sal_custom_header_remove(salCustomHeaders, name.c_str());
}

136 137
string ChatMessagePrivate::getSalCustomHeaderValue(const string& name) {
	return L_C_TO_STRING(sal_custom_header_find(salCustomHeaders, name.c_str()));
138 139 140 141
}

// -----------------------------------------------------------------------------

142
const ContentType& ChatMessagePrivate::getContentType() {
143
	if (direction == ChatMessage::Direction::Incoming) {
144
		if (contents.size() > 0) {
145
			Content content = contents.front();
146
			cContentType = content.getContentType();
147 148 149 150 151 152 153 154 155 156 157
		} else {
			cContentType = internalContent.getContentType();
		}
	} else {
		if (internalContent.getContentType().isValid()) {
			cContentType = internalContent.getContentType();
		} else {
			if (contents.size() > 0) {
				Content content = contents.front();
				cContentType = content.getContentType();
			}
158
		}
159
	}
160 161 162
	return cContentType;
}

163
void ChatMessagePrivate::setContentType(const ContentType &contentType) {
164
	internalContent.setContentType(contentType);
165 166
}

167
const string& ChatMessagePrivate::getText() {
168
	if (direction == ChatMessage::Direction::Incoming) {
169
		if (contents.size() > 0) {
170 171
			Content content = contents.front();
			cText = content.getBodyAsString();
172 173 174 175 176 177 178 179 180 181 182
		} else {
			cText = internalContent.getBodyAsString();
		}
	} else {
		if (!internalContent.isEmpty()) {
			cText = internalContent.getBodyAsString();
		} else {
			if (contents.size() > 0) {
				Content content = contents.front();
				cText = content.getBodyAsString();
			}
183
		}
184
	}
185 186 187
	return cText;
}

188
void ChatMessagePrivate::setText(const string& text) {
189
	internalContent.setBody(text);
190 191 192 193 194 195 196
}

LinphoneContent * ChatMessagePrivate::getFileTransferInformation() const {
	return cFileTransferInformation;
}

void ChatMessagePrivate::setFileTransferInformation(LinphoneContent *content) {
197 198 199 200
	if (cFileTransferInformation) {
		linphone_content_unref(cFileTransferInformation);
		cFileTransferInformation = NULL;
	}
201 202 203 204 205
	cFileTransferInformation = content;
}

// -----------------------------------------------------------------------------

206
string ChatMessagePrivate::createImdnXml(Imdn::Type imdnType, LinphoneReason reason) {
207
	xmlBufferPtr buf;
208 209
	xmlTextWriterPtr writer;
	int err;
210
	string content;
211 212 213
	char *datetime = NULL;

	// Check that the chat message has a message id
214
	if (id.empty()) return NULL;
215 216 217

	buf = xmlBufferCreate();
	if (buf == NULL) {
218
		lError() << "Error creating the XML buffer";
219 220 221 222
		return content;
	}
	writer = xmlNewTextWriterMemory(buf, 0);
	if (writer == NULL) {
223
		lError() << "Error creating the XML writer";
224 225 226
		return content;
	}

227
	datetime = linphone_timestamp_to_rfc3339_string(time);
228 229 230 231 232 233 234 235 236
	err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL);
	if (err >= 0) {
		err = xmlTextWriterStartElementNS(writer, NULL, (const xmlChar *)"imdn",
										  (const xmlChar *)"urn:ietf:params:xml:ns:imdn");
	}
	if ((err >= 0) && (reason != LinphoneReasonNone)) {
		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"linphoneimdn", NULL, (const xmlChar *)"http://www.linphone.org/xsds/imdn.xsd");
	}
	if (err >= 0) {
237
		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"message-id", (const xmlChar *)id.c_str());
238 239 240 241 242
	}
	if (err >= 0) {
		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"datetime", (const xmlChar *)datetime);
	}
	if (err >= 0) {
243
		if (imdnType == Imdn::Type::Delivery) {
244 245 246 247 248 249 250 251 252 253
			err = xmlTextWriterStartElement(writer, (const xmlChar *)"delivery-notification");
		} else {
			err = xmlTextWriterStartElement(writer, (const xmlChar *)"display-notification");
		}
	}
	if (err >= 0) {
		err = xmlTextWriterStartElement(writer, (const xmlChar *)"status");
	}
	if (err >= 0) {
		if (reason == LinphoneReasonNone) {
254
			if (imdnType == Imdn::Type::Delivery) {
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
				err = xmlTextWriterStartElement(writer, (const xmlChar *)"delivered");
			} else {
				err = xmlTextWriterStartElement(writer, (const xmlChar *)"displayed");
			}
		} else {
			err = xmlTextWriterStartElement(writer, (const xmlChar *)"error");
		}
	}
	if (err >= 0) {
		// Close the "delivered", "displayed" or "error" element.
		err = xmlTextWriterEndElement(writer);
	}
	if ((err >= 0) && (reason != LinphoneReasonNone)) {
		err = xmlTextWriterStartElementNS(writer, (const xmlChar *)"linphoneimdn", (const xmlChar *)"reason", NULL);
		if (err >= 0) {
			char codestr[16];
			snprintf(codestr, 16, "%d", linphone_reason_to_error_code(reason));
			err = xmlTextWriterWriteAttribute(writer, (const xmlChar *)"code", (const xmlChar *)codestr);
		}
		if (err >= 0) {
			err = xmlTextWriterWriteString(writer, (const xmlChar *)linphone_reason_to_string(reason));
		}
		if (err >= 0) {
			err = xmlTextWriterEndElement(writer);
		}
	}
	if (err >= 0) {
		// Close the "status" element.
		err = xmlTextWriterEndElement(writer);
	}
	if (err >= 0) {
		// Close the "delivery-notification" or "display-notification" element.
		err = xmlTextWriterEndElement(writer);
	}
	if (err >= 0) {
		// Close the "imdn" element.
		err = xmlTextWriterEndElement(writer);
	}
	if (err >= 0) {
		err = xmlTextWriterEndDocument(writer);
	}
	if (err > 0) {
		// xmlTextWriterEndDocument returns the size of the content.
298
		content = string((char *)buf->content);
299 300 301 302
	}
	xmlFreeTextWriter(writer);
	xmlBufferFree(buf);
	ms_free(datetime);
303
	return content;
304 305
}

306
void ChatMessagePrivate::sendImdn(Imdn::Type imdnType, LinphoneReason reason) {
307 308 309 310
	string content = createImdnXml(imdnType, reason);
	chatRoom->getPrivate()->sendImdn(content, reason);
}

311 312 313 314 315
static void _chat_message_file_transfer_on_progress(belle_sip_body_handler_t *bh, belle_sip_message_t *m,
															void *data, size_t offset, size_t total) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->fileTransferOnProgress(bh, m, offset, total);
}
Sylvain Berfini's avatar
Sylvain Berfini committed
316

317 318 319
void ChatMessagePrivate::fileTransferOnProgress(belle_sip_body_handler_t *bh, belle_sip_message_t *m,
												size_t offset, size_t total) {
	L_Q();
Sylvain Berfini's avatar
Sylvain Berfini committed
320

321
	if (!isFileTransferInProgressAndValid()) {
322
		lWarning() << "Cancelled request for " << (chatRoom ? "" : "ORPHAN") << " msg [" << this << "], ignoring " << __FUNCTION__;
323 324 325
		releaseHttpRequest();
		return;
	}
Ronan's avatar
Ronan committed
326

327 328 329 330 331 332 333 334
	LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q);
	LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
	if (linphone_chat_message_cbs_get_file_transfer_progress_indication(cbs)) {
		linphone_chat_message_cbs_get_file_transfer_progress_indication(cbs)(msg, cFileTransferInformation, offset, total);
	} else {
		// Legacy: call back given by application level
		linphone_core_notify_file_transfer_progress_indication(chatRoom->getCore(), msg, cFileTransferInformation, offset, total);
	}
Sylvain Berfini's avatar
Sylvain Berfini committed
335 336
}

337 338 339 340
static int _chat_message_on_send_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t *m,
										void *data, size_t offset, uint8_t *buffer, size_t *size) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	return d->onSendBody(bh, m, offset, buffer, size);
Ronan's avatar
Ronan committed
341 342
}

343 344 345
int ChatMessagePrivate::onSendBody(belle_sip_user_body_handler_t *bh, belle_sip_message_t *m,
									size_t offset, uint8_t *buffer, size_t *size) {
	L_Q();
346

347 348 349 350
	LinphoneCore *lc = NULL;
	LinphoneImEncryptionEngine *imee = NULL;
	int retval = -1;
	LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q);
351

352 353
	if (!isFileTransferInProgressAndValid()) {
		if (httpRequest) {
354
			lWarning() << "Cancelled request for " << (chatRoom ? "" : "ORPHAN") << " msg [" << this << "], ignoring " << __FUNCTION__;
355 356 357 358
			releaseHttpRequest();
		}
		return BELLE_SIP_STOP;
	}
359

360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
	lc = chatRoom->getCore();
	// if we've not reach the end of file yet, ask for more data
	// in case of file body handler, won't be called
	if (fileTransferFilePath.empty() && offset < linphone_content_get_size(cFileTransferInformation)) {
		// get data from call back
		LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
		LinphoneChatMessageCbsFileTransferSendCb file_transfer_send_cb = linphone_chat_message_cbs_get_file_transfer_send(cbs);
		if (file_transfer_send_cb) {
			LinphoneBuffer *lb = file_transfer_send_cb(msg, cFileTransferInformation, offset, *size);
			if (lb == NULL) {
				*size = 0;
			} else {
				*size = linphone_buffer_get_size(lb);
				memcpy(buffer, linphone_buffer_get_content(lb), *size);
				linphone_buffer_unref(lb);
			}
		} else {
			// Legacy
			linphone_core_notify_file_transfer_send(lc, msg, cFileTransferInformation, (char *)buffer, size);
		}
	}
381

382 383 384 385 386 387 388 389 390 391
	imee = linphone_core_get_im_encryption_engine(lc);
	if (imee) {
		LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
		LinphoneImEncryptionEngineCbsUploadingFileCb cb_process_uploading_file = linphone_im_encryption_engine_cbs_get_process_uploading_file(imee_cbs);
		if (cb_process_uploading_file) {
			size_t max_size = *size;
			uint8_t *encrypted_buffer = (uint8_t *)ms_malloc0(max_size);
			retval = cb_process_uploading_file(imee, msg, offset, (const uint8_t *)buffer, size, encrypted_buffer);
			if (retval == 0) {
				if (*size > max_size) {
392
					lError() << "IM encryption engine process upload file callback returned a size bigger than the size of the buffer, so it will be truncated !";
393 394 395 396 397 398 399
					*size = max_size;
				}
				memcpy(buffer, encrypted_buffer, *size);
			}
			ms_free(encrypted_buffer);
		}
	}
400

401
	return retval <= 0 ? BELLE_SIP_CONTINUE : BELLE_SIP_STOP;
402 403
}

404 405 406
static void _chat_message_on_send_end(belle_sip_user_body_handler_t *bh, void *data) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->onSendEnd(bh);
Ronan's avatar
Ronan committed
407 408
}

409 410
void ChatMessagePrivate::onSendEnd(belle_sip_user_body_handler_t *bh) {
	L_Q();
411

412 413
	LinphoneCore *lc = chatRoom->getCore();
	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc);
414

415 416 417 418 419 420 421
	if (imee) {
		LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
		LinphoneImEncryptionEngineCbsUploadingFileCb cb_process_uploading_file = linphone_im_encryption_engine_cbs_get_process_uploading_file(imee_cbs);
		if (cb_process_uploading_file) {
			cb_process_uploading_file(imee, L_GET_C_BACK_PTR(q), 0, NULL, NULL, NULL);
		}
	}
422 423
}

424 425
void ChatMessagePrivate::fileUploadEndBackgroundTask() {
	if (backgroundTaskId) {
426
		lInfo() << "channel [" << this << "]: ending file upload background task with id=[" << backgroundTaskId << "].";
427 428 429
		sal_end_background_task(backgroundTaskId);
		backgroundTaskId = 0;
	}
430 431
}

432 433 434
static void _chat_message_file_upload_background_task_ended(void *data) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->fileUploadBackgroundTaskEnded();
435 436
}

437
void ChatMessagePrivate::fileUploadBackgroundTaskEnded() {
438
	lWarning() << "channel [" << this << "]: file upload background task has to be ended now, but work isn't finished.";
439
	fileUploadEndBackgroundTask();
440 441
}

442 443 444
void ChatMessagePrivate::fileUploadBeginBackgroundTask() {
	if (backgroundTaskId == 0) {
		backgroundTaskId = sal_begin_background_task("file transfer upload", _chat_message_file_upload_background_task_ended, this);
445
		if (backgroundTaskId) lInfo() << "channel [" << this << "]: starting file upload background task with id=[" << backgroundTaskId << "].";
446
	}
447 448
}

449 450 451
static void _chat_message_on_recv_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t *m, void *data, size_t offset, uint8_t *buffer, size_t size) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->onRecvBody(bh, m, offset, buffer, size);
452 453
}

454 455
void ChatMessagePrivate::onRecvBody(belle_sip_user_body_handler_t *bh, belle_sip_message_t *m, size_t offset, uint8_t *buffer, size_t size) {
	L_Q();
456

457 458 459 460
	LinphoneCore *lc = NULL;
	LinphoneImEncryptionEngine *imee = NULL;
	int retval = -1;
	uint8_t *decrypted_buffer = NULL;
461

462 463 464 465 466
	if (!chatRoom) {
		q->cancelFileTransfer();
		return;
	}
	lc = chatRoom->getCore();
467

468 469 470
	if (lc == NULL) {
		return; // might happen during linphone_core_destroy()
	}
471

472
	if (!httpRequest || belle_http_request_is_cancelled(httpRequest)) {
473
		lWarning() << "Cancelled request for msg [" << this << "], ignoring " << __FUNCTION__;
474 475
		return;
	}
476

477 478 479 480
	// first call may be with a zero size, ignore it
	if (size == 0) {
		return;
	}
481

482 483 484 485 486 487 488 489 490 491 492 493 494
	decrypted_buffer = (uint8_t *)ms_malloc0(size);
	imee = linphone_core_get_im_encryption_engine(lc);
	if (imee) {
		LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
		LinphoneImEncryptionEngineCbsDownloadingFileCb cb_process_downloading_file = linphone_im_encryption_engine_cbs_get_process_downloading_file(imee_cbs);
		if (cb_process_downloading_file) {
			retval = cb_process_downloading_file(imee, L_GET_C_BACK_PTR(q), offset, (const uint8_t *)buffer, size, decrypted_buffer);
			if (retval == 0) {
				memcpy(buffer, decrypted_buffer, size);
			}
		}
	}
	ms_free(decrypted_buffer);
495

496 497 498 499 500 501 502 503 504 505 506 507 508 509
	if (retval <= 0) {
		if (fileTransferFilePath.empty()) {
			LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q);
			LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
			if (linphone_chat_message_cbs_get_file_transfer_recv(cbs)) {
				LinphoneBuffer *lb = linphone_buffer_new_from_data(buffer, size);
				linphone_chat_message_cbs_get_file_transfer_recv(cbs)(msg, cFileTransferInformation, lb);
				linphone_buffer_unref(lb);
			} else {
				// Legacy: call back given by application level
				linphone_core_notify_file_transfer_recv(lc, msg, cFileTransferInformation, (const char *)buffer, size);
			}
		}
	} else {
510
		lWarning() << "File transfer decrypt failed with code " << (int)retval;
511
		setState(ChatMessage::State::FileTransferError);
512
	}
513

514 515
	return;
}
516

517 518 519 520
static void _chat_message_on_recv_end(belle_sip_user_body_handler_t *bh, void *data) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->onRecvEnd(bh);
}
521

522 523
void ChatMessagePrivate::onRecvEnd(belle_sip_user_body_handler_t *bh) {
	L_Q();
524

525 526 527
	LinphoneCore *lc = chatRoom->getCore();
	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc);
	int retval = -1;
528

529 530 531 532 533 534 535
	if (imee) {
		LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
		LinphoneImEncryptionEngineCbsDownloadingFileCb cb_process_downloading_file = linphone_im_encryption_engine_cbs_get_process_downloading_file(imee_cbs);
		if (cb_process_downloading_file) {
			retval = cb_process_downloading_file(imee, L_GET_C_BACK_PTR(q), 0, NULL, 0, NULL);
		}
	}
Ronan's avatar
Ronan committed
536

537 538 539 540 541 542 543 544 545 546 547 548 549 550
	if (retval <= 0) {
		if (fileTransferFilePath.empty()) {
			LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q);
			LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
			if (linphone_chat_message_cbs_get_file_transfer_recv(cbs)) {
				LinphoneBuffer *lb = linphone_buffer_new();
				linphone_chat_message_cbs_get_file_transfer_recv(cbs)(msg, cFileTransferInformation, lb);
				linphone_buffer_unref(lb);
			} else {
				// Legacy: call back given by application level
				linphone_core_notify_file_transfer_recv(lc, msg, cFileTransferInformation, NULL, 0);
			}
		}
	}
Ronan's avatar
Ronan committed
551

552 553 554
	if (retval <= 0 && state != ChatMessage::State::FileTransferError) {
		setState(ChatMessage::State::FileTransferDone);
	}
555 556
}

557
bool ChatMessagePrivate::isFileTransferInProgressAndValid() {
558
	return (chatRoom && chatRoom->getCore() && httpRequest && !belle_http_request_is_cancelled(httpRequest));
559 560
}

561 562 563
static void _chat_message_process_response_from_post_file(void *data, const belle_http_response_event_t *event) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processResponseFromPostFile(event);
564 565
}

566 567
void ChatMessagePrivate::processResponseFromPostFile(const belle_http_response_event_t *event) {
	L_Q();
568

569
	if (httpRequest && !isFileTransferInProgressAndValid()) {
570
		lWarning() << "Cancelled request for " << (chatRoom ? "" : "ORPHAN") << " msg [" << this << "], ignoring " << __FUNCTION__;
571 572
		releaseHttpRequest();
		return;
573 574 575 576 577 578 579 580
	}

	// check the answer code
	if (event->response) {
		int code = belle_http_response_get_status_code(event->response);
		if (code == 204) { // this is the reply to the first post to the server - an empty msg
			// start uploading the file
			belle_sip_multipart_body_handler_t *bh;
581
			string first_part_header;
582 583 584
			belle_sip_body_handler_t *first_part_bh;

			bool_t is_file_encryption_enabled = FALSE;
585
			LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(chatRoom->getCore());
586 587 588 589 590
			if (imee) {
				LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
				LinphoneImEncryptionEngineCbsIsEncryptionEnabledForFileTransferCb is_encryption_enabled_for_file_transfer_cb =
					linphone_im_encryption_engine_cbs_get_is_encryption_enabled_for_file_transfer(imee_cbs);
				if (is_encryption_enabled_for_file_transfer_cb) {
591
					is_file_encryption_enabled = is_encryption_enabled_for_file_transfer_cb(imee, L_GET_C_BACK_PTR(chatRoom));
592 593 594 595 596 597 598 599
				}
			}
			// shall we encrypt the file
			if (is_file_encryption_enabled) {
				LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
				LinphoneImEncryptionEngineCbsGenerateFileTransferKeyCb generate_file_transfer_key_cb =
					linphone_im_encryption_engine_cbs_get_generate_file_transfer_key(imee_cbs);
				if (generate_file_transfer_key_cb) {
600
					generate_file_transfer_key_cb(imee, L_GET_C_BACK_PTR(chatRoom), L_GET_C_BACK_PTR(q));
601 602 603 604
				}
				// temporary storage for the Content-disposition header value : use a generic filename to not leak it
				// Actual filename stored in msg->file_transfer_information->name will be set in encrypted msg
				// sended to the
605
				first_part_header = "form-data; name=\"File\"; filename=\"filename.txt\"";
606 607
			} else {
				// temporary storage for the Content-disposition header value
608
				first_part_header = "form-data; name=\"File\"; filename=\"" +string(linphone_content_get_name(cFileTransferInformation)) + "\"";
609 610 611 612
			}

			// create a user body handler to take care of the file and add the content disposition and content-type headers
			first_part_bh = (belle_sip_body_handler_t *)belle_sip_user_body_handler_new(
613 614 615 616
					linphone_content_get_size(cFileTransferInformation),
					_chat_message_file_transfer_on_progress, NULL, NULL,
					_chat_message_on_send_body, _chat_message_on_send_end, this);
			if (!fileTransferFilePath.empty()) {
617
				belle_sip_user_body_handler_t *body_handler = (belle_sip_user_body_handler_t *)first_part_bh;
618 619 620
				// No need to add again the callback for progression, otherwise it will be called twice
				first_part_bh = (belle_sip_body_handler_t *)belle_sip_file_body_handler_new(fileTransferFilePath.c_str(), NULL, this);
				linphone_content_set_size(cFileTransferInformation, belle_sip_file_body_handler_get_file_size((belle_sip_file_body_handler_t *)first_part_bh));
621
				belle_sip_file_body_handler_set_user_body_handler((belle_sip_file_body_handler_t *)first_part_bh, body_handler);
622
			} else if (linphone_content_get_buffer(cFileTransferInformation) != NULL) {
623
				first_part_bh = (belle_sip_body_handler_t *)belle_sip_memory_body_handler_new_from_buffer(
624 625
					linphone_content_get_buffer(cFileTransferInformation),
					linphone_content_get_size(cFileTransferInformation), _chat_message_file_transfer_on_progress, this);
626 627 628
			}

			belle_sip_body_handler_add_header(first_part_bh,
629
											  belle_sip_header_create("Content-disposition", first_part_header.c_str()));
630 631
			belle_sip_body_handler_add_header(first_part_bh,
											  (belle_sip_header_t *)belle_sip_header_content_type_create(
632 633
												  linphone_content_get_type(cFileTransferInformation),
												  linphone_content_get_subtype(cFileTransferInformation)));
634 635

			// insert it in a multipart body handler which will manage the boundaries of multipart msg
636 637 638 639 640 641
			bh = belle_sip_multipart_body_handler_new(_chat_message_file_transfer_on_progress, this, first_part_bh, NULL);

			releaseHttpRequest();
			fileUploadBeginBackgroundTask();
			q->uploadFile();
			belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(httpRequest), BELLE_SIP_BODY_HANDLER(bh));
642 643 644 645
		} else if (code == 200) { // file has been uploaded correctly, get server reply and send it
			const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response);
			if (body && strlen(body) > 0) {
				// if we have an encryption key for the file, we must insert it into the msg and restore the correct filename
646 647
				const char *content_key = linphone_content_get_key(cFileTransferInformation);
				size_t content_key_size = linphone_content_get_key_size(cFileTransferInformation);
648 649 650 651 652 653 654 655
				if (content_key != NULL) {
					// parse the msg body
					xmlDocPtr xmlMessageBody = xmlParseDoc((const xmlChar *)body);

					xmlNodePtr cur = xmlDocGetRootElement(xmlMessageBody);
					if (cur != NULL) {
						cur = cur->xmlChildrenNode;
						while (cur != NULL) {
656 657
							// we found a file info node, check it has a type="file" attribute
							if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) {
658
								xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type");
659 660 661 662
								// this is the node we are looking for : add a file-key children node
								if (!xmlStrcmp(typeAttribute, (const xmlChar *)"file")) {
									// need to parse the children node to update the file-name one
									xmlNodePtr fileInfoNodeChildren = cur->xmlChildrenNode;
663
									// convert key to base64
664
									size_t b64Size = b64_encode(NULL, content_key_size, NULL, 0);
665 666 667
									char *keyb64 = (char *)ms_malloc0(b64Size + 1);
									int xmlStringLength;

668
									b64Size = b64_encode(content_key, content_key_size, keyb64, b64Size);
669 670 671 672 673 674 675 676 677
									keyb64[b64Size] = '\0'; // libxml need a null terminated string

									// add the node containing the key to the file-info node
									xmlNewTextChild(cur, NULL, (const xmlChar *)"file-key", (const xmlChar *)keyb64);
									xmlFree(typeAttribute);
									ms_free(keyb64);

									// look for the file-name node and update its content
									while (fileInfoNodeChildren != NULL) {
678 679
										// we found a the file-name node, update its content with the real filename
										if (!xmlStrcmp(fileInfoNodeChildren->name, (const xmlChar *)"file-name")) {
680
											// update node content
681
											xmlNodeSetContent(fileInfoNodeChildren, (const xmlChar *)(linphone_content_get_name(cFileTransferInformation)));
682 683 684 685 686 687
											break;
										}
										fileInfoNodeChildren = fileInfoNodeChildren->next;
									}

									// dump the xml into msg->message
688 689 690
									char *buffer;
									xmlDocDumpFormatMemoryEnc(xmlMessageBody, (xmlChar **)&buffer, &xmlStringLength, "UTF-8", 0);
									setText(buffer);
691 692 693 694 695 696 697 698 699
									break;
								}
								xmlFree(typeAttribute);
							}
							cur = cur->next;
						}
					}
					xmlFreeDoc(xmlMessageBody);
				} else { // no encryption key, transfer in plain, just copy the msg sent by server
700
					setText(body);
701
				}
702
				setContentType(ContentType::FileTransfer);
703
				q->updateState(ChatMessage::State::FileTransferDone);
704
				releaseHttpRequest();
705
				send();
706
				fileUploadEndBackgroundTask();
707
			} else {
708
				lWarning() << "Received empty response from server, file transfer failed";
709
				q->updateState(ChatMessage::State::NotDelivered);
710 711
				releaseHttpRequest();
				fileUploadEndBackgroundTask();
712 713
			}
		} else {
714
			lWarning() << "Unhandled HTTP code response " << code << " for file transfer";
715
			q->updateState(ChatMessage::State::NotDelivered);
716 717
			releaseHttpRequest();
			fileUploadEndBackgroundTask();
718 719 720 721
		}
	}
}

722 723 724 725
static void _chat_process_response_headers_from_get_file(void *data, const belle_http_response_event_t *event) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processResponseHeadersFromGetFile(event);
}
726

727 728
static LinphoneContent *createFileTransferInformationFromHeaders(const belle_sip_message_t *m) {
	LinphoneContent *content = linphone_content_new();
729

730 731 732 733 734
	belle_sip_header_content_length_t *content_length_hdr =
		BELLE_SIP_HEADER_CONTENT_LENGTH(belle_sip_message_get_header(m, "Content-Length"));
	belle_sip_header_content_type_t *content_type_hdr =
		BELLE_SIP_HEADER_CONTENT_TYPE(belle_sip_message_get_header(m, "Content-Type"));
	const char *type = NULL, *subtype = NULL;
735

736 737 738 739 740
	linphone_content_set_name(content, "");

	if (content_type_hdr) {
		type = belle_sip_header_content_type_get_type(content_type_hdr);
		subtype = belle_sip_header_content_type_get_subtype(content_type_hdr);
741
		lInfo() << "Extracted content type " << type << " / " << subtype << " from header";
742 743 744 745 746 747
		if (type) {
			linphone_content_set_type(content, type);
		}
		if (subtype) {
			linphone_content_set_subtype(content, subtype);
		}
748 749
	}

750 751
	if (content_length_hdr) {
		linphone_content_set_size(content, belle_sip_header_content_length_get_content_length(content_length_hdr));
752
		lInfo() << "Extracted content length " << linphone_content_get_size(content) << " from header";
753 754
	}

755 756 757 758 759 760 761 762 763 764 765 766
	return content;
}

void ChatMessagePrivate::processResponseHeadersFromGetFile(const belle_http_response_event_t *event) {
	if (event->response) {
		//we are receiving a response, set a specific body handler to acquire the response.
		// if not done, belle-sip will create a memory body handler, the default
		belle_sip_message_t *response = BELLE_SIP_MESSAGE(event->response);
		belle_sip_body_handler_t *body_handler = NULL;
		size_t body_size = 0;

		if (cFileTransferInformation == NULL) {
767
			lWarning() << "No file transfer information for msg [" << this << "]: creating...";
768
			cFileTransferInformation = createFileTransferInformationFromHeaders(response);
769 770
		}

771 772 773 774
		if (cFileTransferInformation) {
			body_size = linphone_content_get_size(cFileTransferInformation);
		}

775 776
		body_handler = (belle_sip_body_handler_t *)belle_sip_user_body_handler_new(body_size, _chat_message_file_transfer_on_progress,
																					NULL, _chat_message_on_recv_body,
777 778 779 780 781 782 783 784
																					NULL, _chat_message_on_recv_end, this);
		if (!fileTransferFilePath.empty()) {
			belle_sip_user_body_handler_t *bh = (belle_sip_user_body_handler_t *)body_handler;
			body_handler = (belle_sip_body_handler_t *)belle_sip_file_body_handler_new(fileTransferFilePath.c_str(), _chat_message_file_transfer_on_progress, this);
			if (belle_sip_body_handler_get_size((belle_sip_body_handler_t *)body_handler) == 0) {
				// If the size of the body has not been initialized from the file stat, use the one from the
				// file_transfer_information.
				belle_sip_body_handler_set_size((belle_sip_body_handler_t *)body_handler, body_size);
785
			}
786
			belle_sip_file_body_handler_set_user_body_handler((belle_sip_file_body_handler_t *)body_handler, bh);
787
		}
788
		belle_sip_message_set_body_handler((belle_sip_message_t *)event->response, body_handler);
789
	}
790
}
791

792 793 794
static void _chat_message_process_auth_requested_download(void *data, belle_sip_auth_event *event) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processAuthRequestedDownload(event);
795 796
}

797
void ChatMessagePrivate::processAuthRequestedDownload(const belle_sip_auth_event *event) {
798 799
	L_Q();

800
	lError() << "Error during file download : auth requested for msg [" << this << "]";
801
	q->updateState(ChatMessage::State::FileTransferError);
802 803
	releaseHttpRequest();
}
804

805 806 807 808
static void _chat_message_process_io_error_upload(void *data, const belle_sip_io_error_event_t *event) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processIoErrorUpload(event);
}
809

810 811
void ChatMessagePrivate::processIoErrorUpload(const belle_sip_io_error_event_t *event) {
	L_Q();
812
	lError() << "I/O Error during file upload of msg [" << this << "]";
813
	q->updateState(ChatMessage::State::NotDelivered);
814
	releaseHttpRequest();
815
	chatRoom->getPrivate()->removeTransientMessage(q->getSharedFromThis());
816
}
817

818 819 820 821
static void _chat_message_process_auth_requested_upload(void *data, belle_sip_auth_event *event) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processAuthRequestedUpload(event);
}
822

823 824
void ChatMessagePrivate::processAuthRequestedUpload(const belle_sip_auth_event *event) {
	L_Q();
825
	lError() << "Error during file upload: auth requested for msg [" << this << "]";
826
	q->updateState(ChatMessage::State::NotDelivered);
827
	releaseHttpRequest();
828
	chatRoom->getPrivate()->removeTransientMessage(q->getSharedFromThis());
829 830 831 832 833 834 835 836
}

static void _chat_message_process_io_error_download(void *data, const belle_sip_io_error_event_t *event) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processIoErrorDownload(event);
}

void ChatMessagePrivate::processIoErrorDownload(const belle_sip_io_error_event_t *event) {
837 838
	L_Q();

839
	lError() << "I/O Error during file download msg [" << this << "]";
840
	q->updateState(ChatMessage::State::FileTransferError);
841 842 843 844 845 846 847 848 849 850 851 852 853
	releaseHttpRequest();
}

static void _chat_message_process_response_from_get_file(void *data, const belle_http_response_event_t *event) {
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processResponseFromGetFile(event);
}

void ChatMessagePrivate::processResponseFromGetFile(const belle_http_response_event_t *event) {
	// check the answer code
	if (event->response) {
		int code = belle_http_response_get_status_code(event->response);
		if (code >= 400 && code < 500) {
854
			lWarning() << "File transfer failed with code " << code;
855
			setState(ChatMessage::State::FileTransferError);
856
		} else if (code != 200) {
857
			lWarning() << "Unhandled HTTP code response " << code << " for file transfer";
858 859 860 861
		}
		releaseHttpRequest();
	}
}
862

863
int ChatMessagePrivate::startHttpTransfer(std::string url, std::string action, belle_http_request_listener_callbacks_t *cbs) {
864
	belle_generic_uri_t *uri = NULL;
865
	const char* ua = linphone_core_get_user_agent(chatRoom->getCore());
866

867
	if (url.empty()) {
868
		lWarning() << "Cannot process file transfer msg [" << this << "]: no file remote URI configured.";
869 870
		goto error;
	}
871 872
	uri = belle_generic_uri_parse(url.c_str());
	if (uri == NULL || belle_generic_uri_get_host(uri) == NULL) {
873
		lWarning() << "Cannot process file transfer msg [" << this << "]: incorrect file remote URI configured '" << url << "'.";
874 875 876
		goto error;
	}

877
	httpRequest = belle_http_request_create(action.c_str(), uri, belle_sip_header_create("User-Agent", ua), NULL);
878

879
	if (httpRequest == NULL) {
880
		lWarning() << "Could not create http request for uri " << url;
881 882 883
		goto error;
	}
	// keep a reference to the http request to be able to cancel it during upload
884 885 886
	belle_sip_object_ref(httpRequest);

	// give msg to listener to be able to start the actual file upload when server answer a 204 No content
887
	httpListener = belle_http_request_listener_create_from_callbacks(cbs, this);
888 889 890 891 892 893 894 895
	belle_http_provider_send_request(chatRoom->getCore()->http_provider, httpRequest, httpListener);
	return 0;
error:
	if (uri) {
		belle_sip_object_unref(uri);
	}
	return -1;
}
896

897 898 899 900 901 902 903
void ChatMessagePrivate::releaseHttpRequest() {
	if (httpRequest) {
		belle_sip_object_unref(httpRequest);
		httpRequest = NULL;
		if (httpListener){
			belle_sip_object_unref(httpListener);
			httpListener = NULL;
904 905
		}
	}
906 907
}

908
void ChatMessagePrivate::createFileTransferInformationsFromVndGsmaRcsFtHttpXml(string body) {
909 910 911 912
	xmlChar *file_url = NULL;
	xmlDocPtr xmlMessageBody;
	xmlNodePtr cur;
	/* parse the msg body to get all informations from it */
913
	xmlMessageBody = xmlParseDoc((const xmlChar *)body.c_str());
914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973