file-transfer-chat-message-modifier.cpp 42.1 KB
Newer Older
1 2
/*
 * file-transfer-chat-message-modifier.cpp
Ronan's avatar
Ronan committed
3
 * Copyright (C) 2010-2018 Belledonne Communications SARL
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 * 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.
 *
 * 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
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

20 21
#include "linphone/api/c-content.h"

22
#include "address/address.h"
23 24
#include "bctoolbox/crypto.h"
#include "c-wrapper/c-wrapper.h"
25
#include "chat/chat-message/chat-message-p.h"
26
#include "chat/chat-room/chat-room-p.h"
27 28
#include "content/content-type.h"
#include "content/content.h"
29
#include "core/core.h"
30 31 32 33 34 35 36 37 38 39
#include "logger/logger.h"

#include "file-transfer-chat-message-modifier.h"

// =============================================================================

using namespace std;

LINPHONE_BEGIN_NAMESPACE

40
FileTransferChatMessageModifier::FileTransferChatMessageModifier (belle_http_provider_t *prov) : provider(prov) {
41 42 43
	bgTask.setName("File transfer upload");
}

44 45 46 47 48 49 50 51
belle_http_request_t *FileTransferChatMessageModifier::getHttpRequest () const {
	return httpRequest;
}

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

Ghislain MARY's avatar
Ghislain MARY committed
52
FileTransferChatMessageModifier::~FileTransferChatMessageModifier () {
53 54 55 56
	if (isFileTransferInProgressAndValid())
		cancelFileTransfer(); //to avoid body handler to still refference zombie FileTransferChatMessageModifier
	else
		releaseHttpRequest();
Ghislain MARY's avatar
Ghislain MARY committed
57 58
}

59
ChatMessageModifier::Result FileTransferChatMessageModifier::encode (const shared_ptr<ChatMessage> &message, int &errorCode) {
60 61
	chatMessage = message;

62 63
	currentFileContentToTransfer = nullptr;
	// For each FileContent, upload it and create a FileTransferContent
64
	for (Content *content : message->getContents()) {
65 66
		if (content->isFile()) {
				lInfo() << "Found file content, set it for file upload";
67 68 69 70 71
				FileContent *fileContent = (FileContent *)content;
				currentFileContentToTransfer = fileContent;
				break;
		}
	}
72
	if (!currentFileContentToTransfer)
73
		return ChatMessageModifier::Result::Skipped;
74

75 76 77 78 79
	/* Open a transaction with the server and send an empty request(RCS5.1 section 3.5.4.8.3.1) */
	if (uploadFile() == 0)
		return ChatMessageModifier::Result::Suspended;

	return ChatMessageModifier::Result::Error;
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
}

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

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
) {
	FileTransferChatMessageModifier *d = (FileTransferChatMessageModifier *)data;
	d->fileTransferOnProgress(bh, m, offset, total);
}

void FileTransferChatMessageModifier::fileTransferOnProgress (
	belle_sip_body_handler_t *bh,
	belle_sip_message_t *m,
	size_t offset,
	size_t total
) {
	if (!isFileTransferInProgressAndValid()) {
		releaseHttpRequest();
		return;
	}

106 107 108 109 110
	shared_ptr<ChatMessage> message = chatMessage.lock();
	if (!message)
		return;

	LinphoneChatMessage *msg = L_GET_C_BACK_PTR(message);
111
	LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
112
	LinphoneContent *content = L_GET_C_BACK_PTR((Content *)currentFileContentToTransfer);
113 114 115 116
	if (linphone_chat_message_cbs_get_file_transfer_progress_indication(cbs)) {
		linphone_chat_message_cbs_get_file_transfer_progress_indication(cbs)(msg, content, offset, total);
	} else {
		// Legacy: call back given by application level.
117
		linphone_core_notify_file_transfer_progress_indication(message->getCore()->getCCore(), msg, content, offset, total);
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
	}
}

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
) {
	FileTransferChatMessageModifier *d = (FileTransferChatMessageModifier *)data;
	return d->onSendBody(bh, m, offset, buffer, size);
}

int FileTransferChatMessageModifier::onSendBody (
	belle_sip_user_body_handler_t *bh,
	belle_sip_message_t *m,
	size_t offset,
	uint8_t *buffer,
	size_t *size
) {
	int retval = -1;
141 142 143 144 145
	shared_ptr<ChatMessage> message = chatMessage.lock();
	if (!message)
		return BELLE_SIP_STOP;

	LinphoneChatMessage *msg = L_GET_C_BACK_PTR(message);
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160

	if (!isFileTransferInProgressAndValid()) {
		if (httpRequest) {
			releaseHttpRequest();
		}
		return BELLE_SIP_STOP;
	}

	// 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 (currentFileContentToTransfer->getFilePath().empty() && offset < currentFileContentToTransfer->getFileSize()) {
		// 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);
161
		LinphoneContent *content = L_GET_C_BACK_PTR((Content *)currentFileContentToTransfer);
162 163
		if (file_transfer_send_cb) {
			LinphoneBuffer *lb = file_transfer_send_cb(msg, content, offset, *size);
164
			if (lb) {
165 166 167
				*size = linphone_buffer_get_size(lb);
				memcpy(buffer, linphone_buffer_get_content(lb), *size);
				linphone_buffer_unref(lb);
168 169
			} else {
				*size = 0;
170 171 172
			}
		} else {
			// Legacy
173
			linphone_core_notify_file_transfer_send(message->getCore()->getCCore(), msg, content, (char *)buffer, size);
174 175 176
		}
	}

