chat-message.cpp 59.8 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
	L_Q();
183
	if (direction == ChatMessage::Direction::Incoming) {
184
		if (contents.size() > 0) {
185 186
			Content content = contents.front();
			cText = content.getBodyAsString();
187 188 189 190
		} else {
			cText = internalContent.getBodyAsString();
		}
	} else {
191 192
		if (q->hasTextContent()) {
			cText = q->getTextContent().getBodyAsString();
193
		} else if (!internalContent.isEmpty()) {
194 195 196 197 198 199
			cText = internalContent.getBodyAsString();
		} else {
			if (contents.size() > 0) {
				Content content = contents.front();
				cText = content.getBodyAsString();
			}
200
		}
201
	}
202 203 204
	return cText;
}

205
void ChatMessagePrivate::setText (const string &text) {
206
	internalContent.setBody(text);
207 208
}

209
LinphoneContent *ChatMessagePrivate::getFileTransferInformation () const {
210 211 212 213 214 215
	L_Q();
	//TODO cache and unref to prevent leak
	if (q->hasFileTransferContent()) {
		return q->getFileTransferContent().toLinphoneContent();
	}
	return NULL;
216 217
}

218 219 220 221 222 223 224 225
void ChatMessagePrivate::setFileTransferInformation (const LinphoneContent *c_content) {
	L_Q();

	Content content;
	ContentType contentType(linphone_content_get_type(c_content), linphone_content_get_subtype(c_content));
	content.setContentType(contentType);
	if (linphone_content_get_string_buffer(c_content) != NULL) {
		content.setBody(linphone_content_get_string_buffer(c_content));
226
	}
227
	content.setContentDisposition(linphone_content_get_name(c_content));
228 229 230 231 232
	
	// This is a ugly workaround required to be able to get the total size of the file in the content
	vector<char> empty(linphone_content_get_size(c_content));
	content.setBody(empty);

233
	q->addContent(content);
234 235 236 237
}

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

238
string ChatMessagePrivate::createImdnXml (Imdn::Type imdnType, LinphoneReason reason) {
239
	xmlBufferPtr buf;
240 241
	xmlTextWriterPtr writer;
	int err;
242
	string content;
243
	char *datetime = nullptr;
244

245 246
	// Check that the chat message has a message id.
	if (id.empty()) return nullptr;
247 248

	buf = xmlBufferCreate();
249
	if (buf == nullptr) {
250
		lError() << "Error creating the XML buffer";
251 252 253
		return content;
	}
	writer = xmlNewTextWriterMemory(buf, 0);
254
	if (writer == nullptr) {
255
		lError() << "Error creating the XML writer";
256 257 258
		return content;
	}

259
	datetime = linphone_timestamp_to_rfc3339_string(time);
260
	err = xmlTextWriterStartDocument(writer, "1.0", "UTF-8", nullptr);
261
	if (err >= 0) {
262 263
		err = xmlTextWriterStartElementNS(writer, nullptr, (const xmlChar *)"imdn",
				(const xmlChar *)"urn:ietf:params:xml:ns:imdn");
264 265
	}
	if ((err >= 0) && (reason != LinphoneReasonNone)) {
266
		err = xmlTextWriterWriteAttributeNS(writer, (const xmlChar *)"xmlns", (const xmlChar *)"linphoneimdn", nullptr, (const xmlChar *)"http://www.linphone.org/xsds/imdn.xsd");
267 268
	}
	if (err >= 0) {
269
		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"message-id", (const xmlChar *)id.c_str());
270 271 272 273 274
	}
	if (err >= 0) {
		err = xmlTextWriterWriteElement(writer, (const xmlChar *)"datetime", (const xmlChar *)datetime);
	}
	if (err >= 0) {
275
		if (imdnType == Imdn::Type::Delivery) {
276 277 278 279 280 281 282 283 284 285
			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) {
286
			if (imdnType == Imdn::Type::Delivery) {
287 288 289 290 291 292 293 294 295 296 297 298 299
				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)) {
300
		err = xmlTextWriterStartElementNS(writer, (const xmlChar *)"linphoneimdn", (const xmlChar *)"reason", nullptr);
301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
		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.
330
		content = string((char *)buf->content);
331 332 333 334
	}
	xmlFreeTextWriter(writer);
	xmlBufferFree(buf);
	ms_free(datetime);
335
	return content;
336 337
}

