diff --git a/src/chat/chat-message-p.h b/src/chat/chat-message-p.h index 953e9b57d..569cd676c 100644 --- a/src/chat/chat-message-p.h +++ b/src/chat/chat-message-p.h @@ -152,7 +152,7 @@ private: bool isFileTransferInProgressAndValid(); int startHttpTransfer(std::string url, std::string action, belle_http_request_listener_callbacks_t *cbs); void releaseHttpRequest(); - void createFileTransferInformationsFromVndGsmaRcsFtHttpXml(); + void createFileTransferInformationsFromVndGsmaRcsFtHttpXml(std::string body); L_DECLARE_PUBLIC(ChatMessage); }; diff --git a/src/chat/chat-message.cpp b/src/chat/chat-message.cpp index 3723533b8..0c969fa4c 100644 --- a/src/chat/chat-message.cpp +++ b/src/chat/chat-message.cpp @@ -888,12 +888,12 @@ void ChatMessagePrivate::releaseHttpRequest() { } } -void ChatMessagePrivate::createFileTransferInformationsFromVndGsmaRcsFtHttpXml() { +void ChatMessagePrivate::createFileTransferInformationsFromVndGsmaRcsFtHttpXml(string body) { xmlChar *file_url = NULL; xmlDocPtr xmlMessageBody; xmlNodePtr cur; /* parse the msg body to get all informations from it */ - xmlMessageBody = xmlParseDoc((const xmlChar *)getText().c_str()); + xmlMessageBody = xmlParseDoc((const xmlChar *)body.c_str()); LinphoneContent *content = linphone_content_new(); setFileTransferInformation(content); @@ -1005,9 +1005,21 @@ LinphoneReason ChatMessagePrivate::receive() { // End of message modification // --------------------------------------- - if ((errorCode <= 0) && (linphone_core_is_content_type_supported(chatRoom->getCore(), getContentType().asString().c_str()) == FALSE)) { - errorCode = 415; - lError() << "Unsupported MESSAGE (content-type " << getContentType().asString() << " not recognized)"; + if (errorCode <= 0) { + bool foundSupportContentType = false; + for (auto it = contents.begin(); it != contents.end(); it++) { + if (linphone_core_is_content_type_supported(chatRoom->getCore(), it->getContentType().asString().c_str())) { + foundSupportContentType = true; + break; + } else { + lError() << "Unsupported content-type: " << it->getContentType().asString(); + } + } + + if (!foundSupportContentType) { + errorCode = 415; + lError() << "No content-type in the contents list is supported..."; + } } if (errorCode > 0) { @@ -1016,11 +1028,13 @@ LinphoneReason ChatMessagePrivate::receive() { return reason; } - if (getContentType() == ContentType::FileTransfer) { - createFileTransferInformationsFromVndGsmaRcsFtHttpXml(); - store = true; - } else if (getContentType() == ContentType::PlainText) { - store = true; + for (auto it = contents.begin(); it != contents.end(); it++) { + if (it->getContentType() == ContentType::FileTransfer) { + store = true; + createFileTransferInformationsFromVndGsmaRcsFtHttpXml(it->getBodyAsString()); + } else if (it->getContentType() == ContentType::PlainText) { + store = true; + } } if (store) { diff --git a/src/chat/chat-room.cpp b/src/chat/chat-room.cpp index c191040bd..eea1feab0 100644 --- a/src/chat/chat-room.cpp +++ b/src/chat/chat-room.cpp @@ -402,7 +402,7 @@ LinphoneReason ChatRoomPrivate::messageReceived (SalOp *op, const SalMessage *sa Content content; content.setContentType(salMsg->content_type); content.setBody(salMsg->text ? salMsg->text : ""); - msg->addContent(content); + msg->setInternalContent(content); msg->setToAddress(op->get_to() ? op->get_to() : linphone_core_get_identity(core)); msg->setFromAddress(peerAddress); diff --git a/src/chat/modifier/cpim-chat-message-modifier.cpp b/src/chat/modifier/cpim-chat-message-modifier.cpp index 8d69fa301..c556c0d25 100644 --- a/src/chat/modifier/cpim-chat-message-modifier.cpp +++ b/src/chat/modifier/cpim-chat-message-modifier.cpp @@ -24,7 +24,7 @@ #include "content/content.h" #include "address/address.h" #include "logger/logger.h" -#include "chat/chat-message-p.h" +#include "chat/chat-message.h" // ============================================================================= diff --git a/src/chat/modifier/encryption-chat-message-modifier.cpp b/src/chat/modifier/encryption-chat-message-modifier.cpp index e1798c57f..cad20317a 100644 --- a/src/chat/modifier/encryption-chat-message-modifier.cpp +++ b/src/chat/modifier/encryption-chat-message-modifier.cpp @@ -26,7 +26,7 @@ #include "content/content.h" #include "address/address.h" #include "chat/chat-room.h" -#include "chat/chat-message-p.h" +#include "chat/chat-message.h" // ============================================================================= diff --git a/src/chat/modifier/multipart-chat-message-modifier.cpp b/src/chat/modifier/multipart-chat-message-modifier.cpp index 7d2f5f36b..089173cd7 100644 --- a/src/chat/modifier/multipart-chat-message-modifier.cpp +++ b/src/chat/modifier/multipart-chat-message-modifier.cpp @@ -18,8 +18,11 @@ */ #include "multipart-chat-message-modifier.h" + #include "address/address.h" -#include "chat/chat-message-p.h" +#include "chat/chat-room.h" +#include "chat/chat-message.h" +#include "logger/logger.h" // ============================================================================= @@ -29,15 +32,86 @@ LINPHONE_BEGIN_NAMESPACE ChatMessageModifier::Result MultipartChatMessageModifier::encode (shared_ptr message, int *errorCode) { if (message->getContents().size() > 1) { - //TODO + LinphoneCore *lc = message->getChatRoom()->getCore(); + char tmp[64]; + lc->sal->create_uuid(tmp, sizeof(tmp)); + string boundary = tmp; + stringstream multipartMessage; + + multipartMessage << "--" << boundary; + for (auto it = message->getContents().begin(); it != message->getContents().end(); it++) { + multipartMessage << "\r\n"; + multipartMessage << "Content-Type: " << it->getContentType().asString() << "\r\n\r\n"; + multipartMessage << it->getBodyAsString() << "\r\n\r\n"; + multipartMessage << "--" << boundary; + } + multipartMessage << "--"; + + Content newContent; + ContentType newContentType("multipart/mixed"); + newContentType.setParameter("boundary=" + boundary); + newContent.setContentType(newContentType); + newContent.setBody(multipartMessage.str()); + message->setInternalContent(newContent); + return ChatMessageModifier::Result::Done; } return ChatMessageModifier::Result::Skipped; -} +} ChatMessageModifier::Result MultipartChatMessageModifier::decode (shared_ptr message, int *errorCode) { - //TODO - if (false) { // Multipart required + if (message->getInternalContent().getContentType().getType() == "multipart") { + string boundary = message->getInternalContent().getContentType().getParameter(); + if (boundary.empty()) { + lError() << "Boundary parameter of content-type not found !"; + return ChatMessageModifier::Result::Error; + } + + size_t pos = boundary.find("="); + if (pos == string::npos) { + lError() << "Parameter seems invalid: " << boundary; + return ChatMessageModifier::Result::Error; + } + boundary = "--" + boundary.substr(pos + 1); + lInfo() << "Multipart boundary is " << boundary; + + const vector body = message->getInternalContent().getBody(); + string contentsString(body.begin(), body.end()); + + pos = contentsString.find(boundary); + if (pos == string::npos) { + lError() << "Boundary not found in body !"; + return ChatMessageModifier::Result::Error; + } + + size_t start = pos + boundary.length() + 2; // 2 is the size of \r\n + size_t end; + do { + end = contentsString.find(boundary, start); + if (end != string::npos) { + string contentString = contentsString.substr(start, end-start); + + size_t contentTypePos = contentString.find(": ") + 2; // 2 is the size of : + size_t endOfLinePos = contentString.find("\r\n"); + if (contentTypePos >= endOfLinePos) { + lError() << "Content should start by a 'Content-Type: ' line !"; + continue; + } + string contentTypeString = contentString.substr(contentTypePos, endOfLinePos - contentTypePos); + ContentType contentType(contentTypeString); + + endOfLinePos += 4; // 4 is two time the size of \r\n + string contentBody = contentString.substr(endOfLinePos, contentString.length() - (endOfLinePos + 4)); // 4 is two time the size of \r\n + + Content content; + content.setContentType(contentType); + content.setBody(contentBody); + message->addContent(content); + + lInfo() << "Parsed and added content with type " << contentType.asString(); + } + start = end + boundary.length() + 2; // 2 is the size of \r\n + } while (end != string::npos); return ChatMessageModifier::Result::Done; } diff --git a/src/content/content-type.cpp b/src/content/content-type.cpp index 76ff21ae3..c6772c207 100644 --- a/src/content/content-type.cpp +++ b/src/content/content-type.cpp @@ -20,7 +20,7 @@ #include "linphone/utils/utils.h" #include "object/clonable-object-p.h" - +#include "logger/logger.h" #include "content-type.h" // ============================================================================= @@ -33,6 +33,7 @@ class ContentTypePrivate : public ClonableObjectPrivate { public: string type; string subType; + string parameter; }; // ----------------------------------------------------------------------------- @@ -52,13 +53,22 @@ ContentType::ContentType (const string &contentType) : ClonableObject(*new Conte L_D(); size_t pos = contentType.find('/'); + size_t posParam = contentType.find("; "); + size_t end = contentType.length(); if (pos == string::npos) return; if (setType(contentType.substr(0, pos))) { - if (!setSubType(contentType.substr(pos + 1))) + if (posParam != string::npos) { + end = posParam; + } + if (!setSubType(contentType.substr(pos + 1, end - (pos + 1)))) d->type.clear(); } + + if (posParam != string::npos) { + setParameter(contentType.substr(posParam + 2)); // We remove the blankspace after the ; + } } ContentType::ContentType (const string &type, const string &subType) : ClonableObject(*new ContentTypePrivate) { @@ -70,19 +80,30 @@ ContentType::ContentType (const string &type, const string &subType) : ClonableO } } -ContentType::ContentType (const ContentType &src) : ContentType(src.getType(), src.getSubType()) {} +ContentType::ContentType (const string &type, const string &subType, const string ¶meter) : ClonableObject(*new ContentTypePrivate) { + L_D(); + + if (setType(type)) { + if (!setSubType(subType)) + d->type.clear(); + } + setParameter(parameter); +} + +ContentType::ContentType (const ContentType &src) : ContentType(src.getType(), src.getSubType(), src.getParameter()) {} ContentType &ContentType::operator= (const ContentType &src) { if (this != &src) { setType(src.getType()); setSubType(src.getSubType()); + setParameter(src.getParameter()); } return *this; } bool ContentType::operator== (const ContentType &contentType) const { - return getType() == contentType.getType() && getSubType() == contentType.getSubType(); + return getType() == contentType.getType() && getSubType() == contentType.getSubType() && getParameter() == contentType.getParameter(); } bool ContentType::operator!= (const ContentType &contentType) const { @@ -117,6 +138,16 @@ bool ContentType::setSubType (const string &subType) { return false; } +const string &ContentType::getParameter () const { + L_D(); + return d->parameter; +} + +void ContentType::setParameter (const string ¶meter) { + L_D(); + d->parameter = parameter; +} + bool ContentType::isValid () const { L_D(); return !d->type.empty() && !d->subType.empty(); @@ -124,7 +155,14 @@ bool ContentType::isValid () const { string ContentType::asString () const { L_D(); - return isValid() ? d->type + "/" + d->subType : ""; + if (isValid()) { + string asString = d->type + "/" + d->subType; + if (!d->parameter.empty()) { + asString += "; " + d->parameter; + } + return asString; + } + return ""; } LINPHONE_END_NAMESPACE diff --git a/src/content/content-type.h b/src/content/content-type.h index dac4f5351..ebbbe9e19 100644 --- a/src/content/content-type.h +++ b/src/content/content-type.h @@ -32,6 +32,7 @@ class LINPHONE_PUBLIC ContentType : public ClonableObject { public: ContentType (const std::string &contentType = ""); ContentType (const std::string &type, const std::string &subType); + ContentType (const std::string &type, const std::string &subType, const std::string ¶meter); ContentType (const ContentType &src); ContentType &operator= (const ContentType &src); @@ -52,6 +53,9 @@ public: const std::string &getSubType () const; bool setSubType (const std::string &subType); + const std::string &getParameter () const; + void setParameter (const std::string ¶meter); + std::string asString () const; static const ContentType Cpim; diff --git a/tester/cpim-tester.cpp b/tester/cpim-tester.cpp index ce778ee6e..191792e57 100644 --- a/tester/cpim-tester.cpp +++ b/tester/cpim-tester.cpp @@ -382,7 +382,7 @@ static void build_message () { BC_ASSERT_STRING_EQUAL(strMessage.c_str(), expectedMessage.c_str()); } -static void cpim_chat_message_modifier(void) { +static void cpim_chat_message_modifier_base(bool_t use_multipart) { LinphoneCoreManager* marie = linphone_core_manager_new("marie_rc"); LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_tcp_rc"); LpConfig *config = linphone_core_get_config(marie->lc); @@ -392,6 +392,12 @@ static void cpim_chat_message_modifier(void) { shared_ptr marieRoom = ObjectFactory::create(marie->lc, paulineAddress); shared_ptr marieMessage = marieRoom->createMessage("Hello CPIM"); + if (use_multipart) { + Content content; + content.setContentType(ContentType::PlainText); + content.setBody("Hello Part 2"); + marieMessage->addContent(content); + } marieRoom->sendMessage(marieMessage); BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneMessageReceived,1)); @@ -407,6 +413,14 @@ static void cpim_chat_message_modifier(void) { linphone_core_manager_destroy(pauline); } +static void cpim_chat_message_modifier(void) { + cpim_chat_message_modifier_base(FALSE); +} + +static void cpim_chat_message_modifier_with_multipart_body(void) { + cpim_chat_message_modifier_base(TRUE); +} + test_t cpim_tests[] = { TEST_NO_TAG("Parse minimal CPIM message", parse_minimal_message), TEST_NO_TAG("Set generic header name", set_generic_header_name), @@ -417,7 +431,8 @@ test_t cpim_tests[] = { TEST_NO_TAG("Parse RFC example", parse_rfc_example), TEST_NO_TAG("Parse Message with generic header parameters", parse_message_with_generic_header_parameters), TEST_NO_TAG("Build Message", build_message), - TEST_NO_TAG("CPIM chat message modifier", cpim_chat_message_modifier) + TEST_NO_TAG("CPIM chat message modifier", cpim_chat_message_modifier), + TEST_NO_TAG("CPIM chat message modifier with multipart body", cpim_chat_message_modifier_with_multipart_body), }; test_suite_t cpim_test_suite = {