From c10b5f652b4da61957f61729a7eb45bfa6ac459f Mon Sep 17 00:00:00 2001 From: Johan Pascal Date: Thu, 29 May 2014 00:10:49 +0200 Subject: [PATCH] File transfer implemented following RCS5.1 recommendation - memory leaks to be fixed --- coreapi/bellesip_sal/sal_op_message.c | 17 +- coreapi/chat.c | 326 +++++++++++++++++++++++++- coreapi/help/Makefile.am | 2 + coreapi/help/filetransfer.c | 69 ++++-- coreapi/linphonecore.c | 12 + coreapi/linphonecore.h | 3 + coreapi/private.h | 3 + include/sal/sal.h | 1 + mediastreamer2 | 2 +- oRTP | 2 +- 10 files changed, 405 insertions(+), 32 deletions(-) diff --git a/coreapi/bellesip_sal/sal_op_message.c b/coreapi/bellesip_sal/sal_op_message.c index 2f161c2a6..3324cefb7 100644 --- a/coreapi/bellesip_sal/sal_op_message.c +++ b/coreapi/bellesip_sal/sal_op_message.c @@ -55,6 +55,11 @@ static void process_response_event(void *op_base, const belle_sip_response_event op->base.root->callbacks.text_delivery_update(op,status); } +static bool_t is_rcs_filetransfer(belle_sip_header_content_type_t* content_type) { + return strcmp("application",belle_sip_header_content_type_get_type(content_type))==0 + && strcmp("vnd.gsma.rcs-ft-http+xml",belle_sip_header_content_type_get_subtype(content_type))==0; +} + static bool_t is_plain_text(belle_sip_header_content_type_t* content_type) { return strcmp("text",belle_sip_header_content_type_get_type(content_type))==0 && strcmp("plain",belle_sip_header_content_type_get_subtype(content_type))==0; @@ -69,7 +74,7 @@ static bool_t is_im_iscomposing(belle_sip_header_content_type_t* content_type) { } static void add_message_accept(belle_sip_message_t *msg){ - belle_sip_message_add_header(msg,belle_sip_header_create("Accept","text/plain, message/external-body, application/im-iscomposing+xml")); + belle_sip_message_add_header(msg,belle_sip_header_create("Accept","text/plain, message/external-body, application/im-iscomposing+xml, application/vnd.gsma.rcs-ft-http+xml")); } void sal_process_incoming_message(SalOp *op,const belle_sip_request_event_t *event){ @@ -85,11 +90,13 @@ void sal_process_incoming_message(SalOp *op,const belle_sip_request_event_t *eve char* from; bool_t plain_text=FALSE; bool_t external_body=FALSE; + bool_t rcs_filetransfer=FALSE; from_header=belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req),belle_sip_header_from_t); content_type=belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req),belle_sip_header_content_type_t); if (content_type && ((plain_text=is_plain_text(content_type)) - || (external_body=is_external_body(content_type)))) { + || (external_body=is_external_body(content_type)) + || (rcs_filetransfer=is_rcs_filetransfer(content_type)))) { SalMessage salmsg; char message_id[256]={0}; @@ -104,8 +111,12 @@ void sal_process_incoming_message(SalOp *op,const belle_sip_request_event_t *eve ,belle_sip_header_call_id_get_call_id(call_id) ,belle_sip_header_cseq_get_seq_number(cseq)); salmsg.from=from; - salmsg.text=plain_text?belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)):NULL; + salmsg.text=(plain_text||rcs_filetransfer)?belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)):NULL; salmsg.url=NULL; + salmsg.content_type = NULL; + if (rcs_filetransfer) { /* if we have a rcs file transfer, set the type, message body (stored in salmsg.text) contains all needed information to retrieve the file */ + salmsg.content_type = "application/vnd.gsma.rcs-ft-http+xml"; + } if (external_body && belle_sip_parameters_get_parameter(BELLE_SIP_PARAMETERS(content_type),"URL")) { size_t url_length=strlen(belle_sip_parameters_get_parameter(BELLE_SIP_PARAMETERS(content_type),"URL")); salmsg.url = ms_strdup(belle_sip_parameters_get_parameter(BELLE_SIP_PARAMETERS(content_type),"URL")+1); /* skip first "*/ diff --git a/coreapi/chat.c b/coreapi/chat.c index fb2a34159..02638a64c 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -25,6 +25,7 @@ #include "linphonecore.h" #include "private.h" #include "lpconfig.h" +#include "belle-sip/belle-sip.h" #include @@ -32,6 +33,135 @@ #define COMPOSING_DEFAULT_REFRESH_TIMEOUT 60 #define COMPOSING_DEFAULT_REMOTE_REFRESH_TIMEOUT 120 + +static void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatMessage* msg); +#define MULTIPART_BOUNDARY "---------------------------14737809831466499882746641449" +#define MULTIPART_HEADER_1 "--" MULTIPART_BOUNDARY "\r\n" \ + "Content-Disposition: form-data; name=\"File\"; filename=\"" +#define MULTIPART_HEADER_2 "\"\r\n" \ + "Content-Type: " +#define MULTIPART_HEADER_3 "\r\n\r\n" +#define MULTIPART_END "\r\n--" MULTIPART_BOUNDARY "--\r\n" +const char *multipart_boundary=MULTIPART_BOUNDARY; + +static size_t linphone_chat_message_compute_multipart_header_size(const char *filename, const char *content_type) { + return strlen(MULTIPART_HEADER_1)+strlen(filename)+strlen(MULTIPART_HEADER_2)+strlen(content_type)+strlen(MULTIPART_HEADER_3); +} +static void process_io_error(void *data, const belle_sip_io_error_event_t *event){ + printf("We have a response io error!\n"); +} +static void process_auth_requested(void *data, belle_sip_auth_event_t *event){ + printf("We have a auth requested!\n"); +} + +/** + * Callback called during upload or download of a file from server + * It is just forwarding the call and some parameters to the vtable defined callback + */ +static void linphone_chat_message_file_transfer_on_progress(belle_sip_body_handler_t *bh, belle_sip_message_t *msg, void *data, size_t offset, size_t total){ + LinphoneChatMessage* chatMsg=(LinphoneChatMessage *)data; + LinphoneCore *lc = chatMsg->chat_room->lc; + /* call back given by application level */ + if (lc->vtable.file_transfer_progress_indication != NULL) { + lc->vtable.file_transfer_progress_indication(lc, chatMsg, chatMsg->file_transfer_information, (size_t)(((double)offset/(double)total)*100.0)); + } + return; +} + +/** + * Callback called when posting a file to server (following rcs5.1 recommendation) + * + * @param bh the body handler + * @param msg the belle sip message + * @param data the user data associated to the handler, contains the linphoneChatMessage we're working on + * @param offset current position in the input buffer + * @param buffer the ouput buffer we to copy the data to be uploaded + * @param size size in byte of the data requested, as output it will contain the effective copied size + * + */ +static int linphone_chat_message_file_transfer_on_send_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t *msg, void *data, size_t offset, void *buffer, size_t *size){ + LinphoneChatMessage* chatMsg=(LinphoneChatMessage *)data; + LinphoneCore *lc = chatMsg->chat_room->lc; + + char *content_type=belle_sip_strdup_printf("%s/%s", chatMsg->file_transfer_information->type, chatMsg->file_transfer_information->subtype); + size_t end_of_file=linphone_chat_message_compute_multipart_header_size(chatMsg->file_transfer_information->name, content_type)+chatMsg->file_transfer_information->size; + + if (offset==0){ + int partlen=linphone_chat_message_compute_multipart_header_size(chatMsg->file_transfer_information->name, content_type); + memcpy(buffer,MULTIPART_HEADER_1,strlen(MULTIPART_HEADER_1)); + buffer += strlen(MULTIPART_HEADER_1); + memcpy(buffer,chatMsg->file_transfer_information->name,strlen(chatMsg->file_transfer_information->name)); + buffer += strlen(chatMsg->file_transfer_information->name); + memcpy(buffer,MULTIPART_HEADER_2,strlen(MULTIPART_HEADER_2)); + buffer += strlen(MULTIPART_HEADER_2); + memcpy(buffer,content_type,strlen(content_type)); + buffer += strlen(content_type); + memcpy(buffer,MULTIPART_HEADER_3,strlen(MULTIPART_HEADER_3)); + + *size=partlen; + }else if (offsetvtable.file_transfer_send(lc, chatMsg, chatMsg->file_transfer_information, buffer, size); + }else{ + *size=strlen(MULTIPART_END); + strncpy(buffer,MULTIPART_END,*size); + } + belle_sip_free(content_type); + return BELLE_SIP_CONTINUE; +} + +/** + * Callback function called when we have a response from server during a file upload to server (rcs5.1 recommandation) + * Note: The first post is empty and the server shall reply a 204 (No content) message, this will trigger a new post request to the server + * to upoad the file. The server response to this second post is processed by this same function + * + * @param data the user define pointer associated with the request, it contains the linphoneChatMessage we're trying to send + * @param event the response from server + */ +static void linphone_chat_message_process_response_from_post_file(void *data, const belle_http_response_event_t *event){ + LinphoneChatMessage* msg=(LinphoneChatMessage *)data; + + /* 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 message */ + /* start uploading the file */ + belle_http_request_listener_callbacks_t cbs={0}; + belle_http_request_listener_t *l; + belle_generic_uri_t *uri; + belle_http_request_t *req; + char *content_type=belle_sip_strdup_printf("%s/%s", msg->file_transfer_information->type, msg->file_transfer_information->subtype); + belle_sip_user_body_handler_t *bh=belle_sip_user_body_handler_new(msg->file_transfer_information->size+linphone_chat_message_compute_multipart_header_size(msg->file_transfer_information->name, content_type)+strlen(MULTIPART_END), linphone_chat_message_file_transfer_on_progress, NULL, linphone_chat_message_file_transfer_on_send_body, msg); + belle_sip_free(content_type); + content_type=belle_sip_strdup_printf("multipart/form-data; boundary=%s",multipart_boundary); + + uri=belle_generic_uri_parse(msg->chat_room->lc->file_transfer_server); + + req=belle_http_request_create("POST", + uri, + belle_sip_header_create("User-Agent","belle-sip/" PACKAGE_VERSION), + belle_sip_header_create("Content-type",content_type), + NULL); + belle_sip_free(content_type); + belle_sip_message_set_body_handler(BELLE_SIP_MESSAGE(req),BELLE_SIP_BODY_HANDLER(bh)); + cbs.process_response=linphone_chat_message_process_response_from_post_file; + cbs.process_io_error=process_io_error; + cbs.process_auth_requested=process_auth_requested; + l=belle_http_request_listener_create_from_callbacks(&cbs,msg); + belle_http_provider_send_request(msg->chat_room->lc->http_provider,req,l); + } + if (code == 200 ) { /* file has been uplaoded correctly, get server reply and send it */ + const char *body = belle_sip_message_get_body((belle_sip_message_t *)event->response); + msg->message = ms_strdup(body); + msg->file_transfer_information = NULL; + msg->content_type = ms_strdup("application/vnd.gsma.rcs-ft-http+xml"); + _linphone_chat_room_send_message(msg->chat_room, msg); + } + } + +} + + static void _linphone_chat_message_destroy(LinphoneChatMessage* msg); BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessage); @@ -178,6 +308,30 @@ static void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatM const char *identity=NULL; time_t t=time(NULL); + /* Check if we shall upload a file to a server */ + if (msg->file_transfer_information != NULL) { + /* open a transaction with the server and send an empty request(RCS5.1 section 3.5.4.8.3.1) */ + belle_http_request_listener_callbacks_t cbs={0}; + belle_http_request_listener_t *l; + belle_generic_uri_t *uri; + belle_http_request_t *req; + + uri=belle_generic_uri_parse(cr->lc->file_transfer_server); + + req=belle_http_request_create("POST", + uri, + NULL, + NULL, + NULL); + cbs.process_response=linphone_chat_message_process_response_from_post_file; + cbs.process_io_error=process_io_error; + cbs.process_auth_requested=process_auth_requested; + l=belle_http_request_listener_create_from_callbacks(&cbs,msg); /* give msg to listener to be able to start the actual file upload when server answer a 204 No content */ + belle_http_provider_send_request(cr->lc->http_provider,req,l); + + return; + } + if (lp_config_get_int(cr->lc->config,"sip","chat_use_call_dialogs",0)){ if((call = linphone_core_get_call_by_remote_address(cr->lc,cr->peer))!=NULL){ if (call->state==LinphoneCallConnected || @@ -207,7 +361,11 @@ static void _linphone_chat_room_send_message(LinphoneChatRoom *cr, LinphoneChatM sal_message_send(op,identity,cr->peer,content_type, NULL); ms_free(content_type); } else { - sal_text_send(op, identity, cr->peer,msg->message); + if (msg->content_type == NULL) { + sal_text_send(op, identity, cr->peer,msg->message); + } else { + sal_message_send(op, identity, cr->peer, msg->content_type, msg->message); + } } msg->dir=LinphoneChatMessageOutgoing; msg->from=linphone_address_new(identity); @@ -280,7 +438,65 @@ void linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessag /* create a new chat room */ cr=linphone_core_create_chat_room(lc,cleanfrom); } - msg = linphone_chat_room_create_message(cr, sal_msg->text); + if (sal_msg->content_type != NULL) { /* content_type field is, for now, used only for rcs file transfer bu twe shall strcmp it with "application/vnd.gsma.rcs-ft-http+xml" */ + msg = linphone_chat_room_create_message(cr, NULL); /* create a message with empty body */ + msg->content_type = ms_strdup(sal_msg->content_type); /* add the content_type "application/vnd.gsma.rcs-ft-http+xml" */ + msg->file_transfer_information = (LinphoneContent *)malloc(sizeof(LinphoneContent)); + memset(msg->file_transfer_information, 0, sizeof(*(msg->file_transfer_information))); + + xmlChar *file_url = NULL; + /* parse the message body to get all informations from it */ + xmlDocPtr xmlMessageBody = xmlParseDoc((const xmlChar *)sal_msg->text); + + xmlNodePtr cur = xmlDocGetRootElement(xmlMessageBody); + if (cur != NULL) { + cur = cur->xmlChildrenNode; + while (cur!=NULL) { + if (!xmlStrcmp(cur->name, (const xmlChar *)"file-info")) { /* we found a file info node, check 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 */ + while (cur!=NULL) { + if (!xmlStrcmp(cur->name, (const xmlChar *)"file-size")) { + xmlChar *fileSizeString = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1); + msg->file_transfer_information->size = strtol((const char*)fileSizeString, NULL, 10); + xmlFree(fileSizeString); + } + + if (!xmlStrcmp(cur->name, (const xmlChar *)"file-name")) { + msg->file_transfer_information->name = (char *)xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1); + } + if (!xmlStrcmp(cur->name, (const xmlChar *)"content-type")) { + xmlChar *contentType = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1); + int contentTypeIndex = 0; + while (contentType[contentTypeIndex]!='/' && contentType[contentTypeIndex]!='\0') { + contentTypeIndex++; + } + msg->file_transfer_information->type = strndup((char *)contentType, contentTypeIndex); + msg->file_transfer_information->subtype = strdup(((char *)contentType+contentTypeIndex+1)); + xmlFree(contentType); + } + if (!xmlStrcmp(cur->name, (const xmlChar *)"data")) { + file_url = xmlGetProp(cur, (const xmlChar *)"url"); + } + + cur=cur->next; + } + xmlFree(typeAttribute); + break; + } + xmlFree(typeAttribute); + } + cur = cur->next; + } + } + xmlFreeDoc(xmlMessageBody); + + linphone_chat_message_set_external_body_url(msg, (const char *)file_url); + xmlFree(file_url); + } else { /* message is not rcs file transfer, create it with provided sal_msg->text as ->message */ + msg = linphone_chat_room_create_message(cr, sal_msg->text); + } linphone_chat_message_set_from(msg, cr->peer_url); { @@ -299,6 +515,7 @@ void linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessag if (sal_msg->url) { linphone_chat_message_set_external_body_url(msg, sal_msg->url); } + linphone_address_destroy(addr); msg->storage_id=linphone_chat_message_store(msg); linphone_chat_room_message_received(cr,lc,msg); @@ -426,6 +643,7 @@ LinphoneChatMessage* linphone_chat_room_create_message(LinphoneChatRoom *cr, con msg->chat_room=(LinphoneChatRoom*)cr; msg->message=message?ms_strdup(message):NULL; msg->is_read=TRUE; + msg->content_type = NULL; /* this property is used only when transfering file */ return msg; } @@ -452,6 +670,7 @@ LinphoneChatMessage* linphone_chat_room_create_message_2( msg->time=time; msg->state=state; msg->is_read=is_read; + msg->content_type = NULL; /* this property is used only when transfering file */ if (is_incoming) { msg->dir=LinphoneChatMessageIncoming; linphone_chat_message_set_from(msg, linphone_chat_room_get_peer_address(cr)); @@ -668,6 +887,87 @@ void linphone_chat_message_set_external_body_url(LinphoneChatMessage* message,co message->external_body_url=url?ms_strdup(url):NULL; } +/** + * Get the file_transfer_information (used by call backs to recover informations during a rcs file transfer) + * + * @param message #LinphoneChatMessage + * @return a pointer to the LinphoneContent structure or NULL if not present. + */ +const LinphoneContent *linphone_chat_message_get_file_transfer_information(const LinphoneChatMessage*message) { + return message->file_transfer_information; +} + +static void on_recv_body(belle_sip_user_body_handler_t *bh, belle_sip_message_t *msg, void *data, size_t offset, const void *buffer, size_t size){ + //printf("Receive %ld bytes\n\n%s\n\n", size, (char *)buffer); + LinphoneChatMessage* chatMsg=(LinphoneChatMessage *)data; + LinphoneCore *lc = chatMsg->chat_room->lc; + /* call back given by application level */ + if (lc->vtable.file_transfer_received != NULL) { + lc->vtable.file_transfer_received(lc, chatMsg, chatMsg->file_transfer_information, buffer, size); + } + return; + + /* feed the callback with the received data */ + + +} + +static void linphone_chat_process_response_headers_from_get_file(void *data, 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*/ + LinphoneChatMessage *message=belle_sip_object_data_get(BELLE_SIP_OBJECT(event->request),"message"); + belle_sip_message_set_body_handler( + (belle_sip_message_t*)event->response, + (belle_sip_body_handler_t*)belle_sip_user_body_handler_new(message->file_transfer_information->size, linphone_chat_message_file_transfer_on_progress,on_recv_body,NULL,message) + ); + } +} + +static void linphone_chat_process_response_from_get_file(void *data, const belle_http_response_event_t *event){ + //LinphoneChatMessage* msg=(LinphoneChatMessage *)data; + + /* check the answer code */ + if (event->response){ + int code=belle_http_response_get_status_code(event->response); + if (code==200) { + LinphoneChatMessage* chatMsg=(LinphoneChatMessage *)data; + LinphoneCore *lc = chatMsg->chat_room->lc; + /* file downloaded succesfully, call again the callback with size at zero */ + if (lc->vtable.file_transfer_received != NULL) { + lc->vtable.file_transfer_received(lc, chatMsg, chatMsg->file_transfer_information, NULL, 0); + } + } + } +} + +/** + * Start the download of the file from remote server + * + * @param message #LinphoneChatMessage + */ +void linphone_chat_message_start_file_download(const LinphoneChatMessage *message) { + belle_http_request_listener_callbacks_t cbs={0}; + belle_http_request_listener_t *l; + belle_generic_uri_t *uri; + belle_http_request_t *req; + const char *url=message->external_body_url; + + uri=belle_generic_uri_parse(url); + + req=belle_http_request_create("GET", + uri, + belle_sip_header_create("User-Agent","belle-sip/" PACKAGE_VERSION), + NULL); + + cbs.process_response_headers=linphone_chat_process_response_headers_from_get_file; + cbs.process_response=linphone_chat_process_response_from_get_file; + cbs.process_io_error=process_io_error; + cbs.process_auth_requested=process_auth_requested; + l=belle_http_request_listener_create_from_callbacks(&cbs, (void *)message); + belle_sip_object_data_set(BELLE_SIP_OBJECT(req),"message",(void *)message,NULL); + belle_http_provider_send_request(message->chat_room->lc->http_provider,req,l); +} /** * Set origin of the message *@param message #LinphoneChatMessage obj @@ -834,6 +1134,7 @@ static void _linphone_chat_message_destroy(LinphoneChatMessage* msg) { if (msg->from) linphone_address_destroy(msg->from); if (msg->to) linphone_address_destroy(msg->to); if (msg->custom_headers) sal_custom_header_free(msg->custom_headers); + if (msg->content_type) ms_free(msg->content_type); } @@ -868,6 +1169,27 @@ LinphoneReason linphone_chat_message_get_reason(LinphoneChatMessage* msg) { return linphone_error_info_get_reason(linphone_chat_message_get_error_info(msg)); } + + +/** + * Create a message attached to a dedicated chat room with a particular content. Use #linphone_chat_room_send_message2 to initiate the transfer + * @param cr the chat room. + * @param a #LinphoneContent initial content. #LinphoneCoreVTable.file_transfer_send is invoked later to notify file transfer progress and collect next chunk of the message if #LinphoneContent.data is NULL. + * @return a new #LinphoneChatMessage + */ + +LinphoneChatMessage* linphone_chat_room_create_file_transfer_message(LinphoneChatRoom *cr, LinphoneContent* initial_content) { + LinphoneChatMessage* msg = belle_sip_object_new(LinphoneChatMessage); + msg->chat_room=(LinphoneChatRoom*)cr; + msg->message = NULL; + msg->file_transfer_information = initial_content; + msg->dir=LinphoneChatMessageOutgoing; + linphone_chat_message_set_to(msg, linphone_chat_room_get_peer_address(cr)); + linphone_chat_message_set_from(msg, linphone_address_new(linphone_core_get_identity(cr->lc))); + msg->content_type=NULL; /* this will be set to application/vnd.gsma.rcs-ft-http+xml when we will transfer the xml reply from server to the peers */ + + return msg; +} /** * @} */ diff --git a/coreapi/help/Makefile.am b/coreapi/help/Makefile.am index 454757094..84822e51c 100644 --- a/coreapi/help/Makefile.am +++ b/coreapi/help/Makefile.am @@ -79,6 +79,8 @@ notify_LDADD=$(helloworld_LDADD) filetransfer_SOURCES=filetransfer.c LINPHONE_TUTOS+=$(filetransfer_SOURCES) +filetransfer_LDADD=$(helloworld_LDADD) + AM_CFLAGS=\ -I$(top_srcdir)/coreapi \ $(STRICT_OPTIONS) \ diff --git a/coreapi/help/filetransfer.c b/coreapi/help/filetransfer.c index 84e8c1f79..b45019c43 100644 --- a/coreapi/help/filetransfer.c +++ b/coreapi/help/filetransfer.c @@ -52,7 +52,7 @@ static void file_transfer_progress_indication(LinphoneCore *lc, LinphoneChatMess const LinphoneAddress* from_address = linphone_chat_message_get_from(message); const LinphoneAddress* to_address = linphone_chat_message_get_to(message); char *address = linphone_chat_message_is_outgoing(message)?linphone_address_as_string(to_address):linphone_address_as_string(from_address); - printf(" File transfer [%i&] %s of type [%s/%s] %s [%s] \n", (int)(content->size/progress)*100 + printf(" File transfer [%d%%] %s of type [%s/%s] %s [%s] \n", (int)progress ,(linphone_chat_message_is_outgoing(message)?"sent":"received") , content->type , content->subtype @@ -73,31 +73,32 @@ static void file_transfer_received(LinphoneCore *lc, LinphoneChatMessage *messag , from); if (!linphone_chat_message_get_user_data(message)) { /*first chunk, creating file*/ - file = open("receive_file.dump",O_WRONLY); + file = open("receive_file.dump",O_WRONLY|O_CREAT, S_IRUSR|S_IWUSR); linphone_chat_message_set_user_data(message,(void*)(long)(0x00000000FFFFFFFF&file)); /*store fd for next chunks*/ } else { /*next chunk*/ - file = (int)linphone_chat_message_get_user_data(message); + file = (int)((long)(linphone_chat_message_get_user_data(message))&0x00000000FFFFFFFF); } - /*store content on a file*/ - write(file,buff,size); - if (size==0) { - printf("File transfert completed"); + printf("File transfert completed\n"); close(file); - } /*else wait for next chunk*/ + running=FALSE; + } else { /* store content on a file*/ + write(file,buff,size); + } + free(from); } char big_file [128000]; /* - * function call when is file transfer is initiated. file content should be feed into object LinphoneContent + * function called when the file transfer is initiated. file content should be feed into object LinphoneContent * */ static void file_transfer_send(LinphoneCore *lc, LinphoneChatMessage *message, const LinphoneContent* content, char* buff, size_t* size){ - const LinphoneAddress* to_address = linphone_chat_message_get_to(message); - char *to = linphone_address_as_string(to_address); + //const LinphoneAddress* to_address = linphone_chat_message_get_to(message); + //char *to = linphone_address_as_string(to_address); int offset=-1; /*content->size can be feed*/ @@ -106,7 +107,7 @@ static void file_transfer_send(LinphoneCore *lc, LinphoneChatMessage *message, offset=0; } else { /*subsequent chunk*/ - offset = (int)linphone_chat_message_get_user_data(message); + offset = (int)((long)(linphone_chat_message_get_user_data(message))&0x00000000FFFFFFFF); } *size = MIN(*size,sizeof(big_file)-offset); /*updating content->size with minimun between remaining data and requested size*/ @@ -119,11 +120,22 @@ static void file_transfer_send(LinphoneCore *lc, LinphoneChatMessage *message, printf(" File transfer sending [%i] bytes of type [%s/%s] from [%s] \n" , (int)*size , content->type , content->subtype - , to); + , "pipo"); /*store offset for next chunk*/ linphone_chat_message_set_user_data(message,(void*)(offset+*size)); - free(to); + //free(to); + +} + +/* + * Call back called when a message is received + */ +static void message_received(LinphoneCore *lc, LinphoneChatRoom *cr, LinphoneChatMessage *msg) { + const LinphoneContent *file_transfer_info = linphone_chat_message_get_file_transfer_information(msg); + printf ("Do you really want to download %s (size %ld)?[Y/n]\nOk, let's go\n", file_transfer_info->name, file_transfer_info->size); + + linphone_chat_message_start_file_download(msg); } /* @@ -137,21 +149,21 @@ static void linphone_file_transfer_state_changed(LinphoneChatMessage* msg,Linpho free(to); } + LinphoneCore *lc; int main(int argc, char *argv[]){ LinphoneCoreVTable vtable={0}; - char* dest_friend=NULL; + const char* dest_friend=NULL; int i; const char* big_file_content="big file"; /*seting dummy file content to something*/ - for (i=0;i1){ - dest_friend=argv[1]; - } signal(SIGINT,stop); //#define DEBUG #ifdef DEBUG @@ -166,20 +178,24 @@ int main(int argc, char *argv[]){ vtable.file_transfer_received=file_transfer_received; vtable.file_transfer_send=file_transfer_send; vtable.file_transfer_progress_indication=file_transfer_progress_indication; + vtable.message_received=message_received; /* Instantiate a LinphoneCore object given the LinphoneCoreVTable */ lc=linphone_core_new(&vtable,NULL,NULL,NULL); + dest_friend = linphone_core_get_primary_contact(lc); + printf("Send message to me : %s\n", dest_friend); /** * Globally configure an http file transfer server. */ - linphone_core_set_file_transfer_server(lc,"http://sharing.linphone.org/upload.php"); + linphone_core_set_file_transfer_server(lc,"http://npasc.al/lft.php"); + //linphone_core_set_file_transfer_server(lc,"https://www.linphone.org:444/upload.php"); - /*Next step is to create a chat root*/ + /*Next step is to create a chat room*/ LinphoneChatRoom* chat_room = linphone_core_create_chat_room(lc,dest_friend); LinphoneContent content; @@ -187,13 +203,16 @@ int main(int argc, char *argv[]){ content.type="text"; content.subtype="plain"; content.size=sizeof(big_file); /*total size to be transfered*/ + content.name = "bigfile.txt"; /*now create a chat message with custom content*/ LinphoneChatMessage* chat_message = linphone_chat_room_create_file_transfer_message(chat_room,&content); + if (chat_message == NULL) { + printf("returned message is null\n"); + } /*initiating file transfer*/ - /**/ - linphone_chat_room_send_message2(chat_room, chat_message,linphone_file_transfer_state_changed,NULL); + linphone_chat_room_send_message2(chat_room, chat_message, linphone_file_transfer_state_changed, NULL); /* main loop for receiving incoming messages and doing background linphone core work: */ while(running){ diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index a77d550ae..b1b77f0fb 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -1392,6 +1392,8 @@ static void linphone_core_init(LinphoneCore * lc, const LinphoneCoreVTable *vtab lc->http_verify_policy = belle_tls_verify_policy_new(); belle_http_provider_set_tls_verify_policy(lc->http_provider,lc->http_verify_policy); + lc->file_transfer_server = NULL; + certificates_config_read(lc); remote_provisioning_uri = linphone_core_get_provisioning_uri(lc); @@ -5891,6 +5893,8 @@ static void linphone_core_uninit(LinphoneCore *lc) lc->last_recv_msg_ids=ms_list_free(lc->last_recv_msg_ids); // Free struct variable + ms_free(lc->file_transfer_server); + if(lc->zrtp_secrets_cache != NULL) { ms_free(lc->zrtp_secrets_cache); } @@ -6536,3 +6540,11 @@ bool_t linphone_core_sdp_200_ack_enabled(const LinphoneCore *lc) { return lc->sip_conf.sdp_200_ack!=0; } +/** + * Globaly set an http file transfer server to be used for content type application/vnd.gsma.rcs-ft-http+xml. This value can also be set for a dedicated account using #linphone_proxy_config_set_file_transfer_server + * @param #LinphoneCore to be modified + * @param const char* url of the file server like https://file.linphone.org/upload.php + **/ +void linphone_core_set_file_transfer_server(LinphoneCore *core, const char * server_url) { + core->file_transfer_server=ms_strdup(server_url); +} diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h index d57e77278..1c8f83992 100644 --- a/coreapi/linphonecore.h +++ b/coreapi/linphonecore.h @@ -137,6 +137,7 @@ struct _LinphoneContent{ size_t size; /**