177
	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(message->getCore()->getCCore());
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
	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) {
					lError() << "IM encryption engine process upload file callback returned a size bigger than the size of the buffer, so it will be truncated !";
					*size = max_size;
				}
				memcpy(buffer, encrypted_buffer, *size);
			}
			ms_free(encrypted_buffer);
		}
	}

	return retval <= 0 ? BELLE_SIP_CONTINUE : BELLE_SIP_STOP;
}

static void _chat_message_on_send_end (belle_sip_user_body_handler_t *bh, void *data) {
	FileTransferChatMessageModifier *d = (FileTransferChatMessageModifier *)data;
	d->onSendEnd(bh);
}

void FileTransferChatMessageModifier::onSendEnd (belle_sip_user_body_handler_t *bh) {
206
	shared_ptr<ChatMessage> message = chatMessage.lock();
207 208
	if (!message)
		return;
209

210
	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(message->getCore()->getCCore());
211 212 213 214
	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) {
215
			cb_process_uploading_file(imee, L_GET_C_BACK_PTR(message), 0, nullptr, nullptr, nullptr);
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
		}
	}
}

static void _chat_message_process_response_from_post_file (void *data, const belle_http_response_event_t *event) {
	FileTransferChatMessageModifier *d = (FileTransferChatMessageModifier *)data;
	d->processResponseFromPostFile(event);
}

void FileTransferChatMessageModifier::processResponseFromPostFile (const belle_http_response_event_t *event) {
	if (httpRequest && !isFileTransferInProgressAndValid()) {
		releaseHttpRequest();
		return;
	}

231
	shared_ptr<ChatMessage> message = chatMessage.lock();
232 233
	if (!message)
		return;
234

235 236 237 238 239 240 241 242 243
	// 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;
			string first_part_header;
			belle_sip_body_handler_t *first_part_bh;

244
			bool is_file_encryption_enabled = false;
245 246
			LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(message->getCore()->getCCore());
			if (imee && message->getChatRoom()) {
247 248 249 250
				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) {
251
					is_file_encryption_enabled = !!is_encryption_enabled_for_file_transfer_cb(imee, L_GET_C_BACK_PTR(message->getChatRoom()));
252 253
				}
			}
Sylvain Berfini's avatar
Sylvain Berfini committed
254 255 256 257 258 259

			FileTransferContent *fileTransferContent = new FileTransferContent();
			fileTransferContent->setContentType(ContentType::FileTransfer);
			fileTransferContent->setFileSize(currentFileContentToTransfer->getFileSize()); // Copy file size information
			message->getPrivate()->addContent(fileTransferContent);

260
			// shall we encrypt the file
261
			if (is_file_encryption_enabled && message->getChatRoom()) {
262 263 264 265
				// 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
				first_part_header = "form-data; name=\"File\"; filename=\"filename.txt\"";
266

Sylvain Berfini's avatar
Sylvain Berfini committed
267 268 269 270 271 272
				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) {
					generate_file_transfer_key_cb(imee, L_GET_C_BACK_PTR(message->getChatRoom()), L_GET_C_BACK_PTR(message));
				}
