1 /***************************************************************************
2 * chat_file_transfer.c
3 *
4 * Sun Jun 5 19:34:18 2005
5 * Copyright 2005 Simon Morlat
6 * Email simon dot morlat at linphone dot org
7 ****************************************************************************/
8
9 /*
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 */
24
25 #include "linphone/core.h"
26 #include "private.h"
27 #include "ortp/b64.h"
28
file_transfer_in_progress_and_valid(LinphoneChatMessage * msg)29 static bool_t file_transfer_in_progress_and_valid(LinphoneChatMessage* msg) {
30 return (msg->chat_room && msg->chat_room->lc && msg->http_request && !belle_http_request_is_cancelled(msg->http_request));
31 }
32
_release_http_request(LinphoneChatMessage * msg)33 static void _release_http_request(LinphoneChatMessage* msg) {
34 if (msg->http_request) {
35 belle_sip_object_unref(msg->http_request);
36 msg->http_request = NULL;
37 if (msg->http_listener){
38 belle_sip_object_unref(msg->http_listener);
39 msg->http_listener = NULL;
40 // unhold the reference that the listener was holding on the message
41 linphone_chat_message_unref(msg);
42 }
43 }
44 }
45
linphone_chat_message_process_io_error_upload(void * data,const belle_sip_io_error_event_t * event)46 static void linphone_chat_message_process_io_error_upload(void *data, const belle_sip_io_error_event_t *event) {
47 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
48 ms_error("I/O Error during file upload of msg [%p]", msg);
49 linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered);
50 _release_http_request(msg);
51 linphone_chat_room_remove_transient_message(msg->chat_room, msg);
52 linphone_chat_message_unref(msg);
53 }
54
linphone_chat_message_process_auth_requested_upload(void * data,belle_sip_auth_event_t * event)55 static void linphone_chat_message_process_auth_requested_upload(void *data, belle_sip_auth_event_t *event) {
56 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
57 ms_error("Error during file upload: auth requested for msg [%p]", msg);
58 linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered);
59 _release_http_request(msg);
60 linphone_chat_room_remove_transient_message(msg->chat_room, msg);
61 linphone_chat_message_unref(msg);
62 }
63
linphone_chat_message_process_io_error_download(void * data,const belle_sip_io_error_event_t * event)64 static void linphone_chat_message_process_io_error_download(void *data, const belle_sip_io_error_event_t *event) {
65 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
66 ms_error("I/O Error during file download msg [%p]", msg);
67 linphone_chat_message_update_state(msg, LinphoneChatMessageStateFileTransferError);
68 _release_http_request(msg);
69 }
70
linphone_chat_message_process_auth_requested_download(void * data,belle_sip_auth_event_t * event)71 static void linphone_chat_message_process_auth_requested_download(void *data, belle_sip_auth_event_t *event) {
72 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
73 ms_error("Error during file download : auth requested for msg [%p]", msg);
74 linphone_chat_message_update_state(msg, LinphoneChatMessageStateFileTransferError);
75 _release_http_request(msg);
76 }
77
linphone_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)78 static void linphone_chat_message_file_transfer_on_progress(belle_sip_body_handler_t *bh, belle_sip_message_t *m,
79 void *data, size_t offset, size_t total) {
80 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
81 if (!file_transfer_in_progress_and_valid(msg)) {
82 ms_warning("Cancelled request for %s msg [%p], ignoring %s", msg->chat_room?"":"ORPHAN", msg, __FUNCTION__);
83 _release_http_request(msg);
84 return;
85 }
86 if (linphone_chat_message_cbs_get_file_transfer_progress_indication(msg->callbacks)) {
87 linphone_chat_message_cbs_get_file_transfer_progress_indication(msg->callbacks)(
88 msg, msg->file_transfer_information, offset, total);
89 } else {
90 /* Legacy: call back given by application level */
91 linphone_core_notify_file_transfer_progress_indication(msg->chat_room->lc, msg, msg->file_transfer_information,
92 offset, total);
93 }
94 }
95
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)96 static int on_send_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t *m,
97 void *data, size_t offset, uint8_t *buffer, size_t *size) {
98 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
99 LinphoneCore *lc = NULL;
100 LinphoneImEncryptionEngine *imee = NULL;
101 int retval = -1;
102
103 if (!file_transfer_in_progress_and_valid(msg)) {
104 if (msg->http_request) {
105 ms_warning("Cancelled request for %s msg [%p], ignoring %s", msg->chat_room?"":"ORPHAN", msg, __FUNCTION__);
106 _release_http_request(msg);
107 }
108 return BELLE_SIP_STOP;
109 }
110
111 lc = msg->chat_room->lc;
112 /* if we've not reach the end of file yet, ask for more data */
113 /* in case of file body handler, won't be called */
114 if (msg->file_transfer_filepath == NULL && offset < linphone_content_get_size(msg->file_transfer_information)) {
115 /* get data from call back */
116 LinphoneChatMessageCbsFileTransferSendCb file_transfer_send_cb = linphone_chat_message_cbs_get_file_transfer_send(msg->callbacks);
117 if (file_transfer_send_cb) {
118 LinphoneBuffer *lb = file_transfer_send_cb(msg, msg->file_transfer_information, offset, *size);
119 if (lb == NULL) {
120 *size = 0;
121 } else {
122 *size = linphone_buffer_get_size(lb);
123 memcpy(buffer, linphone_buffer_get_content(lb), *size);
124 linphone_buffer_unref(lb);
125 }
126 } else {
127 /* Legacy */
128 linphone_core_notify_file_transfer_send(lc, msg, msg->file_transfer_information, (char *)buffer, size);
129 }
130 }
131
132 imee = linphone_core_get_im_encryption_engine(lc);
133 if (imee) {
134 LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
135 LinphoneImEncryptionEngineCbsUploadingFileCb cb_process_uploading_file = linphone_im_encryption_engine_cbs_get_process_uploading_file(imee_cbs);
136 if (cb_process_uploading_file) {
137 size_t max_size = *size;
138 uint8_t *encrypted_buffer = (uint8_t *)ms_malloc0(max_size);
139 retval = cb_process_uploading_file(imee, msg, offset, (const uint8_t *)buffer, size, encrypted_buffer);
140 if (retval == 0) {
141 if (*size > max_size) {
142 ms_error("IM encryption engine process upload file callback returned a size bigger than the size of the buffer, so it will be truncated !");
143 *size = max_size;
144 }
145 memcpy(buffer, encrypted_buffer, *size);
146 }
147 ms_free(encrypted_buffer);
148 }
149 }
150
151 return retval <= 0 ? BELLE_SIP_CONTINUE : BELLE_SIP_STOP;
152 }
153
on_send_end(belle_sip_user_body_handler_t * bh,void * data)154 static void on_send_end(belle_sip_user_body_handler_t *bh, void *data) {
155 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
156 LinphoneCore *lc = msg->chat_room->lc;
157 LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc);
158
159 if (imee) {
160 LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
161 LinphoneImEncryptionEngineCbsUploadingFileCb cb_process_uploading_file = linphone_im_encryption_engine_cbs_get_process_uploading_file(imee_cbs);
162 if (cb_process_uploading_file) {
163 cb_process_uploading_file(imee, msg, 0, NULL, NULL, NULL);
164 }
165 }
166 }
167
file_upload_end_background_task(LinphoneChatMessage * obj)168 static void file_upload_end_background_task(LinphoneChatMessage *obj){
169 if (obj->bg_task_id){
170 ms_message("channel [%p]: ending file upload background task with id=[%lx].",obj,obj->bg_task_id);
171 sal_end_background_task(obj->bg_task_id);
172 obj->bg_task_id=0;
173 }
174 }
175
file_upload_background_task_ended(LinphoneChatMessage * obj)176 static void file_upload_background_task_ended(LinphoneChatMessage *obj){
177 ms_warning("channel [%p]: file upload background task has to be ended now, but work isn't finished.",obj);
178 file_upload_end_background_task(obj);
179 }
180
file_upload_begin_background_task(LinphoneChatMessage * obj)181 static void file_upload_begin_background_task(LinphoneChatMessage *obj){
182 if (obj->bg_task_id==0){
183 obj->bg_task_id=sal_begin_background_task("file transfer upload",(void (*)(void*))file_upload_background_task_ended, obj);
184 if (obj->bg_task_id) ms_message("channel [%p]: starting file upload background task with id=[%lx].",obj,obj->bg_task_id);
185 }
186 }
187
linphone_chat_message_process_response_from_post_file(void * data,const belle_http_response_event_t * event)188 static void linphone_chat_message_process_response_from_post_file(void *data,
189 const belle_http_response_event_t *event) {
190 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
191
192 if (msg->http_request && !file_transfer_in_progress_and_valid(msg)) {
193 ms_warning("Cancelled request for %s msg [%p], ignoring %s", msg->chat_room?"":"ORPHAN", msg, __FUNCTION__);
194 _release_http_request(msg);
195 return;
196 }
197
198 /* check the answer code */
199 if (event->response) {
200 int code = belle_http_response_get_status_code(event->response);
201 if (code == 204) { /* this is the reply to the first post to the server - an empty msg */
202 /* start uploading the file */
203 belle_sip_multipart_body_handler_t *bh;
204 char *first_part_header;
205 belle_sip_body_handler_t *first_part_bh;
206
207 bool_t is_file_encryption_enabled = FALSE;
208 LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(msg->chat_room->lc);
209 if (imee) {
210 LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
211 LinphoneImEncryptionEngineCbsIsEncryptionEnabledForFileTransferCb is_encryption_enabled_for_file_transfer_cb =
212 linphone_im_encryption_engine_cbs_get_is_encryption_enabled_for_file_transfer(imee_cbs);
213 if (is_encryption_enabled_for_file_transfer_cb) {
214 is_file_encryption_enabled = is_encryption_enabled_for_file_transfer_cb(imee, msg->chat_room);
215 }
216 }
217 /* shall we encrypt the file */
218 if (is_file_encryption_enabled) {
219 LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
220 LinphoneImEncryptionEngineCbsGenerateFileTransferKeyCb generate_file_transfer_key_cb =
221 linphone_im_encryption_engine_cbs_get_generate_file_transfer_key(imee_cbs);
222 if (generate_file_transfer_key_cb) {
223 generate_file_transfer_key_cb(imee, msg->chat_room, msg);
224 }
225 /* temporary storage for the Content-disposition header value : use a generic filename to not leak it
226 * Actual filename stored in msg->file_transfer_information->name will be set in encrypted msg
227 * sended to the */
228 first_part_header = belle_sip_strdup_printf("form-data; name=\"File\"; filename=\"filename.txt\"");
229 } else {
230 /* temporary storage for the Content-disposition header value */
231 first_part_header = belle_sip_strdup_printf("form-data; name=\"File\"; filename=\"%s\"",
232 linphone_content_get_name(msg->file_transfer_information));
233 }
234
235 /* create a user body handler to take care of the file and add the content disposition and content-type
236 * headers */
237 first_part_bh = (belle_sip_body_handler_t *)belle_sip_user_body_handler_new(
238 linphone_content_get_size(msg->file_transfer_information),
239 linphone_chat_message_file_transfer_on_progress, NULL, NULL,
240 on_send_body, on_send_end, msg);
241 if (msg->file_transfer_filepath != NULL) {
242 belle_sip_user_body_handler_t *body_handler = (belle_sip_user_body_handler_t *)first_part_bh;
243 first_part_bh = (belle_sip_body_handler_t *)belle_sip_file_body_handler_new(msg->file_transfer_filepath,
244 NULL, msg); // No need to add again the callback for progression, otherwise it will be called twice
245 linphone_content_set_size(msg->file_transfer_information, belle_sip_file_body_handler_get_file_size((belle_sip_file_body_handler_t *)first_part_bh));
246 belle_sip_file_body_handler_set_user_body_handler((belle_sip_file_body_handler_t *)first_part_bh, body_handler);
247 } else if (linphone_content_get_buffer(msg->file_transfer_information) != NULL) {
248 first_part_bh = (belle_sip_body_handler_t *)belle_sip_memory_body_handler_new_from_buffer(
249 linphone_content_get_buffer(msg->file_transfer_information),
250 linphone_content_get_size(msg->file_transfer_information), linphone_chat_message_file_transfer_on_progress, msg);
251 }
252
253 belle_sip_body_handler_add_header(first_part_bh,
254 belle_sip_header_create("Content-disposition", first_part_header));
255 belle_sip_free(first_part_header);
256 belle_sip_body_handler_add_header(first_part_bh,
257 (belle_sip_header_t *)belle_sip_header_content_type_create(
258 linphone_content_get_type(msg->file_transfer_information),
259 linphone_content_get_subtype(msg->file_transfer_information)));
260
261 /* insert it in a multipart body handler which will manage the boundaries of multipart msg */
262 bh = belle_sip_multipart_body_handler_new(linphone_chat_message_file_transfer_on_progress, msg, first_part_bh, NULL);
263
264 linphone_chat_message_ref(msg);
265 _release_http_request(msg);
266 file_upload_begin_background_task(msg);
267 linphone_chat_room_upload_file(msg);
268 belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(msg->http_request), BELLE_SIP_BODY_HANDLER(bh));
269 linphone_chat_message_unref(msg);
270 } else if (code == 200) { /* file has been uploaded correctly, get server reply and send it */
271 const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response);
272 if (body && strlen(body) > 0) {
273 /* if we have an encryption key for the file, we must insert it into the msg and restore the correct
274 * filename */
275 const char *content_key = linphone_content_get_key(msg->file_transfer_information);
276 size_t content_key_size = linphone_content_get_key_size(msg->file_transfer_information);
277 if (content_key != NULL) {
278 /* parse the msg body */
279 xmlDocPtr xmlMessageBody = xmlParseDoc((const xmlChar *)body);
280
281 xmlNodePtr cur = xmlDocGetRootElement(xmlMessageBody);
282 if (cur != NULL) {
283 cur = cur->xmlChildrenNode;
284 while (cur != NULL) {
285 if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) { /* we found a file info node, check
286 it has a type="file" attribute */
287 xmlChar *typeAttribute = xmlGetProp(cur, (const xmlChar *)"type");
288 if (!xmlStrcmp(typeAttribute,
289 (const xmlChar *)"file")) { /* this is the node we are looking for : add a
290 file-key children node */
291 xmlNodePtr fileInfoNodeChildren =
292 cur
293 ->xmlChildrenNode; /* need to parse the children node to update the file-name
294 one */
295 /* convert key to base64 */
296 size_t b64Size = b64_encode(NULL, content_key_size, NULL, 0);
297 char *keyb64 = (char *)ms_malloc0(b64Size + 1);
298 int xmlStringLength;
299
300 b64Size = b64_encode(content_key, content_key_size, keyb64, b64Size);
301 keyb64[b64Size] = '\0'; /* libxml need a null terminated string */
302
303 /* add the node containing the key to the file-info node */
304 xmlNewTextChild(cur, NULL, (const xmlChar *)"file-key", (const xmlChar *)keyb64);
305 xmlFree(typeAttribute);
306 ms_free(keyb64);
307
308 /* look for the file-name node and update its content */
309 while (fileInfoNodeChildren != NULL) {
310 if (!xmlStrcmp(
311 fileInfoNodeChildren->name,
312 (const xmlChar *)"file-name")) { /* we found a the file-name node, update
313 its content with the real filename */
314 /* update node content */
315 xmlNodeSetContent(fileInfoNodeChildren,
316 (const xmlChar *)(linphone_content_get_name(
317 msg->file_transfer_information)));
318 break;
319 }
320 fileInfoNodeChildren = fileInfoNodeChildren->next;
321 }
322
323 /* dump the xml into msg->message */
324 xmlDocDumpFormatMemoryEnc(xmlMessageBody, (xmlChar **)&msg->message, &xmlStringLength,
325 "UTF-8", 0);
326
327 break;
328 }
329 xmlFree(typeAttribute);
330 }
331 cur = cur->next;
332 }
333 }
334 xmlFreeDoc(xmlMessageBody);
335 } else { /* no encryption key, transfer in plain, just copy the msg sent by server */
336 msg->message = ms_strdup(body);
337 }
338 msg->content_type = ms_strdup("application/vnd.gsma.rcs-ft-http+xml");
339 linphone_chat_message_ref(msg);
340 linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferDone);
341 _release_http_request(msg);
342 _linphone_chat_room_send_message(msg->chat_room, msg);
343 file_upload_end_background_task(msg);
344 linphone_chat_message_unref(msg);
345 } else {
346 ms_warning("Received empty response from server, file transfer failed");
347 linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered);
348 _release_http_request(msg);
349 file_upload_end_background_task(msg);
350 linphone_chat_message_unref(msg);
351 }
352 } else {
353 ms_warning("Unhandled HTTP code response %d for file transfer", code);
354 linphone_chat_message_update_state(msg, LinphoneChatMessageStateNotDelivered);
355 _release_http_request(msg);
356 file_upload_end_background_task(msg);
357 linphone_chat_message_unref(msg);
358 }
359 }
360 }
361
linphone_chat_message_get_file_transfer_information(const LinphoneChatMessage * msg)362 const LinphoneContent *linphone_chat_message_get_file_transfer_information(const LinphoneChatMessage *msg) {
363 return msg->file_transfer_information;
364 }
365
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)366 static void 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) {
367 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
368 LinphoneCore *lc = NULL;
369 LinphoneImEncryptionEngine *imee = NULL;
370 int retval = -1;
371 uint8_t *decrypted_buffer = NULL;
372
373 if (!msg->chat_room) {
374 linphone_chat_message_cancel_file_transfer(msg);
375 return;
376 }
377 lc = msg->chat_room->lc;
378
379 if (lc == NULL){
380 return; /*might happen during linphone_core_destroy()*/
381 }
382
383 if (!msg->http_request || belle_http_request_is_cancelled(msg->http_request)) {
384 ms_warning("Cancelled request for msg [%p], ignoring %s", msg, __FUNCTION__);
385 return;
386 }
387
388 /* first call may be with a zero size, ignore it */
389 if (size == 0) {
390 return;
391 }
392
393 decrypted_buffer = (uint8_t *)ms_malloc0(size);
394 imee = linphone_core_get_im_encryption_engine(lc);
395 if (imee) {
396 LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
397 LinphoneImEncryptionEngineCbsDownloadingFileCb cb_process_downloading_file = linphone_im_encryption_engine_cbs_get_process_downloading_file(imee_cbs);
398 if (cb_process_downloading_file) {
399 retval = cb_process_downloading_file(imee, msg, offset, (const uint8_t *)buffer, size, decrypted_buffer);
400 if (retval == 0) {
401 memcpy(buffer, decrypted_buffer, size);
402 }
403 }
404 }
405 ms_free(decrypted_buffer);
406
407 if (retval <= 0) {
408 if (msg->file_transfer_filepath == NULL) {
409 if (linphone_chat_message_cbs_get_file_transfer_recv(msg->callbacks)) {
410 LinphoneBuffer *lb = linphone_buffer_new_from_data(buffer, size);
411 linphone_chat_message_cbs_get_file_transfer_recv(msg->callbacks)(msg, msg->file_transfer_information, lb);
412 linphone_buffer_unref(lb);
413 } else {
414 /* Legacy: call back given by application level */
415 linphone_core_notify_file_transfer_recv(lc, msg, msg->file_transfer_information, (const char *)buffer, size);
416 }
417 }
418 } else {
419 ms_warning("File transfer decrypt failed with code %d", (int)retval);
420 linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferError);
421 }
422
423 return;
424 }
425
on_recv_end(belle_sip_user_body_handler_t * bh,void * data)426 static void on_recv_end(belle_sip_user_body_handler_t *bh, void *data) {
427 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
428 LinphoneCore *lc = msg->chat_room->lc;
429 LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc);
430 int retval = -1;
431
432 if (imee) {
433 LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee);
434 LinphoneImEncryptionEngineCbsDownloadingFileCb cb_process_downloading_file = linphone_im_encryption_engine_cbs_get_process_downloading_file(imee_cbs);
435 if (cb_process_downloading_file) {
436 retval = cb_process_downloading_file(imee, msg, 0, NULL, 0, NULL);
437 }
438 }
439
440 if (retval <= 0) {
441 if (msg->file_transfer_filepath == NULL) {
442 if (linphone_chat_message_cbs_get_file_transfer_recv(msg->callbacks)) {
443 LinphoneBuffer *lb = linphone_buffer_new();
444 linphone_chat_message_cbs_get_file_transfer_recv(msg->callbacks)(msg, msg->file_transfer_information, lb);
445 linphone_buffer_unref(lb);
446 } else {
447 /* Legacy: call back given by application level */
448 linphone_core_notify_file_transfer_recv(lc, msg, msg->file_transfer_information, NULL, 0);
449 }
450 }
451 }
452
453 if (retval <= 0 && linphone_chat_message_get_state(msg) != LinphoneChatMessageStateFileTransferError) {
454 linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferDone);
455 }
456 }
457
linphone_chat_create_file_transfer_information_from_headers(const belle_sip_message_t * m)458 static LinphoneContent *linphone_chat_create_file_transfer_information_from_headers(const belle_sip_message_t *m) {
459 LinphoneContent *content = linphone_content_new();
460
461 belle_sip_header_content_length_t *content_length_hdr =
462 BELLE_SIP_HEADER_CONTENT_LENGTH(belle_sip_message_get_header(m, "Content-Length"));
463 belle_sip_header_content_type_t *content_type_hdr =
464 BELLE_SIP_HEADER_CONTENT_TYPE(belle_sip_message_get_header(m, "Content-Type"));
465 const char *type = NULL, *subtype = NULL;
466
467 linphone_content_set_name(content, "");
468
469 if (content_type_hdr) {
470 type = belle_sip_header_content_type_get_type(content_type_hdr);
471 subtype = belle_sip_header_content_type_get_subtype(content_type_hdr);
472 ms_message("Extracted content type %s / %s from header", type ? type : "", subtype ? subtype : "");
473 if (type)
474 linphone_content_set_type(content, type);
475 if (subtype)
476 linphone_content_set_subtype(content, subtype);
477 }
478
479 if (content_length_hdr) {
480 linphone_content_set_size(content, belle_sip_header_content_length_get_content_length(content_length_hdr));
481 ms_message("Extracted content length %i from header", (int)linphone_content_get_size(content));
482 }
483
484 return content;
485 }
486
linphone_chat_process_response_headers_from_get_file(void * data,const belle_http_response_event_t * event)487 static void linphone_chat_process_response_headers_from_get_file(void *data, const belle_http_response_event_t *event) {
488 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
489 if (event->response) {
490 /*we are receiving a response, set a specific body handler to acquire the response.
491 * if not done, belle-sip will create a memory body handler, the default*/
492 belle_sip_message_t *response = BELLE_SIP_MESSAGE(event->response);
493 belle_sip_body_handler_t *body_handler = NULL;
494 size_t body_size = 0;
495
496 if (msg->file_transfer_information == NULL) {
497 ms_warning("No file transfer information for msg %p: creating...", msg);
498 msg->file_transfer_information = linphone_chat_create_file_transfer_information_from_headers(response);
499 }
500
501 if (msg->file_transfer_information) {
502 body_size = linphone_content_get_size(msg->file_transfer_information);
503 }
504
505
506 body_handler = (belle_sip_body_handler_t *)belle_sip_user_body_handler_new(body_size, linphone_chat_message_file_transfer_on_progress, NULL, on_recv_body, NULL, on_recv_end, msg);
507 if (msg->file_transfer_filepath != NULL) {
508 belle_sip_user_body_handler_t *bh = (belle_sip_user_body_handler_t *)body_handler;
509 body_handler = (belle_sip_body_handler_t *)belle_sip_file_body_handler_new(
510 msg->file_transfer_filepath, linphone_chat_message_file_transfer_on_progress, msg);
511 if (belle_sip_body_handler_get_size((belle_sip_body_handler_t *)body_handler) == 0) {
512 /* If the size of the body has not been initialized from the file stat, use the one from the
513 * file_transfer_information. */
514 belle_sip_body_handler_set_size((belle_sip_body_handler_t *)body_handler, body_size);
515 }
516 belle_sip_file_body_handler_set_user_body_handler((belle_sip_file_body_handler_t *)body_handler, bh);
517 }
518 belle_sip_message_set_body_handler((belle_sip_message_t *)event->response, body_handler);
519 }
520 }
521
linphone_chat_process_response_from_get_file(void * data,const belle_http_response_event_t * event)522 static void linphone_chat_process_response_from_get_file(void *data, const belle_http_response_event_t *event) {
523 LinphoneChatMessage *msg = (LinphoneChatMessage *)data;
524 /* check the answer code */
525 if (event->response) {
526 int code = belle_http_response_get_status_code(event->response);
527 if (code >= 400 && code < 500) {
528 ms_warning("File transfer failed with code %d", code);
529 linphone_chat_message_set_state(msg, LinphoneChatMessageStateFileTransferError);
530 } else if (code != 200) {
531 ms_warning("Unhandled HTTP code response %d for file transfer", code);
532 }
533 _release_http_request(msg);
534 }
535 }
536
_linphone_chat_room_start_http_transfer(LinphoneChatMessage * msg,const char * url,const char * action,const belle_http_request_listener_callbacks_t * cbs)537 int _linphone_chat_room_start_http_transfer(LinphoneChatMessage *msg, const char* url, const char* action, const belle_http_request_listener_callbacks_t *cbs) {
538 belle_generic_uri_t *uri = NULL;
539 const char* ua = linphone_core_get_user_agent(msg->chat_room->lc);
540
541 if (url == NULL) {
542 ms_warning("Cannot process file transfer msg: no file remote URI configured.");
543 goto error;
544 }
545 uri = belle_generic_uri_parse(url);
546 if (uri == NULL || belle_generic_uri_get_host(uri)==NULL) {
547 ms_warning("Cannot process file transfer msg: incorrect file remote URI configured '%s'.", url);
548 goto error;
549 }
550
551 msg->http_request = belle_http_request_create(action, uri, belle_sip_header_create("User-Agent", ua), NULL);
552
553 if (msg->http_request == NULL) {
554 ms_warning("Could not create http request for uri %s", url);
555 goto error;
556 }
557 /* keep a reference to the http request to be able to cancel it during upload */
558 belle_sip_object_ref(msg->http_request);
559
560 /* give msg to listener to be able to start the actual file upload when server answer a 204 No content */
561 msg->http_listener = belle_http_request_listener_create_from_callbacks(cbs, linphone_chat_message_ref(msg));
562 belle_http_provider_send_request(msg->chat_room->lc->http_provider, msg->http_request, msg->http_listener);
563 return 0;
564 error:
565 if (uri) {
566 belle_sip_object_unref(uri);
567 }
568 return -1;
569 }
570
linphone_chat_room_upload_file(LinphoneChatMessage * msg)571 int linphone_chat_room_upload_file(LinphoneChatMessage *msg) {
572 belle_http_request_listener_callbacks_t cbs = {0};
573 int err;
574
575 if (msg->http_request){
576 ms_error("linphone_chat_room_upload_file(): there is already an upload in progress.");
577 return -1;
578 }
579
580 cbs.process_response = linphone_chat_message_process_response_from_post_file;
581 cbs.process_io_error = linphone_chat_message_process_io_error_upload;
582 cbs.process_auth_requested = linphone_chat_message_process_auth_requested_upload;
583 err = _linphone_chat_room_start_http_transfer(msg, linphone_core_get_file_transfer_server(msg->chat_room->lc), "POST", &cbs);
584 if (err == -1){
585 linphone_chat_message_set_state(msg, LinphoneChatMessageStateNotDelivered);
586 }
587 return err;
588 }
589
linphone_chat_message_download_file(LinphoneChatMessage * msg)590 LinphoneStatus linphone_chat_message_download_file(LinphoneChatMessage *msg) {
591 belle_http_request_listener_callbacks_t cbs = {0};
592 int err;
593
594 if (msg->http_request){
595 ms_error("linphone_chat_message_download_file(): there is already a download in progress");
596 return -1;
597 }
598 cbs.process_response_headers = linphone_chat_process_response_headers_from_get_file;
599 cbs.process_response = linphone_chat_process_response_from_get_file;
600 cbs.process_io_error = linphone_chat_message_process_io_error_download;
601 cbs.process_auth_requested = linphone_chat_message_process_auth_requested_download;
602 err = _linphone_chat_room_start_http_transfer(msg, msg->external_body_url, "GET", &cbs);
603 if (err == -1) return -1;
604 /* start the download, status is In Progress */
605 linphone_chat_message_set_state(msg, LinphoneChatMessageStateInProgress);
606 return 0;
607 }
608
linphone_chat_message_start_file_download(LinphoneChatMessage * msg,LinphoneChatMessageStateChangedCb status_cb,void * ud)609 void linphone_chat_message_start_file_download(LinphoneChatMessage *msg,
610 LinphoneChatMessageStateChangedCb status_cb, void *ud) {
611 msg->message_state_changed_cb = status_cb;
612 msg->message_state_changed_user_data = ud;
613 linphone_chat_message_download_file(msg);
614 }
615
linphone_chat_message_cancel_file_transfer(LinphoneChatMessage * msg)616 void linphone_chat_message_cancel_file_transfer(LinphoneChatMessage *msg) {
617 if (msg->http_request) {
618 if (msg->state == LinphoneChatMessageStateInProgress) {
619 linphone_chat_message_set_state(msg, LinphoneChatMessageStateNotDelivered);
620 }
621 if (!belle_http_request_is_cancelled(msg->http_request)) {
622 if (msg->chat_room) {
623 ms_message("Canceling file transfer %s - msg [%p] chat room[%p]"
624 , (msg->external_body_url == NULL) ? linphone_core_get_file_transfer_server(msg->chat_room->lc) : msg->external_body_url
625 , msg
626 , msg->chat_room);
627 belle_http_provider_cancel_request(msg->chat_room->lc->http_provider, msg->http_request);
628 if (msg->dir == LinphoneChatMessageOutgoing) {
629 // must release it
630 linphone_chat_message_unref(msg);
631 }
632 } else {
633 ms_message("Warning: http request still running for ORPHAN msg [%p]: this is a memory leak", msg);
634 }
635 }
636 _release_http_request(msg);
637 } else {
638 ms_message("No existing file transfer - nothing to cancel");
639 }
640 }
641
linphone_chat_message_set_file_transfer_filepath(LinphoneChatMessage * msg,const char * filepath)642 void linphone_chat_message_set_file_transfer_filepath(LinphoneChatMessage *msg, const char *filepath) {
643 if (msg->file_transfer_filepath != NULL) {
644 ms_free(msg->file_transfer_filepath);
645 }
646 msg->file_transfer_filepath = ms_strdup(filepath);
647 }
648
linphone_chat_message_get_file_transfer_filepath(LinphoneChatMessage * msg)649 const char *linphone_chat_message_get_file_transfer_filepath(LinphoneChatMessage *msg) {
650 return msg->file_transfer_filepath;
651 }
652
linphone_chat_room_create_file_transfer_message(LinphoneChatRoom * cr,const LinphoneContent * initial_content)653 LinphoneChatMessage *linphone_chat_room_create_file_transfer_message(LinphoneChatRoom *cr,
654 const LinphoneContent *initial_content) {
655 LinphoneChatMessage *msg = belle_sip_object_new(LinphoneChatMessage);
656 msg->callbacks = linphone_chat_message_cbs_new();
657 msg->chat_room = (LinphoneChatRoom *)cr;
658 msg->message = NULL;
659 msg->file_transfer_information = linphone_content_copy(initial_content);
660 msg->dir = LinphoneChatMessageOutgoing;
661 linphone_chat_message_set_to(msg, linphone_chat_room_get_peer_address(cr));
662 msg->from = linphone_address_new(linphone_core_get_identity(cr->lc)); /*direct assignment*/
663 /* this will be set to application/vnd.gsma.rcs-ft-http+xml when we will transfer the xml reply from server to the peers */
664 msg->content_type = NULL;
665 /* this will store the http request during file upload to the server */
666 msg->http_request = NULL;
667 msg->time = ms_time(0);
668 return msg;
669 }
670