diff --git a/coreapi/bellesip_sal/sal_impl.c b/coreapi/bellesip_sal/sal_impl.c index 69e13c9a3..2809d9309 100644 --- a/coreapi/bellesip_sal/sal_impl.c +++ b/coreapi/bellesip_sal/sal_impl.c @@ -591,12 +591,8 @@ void sal_set_callbacks(Sal *ctx, const SalCallbacks *cbs){ ctx->callbacks.notify_presence=(SalOnNotifyPresence)unimplemented_stub; if (ctx->callbacks.subscribe_presence_received==NULL) ctx->callbacks.subscribe_presence_received=(SalOnSubscribePresenceReceived)unimplemented_stub; - if (ctx->callbacks.text_received==NULL) - ctx->callbacks.text_received=(SalOnTextReceived)unimplemented_stub; - if (ctx->callbacks.is_composing_received==NULL) - ctx->callbacks.is_composing_received=(SalOnIsComposingReceived)unimplemented_stub; - if (ctx->callbacks.imdn_received == NULL) - ctx->callbacks.imdn_received = (SalOnImdnReceived)unimplemented_stub; + if (ctx->callbacks.message_received==NULL) + ctx->callbacks.message_received=(SalOnMessageReceived)unimplemented_stub; if (ctx->callbacks.ping_reply==NULL) ctx->callbacks.ping_reply=(SalOnPingReply)unimplemented_stub; if (ctx->callbacks.auth_requested==NULL) @@ -618,6 +614,7 @@ void sal_uninit(Sal* sal){ belle_sip_object_unref(sal->listener); if (sal->supported) belle_sip_object_unref(sal->supported); bctbx_list_free_with_data(sal->supported_tags,ms_free); + bctbx_list_free_with_data(sal->supported_content_types, ms_free); if (sal->uuid) ms_free(sal->uuid); if (sal->root_ca) ms_free(sal->root_ca); if (sal->root_ca_data) ms_free(sal->root_ca_data); @@ -1500,4 +1497,17 @@ void *sal_get_stack_impl(Sal *sal) { return sal->stack; } +bool_t sal_is_content_type_supported(const Sal *sal, const char *content_type) { + bctbx_list_t *item; + for (item = sal->supported_content_types; item != NULL; item = bctbx_list_next(item)) { + const char *item_content_type = (const char *)bctbx_list_get_data(item); + if (strcmp(item_content_type, content_type) == 0) return TRUE; + } + return FALSE; +} +void sal_add_content_type_support(Sal *sal, const char *content_type) { + if ((content_type != NULL) && (sal_is_content_type_supported(sal, content_type) == FALSE)) { + sal->supported_content_types = bctbx_list_append(sal->supported_content_types, ms_strdup(content_type)); + } +} diff --git a/coreapi/bellesip_sal/sal_impl.h b/coreapi/bellesip_sal/sal_impl.h index 174d2b435..48e477fde 100644 --- a/coreapi/bellesip_sal/sal_impl.h +++ b/coreapi/bellesip_sal/sal_impl.h @@ -55,6 +55,7 @@ struct Sal{ SalOpSDPHandling default_sdp_handling; bool_t pending_trans_checking; /*testing purpose*/ void *ssl_config; + bctbx_list_t *supported_content_types; /* list of char* */ }; typedef enum SalOpState { @@ -178,4 +179,5 @@ void _sal_op_add_custom_headers(SalOp *op, belle_sip_message_t *msg); SalSubscribeStatus belle_sip_message_get_subscription_state(const belle_sip_message_t *msg); + #endif /* SAL_IMPL_H_ */ diff --git a/coreapi/bellesip_sal/sal_op_message.c b/coreapi/bellesip_sal/sal_op_message.c index 445afd1e2..348e271ec 100644 --- a/coreapi/bellesip_sal/sal_op_message.c +++ b/coreapi/bellesip_sal/sal_op_message.c @@ -24,7 +24,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. static void process_error( SalOp* op) { if (op->dir == SalOpDirOutgoing) { - op->base.root->callbacks.text_delivery_update(op, SalTextDeliveryFailed); + op->base.root->callbacks.message_delivery_update(op, SalMessageDeliveryFailed); } else { ms_warning("unexpected io error for incoming message on op [%p]",op); } @@ -46,35 +46,39 @@ static void process_timeout(void *user_ctx, const belle_sip_timeout_event_t *eve static void process_response_event(void *op_base, const belle_sip_response_event_t *event){ SalOp* op = (SalOp*)op_base; int code = belle_sip_response_get_status_code(belle_sip_response_event_get_response(event)); - SalTextDeliveryStatus status; + SalMessageDeliveryStatus status; sal_op_set_error_info_from_response(op,belle_sip_response_event_get_response(event)); if (code>=100 && code <200) - status=SalTextDeliveryInProgress; + status=SalMessageDeliveryInProgress; else if (code>=200 && code <300) - status=SalTextDeliveryDone; + status=SalMessageDeliveryDone; else - status=SalTextDeliveryFailed; + status=SalMessageDeliveryFailed; - op->base.root->callbacks.text_delivery_update(op,status); + op->base.root->callbacks.message_delivery_update(op,status); } static bool_t is_external_body(belle_sip_header_content_type_t* content_type) { return strcmp("message",belle_sip_header_content_type_get_type(content_type))==0 && strcmp("external-body",belle_sip_header_content_type_get_subtype(content_type))==0; } -static bool_t is_im_iscomposing(belle_sip_header_content_type_t* content_type) { - return strcmp("application",belle_sip_header_content_type_get_type(content_type))==0 - && strcmp("im-iscomposing+xml",belle_sip_header_content_type_get_subtype(content_type))==0; -} -static bool_t is_imdn_xml(belle_sip_header_content_type_t *content_type) { - return (strcmp("message", belle_sip_header_content_type_get_type(content_type)) == 0) - && (strcmp("imdn+xml", belle_sip_header_content_type_get_subtype(content_type)) == 0); -} +static void add_message_accept(SalOp *op, belle_sip_message_t *msg) { + bctbx_list_t *item; + const char *str; + char *old; + char *header = ms_strdup("xml/cipher, application/cipher.vnd.gsma.rcs-ft-http+xml"); -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, xml/cipher, application/vnd.gsma.rcs-ft-http+xml, application/cipher.vnd.gsma.rcs-ft-http+xml, message/imdn+xml")); + for (item = op->base.root->supported_content_types; item != NULL; item = bctbx_list_next(item)) { + str = (const char *)bctbx_list_get_data(item); + old = header; + header = ms_strdup_printf("%s, %s", old, str); + ms_free(old); + } + + belle_sip_message_add_header(msg, belle_sip_header_create("Accept", header)); + ms_free(header); } void sal_process_incoming_message(SalOp *op,const belle_sip_request_event_t *event){ @@ -95,72 +99,45 @@ void sal_process_incoming_message(SalOp *op,const belle_sip_request_event_t *eve content_type=belle_sip_message_get_header_by_type(BELLE_SIP_MESSAGE(req),belle_sip_header_content_type_t); if (content_type) { - + SalMessage salmsg; + char message_id[256]={0}; + if (op->pending_server_trans) belle_sip_object_unref(op->pending_server_trans); op->pending_server_trans=server_transaction; belle_sip_object_ref(op->pending_server_trans); - - if (is_im_iscomposing(content_type)) { - SalIsComposing saliscomposing; - address=belle_sip_header_address_create(belle_sip_header_address_get_displayname(BELLE_SIP_HEADER_ADDRESS(from_header)) - ,belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(from_header))); - from=belle_sip_object_to_string(BELLE_SIP_OBJECT(address)); - saliscomposing.from=from; - saliscomposing.text=belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)); - op->base.root->callbacks.is_composing_received(op,&saliscomposing); - belle_sip_object_unref(address); - belle_sip_free(from); - } else if (is_imdn_xml(content_type)) { - SalImdn salimdn; - address = belle_sip_header_address_create(belle_sip_header_address_get_displayname(BELLE_SIP_HEADER_ADDRESS(from_header)), - belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(from_header))); - from = belle_sip_object_to_string(BELLE_SIP_OBJECT(address)); - salimdn.from = from; - salimdn.content = belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)); - op->base.root->callbacks.imdn_received(op, &salimdn); - belle_sip_object_unref(address); - belle_sip_free(from); - } else { - SalMessage salmsg; - char message_id[256]={0}; - - external_body=is_external_body(content_type); - - address=belle_sip_header_address_create(belle_sip_header_address_get_displayname(BELLE_SIP_HEADER_ADDRESS(from_header)) - ,belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(from_header))); - from=belle_sip_object_to_string(BELLE_SIP_OBJECT(address)); - snprintf(message_id,sizeof(message_id)-1,"%s%i" - ,belle_sip_header_call_id_get_call_id(call_id) - ,belle_sip_header_cseq_get_seq_number(cseq)); - salmsg.from=from; - /* if we just deciphered a message, use the deciphered part(which can be a rcs xml body pointing to the file to retreive from server)*/ - salmsg.text=(!external_body)?belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)):NULL; - salmsg.url=NULL; - salmsg.content_type = ms_strdup_printf("%s/%s", belle_sip_header_content_type_get_type(content_type), belle_sip_header_content_type_get_subtype(content_type)); - 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 "*/ - ((char*)salmsg.url)[url_length-2]='\0'; /*remove trailing "*/ - } - salmsg.message_id=message_id; - salmsg.time=date ? belle_sip_header_date_get_time(date) : time(NULL); - op->base.root->callbacks.text_received(op,&salmsg); - belle_sip_object_unref(address); - belle_sip_free(from); - if (salmsg.url) ms_free((char*)salmsg.url); - ms_free((char *)salmsg.content_type); + external_body=is_external_body(content_type); + address=belle_sip_header_address_create(belle_sip_header_address_get_displayname(BELLE_SIP_HEADER_ADDRESS(from_header)) + ,belle_sip_header_address_get_uri(BELLE_SIP_HEADER_ADDRESS(from_header))); + from=belle_sip_object_to_string(BELLE_SIP_OBJECT(address)); + snprintf(message_id,sizeof(message_id)-1,"%s%i" + ,belle_sip_header_call_id_get_call_id(call_id) + ,belle_sip_header_cseq_get_seq_number(cseq)); + salmsg.from=from; + /* if we just deciphered a message, use the deciphered part(which can be a rcs xml body pointing to the file to retreive from server)*/ + salmsg.text=(!external_body)?belle_sip_message_get_body(BELLE_SIP_MESSAGE(req)):NULL; + salmsg.url=NULL; + salmsg.content_type = ms_strdup_printf("%s/%s", belle_sip_header_content_type_get_type(content_type), belle_sip_header_content_type_get_subtype(content_type)); + 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 "*/ + ((char*)salmsg.url)[url_length-2]='\0'; /*remove trailing "*/ } + salmsg.message_id=message_id; + salmsg.time=date ? belle_sip_header_date_get_time(date) : time(NULL); + op->base.root->callbacks.message_received(op,&salmsg); + + belle_sip_object_unref(address); + belle_sip_free(from); + if (salmsg.url) ms_free((char*)salmsg.url); + ms_free((char *)salmsg.content_type); } else { ms_error("Unsupported MESSAGE (no Content-Type)"); - goto error; + resp = belle_sip_response_create_from_request(req, errcode); + add_message_accept(op, (belle_sip_message_t*)resp); + belle_sip_server_transaction_send_response(server_transaction,resp); + sal_op_release(op); } - return; -error: - resp = belle_sip_response_create_from_request(req, errcode); - add_message_accept((belle_sip_message_t*)resp); - belle_sip_server_transaction_send_response(server_transaction,resp); - sal_op_release(op); } static void process_request_event(void *op_base, const belle_sip_request_event_t *event) { diff --git a/coreapi/callbacks.c b/coreapi/callbacks.c index 5efede442..5f5a67ab9 100644 --- a/coreapi/callbacks.c +++ b/coreapi/callbacks.c @@ -1169,7 +1169,7 @@ static bool_t is_duplicate_msg(LinphoneCore *lc, const char *msg_id){ } -static void text_received(SalOp *op, const SalMessage *msg){ +static void message_received(SalOp *op, const SalMessage *msg){ LinphoneCore *lc=(LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); LinphoneCall *call=(LinphoneCall*)sal_op_get_user_pointer(op); LinphoneReason reason = lc->chat_deny_code; @@ -1180,20 +1180,6 @@ static void text_received(SalOp *op, const SalMessage *msg){ if (!call) sal_op_release(op); } -static void is_composing_received(SalOp *op, const SalIsComposing *is_composing) { - LinphoneCore *lc = (LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); - LinphoneReason reason = linphone_core_is_composing_received(lc, op, is_composing); - sal_message_reply(op, linphone_reason_to_sal(reason)); - sal_op_release(op); -} - -static void imdn_received(SalOp *op, const SalImdn *imdn) { - LinphoneCore *lc = (LinphoneCore *)sal_get_user_pointer(sal_op_get_sal(op)); - LinphoneReason reason = linphone_core_imdn_received(lc, op, imdn); - sal_message_reply(op, linphone_reason_to_sal(reason)); - sal_op_release(op); -} - static void parse_presence_requested(SalOp *op, const char *content_type, const char *content_subtype, const char *body, SalPresenceModel **result) { linphone_notify_parse_presence(content_type, content_subtype, body, result); } @@ -1348,19 +1334,19 @@ static void notify_refer(SalOp *op, SalReferStatus status){ } } -static LinphoneChatMessageState chatStatusSal2Linphone(SalTextDeliveryStatus status){ +static LinphoneChatMessageState chatStatusSal2Linphone(SalMessageDeliveryStatus status){ switch(status){ - case SalTextDeliveryInProgress: + case SalMessageDeliveryInProgress: return LinphoneChatMessageStateInProgress; - case SalTextDeliveryDone: + case SalMessageDeliveryDone: return LinphoneChatMessageStateDelivered; - case SalTextDeliveryFailed: + case SalMessageDeliveryFailed: return LinphoneChatMessageStateNotDelivered; } return LinphoneChatMessageStateIdle; } -static void text_delivery_update(SalOp *op, SalTextDeliveryStatus status){ +static void message_delivery_update(SalOp *op, SalMessageDeliveryStatus status){ LinphoneChatMessage *chat_msg=(LinphoneChatMessage* )sal_op_get_user_pointer(op); if (chat_msg == NULL) { @@ -1371,7 +1357,7 @@ static void text_delivery_update(SalOp *op, SalTextDeliveryStatus status){ if (chat_msg->chat_room != NULL) { linphone_chat_message_update_state(chat_msg, chatStatusSal2Linphone(status)); } - if (status != SalTextDeliveryInProgress) { /*only release op if not in progress*/ + if (status != SalMessageDeliveryInProgress) { /*only release op if not in progress*/ linphone_chat_message_destroy(chat_msg); } } @@ -1507,10 +1493,8 @@ SalCallbacks linphone_sal_callbacks={ vfu_request, dtmf_received, refer_received, - text_received, - text_delivery_update, - is_composing_received, - imdn_received, + message_received, + message_delivery_update, notify_refer, subscribe_received, incoming_subscribe_closed, diff --git a/coreapi/chat.c b/coreapi/chat.c index d46f9751e..d572152bd 100644 --- a/coreapi/chat.c +++ b/coreapi/chat.c @@ -41,6 +41,8 @@ static void linphone_chat_room_delete_composing_idle_timer(LinphoneChatRoom *cr) static void linphone_chat_room_delete_composing_refresh_timer(LinphoneChatRoom *cr); static void linphone_chat_room_delete_remote_composing_refresh_timer(LinphoneChatRoom *cr); static void _linphone_chat_message_destroy(LinphoneChatMessage *msg); +static void linphone_chat_room_notify_is_composing(LinphoneChatRoom *cr, const char *text); +static void linphone_chat_room_notify_imdn(LinphoneChatRoom *cr, const char *text); BELLE_SIP_DECLARE_NO_IMPLEMENTED_INTERFACES(LinphoneChatMessageCbs); @@ -511,6 +513,97 @@ void linphone_chat_room_message_received(LinphoneChatRoom *cr, LinphoneCore *lc, linphone_chat_message_send_delivery_notification(msg, LinphoneReasonNone); } +static bool_t is_file_transfer(const char *content_type) { + return (strcmp("application/vnd.gsma.rcs-ft-http+xml", content_type) == 0); +} + +static bool_t is_im_iscomposing(const char* content_type) { + return (strcmp("application/im-iscomposing+xml", content_type) == 0); +} + +static bool_t is_imdn(const char *content_type) { + return (strcmp("message/imdn+xml", content_type) == 0); +} + +static void create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(LinphoneChatMessage *msg) { + xmlChar *file_url = NULL; + xmlDocPtr xmlMessageBody; + xmlNodePtr cur; + /* parse the msg body to get all informations from it */ + xmlMessageBody = xmlParseDoc((const xmlChar *)msg->message); + msg->file_transfer_information = linphone_content_new(); + + 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 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 */ + while (cur != NULL) { + if (!xmlStrcmp(cur->name, (const xmlChar *)"file-size")) { + xmlChar *fileSizeString = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1); + linphone_content_set_size(msg->file_transfer_information, strtol((const char *)fileSizeString, NULL, 10)); + xmlFree(fileSizeString); + } + + if (!xmlStrcmp(cur->name, (const xmlChar *)"file-name")) { + xmlChar *filename = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1); + linphone_content_set_name(msg->file_transfer_information, (char *)filename); + xmlFree(filename); + } + if (!xmlStrcmp(cur->name, (const xmlChar *)"content-type")) { + xmlChar *contentType = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1); + int contentTypeIndex = 0; + char *type; + char *subtype; + while (contentType[contentTypeIndex] != '/' && contentType[contentTypeIndex] != '\0') { + contentTypeIndex++; + } + type = ms_strndup((char *)contentType, contentTypeIndex); + subtype = ms_strdup(((char *)contentType + contentTypeIndex + 1)); + linphone_content_set_type(msg->file_transfer_information, type); + linphone_content_set_subtype(msg->file_transfer_information, subtype); + ms_free(subtype); + ms_free(type); + xmlFree(contentType); + } + if (!xmlStrcmp(cur->name, (const xmlChar *)"data")) { + file_url = xmlGetProp(cur, (const xmlChar *)"url"); + } + + 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 = b64_decode((char *)keyb64, strlen((char *)keyb64), NULL, 0); + uint8_t *keyBuffer = (uint8_t *)malloc(keyLength); + /* decode the key into local key buffer */ + b64_decode((char *)keyb64, strlen((char *)keyb64), keyBuffer, keyLength); + linphone_content_set_key(msg->file_transfer_information, (char *)keyBuffer, keyLength); + /* duplicate key value into the linphone content private structure */ + xmlFree(keyb64); + free(keyBuffer); + } + + 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); +} + LinphoneReason linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *sal_msg) { LinphoneChatRoom *cr = NULL; LinphoneAddress *addr; @@ -525,18 +618,18 @@ LinphoneReason linphone_core_message_received(LinphoneCore *lc, SalOp *op, const linphone_address_clean(addr); cr = linphone_core_get_chat_room(lc, addr); - msg = linphone_chat_room_create_message(cr, sal_msg->text); /* create a msg with empty body */ - msg->content_type = ms_strdup(sal_msg->content_type); /* add the content_type "application/vnd.gsma.rcs-ft-http+xml" */ + msg = linphone_chat_room_create_message(cr, sal_msg->text); + msg->content_type = ms_strdup(sal_msg->content_type); linphone_chat_message_set_from(msg, cr->peer_url); to = sal_op_get_to(op) ? linphone_address_new(sal_op_get_to(op)) : linphone_address_new(linphone_core_get_identity(lc)); msg->to = to; - + msg->time = sal_msg->time; msg->state = LinphoneChatMessageStateDelivered; msg->dir = LinphoneChatMessageIncoming; msg->message_id = ms_strdup(sal_op_get_call_id(op)); - + ch = sal_op_get_recv_custom_header(op); if (ch) { msg->custom_headers = sal_custom_header_clone(ch); @@ -545,7 +638,7 @@ LinphoneReason linphone_core_message_received(LinphoneCore *lc, SalOp *op, const if (sal_msg->url) { linphone_chat_message_set_external_body_url(msg, sal_msg->url); } - + if (imee) { LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); LinphoneImEncryptionEngineCbsIncomingMessageCb cb_process_incoming_message = linphone_im_encryption_engine_cbs_get_process_incoming_message(imee_cbs); @@ -553,101 +646,26 @@ LinphoneReason linphone_core_message_received(LinphoneCore *lc, SalOp *op, const retval = cb_process_incoming_message(imee, cr, msg); } } - - if (retval <= 0 && strcmp("text/plain", msg->content_type) != 0 && strcmp("message/external-body", msg->content_type) != 0 - && strcmp("application/vnd.gsma.rcs-ft-http+xml", msg->content_type) != 0) { + + if ((retval <= 0) && (linphone_core_is_content_type_supported(lc, msg->content_type) == FALSE)) { retval = 415; ms_error("Unsupported MESSAGE (content-type %s not recognized)", msg->content_type); } - + if (retval > 0) { reason = linphone_error_code_to_reason(retval); linphone_chat_message_send_delivery_notification(msg, reason); goto end; } - if (strcmp("application/vnd.gsma.rcs-ft-http+xml", msg->content_type) == 0) { - xmlChar *file_url = NULL; - xmlDocPtr xmlMessageBody; - xmlNodePtr cur; - /* content_type field is, for now, used only for rcs file transfer but we shall strcmp it with "application/vnd.gsma.rcs-ft-http+xml" */ - /* parse the msg body to get all informations from it */ - xmlMessageBody = xmlParseDoc((const xmlChar *)msg->message); - msg->file_transfer_information = linphone_content_new(); - - 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); - linphone_content_set_size(msg->file_transfer_information, - strtol((const char *)fileSizeString, NULL, 10)); - xmlFree(fileSizeString); - } - - if (!xmlStrcmp(cur->name, (const xmlChar *)"file-name")) { - xmlChar *filename = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1); - linphone_content_set_name( - msg->file_transfer_information, - (char *)filename); - xmlFree(filename); - } - if (!xmlStrcmp(cur->name, (const xmlChar *)"content-type")) { - xmlChar *contentType = xmlNodeListGetString(xmlMessageBody, cur->xmlChildrenNode, 1); - int contentTypeIndex = 0; - char *type; - char *subtype; - while (contentType[contentTypeIndex] != '/' && contentType[contentTypeIndex] != '\0') { - contentTypeIndex++; - } - type = ms_strndup((char *)contentType, contentTypeIndex); - subtype = ms_strdup(((char *)contentType + contentTypeIndex + 1)); - linphone_content_set_type(msg->file_transfer_information, type); - linphone_content_set_subtype(msg->file_transfer_information, subtype); - ms_free(subtype); - ms_free(type); - xmlFree(contentType); - } - if (!xmlStrcmp(cur->name, (const xmlChar *)"data")) { - file_url = xmlGetProp(cur, (const xmlChar *)"url"); - } - - 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 = b64_decode((char *)keyb64, strlen((char *)keyb64), NULL, 0); - uint8_t *keyBuffer = (uint8_t *)malloc(keyLength); - /* decode the key into local key buffer */ - b64_decode((char *)keyb64, strlen((char *)keyb64), keyBuffer, keyLength); - linphone_content_set_key(msg->file_transfer_information, (char *)keyBuffer, keyLength); - /* duplicate key value into the linphone content private structure */ - xmlFree(keyb64); - free(keyBuffer); - } - - 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); + if (is_file_transfer(msg->content_type)) { + create_file_transfer_information_from_vnd_gsma_rcs_ft_http_xml(msg); + } else if (is_im_iscomposing(msg->content_type)) { + linphone_chat_room_notify_is_composing(cr, msg->message); + goto end; + } else if (is_imdn(msg->content_type)) { + linphone_chat_room_notify_imdn(cr, msg->message); + goto end; } msg->storage_id = linphone_chat_message_store(msg); @@ -744,36 +762,6 @@ static void linphone_chat_room_notify_is_composing(LinphoneChatRoom *cr, const c linphone_xmlparsing_context_destroy(xml_ctx); } -LinphoneReason linphone_core_is_composing_received(LinphoneCore *lc, SalOp *op, const SalIsComposing *is_composing) { - LinphoneAddress *addr = linphone_address_new(is_composing->from); - LinphoneChatRoom *cr = _linphone_core_get_chat_room(lc, addr); - LinphoneImEncryptionEngine *imee = linphone_core_get_im_encryption_engine(lc); - LinphoneReason reason = LinphoneReasonNone; - - if (cr != NULL) { - int retval = -1; - LinphoneChatMessage *msg = linphone_chat_room_create_message(cr, is_composing->text); - linphone_chat_message_set_from_address(msg, addr); - msg->content_type = ms_strdup("application/im-iscomposing+xml"); - if (imee) { - LinphoneImEncryptionEngineCbs *imee_cbs = linphone_im_encryption_engine_get_callbacks(imee); - LinphoneImEncryptionEngineCbsIncomingMessageCb cb_process_incoming_message = linphone_im_encryption_engine_cbs_get_process_incoming_message(imee_cbs); - if (cb_process_incoming_message) { - retval = cb_process_incoming_message(imee, cr, msg); - } - } - if (retval <= 0) { - linphone_chat_room_notify_is_composing(cr, msg->message); - } else { - reason = linphone_error_code_to_reason(retval); - } - linphone_chat_message_unref(msg); - } - linphone_address_unref(addr); - - return reason; -} - bool_t linphone_chat_room_is_remote_composing(const LinphoneChatRoom *cr) { return (cr->remote_is_composing == LinphoneIsComposingActive) ? TRUE : FALSE; } @@ -858,16 +846,6 @@ static void linphone_chat_room_notify_imdn(LinphoneChatRoom *cr, const char *tex linphone_xmlparsing_context_destroy(xml_ctx); } -LinphoneReason linphone_core_imdn_received(LinphoneCore *lc, SalOp *op, const SalImdn *imdn) { - LinphoneAddress *addr = linphone_address_new(imdn->from); - LinphoneChatRoom *cr = _linphone_core_get_chat_room(lc, addr); - if (cr != NULL) { - linphone_chat_room_notify_imdn(cr, imdn->content); - } - linphone_address_unref(addr); - return LinphoneReasonNone; -} - LinphoneCore *linphone_chat_room_get_lc(LinphoneChatRoom *cr) { return linphone_chat_room_get_core(cr); } @@ -886,7 +864,7 @@ LinphoneChatMessage *linphone_chat_room_create_message(LinphoneChatRoom *cr, con msg->callbacks = linphone_chat_message_cbs_new(); msg->chat_room = (LinphoneChatRoom *)cr; msg->message = message ? ms_strdup(message) : NULL; - msg->content_type = NULL; /* this property is used only when transfering file */ + msg->content_type = ms_strdup("text/plain"); msg->file_transfer_information = NULL; /* this property is used only when transfering file */ msg->http_request = NULL; msg->time = ms_time(0); @@ -1432,6 +1410,17 @@ void linphone_chat_message_set_external_body_url(LinphoneChatMessage *msg, const msg->external_body_url = url ? ms_strdup(url) : NULL; } +const char * linphone_chat_message_get_content_type(const LinphoneChatMessage *msg) { + return msg->content_type; +} + +void linphone_chat_message_set_content_type(LinphoneChatMessage *msg, const char *content_type) { + if (msg->content_type) { + ms_free(msg->content_type); + } + msg->content_type = content_type ? ms_strdup(content_type) : NULL; +} + const char *linphone_chat_message_get_appdata(const LinphoneChatMessage *msg) { return msg->appdata; } diff --git a/coreapi/lime.c b/coreapi/lime.c index 7315323c7..ac46c86a6 100644 --- a/coreapi/lime.c +++ b/coreapi/lime.c @@ -89,7 +89,7 @@ uint8_t lime_byteToChar(uint8_t inputByte) { * @param[in] inputString The input string buffer, must be hexadecimal(it is not checked by function, any non hexa char is converted to 0) * @param[in] inputStringLength The lenght in chars of the string buffer, output is half this length */ -void lime_strToUint8(uint8_t *outputBytes, uint8_t *inputString, uint16_t inputStringLength) { +void lime_strToUint8(uint8_t *outputBytes, const uint8_t *inputString, uint16_t inputStringLength) { int i; for (i=0; ipeerURI = NULL; } -int lime_encryptMessage(limeKey_t *key, uint8_t *plainMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *encryptedMessage) { +int lime_encryptMessage(limeKey_t *key, const uint8_t *plainMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *encryptedMessage) { uint8_t authenticatedData[28]; /* Authenticated data is senderZID(12 bytes)||receiverZID(12 bytes)||sessionIndex(4 bytes) */ memcpy(authenticatedData, selfZID, 12); @@ -573,10 +573,11 @@ int lime_decryptMessage(limeKey_t *key, uint8_t *encryptedMessage, uint32_t mess return retval; } -int lime_createMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t *peerURI, uint8_t **output) { +int lime_createMultipartMessage(xmlDocPtr cacheBuffer, const char *contentType, uint8_t *message, uint8_t *peerURI, uint8_t **output) { uint8_t selfZidHex[25]; uint8_t selfZid[12]; /* same data but in byte buffer */ uint32_t encryptedMessageLength; + uint32_t encryptedContentTypeLength; limeURIKeys_t associatedKeys; xmlDocPtr xmlOutputMessage; xmlNodePtr rootNode; @@ -592,6 +593,7 @@ int lime_createMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t /* encrypted message length is plaintext + 16 for tag */ encryptedMessageLength = (uint32_t)strlen((char *)message) + 16; + encryptedContentTypeLength = (uint32_t)strlen((char *)contentType) + 16; /* retrieve keys associated to the peer URI */ associatedKeys.peerURI = (uint8_t *)malloc(strlen((char *)peerURI)+1); @@ -624,17 +626,21 @@ int lime_createMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t xmlNodePtr msgNode; size_t b64Size = 0; unsigned char *encryptedMessageb64; + unsigned char *encryptedContentTypeb64; /* encrypt message with current key */ limeKey_t *currentKey = associatedKeys.peerKeys[i]; /* encrypted message include a 16 bytes tag */ uint8_t *encryptedMessage = (uint8_t *)ms_malloc(encryptedMessageLength); + uint8_t *encryptedContentType = (uint8_t *)ms_malloc(encryptedContentTypeLength); lime_encryptMessage(currentKey, message, (uint32_t)strlen((char *)message), selfZid, encryptedMessage); + lime_encryptMessage(currentKey, (const uint8_t *)contentType, (uint32_t)strlen((char *)contentType), selfZid, encryptedContentType); /* add a "msg" node the the output message, doc node is : * * peerZID * session index * ciphertext + * ciphertext * */ msgNode = xmlNewDocNode(xmlOutputMessage, NULL, (const xmlChar *)"msg", NULL); lime_int8ToStr(peerZidHex, currentKey->peerZID, 12); @@ -661,6 +667,16 @@ int lime_createMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t ms_free(encryptedMessage); ms_free(encryptedMessageb64); + /* convert the encrypted content-type to base 64 */ + b64Size = 0; + bctbx_base64_encode(NULL, &b64Size, encryptedContentType, encryptedContentTypeLength); /* b64Size is 0, so it is set to the requested output buffer size */ + encryptedContentTypeb64 = ms_malloc(b64Size+1); /* allocate a buffer of requested size +1 for NULL termination */ + bctbx_base64_encode(encryptedContentTypeb64, &b64Size, encryptedContentType, encryptedContentTypeLength); /* b64Size is 0, so it is set to the requested output buffer size */ + encryptedContentTypeb64[b64Size] = '\0'; /* libxml need a null terminated string */ + xmlNewTextChild(msgNode, NULL, (const xmlChar *)"content-type", (const xmlChar *)encryptedContentTypeb64); + ms_free(encryptedContentType); + ms_free(encryptedContentTypeb64); + /* add the message Node into the doc */ xmlAddChild(rootNode, msgNode); @@ -683,17 +699,21 @@ int lime_createMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t return 0; } -int lime_decryptMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t **output) { - int retval; +int lime_decryptMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t **output, char **content_type) { + int retval = 0; uint8_t selfZidHex[25]; uint8_t selfZid[12]; /* same data but in byte buffer */ + char xpath_str[MAX_XPATH_LENGTH]; limeKey_t associatedKey; - xmlChar *peerZidHex = NULL; - xmlNodePtr cur; + const char *peerZidHex = NULL; + xmlparsing_context_t *xml_ctx; + xmlXPathObjectPtr msg_object; uint8_t *encryptedMessage = NULL; size_t encryptedMessageLength = 0; + uint8_t *encryptedContentType = NULL; + size_t encryptedContentTypeLength = 0; uint32_t usedSessionIndex = 0; - xmlDocPtr xmlEncryptedMessage; + int i; if (cacheBuffer == NULL) { return LIME_INVALID_CACHE; @@ -704,81 +724,80 @@ int lime_decryptMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_ } lime_strToUint8(selfZid, selfZidHex, 24); - /* parse the message into an xml doc */ - /* make sure we have a valid xml message before trying to parse it */ - if (memcmp(message, "", 38) != 0) { - return LIME_INVALID_ENCRYPTED_MESSAGE; - } - xmlEncryptedMessage = xmlParseDoc((const xmlChar *)message); - if (xmlEncryptedMessage == NULL) { - return LIME_INVALID_ENCRYPTED_MESSAGE; + xml_ctx = linphone_xmlparsing_context_new(); + xmlSetGenericErrorFunc(xml_ctx, linphone_xmlparsing_genericxml_error); + xml_ctx->doc = xmlReadDoc((const unsigned char*)message, 0, NULL, 0); + if (xml_ctx->doc == NULL) { + retval = LIME_INVALID_ENCRYPTED_MESSAGE; + goto error; } - /* retrieve the sender ZID which is the first child of root */ - cur = xmlDocGetRootElement(xmlEncryptedMessage); - if (cur != NULL) { - cur = cur->xmlChildrenNode; - if ((!xmlStrcmp(cur->name, (const xmlChar *)"ZID"))){ /* sender ZID found, extract it */ - peerZidHex = xmlNodeListGetString(xmlEncryptedMessage, cur->xmlChildrenNode, 1); - /* convert it from hexa string to bytes string and set the result in the associatedKey structure */ - lime_strToUint8(associatedKey.peerZID, peerZidHex, (uint16_t)strlen((char *)peerZidHex)); - cur = cur->next; - } + if (linphone_create_xml_xpath_context(xml_ctx) < 0) { + retval = LIME_INVALID_ENCRYPTED_MESSAGE; + goto error; } + /* Retrieve the sender ZID */ + peerZidHex = linphone_get_xml_text_content(xml_ctx, "/doc/ZID"); if (peerZidHex != NULL) { - /* get from cache the matching key */ - retval = lime_getCachedRcvKeyByZid(cacheBuffer, &associatedKey); + /* Convert it from hexa string to bytes string and set the result in the associatedKey structure */ + lime_strToUint8(associatedKey.peerZID, (const uint8_t *)peerZidHex, (uint16_t)strlen(peerZidHex)); + linphone_free_xml_text_content(peerZidHex); + /* Get the matching key from cache */ + retval = lime_getCachedRcvKeyByZid(cacheBuffer, &associatedKey); if (retval != 0) { - xmlFree(peerZidHex); - xmlFreeDoc(xmlEncryptedMessage); - return retval; + goto error; } - /* retrieve the portion of message which is encrypted with our key */ - while (cur != NULL) { /* loop on all "msg" node in the message */ - xmlNodePtr msgChildrenNode = cur->xmlChildrenNode; - xmlChar *currentZidHex = xmlNodeListGetString(cacheBuffer, msgChildrenNode->xmlChildrenNode, 1); /* pZID is the first element of msg */ - if (!xmlStrcmp(currentZidHex, (const xmlChar *)selfZidHex)) { /* we found the msg node we are looking for */ - /* get the index (second node in the msg one) */ - xmlChar *sessionIndexHex; - xmlChar *encryptedMessageb64; - - msgChildrenNode = msgChildrenNode->next; - sessionIndexHex = xmlNodeListGetString(cacheBuffer, msgChildrenNode->xmlChildrenNode, 1); - usedSessionIndex = (((uint32_t)lime_charToByte(sessionIndexHex[0]))<<28) - | (((uint32_t)lime_charToByte(sessionIndexHex[1]))<<24) - | (((uint32_t)lime_charToByte(sessionIndexHex[2]))<<20) - | (((uint32_t)lime_charToByte(sessionIndexHex[3]))<<16) - | (((uint32_t)lime_charToByte(sessionIndexHex[4]))<<12) - | (((uint32_t)lime_charToByte(sessionIndexHex[5]))<<8) - | (((uint32_t)lime_charToByte(sessionIndexHex[6]))<<4) - | (((uint32_t)lime_charToByte(sessionIndexHex[7]))); - xmlFree(sessionIndexHex); - - /* get the encrypted message */ - msgChildrenNode = msgChildrenNode->next; - - /* convert the cipherText from base 64 */ - encryptedMessageb64 = xmlNodeListGetString(cacheBuffer, msgChildrenNode->xmlChildrenNode, 1); - bctbx_base64_decode(NULL, &encryptedMessageLength, encryptedMessageb64, strlen((char *)encryptedMessageb64)); /* encryptedMessageLength is 0, so it will be set to the requested buffer length */ - encryptedMessage = (uint8_t *)ms_malloc(encryptedMessageLength); - bctbx_base64_decode(encryptedMessage, &encryptedMessageLength, encryptedMessageb64, strlen((char *)encryptedMessageb64)); - - xmlFree(encryptedMessageb64); - xmlFree(currentZidHex); - break; - } - - cur = cur->next; - xmlFree(currentZidHex); + /* Retrieve the portion of message which is encrypted with our key */ + msg_object = linphone_get_xml_xpath_object_for_node_list(xml_ctx, "/doc/msg"); + if ((msg_object != NULL) && (msg_object->nodesetval != NULL)) { + for (i = 1; i <= msg_object->nodesetval->nodeNr; i++) { + const char *currentZidHex; + const char *sessionIndexHex; + const char *encryptedMessageb64; + const char *encryptedContentTypeb64; + snprintf(xpath_str, sizeof(xpath_str), "/doc/msg[%i]/pzid", i); + currentZidHex = linphone_get_xml_text_content(xml_ctx, xpath_str); + if ((currentZidHex != NULL) && (strcmp(currentZidHex, (char *)selfZidHex) == 0)) { + /* We found the msg node we are looking for */ + snprintf(xpath_str, sizeof(xpath_str), "/doc/msg[%i]/index", i); + sessionIndexHex = linphone_get_xml_text_content(xml_ctx, xpath_str); + if (sessionIndexHex != NULL) { + usedSessionIndex = (((uint32_t)lime_charToByte(sessionIndexHex[0]))<<28) + | (((uint32_t)lime_charToByte(sessionIndexHex[1]))<<24) + | (((uint32_t)lime_charToByte(sessionIndexHex[2]))<<20) + | (((uint32_t)lime_charToByte(sessionIndexHex[3]))<<16) + | (((uint32_t)lime_charToByte(sessionIndexHex[4]))<<12) + | (((uint32_t)lime_charToByte(sessionIndexHex[5]))<<8) + | (((uint32_t)lime_charToByte(sessionIndexHex[6]))<<4) + | (((uint32_t)lime_charToByte(sessionIndexHex[7]))); + linphone_free_xml_text_content(sessionIndexHex); + } + snprintf(xpath_str, sizeof(xpath_str), "/doc/msg[%i]/text", i); + encryptedMessageb64 = linphone_get_xml_text_content(xml_ctx, xpath_str); + if (encryptedMessageb64 != NULL) { + bctbx_base64_decode(NULL, &encryptedMessageLength, (const unsigned char *)encryptedMessageb64, strlen(encryptedMessageb64)); /* encryptedMessageLength is 0, so it will be set to the requested buffer length */ + encryptedMessage = (uint8_t *)ms_malloc(encryptedMessageLength); + bctbx_base64_decode(encryptedMessage, &encryptedMessageLength, (const unsigned char *)encryptedMessageb64, strlen(encryptedMessageb64)); + linphone_free_xml_text_content(encryptedMessageb64); + } + snprintf(xpath_str, sizeof(xpath_str), "/doc/msg[%i]/content-type", i); + encryptedContentTypeb64 = linphone_get_xml_text_content(xml_ctx, xpath_str); + if (encryptedContentTypeb64 != NULL) { + bctbx_base64_decode(NULL, &encryptedContentTypeLength, (const unsigned char *)encryptedContentTypeb64, strlen(encryptedContentTypeb64)); /* encryptedContentTypeLength is 0, so it will be set to the requested buffer length */ + encryptedContentType = (uint8_t *)ms_malloc(encryptedContentTypeLength); + bctbx_base64_decode(encryptedContentType, &encryptedContentTypeLength, (const unsigned char *)encryptedContentTypeb64, strlen(encryptedContentTypeb64)); + linphone_free_xml_text_content(encryptedContentTypeb64); + } + break; + } + if (currentZidHex != NULL) linphone_free_xml_text_content(currentZidHex); } + } } - xmlFree(peerZidHex); - xmlFreeDoc(xmlEncryptedMessage); - /* do we have retrieved correctly all the needed data */ if (encryptedMessage == NULL) { return LIME_UNABLE_TO_DECRYPT_MESSAGE; @@ -801,23 +820,35 @@ int lime_decryptMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_ lime_deriveKey(&associatedKey); } - /* decrypt the message */ - *output = (uint8_t *)ms_malloc(encryptedMessageLength - 16 +1); /* plain message is same length than encrypted one with 16 bytes less for the tag + 1 to add the null termination char */ + /* Decrypt the message */ + *output = (uint8_t *)ms_malloc(encryptedMessageLength - 16 + 1); /* plain message is same length than encrypted one with 16 bytes less for the tag + 1 to add the null termination char */ retval = lime_decryptMessage(&associatedKey, encryptedMessage, (uint32_t)encryptedMessageLength, selfZid, *output); - ms_free(encryptedMessage); - if (retval != 0) { ms_free(*output); *output = NULL; return LIME_UNABLE_TO_DECRYPT_MESSAGE; } + /* Decrypt the content-type */ + if (encryptedContentType != NULL) { + *content_type = (char *)ms_malloc(encryptedContentTypeLength - 16 + 1); /* content-type is same length than encrypted one with 16 bytes less for the tag + 1 to add the null termination char */ + retval = lime_decryptMessage(&associatedKey, encryptedContentType, (uint32_t)encryptedContentTypeLength, selfZid, *((uint8_t **)content_type)); + ms_free(encryptedContentType); + if (retval != 0) { + ms_free(*content_type); + *content_type = NULL; + return LIME_UNABLE_TO_DECRYPT_MESSAGE; + } + } + /* update used key */ lime_deriveKey(&associatedKey); lime_setCachedKey(cacheBuffer, &associatedKey, LIME_RECEIVER); - return 0; +error: + linphone_xmlparsing_context_destroy(xml_ctx); + return retval; } bool_t linphone_chat_room_lime_available(LinphoneChatRoom *cr) { @@ -887,6 +918,7 @@ int lime_im_encryption_engine_process_incoming_message_cb(LinphoneImEncryptionEn int retval; xmlDocPtr cacheXml; uint8_t *decrypted_body = NULL; + char *decrypted_content_type = NULL; cacheString=ms_load_file_content(CACHEFD, &cacheSize); if (!cacheString){ @@ -899,7 +931,7 @@ int lime_im_encryption_engine_process_incoming_message_cb(LinphoneImEncryptionEn fclose(CACHEFD); cacheXml = xmlParseDoc((xmlChar*)cacheString); ms_free(cacheString); - retval = lime_decryptMultipartMessage(cacheXml, (uint8_t *)msg->message, &decrypted_body); + retval = lime_decryptMultipartMessage(cacheXml, (uint8_t *)msg->message, &decrypted_body, &decrypted_content_type); if (retval != 0) { ms_warning("Unable to decrypt message, reason : %s", lime_error_code_to_string(retval)); if (decrypted_body) ms_free(decrypted_body); @@ -922,13 +954,15 @@ int lime_im_encryption_engine_process_incoming_message_cb(LinphoneImEncryptionEn ms_free(msg->message); } msg->message = (char *)decrypted_body; - - if (strcmp("application/cipher.vnd.gsma.rcs-ft-http+xml", msg->content_type) == 0) { - ms_free(msg->content_type); - msg->content_type = ms_strdup("application/vnd.gsma.rcs-ft-http+xml"); + + if (decrypted_content_type != NULL) { + linphone_chat_message_set_content_type(msg, decrypted_content_type); } else { - ms_free(msg->content_type); - msg->content_type = ms_strdup("text/plain"); + if (strcmp("application/cipher.vnd.gsma.rcs-ft-http+xml", msg->content_type) == 0) { + linphone_chat_message_set_content_type(msg, "application/vnd.gsma.rcs-ft-http+xml"); + } else { + linphone_chat_message_set_content_type(msg, "text/plain"); + } } } @@ -941,25 +975,22 @@ int lime_im_encryption_engine_process_incoming_message_cb(LinphoneImEncryptionEn int lime_im_encryption_engine_process_outgoing_message_cb(LinphoneImEncryptionEngine *engine, LinphoneChatRoom *room, LinphoneChatMessage *msg) { LinphoneCore *lc = linphone_im_encryption_engine_get_core(engine); int errcode = -1; - char *content_type = "xml/cipher"; + char *new_content_type = "xml/cipher"; if(linphone_core_lime_enabled(room->lc)) { if (linphone_chat_room_lime_available(room)) { if (msg->content_type) { if (strcmp(msg->content_type, "application/vnd.gsma.rcs-ft-http+xml") == 0) { - /* it's a file transfer, content type shall be set to - application/cipher.vnd.gsma.rcs-ft-http+xml*/ - content_type = "application/cipher.vnd.gsma.rcs-ft-http+xml"; + /* It's a file transfer, content type shall be set to application/cipher.vnd.gsma.rcs-ft-http+xml + TODO: As of january 2017, the content type is now included in the encrypted body, this + application/cipher.vnd.gsma.rcs-ft-http+xml is kept for compatibility with previous versions, + but may be dropped in the future to use xml/cipher instead. */ + new_content_type = "application/cipher.vnd.gsma.rcs-ft-http+xml"; } else if (strcmp(msg->content_type, "application/im-iscomposing+xml") == 0) { /* We don't encrypt composing messages */ return errcode; - } else if (strcmp(msg->content_type, "message/imdn+xml") == 0) { - /* We don't encrypt imdn messages */ - return errcode; } } - msg->content_type = ms_strdup(content_type); - - + /* access the zrtp cache to get keys needed to cipher the message */ const char *zrtp_secrets_cache = linphone_core_get_zrtp_secrets_file(lc); FILE *CACHEFD = fopen(zrtp_secrets_cache, "rb+"); @@ -986,7 +1017,7 @@ int lime_im_encryption_engine_process_outgoing_message_cb(LinphoneImEncryptionEn fclose(CACHEFD); cacheXml = xmlParseDoc((xmlChar*)cacheString); ms_free(cacheString); - retval = lime_createMultipartMessage(cacheXml, (uint8_t *)msg->message, (uint8_t *)peer, &crypted_body); + retval = lime_createMultipartMessage(cacheXml, msg->content_type, (uint8_t *)msg->message, (uint8_t *)peer, &crypted_body); if (retval != 0) { ms_warning("Unable to encrypt message for %s : %s", peer, lime_error_code_to_string(retval)); if (crypted_body) ms_free(crypted_body); @@ -1007,6 +1038,7 @@ int lime_im_encryption_engine_process_outgoing_message_cb(LinphoneImEncryptionEn ms_free(msg->message); } msg->message = (char *)crypted_body; + msg->content_type = ms_strdup(new_content_type); } ms_free(peer); xmlFreeDoc(cacheXml); @@ -1070,14 +1102,14 @@ void lime_im_encryption_engine_generate_file_transfer_key_cb(LinphoneImEncryptio bool_t lime_is_available() { return FALSE; } int lime_decryptFile(void **cryptoContext, unsigned char *key, size_t length, char *plain, char *cipher) { return LIME_NOT_ENABLED;} int lime_decryptMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t **output) { return LIME_NOT_ENABLED;} -int lime_createMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t *peerURI, uint8_t **output) { return LIME_NOT_ENABLED;} +int lime_createMultipartMessage(xmlDocPtr cacheBuffer, const char *content_type, uint8_t *message, uint8_t *peerURI, uint8_t **output) { return LIME_NOT_ENABLED;} int lime_encryptFile(void **cryptoContext, unsigned char *key, size_t length, char *plain, char *cipher) {return LIME_NOT_ENABLED;} void lime_freeKeys(limeURIKeys_t *associatedKeys){ } int lime_getCachedSndKeysByURI(xmlDocPtr cacheBuffer, limeURIKeys_t *associatedKeys){ return LIME_NOT_ENABLED; } -int lime_encryptMessage(limeKey_t *key, uint8_t *plainMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *encryptedMessage) { +int lime_encryptMessage(limeKey_t *key, const uint8_t *plainMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *encryptedMessage) { return LIME_NOT_ENABLED; } int lime_setCachedKey(xmlDocPtr cacheBuffer, limeKey_t *associatedKey, uint8_t role) { diff --git a/coreapi/lime.h b/coreapi/lime.h index 18fc98885..ec0a92394 100644 --- a/coreapi/lime.h +++ b/coreapi/lime.h @@ -119,7 +119,7 @@ LINPHONE_PUBLIC void lime_freeKeys(limeURIKeys_t *associatedKeys); * @return 0 on success, error code otherwise * */ -LINPHONE_PUBLIC int lime_encryptMessage(limeKey_t *key, uint8_t *plainMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *encryptedMessage); +LINPHONE_PUBLIC int lime_encryptMessage(limeKey_t *key, const uint8_t *plainMessage, uint32_t messageLength, uint8_t selfZID[12], uint8_t *encryptedMessage); /** * @brief Encrypt a file before transfering it to the server, encryption is done in several call, first one will be done with cryptoContext null, last one with length = 0 @@ -170,13 +170,14 @@ LINPHONE_PUBLIC int lime_decryptMessage(limeKey_t *key, uint8_t *encryptedMessag * Retrieve in cache the needed keys which are then updated. Output buffer is allocated and must be freed by caller * * @param[in,out] cacheBuffer The xmlDoc containing current cache, get the keys and selfZID from it, updated by this function with derivated keys - * @param[in] message The plain text message to be encrypted + * @param[in] content_type The content type of the message to encrypt + * @param[in] message The message content to be encrypted * @param[in] peerURI The destination URI, associated keys will be found in cache * @param[out] output The output buffer, allocated and set with the encrypted message xml body(null terminated string). Must be freed by caller * * @return 0 on success, error code otherwise */ -LINPHONE_PUBLIC int lime_createMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t *peerURI, uint8_t **output); +LINPHONE_PUBLIC int lime_createMultipartMessage(xmlDocPtr cacheBuffer, const char *content_type, uint8_t *message, uint8_t *peerURI, uint8_t **output); /** * @brief decrypt a multipart xml message @@ -185,11 +186,11 @@ LINPHONE_PUBLIC int lime_createMultipartMessage(xmlDocPtr cacheBuffer, uint8_t * * @param[in,out] cacheBuffer The xmlDoc containing current cache, get the key and selfZID from it, updated by this function with derivated keys * @param[in] message The multipart message, contain one or several part identified by destination ZID, one shall match the self ZID retrieved from cache * @param[out] output The output buffer, allocated and set with the decrypted message(null terminated string). Must be freed by caller - * + * @param[out] content_type The content type of the decrypted message * @return 0 on success, error code otherwise */ -LINPHONE_PUBLIC int lime_decryptMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t **output); +LINPHONE_PUBLIC int lime_decryptMultipartMessage(xmlDocPtr cacheBuffer, uint8_t *message, uint8_t **output, char **content_type); /** * @brief given a readable version of error code generated by Lime functions diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c index 6f1f7df9f..2853ef7df 100644 --- a/coreapi/linphonecore.c +++ b/coreapi/linphonecore.c @@ -1997,9 +1997,10 @@ static void linphone_core_init(LinphoneCore * lc, LinphoneCoreCbs *cbs, LpConfig #ifdef SQLITE_STORAGE_ENABLED sqlite3_bctbx_vfs_register(0); #endif - + lc->vcard_context = linphone_vcard_context_new(); - + linphone_core_initialize_supported_content_types(lc); + remote_provisioning_uri = linphone_core_get_provisioning_uri(lc); if (remote_provisioning_uri == NULL) { linphone_configuring_terminated(lc, LinphoneConfiguringSkipped, NULL); @@ -7199,3 +7200,19 @@ void linphone_core_set_im_encryption_engine(LinphoneCore *lc, LinphoneImEncrypti LinphoneImEncryptionEngine *linphone_core_get_im_encryption_engine(const LinphoneCore *lc) { return lc->im_encryption_engine; } + +void linphone_core_initialize_supported_content_types(LinphoneCore *lc) { + sal_add_content_type_support(lc->sal, "text/plain"); + sal_add_content_type_support(lc->sal, "message/external-body"); + sal_add_content_type_support(lc->sal, "application/vnd.gsma.rcs-ft-http+xml"); + sal_add_content_type_support(lc->sal, "application/im-iscomposing+xml"); + sal_add_content_type_support(lc->sal, "message/imdn+xml"); +} + +bool_t linphone_core_is_content_type_supported(const LinphoneCore *lc, const char *content_type) { + return sal_is_content_type_supported(lc->sal, content_type); +} + +void linphone_core_add_content_type_support(LinphoneCore *lc, const char *content_type) { + sal_add_content_type_support(lc->sal, content_type); +} diff --git a/coreapi/message_storage.c b/coreapi/message_storage.c index 262a6b669..c2f49b3b0 100644 --- a/coreapi/message_storage.c +++ b/coreapi/message_storage.c @@ -203,16 +203,17 @@ static int callback_all(void *data, int argc, char **argv, char **colName){ * | 0 | storage_id * | 1 | localContact * | 2 | remoteContact - * | 3 | direction flag - * | 4 | message - * | 5 | time (unused now, used to be string-based timestamp) - * | 6 | read flag - * | 7 | status - * | 8 | external body url + * | 3 | direction flag (LinphoneChatMessageDir) + * | 4 | message (text content of the message) + * | 5 | time (unused now, used to be string-based timestamp, replaced by the utc timestamp) + * | 6 | read flag (no longer used, replaced by the LinphoneChatMessageStateDisplayed state) + * | 7 | status (LinphoneChatMessageState) + * | 8 | external body url (deprecated file transfer system) * | 9 | utc timestamp * | 10 | app data text - * | 11 | linphone content id - * | 12 | message id + * | 11 | linphone content id (LinphoneContent describing a file transfer) + * | 12 | message id (used for IMDN) + * | 13 | content type (of the message field [must be text representable]) */ static int create_chat_message(void *data, int argc, char **argv, char **colName){ LinphoneChatRoom *cr = (LinphoneChatRoom *)data; @@ -245,6 +246,7 @@ static int create_chat_message(void *data, int argc, char **argv, char **colName new_message->external_body_url= ms_strdup(argv[8]); new_message->appdata = ms_strdup(argv[10]); new_message->message_id = ms_strdup(argv[12]); + new_message->content_type = ms_strdup(argv[13]); if (argv[11] != NULL) { int id = atoi(argv[11]); @@ -253,6 +255,17 @@ static int create_chat_message(void *data, int argc, char **argv, char **colName } } + /* Fix content type for old messages that were stored without it */ + if (new_message->content_type == NULL) { + if (new_message->file_transfer_information != NULL) { + new_message->content_type = ms_strdup("application/vnd.gsma.rcs-ft-http+xml"); + } else if (new_message->external_body_url != NULL) { + new_message->content_type = ms_strdup("message/external-body"); + } else { + new_message->content_type = ms_strdup("text/plain"); + } + } + /* Add the new message to the weak messages list. */ linphone_chat_room_add_weak_message(cr, new_message); } @@ -330,7 +343,7 @@ unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){ peer=linphone_address_as_string_uri_only(linphone_chat_room_get_peer_address(msg->chat_room)); local_contact=linphone_address_as_string_uri_only(linphone_chat_message_get_local_address(msg)); - buf = sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q,%lld,%Q,%i,%Q);", + buf = sqlite3_mprintf("INSERT INTO history VALUES(NULL,%Q,%Q,%i,%Q,%Q,%i,%i,%Q,%lld,%Q,%i,%Q,%Q);", local_contact, peer, msg->dir, @@ -342,7 +355,8 @@ unsigned int linphone_chat_message_store(LinphoneChatMessage *msg){ (int64_t)msg->time, msg->appdata, content_id, - msg->message_id + msg->message_id, + msg->content_type ); linphone_sql_request(lc->db,buf); sqlite3_free(buf); @@ -748,13 +762,21 @@ void linphone_update_table(sqlite3* db) { if (ret != SQLITE_OK) { ms_message("Table already up to date: %s", errmsg); } else { - ms_message("Table history updated successfully for message_id data."); + ms_message("Table history updated successfully for messageId data."); } // Convert is_read to LinphoneChatMessageStateDisplayed buf = sqlite3_mprintf("UPDATE history SET status=%i WHERE read=1 AND direction=%i;", LinphoneChatMessageStateDisplayed, LinphoneChatMessageIncoming); linphone_sql_request(db, buf); sqlite3_free(buf); + + /* New field for content type */ + ret = sqlite3_exec(db, "ALTER TABLE history ADD COLUMN content_type TEXT;", NULL, NULL, &errmsg); + if (ret != SQLITE_OK) { + ms_message("Table already up to date: %s", errmsg); + } else { + ms_message("Table history updated successfully for content_type data."); + } } void linphone_message_storage_init_chat_rooms(LinphoneCore *lc) { diff --git a/coreapi/private.h b/coreapi/private.h index 0f1e234dd..ee42a1f04 100644 --- a/coreapi/private.h +++ b/coreapi/private.h @@ -554,8 +554,6 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LinphoneCore *lc void linphone_proxy_config_write_to_config_file(struct _LpConfig* config,LinphoneProxyConfig *obj, int index); LinphoneReason linphone_core_message_received(LinphoneCore *lc, SalOp *op, const SalMessage *msg); -LinphoneReason linphone_core_is_composing_received(LinphoneCore *lc, SalOp *op, const SalIsComposing *is_composing); -LinphoneReason linphone_core_imdn_received(LinphoneCore *lc, SalOp *op, const SalImdn *imdn); void linphone_core_real_time_text_received(LinphoneCore *lc, LinphoneChatRoom *cr, uint32_t character, LinphoneCall *call); void linphone_call_init_stats(LinphoneCallStats *stats, int type); @@ -1093,9 +1091,7 @@ struct _LinphoneCore char *tls_cert; char *tls_key; - /*default resource list server*/ - LinphoneAddress *default_rls_addr; - + LinphoneAddress *default_rls_addr; /*default resource list server*/ LinphoneImEncryptionEngine *im_encryption_engine; }; @@ -1148,6 +1144,8 @@ bool_t linphone_core_is_payload_type_usable_for_bandwidth(LinphoneCore *lc, cons #define linphone_core_ready(lc) ((lc)->state==LinphoneGlobalOn || (lc)->state==LinphoneGlobalShutdown) void _linphone_core_configure_resolver(void); +void linphone_core_initialize_supported_content_types(LinphoneCore *lc); + struct _EcCalibrator{ MSFactory *factory; ms_thread_t thread; diff --git a/include/linphone/chat.h b/include/linphone/chat.h index c1f72f67b..81364d201 100644 --- a/include/linphone/chat.h +++ b/include/linphone/chat.h @@ -310,11 +310,13 @@ LINPHONE_PUBLIC bool_t linphone_chat_room_is_remote_composing(const LinphoneChat * @return the number of unread messages. */ LINPHONE_PUBLIC int linphone_chat_room_get_unread_messages_count(LinphoneChatRoom *cr); + /** * Returns back pointer to #LinphoneCore object. * @deprecated use linphone_chat_room_get_core() **/ LINPHONE_PUBLIC LINPHONE_DEPRECATED LinphoneCore* linphone_chat_room_get_lc(LinphoneChatRoom *cr); + /** * Returns back pointer to #LinphoneCore object. **/ @@ -349,68 +351,83 @@ LINPHONE_PUBLIC unsigned int linphone_chat_message_store(LinphoneChatMessage *ms * Returns a #LinphoneChatMessageState as a string. */ LINPHONE_PUBLIC const char* linphone_chat_message_state_to_string(const LinphoneChatMessageState state); + /** * Get the state of the message *@param message #LinphoneChatMessage obj *@return #LinphoneChatMessageState */ LINPHONE_PUBLIC LinphoneChatMessageState linphone_chat_message_get_state(const LinphoneChatMessage* message); + /** * Duplicate a LinphoneChatMessage **/ LINPHONE_PUBLIC LinphoneChatMessage* linphone_chat_message_clone(const LinphoneChatMessage* message); + /** * Acquire a reference to the chat message. * @param msg the chat message * @return the same chat message **/ LINPHONE_PUBLIC LinphoneChatMessage * linphone_chat_message_ref(LinphoneChatMessage *msg); + /** * Release reference to the chat message. * @param msg the chat message. **/ LINPHONE_PUBLIC void linphone_chat_message_unref(LinphoneChatMessage *msg); + /** * Destroys a LinphoneChatMessage. **/ LINPHONE_PUBLIC void linphone_chat_message_destroy(LinphoneChatMessage* msg); + /** @deprecated Use linphone_chat_message_set_from_address() instead. */ #define linphone_chat_message_set_from(msg, addr) linphone_chat_message_set_from_address(msg, addr) + /** * Set origin of the message * @param[in] message #LinphoneChatMessage obj * @param[in] from #LinphoneAddress origin of this message (copied) */ LINPHONE_PUBLIC void linphone_chat_message_set_from_address(LinphoneChatMessage* message, const LinphoneAddress* from); + /** @deprecated Use linphone_chat_message_get_from_address() instead. */ #define linphone_chat_message_get_from(msg) linphone_chat_message_get_from_address(msg) + /** * Get origin of the message * @param[in] message #LinphoneChatMessage obj * @return #LinphoneAddress */ LINPHONE_PUBLIC const LinphoneAddress* linphone_chat_message_get_from_address(const LinphoneChatMessage* message); + #define linphone_chat_message_set_to(msg, addr) linphone_chat_message_set_to_address(msg, addr) + /** * Set destination of the message * @param[in] message #LinphoneChatMessage obj * @param[in] addr #LinphoneAddress destination of this message (copied) */ LINPHONE_PUBLIC void linphone_chat_message_set_to_address(LinphoneChatMessage* message, const LinphoneAddress* addr); + /** @deprecated Use linphone_chat_message_get_to_address() instead. */ #define linphone_chat_message_get_to(msg) linphone_chat_message_get_to_address(msg) + /** * Get destination of the message * @param[in] message #LinphoneChatMessage obj * @return #LinphoneAddress */ LINPHONE_PUBLIC const LinphoneAddress* linphone_chat_message_get_to_address(const LinphoneChatMessage* message); + /** * Linphone message can carry external body as defined by rfc2017 * @param message #LinphoneChatMessage * @return external body url or NULL if not present. */ LINPHONE_PUBLIC const char* linphone_chat_message_get_external_body_url(const LinphoneChatMessage* message); + /** * Linphone message can carry external body as defined by rfc2017 * @@ -418,6 +435,7 @@ LINPHONE_PUBLIC const char* linphone_chat_message_get_external_body_url(const Li * @param url ex: access-type=URL; URL="http://www.foo.com/file" */ LINPHONE_PUBLIC void linphone_chat_message_set_external_body_url(LinphoneChatMessage* message,const char* url); + /** * Get the file_transfer_information (used by call backs to recover informations during a rcs file transfer) * @@ -425,6 +443,22 @@ LINPHONE_PUBLIC void linphone_chat_message_set_external_body_url(LinphoneChatMes * @return a pointer to the LinphoneContent structure or NULL if not present. */ LINPHONE_PUBLIC const LinphoneContent* linphone_chat_message_get_file_transfer_information(const LinphoneChatMessage* message); + +/** + * Get the content type of a chat message. + * @param[in] message LinphoneChatMessage object + * @return The content type of the chat message + */ +LINPHONE_PUBLIC const char * linphone_chat_message_get_content_type(const LinphoneChatMessage *message); + +/** + * Set the content type of a chat message. + * This content type must match a content that is text representable, such as text/plain, text/html or image/svg+xml. + * @param[in] message LinphoneChatMessage object + * @param[in] content_type The new content type of the chat message + */ +LINPHONE_PUBLIC void linphone_chat_message_set_content_type(LinphoneChatMessage *message, const char *content_type); + /** * Start the download of the file from remote server * @@ -434,16 +468,19 @@ LINPHONE_PUBLIC const LinphoneContent* linphone_chat_message_get_file_transfer_i * @deprecated Use linphone_chat_message_download_file() instead. */ LINPHONE_PUBLIC LINPHONE_DEPRECATED void linphone_chat_message_start_file_download(LinphoneChatMessage* message, LinphoneChatMessageStateChangedCb status_cb, void* ud); + /** * Start the download of the file referenced in a LinphoneChatMessage from remote server. * @param[in] message LinphoneChatMessage object. */ LINPHONE_PUBLIC int linphone_chat_message_download_file(LinphoneChatMessage *message); + /** * Cancel an ongoing file transfer attached to this message.(upload or download) * @param msg #LinphoneChatMessage */ LINPHONE_PUBLIC void linphone_chat_message_cancel_file_transfer(LinphoneChatMessage* msg); + /** * Linphone message has an app-specific field that can store a text. The application might want * to use it for keeping data over restarts, like thumbnail image path. @@ -451,6 +488,7 @@ LINPHONE_PUBLIC void linphone_chat_message_cancel_file_transfer(LinphoneChatMess * @return the application-specific data or NULL if none has been stored. */ LINPHONE_PUBLIC const char* linphone_chat_message_get_appdata(const LinphoneChatMessage* message); + /** * Linphone message has an app-specific field that can store a text. The application might want * to use it for keeping data over restarts, like thumbnail image path. @@ -462,35 +500,42 @@ LINPHONE_PUBLIC const char* linphone_chat_message_get_appdata(const LinphoneChat * @param data the data to store into the message */ LINPHONE_PUBLIC void linphone_chat_message_set_appdata(LinphoneChatMessage* message, const char* data); + /** * Get text part of this message * @return text or NULL if no text. */ LINPHONE_PUBLIC const char* linphone_chat_message_get_text(const LinphoneChatMessage* message); + /** * Get the time the message was sent. */ LINPHONE_PUBLIC time_t linphone_chat_message_get_time(const LinphoneChatMessage* message); + /** * User pointer get function */ LINPHONE_PUBLIC void* linphone_chat_message_get_user_data(const LinphoneChatMessage* message); + /** *User pointer set function */ LINPHONE_PUBLIC void linphone_chat_message_set_user_data(LinphoneChatMessage* message,void*); + /** * Returns the chatroom this message belongs to. **/ LINPHONE_PUBLIC LinphoneChatRoom* linphone_chat_message_get_chat_room(LinphoneChatMessage *msg); LINPHONE_PUBLIC const LinphoneAddress* linphone_chat_message_get_peer_address(LinphoneChatMessage *msg); + /** * Returns the origin address of a message if it was a outgoing message, or the destination address if it was an incoming message. *@param message #LinphoneChatMessage obj *@return #LinphoneAddress */ LINPHONE_PUBLIC LinphoneAddress *linphone_chat_message_get_local_address(const LinphoneChatMessage* message); + /** * Add custom headers to the message. * @param message the message @@ -498,47 +543,56 @@ LINPHONE_PUBLIC LinphoneAddress *linphone_chat_message_get_local_address(const L * @param header_value header value **/ LINPHONE_PUBLIC void linphone_chat_message_add_custom_header(LinphoneChatMessage* message, const char *header_name, const char *header_value); + /** * Retrieve a custom header value given its name. * @param message the message * @param header_name header name searched **/ LINPHONE_PUBLIC const char * linphone_chat_message_get_custom_header(LinphoneChatMessage* message, const char *header_name); + /** * Removes a custom header from the message. * @param msg the message * @param header_name name of the header to remove **/ LINPHONE_PUBLIC void linphone_chat_message_remove_custom_header(LinphoneChatMessage *msg, const char *header_name); + /** * Returns TRUE if the message has been read, otherwise returns FALSE. * @param message the message **/ LINPHONE_PUBLIC bool_t linphone_chat_message_is_read(LinphoneChatMessage* message); + /** * Returns TRUE if the message has been sent, returns FALSE if the message has been received. * @param message the message **/ LINPHONE_PUBLIC bool_t linphone_chat_message_is_outgoing(LinphoneChatMessage* message); + /** * Returns the id used to identify this message in the storage database * @param message the message * @return the id */ LINPHONE_PUBLIC unsigned int linphone_chat_message_get_storage_id(LinphoneChatMessage* message); + LINPHONE_PUBLIC LinphoneReason linphone_chat_message_get_reason(LinphoneChatMessage* msg); + /** * Get full details about delivery error of a chat message. * @param msg a LinphoneChatMessage * @return a LinphoneErrorInfo describing the details. **/ LINPHONE_PUBLIC const LinphoneErrorInfo *linphone_chat_message_get_error_info(const LinphoneChatMessage *msg); + /** * Set the path to the file to read from or write to during the file transfer. * @param[in] msg LinphoneChatMessage object * @param[in] filepath The path to the file to use for the file transfer. */ LINPHONE_PUBLIC void linphone_chat_message_set_file_transfer_filepath(LinphoneChatMessage *msg, const char *filepath); + /** * Get the path to the file to read from or write to during the file transfer. * @param[in] msg LinphoneChatMessage object diff --git a/include/linphone/core.h b/include/linphone/core.h index 8c219ee19..d997ad6c3 100644 --- a/include/linphone/core.h +++ b/include/linphone/core.h @@ -3206,7 +3206,7 @@ LINPHONE_PUBLIC void linphone_core_set_root_ca_data(LinphoneCore *lc, const char * Set the pointer to an externally provided ssl configuration for the crypto library * @param lc #LinphoneCore object * @param[in] ssl_config A pointer to an opaque structure which will be provided directly to the crypto library used in bctoolbox. Use with extra care. - * This ssl_config structure is responsability of the caller and will not be freed at the connection's end. + * This ssl_config structure is responsibility of the caller and will not be freed at the connection's end. * @ingroup initializing * @endinternal */ @@ -3374,7 +3374,7 @@ LINPHONE_PUBLIC const bctbx_list_t * linphone_core_get_call_logs(LinphoneCore *l /** * Get the list of call logs (past calls) that matches the given #LinphoneAddress. - * At the contrary of linphone_core_get_call_logs, it is your responsability to unref the logs and free this list once you are done using it. + * At the contrary of linphone_core_get_call_logs, it is your responsibility to unref the logs and free this list once you are done using it. * @param[in] lc LinphoneCore object * @param[in] addr LinphoneAddress object * @return \bctbx_list{LinphoneCallLog} @@ -4920,6 +4920,22 @@ LINPHONE_PUBLIC void linphone_core_set_im_encryption_engine(LinphoneCore *lc, Li */ LINPHONE_PUBLIC LinphoneImEncryptionEngine * linphone_core_get_im_encryption_engine(const LinphoneCore *lc); +/** + * Tells whether a content type is supported. + * @param[in] lc LinphoneCore object + * @param[in] content_type The content type to check + * @return A boolean value telling whether the specified content type is supported or not. + */ +LINPHONE_PUBLIC bool_t linphone_core_is_content_type_supported(const LinphoneCore *lc, const char *content_type); + +/** + * Add support for the specified content type. + * It is the application responsibility to handle it correctly afterwards. + * @param[in] lc LinphoneCore object + * @param[in] content_type The content type to add support for + */ +LINPHONE_PUBLIC void linphone_core_add_content_type_support(LinphoneCore *lc, const char *content_type); + #include "linphone/ringtoneplayer.h" #include "linphone/factory.h" diff --git a/include/sal/sal.h b/include/sal/sal.h index 12dde05bb..01b93799c 100644 --- a/include/sal/sal.h +++ b/include/sal/sal.h @@ -307,16 +307,6 @@ typedef struct SalMessage{ time_t time; }SalMessage; -typedef struct SalIsComposing { - const char *from; - const char *text; -} SalIsComposing; - -typedef struct SalImdn { - const char *from; - const char *content; -} SalImdn; - #define SAL_MEDIA_DESCRIPTION_MAX_MESSAGE_ATTRIBUTES 5 SalMediaDescription *sal_media_description_new(void); @@ -447,11 +437,11 @@ typedef enum SalSubscribeStatus{ SalSubscribeTerminated }SalSubscribeStatus; -typedef enum SalTextDeliveryStatus{ - SalTextDeliveryInProgress, - SalTextDeliveryDone, - SalTextDeliveryFailed -}SalTextDeliveryStatus; +typedef enum SalMessageDeliveryStatus{ + SalMessageDeliveryInProgress, + SalMessageDeliveryDone, + SalMessageDeliveryFailed +}SalMessageDeliveryStatus; /** * auth event mode @@ -505,10 +495,8 @@ typedef void (*SalOnRegisterFailure)(SalOp *op); typedef void (*SalOnVfuRequest)(SalOp *op); typedef void (*SalOnDtmfReceived)(SalOp *op, char dtmf); typedef void (*SalOnRefer)(Sal *sal, SalOp *op, const char *referto); -typedef void (*SalOnTextReceived)(SalOp *op, const SalMessage *msg); -typedef void (*SalOnTextDeliveryUpdate)(SalOp *op, SalTextDeliveryStatus); -typedef void (*SalOnIsComposingReceived)(SalOp *op, const SalIsComposing *is_composing); -typedef void (*SalOnImdnReceived)(SalOp *op, const SalImdn *imdn); +typedef void (*SalOnMessageReceived)(SalOp *op, const SalMessage *msg); +typedef void (*SalOnMessageDeliveryUpdate)(SalOp *op, SalMessageDeliveryStatus); typedef void (*SalOnNotifyRefer)(SalOp *op, SalReferStatus state); typedef void (*SalOnSubscribeResponse)(SalOp *op, SalSubscribeStatus status, int will_retry); typedef void (*SalOnNotify)(SalOp *op, SalSubscribeStatus status, const char *event, SalBodyHandler *body); @@ -544,10 +532,8 @@ typedef struct SalCallbacks{ SalOnVfuRequest vfu_request; SalOnDtmfReceived dtmf_received; SalOnRefer refer_received; - SalOnTextReceived text_received; - SalOnTextDeliveryUpdate text_delivery_update; - SalOnIsComposingReceived is_composing_received; - SalOnImdnReceived imdn_received; + SalOnMessageReceived message_received; + SalOnMessageDeliveryUpdate message_delivery_update; SalOnNotifyRefer notify_refer; SalOnSubscribeReceived subscribe_received; SalOnIncomingSubscribeClosed incoming_subscribe_closed; @@ -644,6 +630,8 @@ void sal_use_tcp_tls_keepalive(Sal *ctx, bool_t enabled); int sal_set_tunnel(Sal *ctx, void *tunnelclient); /*Default value is true*/ void sal_enable_sip_update_method(Sal *ctx,bool_t value); +bool_t sal_is_content_type_supported(const Sal *sal, const char *content_type); +void sal_add_content_type_support(Sal *sal, const char *content_type); /** * returns keepalive period in ms diff --git a/pixmaps/svg/.directory b/pixmaps/svg/.directory deleted file mode 100644 index c99a6191a..000000000 --- a/pixmaps/svg/.directory +++ /dev/null @@ -1,4 +0,0 @@ -[Dolphin] -PreviewsShown=true -Timestamp=2015,8,3,13,7,36 -Version=3 diff --git a/tester/CMakeLists.txt b/tester/CMakeLists.txt index 187682d6d..b662a2c06 100644 --- a/tester/CMakeLists.txt +++ b/tester/CMakeLists.txt @@ -130,9 +130,11 @@ set(RC_FILES rcfiles/zero_length_params_rc ) -set(IMAGE_FILES +set(IMAGE_FILES + images/linphone.svg images/nowebcamCIF.jpg - images/nowebcamVGA.jpg) + images/nowebcamVGA.jpg +) set(VCARD_FILES vcards/thousand_vcards.vcf diff --git a/tester/Makefile.am b/tester/Makefile.am index 4bc979e1c..edcfd27d7 100644 --- a/tester/Makefile.am +++ b/tester/Makefile.am @@ -78,7 +78,7 @@ RCFILES = \ rcfiles/friends_rc\ rcfiles/carddav_rc -IMAGE_FILES = images/nowebcamCIF.jpg images/nowebcamVGA.jpg +IMAGE_FILES = images/linphone.svg images/nowebcamCIF.jpg images/nowebcamVGA.jpg VCARDS_FILE = vcards/vcards.vcf vcards/thousand_vcards.vcf diff --git a/tester/images/linphone.svg b/tester/images/linphone.svg new file mode 100644 index 000000000..528247c89 --- /dev/null +++ b/tester/images/linphone.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tester/message_tester.c b/tester/message_tester.c index 32b06ede8..ccd641e6f 100644 --- a/tester/message_tester.c +++ b/tester/message_tester.c @@ -279,6 +279,10 @@ void text_message_base(LinphoneCoreManager* marie, LinphoneCoreManager* pauline) BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageDelivered,1)); BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneMessageReceived,1)); + BC_ASSERT_PTR_NOT_NULL(marie->stat.last_received_chat_message); + if (marie->stat.last_received_chat_message != NULL) { + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_content_type(marie->stat.last_received_chat_message), "text/plain"); + } BC_ASSERT_PTR_NOT_NULL(linphone_core_get_chat_room(marie->lc,pauline->identity)); } @@ -1410,6 +1414,7 @@ static void lime_unit(void) { xmlDocPtr cacheBufferBob; uint8_t *multipartMessage = NULL; uint8_t *decryptedMessage = NULL; + char *decryptedContentType = NULL; xmlChar *xmlStringOutput; int xmlStringLength; limeURIKeys_t associatedKeys; @@ -1529,7 +1534,7 @@ static void lime_unit(void) { /* encrypt a msg */ - retval = lime_createMultipartMessage(cacheBufferAlice, (uint8_t *)PLAIN_TEXT_TEST_MESSAGE, (uint8_t *)"sip:pauline@sip.example.org", &multipartMessage); + retval = lime_createMultipartMessage(cacheBufferAlice, "text/plain", (uint8_t *)PLAIN_TEXT_TEST_MESSAGE, (uint8_t *)"sip:pauline@sip.example.org", &multipartMessage); BC_ASSERT_EQUAL(retval, 0, int, "%d"); if (retval == 0) { @@ -1537,15 +1542,17 @@ static void lime_unit(void) { } /* decrypt the multipart msg */ - retval = lime_decryptMultipartMessage(cacheBufferBob, multipartMessage, &decryptedMessage); + retval = lime_decryptMultipartMessage(cacheBufferBob, multipartMessage, &decryptedMessage, &decryptedContentType); BC_ASSERT_EQUAL(retval, 0, int, "%d"); if (retval == 0) { BC_ASSERT_STRING_EQUAL((char *)decryptedMessage, (char *)PLAIN_TEXT_TEST_MESSAGE); + BC_ASSERT_STRING_EQUAL((char *)decryptedContentType, "text/plain"); ms_message("Succesfully decrypted msg is %s", decryptedMessage); } - free(multipartMessage); - free(decryptedMessage); + ms_free(multipartMessage); + ms_free(decryptedMessage); + ms_free(decryptedContentType); /* update ZID files */ /* dump the xml document into a string */ @@ -2304,6 +2311,64 @@ void chat_message_custom_headers(void) { linphone_core_manager_destroy(pauline); } +void _text_message_with_custom_content_type(bool_t with_lime) { + LinphoneCoreManager *marie = linphone_core_manager_new("marie_rc"); + LinphoneCoreManager *pauline = linphone_core_manager_new("pauline_tcp_rc"); + LinphoneChatRoom *chat_room = linphone_core_get_chat_room(pauline->lc, marie->identity); + LinphoneChatMessage *msg; + LinphoneChatMessageCbs *cbs; + bctbx_vfs_t *vfs = bctbx_vfs_get_default(); + char *send_filepath; + bctbx_vfs_file_t *file_to_send; + size_t file_size; + char *buf; + + if (with_lime) { + if (enable_lime_for_message_test(marie, pauline) < 0) goto end; + } + + send_filepath = bc_tester_res("images/linphone.svg"); + file_to_send = bctbx_file_open(vfs, send_filepath, "r"); + file_size = (size_t)bctbx_file_size(file_to_send); + buf = bctbx_malloc(file_size + 1); + bctbx_file_read(file_to_send, buf, file_size, 0); + buf[file_size] = '\0'; + bctbx_file_close(file_to_send); + bc_free(send_filepath); + msg = linphone_chat_room_create_message(chat_room, buf); + linphone_chat_message_set_content_type(msg, "image/svg+xml"); + + linphone_core_add_content_type_support(marie->lc, "image/svg+xml"); + linphone_core_add_content_type_support(pauline->lc, "image/svg+xml"); + cbs = linphone_chat_message_get_callbacks(msg); + linphone_chat_message_cbs_set_msg_state_changed(cbs, liblinphone_tester_chat_message_msg_state_changed); + linphone_chat_room_send_chat_message(chat_room, msg); + + BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &marie->stat.number_of_LinphoneMessageReceived, 1)); + BC_ASSERT_TRUE(wait_for(pauline->lc, marie->lc, &pauline->stat.number_of_LinphoneMessageDelivered, 1)); + + if (marie->stat.last_received_chat_message) { + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_content_type(marie->stat.last_received_chat_message), "image/svg+xml"); + BC_ASSERT_STRING_EQUAL(linphone_chat_message_get_text(marie->stat.last_received_chat_message), buf); + } + + bctbx_free(buf); + +end: + linphone_core_manager_destroy(marie); + linphone_core_manager_destroy(pauline); + remove("tmpZIDCacheMarie.xml"); + remove("tmpZIDCachePauline.xml"); +} + +void text_message_with_custom_content_type(void) { + _text_message_with_custom_content_type(FALSE); +} + +void text_message_with_custom_content_type_and_lime(void) { + _text_message_with_custom_content_type(TRUE); +} + test_t message_tests[] = { TEST_NO_TAG("Text message", text_message), TEST_NO_TAG("Text message within call dialog", text_message_within_call_dialog), @@ -2371,7 +2436,9 @@ test_t message_tests[] = { TEST_ONE_TAG("Real Time Text offer answer with different payload numbers (sender side)", real_time_text_message_different_text_codecs_payload_numbers_sender_side, "RTT"), TEST_ONE_TAG("Real Time Text offer answer with different payload numbers (receiver side)", real_time_text_message_different_text_codecs_payload_numbers_receiver_side, "RTT"), TEST_ONE_TAG("Real Time Text copy paste", real_time_text_copy_paste, "RTT"), - TEST_NO_TAG("IM Encryption Engine custom headers", chat_message_custom_headers) + TEST_NO_TAG("IM Encryption Engine custom headers", chat_message_custom_headers), + TEST_NO_TAG("Text message with custom content-type", text_message_with_custom_content_type), + TEST_ONE_TAG("Text message with custom content-type and lime", text_message_with_custom_content_type_and_lime, "LIME") }; test_suite_t message_test_suite = {