diff --git a/CMakeLists.txt b/CMakeLists.txt
index e4ac40ba1..8d0ff54e5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -115,11 +115,13 @@ if(LINPHONE_BUILDER_GROUP_EXTERNAL_SOURCE_PATH_BUILDERS)
include("${EP_ortp_CONFIG_DIR}/ORTPConfig.cmake")
set(BcToolbox_FIND_COMPONENTS tester)
include("${EP_bctoolbox_CONFIG_DIR}/BcToolboxConfig.cmake")
+ include("${EP_belr_CONFIG_DIR}/BelrConfig.cmake")
else()
find_package(BelleSIP REQUIRED)
find_package(Mediastreamer2 REQUIRED)
find_package(ORTP REQUIRED)
find_package(BcToolbox 0.0.3 REQUIRED OPTIONAL_COMPONENTS tester)
+ find_package(Belr REQUIRED)
endif()
find_package(XML2 REQUIRED)
find_package(Zlib)
@@ -208,6 +210,7 @@ set(LINPHONE_INCLUDE_DIRS
${BELLESIP_INCLUDE_DIRS}
${MEDIASTREAMER2_INCLUDE_DIRS}
${BCTOOLBOX_CORE_INCLUDE_DIRS}
+ ${BELR_INCLUDE_DIRS}
)
if (BZRTP_FOUND)
list(APPEND LINPHONE_INCLUDE_DIRS ${BZRTP_INCLUDE_DIRS})
@@ -243,7 +246,7 @@ if(MSVC)
endif()
add_definitions("-DLINPHONE_EXPORTS")
-set(LINPHONE_CPPFLAGS ${BELCARD_CPPFLAGS} ${BELLESIP_CPPFLAGS} ${MEDIASTREAMER2_CPPFLAGS} ${BCTOOLBOX_CPPFLAGS})
+set(LINPHONE_CPPFLAGS ${BELCARD_CPPFLAGS} ${BELLESIP_CPPFLAGS} ${MEDIASTREAMER2_CPPFLAGS} ${BCTOOLBOX_CPPFLAGS} ${BELR_CPPFLAGS})
if(ENABLE_STATIC)
list(APPEND LINPHONE_CPPFLAGS "-DLINPHONE_STATIC")
endif()
diff --git a/coreapi/CMakeLists.txt b/coreapi/CMakeLists.txt
index 4dd144b9d..03cf08acb 100644
--- a/coreapi/CMakeLists.txt
+++ b/coreapi/CMakeLists.txt
@@ -35,9 +35,18 @@ endif()
set(LINPHONE_PRIVATE_HEADER_FILES
+ ../src/cpim/cpim.h
+ ../src/cpim/header/cpim-core-headers.h
+ ../src/cpim/header/cpim-generic-header.h
+ ../src/cpim/header/cpim-header-p.h
+ ../src/cpim/header/cpim-header.h
+ ../src/cpim/message/cpim-message.h
+ ../src/cpim/parser/cpim-grammar.h
+ ../src/cpim/parser/cpim-parser.h
../src/object/object.h
../src/object/singleton.h
../src/utils/general.h
+ ../src/utils/utils.h
bellesip_sal/sal_impl.h
carddav.h
conference_private.h
@@ -122,7 +131,16 @@ set(LINPHONE_SOURCE_FILES_C
xmlrpc.c
vtables.c
)
-set(LINPHONE_SOURCE_FILES_CXX conference.cc)
+set(LINPHONE_SOURCE_FILES_CXX
+ ../src/cpim/header/cpim-core-headers.cpp
+ ../src/cpim/header/cpim-generic-header.cpp
+ ../src/cpim/header/cpim-header.cpp
+ ../src/cpim/message/cpim-message.cpp
+ ../src/cpim/parser/cpim-grammar.cpp
+ ../src/cpim/parser/cpim-parser.cpp
+ ../src/utils/utils.cpp
+ conference.cc
+)
if(ANDROID)
list(APPEND LINPHONE_SOURCE_FILES_CXX linphonecore_jni.cc)
set_source_files_properties(linphonecore_jni.cc PROPERTIES COMPILE_DEFINITIONS "USE_JAVAH")
@@ -158,6 +176,7 @@ set(LIBS
${MEDIASTREAMER2_LIBRARIES}
${ORTP_LIBRARIES}
${XML2_LIBRARIES}
+ ${BELR_LIBRARIES}
)
if(WIN32 AND NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
list(APPEND LIBS "Ws2_32")
diff --git a/src/cpim/cpim.h b/src/cpim/cpim.h
new file mode 100644
index 000000000..a2aa0a824
--- /dev/null
+++ b/src/cpim/cpim.h
@@ -0,0 +1,26 @@
+/*
+ * cpim.h
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _CPIM_H_
+#define _CPIM_H_
+
+#include "message/cpim-message.h"
+
+// =============================================================================
+
+#endif // ifndef _CPIM_H_
diff --git a/src/cpim/header/cpim-core-headers.cpp b/src/cpim/header/cpim-core-headers.cpp
new file mode 100644
index 000000000..d49ba6ee0
--- /dev/null
+++ b/src/cpim/header/cpim-core-headers.cpp
@@ -0,0 +1,105 @@
+/*
+ * cpim-core-headers.cpp
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "cpim-header-p.h"
+#include "cpim/parser/cpim-parser.h"
+
+#include "cpim-core-headers.h"
+
+using namespace std;
+
+using namespace LinphonePrivate;
+
+// =============================================================================
+
+Cpim::CoreHeader::CoreHeader () : Header(*new HeaderPrivate) {}
+
+Cpim::CoreHeader::CoreHeader (HeaderPrivate &p) : Header(p) {}
+
+Cpim::CoreHeader::~CoreHeader () {}
+
+bool Cpim::CoreHeader::isValid () const {
+ return !getValue().empty();
+}
+
+// -----------------------------------------------------------------------------
+
+#define MAKE_CORE_HEADER_IMPL(CLASS_PREFIX) \
+ bool Cpim::CLASS_PREFIX ## Header::setValue(const string &value) { \
+ return Parser::getInstance()->coreHeaderIsValid(value) && Header::setValue(value); \
+ }
+
+MAKE_CORE_HEADER_IMPL(From);
+MAKE_CORE_HEADER_IMPL(To);
+MAKE_CORE_HEADER_IMPL(Cc);
+MAKE_CORE_HEADER_IMPL(DateTime);
+
+MAKE_CORE_HEADER_IMPL(Ns);
+MAKE_CORE_HEADER_IMPL(Require);
+
+#undef MAKE_CORE_HEADER_IMPL
+
+// -----------------------------------------------------------------------------
+
+void Cpim::CoreHeader::force (const std::string &value) {
+ Header::setValue(value);
+}
+
+// -----------------------------------------------------------------------------
+
+class Cpim::SubjectHeaderPrivate : public HeaderPrivate {
+public:
+ string language;
+};
+
+Cpim::SubjectHeader::SubjectHeader () : CoreHeader(*new SubjectHeaderPrivate) {}
+
+bool Cpim::SubjectHeader::setValue (const string &value) {
+ return Parser::getInstance()->coreHeaderIsValid(value) && Header::setValue(value);
+}
+
+string Cpim::SubjectHeader::getLanguage () const {
+ L_D(const SubjectHeader);
+ return d->language;
+}
+
+bool Cpim::SubjectHeader::setLanguage (const string &language) {
+ if (!language.empty() && !Parser::getInstance()->subjectHeaderLanguageIsValid(language))
+ return false;
+
+ L_D(SubjectHeader);
+ d->language = language;
+
+ return true;
+}
+
+string Cpim::SubjectHeader::asString () const {
+ L_D(const SubjectHeader);
+
+ string languageParam;
+ if (!d->language.empty())
+ languageParam = ";lang=" + d->language;
+
+ return getName() + ":" + languageParam + " " + getValue() + "\r\n";
+}
+
+void Cpim::SubjectHeader::force (const string &value, const string &language) {
+ L_D(SubjectHeader);
+ CoreHeader::force(value);
+ d->language = language;
+}
diff --git a/src/cpim/header/cpim-core-headers.h b/src/cpim/header/cpim-core-headers.h
new file mode 100644
index 000000000..965904908
--- /dev/null
+++ b/src/cpim/header/cpim-core-headers.h
@@ -0,0 +1,111 @@
+/*
+ * cpim-core-headers.h
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _CPIM_CORE_HEADERS_H_
+#define _CPIM_CORE_HEADERS_H_
+
+#include "cpim-header.h"
+
+// =============================================================================
+
+#define MAKE_CORE_HEADER(CLASS_PREFIX, NAME) \
+ class CLASS_PREFIX ## Header : public CoreHeader { \
+ public: \
+ CLASS_PREFIX ## Header() = default; \
+ inline std::string getName() const override { \
+ return NAME; \
+ } \
+ bool setValue(const std::string &value) override; \
+ private: \
+ L_DISABLE_COPY(CLASS_PREFIX ## Header); \
+ };
+
+namespace LinphonePrivate {
+ namespace Cpim {
+ class HeaderNode;
+
+ // -------------------------------------------------------------------------
+ // Generic core header.
+ // -------------------------------------------------------------------------
+
+ class CoreHeader : public Header {
+ friend class HeaderNode;
+
+ public:
+ CoreHeader ();
+
+ virtual ~CoreHeader () = 0;
+
+ bool isValid () const override;
+
+ protected:
+ explicit CoreHeader (HeaderPrivate &p);
+
+ void force (const std::string &value);
+
+ private:
+ L_DISABLE_COPY(CoreHeader);
+ };
+
+ // -------------------------------------------------------------------------
+ // Core headers.
+ // -------------------------------------------------------------------------
+
+ MAKE_CORE_HEADER(From, "From");
+ MAKE_CORE_HEADER(To, "To");
+ MAKE_CORE_HEADER(Cc, "cc");
+ MAKE_CORE_HEADER(DateTime, "DateTime");
+ MAKE_CORE_HEADER(Ns, "NS");
+ MAKE_CORE_HEADER(Require, "Require");
+
+ // -------------------------------------------------------------------------
+ // Specific Subject declaration.
+ // -------------------------------------------------------------------------
+
+ class SubjectHeaderPrivate;
+
+ class SubjectHeader : public CoreHeader {
+ friend class HeaderNode;
+
+ public:
+ SubjectHeader ();
+
+ inline std::string getName () const override {
+ return "Subject";
+ }
+
+ bool setValue (const std::string &value) override;
+
+ std::string getLanguage () const;
+ bool setLanguage (const std::string &language);
+
+ std::string asString () const override;
+
+ protected:
+ void force (const std::string &value, const std::string &language);
+
+ private:
+ L_DECLARE_PRIVATE(SubjectHeader);
+ L_DISABLE_COPY(SubjectHeader);
+ };
+ }
+}
+
+#undef MAKE_CORE_HEADER
+
+#endif // ifndef _CPIM_CORE_HEADERS_H_
diff --git a/src/cpim/header/cpim-generic-header.cpp b/src/cpim/header/cpim-generic-header.cpp
new file mode 100644
index 000000000..178cfbb27
--- /dev/null
+++ b/src/cpim/header/cpim-generic-header.cpp
@@ -0,0 +1,117 @@
+/*
+ * cpim-generic-header.cpp
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+
+#include "cpim-header-p.h"
+#include "cpim/parser/cpim-parser.h"
+#include "utils/utils.h"
+
+#include "cpim-generic-header.h"
+
+using namespace std;
+
+using namespace LinphonePrivate;
+
+// =============================================================================
+
+class Cpim::GenericHeaderPrivate : public HeaderPrivate {
+public:
+ string name;
+ shared_ptr > > parameters = make_shared > >();
+};
+
+Cpim::GenericHeader::GenericHeader () : Header(*new GenericHeaderPrivate) {}
+
+string Cpim::GenericHeader::getName () const {
+ L_D(const GenericHeader);
+ return d->name;
+}
+
+bool Cpim::GenericHeader::setName (const string &name) {
+ L_D(GenericHeader);
+
+ static const set reserved = {
+ "From", "To", "cc", "DateTime", "Subject", "NS", "Require"
+ };
+
+ if (
+ reserved.find(name) != reserved.end() ||
+ !Parser::getInstance()->headerNameIsValid(name)
+ )
+ return false;
+
+ d->name = name;
+ return true;
+}
+
+bool Cpim::GenericHeader::setValue (const string &value) {
+ return Parser::getInstance()->headerValueIsValid(value) && Header::setValue(value);
+}
+
+Cpim::GenericHeader::ParameterList Cpim::GenericHeader::getParameters () const {
+ L_D(const GenericHeader);
+ return d->parameters;
+}
+
+bool Cpim::GenericHeader::addParameter (const string &key, const string &value) {
+ L_D(GenericHeader);
+
+ if (!Parser::getInstance()->headerParameterIsValid(key + "=" + value))
+ return false;
+
+ d->parameters->push_back(make_pair(key, value));
+ return true;
+}
+
+void Cpim::GenericHeader::removeParameter (const string &key, const string &value) {
+ L_D(GenericHeader);
+ d->parameters->remove(make_pair(key, value));
+}
+
+bool Cpim::GenericHeader::isValid () const {
+ L_D(const GenericHeader);
+ return !d->name.empty() && !getValue().empty();
+}
+
+string Cpim::GenericHeader::asString () const {
+ L_D(const GenericHeader);
+
+ string parameters;
+ for (const auto ¶meter : *d->parameters)
+ parameters += ";" + parameter.first + "=" + parameter.second;
+
+ return d->name + ":" + parameters + " " + getValue() + "\r\n";
+}
+
+// -----------------------------------------------------------------------------
+
+void Cpim::GenericHeader::force (const string &name, const string &value, const string ¶meters) {
+ L_D(GenericHeader);
+
+ // Set name/value.
+ d->name = name;
+ Header::setValue(value);
+
+ // Parse and build parameters list.
+ for (const auto ¶meter : Utils::split(parameters, ';')) {
+ size_t equalIndex = parameter.find('=');
+ if (equalIndex != string::npos)
+ d->parameters->push_back(make_pair(parameter.substr(0, equalIndex), parameter.substr(equalIndex + 1)));
+ }
+}
diff --git a/src/cpim/header/cpim-generic-header.h b/src/cpim/header/cpim-generic-header.h
new file mode 100644
index 000000000..fb1a198cf
--- /dev/null
+++ b/src/cpim/header/cpim-generic-header.h
@@ -0,0 +1,65 @@
+/*
+ * cpim-generic-header.h
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _CPIM_GENERIC_HEADER_H_
+#define _CPIM_GENERIC_HEADER_H_
+
+#include
+#include
+
+#include "cpim-header.h"
+
+// =============================================================================
+
+namespace LinphonePrivate {
+ namespace Cpim {
+ class GenericHeaderPrivate;
+ class HeaderNode;
+
+ class GenericHeader : public Header {
+ friend class HeaderNode;
+
+ public:
+ GenericHeader ();
+
+ std::string getName () const override;
+ bool setName (const std::string &name);
+
+ bool setValue (const std::string &value) override;
+
+ typedef std::shared_ptr > > ParameterList;
+
+ ParameterList getParameters () const;
+ bool addParameter (const std::string &key, const std::string &value);
+ void removeParameter (const std::string &key, const std::string &value);
+
+ bool isValid () const override;
+
+ std::string asString () const override;
+
+ protected:
+ void force (const std::string &name, const std::string &value, const std::string ¶meters);
+
+ private:
+ L_DECLARE_PRIVATE(GenericHeader);
+ L_DISABLE_COPY(GenericHeader);
+ };
+ }
+}
+
+#endif // ifndef _CPIM_GENERIC_HEADER_H_
diff --git a/src/cpim/header/cpim-header-p.h b/src/cpim/header/cpim-header-p.h
new file mode 100644
index 000000000..2e3208dd4
--- /dev/null
+++ b/src/cpim/header/cpim-header-p.h
@@ -0,0 +1,40 @@
+/*
+ * cpim-header-p.h
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _CPIM_HEADER_P_H_
+#define _CPIM_HEADER_P_H_
+
+#include "cpim-header.h"
+
+// =============================================================================
+
+namespace LinphonePrivate {
+ namespace Cpim {
+ class HeaderPrivate : public ObjectPrivate {
+ public:
+ virtual ~HeaderPrivate () = default;
+
+ private:
+ std::string value;
+
+ L_DECLARE_PUBLIC(Header);
+ };
+ }
+}
+
+#endif // ifndef _CPIM_HEADER_P_H_
diff --git a/src/cpim/header/cpim-header.cpp b/src/cpim/header/cpim-header.cpp
new file mode 100644
index 000000000..694b9ffd1
--- /dev/null
+++ b/src/cpim/header/cpim-header.cpp
@@ -0,0 +1,45 @@
+/*
+ * cpim-header.cpp
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "cpim-header-p.h"
+
+#include "cpim-header.h"
+
+using namespace std;
+
+using namespace LinphonePrivate;
+
+// =============================================================================
+
+Cpim::Header::Header (HeaderPrivate &p) : Object(p) {}
+
+string Cpim::Header::getValue () const {
+ L_D(const Header);
+ return d->value;
+}
+
+bool Cpim::Header::setValue (const string &value) {
+ L_D(Header);
+ d->value = value;
+ return true;
+}
+
+string Cpim::Header::asString () const {
+ L_D(const Header);
+ return getName() + ": " + d->value + "\r\n";
+}
diff --git a/src/cpim/header/cpim-header.h b/src/cpim/header/cpim-header.h
new file mode 100644
index 000000000..3dbcb2fbe
--- /dev/null
+++ b/src/cpim/header/cpim-header.h
@@ -0,0 +1,55 @@
+/*
+ * cpim-header.h
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _CPIM_HEADER_H_
+#define _CPIM_HEADER_H_
+
+#include
+
+#include "object/object.h"
+
+// =============================================================================
+
+namespace LinphonePrivate {
+ namespace Cpim {
+ class HeaderPrivate;
+
+ class Header : public Object {
+ public:
+ virtual ~Header () = default;
+
+ virtual std::string getName () const = 0;
+
+ std::string getValue () const;
+ virtual bool setValue (const std::string &value);
+
+ virtual bool isValid () const = 0;
+
+ virtual std::string asString () const;
+
+ protected:
+ explicit Header (HeaderPrivate &p);
+
+ private:
+ L_DECLARE_PRIVATE(Header);
+ L_DISABLE_COPY(Header);
+ };
+ }
+}
+
+#endif // ifndef _CPIM_HEADER_H_
diff --git a/src/cpim/message/cpim-message.cpp b/src/cpim/message/cpim-message.cpp
new file mode 100644
index 000000000..af1e06209
--- /dev/null
+++ b/src/cpim/message/cpim-message.cpp
@@ -0,0 +1,140 @@
+/*
+ * cpim-message.cpp
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+
+#include "cpim/parser/cpim-parser.h"
+#include "utils/utils.h"
+
+#include "cpim-message.h"
+
+using namespace std;
+
+using namespace LinphonePrivate;
+
+// =============================================================================
+
+class Cpim::MessagePrivate : public ObjectPrivate {
+public:
+ typedef list > PrivHeaderList;
+
+ shared_ptr cpimHeaders = make_shared();
+ shared_ptr messageHeaders = make_shared();
+ string content;
+};
+
+Cpim::Message::Message () : Object(*new MessagePrivate) {}
+
+// -----------------------------------------------------------------------------
+
+Cpim::Message::HeaderList Cpim::Message::getCpimHeaders () const {
+ L_D(const Message);
+ return d->cpimHeaders;
+}
+
+bool Cpim::Message::addCpimHeader (const Header &cpimHeader) {
+ L_D(Message);
+
+ if (!cpimHeader.isValid())
+ return false;
+
+ d->cpimHeaders->push_back(Parser::getInstance()->cloneHeader(cpimHeader));
+ return true;
+}
+
+void Cpim::Message::removeCpimHeader (const Header &cpimHeader) {
+ L_D(Message);
+ d->cpimHeaders->remove_if([&cpimHeader](const shared_ptr &header) {
+ return cpimHeader.getName() == header->getName() && cpimHeader.getValue() == header->getValue();
+ });
+}
+
+// -----------------------------------------------------------------------------
+
+Cpim::Message::HeaderList Cpim::Message::getMessageHeaders () const {
+ L_D(const Message);
+ return d->messageHeaders;
+}
+
+bool Cpim::Message::addMessageHeader (const Header &messageHeader) {
+ L_D(Message);
+
+ if (!messageHeader.isValid())
+ return false;
+
+ d->messageHeaders->push_back(Parser::getInstance()->cloneHeader(messageHeader));
+ return true;
+}
+
+void Cpim::Message::removeMessageHeader (const Header &messageHeader) {
+ L_D(Message);
+ d->messageHeaders->remove_if([&messageHeader](const shared_ptr &header) {
+ return messageHeader.getName() == header->getName() && messageHeader.getValue() == header->getValue();
+ });
+}
+
+// -----------------------------------------------------------------------------
+
+string Cpim::Message::getContent () const {
+ L_D(const Message);
+ return d->content;
+}
+
+bool Cpim::Message::setContent (const string &content) {
+ L_D(Message);
+ d->content = content;
+ return true;
+}
+
+// -----------------------------------------------------------------------------
+
+bool Cpim::Message::isValid () const {
+ L_D(const Message);
+
+ return find_if(d->cpimHeaders->cbegin(), d->cpimHeaders->cend(),
+ [](const shared_ptr &header) {
+ return Utils::iequals(header->getName(), "content-type") && header->getValue() == "Message/CPIM";
+ }) != d->cpimHeaders->cend();
+}
+
+// -----------------------------------------------------------------------------
+
+string Cpim::Message::asString () const {
+ L_D(const Message);
+
+ string output;
+ for (const auto &cpimHeader : *d->cpimHeaders)
+ output += cpimHeader->asString();
+
+ output += "\r\n";
+
+ for (const auto &messageHeader : *d->messageHeaders)
+ output += messageHeader->asString();
+
+ output += "\r\n";
+ output += ""; // TODO: Headers MIME.
+ output += getContent();
+
+ return output;
+}
+
+// -----------------------------------------------------------------------------
+
+shared_ptr Cpim::Message::createFromString (const string &str) {
+ return Parser::getInstance()->parseMessage(str);
+}
diff --git a/src/cpim/message/cpim-message.h b/src/cpim/message/cpim-message.h
new file mode 100644
index 000000000..2620fe336
--- /dev/null
+++ b/src/cpim/message/cpim-message.h
@@ -0,0 +1,61 @@
+/*
+ * cpim-message.h
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _CPIM_MESSAGE_H_
+#define _CPIM_MESSAGE_H_
+
+#include "cpim/header/cpim-core-headers.h"
+#include "cpim/header/cpim-generic-header.h"
+
+// =============================================================================
+
+namespace LinphonePrivate {
+ namespace Cpim {
+ class MessagePrivate;
+
+ class Message : public Object {
+ public:
+ Message ();
+
+ typedef std::shared_ptr > > HeaderList;
+
+ HeaderList getCpimHeaders () const;
+ bool addCpimHeader (const Header &cpimHeader);
+ void removeCpimHeader (const Header &cpimHeader);
+
+ HeaderList getMessageHeaders () const;
+ bool addMessageHeader (const Header &messageHeader);
+ void removeMessageHeader (const Header &messageHeader);
+
+ std::string getContent () const;
+ bool setContent (const std::string &content);
+
+ bool isValid () const;
+
+ std::string asString () const;
+
+ static std::shared_ptr createFromString (const std::string &str);
+
+ private:
+ L_DECLARE_PRIVATE(Message);
+ L_DISABLE_COPY(Message);
+ };
+ }
+}
+
+#endif // ifndef _CPIM_MESSAGE_H_
diff --git a/src/cpim/parser/cpim-grammar.cpp b/src/cpim/parser/cpim-grammar.cpp
new file mode 100644
index 000000000..91eab3d30
--- /dev/null
+++ b/src/cpim/parser/cpim-grammar.cpp
@@ -0,0 +1,207 @@
+/*
+ * cpim-grammar.cpp
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "cpim-grammar.h"
+
+// =============================================================================
+
+namespace LinphonePrivate {
+ static const char *grammar =
+// See: https://tools.ietf.org/html/rfc3862
+R"==GRAMMAR==(
+Message = Headers CRLF Headers CRLF
+
+Headers = *Header
+Header = Header-name ":" Header-parameters SP Header-value CRLF
+
+Header-name = [ Name-prefix "." ] Name
+Name-prefix = Name
+
+Header-parameters = *( ";" Parameter )
+
+Parameter = Lang-param / Ext-param
+Lang-param = "lang=" Language-tag
+Ext-param = Param-name "=" Param-value
+Param-name = Name
+Param-value = Token / Number / String
+
+Header-value = *HEADERCHAR
+
+From-header = %d70.114.111.109 ": " From-header-value
+From-header-value = [ Formal-name ] "<" URI ">"
+
+To-header = %d84.111 ": " To-header-value
+To-header-value = [ Formal-name ] "<" URI ">"
+
+DateTime-header = %d68.97.116.101.84.105.109.101 ": " DateTime-header-value
+DateTime-header-value = date-time
+
+cc-header = %d99.99 ": " cc-header-value
+cc-header-value = [ Formal-name ] "<" URI ">"
+
+Subject-header = %d83.117.98.106.101.99.116 ":" Subject-header-value
+Subject-header-value = [ ";" Lang-param ] SP *HEADERCHAR
+
+NS-header = %d78.83 ": " NS-header-value
+NS-header-value = [ Name-prefix SP ] "<" URI ">"
+
+Require-header = %d82.101.113.117.105.114.101 ": " Require-header-value
+Require-header-value = Header-name *( "," Header-name )
+
+Name = 1*NAMECHAR
+Token = 1*TOKENCHAR
+Number = 1*DIGIT
+String = DQUOTE *( Str-char / Escape ) DQUOTE
+Str-char = %x20-21 / %x23-5B / %x5D-7E / UCS-high
+Escape = "\" ( "u" 4(HEXDIG) / "b" / "t" / "n" / "r" / DQUOTE / "'" / "\" )
+
+Formal-name = 1*( Token SP ) / String
+
+HEADERCHAR = UCS-no-CTL / Escape
+
+NAMECHAR = %x21 / %x23-27 / %x2a-2b / %x2d / %x5e-60
+ / %x7c / %x7e / ALPHA / DIGIT
+
+TOKENCHAR = NAMECHAR / "." / UCS-high
+
+UCS-no-CTL = UTF8-no-CTL
+UCS-high = UTF8-multi
+UTF8-no-CTL = %x20-7e / UTF8-multi
+UTF8-multi = %xC0-DF %x80-BF
+ / %xE0-EF %x80-BF %x80-BF
+ / %xF0-F7 %x80-BF %x80-BF %x80-BF
+ / %xF8-FB %x80-BF %x80-BF %x80-BF %x80-BF
+ / %xFC-FD %x80-BF %x80-BF %x80-BF %x80-BF %x80-BF
+)==GRAMMAR=="
+
+// See: https://tools.ietf.org/html/rfc2396
+R"==GRAMMAR==(
+URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+
+hier-part = "//" authority path-abempty
+ / path-absolute
+ / path-rootless
+ / path-empty
+
+URI-reference = URI / relative-ref
+
+absolute-URI = scheme ":" hier-part [ "?" query ]
+
+relative-ref = relative-part [ "?" query ] [ "#" fragment ]
+
+relative-part = "//" authority path-abempty
+ / path-absolute
+ / path-noscheme
+ / path-empty
+
+scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+
+authority = [ userinfo "@" ] host [ ":" port ]
+userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
+host = IP-literal / IPv4address / reg-name
+port = *DIGIT
+
+IP-literal = "[" ( IPv6address / IPvFuture ) "]"
+
+IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
+
+IPv6address = 6( h16 ":" ) ls32
+ / "::" 5( h16 ":" ) ls32
+ / [ h16 ] "::" 4( h16 ":" ) ls32
+ / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
+ / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
+ / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
+ / [ *4( h16 ":" ) h16 ] "::" ls32
+ / [ *5( h16 ":" ) h16 ] "::" h16
+ / [ *6( h16 ":" ) h16 ] "::"
+
+h16 = 1*4HEXDIG
+ls32 = ( h16 ":" h16 ) / IPv4address
+IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
+dec-octet = DIGIT
+ / %x31-39 DIGIT
+ / "1" 2DIGIT
+ / "2" %x30-34 DIGIT
+ / "25" %x30-35
+
+reg-name = *( unreserved / pct-encoded / sub-delims )
+
+path = path-abempty
+ / path-absolute
+ / path-noscheme
+ / path-rootless
+ / path-empty
+
+path-abempty = *( "/" segment )
+path-absolute = "/" [ segment-nz *( "/" segment ) ]
+path-noscheme = segment-nz-nc *( "/" segment )
+path-rootless = segment-nz *( "/" segment )
+path-empty = [pchar]
+
+segment = *pchar
+segment-nz = 1*pchar
+segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" )
+
+pchar = unreserved / pct-encoded / sub-delims / ":" / "@" / "\,"
+
+query = *( pchar / "/" / "?" )
+
+fragment = *( pchar / "/" / "?" )
+
+pct-encoded = "%" HEXDIG HEXDIG
+
+unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+reserved = gen-delims / sub-delims
+gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+ / "*" / "+" / "," / ";" / "="
+)==GRAMMAR=="
+
+// See: https://tools.ietf.org/html/rfc3066
+R"==GRAMMAR==(
+Language-Tag = Primary-subtag *( "-" Subtag )
+Primary-subtag = 1*8ALPHA
+Subtag = 1*8(ALPHA / DIGIT)
+)==GRAMMAR=="
+
+// See: https://tools.ietf.org/html/rfc3339
+R"==GRAMMAR==(
+date-fullyear = 4DIGIT
+date-month = 2DIGIT
+date-mday = 2DIGIT
+
+time-hour = 2DIGIT
+time-minute = 2DIGIT
+time-second = 2DIGIT
+
+time-secfrac = "." 1*DIGIT
+time-numoffset = ( "+" / "-" ) time-hour ":" time-minute
+time-offset = "Z" / time-numoffset
+
+partial-time = time-hour ":" time-minute ":" time-second [ time-secfrac ]
+
+full-date = date-fullyear "-" date-month "-" date-mday
+full-time = partial-time time-offset
+
+date-time = full-date "T" full-time
+)==GRAMMAR==";
+}
+
+const char *LinphonePrivate::Cpim::getGrammar () {
+ return grammar;
+}
diff --git a/src/cpim/parser/cpim-grammar.h b/src/cpim/parser/cpim-grammar.h
new file mode 100644
index 000000000..45887a661
--- /dev/null
+++ b/src/cpim/parser/cpim-grammar.h
@@ -0,0 +1,30 @@
+/*
+ * cpim-grammar.h
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _CPIM_GRAMMAR_H_
+#define _CPIM_GRAMMAR_H_
+
+// =============================================================================
+
+namespace LinphonePrivate {
+ namespace Cpim {
+ const char *getGrammar ();
+ }
+}
+
+#endif // ifndef _CPIM_GRAMMAR_H_
diff --git a/src/cpim/parser/cpim-parser.cpp b/src/cpim/parser/cpim-parser.cpp
new file mode 100644
index 000000000..1235b0896
--- /dev/null
+++ b/src/cpim/parser/cpim-parser.cpp
@@ -0,0 +1,413 @@
+/*
+ * cpim-parser.cpp
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include
+
+#include
+#include
+
+#include "linphone/core.h"
+
+#include "cpim-grammar.h"
+#include "utils/utils.h"
+
+#include "cpim-parser.h"
+
+using namespace std;
+
+using namespace LinphonePrivate;
+
+// =============================================================================
+
+namespace LinphonePrivate {
+ namespace Cpim {
+ class Node {
+ public:
+ virtual ~Node () = default;
+ };
+
+ class HeaderNode : public Node {
+ public:
+ HeaderNode () = default;
+
+ explicit HeaderNode (const Header &header) {
+ mName = header.getName();
+ mValue = header.getValue();
+
+ // Generic header.
+ const GenericHeader *genericHeader = dynamic_cast(&header);
+ if (genericHeader) {
+ for (const auto ¶meter : *genericHeader->getParameters())
+ mParameters += ";" + parameter.first + "=" + parameter.second;
+ return;
+ }
+
+ // Subject header.
+ const SubjectHeader *subjectHeader = dynamic_cast(&header);
+ if (subjectHeader) {
+ const string language = subjectHeader->getLanguage();
+ if (!language.empty())
+ mParameters = ";lang=" + language;
+ }
+ }
+
+ string getName () const {
+ return mName;
+ }
+
+ void setName (const string &name) {
+ mName = name;
+ }
+
+ string getParameters () const {
+ return mParameters;
+ }
+
+ void setParameters (const string ¶meters) {
+ mParameters = parameters;
+ }
+
+ string getValue () const {
+ return mValue;
+ }
+
+ void setValue (const string &value) {
+ mValue = value;
+ }
+
+ shared_ptr createHeader (bool force) const;
+
+ private:
+ template
+ shared_ptr createCoreHeader (bool force) const {
+ shared_ptr header = make_shared();
+ if (force)
+ header->force(mValue);
+ else if (!header->setValue(mValue)) {
+ ms_fatal("Unable to set value on core header: `%s` => `%s`.", mName.c_str(), mValue.c_str());
+ return nullptr;
+ }
+
+ return header;
+ }
+
+ string mValue;
+ string mName;
+ string mParameters;
+ };
+
+ template<>
+ shared_ptr HeaderNode::createCoreHeader(bool force) const {
+ shared_ptr header = make_shared();
+ const string language = mParameters.length() >= 6 ? mParameters.substr(6) : "";
+
+ if (force)
+ header->force(mValue, language);
+ else if (!header->setValue(mValue) || (!language.empty() && !header->setLanguage(language))) {
+ ms_fatal("Unable to set value on subject header: `%s` => `%s`, `%s`.", mName.c_str(), mValue.c_str(), language.c_str());
+ return nullptr;
+ }
+
+ return header;
+ }
+
+ shared_ptr HeaderNode::createHeader (bool force = false) const {
+ static const unordered_map(HeaderNode::*)(bool)const> reservedHandlers = {
+ { "From", &HeaderNode::createCoreHeader },
+ { "To", &HeaderNode::createCoreHeader },
+ { "cc", &HeaderNode::createCoreHeader },
+ { "DateTime", &HeaderNode::createCoreHeader },
+ { "Subject", &HeaderNode::createCoreHeader },
+ { "NS", &HeaderNode::createCoreHeader },
+ { "Require", &HeaderNode::createCoreHeader }
+ };
+
+ // Core Header.
+ const auto it = reservedHandlers.find(mName);
+ if (it != reservedHandlers.cend())
+ return (this->*it->second)(force);
+
+ // Generic Header
+ shared_ptr genericHeader = make_shared();
+ genericHeader->force(mName, mValue, mParameters);
+ return genericHeader;
+ }
+
+ // -------------------------------------------------------------------------
+
+ class ListHeaderNode :
+ public Node,
+ public list > {};
+
+ // -------------------------------------------------------------------------
+
+ class MessageNode : public Node {
+ public:
+ void addHeaders (const shared_ptr &headers) {
+ mHeaders->push_back(headers);
+ }
+
+ // Warning: Call this function one time!
+ shared_ptr createMessage () const {
+ size_t size = mHeaders->size();
+ if (size != 2) {
+ ms_fatal("Bad headers lists size.");
+ return nullptr;
+ }
+
+ const shared_ptr message = make_shared();
+ const shared_ptr cpimHeaders = mHeaders->front();
+
+ if (find_if(cpimHeaders->cbegin(), cpimHeaders->cend(),
+ [](const shared_ptr &headerNode) {
+ return Utils::iequals(headerNode->getName(), "content-type") && headerNode->getValue() == "Message/CPIM";
+ }) == cpimHeaders->cend()) {
+ ms_fatal("No MIME `Content-Type` found!");
+ return nullptr;
+ }
+
+ // Add MIME headers.
+ for (const auto &headerNode : *cpimHeaders) {
+ const shared_ptr header = headerNode->createHeader();
+ if (!header || !message->addCpimHeader(*header))
+ return nullptr;
+ }
+
+ // Add message headers.
+ for (const auto &headerNode : *mHeaders->back()) {
+ const shared_ptr header = headerNode->createHeader();
+ if (!header || !message->addMessageHeader(*header))
+ return nullptr;
+ }
+
+ return message;
+ }
+
+ private:
+ shared_ptr > > mHeaders = make_shared > >();
+ };
+ }
+}
+
+// -----------------------------------------------------------------------------
+
+class Cpim::ParserPrivate : public ObjectPrivate {
+public:
+ shared_ptr grammar;
+};
+
+Cpim::Parser::Parser () : Singleton(*new ParserPrivate) {
+ L_D(Parser);
+
+ belr::ABNFGrammarBuilder builder;
+
+ d->grammar = builder.createFromAbnf(getGrammar(), make_shared());
+ if (!d->grammar)
+ ms_fatal("Unable to build CPIM grammar.");
+}
+
+// -----------------------------------------------------------------------------
+
+shared_ptr Cpim::Parser::parseMessage (const string &input) {
+ L_D(Parser);
+
+ typedef void (list >::*pushPtr)(const shared_ptr &value);
+
+ belr::Parser > parser(d->grammar);
+ parser.setHandler(
+ "Message", belr::make_fn(make_shared )
+ )->setCollector(
+ "Headers", belr::make_sfn(&MessageNode::addHeaders)
+ );
+
+ parser.setHandler(
+ "Headers", belr::make_fn(make_shared )
+ )->setCollector(
+ "Header", belr::make_sfn(static_cast(&ListHeaderNode::push_back))
+ );
+
+ parser.setHandler(
+ "Header", belr::make_fn(make_shared )
+ )->setCollector(
+ "Header-name", belr::make_sfn(&HeaderNode::setName)
+ )->setCollector(
+ "Header-value", belr::make_sfn(&HeaderNode::setValue)
+ )->setCollector(
+ "Header-parameters", belr::make_sfn(&HeaderNode::setParameters)
+ );
+
+ size_t parsedSize;
+ shared_ptr node = parser.parseInput("Message", input, &parsedSize);
+ if (!node) {
+ ms_fatal("Unable to parse message.");
+ return nullptr;
+ }
+
+ shared_ptr messageNode = dynamic_pointer_cast(node);
+ if (!messageNode) {
+ ms_fatal("Unable to cast belr result to message node.");
+ return nullptr;
+ }
+
+ shared_ptr message = messageNode->createMessage();
+ if (message)
+ message->setContent(input.substr(parsedSize));
+ return message;
+}
+
+// -----------------------------------------------------------------------------
+
+shared_ptr Cpim::Parser::cloneHeader (const Header &header) {
+ return HeaderNode(header).createHeader(true);
+}
+
+// -----------------------------------------------------------------------------
+
+class EmptyObject {};
+
+inline bool headerIsValid (const shared_ptr &grammar, const string &input) {
+ belr::Parser > parser(grammar);
+ parser.setHandler(
+ "Header", belr::make_fn(make_shared )
+ );
+
+ size_t parsedSize;
+ shared_ptr node = parser.parseInput("Header", input, &parsedSize);
+ return node && parsedSize == input.length();
+}
+
+bool Cpim::Parser::headerNameIsValid (const string &headerName) const {
+ L_D(const Parser);
+ return ::headerIsValid(d->grammar, headerName + ": value\r\n");
+}
+
+bool Cpim::Parser::headerValueIsValid (const string &headerValue) const {
+ L_D(const Parser);
+ return ::headerIsValid(d->grammar, "key: " + headerValue + "\r\n");
+}
+
+bool Cpim::Parser::headerParameterIsValid (const std::string &headerParameter) const {
+ L_D(const Parser);
+ return ::headerIsValid(d->grammar, "key:;" + headerParameter + " value\r\n");
+}
+
+// -----------------------------------------------------------------------------
+
+inline bool coreHeaderIsValid (
+ const shared_ptr &grammar,
+ const string &headerName,
+ const string &headerValue,
+ const string &headerParams = string()
+) {
+ const string mainRule = headerName + "-header";
+
+ belr::Parser > parser(grammar);
+ parser.setHandler(
+ mainRule, belr::make_fn(make_shared )
+ );
+
+ const string input = headerName + ":" + headerParams + " " + headerValue;
+
+ size_t parsedSize;
+ shared_ptr node = parser.parseInput(mainRule, input, &parsedSize);
+ return node && parsedSize == input.length();
+}
+
+template<>
+bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const {
+ L_D(const Parser);
+ return ::coreHeaderIsValid(d->grammar, "From", headerValue);
+}
+
+template<>
+bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const {
+ L_D(const Parser);
+ return ::coreHeaderIsValid(d->grammar, "To", headerValue);
+}
+
+template<>
+bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const {
+ L_D(const Parser);
+ return ::coreHeaderIsValid(d->grammar, "cc", headerValue);
+}
+
+template<>
+bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const {
+ static const int daysInMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+ L_D(const Parser);
+ if (!::coreHeaderIsValid(d->grammar, "DateTime", headerValue))
+ return false;
+
+ // Check date.
+ const int year = stoi(headerValue.substr(0, 4));
+ const bool isLeapYear = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
+
+ const int month = stoi(headerValue.substr(5, 2));
+ if (month < 1 || month > 12)
+ return false;
+
+ const int day = stoi(headerValue.substr(8, 2));
+ if (day < 1 || (month == 2 && isLeapYear ? day > 29 : day > daysInMonth[month - 1]))
+ return false;
+
+ // Check time.
+ if (
+ stoi(headerValue.substr(11, 2)) > 24 ||
+ stoi(headerValue.substr(14, 2)) > 59 ||
+ stoi(headerValue.substr(17, 2)) > 60
+ )
+ return false;
+
+ // Check num offset.
+ if (headerValue.back() != 'Z') {
+ size_t length = headerValue.length();
+ if (
+ stoi(headerValue.substr(length - 5, 2)) > 24 ||
+ stoi(headerValue.substr(length - 2, 2)) > 59
+ )
+ return false;
+ }
+
+ return true;
+}
+
+template<>
+bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const {
+ L_D(const Parser);
+ return ::coreHeaderIsValid(d->grammar, "Subject", headerValue);
+}
+
+template<>
+bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const {
+ L_D(const Parser);
+ return ::coreHeaderIsValid(d->grammar, "NS", headerValue);
+}
+
+template<>
+bool Cpim::Parser::coreHeaderIsValid(const string &headerValue) const {
+ L_D(const Parser);
+ return ::coreHeaderIsValid(d->grammar, "Require", headerValue);
+}
+
+// -----------------------------------------------------------------------------
+
+bool Cpim::Parser::subjectHeaderLanguageIsValid (const string &language) const {
+ L_D(const Parser);
+ return ::coreHeaderIsValid(d->grammar, "Subject", "SubjectValue", ";lang=" + language);
+}
diff --git a/src/cpim/parser/cpim-parser.h b/src/cpim/parser/cpim-parser.h
new file mode 100644
index 000000000..397e9e3b7
--- /dev/null
+++ b/src/cpim/parser/cpim-parser.h
@@ -0,0 +1,82 @@
+/*
+ * cpim-parser.h
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _CPIM_PARSER_H_
+#define _CPIM_PARSER_H_
+
+#include "cpim/message/cpim-message.h"
+#include "object/singleton.h"
+
+// =============================================================================
+
+namespace LinphonePrivate {
+ namespace Cpim {
+ class ParserPrivate;
+
+ class Parser : public Singleton {
+ friend class Singleton;
+
+ public:
+ std::shared_ptr parseMessage (const std::string &input);
+
+ std::shared_ptr cloneHeader (const Header &header);
+
+ bool headerNameIsValid (const std::string &headerName) const;
+ bool headerValueIsValid (const std::string &headerValue) const;
+ bool headerParameterIsValid (const std::string &headerParameter) const;
+
+ template
+ bool coreHeaderIsValid (const std::string &headerValue) const {
+ return false;
+ }
+
+ bool subjectHeaderLanguageIsValid (const std::string &language) const;
+
+ private:
+ Parser ();
+
+ L_DECLARE_PRIVATE(Parser);
+ L_DISABLE_COPY(Parser);
+ };
+
+ // -------------------------------------------------------------------------
+
+ template<>
+ bool Parser::coreHeaderIsValid(const std::string &headerValue) const;
+
+ template<>
+ bool Parser::coreHeaderIsValid(const std::string &headerValue) const;
+
+ template<>
+ bool Parser::coreHeaderIsValid(const std::string &headerValue) const;
+
+ template<>
+ bool Parser::coreHeaderIsValid(const std::string &headerValue) const;
+
+ template<>
+ bool Parser::coreHeaderIsValid(const std::string &headerValue) const;
+
+ template<>
+ bool Parser::coreHeaderIsValid(const std::string &headerValue) const;
+
+ template<>
+ bool Parser::coreHeaderIsValid(const std::string &headerValue) const;
+ }
+}
+
+#endif // ifndef _CPIM_PARSER_H_
diff --git a/src/object/object.h b/src/object/object.h
index 9b387e804..76ae2d67f 100644
--- a/src/object/object.h
+++ b/src/object/object.h
@@ -44,9 +44,8 @@ namespace LinphonePrivate {
}
protected:
- explicit Object (ObjectPrivate *objectPrivate) : mPrivate(objectPrivate) {
- if (mPrivate)
- mPrivate->mPublic = this;
+ explicit Object (ObjectPrivate &p) : mPrivate(&p) {
+ mPrivate->mPublic = this;
}
ObjectPrivate *mPrivate = nullptr;
diff --git a/src/object/singleton.h b/src/object/singleton.h
index 5123e0e73..6acad3c6d 100644
--- a/src/object/singleton.h
+++ b/src/object/singleton.h
@@ -27,25 +27,25 @@ namespace LinphonePrivate {
template
class Singleton : public Object {
public:
- static Singleton *getInstance () {
+ virtual ~Singleton () = default;
+
+ static T *getInstance () {
if (!mInstance)
- mInstance = new Singleton();
+ mInstance = new T();
return mInstance;
}
- virtual ~Singleton () = default;
-
protected:
- explicit Singleton (ObjectPrivate *objectPrivate = nullptr) : Object(objectPrivate) {}
+ explicit Singleton (ObjectPrivate &p) : Object(p) {}
private:
- static Singleton *mInstance;
+ static T *mInstance;
L_DISABLE_COPY(Singleton);
};
template
- Singleton *Singleton::mInstance = nullptr;
+ T *Singleton::mInstance = nullptr;
}
#endif // ifndef _SINGLETON_H_
diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp
new file mode 100644
index 000000000..9e32a883a
--- /dev/null
+++ b/src/utils/utils.cpp
@@ -0,0 +1,49 @@
+/*
+ * utils.cpp
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "utils.h"
+
+using namespace std;
+
+using namespace LinphonePrivate;
+
+// =============================================================================
+
+bool Utils::iequals (const string &a, const string &b) {
+ size_t size = a.size();
+ if (b.size() != size)
+ return false;
+
+ for (size_t i = 0; i < size; ++i) {
+ if (tolower(a[i]) != tolower(b[i]))
+ return false;
+ }
+
+ return true;
+}
+
+vector Utils::split (const string &str, const string &delimiter) {
+ vector out;
+
+ size_t pos = 0, oldPos = 0;
+ for (; (pos = str.find(delimiter, pos)) != string::npos; oldPos = pos + 1, pos = oldPos)
+ out.push_back(str.substr(oldPos, pos - oldPos));
+ out.push_back(str.substr(oldPos));
+
+ return out;
+}
diff --git a/src/utils/utils.h b/src/utils/utils.h
new file mode 100644
index 000000000..1fc468add
--- /dev/null
+++ b/src/utils/utils.h
@@ -0,0 +1,39 @@
+/*
+ * utils.h
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#ifndef _UTILS_H_
+#define _UTILS_H_
+
+#include
+#include
+
+// =============================================================================
+
+namespace LinphonePrivate {
+ namespace Utils {
+ bool iequals (const std::string &a, const std::string &b);
+
+ std::vector split (const std::string &str, const std::string &delimiter);
+
+ inline std::vector split (const std::string &str, char delimiter) {
+ return split(str, std::string(1, delimiter));
+ }
+ }
+}
+
+#endif // ifndef _UTILS_H_
diff --git a/tester/CMakeLists.txt b/tester/CMakeLists.txt
index 45e83bb6c..099c37c2b 100644
--- a/tester/CMakeLists.txt
+++ b/tester/CMakeLists.txt
@@ -192,6 +192,10 @@ set(SOURCE_FILES_C
video_tester.c
)
+set(SOURCE_FILES_CXX
+ cpim_tester.cpp
+)
+
set(SOURCE_FILES_OBJC )
if(APPLE)
if (IOS)
@@ -200,6 +204,7 @@ if(APPLE)
endif()
bc_apply_compile_flags(SOURCE_FILES_C STRICT_OPTIONS_CPP STRICT_OPTIONS_C)
+bc_apply_compile_flags(SOURCE_FILES_C_CXX STRICT_OPTIONS_CPP STRICT_OPTIONS_CXX)
bc_apply_compile_flags(SOURCE_FILES_OBJC STRICT_OPTIONS_CPP STRICT_OPTIONS_OBJC)
if(MSVC)
@@ -222,7 +227,7 @@ endif()
# on mobile platforms, we compile the tester as a library so that we can link with it directly from native applications
if(ANDROID OR IOS)
- add_library(linphonetester SHARED ${SOURCE_FILES_C})
+ add_library(linphonetester SHARED ${SOURCE_FILES_C} ${SOURCE_FILES_CXX})
target_include_directories(linphonetester PUBLIC ${BCTOOLBOX_TESTER_INCLUDE_DIRS})
target_link_libraries(linphonetester ${LINPHONE_LIBS_FOR_TOOLS} ${OTHER_LIBS_FOR_TESTER})
if(IOS)
@@ -243,7 +248,7 @@ if(ANDROID OR IOS)
PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
)
elseif(CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
- add_library(linphone_tester_static STATIC ${SOURCE_FILES_C})
+ add_library(linphone_tester_static STATIC ${SOURCE_FILES_C} ${SOURCE_FILES_CXX})
target_include_directories(linphone_tester_static PUBLIC ${BCTOOLBOX_TESTER_INCLUDE_DIRS})
target_link_libraries(linphone_tester_static ${LINPHONE_LIBS_FOR_TOOLS} ${OTHER_LIBS_FOR_TESTER})
@@ -277,9 +282,9 @@ endif()
if (NOT ANDROID AND NOT CMAKE_SYSTEM_NAME STREQUAL "WindowsStore")
if(IOS)
set_source_files_properties(${IOS_RESOURCES_FILES} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
- add_executable(liblinphone_tester MACOSX_BUNDLE ${IOS_RESOURCES_FILES} ${SOURCE_FILES_C} ${SOURCE_FILES_OBJC})
+ add_executable(liblinphone_tester MACOSX_BUNDLE ${IOS_RESOURCES_FILES} ${SOURCE_FILES_C} ${SOURCE_FILES_CXX} ${SOURCE_FILES_OBJC})
else()
- add_executable(liblinphone_tester ${SOURCE_FILES_C} ${SOURCE_FILES_OBJC})
+ add_executable(liblinphone_tester ${SOURCE_FILES_C} ${SOURCE_FILES_CXX} ${SOURCE_FILES_OBJC})
endif()
set_target_properties(liblinphone_tester PROPERTIES LINK_FLAGS "${LINPHONE_LDFLAGS}")
set_target_properties(liblinphone_tester PROPERTIES LINKER_LANGUAGE CXX)
diff --git a/tester/call_multi_tester.c b/tester/call_multi_tester.c
index 2b6ac3cbc..11c0718a5 100644
--- a/tester/call_multi_tester.c
+++ b/tester/call_multi_tester.c
@@ -254,7 +254,7 @@ static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManag
bool_t is_remote_conf;
bool_t focus_is_up = (focus && ((LinphoneConferenceServer *)focus)->reg_state == LinphoneRegistrationOk);
bctbx_list_t* lcs=bctbx_list_append(NULL,marie->lc);
-
+
lcs=bctbx_list_append(lcs,pauline->lc);
lcs=bctbx_list_append(lcs,laure->lc);
if (focus) lcs=bctbx_list_append(lcs,focus->lc);
@@ -263,7 +263,7 @@ static void simple_conference_base(LinphoneCoreManager* marie, LinphoneCoreManag
if(is_remote_conf) BC_ASSERT_PTR_NOT_NULL(focus);
if (!BC_ASSERT_TRUE(call(marie,pauline))) goto end;
-
+
marie_call_pauline=linphone_core_get_current_call(marie->lc);
pauline_called_by_marie=linphone_core_get_current_call(pauline->lc);
BC_ASSERT_TRUE(pause_call_1(marie,marie_call_pauline,pauline,pauline_called_by_marie));
@@ -391,40 +391,40 @@ static void simple_conference_from_scratch(void){
LinphoneCall *pauline_call, *laure_call;
bctbx_list_t *participants = NULL;
bctbx_list_t *lcs = NULL;
-
+
lcs = bctbx_list_append(lcs, marie->lc);
lcs = bctbx_list_append(lcs, pauline->lc);
lcs = bctbx_list_append(lcs, laure->lc);
-
+
/*marie creates the conference*/
conf_params = linphone_core_create_conference_params(marie->lc);
linphone_conference_params_enable_video(conf_params, FALSE);
conf = linphone_core_create_conference_with_params(marie->lc, conf_params);
linphone_conference_params_unref(conf_params);
-
+
participants = bctbx_list_append(participants, pauline->identity);
participants = bctbx_list_append(participants, laure->identity);
-
+
linphone_conference_invite_participants(conf, participants, NULL);
-
+
BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallOutgoingProgress,2,2000));
BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallIncomingReceived,1,10000));
BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallIncomingReceived,1,10000));
-
+
pauline_call = linphone_core_get_current_call(pauline->lc);
laure_call = linphone_core_get_current_call(laure->lc);
-
+
BC_ASSERT_PTR_NOT_NULL(pauline_call);
BC_ASSERT_PTR_NOT_NULL(laure_call);
-
+
if (pauline_call && laure_call){
const bctbx_list_t *marie_calls, *it;
linphone_call_accept(pauline_call);
linphone_call_accept(laure_call);
-
+
BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallConnected,2,10000));
BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallStreamsRunning,2,3000));
-
+
/*make sure that the two calls from Marie's standpoint are in conference*/
marie_calls = linphone_core_get_calls(marie->lc);
BC_ASSERT_EQUAL((int)bctbx_list_size(marie_calls), 2, int, "%i");
@@ -433,12 +433,12 @@ static void simple_conference_from_scratch(void){
}
/*wait a bit for the conference audio processing to run, despite we do not test it for the moment*/
wait_for_list(lcs,NULL,0,5000);
-
+
linphone_core_terminate_conference(marie->lc);
BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,2,5000));
BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,1,10000));
BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallEnd,1,10000));
-
+
BC_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallReleased,2,1000));
BC_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallReleased,1,1000));
BC_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallReleased,1,1000));
@@ -447,7 +447,7 @@ static void simple_conference_from_scratch(void){
linphone_core_manager_destroy(marie);
linphone_core_manager_destroy(pauline);
linphone_core_manager_destroy(laure);
-
+
bctbx_list_free(participants);
bctbx_list_free(lcs);
}
@@ -691,7 +691,7 @@ static void call_transfer_existing_call(bool_t outgoing_call) {
goto end;
}
}
-
+
marie_call_laure=linphone_core_get_current_call(marie->lc);
laure_called_by_marie=linphone_core_get_current_call(laure->lc);
diff --git a/tester/cpim_tester.cpp b/tester/cpim_tester.cpp
new file mode 100644
index 000000000..27df3690c
--- /dev/null
+++ b/tester/cpim_tester.cpp
@@ -0,0 +1,379 @@
+/*
+ * liblinphone_tester - liblinphone test suite
+ * Copyright (C) 2017 Belledonne Communications SARL
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "cpim/cpim.h"
+
+#include "liblinphone_tester.h"
+
+using namespace std;
+
+using namespace LinphonePrivate;
+
+// =============================================================================
+
+static void parse_minimal_message (void) {
+ const string str = "Content-type: Message/CPIM\r\n"
+ "\r\n"
+ "Content-Type: text/plain; charset=utf-8\r\n"
+ "\r\n";
+
+ shared_ptr 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());
+}
+
+static void set_generic_header_name (void) {
+ const list > entries = {
+ { "toto", true },
+ { "george.abitbol", true },
+ { "tata/titi", false },
+ { "hey ho", false },
+ { " fail", false },
+ { "fail2 ", false },
+
+ // Reserved.
+ { "From", false },
+ { "To", false },
+ { "cc", false },
+ { "DateTime", false },
+ { "Subject", false },
+ { "NS", false },
+ { "Require", false },
+
+ // Case sensitivity.
+ { "FROM", true },
+ { "to", true },
+ { "cC", true },
+ { "Datetime", true },
+ { "SuBject", true },
+ { "nS", true },
+ { "requirE", true }
+ };
+
+ for (const auto &entry : entries) {
+ Cpim::GenericHeader genericHeader;
+
+ const bool result = genericHeader.setName(entry.first);
+ BC_ASSERT_EQUAL(result, entry.second, bool, "%d");
+
+ const string name = genericHeader.getName();
+
+ if (result)
+ BC_ASSERT_STRING_EQUAL(name.c_str(), entry.first.c_str());
+ else
+ BC_ASSERT_STRING_EQUAL(name.c_str(), "");
+ }
+}
+
+static void set_generic_header_value (void) {
+ const list > entries = {
+ { "MyFeatures ", true },
+ { "2000-12-13T13:40:00-08:00", true },
+ { "2000-12-13T13:40:00-08:00", true },
+ { "text/xml; charset=utf-8", true },
+ { "text/xml; charset=ut\r\nf-8", false }
+ };
+
+ for (const auto &entry : entries) {
+ Cpim::GenericHeader genericHeader;
+
+ const bool result = genericHeader.setValue(entry.first);
+ BC_ASSERT_EQUAL(result, entry.second, bool, "%d");
+
+ const string value = genericHeader.getValue();
+
+ if (result)
+ BC_ASSERT_STRING_EQUAL(value.c_str(), entry.first.c_str());
+ else
+ BC_ASSERT_STRING_EQUAL(value.c_str(), "");
+ }
+}
+
+static void check_core_header_names (void) {
+ const list, string> > entries = {
+ { make_shared(), "From" },
+ { make_shared(), "To" },
+ { make_shared(), "cc" },
+ { make_shared(), "DateTime" },
+ { make_shared(), "Subject" },
+ { make_shared(), "NS" },
+ { make_shared(), "Require" }
+ };
+
+ for (const auto &entry : entries) {
+ const string name = entry.first->getName();
+ BC_ASSERT_STRING_EQUAL(name.c_str(), entry.second.c_str());
+ }
+}
+
+static void set_core_header_values (void) {
+ const list, list > > > entries = {
+ { make_shared(), {
+ { "Winnie the Pooh ", true },
+ { "", true },
+ { "", true },
+ { "toto", false }
+ } },
+ { make_shared(), {
+ { "", true },
+ { "toto", false },
+ { "", true },
+ { "", true }
+ } },
+ { make_shared(), {
+ { "", true },
+ { "", true },
+ { "", true },
+ { "toto", false }
+ } },
+ { make_shared(), {
+ { "abcd", false },
+ { "1985-04-12T23:20:50.52Z", true },
+ { "1996-12-19T16:39:57-08:00", true },
+ { "1990-12-31T23:59:60Z", true },
+ { "1990-12-31T15:59:60-08:00", true },
+ { "2001-02-29T10:10:10Z", false },
+ { "2000-02-29T10:10:10Z", true },
+ { "1937-01-01T12:00:27.87+00:20", true },
+ { "1937-01-01T12:00:27.87Z", true },
+ { "1956", false }
+ } },
+ { make_shared(), {
+ { "Eeyore's feeling very depressed today", true },
+ { "🤣", true },
+ { "hello", true }
+ } },
+ { make_shared(), {
+ { "MyAlias ", true },
+ { "What is this? - Barry Burton", false },
+ { "", true },
+ { "(), {
+ { "MyAlias.VitalHeader", true },
+ { "MyAlias.VitalHeader,Test", true },
+ { "MyAlias.VitalHeader,🤣", false }
+ } }
+ };
+
+ for (const auto &entry : entries) {
+ const shared_ptr header = entry.first;
+ string previousValue;
+
+ for (const auto &test : entry.second) {
+ const bool result = header->setValue(test.first);
+ BC_ASSERT_EQUAL(result, test.second, bool, "%d");
+
+ const string value = header->getValue();
+
+ if (result)
+ BC_ASSERT_STRING_EQUAL(value.c_str(), test.first.c_str());
+ else
+ BC_ASSERT_STRING_EQUAL(value.c_str(), previousValue.c_str());
+
+ previousValue = value;
+ }
+ }
+}
+
+static void check_subject_header_language (void) {
+ Cpim::SubjectHeader subjectHeader;
+
+ // Check for not defined language.
+ {
+ const string language = subjectHeader.getLanguage();
+ BC_ASSERT_STRING_EQUAL(language.c_str(), "");
+ }
+
+ // Set valid language.
+ {
+ const string languageToSet = "fr";
+
+ BC_ASSERT_TRUE(subjectHeader.setLanguage(languageToSet));
+ BC_ASSERT_TRUE(languageToSet == subjectHeader.getLanguage());
+
+ const string str = subjectHeader.asString();
+ const string expected = "Subject:;lang=" + languageToSet + " \r\n";
+ BC_ASSERT_STRING_EQUAL(str.c_str(), expected.c_str());
+ }
+
+ // Set invalid language.
+ {
+ const string languageToSet = "fr--";
+ BC_ASSERT_FALSE(subjectHeader.setLanguage(languageToSet));
+ BC_ASSERT_FALSE(languageToSet == subjectHeader.getLanguage());
+ BC_ASSERT_FALSE(subjectHeader.isValid());
+ }
+}
+
+static void parse_rfc_example (void) {
+ const string str = "Content-type: Message/CPIM\r\n"
+ "\r\n"
+ "From: MR SANDERS \r\n"
+ "To: Depressed Donkey \r\n"
+ "DateTime: 2000-12-13T13:40:00-08:00\r\n"
+ "Subject: the weather will be fine today\r\n"
+ "Subject:;lang=fr beau temps prevu pour aujourd'hui\r\n"
+ "NS: MyFeatures \r\n"
+ "Require: MyFeatures.VitalMessageOption\r\n"
+ "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-ID: <1234567890@foo.com>\r\n"
+ "\r\n"
+ ""
+ "Here is the text of my message."
+ "";
+
+ shared_ptr 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());
+}
+
+static void parse_message_with_generic_header_parameters (void) {
+ const string str = "Content-type: Message/CPIM\r\n"
+ "\r\n"
+ "From: MR SANDERS \r\n"
+ "Test:;aaa=bbb;yes=no CheckMe\r\n"
+ "yaya: coucou\r\n"
+ "yepee:;good=bad ugly\r\n"
+ "\r\n"
+ "Content-type text/xml; charset=utf-8\r\n"
+ "Content-ID: <1234567890@foo.com>\r\n"
+ "\r\n"
+ ""
+ "Here is the text of my message."
+ "";
+
+ shared_ptr 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());
+}
+
+static void build_message (void) {
+ Cpim::Message message;
+ if (!BC_ASSERT_FALSE(message.isValid()))
+ 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;
+
+ if (!BC_ASSERT_TRUE(message.addCpimHeader(contentTypeHeader))) return;
+
+ // Set message headers.
+ Cpim::FromHeader fromHeader;
+ if (!BC_ASSERT_TRUE(fromHeader.setValue("MR SANDERS "))) return;
+
+ Cpim::ToHeader toHeader;
+ if (!BC_ASSERT_TRUE(toHeader.setValue("Depressed Donkey "))) return;
+
+ Cpim::DateTimeHeader dateTimeHeader;
+ if (!BC_ASSERT_TRUE(dateTimeHeader.setValue("2000-12-13T13:40:00-08:00"))) return;
+
+ Cpim::SubjectHeader subjectHeader;
+ if (!BC_ASSERT_TRUE(subjectHeader.setValue("the weather will be fine today"))) return;
+
+ Cpim::SubjectHeader subjectWithLanguageHeader;
+ if (!BC_ASSERT_TRUE(subjectWithLanguageHeader.setValue("beau temps prevu pour aujourd'hui"))) return;
+ if (!BC_ASSERT_TRUE(subjectWithLanguageHeader.setLanguage("fr"))) return;
+
+ Cpim::NsHeader nsHeader;
+ if (!BC_ASSERT_TRUE(nsHeader.setValue("MyFeatures "))) return;
+
+ Cpim::RequireHeader requireHeader;
+ if (!BC_ASSERT_TRUE(requireHeader.setValue("MyFeatures.VitalMessageOption"))) return;
+
+ Cpim::GenericHeader vitalMessageHeader;
+ if (!BC_ASSERT_TRUE(vitalMessageHeader.setName("MyFeatures.VitalMessageOption"))) return;
+ if (!BC_ASSERT_TRUE(vitalMessageHeader.setValue("Confirmation-requested"))) return;
+
+ Cpim::GenericHeader wackyMessageHeader;
+ if (!BC_ASSERT_TRUE(wackyMessageHeader.setName("MyFeatures.WackyMessageOption"))) return;
+ if (!BC_ASSERT_TRUE(wackyMessageHeader.setValue("Use-silly-font"))) return;
+
+ if (!BC_ASSERT_TRUE(message.addMessageHeader(fromHeader))) return;
+ if (!BC_ASSERT_TRUE(message.addMessageHeader(toHeader))) return;
+ if (!BC_ASSERT_TRUE(message.addMessageHeader(dateTimeHeader))) return;
+ if (!BC_ASSERT_TRUE(message.addMessageHeader(subjectHeader))) return;
+ if (!BC_ASSERT_TRUE(message.addMessageHeader(subjectWithLanguageHeader))) return;
+ if (!BC_ASSERT_TRUE(message.addMessageHeader(nsHeader))) return;
+ if (!BC_ASSERT_TRUE(message.addMessageHeader(requireHeader))) return;
+ 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"
+ ""
+ "Here is the text of my message."
+ "";
+
+ if (!BC_ASSERT_TRUE(message.setContent(content))) return;
+ if (!BC_ASSERT_TRUE(message.isValid())) return;
+
+ const string strMessage = message.asString();
+ const string expectedMessage = "Content-Type: Message/CPIM\r\n"
+ "\r\n"
+ "From: MR SANDERS \r\n"
+ "To: Depressed Donkey \r\n"
+ "DateTime: 2000-12-13T13:40:00-08:00\r\n"
+ "Subject: the weather will be fine today\r\n"
+ "Subject:;lang=fr beau temps prevu pour aujourd'hui\r\n"
+ "NS: MyFeatures \r\n"
+ "Require: MyFeatures.VitalMessageOption\r\n"
+ "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-ID: <1234567890@foo.com>\r\n"
+ "\r\n"
+ ""
+ "Here is the text of my message."
+ "";
+
+ BC_ASSERT_STRING_EQUAL(strMessage.c_str(), expectedMessage.c_str());
+}
+
+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),
+ TEST_NO_TAG("Set generic header value", set_generic_header_value),
+ TEST_NO_TAG("Check core header names", check_core_header_names),
+ TEST_NO_TAG("Set core header values", set_core_header_values),
+ 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_suite_t cpim_test_suite = {
+ "Cpim", NULL, NULL, liblinphone_tester_before_each, liblinphone_tester_after_each,
+ sizeof(cpim_tests) / sizeof(cpim_tests[0]), cpim_tests
+};
diff --git a/tester/liblinphone_tester.h b/tester/liblinphone_tester.h
index 15f55b0d6..dc5f07f9c 100644
--- a/tester/liblinphone_tester.h
+++ b/tester/liblinphone_tester.h
@@ -39,36 +39,40 @@
extern "C" {
#endif
-extern test_suite_t setup_test_suite;
-extern test_suite_t register_test_suite;
+extern test_suite_t account_creator_test_suite;
extern test_suite_t call_test_suite;
extern test_suite_t call_video_test_suite;
-extern test_suite_t message_test_suite;
-extern test_suite_t presence_test_suite;
-extern test_suite_t presence_server_test_suite;
-extern test_suite_t upnp_test_suite;
+extern test_suite_t cpim_test_suite;
+extern test_suite_t dtmf_test_suite;
extern test_suite_t event_test_suite;
extern test_suite_t flexisip_test_suite;
-extern test_suite_t stun_test_suite;
-extern test_suite_t remote_provisioning_test_suite;
-extern test_suite_t quality_reporting_test_suite;
extern test_suite_t log_collection_test_suite;
-extern test_suite_t tunnel_test_suite;
-extern test_suite_t player_test_suite;
-extern test_suite_t dtmf_test_suite;
-extern test_suite_t offeranswer_test_suite;
-extern test_suite_t video_test_suite;
-extern test_suite_t multicast_call_test_suite;
+extern test_suite_t message_test_suite;
extern test_suite_t multi_call_test_suite;
+extern test_suite_t multicast_call_test_suite;
+extern test_suite_t offeranswer_test_suite;
+extern test_suite_t player_test_suite;
+extern test_suite_t presence_server_test_suite;
+extern test_suite_t presence_test_suite;
extern test_suite_t proxy_config_test_suite;
-extern test_suite_t account_creator_test_suite;
+extern test_suite_t quality_reporting_test_suite;
+extern test_suite_t register_test_suite;
+extern test_suite_t remote_provisioning_test_suite;
+extern test_suite_t setup_test_suite;
+extern test_suite_t stun_test_suite;
+extern test_suite_t tunnel_test_suite;
+extern test_suite_t upnp_test_suite;
+extern test_suite_t video_test_suite;
+
#ifdef VCARD_ENABLED
extern test_suite_t vcard_test_suite;
#endif
+
extern test_suite_t audio_bypass_suite;
#if HAVE_SIPP
extern test_suite_t complex_sip_call_test_suite;
#endif
+
extern int manager_count;
extern int liblinphone_tester_ipv6_available(void);
diff --git a/tester/tester.c b/tester/tester.c
index 40f8b43b9..51062890f 100644
--- a/tester/tester.c
+++ b/tester/tester.c
@@ -572,6 +572,7 @@ void liblinphone_tester_add_suites() {
bc_tester_add_suite(&log_collection_test_suite);
bc_tester_add_suite(&player_test_suite);
bc_tester_add_suite(&dtmf_test_suite);
+ bc_tester_add_suite(&cpim_test_suite);
#if defined(VIDEO_ENABLED) && defined(HAVE_GTK)
bc_tester_add_suite(&video_test_suite);
#endif