273 274 275 276 277 278 279 280 281 282 283 284 285 286
			} else {
				// temporary storage for the Content-disposition header value
				first_part_header = "form-data; name=\"File\"; filename=\"" + currentFileContentToTransfer->getFileName() + "\"";
			}

			// 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(currentFileContentToTransfer->getFileSize(),
					_chat_message_file_transfer_on_progress, nullptr, nullptr,
					_chat_message_on_send_body, _chat_message_on_send_end, this);
			if (!currentFileContentToTransfer->getFilePath().empty()) {
				belle_sip_user_body_handler_t *body_handler = (belle_sip_user_body_handler_t *)first_part_bh;
				// 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(currentFileContentToTransfer->getFilePath().c_str(), nullptr, this);
				belle_sip_file_body_handler_set_user_body_handler((belle_sip_file_body_handler_t *)first_part_bh, body_handler);
Sylvain Berfini's avatar
Sylvain Berfini committed
287 288
				// Ensure the file size has been set to the correct value
				fileTransferContent->setFileSize(belle_sip_file_body_handler_get_file_size((belle_sip_file_body_handler_t *)first_part_bh));
289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
			} else if (!currentFileContentToTransfer->isEmpty()) {
				first_part_bh = (belle_sip_body_handler_t *)belle_sip_memory_body_handler_new_from_buffer(
						ms_strdup(currentFileContentToTransfer->getBodyAsString().c_str()),
						currentFileContentToTransfer->getSize(), _chat_message_file_transfer_on_progress, this);
			}

			belle_sip_body_handler_add_header(first_part_bh,
				belle_sip_header_create("Content-disposition", first_part_header.c_str()));
			belle_sip_body_handler_add_header(first_part_bh,
				(belle_sip_header_t *)belle_sip_header_content_type_create(
					currentFileContentToTransfer->getContentType().getType().c_str(),
					currentFileContentToTransfer->getContentType().getSubType().c_str()));

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

			releaseHttpRequest();
			fileUploadBeginBackgroundTask();
			uploadFile();
			belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(httpRequest), BELLE_SIP_BODY_HANDLER(bh));
		} else if (code == 200) {     // file has been uploaded correctly, get server reply and send it
Sylvain Berfini's avatar
Sylvain Berfini committed
310 311 312
			FileTransferContent *fileTransferContent = nullptr;
			for (Content *c : message->getPrivate()->getContents()) {
				if (c->isFileTransfer()) {
313 314
					FileTransferContent *tmpContent = static_cast<FileTransferContent *>(c);
					if (!tmpContent->getFileContent() && tmpContent->getSize() == 0) {
315
						// If FileTransferContent doesn't have a FileContent yet and is empty
316 317 318 319
						// It's the one we seek, otherwise it may be a previous uploaded FileTransferContent
						fileTransferContent = tmpContent;
						break;
					}
320
				}
Sylvain Berfini's avatar
Sylvain Berfini committed
321
			}
322

Sylvain Berfini's avatar
Sylvain Berfini committed
323 324
			const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response);
			if (body && strlen(body) > 0) {
325
				// if we have an encryption key for the file, we must insert it into the msg and restore the correct filename
326 327 328
				const unsigned char *contentKey = reinterpret_cast<const unsigned char *>(fileTransferContent->getFileKey().data());
				size_t contentKeySize = fileTransferContent->getFileKeySize();
				if (contentKeySize > 0) {
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
					// parse the msg body
					xmlDocPtr xmlMessageBody = xmlParseDoc((const xmlChar *)body);

					xmlNodePtr cur = xmlDocGetRootElement(xmlMessageBody);
					if (cur != nullptr) {
						cur = cur->xmlChildrenNode;
						while (cur != nullptr) {
							// we found a file info node, check it has a type="file" attribute
							if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) {
								xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type");
								// 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;
									// convert key to base64
344
									size_t b64Size;
345
									bctbx_base64_encode(nullptr, &b64Size, contentKey, contentKeySize);
346
									unsigned char *keyb64 = (unsigned char *)ms_malloc0(b64Size + 1);
347 348
									int xmlStringLength;

349
									bctbx_base64_encode(keyb64, &b64Size, contentKey, contentKeySize);
350
									keyb64[b64Size] = '\0'; // libxml need a null terminated string
351 352 353 354 355 356 357 358 359 360 361

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

									// look for the file-name node and update its content
									while (fileInfoNodeChildren != nullptr) {
										// we found a the file-name node, update its content with the real filename
										if (!xmlStrcmp(fileInfoNodeChildren->name, (const xmlChar *)"file-name")) {
											// update node content
362
											xmlNodeSetContent(fileInfoNodeChildren, (const xmlChar *)(currentFileContentToTransfer->getFileName().c_str()));
363 364 365 366 367 368 369 370
											break;
										}
										fileInfoNodeChildren = fileInfoNodeChildren->next;
									}

									// dump the xml into msg->message
									char *buffer;
									xmlDocDumpFormatMemoryEnc(xmlMessageBody, (xmlChar **)&buffer, &xmlStringLength, "UTF-8", 0);
371
									fileTransferContent->setBody(buffer);
372 373 374 375 376 377 378 379
									break;
								}
								xmlFree(typeAttribute);
							}
							cur = cur->next;
						}
					}
					xmlFreeDoc(xmlMessageBody);
380 381
				} else { // no encryption key, transfer in plain, just copy the msg sent by server
					fileTransferContent->setBody(body);
382 383
				}

384
				FileContent *fileContent = currentFileContentToTransfer;
385 386
				fileTransferContent->setFileContent(fileContent);

387
				message->getPrivate()->removeContent(fileContent);
388

389
				message->getPrivate()->setState(ChatMessage::State::FileTransferDone);
390
				releaseHttpRequest();
391
				message->getPrivate()->send();
392 393 394
				fileUploadEndBackgroundTask();
			} else {
				lWarning() << "Received empty response from server, file transfer failed";
Sylvain Berfini's avatar
Sylvain Berfini committed
395 396 397 398 399 400 401 402 403
				FileTransferContent *fileTransferContent = nullptr;
				for (Content *c : message->getPrivate()->getContents()) {
					if (c->isFileTransfer()) {
						fileTransferContent = static_cast<FileTransferContent *>(c);
						message->getPrivate()->removeContent(fileTransferContent);
						delete fileTransferContent;
						break;
					}
				}
404
				message->getPrivate()->setState(ChatMessage::State::NotDelivered);
405 406 407
				releaseHttpRequest();
				fileUploadEndBackgroundTask();
			}
408 409
		} else if (code == 400) {
			lWarning() << "Received HTTP code response " << code << " for file transfer, probably meaning file is too large";
Sylvain Berfini's avatar
Sylvain Berfini committed
410 411 412 413 414 415 416 417 418
			FileTransferContent *fileTransferContent = nullptr;
			for (Content *c : message->getPrivate()->getContents()) {
				if (c->isFileTransfer()) {
					fileTransferContent = static_cast<FileTransferContent *>(c);
					message->getPrivate()->removeContent(fileTransferContent);
					delete fileTransferContent;
					break;
				}
			}
419
			message->getPrivate()->setState(ChatMessage::State::FileTransferError);
420 421
			releaseHttpRequest();
			fileUploadEndBackgroundTask();
422 423
		} else {
			lWarning() << "Unhandled HTTP code response " << code << " for file transfer";
Sylvain Berfini's avatar
Sylvain Berfini committed
424 425 426 427 428 429 430 431 432
			FileTransferContent *fileTransferContent = nullptr;
			for (Content *c : message->getPrivate()->getContents()) {
				if (c->isFileTransfer()) {
					fileTransferContent = static_cast<FileTransferContent *>(c);
					message->getPrivate()->removeContent(fileTransferContent);
					delete fileTransferContent;
					break;
				}
			}
433
			message->getPrivate()->setState(ChatMessage::State::NotDelivered);
434 435 436 437 438 439 440 441 442
			releaseHttpRequest();
			fileUploadEndBackgroundTask();
		}
	}
}

static void _chat_message_process_io_error_upload (void *data, const belle_sip_io_error_event_t *event) {
	FileTransferChatMessageModifier *d = (FileTransferChatMessageModifier *)data;
	d->processIoErrorUpload(event);
443 444
}

445 446
void FileTransferChatMessageModifier::processIoErrorUpload (const belle_sip_io_error_event_t *event) {
	lError() << "I/O Error during file upload of msg [" << this << "]";
447 448 449
	shared_ptr<ChatMessage> message = chatMessage.lock();
	if (!message)
		return;
450
	message->getPrivate()->setState(ChatMessage::State::NotDelivered);
451 452 453 454 455 456 457 458 459 460
	releaseHttpRequest();
}

static void _chat_message_process_auth_requested_upload (void *data, belle_sip_auth_event *event) {
	FileTransferChatMessageModifier *d = (FileTransferChatMessageModifier *)data;
	d->processAuthRequestedUpload(event);
}

void FileTransferChatMessageModifier::processAuthRequestedUpload (const belle_sip_auth_event *event) {
	lError() << "Error during file upload: auth requested for msg [" << this << "]";
461 462 463
	shared_ptr<ChatMessage> message = chatMessage.lock();
	if (!message)
		return;
464
	message->getPrivate()->setState(ChatMessage::State::NotDelivered);
465 466 467 468 469
	releaseHttpRequest();
}

int FileTransferChatMessageModifier::uploadFile () {
	if (httpRequest) {
470
		lError() << "Unable to upload file: there is already an upload in progress.";
471 472 473
		return -1;
	}

474 475 476 477
	shared_ptr<ChatMessage> message = chatMessage.lock();
	if (!message)
		return -1;

478
	// THIS IS ONLY FOR BACKWARD C API COMPAT
479 480
	if (currentFileContentToTransfer->getFilePath().empty() && !message->getPrivate()->getFileTransferFilepath().empty()) {
		currentFileContentToTransfer->setFilePath(message->getPrivate()->getFileTransferFilepath());
481 482 483 484 485 486 487
	}

	belle_http_request_listener_callbacks_t cbs = { 0 };
	cbs.process_response = _chat_message_process_response_from_post_file;
	cbs.process_io_error = _chat_message_process_io_error_upload;
	cbs.process_auth_requested = _chat_message_process_auth_requested_upload;

488
	const char *url = linphone_core_get_file_transfer_server(message->getCore()->getCCore());
489
	return startHttpTransfer(url ? url : "", "POST", &cbs);
490 491 492 493 494
}

int FileTransferChatMessageModifier::startHttpTransfer (const string &url, const string &action, belle_http_request_listener_callbacks_t *cbs) {
	belle_generic_uri_t *uri = nullptr;

495 496 497 498
	shared_ptr<ChatMessage> message = chatMessage.lock();
	if (!message)
		return -1;

499 500 501 502 503
	if (url.empty()) {
		lWarning() << "Cannot process file transfer msg [" << this << "]: no file remote URI configured.";
		goto error;
	}
	uri = belle_generic_uri_parse(url.c_str());
504
	if (!uri || !belle_generic_uri_get_host(uri)) {
505 506 507 508 509
		lWarning() << "Cannot process file transfer msg [" << this << "]: incorrect file remote URI configured '" <<
			url << "'.";
		goto error;
	}

510 511 512 513 514 515
	httpRequest = belle_http_request_create(
		action.c_str(),
		uri,
		belle_sip_header_create("User-Agent", linphone_core_get_user_agent(message->getCore()->getCCore())),
		nullptr
	);
516

517
	if (!httpRequest) {
518 519 520 521 522 523 524 525
		lWarning() << "Could not create http request for uri " << url;
		goto error;
	}
	// keep a reference to the http request to be able to cancel it during upload
	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
	httpListener = belle_http_request_listener_create_from_callbacks(cbs, this);
526
	belle_http_provider_send_request(provider, httpRequest, httpListener);
527 528 529 530 531 532 533 534 535 536
	return 0;

error:
	if (uri) {
		belle_sip_object_unref(uri);
	}
	return -1;
}

void FileTransferChatMessageModifier::fileUploadBeginBackgroundTask () {
537 538 539 540
	shared_ptr<ChatMessage> message = chatMessage.lock();
	if (!message)
		return;
	bgTask.start(message->getCore());
541 542 543
}

void FileTransferChatMessageModifier::fileUploadEndBackgroundTask () {
544
	bgTask.stop();
545 546 547 548
}

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

549
static void fillFileTransferContentInformationsFromVndGsmaRcsFtHttpXml (FileTransferContent *fileTransferContent) {
550
	xmlChar *fileUrl = nullptr;
551 552 553 554 555 556
	xmlDocPtr xmlMessageBody;
	xmlNodePtr cur;
	/* parse the msg body to get all informations from it */
	xmlMessageBody = xmlParseDoc((const xmlChar *)fileTransferContent->getBodyAsString().c_str());

	cur = xmlDocGetRootElement(xmlMessageBody);
557
	if (cur) {
558
		cur = cur->xmlChildrenNode;
559
		while (cur) {
560 561 562 563 564
			if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) {
				/* we found a file info node, check if it has a type="file" attribute */
				xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type");
				if (!xmlStrcmp(typeAttribute, (const xmlChar *)"file")) {         /* this is the node we are looking for */
					cur = cur->xmlChildrenNode;           /* now loop on the content of the file-info node */
565
					while (cur) {
566 567 568 569 570 571
						if (!xmlStrcmp(cur->name, (const xmlChar *)"file-size")) {
							xmlChar *fileSizeString = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
							size_t size = (size_t)strtol((const char *)fileSizeString, nullptr, 10);
							fileTransferContent->setFileSize(size);
							xmlFree(fileSizeString);
						}
572 573 574 575 576 577
						if (!xmlStrcmp(cur->name, (const xmlChar *)"file-name")) {
							xmlChar *filename = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
							fileTransferContent->setFileName((char *)filename);
							xmlFree(filename);
						}
						if (!xmlStrcmp(cur->name, (const xmlChar *)"data")) {
578
							fileUrl = xmlGetProp(cur, (const xmlChar *)"url");
579
						}
Sylvain Berfini's avatar
Sylvain Berfini committed
580 581 582 583 584 585
						if (!xmlStrcmp(cur->name, (const xmlChar *)"file-key")) {
							// there is a key in the msg: file has been encrypted
							// convert the key from base 64
							xmlChar *keyb64 = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
							size_t keyLength;
							bctbx_base64_decode(NULL, &keyLength, (unsigned char *)keyb64, strlen((const char *)keyb64));
586
							uint8_t *keyBuffer = (uint8_t *)malloc(keyLength + 1);
Sylvain Berfini's avatar
Sylvain Berfini committed
587 588
							// decode the key into local key buffer
							bctbx_base64_decode(keyBuffer, &keyLength, (unsigned char *)keyb64, strlen((const char *)keyb64));
589
+							keyBuffer[keyLength] = '\0';
Sylvain Berfini's avatar
Sylvain Berfini committed
590 591 592 593 594
							fileTransferContent->setFileKey((const char *)keyBuffer, keyLength);
							// duplicate key value into the linphone content private structure
							xmlFree(keyb64);
							free(keyBuffer);
						}
595 596 597 598 599 600 601 602 603 604 605 606 607

						cur = cur->next;
					}
					xmlFree(typeAttribute);
					break;
				}
				xmlFree(typeAttribute);
			}
			cur = cur->next;
		}
	}
	xmlFreeDoc(xmlMessageBody);

608
	fileTransferContent->setFileUrl(fileUrl ? (const char *)fileUrl : "");
609

610
	xmlFree(fileUrl);
611 612
}

613
ChatMessageModifier::Result FileTransferChatMessageModifier::decode (const shared_ptr<ChatMessage> &message, int &errorCode) {
614 615
	chatMessage = message;

616
	Content internalContent = message->getInternalContent();
617 618 619 620 621
	if (internalContent.getContentType() == ContentType::FileTransfer) {
		FileTransferContent *fileTransferContent = new FileTransferContent();
		fileTransferContent->setContentType(internalContent.getContentType());
		fileTransferContent->setBody(internalContent.getBody());
		fillFileTransferContentInformationsFromVndGsmaRcsFtHttpXml(fileTransferContent);
622
		message->addContent(fileTransferContent);
623 624 625
		return ChatMessageModifier::Result::Done;
	}

626
	for (Content *content : message->getContents()) {
627 628
		if (content->isFileTransfer()) {
			FileTransferContent *fileTransferContent = static_cast<FileTransferContent *>(content);
629 630 631 632
			fillFileTransferContentInformationsFromVndGsmaRcsFtHttpXml(fileTransferContent);
		}
	}
	return ChatMessageModifier::Result::Done;
633 634
}

635 636
// ----------------------------------------------------------

637
static void createFileTransferInformationsFromVndGsmaRcsFtHttpXml (FileTransferContent *fileTransferContent) {
638
	xmlChar *fileUrl = nullptr;
639 640 641 642 643 644 645
	xmlDocPtr xmlMessageBody;
	xmlNodePtr cur;
	/* parse the msg body to get all informations from it */
	xmlMessageBody = xmlParseDoc((const xmlChar *)fileTransferContent->getBodyAsString().c_str());
	FileContent *fileContent = new FileContent();

	cur = xmlDocGetRootElement(xmlMessageBody);
646
	if (cur) {
647
		cur = cur->xmlChildrenNode;
648
		while (cur) {
649 650 651 652 653
			if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) {
				/* we found a file info node, check if it has a type="file" attribute */
				xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type");
				if (!xmlStrcmp(typeAttribute, (const xmlChar *)"file")) {         /* this is the node we are looking for */
					cur = cur->xmlChildrenNode;           /* now loop on the content of the file-info node */
654
					while (cur) {
655 656 657 658 659 660 661 662 663 664
						if (!xmlStrcmp(cur->name, (const xmlChar *)"file-size")) {
							xmlChar *fileSizeString = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
							size_t size = (size_t)strtol((const char *)fileSizeString, nullptr, 10);
							fileContent->setFileSize(size);
							xmlFree(fileSizeString);
						}

						if (!xmlStrcmp(cur->name, (const xmlChar *)"file-name")) {
							xmlChar *filename = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
							fileContent->setFileName((char *)filename);
665

666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
							xmlFree(filename);
						}
						if (!xmlStrcmp(cur->name, (const xmlChar *)"content-type")) {
							xmlChar *content_type = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
							int contentTypeIndex = 0;
							char *type;
							char *subtype;
							while (content_type[contentTypeIndex] != '/' && content_type[contentTypeIndex] != '\0') {
								contentTypeIndex++;
							}
							type = ms_strndup((char *)content_type, contentTypeIndex);
							subtype = ms_strdup(((char *)content_type + contentTypeIndex + 1));
							ContentType contentType(type, subtype);
							fileContent->setContentType(contentType);
							ms_free(subtype);
							ms_free(type);
							ms_free(content_type);
						}
						if (!xmlStrcmp(cur->name, (const xmlChar *)"data")) {
685
							fileUrl = xmlGetProp(cur, (const xmlChar *)"url");
686 687
						}

688
						if (!xmlStrcmp(cur->name, (const xmlChar *)"file-key")) {
689 690 691
							// there is a key in the msg: file has been encrypted
							// convert the key from base 64
							xmlChar *keyb64 = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1);
692 693
							size_t keyLength;
							bctbx_base64_decode(NULL, &keyLength, (unsigned char *)keyb64, strlen((const char *)keyb64));
694 695
							uint8_t *keyBuffer = (uint8_t *)malloc(keyLength);
							// decode the key into local key buffer
696
							bctbx_base64_decode(keyBuffer, &keyLength, (unsigned char *)keyb64, strlen((const char *)keyb64));
697
							fileTransferContent->setFileKey((const char *)keyBuffer, keyLength);
698 699 700
							// duplicate key value into the linphone content private structure
							xmlFree(keyb64);
							free(keyBuffer);
701
						}
702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718

						cur = cur->next;
					}
					xmlFree(typeAttribute);
					break;
				}
				xmlFree(typeAttribute);
			}
			cur = cur->next;
		}
	}
	xmlFreeDoc(xmlMessageBody);

	fileContent->setFilePath(fileTransferContent->getFilePath()); // Copy file path from file transfer content to file content for file body handler
	// Link the FileContent to the FileTransferContent
	fileTransferContent->setFileContent(fileContent);

719
	xmlFree(fileUrl);
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737
}

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) {
	FileTransferChatMessageModifier *d = (FileTransferChatMessageModifier *)data;
	d->onRecvBody(bh, m, offset, buffer, size);
}

void FileTransferChatMessageModifier::onRecvBody (belle_sip_user_body_handler_t *bh, belle_sip_message_t *m, size_t offset, uint8_t *buffer, size_t size) {
	if (!httpRequest || belle_http_request_is_cancelled(httpRequest)) {
		lWarning() << "Cancelled request for msg [" << this << "], ignoring " << __FUNCTION__;
		return;
	}

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

738
	shared_ptr<ChatMessage> message = chatMessage.lock();
739 740
	if (!message)
		return;
741

742
	int retval = -1;
743
	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(message->getCore()->getCCore());
744 745 746 747
	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) {
748
			uint8_t *decrypted_buffer = (uint8_t *)ms_malloc0(size);
749
			retval = cb_process_downloading_file(imee, L_GET_C_BACK_PTR(message), offset, (const uint8_t *)buffer, size, decrypted_buffer);
750 751 752
			if (retval == 0) {
				memcpy(buffer, decrypted_buffer, size);
			}
753
			ms_free(decrypted_buffer);
754 755 756 757 758
		}
	}

	if (retval <= 0) {
		if (currentFileContentToTransfer->getFilePath().empty()) {
759
			LinphoneChatMessage *msg = L_GET_C_BACK_PTR(message);
760
			LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
761
			LinphoneContent *content = L_GET_C_BACK_PTR((Content *)currentFileContentToTransfer);
762 763 764 765 766 767
			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, content, lb);
				linphone_buffer_unref(lb);
			} else {
				// Legacy: call back given by application level
768
				linphone_core_notify_file_transfer_recv(message->getCore()->getCCore(), msg, content, (const char *)buffer, size);
769 770 771 772
			}
		}
	} else {
		lWarning() << "File transfer decrypt failed with code " << (int)retval;
773
		message->getPrivate()->setState(ChatMessage::State::FileTransferError);
774 775 776 777 778 779 780 781 782
	}
}

