Commit 0c9fbfc1 authored by Sylvain Berfini's avatar Sylvain Berfini 🎩

Improved CPIM parser to parse all MIME-encapsulated message content headers

parent 3d30114e
......@@ -20,6 +20,7 @@
#include "linphone/utils/utils.h"
#include "logger/logger.h"
#include "chat/cpim/parser/cpim-parser.h"
#include "object/object-p.h"
......@@ -37,6 +38,7 @@ public:
shared_ptr<PrivHeaderList> cpimHeaders = make_shared<PrivHeaderList>();
shared_ptr<PrivHeaderList> messageHeaders = make_shared<PrivHeaderList>();
shared_ptr<PrivHeaderList> contentHeaders = make_shared<PrivHeaderList>();
string content;
};
......@@ -92,6 +94,30 @@ void Cpim::Message::removeMessageHeader (const Header &messageHeader) {
// -----------------------------------------------------------------------------
Cpim::Message::HeaderList Cpim::Message::getContentHeaders () const {
L_D(const Message);
return d->contentHeaders;
}
bool Cpim::Message::addContentHeader (const Header &contentHeader) {
L_D(Message);
if (!contentHeader.isValid())
return false;
d->contentHeaders->push_back(Parser::getInstance()->cloneHeader(contentHeader));
return true;
}
void Cpim::Message::removeContentHeader (const Header &contentHeader) {
L_D(Message);
d->contentHeaders->remove_if([&contentHeader](const shared_ptr<const Header> &header) {
return contentHeader.getName() == header->getName() && contentHeader.getValue() == header->getValue();
});
}
// -----------------------------------------------------------------------------
string Cpim::Message::getContent () const {
L_D(const Message);
return d->content;
......@@ -125,11 +151,18 @@ string Cpim::Message::asString () const {
output += "\r\n";
for (const auto &messageHeader : *d->messageHeaders)
output += messageHeader->asString();
if (d->messageHeaders->size() > 0) {
for (const auto &messageHeader : *d->messageHeaders)
output += messageHeader->asString();
output += "\r\n";
}
for (const auto &contentHeaders : *d->contentHeaders)
output += contentHeaders->asString();
output += "\r\n";
output += ""; // TODO: Headers MIME.
output += getContent();
return output;
......
......@@ -42,6 +42,10 @@ namespace Cpim {
HeaderList getMessageHeaders () const;
bool addMessageHeader (const Header &messageHeader);
void removeMessageHeader (const Header &messageHeader);
HeaderList getContentHeaders () const;
bool addContentHeader (const Header &contentHeader);
void removeContentHeader (const Header &contentHeader);
std::string getContent () const;
bool setContent (const std::string &content);
......
......@@ -25,7 +25,7 @@ LINPHONE_BEGIN_NAMESPACE
static const char *grammar =
// See: https://tools.ietf.org/html/rfc3862
R"==GRAMMAR==(
Message = Headers CRLF Headers CRLF
Message = Headers CRLF Headers CRLF [Headers CRLF]
Headers = *Header
Header = Header-name ":" Header-parameters SP Header-value CRLF
......
......@@ -166,7 +166,7 @@ namespace Cpim {
// Warning: Call this function one time!
shared_ptr<Message> createMessage () const {
size_t size = mHeaders->size();
if (size != 2) {
if (size < 2 || size > 3) {
lWarning() << "Bad headers lists size.";
return nullptr;
}
......@@ -190,9 +190,21 @@ namespace Cpim {
}
// Add message headers.
if (mHeaders->size() > 2) {
list<shared_ptr<ListHeaderNode>>::iterator it = mHeaders->begin();
std::advance(it, 1);
shared_ptr<ListHeaderNode> messageHeaders = *it;
for (const auto &headerNode : *messageHeaders) {
const shared_ptr<const Header> header = headerNode->createHeader();
if (!header || !message->addMessageHeader(*header))
return nullptr;
}
}
// Add content headers.
for (const auto &headerNode : *mHeaders->back()) {
const shared_ptr<const Header> header = headerNode->createHeader();
if (!header || !message->addMessageHeader(*header))
if (!header || !message->addContentHeader(*header))
return nullptr;
}
......@@ -265,8 +277,9 @@ shared_ptr<Cpim::Message> Cpim::Parser::parseMessage (const string &input) {
}
shared_ptr<Message> message = messageNode->createMessage();
if (message)
if (message) {
message->setContent(input.substr(parsedSize));
}
return message;
}
......
......@@ -31,10 +31,10 @@
void CpimChatMessageModifier::encode(LinphonePrivate::ChatMessagePrivate* msg) {
Cpim::Message message;
Cpim::GenericHeader contentTypeHeader;
contentTypeHeader.setName("Content-Type");
contentTypeHeader.setValue("Message/CPIM");
message.addCpimHeader(contentTypeHeader);
Cpim::GenericHeader cpimContentTypeHeader;
cpimContentTypeHeader.setName("Content-Type");
cpimContentTypeHeader.setValue("Message/CPIM");
message.addCpimHeader(cpimContentTypeHeader);
shared_ptr<Content> content;
if (msg->internalContent) {
......@@ -50,7 +50,13 @@
string contentType = content->getContentType().asString();
const vector<char> body = content->getBody();
string contentBody(body.begin(), body.end());
message.setContent("ContentType: " + contentType + "\r\n" + contentBody);
Cpim::GenericHeader contentTypeHeader;
contentTypeHeader.setName("Content-Type");
contentTypeHeader.setValue(contentType);
message.addContentHeader(contentTypeHeader);
message.setContent(contentBody);
if (!message.isValid()) {
//TODO
......@@ -64,7 +70,29 @@
}
void CpimChatMessageModifier::decode(LinphonePrivate::ChatMessagePrivate* msg) {
//TODO
shared_ptr<Content> content;
if (msg->internalContent) {
content = msg->internalContent;
} else {
content = msg->contents.front();
}
ContentType contentType = content->getContentType();
if (contentType.asString() == "Message/CPIM") {
const vector<char> body = content->getBody();
string contentBody(body.begin(), body.end());
shared_ptr<const Cpim::Message> message = Cpim::Message::createFromString(contentBody);
if (message && message->isValid()) {
shared_ptr<Content> newContent = make_shared<Content>();
ContentType newContentType(message->getContentHeaders()->front()->getValue());
newContent->setContentType(newContentType);
newContent->setBody(message->getContent());
} else {
//TODO
}
} else {
//TODO
}
}
LINPHONE_END_NAMESPACE
\ No newline at end of file
......@@ -70,6 +70,10 @@ bool ContentType::operator== (const ContentType &contentType) {
return getType() == contentType.getType() && getSubType() == contentType.getSubType();
}
bool ContentType::operator== (const string &contentType) {
return *this == ContentType(contentType);
}
const string &ContentType::getType () const {
L_D(const ContentType);
return d->type;
......
......@@ -38,6 +38,7 @@ public:
ContentType &operator= (const ContentType &src);
bool operator== (const ContentType &contentType);
bool operator== (const std::string &contentType);
bool isValid () const;
......
......@@ -37,6 +37,8 @@ static void parse_minimal_message () {
const string str2 = message->asString();
BC_ASSERT_STRING_EQUAL(str2.c_str(), str.c_str());
BC_ASSERT_STRING_EQUAL(message->getContent().c_str(), "");
}
static void set_generic_header_name () {
......@@ -227,6 +229,10 @@ static void check_subject_header_language () {
}
static void parse_rfc_example () {
const string body = "<body>"
"Here is the text of my message."
"</body>";
const string str = "Content-type: Message/CPIM\r\n"
"\r\n"
"From: MR SANDERS <im:piglet@100akerwood.com>\r\n"
......@@ -239,21 +245,25 @@ static void parse_rfc_example () {
"MyFeatures.VitalMessageOption: Confirmation-requested\r\n"
"MyFeatures.WackyMessageOption: Use-silly-font\r\n"
"\r\n"
"Content-type text/xml; charset=utf-8\r\n"
"Content-Type: text/xml; charset=utf-8\r\n"
"Content-ID: <1234567890@foo.com>\r\n"
"\r\n"
"<body>"
"Here is the text of my message."
"</body>";
"\r\n" + body;
shared_ptr<const Cpim::Message> message = Cpim::Message::createFromString(str);
if (!BC_ASSERT_PTR_NOT_NULL(message)) return;
const string str2 = message->asString();
BC_ASSERT_STRING_EQUAL(str2.c_str(), str.c_str());
string content = message->getContent();
BC_ASSERT_STRING_EQUAL(content.c_str(), body.c_str());
}
static void parse_message_with_generic_header_parameters () {
const string body = "<body>"
"Here is the text of my message."
"</body>";
const string str = "Content-type: Message/CPIM\r\n"
"\r\n"
"From: MR SANDERS <im:piglet@100akerwood.com>\r\n"
......@@ -261,18 +271,18 @@ static void parse_message_with_generic_header_parameters () {
"yaya: coucou\r\n"
"yepee:;good=bad ugly\r\n"
"\r\n"
"Content-type text/xml; charset=utf-8\r\n"
"Content-Type: text/xml; charset=utf-8\r\n"
"Content-ID: <1234567890@foo.com>\r\n"
"\r\n"
"<body>"
"Here is the text of my message."
"</body>";
"\r\n" + body;
shared_ptr<const Cpim::Message> message = Cpim::Message::createFromString(str);
if (!BC_ASSERT_PTR_NOT_NULL(message)) return;
const string str2 = message->asString();
BC_ASSERT_STRING_EQUAL(str2.c_str(), str.c_str());
string content = message->getContent();
BC_ASSERT_STRING_EQUAL(content.c_str(), body.c_str());
}
static void build_message () {
......@@ -281,11 +291,11 @@ static void build_message () {
return;
// Set CPIM headers.
Cpim::GenericHeader contentTypeHeader;
if (!BC_ASSERT_TRUE(contentTypeHeader.setName("Content-Type"))) return;
if (!BC_ASSERT_TRUE(contentTypeHeader.setValue("Message/CPIM"))) return;
Cpim::GenericHeader cpimContentTypeHeader;
if (!BC_ASSERT_TRUE(cpimContentTypeHeader.setName("Content-Type"))) return;
if (!BC_ASSERT_TRUE(cpimContentTypeHeader.setValue("Message/CPIM"))) return;
if (!BC_ASSERT_TRUE(message.addCpimHeader(contentTypeHeader))) return;
if (!BC_ASSERT_TRUE(message.addCpimHeader(cpimContentTypeHeader))) return;
// Set message headers.
Cpim::FromHeader fromHeader;
......@@ -328,10 +338,18 @@ static void build_message () {
if (!BC_ASSERT_TRUE(message.addMessageHeader(vitalMessageHeader))) return;
if (!BC_ASSERT_TRUE(message.addMessageHeader(wackyMessageHeader))) return;
const string content = "Content-type text/xml; charset=utf-8\r\n"
"Content-ID: <1234567890@foo.com>\r\n"
"\r\n"
"<body>"
// Set Content headers.
Cpim::GenericHeader contentTypeHeader;
if (!BC_ASSERT_TRUE(contentTypeHeader.setName("Content-Type"))) return;
if (!BC_ASSERT_TRUE( contentTypeHeader.setValue("text/xml; charset=utf-8"))) return;
if (!BC_ASSERT_TRUE(message.addContentHeader(contentTypeHeader))) return;
Cpim::GenericHeader contentIdHeader;
if (!BC_ASSERT_TRUE(contentIdHeader.setName("Content-ID"))) return;
if (!BC_ASSERT_TRUE( contentIdHeader.setValue("<1234567890@foo.com>"))) return;
if (!BC_ASSERT_TRUE(message.addContentHeader(contentIdHeader))) return;
const string content = "<body>"
"Here is the text of my message."
"</body>";
......@@ -351,7 +369,7 @@ static void build_message () {
"MyFeatures.VitalMessageOption: Confirmation-requested\r\n"
"MyFeatures.WackyMessageOption: Use-silly-font\r\n"
"\r\n"
"Content-type text/xml; charset=utf-8\r\n"
"Content-Type: text/xml; charset=utf-8\r\n"
"Content-ID: <1234567890@foo.com>\r\n"
"\r\n"
"<body>"
......@@ -361,6 +379,14 @@ static void build_message () {
BC_ASSERT_STRING_EQUAL(strMessage.c_str(), expectedMessage.c_str());
}
static void cpim_chat_message_modifier_encoder(void) {
}
static void cpim_chat_message_modifier_decoder(void) {
}
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),
......@@ -370,7 +396,9 @@ test_t cpim_tests[] = {
TEST_NO_TAG("Check Subject header language", check_subject_header_language),
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("Build Message", build_message),
TEST_NO_TAG("CPIM chat message modifier encoder", cpim_chat_message_modifier_encoder),
TEST_NO_TAG("CPIM chat message modifier decoder", cpim_chat_message_modifier_decoder)
};
test_suite_t cpim_test_suite = {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment