chat-message.cpp 57.3 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

#include "object/object-p.h"

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

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

Ronan's avatar
Ronan committed
29 30 31 32 33
#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"
34 35
#include "content/content.h"
#include "core/core.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

51
ChatMessagePrivate::ChatMessagePrivate () {}
Sylvain Berfini's avatar
Sylvain Berfini committed
52

Ghislain MARY's avatar
Ghislain MARY committed
53 54 55 56
ChatMessagePrivate::~ChatMessagePrivate () {
	if (salOp)
		salOp->release();
}
57

58 59
// -----------------------------------------------------------------------------

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

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

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

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

76
void ChatMessagePrivate::setState (ChatMessage::State s) {
77 78
	L_Q();

79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
	if (s == state || !q->getChatRoom())
		return;

	if (
		(state == ChatMessage::State::Displayed || state == ChatMessage::State::DeliveredToUser) &&
		(
			s == ChatMessage::State::DeliveredToUser ||
			s == ChatMessage::State::Delivered ||
			s == ChatMessage::State::NotDelivered
		)
	)
		return;

	lInfo() << "Chat message " << this << ": moving from state " <<
		linphone_chat_message_state_to_string((LinphoneChatMessageState)state) << " to " <<
		linphone_chat_message_state_to_string((LinphoneChatMessageState)s);
	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);
	if (cbs && linphone_chat_message_cbs_get_msg_state_changed(cbs))
		linphone_chat_message_cbs_get_msg_state_changed(cbs)(msg, linphone_chat_message_get_state(msg));
108 109
}

110
unsigned int ChatMessagePrivate::getStorageId () const {
111 112 113
	return storageId;
}

114
void ChatMessagePrivate::setStorageId (unsigned int id) {
115 116 117
	storageId = id;
}

118
belle_http_request_t *ChatMessagePrivate::getHttpRequest () const {
119 120 121
	return httpRequest;
}

122
void ChatMessagePrivate::setHttpRequest (belle_http_request_t *request) {
123 124 125
	httpRequest = request;
}

126
SalOp *ChatMessagePrivate::getSalOp () const {
127 128 129
	return salOp;
}

130
void ChatMessagePrivate::setSalOp (SalOp *op) {
131 132 133
	salOp = op;
}

134
SalCustomHeader *ChatMessagePrivate::getSalCustomHeaders () const {
135 136 137
	return salCustomHeaders;
}

138
void ChatMessagePrivate::setSalCustomHeaders (SalCustomHeader *headers) {
139 140 141
	salCustomHeaders = headers;
}

142
void ChatMessagePrivate::addSalCustomHeader (const string &name, const string &value) {
143 144 145
	salCustomHeaders = sal_custom_header_append(salCustomHeaders, name.c_str(), value.c_str());
}

146
void ChatMessagePrivate::removeSalCustomHeader (const string &name) {
147 148 149
	salCustomHeaders = sal_custom_header_remove(salCustomHeaders, name.c_str());
}

150
string ChatMessagePrivate::getSalCustomHeaderValue (const string &name) {
151
	return L_C_TO_STRING(sal_custom_header_find(salCustomHeaders, name.c_str()));
152 153 154 155
}

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

156
const ContentType &ChatMessagePrivate::getContentType () {
157
	if (direction == ChatMessage::Direction::Incoming) {
158
		if (contents.size() > 0) {
159
			Content content = contents.front();
160
			cContentType = content.getContentType();
161 162 163 164 165 166 167 168 169 170 171
		} else {
			cContentType = internalContent.getContentType();
		}
	} else {
		if (internalContent.getContentType().isValid()) {
			cContentType = internalContent.getContentType();
		} else {
			if (contents.size() > 0) {
				Content content = contents.front();
				cContentType = content.getContentType();
			}
172
		}
173
	}
174 175 176
	return cContentType;
}

177
void ChatMessagePrivate::setContentType (const ContentType &contentType) {
178
	internalContent.setContentType(contentType);
179 180
}

181
const string &ChatMessagePrivate::getText () {
182
	if (direction == ChatMessage::Direction::Incoming) {
183
		if (contents.size() > 0) {
184 185
			Content content = contents.front();
			cText = content.getBodyAsString();
186 187 188 189 190 191 192 193 194 195 196
		} else {
			cText = internalContent.getBodyAsString();
		}
	} else {
		if (!internalContent.isEmpty()) {
			cText = internalContent.getBodyAsString();
		} else {
			if (contents.size() > 0) {
				Content content = contents.front();
				cText = content.getBodyAsString();
			}
197
		}
198
	}
199 200 201
	return cText;
}

202
void ChatMessagePrivate::setText (const string &text) {
203
	internalContent.setBody(text);
204 205
}

206
LinphoneContent *ChatMessagePrivate::getFileTransferInformation () const {
207 208 209
	return cFileTransferInformation;
}

210
void ChatMessagePrivate::setFileTransferInformation (LinphoneContent *content) {
211 212
	if (cFileTransferInformation) {
		linphone_content_unref(cFileTransferInformation);
213
		cFileTransferInformation = nullptr;
214
	}
215 216 217 218 219
	cFileTransferInformation = content;
}

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

220
string ChatMessagePrivate::createImdnXml (Imdn::Type imdnType, LinphoneReason reason) {
221
	xmlBufferPtr buf;
222 223
	xmlTextWriterPtr writer;
	int err;
224
	string content;
225
	char *datetime = nullptr;
226

227 228
	// Check that the chat message has a message id.
	if (id.empty()) return nullptr;
229 230

	buf = xmlBufferCreate();
231
	if (buf == nullptr) {
232
		lError() << "Error creating the XML buffer";
233 234 235
		return content;
	}
	writer = xmlNewTextWriterMemory(buf, 0);
236
	if (writer == nullptr) {
237
		lError() << "Error creating the XML writer";
238 239 240
		return content;
	}

241
	datetime = linphone_timestamp_to_rfc3339_string(time);
242
	err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", nullptr);
243
	if (err >= 0) {
244 245
		err = xmlTextWriterStartElementNS(writer, nullptr, (const xmlChar *)"imdn",
				(const xmlChar *)"urn:ietf:params:xml:ns:imdn");
246 247
	}
	if ((err >= 0) && (reason != LinphoneReasonNone)) {
248
		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"linphoneimdn", nullptr, (const xmlChar *)"http://www.linphone.org/xsds/imdn.xsd");
249 250
	}
	if (err >= 0) {
251
		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"message-id", (const xmlChar *)id.c_str());
252 253 254 255 256
	}
	if (err >= 0) {
		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"datetime", (const xmlChar *)datetime);
	}
	if (err >= 0) {
257
		if (imdnType == Imdn::Type::Delivery) {
258 259 260 261 262 263 264 265 266 267
			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) {
268
			if (imdnType == Imdn::Type::Delivery) {
269 270 271 272 273 274 275 276 277 278 279 280 281
				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)) {
282
		err = xmlTextWriterStartElementNS(writer, (const xmlChar *)"linphoneimdn", (const xmlChar *)"reason", nullptr);
283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311
		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.
312
		content = string((char *)buf->content);
313 314 315 316
	}
	xmlFreeTextWriter(writer);
	xmlBufferFree(buf);
	ms_free(datetime);
317
	return content;
318 319
}

320 321 322 323 324 325 326 327 328 329 330 331 332 333
void ChatMessagePrivate::sendImdn (Imdn::Type imdnType, LinphoneReason reason) {
	L_Q();
	shared_ptr<ChatRoom> chatRoom = q->getChatRoom();
	if (chatRoom)
		chatRoom->getPrivate()->sendImdn(createImdnXml(imdnType, reason), reason);
}

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
) {
334 335 336
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->fileTransferOnProgress(bh, m, offset, total);
}
Sylvain Berfini's avatar
Sylvain Berfini committed
337

338 339 340 341 342 343
void ChatMessagePrivate::fileTransferOnProgress (
	belle_sip_body_handler_t *bh,
	belle_sip_message_t *m,
	size_t offset,
	size_t total
) {
344
	L_Q();
Sylvain Berfini's avatar
Sylvain Berfini committed
345

346
	if (!isFileTransferInProgressAndValid()) {
347 348
		lWarning() << "Cancelled request for " << (chatRoom.lock() ? "" : "ORPHAN") << " msg [" << this <<
			"], ignoring " << __FUNCTION__;
349 350 351
		releaseHttpRequest();
		return;
	}
Ronan's avatar
Ronan committed
352

353 354 355 356 357
	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 {
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378
		// Legacy: call back given by application level.
		shared_ptr<Core> core = q->getCore();
		if (core)
			linphone_core_notify_file_transfer_progress_indication(
				core->getCCore(),
				msg,
				cFileTransferInformation,
				offset,
				total
			);
	}
}

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
) {
379 380
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	return d->onSendBody(bh, m, offset, buffer, size);
Ronan's avatar
Ronan committed
381 382
}