static void _chat_message_on_recv_end (belle_sip_user_body_handler_t *bh, void *data) {
	FileTransferChatMessageModifier *d = (FileTransferChatMessageModifier *)data;
	d->onRecvEnd(bh);
}

void FileTransferChatMessageModifier::onRecvEnd (belle_sip_user_body_handler_t *bh) {
783 784 785
	shared_ptr<ChatMessage> message = chatMessage.lock();
	if (!message)
		return;
786 787

	int retval = -1;
788
	LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(message->getCore()->getCCore());
789 790 791 792
	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) {
793
			retval = cb_process_downloading_file(imee, L_GET_C_BACK_PTR(message), 0, nullptr, 0, nullptr);
794 795 796 797 798
		}
	}

	if (retval <= 0) {
		if (currentFileContentToTransfer->getFilePath().empty()) {
799
			LinphoneChatMessage *msg = L_GET_C_BACK_PTR(message);
800
			LinphoneChatMessageCbs *cbs = linphone_chat_message_get_callbacks(msg);
801
			LinphoneContent *content = L_GET_C_BACK_PTR((Content *)currentFileContentToTransfer);
802 803 804 805 806 807
			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, content, lb);
				linphone_buffer_unref(lb);
			} else {
				// Legacy: call back given by application level
808
				linphone_core_notify_file_transfer_recv(message->getCore()->getCCore(), msg, content, nullptr, 0);
809 810 811 812
			}
		}
	}

813
	if (retval <= 0 && message->getState() != ChatMessage::State::FileTransferError) {
814 815
		// Remove the FileTransferContent from the message and store the FileContent
		FileContent *fileContent = currentFileContentToTransfer;
816
		message->getPrivate()->addContent(fileContent);
817
		for (Content *content : message->getContents()) {
818 819
			if (content->isFileTransfer()) {
				FileTransferContent *fileTransferContent = static_cast<FileTransferContent *>(content);
820
				if (fileTransferContent->getFileContent() == fileContent) {
821
					message->getPrivate()->removeContent(content);
822
					delete fileTransferContent;
823 824 825 826
					break;
				}
			}
		}
827
		message->getPrivate()->setState(ChatMessage::State::FileTransferDone);
828 829 830 831 832 833 834 835 836 837 838 839 840 841 842
	}
}

static void _chat_process_response_headers_from_get_file (void *data, const belle_http_response_event_t *event) {
	FileTransferChatMessageModifier *d = (FileTransferChatMessageModifier *)data;
	d->processResponseHeadersFromGetFile(event);
}

static FileContent* createFileTransferInformationFromHeaders (const belle_sip_message_t *m) {
	FileContent *fileContent = new FileContent();

	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"));

	if (content_type_hdr) {
843 844
		const char *type = belle_sip_header_content_type_get_type(content_type_hdr);
		const char *subtype = belle_sip_header_content_type_get_subtype(content_type_hdr);
845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862
		lInfo() << "Extracted content type " << type << " / " << subtype << " from header";
		ContentType contentType(type, subtype);
		fileContent->setContentType(contentType);
	}
	if (content_length_hdr) {
		fileContent->setFileSize(belle_sip_header_content_length_get_content_length(content_length_hdr));
		lInfo() << "Extracted content length " << fileContent->getFileSize() << " from header";
	}

	return fileContent;
}

void FileTransferChatMessageModifier::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);

863 864 865 866
		shared_ptr<ChatMessage> message = chatMessage.lock();
		if (!message)
			return;

867
		if (currentFileContentToTransfer) {
868 869 870
			belle_sip_header_content_length_t *content_length_hdr = BELLE_SIP_HEADER_CONTENT_LENGTH(belle_sip_message_get_header(response, "Content-Length"));
			currentFileContentToTransfer->setFileSize(belle_sip_header_content_length_get_content_length(content_length_hdr));
			lInfo() << "Extracted content length " << currentFileContentToTransfer->getFileSize() << " from header";
871 872 873
		} else {
			lWarning() << "No file transfer information for msg [" << this << "]: creating...";
			FileContent *content = createFileTransferInformationFromHeaders(response);
874
			message->addContent(content);
875 876
		}

877 878
		size_t body_size = 0;
		if (currentFileContentToTransfer)
879 880
			body_size = currentFileContentToTransfer->getFileSize();

881 882 883 884 885
		belle_sip_body_handler_t *body_handler = (belle_sip_body_handler_t *)belle_sip_user_body_handler_new(
			body_size, _chat_message_file_transfer_on_progress,
			nullptr, _chat_message_on_recv_body,
			nullptr, _chat_message_on_recv_end, this
		);