338 339 340 341 342 343 344 345 346 347 348 349 350 351
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
) {
352 353 354
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->fileTransferOnProgress(bh, m, offset, total);
}
Sylvain Berfini's avatar
Sylvain Berfini committed
355

356 357 358 359 360 361
void ChatMessagePrivate::fileTransferOnProgress (
	belle_sip_body_handler_t *bh,
	belle_sip_message_t *m,
	size_t offset,
	size_t total
) {
362
	L_Q();
Sylvain Berfini's avatar
Sylvain Berfini committed
363

364
	if (!isFileTransferInProgressAndValid()) {
365 366
		lWarning() << "Cancelled request for " << (chatRoom.lock() ? "" : "ORPHAN") << " msg [" << this <<
			"], ignoring " << __FUNCTION__;
367 368 369
		releaseHttpRequest();
		return;
	}
Ronan's avatar
Ronan committed
370

371 372
	LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q);
	LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
373
	LinphoneContent *content = currentFileTransferContent->toLinphoneContent();
374
	if (linphone_chat_message_cbs_get_file_transfer_progress_indication(cbs)) {
375
		linphone_chat_message_cbs_get_file_transfer_progress_indication(cbs)(msg, content, offset, total);
376
	} else {
377 378 379 380 381 382
		// 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,
383
				content,
384 385 386 387
				offset,
				total
			);
	}
388
	linphone_content_unref(content);
389 390 391 392 393 394 395 396 397 398
}

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
) {
399 400
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	return d->onSendBody(bh, m, offset, buffer, size);
Ronan's avatar
Ronan committed
401 402
}

403 404 405 406 407 408 409
int ChatMessagePrivate::onSendBody (
	belle_sip_user_body_handler_t *bh,
	belle_sip_message_t *m,
	size_t offset,
	uint8_t *buffer,
	size_t *size
) {
410
	L_Q();
411

412 413
	int retval = -1;
	LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q);
414

415 416
	if (!isFileTransferInProgressAndValid()) {
		if (httpRequest) {
417 418
			lWarning() << "Cancelled request for " << (chatRoom.lock() ? "" : "ORPHAN") <<
				" msg [" << this << "], ignoring " << __FUNCTION__;
419 420 421 422
			releaseHttpRequest();
		}
		return BELLE_SIP_STOP;
	}
423

424 425
	// if we've not reach the end of file yet, ask for more data
	// in case of file body handler, won't be called
426
	if (fileTransferFilePath.empty() && offset < currentFileTransferContent->getSize()) {
427 428
		// get data from call back
		LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
429 430
		LinphoneChatMessageCbsFileTransferSendCb file_transfer_send_cb =
			linphone_chat_message_cbs_get_file_transfer_send(cbs);
431
		LinphoneContent *content = currentFileTransferContent->toLinphoneContent();
432
		if (file_transfer_send_cb) {
433
			LinphoneBuffer *lb = file_transfer_send_cb(msg, content, offset, *size);
434
			if (lb == nullptr) {
435 436 437 438 439 440 441 442
				*size = 0;
			} else {
				*size = linphone_buffer_get_size(lb);
				memcpy(buffer, linphone_buffer_get_content(lb), *size);
				linphone_buffer_unref(lb);
			}
		} else {
			// Legacy
443 444
			shared_ptr<Core> core = q->getCore();
			if (core)
445
				linphone_core_notify_file_transfer_send(core->getCCore(), msg, content, (char *)buffer, size);
446
		}
447
		linphone_content_unref(content);
448
	}