383 384 385 386 387 388 389
int ChatMessagePrivate::onSendBody (
	belle_sip_user_body_handler_t *bh,
	belle_sip_message_t *m,
	size_t offset,
	uint8_t *buffer,
	size_t *size
) {
390
	L_Q();
391

392 393
	int retval = -1;
	LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q);
394

395 396
	if (!isFileTransferInProgressAndValid()) {
		if (httpRequest) {
397 398
			lWarning() << "Cancelled request for " << (chatRoom.lock() ? "" : "ORPHAN") <<
				" msg [" << this << "], ignoring " << __FUNCTION__;
399 400 401 402
			releaseHttpRequest();
		}
		return BELLE_SIP_STOP;
	}
403

404 405 406 407 408
	// 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);
409 410
		LinphoneChatMessageCbsFileTransferSendCb file_transfer_send_cb =
			linphone_chat_message_cbs_get_file_transfer_send(cbs);
411 412
		if (file_transfer_send_cb) {
			LinphoneBuffer *lb = file_transfer_send_cb(msg, cFileTransferInformation, offset, *size);
413
			if (lb == nullptr) {
414 415 416 417 418 419 420 421
				*size = 0;
			} else {
				*size = linphone_buffer_get_size(lb);
				memcpy(buffer, linphone_buffer_get_content(lb), *size);
				linphone_buffer_unref(lb);
			}
		} else {
			// Legacy
422 423 424
			shared_ptr<Core> core = q->getCore();
			if (core)
				linphone_core_notify_file_transfer_send(core->getCCore(), msg, cFileTransferInformation, (char *)buffer, size);
425 426
		}
	}
427

428 429 430 431 432
	LinphoneImEncryptionEngine *imee = nullptr;
	shared_ptr<Core> core = q->getCore();
	if (core)
		imee = linphone_core_get_im_encryption_engine(core->getCCore());

433 434
	if (imee) {
		LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
435 436
		LinphoneImEncryptionEngineCbsUploadingFileCb cb_process_uploading_file =
			linphone_im_encryption_engine_cbs_get_process_uploading_file(imee_cbs);
437 438 439 440 441 442
		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) {
443
					lError() << "IM encryption engine process upload file callback returned a size bigger than the size of the buffer, so it will be truncated !";
444 445 446 447 448 449 450
					*size = max_size;
				}
				memcpy(buffer, encrypted_buffer, *size);
			}
			ms_free(encrypted_buffer);
		}
	}
451

452
	return retval <= 0 ? BELLE_SIP_CONTINUE : BELLE_SIP_STOP;
453 454
}

455
static void _chat_message_on_send_end (belle_sip_user_body_handler_t *bh, void *data) {
456 457
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->onSendEnd(bh);
Ronan's avatar
Ronan committed
458 459
}

460
void ChatMessagePrivate::onSendEnd (belle_sip_user_body_handler_t *bh) {
461
	L_Q();
462

463 464 465 466
	LinphoneImEncryptionEngine *imee = nullptr;
	shared_ptr<Core> core = q->getCore();
	if (core)
		imee = linphone_core_get_im_encryption_engine(core->getCCore());
467

468 469 470 471
	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) {
472
			cb_process_uploading_file(imee, L_GET_C_BACK_PTR(q), 0, nullptr, nullptr, nullptr);
473 474
		}
	}
475 476
}

477
void ChatMessagePrivate::fileUploadEndBackgroundTask () {
478
	if (backgroundTaskId) {
479
		lInfo() << "channel [" << this << "]: ending file upload background task with id=[" << backgroundTaskId << "].";
480 481 482
		sal_end_background_task(backgroundTaskId);
		backgroundTaskId = 0;
	}
483 484
}

485
static void _chat_message_file_upload_background_task_ended (void *data) {
486 487
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->fileUploadBackgroundTaskEnded();
488 489
}

490
void ChatMessagePrivate::fileUploadBackgroundTaskEnded () {
491
	lWarning() << "channel [" << this << "]: file upload background task has to be ended now, but work isn't finished.";
492
	fileUploadEndBackgroundTask();
493 494
}

