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