449

450 451 452 453 454
	LinphoneImEncryptionEngine *imee = nullptr;
	shared_ptr<Core> core = q->getCore();
	if (core)
		imee = linphone_core_get_im_encryption_engine(core->getCCore());

455 456
	if (imee) {
		LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
457 458
		LinphoneImEncryptionEngineCbsUploadingFileCb cb_process_uploading_file =
			linphone_im_encryption_engine_cbs_get_process_uploading_file(imee_cbs);
459 460 461 462 463 464
		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) {
465
					lError() << "IM encryption engine process upload file callback returned a size bigger than the size of the buffer, so it will be truncated !";
466 467 468 469 470 471 472
					*size = max_size;
				}
				memcpy(buffer, encrypted_buffer, *size);
			}
			ms_free(encrypted_buffer);
		}
	}
473

474
	return retval <= 0 ? BELLE_SIP_CONTINUE : BELLE_SIP_STOP;
475 476
}

477
static void _chat_message_on_send_end (belle_sip_user_body_handler_t *bh, void *data) {
478 479
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->onSendEnd(bh);
Ronan's avatar
Ronan committed
480 481
}

482
void ChatMessagePrivate::onSendEnd (belle_sip_user_body_handler_t *bh) {
483
	L_Q();
484

485 486 487 488
	LinphoneImEncryptionEngine *imee = nullptr;
	shared_ptr<Core> core = q->getCore();
	if (core)
		imee = linphone_core_get_im_encryption_engine(core->getCCore());
489

490 491 492 493
	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) {
494
			cb_process_uploading_file(imee, L_GET_C_BACK_PTR(q), 0, nullptr, nullptr, nullptr);
495 496
		}
	}
497 498
}

499
void ChatMessagePrivate::fileUploadEndBackgroundTask () {
500
	if (backgroundTaskId) {
501
		lInfo() << "channel [" << this << "]: ending file upload background task with id=[" << backgroundTaskId << "].";
502 503 504
		sal_end_background_task(backgroundTaskId);
		backgroundTaskId = 0;
	}
505 506
}

507
static void _chat_message_file_upload_background_task_ended (void *data) {
508 509
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->fileUploadBackgroundTaskEnded();
510 511
}

512
void ChatMessagePrivate::fileUploadBackgroundTaskEnded () {
513
	lWarning() << "channel [" << this << "]: file upload background task has to be ended now, but work isn't finished.";
514
	fileUploadEndBackgroundTask();
515 516
}

517
void ChatMessagePrivate::fileUploadBeginBackgroundTask () {
518 519
	if (backgroundTaskId == 0) {
		backgroundTaskId = sal_begin_background_task("file transfer upload", _chat_message_file_upload_background_task_ended, this);
520
		if (backgroundTaskId) lInfo() << "channel [" << this << "]: starting file upload background task with id=[" << backgroundTaskId << "].";
521
	}
522 523
}

524
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) {
525 526
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->onRecvBody(bh, m, offset, buffer, size);
527 528
}

529
void ChatMessagePrivate::onRecvBody (belle_sip_user_body_handler_t *bh, belle_sip_message_t *m, size_t offset, uint8_t *buffer, size_t size) {
530
	L_Q();
531

532
	int retval = -1;
533
	uint8_t *decrypted_buffer = nullptr;
534

535
	if (!chatRoom.lock()) {
536 537 538
		q->cancelFileTransfer();
		return;
	}
539

540
	if (!httpRequest || belle_http_request_is_cancelled(httpRequest)) {
541
		lWarning() << "Cancelled request for msg [" << this << "], ignoring " << __FUNCTION__;
542 543
		return;
	}
544

545 546 547 548
	// first call may be with a zero size, ignore it
	if (size == 0) {
		return;
	}
549

550
	decrypted_buffer = (uint8_t *)ms_malloc0(size);
551 552 553 554 555 556

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

557 558 559 560 561 562 563 564 565 566 567
	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);