495
void ChatMessagePrivate::fileUploadBeginBackgroundTask () {
496 497
	if (backgroundTaskId == 0) {
		backgroundTaskId = sal_begin_background_task("file transfer upload", _chat_message_file_upload_background_task_ended, this);
498
		if (backgroundTaskId) lInfo() << "channel [" << this << "]: starting file upload background task with id=[" << backgroundTaskId << "].";
499
	}
500 501
}

502
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) {
503 504
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->onRecvBody(bh, m, offset, buffer, size);
505 506
}

507
void ChatMessagePrivate::onRecvBody (belle_sip_user_body_handler_t *bh, belle_sip_message_t *m, size_t offset, uint8_t *buffer, size_t size) {
508
	L_Q();
509

510
	int retval = -1;
511
	uint8_t *decrypted_buffer = nullptr;
512

513
	if (!chatRoom.lock()) {
514 515 516
		q->cancelFileTransfer();
		return;
	}
517

518
	if (!httpRequest || belle_http_request_is_cancelled(httpRequest)) {
519
		lWarning() << "Cancelled request for msg [" << this << "], ignoring " << __FUNCTION__;
520 521
		return;
	}
522

523 524 525 526
	// first call may be with a zero size, ignore it
	if (size == 0) {
		return;
	}
527

528
	decrypted_buffer = (uint8_t *)ms_malloc0(size);
529 530 531 532 533 534

	LinphoneImEncryptionEngine *imee = nullptr;
	shared_ptr<Core> core = q->getCore();
	if (core)
		imee = linphone_core_get_im_encryption_engine(core->getCCore());

535 536 537 538 539 540 541 542 543 544 545
	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);
546

547 548 549 550 551 552 553 554 555 556
	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
557
				linphone_core_notify_file_transfer_recv(core->getCCore(), msg, cFileTransferInformation, (const char *)buffer, size);
558 559 560
			}
		}
	} else {
561
		lWarning() << "File transfer decrypt failed with code " << (int)retval;
562
		setState(ChatMessage::State::FileTransferError);
563 564
	}
}
565

566
static void _chat_message_on_recv_end (belle_sip_user_body_handler_t *bh, void *data) {
567 568 569
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->onRecvEnd(bh);
}
570

571
void ChatMessagePrivate::onRecvEnd (belle_sip_user_body_handler_t *bh) {
572
	L_Q();
573

574 575 576 577 578
	shared_ptr<Core> core = q->getCore();
	if (!core)
		return;

	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(core->getCCore());
579
	int retval = -1;
580

581 582 583 584
	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) {
585
			retval = cb_process_downloading_file(imee, L_GET_C_BACK_PTR(q), 0, nullptr, 0, nullptr);
586 587
		}
	}
Ronan's avatar
Ronan committed
588

589 590 591 592 593 594 595 596 597 598
	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
599
				linphone_core_notify_file_transfer_recv(core->getCCore(), msg, cFileTransferInformation, nullptr, 0);
600 601 602
			}
		}
	}
Ronan's avatar
Ronan committed
603

604 605 606
	if (retval <= 0 && state != ChatMessage::State::FileTransferError) {
		setState(ChatMessage::State::FileTransferDone);
	}
607 608
}

609 610 611 612
bool ChatMessagePrivate::isFileTransferInProgressAndValid () {
	L_Q();
	shared_ptr<ChatRoom> chatRoom = q->getChatRoom();
	return chatRoom && q->getCore() && httpRequest && !belle_http_request_is_cancelled(httpRequest);
613 614
}

615
static void _chat_message_process_response_from_post_file (void *data, const belle_http_response_event_t *event) {
616 617
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processResponseFromPostFile(event);
618 619
}

620
void ChatMessagePrivate::processResponseFromPostFile (const belle_http_response_event_t *event) {
621
	L_Q();
622

623
	if (httpRequest && !isFileTransferInProgressAndValid()) {
624 625
		lWarning() << "Cancelled request for " << (chatRoom.lock() ? "" : "ORPHAN") <<
			" msg [" << this << "], ignoring " << __FUNCTION__;
626 627
		releaseHttpRequest();
		return;
628 629 630 631 632 633 634 635
	}

	// 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;
636
			string first_part_header;
637 638 639
			belle_sip_body_handler_t *first_part_bh;

			bool_t is_file_encryption_enabled = FALSE;
640 641 642 643 644 645 646 647 648
			LinphoneImEncryptionEngine *imee = nullptr;

			shared_ptr<Core> core = q->getCore();
			shared_ptr<ChatRoom> chatRoom = q->getChatRoom();

			if (core)
				imee = linphone_core_get_im_encryption_engine(core->getCCore());

			if (imee && chatRoom) {
649 650 651 652
				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) {
653
					is_file_encryption_enabled = is_encryption_enabled_for_file_transfer_cb(imee, L_GET_C_BACK_PTR(chatRoom));
654 655 656
				}
			}
			// shall we encrypt the file