886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906
		if (!currentFileContentToTransfer->getFilePath().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(currentFileContentToTransfer->getFilePath().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);
			}
			belle_sip_file_body_handler_set_user_body_handler((belle_sip_file_body_handler_t *)body_handler, bh);
		}
		belle_sip_message_set_body_handler((belle_sip_message_t *)event->response, body_handler);
	}
}

static void _chat_message_process_auth_requested_download (void *data, belle_sip_auth_event *event) {
	FileTransferChatMessageModifier *d = (FileTransferChatMessageModifier *)data;
	d->processAuthRequestedDownload(event);
}

void FileTransferChatMessageModifier::processAuthRequestedDownload (const belle_sip_auth_event *event) {
	lError() << "Error during file download : auth requested for msg [" << this << "]";
907 908 909
	shared_ptr<ChatMessage> message = chatMessage.lock();
	if (!message)
		return;
910
	message->getPrivate()->setState(ChatMessage::State::FileTransferError);
911 912 913 914 915 916 917 918 919 920
	releaseHttpRequest();
}

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

void FileTransferChatMessageModifier::processIoErrorDownload (const belle_sip_io_error_event_t *event) {
	lError() << "I/O Error during file download msg [" << this << "]";
921 922 923
	shared_ptr<ChatMessage> message = chatMessage.lock();
	if (!message)
		return;
924
	message->getPrivate()->setState(ChatMessage::State::FileTransferError);
925 926 927 928 929 930 931 932 933 934 935
	releaseHttpRequest();
}

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

void FileTransferChatMessageModifier::processResponseFromGetFile (const belle_http_response_event_t *event) {
	// check the answer code
	if (event->response) {
936 937 938 939
		shared_ptr<ChatMessage> message = chatMessage.lock();
		if (!message)
			return;

940 941 942
		int code = belle_http_response_get_status_code(event->response);
		if (code >= 400 && code < 500) {
			lWarning() << "File transfer failed with code " << code;
943
			message->getPrivate()->setState(ChatMessage::State::FileTransferError);
944 945 946 947 948 949 950
		} else if (code != 200) {
			lWarning() << "Unhandled HTTP code response " << code << " for file transfer";
		}
		releaseHttpRequest();
	}
}

951 952 953 954
bool FileTransferChatMessageModifier::downloadFile (
	const shared_ptr<ChatMessage> &message,
	FileTransferContent *fileTransferContent
) {
955
	chatMessage = message;
956

957
	if (httpRequest) {
958 959
		lError() << "There is already a download in progress.";
		return false;
960 961 962
	}

	if (fileTransferContent->getContentType() != ContentType::FileTransfer) {
963 964
		lError() << "Content type is not a FileTransfer.";
		return false;
965 966 967 968 969
	}

	createFileTransferInformationsFromVndGsmaRcsFtHttpXml(fileTransferContent);
	FileContent *fileContent = fileTransferContent->getFileContent();
	currentFileContentToTransfer = fileContent;
970
	if (!currentFileContentToTransfer)
971
		return false;
972

973
	// THIS IS ONLY FOR BACKWARD C API COMPAT
974 975
	if (currentFileContentToTransfer->getFilePath().empty() && !message->getPrivate()->getFileTransferFilepath().empty()) {
		currentFileContentToTransfer->setFilePath(message->getPrivate()->getFileTransferFilepath());
976
	}
977

978 979 980 981 982 983
	belle_http_request_listener_callbacks_t cbs = { 0 };
	cbs.process_response_headers = _chat_process_response_headers_from_get_file;
	cbs.process_response = _chat_message_process_response_from_get_file;
	cbs.process_io_error = _chat_message_process_io_error_download;
	cbs.process_auth_requested = _chat_message_process_auth_requested_download;
	int err = startHttpTransfer(fileTransferContent->getFileUrl(), "GET", &cbs); // File URL has been set by createFileTransferInformationsFromVndGsmaRcsFtHttpXml
984
	if (err == -1)
985
		return false;
986
	// start the download, status is In Progress
987
	message->getPrivate()->setState(ChatMessage::State::InProgress);
988
	return true;
989
}
990 991 992

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

993
void FileTransferChatMessageModifier::cancelFileTransfer () {
994
	if (!httpRequest) {
995
		lInfo() << "No existing file transfer - nothing to cancel";
996 997 998 999 1000 1001 1002 1003 1004 1005 1006
		return;
	}

	if (!belle_http_request_is_cancelled(httpRequest)) {
		shared_ptr<ChatMessage> message = chatMessage.lock();
		if (message) {
			lInfo() << "Canceling file transfer " << (
				currentFileContentToTransfer->getFilePath().empty()
					? L_C_TO_STRING(linphone_core_get_file_transfer_server(message->getCore()->getCCore()))
					: currentFileContentToTransfer->getFilePath().c_str()
				);
1007

1008 1009 1010
		} else {
			lInfo() << "Warning: http request still running for ORPHAN msg: this is a memory leak";
		}
1011
		belle_http_provider_cancel_request(provider, httpRequest);
1012
	}
1013
	releaseHttpRequest();
1014 1015
}

1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026