568

569 570 571 572
	if (retval <= 0) {
		if (fileTransferFilePath.empty()) {
			LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q);
			LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
573
			LinphoneContent *content = currentFileTransferContent->toLinphoneContent();
574 575
			if (linphone_chat_message_cbs_get_file_transfer_recv(cbs)) {
				LinphoneBuffer *lb = linphone_buffer_new_from_data(buffer, size);
576
				linphone_chat_message_cbs_get_file_transfer_recv(cbs)(msg, content, lb);
577 578 579
				linphone_buffer_unref(lb);
			} else {
				// Legacy: call back given by application level
580
				linphone_core_notify_file_transfer_recv(core->getCCore(), msg, content, (const char *)buffer, size);
581
			}
582
			linphone_content_unref(content);
583 584
		}
	} else {
585
		lWarning() << "File transfer decrypt failed with code " << (int)retval;
586
		setState(ChatMessage::State::FileTransferError);
587 588
	}
}
589

590
static void _chat_message_on_recv_end (belle_sip_user_body_handler_t *bh, void *data) {
591 592 593
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->onRecvEnd(bh);
}
594

595
void ChatMessagePrivate::onRecvEnd (belle_sip_user_body_handler_t *bh) {
596
	L_Q();
597

598 599 600 601 602
	shared_ptr<Core> core = q->getCore();
	if (!core)
		return;

	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(core->getCCore());
603
	int retval = -1;
604

605 606 607 608
	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) {
609
			retval = cb_process_downloading_file(imee, L_GET_C_BACK_PTR(q), 0, nullptr, 0, nullptr);
610 611
		}
	}
Ronan's avatar
Ronan committed
612

613 614 615 616
	if (retval <= 0) {
		if (fileTransferFilePath.empty()) {
			LinphoneChatMessage *msg = L_GET_C_BACK_PTR(q);
			LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
617
			LinphoneContent *content = currentFileTransferContent->toLinphoneContent();
618 619
			if (linphone_chat_message_cbs_get_file_transfer_recv(cbs)) {
				LinphoneBuffer *lb = linphone_buffer_new();
620
				linphone_chat_message_cbs_get_file_transfer_recv(cbs)(msg, content, lb);
621 622 623
				linphone_buffer_unref(lb);
			} else {
				// Legacy: call back given by application level
624
				linphone_core_notify_file_transfer_recv(core->getCCore(), msg, content, nullptr, 0);
625
			}
626
			linphone_content_unref(content);
627 628
		}
	}
Ronan's avatar
Ronan committed
629

630 631 632
	if (retval <= 0 && state != ChatMessage::State::FileTransferError) {
		setState(ChatMessage::State::FileTransferDone);
	}
633 634
}

635 636 637 638
bool ChatMessagePrivate::isFileTransferInProgressAndValid () {
	L_Q();
	shared_ptr<ChatRoom> chatRoom = q->getChatRoom();
	return chatRoom && q->getCore() && httpRequest && !belle_http_request_is_cancelled(httpRequest);
639 640
}

641
static void _chat_message_process_response_from_post_file (void *data, const belle_http_response_event_t *event) {
642 643
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processResponseFromPostFile(event);
644 645
}

646
void ChatMessagePrivate::processResponseFromPostFile (const belle_http_response_event_t *event) {
647
	L_Q();
648

649
	if (httpRequest && !isFileTransferInProgressAndValid()) {
650 651
		lWarning() << "Cancelled request for " << (chatRoom.lock() ? "" : "ORPHAN") <<
			" msg [" << this << "], ignoring " << __FUNCTION__;
652 653
		releaseHttpRequest();
		return;
654 655 656 657 658 659 660 661
	}

	// 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;
662
			string first_part_header;
663 664 665
			belle_sip_body_handler_t *first_part_bh;

			bool_t is_file_encryption_enabled = FALSE;
666 667 668 669 670
			LinphoneImEncryptionEngine *imee = nullptr;

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

671
			imee = linphone_core_get_im_encryption_engine(core->getCCore());
672 673

			if (imee && chatRoom) {
674 675 676 677
				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) {
678
					is_file_encryption_enabled = is_encryption_enabled_for_file_transfer_cb(imee, L_GET_C_BACK_PTR(chatRoom));
679 680 681
				}
			}
			// shall we encrypt the file