657
			if (is_file_encryption_enabled && chatRoom) {
658 659 660 661
				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) {
662
					generate_file_transfer_key_cb(imee, L_GET_C_BACK_PTR(chatRoom), L_GET_C_BACK_PTR(q));
663 664 665 666
				}
				// 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
667
				first_part_header = "form-data; name=\"File\"; filename=\"filename.txt\"";
668 669
			} else {
				// temporary storage for the Content-disposition header value
670
				first_part_header = "form-data; name=\"File\"; filename=\"" + string(linphone_content_get_name(cFileTransferInformation)) + "\"";
671 672 673 674
			}

			// 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(
675
					linphone_content_get_size(cFileTransferInformation),
676
					_chat_message_file_transfer_on_progress, nullptr, nullptr,
677 678
					_chat_message_on_send_body, _chat_message_on_send_end, this);
			if (!fileTransferFilePath.empty()) {
679
				belle_sip_user_body_handler_t *body_handler = (belle_sip_user_body_handler_t *)first_part_bh;
680
				// No need to add again the callback for progression, otherwise it will be called twice
681
				first_part_bh = (belle_sip_body_handler_t *)belle_sip_file_body_handler_new(fileTransferFilePath.c_str(), nullptr, this);
682
				linphone_content_set_size(cFileTransferInformation, belle_sip_file_body_handler_get_file_size((belle_sip_file_body_handler_t *)first_part_bh));
683
				belle_sip_file_body_handler_set_user_body_handler((belle_sip_file_body_handler_t *)first_part_bh, body_handler);
684
			} else if (linphone_content_get_buffer(cFileTransferInformation) != nullptr) {
685
				first_part_bh = (belle_sip_body_handler_t *)belle_sip_memory_body_handler_new_from_buffer(
686 687
						linphone_content_get_buffer(cFileTransferInformation),
						linphone_content_get_size(cFileTransferInformation), _chat_message_file_transfer_on_progress, this);
688 689 690
			}

			belle_sip_body_handler_add_header(first_part_bh,
691
				belle_sip_header_create("Content-disposition", first_part_header.c_str()));
692
			belle_sip_body_handler_add_header(first_part_bh,
693 694 695
				(belle_sip_header_t *)belle_sip_header_content_type_create(
					linphone_content_get_type(cFileTransferInformation),
					linphone_content_get_subtype(cFileTransferInformation)));
696 697

			// insert it in a multipart body handler which will manage the boundaries of multipart msg
698
			bh = belle_sip_multipart_body_handler_new(_chat_message_file_transfer_on_progress, this, first_part_bh, nullptr);
699 700 701 702 703

			releaseHttpRequest();
			fileUploadBeginBackgroundTask();
			q->uploadFile();
			belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(httpRequest), BELLE_SIP_BODY_HANDLER(bh));