682
			if (is_file_encryption_enabled && chatRoom) {
683 684 685 686
				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) {
687
					generate_file_transfer_key_cb(imee, L_GET_C_BACK_PTR(chatRoom), L_GET_C_BACK_PTR(q));
688 689 690 691
				}
				// 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
692
				first_part_header = "form-data; name=\"File\"; filename=\"filename.txt\"";
693 694
			} else {
				// temporary storage for the Content-disposition header value
695
				first_part_header = "form-data; name=\"File\"; filename=\"" + currentFileTransferContent->getContentDisposition() + "\"";
696 697 698
			}

			// create a user body handler to take care of the file and add the content disposition and content-type headers
699
			first_part_bh = (belle_sip_body_handler_t *)belle_sip_user_body_handler_new(currentFileTransferContent->getSize(),
700
					_chat_message_file_transfer_on_progress, nullptr, nullptr,
701 702
					_chat_message_on_send_body, _chat_message_on_send_end, this);
			if (!fileTransferFilePath.empty()) {
703
				belle_sip_user_body_handler_t *body_handler = (belle_sip_user_body_handler_t *)first_part_bh;
704
				// No need to add again the callback for progression, otherwise it will be called twice
705
				first_part_bh = (belle_sip_body_handler_t *)belle_sip_file_body_handler_new(fileTransferFilePath.c_str(), nullptr, this);
706
				//linphone_content_set_size(cFileTransferInformation, belle_sip_file_body_handler_get_file_size((belle_sip_file_body_handler_t *)first_part_bh));
707
				belle_sip_file_body_handler_set_user_body_handler((belle_sip_file_body_handler_t *)first_part_bh, body_handler);
708
			} else if (!currentFileTransferContent->isEmpty()) {
709
				first_part_bh = (belle_sip_body_handler_t *)belle_sip_memory_body_handler_new_from_buffer(
710 711
						ms_strdup(currentFileTransferContent->getBodyAsString().c_str()),
						currentFileTransferContent->getSize(), _chat_message_file_transfer_on_progress, this);
712 713 714
			}

			belle_sip_body_handler_add_header(first_part_bh,
715
				belle_sip_header_create("Content-disposition", first_part_header.c_str()));
716
			belle_sip_body_handler_add_header(first_part_bh,
717
				(belle_sip_header_t *)belle_sip_header_content_type_create(
718 719
					currentFileTransferContent->getContentType().getType().c_str(),
					currentFileTransferContent->getContentType().getSubType().c_str()));
720 721

			// insert it in a multipart body handler which will manage the boundaries of multipart msg
722
			bh = belle_sip_multipart_body_handler_new(_chat_message_file_transfer_on_progress, this, first_part_bh, nullptr);
723 724 725 726 727

			releaseHttpRequest();
			fileUploadBeginBackgroundTask();
			q->uploadFile();
			belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(httpRequest), BELLE_SIP_BODY_HANDLER(bh));