704
		} else if (code == 200) {     // file has been uploaded correctly, get server reply and send it
705 706 707
			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
708 709
				const char *content_key = linphone_content_get_key(cFileTransferInformation);
				size_t content_key_size = linphone_content_get_key_size(cFileTransferInformation);
710
				if (content_key != nullptr) {
711 712 713 714
					// parse the msg body
					xmlDocPtr xmlMessageBody = xmlParseDoc((const xmlChar *)body);

					xmlNodePtr cur = xmlDocGetRootElement(xmlMessageBody);
715
					if (cur != nullptr) {
716
						cur = cur->xmlChildrenNode;
717
						while (cur != nullptr) {
718 719
							// we found a file info node, check it has a type="file" attribute
							if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) {
720
								xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type");
721 722 723 724
								// 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;
725
									// convert key to base64
726
									size_t b64Size = b64_encode(nullptr, content_key_size, nullptr, 0);
727 728 729
									char *keyb64 = (char *)ms_malloc0(b64Size + 1);
									int xmlStringLength;

730
									b64Size = b64_encode(content_key, content_key_size, keyb64, b64Size);
731
									keyb64[b64Size] = '\0';                   // libxml need a null terminated string
732 733

									// add the node containing the key to the file-info node
734
									xmlNewTextChild(cur, nullptr, (const xmlChar *)"file-key", (const xmlChar *)keyb64);
735 736 737 738
									xmlFree(typeAttribute);
									ms_free(keyb64);

									// look for the file-name node and update its content
739
									while (fileInfoNodeChildren != nullptr) {
740 741
										// we found a the file-name node, update its content with the real filename
										if (!xmlStrcmp(fileInfoNodeChildren->name, (const xmlChar *)"file-name")) {
742
											// update node content
743
											xmlNodeSetContent(fileInfoNodeChildren, (const xmlChar *)(linphone_content_get_name(cFileTransferInformation)));
744 745 746 747 748 749
											break;
										}
										fileInfoNodeChildren = fileInfoNodeChildren->next;
									}

									// dump the xml into msg->message
750 751 752
									char *buffer;
									xmlDocDumpFormatMemoryEnc(xmlMessageBody, (xmlChar **)&buffer, &xmlStringLength, "UTF-8", 0);
									setText(buffer);
753 754 755 756 757 758 759 760
									break;
								}
								xmlFree(typeAttribute);
							}
							cur = cur->next;
						}
					}
					xmlFreeDoc(xmlMessageBody);
761
				} else {         // no encryption key, transfer in plain, just copy the msg sent by server
762
					setText(body);
763
				}
764
				setContentType(ContentType::FileTransfer);
765
				q->updateState(ChatMessage::State::FileTransferDone);
766
				releaseHttpRequest();
767
				send();
768
				fileUploadEndBackgroundTask();
769
			} else {
770
				lWarning() << "Received empty response from server, file transfer failed";
771
				q->updateState(ChatMessage::State::NotDelivered);
772 773
				releaseHttpRequest();
				fileUploadEndBackgroundTask();
774 775
			}
		} else {
776
			lWarning() << "Unhandled HTTP code response " << code << " for file transfer";
777
			q->updateState(ChatMessage::State::NotDelivered);
778 779
			releaseHttpRequest();
			fileUploadEndBackgroundTask();
780 781 782 783
		}
	}
}

784
static void _chat_process_response_headers_from_get_file (void *data, const belle_http_response_event_t *event) {
785 786 787
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processResponseHeadersFromGetFile(event);
}
788

789
static LinphoneContent *createFileTransferInformationFromHeaders (const belle_sip_message_t *m) {
790
	LinphoneContent *content = linphone_content_new();
791

792 793 794
	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 = nullptr, *subtype = nullptr;
795

796 797 798 799 800
	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);
801
		lInfo() << "Extracted content type " << type << " / " << subtype << " from header";
802 803 804 805 806 807
		if (type) {
			linphone_content_set_type(content, type);
		}
		if (subtype) {
			linphone_content_set_subtype(content, subtype);
		}
808 809
	}

810 811
	if (content_length_hdr) {
		linphone_content_set_size(content, belle_sip_header_content_length_get_content_length(content_length_hdr));
812
		lInfo() << "Extracted content length " << linphone_content_get_size(content) << " from header";
813 814
	}

815 816 817
	return content;
}

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

826
		if (cFileTransferInformation == nullptr) {
827
			lWarning() << "No file transfer information for msg [" << this << "]: creating...";
828
			cFileTransferInformation = createFileTransferInformationFromHeaders(response);
829 830
		}

831 832 833 834
		if (cFileTransferInformation) {
			body_size = linphone_content_get_size(cFileTransferInformation);
		}

835
		body_handler = (belle_sip_body_handler_t *)belle_sip_user_body_handler_new(body_size, _chat_message_file_transfer_on_progress,
836 837
				nullptr, _chat_message_on_recv_body,
				nullptr, _chat_message_on_recv_end, this);
838 839 840 841 842 843 844
		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);
845
			}
846
			belle_sip_file_body_handler_set_user_body_handler((belle_sip_file_body_handler_t *)body_handler, bh);
847
		}
848
		belle_sip_message_set_body_handler((belle_sip_message_t *)event->response, body_handler);
849
	}
850
}
851

852
static void _chat_message_process_auth_requested_download (void *data, belle_sip_auth_event *event) {
853 854
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processAuthRequestedDownload(event);
855 856
}

857
void ChatMessagePrivate::processAuthRequestedDownload (const belle_sip_auth_event *event) {
858 859
	L_Q();