728
		} else if (code == 200) {     // file has been uploaded correctly, get server reply and send it
729 730
			const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response);
			if (body && strlen(body) > 0) {
731
				//TODO
732
				// if we have an encryption key for the file, we must insert it into the msg and restore the correct filename
733
				/*const char *content_key = linphone_content_get_key(cFileTransferInformation);
734
				size_t content_key_size = linphone_content_get_key_size(cFileTransferInformation);
735
				if (content_key != nullptr) {
736 737 738 739
					// parse the msg body
					xmlDocPtr xmlMessageBody = xmlParseDoc((const xmlChar *)body);

					xmlNodePtr cur = xmlDocGetRootElement(xmlMessageBody);
740
					if (cur != nullptr) {
741
						cur = cur->xmlChildrenNode;
742
						while (cur != nullptr) {
743 744
							// we found a file info node, check it has a type="file" attribute
							if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) {
745
								xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type");
746 747 748 749
								// 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;
750
									// convert key to base64
751
									size_t b64Size = b64_encode(nullptr, content_key_size, nullptr, 0);
752 753 754
									char *keyb64 = (char *)ms_malloc0(b64Size + 1);
									int xmlStringLength;

755
									b64Size = b64_encode(content_key, content_key_size, keyb64, b64Size);
756
									keyb64[b64Size] = '\0';                   // libxml need a null terminated string
757 758

									// add the node containing the key to the file-info node
759
									xmlNewTextChild(cur, nullptr, (const xmlChar *)"file-key", (const xmlChar *)keyb64);
760 761 762 763
									xmlFree(typeAttribute);
									ms_free(keyb64);

									// look for the file-name node and update its content
764
									while (fileInfoNodeChildren != nullptr) {
765 766
										// we found a the file-name node, update its content with the real filename
										if (!xmlStrcmp(fileInfoNodeChildren->name, (const xmlChar *)"file-name")) {
767
											// update node content
768
											xmlNodeSetContent(fileInfoNodeChildren, (const xmlChar *)(linphone_content_get_name(cFileTransferInformation)));
769 770 771 772 773 774
											break;
										}
										fileInfoNodeChildren = fileInfoNodeChildren->next;
									}

									// dump the xml into msg->message
775 776 777
									char *buffer;
									xmlDocDumpFormatMemoryEnc(xmlMessageBody, (xmlChar **)&buffer, &xmlStringLength, "UTF-8", 0);
									setText(buffer);
778 779 780 781 782 783 784 785
									break;
								}
								xmlFree(typeAttribute);
							}
							cur = cur->next;
						}
					}
					xmlFreeDoc(xmlMessageBody);
786
				} else {        // no encryption key, transfer in plain, just copy the msg sent by server
787
					setText(body);
788
				}
789
				setContentType(ContentType::FileTransfer);*/
790
				
791 792
				(*currentFileTransferContent).setContentType(ContentType::FileTransfer);
				(*currentFileTransferContent).setBody(body);
793

794
				q->updateState(ChatMessage::State::FileTransferDone);
795
				releaseHttpRequest();
796
				send();
797
				fileUploadEndBackgroundTask();
798
			} else {
799
				lWarning() << "Received empty response from server, file transfer failed";
800
				q->updateState(ChatMessage::State::NotDelivered);
801 802
				releaseHttpRequest();
				fileUploadEndBackgroundTask();
803 804
			}
		} else {
805
			lWarning() << "Unhandled HTTP code response " << code << " for file transfer";
806
			q->updateState(ChatMessage::State::NotDelivered);
807 808
			releaseHttpRequest();
			fileUploadEndBackgroundTask();
809 810 811 812
		}
	}
}

813
static void _chat_process_response_headers_from_get_file (void *data, const belle_http_response_event_t *event) {
814 815 816
	ChatMessagePrivate *d = (ChatMessagePrivate *)data;
	d->processResponseHeadersFromGetFile(event);
}
817

818 819
static Content createFileTransferInformationFromHeaders (const belle_sip_message_t *m) {
	Content content;
820

821 822 823
	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;
824

825 826 827
	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);
828
		lInfo() << "Extracted content type " << type << " / " << subtype << " from header";
829
		ContentType contentType(type, subtype);
830
	}
831
	if (content_length_hdr) {
Sylvain Berfini's avatar