diff options
author | Thanos Doukoudakis <thanos.doukoudakis@isode.com> | 2017-07-20 16:28:31 (GMT) |
---|---|---|
committer | Thanos Doukoudakis <thanos.doukoudakis@isode.com> | 2017-07-26 08:35:20 (GMT) |
commit | bc58d59636f28909d90464cea7ebf5eeb8f230be (patch) | |
tree | 67c77e5f10f15eb2ec6202591ed20e24cb1d9ede | |
parent | 810d77181de6408d54fd3a07964317d4866c732d (diff) | |
download | swift-bc58d59636f28909d90464cea7ebf5eeb8f230be.zip swift-bc58d59636f28909d90464cea7ebf5eeb8f230be.tar.bz2 |
Workaround Boost bug to prevent crash in recents loading
Boost 1.64 introduced a regression where boost::optional deserialisation
could lead to a crash when loading the recent chats.
This fix updates the way we serialise the ChatListWindow::Chat and
HighlightAction classes to avoid these errors.
A flag has been added, to allow the use of the erroneous version of the
library during the build. During runtime if needed, the client will skip
loading the data, to avoid any unpredicted behaviour.
Test-Information:
Tested on windows 10 with different versions of boost 1.56(bundled), 1.63,
1.64, 1.65 and Ubuntu 17.04 with versions 1.56(bundled) and 1.64. Added
unit tests for the serialisation ChatListWindow::Chat class.
Change-Id: Idc5c3a6cfd92272b8eab2d77e243dda743803a31
-rw-r--r-- | BuildTools/SCons/SConscript.boot | 1 | ||||
-rw-r--r-- | BuildTools/SCons/SConstruct | 7 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h | 82 | ||||
-rw-r--r-- | Swift/Controllers/Chat/ChatsManager.cpp | 46 | ||||
-rw-r--r-- | Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp | 292 | ||||
-rw-r--r-- | Swift/Controllers/Highlighting/HighlightAction.cpp | 2 | ||||
-rw-r--r-- | Swift/Controllers/Highlighting/HighlightAction.h | 56 | ||||
-rw-r--r-- | Swift/Controllers/SConscript | 1 | ||||
-rw-r--r-- | Swift/SConscript | 6 |
9 files changed, 441 insertions, 52 deletions
diff --git a/BuildTools/SCons/SConscript.boot b/BuildTools/SCons/SConscript.boot index f815bbc..6ac4981 100644 --- a/BuildTools/SCons/SConscript.boot +++ b/BuildTools/SCons/SConscript.boot @@ -53,6 +53,7 @@ vars.Add(PathVariable("boost_includedir", "Boost headers location", None, PathVa vars.Add(PathVariable("boost_libdir", "Boost library location", None, PathVariable.PathAccept)) vars.Add(BoolVariable("boost_bundled_enable", "Allow use of bundled Boost as last resort", "true")) vars.Add(BoolVariable("boost_force_bundled", "Force use of bundled Boost.", False)) +vars.Add(BoolVariable("allow_boost_1_64", "Allow use of Boost 1.64", False)) vars.Add(PathVariable("zlib_includedir", "Zlib headers location", None, PathVariable.PathAccept)) vars.Add(PathVariable("zlib_libdir", "Zlib library location", None, PathVariable.PathAccept)) vars.Add(PathVariable("zlib_libfile", "Zlib library file (full path to file)", None, PathVariable.PathAccept)) diff --git a/BuildTools/SCons/SConstruct b/BuildTools/SCons/SConstruct index 2c2d629..6d63d5b 100644 --- a/BuildTools/SCons/SConstruct +++ b/BuildTools/SCons/SConstruct @@ -242,6 +242,12 @@ elif not env.get("boost_bundled_enable", True) : Exit(1) else : env["BOOST_BUNDLED"] = True +boost_version = GetVersion(conf, "BOOST_VERSION", "boost/version.hpp") +if boost_version == 106400 : + #Version 1.64 has some issues with the serialization of boost::optional, see https://svn.boost.org/trac10/ticket/13050 + env["BOOST_1_64_DETECTED"] = True +else: + env["BOOST_1_64_DETECTED"] = False conf.Finish() @@ -780,4 +786,3 @@ print if not GetOption("help") and not env.get("HAVE_OPENSSL", 0) and not env.get("HAVE_SCHANNEL", 0) and not env.get("HAVE_SECURETRANSPORT", 0): print "Error: A working TLS backend is required. Please check the documentation for more information." Exit(1) - diff --git a/Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h b/Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h new file mode 100644 index 0000000..1e053b2 --- /dev/null +++ b/Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#pragma once + +#include <memory> + +#include <boost/version.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/archive/text_iarchive.hpp> +#include <boost/archive/text_oarchive.hpp> +#include <boost/bind.hpp> +#include <boost/serialization/map.hpp> +#include <boost/serialization/optional.hpp> +#include <boost/serialization/split_free.hpp> +#include <boost/serialization/string.hpp> +#include <boost/serialization/vector.hpp> + +#include <Swift/Controllers/UIInterfaces/ChatListWindow.h> + +BOOST_CLASS_VERSION(Swift::ChatListWindow::Chat, 3) +namespace Swift { + const boost::archive::library_version_type BoostArchiveSkipVersion(15); +} + +namespace boost { + namespace serialization { + template<class Archive> void save(Archive& ar, const Swift::JID& jid, const unsigned int /*version*/) { + std::string jidStr = jid.toString(); + ar << jidStr; + } + + template<class Archive> void load(Archive& ar, Swift::JID& jid, const unsigned int /*version*/) { + std::string stringJID; + ar >> stringJID; + jid = Swift::JID(stringJID); + } + + template<class Archive> inline void serialize(Archive& ar, Swift::JID& t, const unsigned int file_version) { + split_free(ar, t, file_version); + } + + template<class Archive> void serialize(Archive& ar, Swift::ChatListWindow::Chat& chat, const unsigned int version) { + auto archiveLibraryVersion = boost::archive::BOOST_ARCHIVE_VERSION(); + int archiveVersion = 0; + archive::text_oarchive* outputStream = dynamic_cast<archive::text_oarchive*>(&ar); + if (outputStream) { + archiveVersion = outputStream->get_library_version(); + } + archive::text_iarchive* inputStream = dynamic_cast<archive::text_iarchive*>(&ar); + if (inputStream) { + archiveVersion = inputStream->get_library_version(); + //Due to https://svn.boost.org/trac10/ticket/13050 the password field may fail to load and crash the client. Therefore we skip loading the values from previous versions. + if (archiveLibraryVersion == Swift::BoostArchiveSkipVersion && archiveLibraryVersion > archiveVersion) { + return; + } + } + ar & chat.jid; + ar & chat.chatName; + ar & chat.activity; + ar & chat.isMUC; + ar & chat.nick; + ar & chat.impromptuJIDs; + if (version > 0) { + if (outputStream && archiveLibraryVersion == Swift::BoostArchiveSkipVersion) { + //The bug does not affect the case boost::optional doesn't have a value. Therefore we store always that value, to avoid problem on future launches of the client. + boost::optional<std::string> empty; + ar & empty; + } + else { + ar & chat.password; + } + } + if (version > 1) { + ar & chat.inviteesNames; + } + } + } +} diff --git a/Swift/Controllers/Chat/ChatsManager.cpp b/Swift/Controllers/Chat/ChatsManager.cpp index b7087fd..95a9f64 100644 --- a/Swift/Controllers/Chat/ChatsManager.cpp +++ b/Swift/Controllers/Chat/ChatsManager.cpp @@ -9,14 +9,7 @@ #include <memory> #include <boost/algorithm/string.hpp> -#include <boost/archive/text_iarchive.hpp> -#include <boost/archive/text_oarchive.hpp> #include <boost/bind.hpp> -#include <boost/serialization/map.hpp> -#include <boost/serialization/optional.hpp> -#include <boost/serialization/split_free.hpp> -#include <boost/serialization/string.hpp> -#include <boost/serialization/vector.hpp> #include <Swiften/Avatars/AvatarManager.h> #include <Swiften/Base/Log.h> @@ -40,6 +33,7 @@ #include <Swiften/StringCodecs/Base64.h> #include <Swiften/VCards/VCardManager.h> +#include <Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h> #include <Swift/Controllers/Chat/AutoAcceptMUCInviteDecider.h> #include <Swift/Controllers/Chat/ChatController.h> #include <Swift/Controllers/Chat/ChatControllerBase.h> @@ -67,42 +61,6 @@ #include <Swift/Controllers/WhiteboardManager.h> #include <Swift/Controllers/XMPPEvents/EventController.h> -BOOST_CLASS_VERSION(Swift::ChatListWindow::Chat, 2) - -namespace boost { -namespace serialization { - template<class Archive> void save(Archive& ar, const Swift::JID& jid, const unsigned int /*version*/) { - std::string jidStr = jid.toString(); - ar << jidStr; - } - - template<class Archive> void load(Archive& ar, Swift::JID& jid, const unsigned int /*version*/) { - std::string stringJID; - ar >> stringJID; - jid = Swift::JID(stringJID); - } - - template<class Archive> inline void serialize(Archive& ar, Swift::JID& t, const unsigned int file_version){ - split_free(ar, t, file_version); - } - - template<class Archive> void serialize(Archive& ar, Swift::ChatListWindow::Chat& chat, const unsigned int version) { - ar & chat.jid; - ar & chat.chatName; - ar & chat.activity; - ar & chat.isMUC; - ar & chat.nick; - ar & chat.impromptuJIDs; - if (version > 0) { - ar & chat.password; - } - if (version > 1) { - ar & chat.inviteesNames; - } - } -} -} - namespace Swift { typedef std::pair<JID, ChatController*> JIDChatControllerPair; @@ -341,7 +299,7 @@ void ChatsManager::loadRecents() { SWIFT_LOG(debug) << "Failed to load recents: " << e.what() << std::endl; return; } - + recentChats.erase(std::remove(recentChats.begin(), recentChats.end(), ChatListWindow::Chat()), recentChats.end()); for (auto chat : recentChats) { chat.statusType = StatusShow::None; chat = updateChatStatusAndAvatarHelper(chat); diff --git a/Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp b/Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp new file mode 100644 index 0000000..9561e2b --- /dev/null +++ b/Swift/Controllers/Chat/UnitTest/ChatListWindowChatTest.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (c) 2017 Isode Limited. + * All rights reserved. + * See the COPYING file for more information. + */ + +#include <memory> +#include <string> +#include <vector> + +#include <boost/algorithm/string.hpp> +#include <boost/version.hpp> + +#include <gtest/gtest.h> + +#include <Swiften/Base/ByteArray.h> +#include <Swiften/StringCodecs/Base64.h> + +#include <Swift/Controllers/Chat/ChatListWindowChatBoostSerialize.h> +#include <Swift/Controllers/UIInterfaces/ChatListWindow.h> + +using namespace Swift; + +class ChatListWindowChatTest : public ::testing::Test { +protected: + virtual void SetUp() {} + virtual void TearDown() {} + + void testOptionalPasswordValue(const boost::optional<std::string>& value1, const std::string& value2) { + auto archiveLibraryVersion = boost::archive::BOOST_ARCHIVE_VERSION(); + if (archiveLibraryVersion != Swift::BoostArchiveSkipVersion) { + EXPECT_EQ(value1.get_value_or(""), value2); + } + else { + EXPECT_EQ(value1.get_value_or(""), ""); + } + } + + std::string chatsSerialise(const std::vector<ChatListWindow::Chat>& chats) { + std::stringstream serializeStream; + boost::archive::text_oarchive oa(serializeStream); + oa & chats; + std::string serializedStr = Base64::encode(createByteArray(serializeStream.str())); + return serializedStr; + } + + std::vector<ChatListWindow::Chat> chatsDeserialise(const std::string& b64chats) { + ByteArray debase64 = Base64::decode(b64chats); + std::vector<ChatListWindow::Chat> recentChats; + std::stringstream deserializeStream(std::string(reinterpret_cast<const char*>(vecptr(debase64)), debase64.size())); + try { + boost::archive::text_iarchive ia(deserializeStream); + ia >> recentChats; + } + catch (const boost::archive::archive_exception& e) { + EXPECT_TRUE(false) << "Archive Version:" << boost::archive::BOOST_ARCHIVE_VERSION() << " " << e.what() << std::endl; + recentChats.clear(); + return recentChats; + } + recentChats.erase(std::remove(recentChats.begin(), recentChats.end(), ChatListWindow::Chat()), recentChats.end()); + return recentChats; + } +}; + +TEST_F(ChatListWindowChatTest, testNormalSerialization) { + ChatListWindow::Chat chat1("swift@rooms.swift.im", "swift@rooms.swift.im", "Some text 0", 0, StatusShow::None, "", false, false, "Nick Name"); + ChatListWindow::Chat chat2("testuser1@domain.com", "swift@rooms2.swift.im", "Some text 1", 0, StatusShow::None, "", false, false, "Nick Name", std::string("pass")); + ChatListWindow::Chat chat3("testuser2@domain.com", "room 2", "Some text 2", 0, StatusShow::None, "", true, false, "Nick Name"); + ChatListWindow::Chat chat4("testuser3@domain.com", "room 3", "Some text 2", 0, StatusShow::None, "", true, false, "Nick Name", std::string("pass")); + + std::map<std::string, JID> impromptuJIDs; + impromptuJIDs["testuser1@domain.com"] = "testuser1@domain.com"; + impromptuJIDs["testuser2@domain.com"] = "testuser2@domain.com"; + std::map<JID, std::string> inviteesNames; + inviteesNames["user1@domain.com"] = "user1@domain.com"; + + chat3.impromptuJIDs = impromptuJIDs; + chat3.inviteesNames = inviteesNames; + chat4.impromptuJIDs = impromptuJIDs; + chat4.inviteesNames = inviteesNames; + + std::vector<ChatListWindow::Chat> chats; + chats.push_back(chat1); + chats.push_back(chat2); + chats.push_back(chat3); + chats.push_back(chat4); + + auto base64 = chatsSerialise(chats); + ASSERT_TRUE(base64.size() > 0); + auto restoredChats = chatsDeserialise(base64); + ASSERT_EQ(restoredChats.size(), 4); + + EXPECT_FALSE(restoredChats[0].isMUC); + EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].activity, "Some text 0"); + EXPECT_EQ(restoredChats[0].nick, "Nick Name"); + EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0); + testOptionalPasswordValue(restoredChats[0].password, ""); + EXPECT_EQ(restoredChats[0].inviteesNames.size(), 0); + + EXPECT_FALSE(restoredChats[1].isMUC); + EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com"); + EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im"); + EXPECT_EQ(restoredChats[1].activity, "Some text 1"); + EXPECT_EQ(restoredChats[1].nick, "Nick Name"); + EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0); + testOptionalPasswordValue(restoredChats[1].password, "pass"); + EXPECT_EQ(restoredChats[1].inviteesNames.size(), 0); + + EXPECT_TRUE(restoredChats[2].isMUC); + EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].chatName, "room 2"); + EXPECT_EQ(restoredChats[2].activity, "Some text 2"); + EXPECT_EQ(restoredChats[2].nick, "Nick Name"); + ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + testOptionalPasswordValue(restoredChats[2].password, ""); + ASSERT_EQ(restoredChats[2].inviteesNames.size(), 1); + EXPECT_EQ(restoredChats[2].inviteesNames["user1@domain.com"], "user1@domain.com"); + + EXPECT_TRUE(restoredChats[3].isMUC); + EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com"); + EXPECT_EQ(restoredChats[3].chatName, "room 3"); + EXPECT_EQ(restoredChats[3].activity, "Some text 2"); + EXPECT_EQ(restoredChats[3].nick, "Nick Name"); + ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + testOptionalPasswordValue(restoredChats[3].password, "pass"); + ASSERT_EQ(restoredChats[3].inviteesNames.size(), 1); + EXPECT_EQ(restoredChats[3].inviteesNames["user1@domain.com"], "user1@domain.com"); +} + +TEST_F(ChatListWindowChatTest, testVersionsSerialization) { + auto archiveLibraryVersion = boost::archive::BOOST_ARCHIVE_VERSION(); + /* + The following strings are base64 serialised vectors with these Swift::ChatListWindow::Chat elements: + + Chat1: Jid = "swift@rooms.swift.im", ChatName = "swift@rooms.swift.im", Activity = "Some text 0", isMUC = false, nick="Nick Name" + Chat2: Jid = "testuser1@domain.com", ChatName = "swift@rooms2.swift.im", Activity = "Some text 1", isMUC = false, nick="Nick Name", password = "pass" + Chat3: Jid = "testuser2@domain.com", ChatName = "room2", Activity = "Some text 2", isMUC = true, nick="Nick Name", impromptuJIDs, inviteesNames + Chat4: Jid = "testuser3@domain.com", ChatName = "room3", Activity = "Some text 2", isMUC = true, nick="Nick Name", impromptuJIDs, password = "pass", inviteesNames + + impromptuJIDs = {("testuser1@domain.com","testuser1@domain.com"), ("testuser2@domain.com", "testuser2@domain.com")} + inviteesNames = {("user1@domain.com","user1@domain.com")} + */ + std::string serializedChats_BoostArchiveV10_ClassVersion_0 = "MjIgc2VyaWFsaXphdGlvbjo6YXJjaGl2ZSAxMCAwIDAgNCAwIDAgMCAwIDAgMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMTEgU29tZSB0ZXh0IDAgMCA5IE5pY2sgTmFtZSAwIDAgMCAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIxIHN3aWZ0QHJvb21zMi5zd2lmdC5pbSAxMSBTb21lIHRleHQgMSAwIDkgTmljayBOYW1lIDAgMCAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSA2IHJvb20gMiAxMSBTb21lIHRleHQgMiAxIDkgTmljayBOYW1lIDIgMCAwIDAgMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIzQGRvbWFpbi5jb20gNiByb29tIDMgMTEgU29tZSB0ZXh0IDIgMSA5IE5pY2sgTmFtZSAyIDAgMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20gMjAgdGVzdHVzZXIyQGRvbWFpbi5jb20="; + { + auto restoredChats = chatsDeserialise(serializedChats_BoostArchiveV10_ClassVersion_0); + if (archiveLibraryVersion == Swift::BoostArchiveSkipVersion) { + ASSERT_EQ(restoredChats.size(), 0); + } + else { + ASSERT_EQ(restoredChats.size(), 4); + + EXPECT_FALSE(restoredChats[0].isMUC); + EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].activity, "Some text 0"); + EXPECT_EQ(restoredChats[0].nick, "Nick Name"); + EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0); + + EXPECT_FALSE(restoredChats[1].isMUC); + EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com"); + EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im"); + EXPECT_EQ(restoredChats[1].activity, "Some text 1"); + EXPECT_EQ(restoredChats[1].nick, "Nick Name"); + EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0); + + EXPECT_TRUE(restoredChats[2].isMUC); + EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].chatName, "room 2"); + EXPECT_EQ(restoredChats[2].activity, "Some text 2"); + EXPECT_EQ(restoredChats[2].nick, "Nick Name"); + ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + + EXPECT_TRUE(restoredChats[3].isMUC); + EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com"); + EXPECT_EQ(restoredChats[3].chatName, "room 3"); + EXPECT_EQ(restoredChats[3].activity, "Some text 2"); + EXPECT_EQ(restoredChats[3].nick, "Nick Name"); + ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + } + } + + std::string serializedChats_BoostArchiveV10_ClassVersion_1 = "MjIgc2VyaWFsaXphdGlvbjo6YXJjaGl2ZSAxMCAwIDAgNCAxIDAgMSAwIDAgMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMTEgU29tZSB0ZXh0IDAgMCA5IE5pY2sgTmFtZSAwIDAgMCAwIDAgMCAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIxIHN3aWZ0QHJvb21zMi5zd2lmdC5pbSAxMSBTb21lIHRleHQgMSAwIDkgTmljayBOYW1lIDAgMCAxIDAgNCBwYXNzIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDYgcm9vbSAyIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDAgMCAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAwIDIwIHRlc3R1c2VyM0Bkb21haW4uY29tIDYgcm9vbSAzIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDEgMCA0IHBhc3M="; + { + auto restoredChats = chatsDeserialise(serializedChats_BoostArchiveV10_ClassVersion_1); + if (archiveLibraryVersion == Swift::BoostArchiveSkipVersion) { + ASSERT_EQ(restoredChats.size(), 0); + } + else { + ASSERT_EQ(restoredChats.size(), 4); + + EXPECT_FALSE(restoredChats[0].isMUC); + EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].activity, "Some text 0"); + EXPECT_EQ(restoredChats[0].nick, "Nick Name"); + EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0); + EXPECT_EQ(restoredChats[0].password.get_value_or(""), ""); + + EXPECT_FALSE(restoredChats[1].isMUC); + EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com"); + EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im"); + EXPECT_EQ(restoredChats[1].activity, "Some text 1"); + EXPECT_EQ(restoredChats[1].nick, "Nick Name"); + EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0); + EXPECT_EQ(restoredChats[1].password.get_value_or(""), "pass"); + + EXPECT_TRUE(restoredChats[2].isMUC); + EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].chatName, "room 2"); + EXPECT_EQ(restoredChats[2].activity, "Some text 2"); + EXPECT_EQ(restoredChats[2].nick, "Nick Name"); + ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].password.get_value_or(""), ""); + + EXPECT_TRUE(restoredChats[3].isMUC); + EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com"); + EXPECT_EQ(restoredChats[3].chatName, "room 3"); + EXPECT_EQ(restoredChats[3].activity, "Some text 2"); + EXPECT_EQ(restoredChats[3].nick, "Nick Name"); + ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + EXPECT_EQ(restoredChats[3].password.get_value_or(""), "pass"); + } + } + + std::string serializedChats_BoostArchiveV10_ClassVersion_2 = "MjIgc2VyaWFsaXphdGlvbjo6YXJjaGl2ZSAxMCAwIDAgNCAyIDAgMiAwIDAgMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMjAgc3dpZnRAcm9vbXMuc3dpZnQuaW0gMTEgU29tZSB0ZXh0IDAgMCA5IE5pY2sgTmFtZSAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMjAgdGVzdHVzZXIxQGRvbWFpbi5jb20gMjEgc3dpZnRAcm9vbXMyLnN3aWZ0LmltIDExIFNvbWUgdGV4dCAxIDAgOSBOaWNrIE5hbWUgMCAwIDEgMCA0IHBhc3MgMCAwIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDYgcm9vbSAyIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDAgMCAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjFAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAyMCB0ZXN0dXNlcjJAZG9tYWluLmNvbSAwIDEgMCAwIDAgMTYgdXNlcjFAZG9tYWluLmNvbSAxNiB1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyM0Bkb21haW4uY29tIDYgcm9vbSAzIDExIFNvbWUgdGV4dCAyIDEgOSBOaWNrIE5hbWUgMiAwIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMUBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDIwIHRlc3R1c2VyMkBkb21haW4uY29tIDEgMCA0IHBhc3MgMSAwIDE2IHVzZXIxQGRvbWFpbi5jb20gMTYgdXNlcjFAZG9tYWluLmNvbQ=="; + { + auto restoredChats = chatsDeserialise(serializedChats_BoostArchiveV10_ClassVersion_2); + if (archiveLibraryVersion == Swift::BoostArchiveSkipVersion) { + ASSERT_EQ(restoredChats.size(), 0); + } + else { + ASSERT_EQ(restoredChats.size(), 4); + + EXPECT_FALSE(restoredChats[0].isMUC); + EXPECT_EQ(restoredChats[0].jid, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].chatName, "swift@rooms.swift.im"); + EXPECT_EQ(restoredChats[0].activity, "Some text 0"); + EXPECT_EQ(restoredChats[0].nick, "Nick Name"); + EXPECT_EQ(restoredChats[0].impromptuJIDs.size(), 0); + EXPECT_EQ(restoredChats[0].password.get_value_or(""), ""); + EXPECT_EQ(restoredChats[0].inviteesNames.size(), 0); + + EXPECT_FALSE(restoredChats[1].isMUC); + EXPECT_EQ(restoredChats[1].jid, "testuser1@domain.com"); + EXPECT_EQ(restoredChats[1].chatName, "swift@rooms2.swift.im"); + EXPECT_EQ(restoredChats[1].activity, "Some text 1"); + EXPECT_EQ(restoredChats[1].nick, "Nick Name"); + EXPECT_EQ(restoredChats[1].impromptuJIDs.size(), 0); + EXPECT_EQ(restoredChats[1].password.get_value_or(""), "pass"); + EXPECT_EQ(restoredChats[1].inviteesNames.size(), 0); + + EXPECT_TRUE(restoredChats[2].isMUC); + EXPECT_EQ(restoredChats[2].jid, "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].chatName, "room 2"); + EXPECT_EQ(restoredChats[2].activity, "Some text 2"); + EXPECT_EQ(restoredChats[2].nick, "Nick Name"); + ASSERT_EQ(restoredChats[2].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[2].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + EXPECT_EQ(restoredChats[2].password.get_value_or(""), ""); + ASSERT_EQ(restoredChats[2].inviteesNames.size(), 1); + EXPECT_EQ(restoredChats[2].inviteesNames["user1@domain.com"], "user1@domain.com"); + + EXPECT_TRUE(restoredChats[3].isMUC); + EXPECT_EQ(restoredChats[3].jid, "testuser3@domain.com"); + EXPECT_EQ(restoredChats[3].chatName, "room 3"); + EXPECT_EQ(restoredChats[3].activity, "Some text 2"); + EXPECT_EQ(restoredChats[3].nick, "Nick Name"); + ASSERT_EQ(restoredChats[3].impromptuJIDs.size(), 2); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser1@domain.com"], "testuser1@domain.com"); + EXPECT_EQ(restoredChats[3].impromptuJIDs["testuser2@domain.com"], "testuser2@domain.com"); + EXPECT_EQ(restoredChats[3].password.get_value_or(""), "pass"); + ASSERT_EQ(restoredChats[3].inviteesNames.size(), 1); + EXPECT_EQ(restoredChats[3].inviteesNames["user1@domain.com"], "user1@domain.com"); + } + } +} + diff --git a/Swift/Controllers/Highlighting/HighlightAction.cpp b/Swift/Controllers/Highlighting/HighlightAction.cpp index e9f14df..60ace42 100644 --- a/Swift/Controllers/Highlighting/HighlightAction.cpp +++ b/Swift/Controllers/Highlighting/HighlightAction.cpp @@ -12,6 +12,8 @@ #include <Swift/Controllers/Highlighting/HighlightAction.h> +BOOST_CLASS_VERSION(Swift::HighlightAction, 1) + namespace Swift { diff --git a/Swift/Controllers/Highlighting/HighlightAction.h b/Swift/Controllers/Highlighting/HighlightAction.h index da92901..d11f3ec 100644 --- a/Swift/Controllers/Highlighting/HighlightAction.h +++ b/Swift/Controllers/Highlighting/HighlightAction.h @@ -12,11 +12,14 @@ #pragma once +#include <map> +#include <memory> #include <string> #include <boost/archive/text_iarchive.hpp> #include <boost/archive/text_oarchive.hpp> #include <boost/optional.hpp> +#include <boost/serialization/map.hpp> #include <boost/serialization/optional.hpp> namespace Swift { @@ -57,12 +60,53 @@ namespace Swift { bool operator ==(const HighlightAction& a, const HighlightAction& b); bool operator !=(const HighlightAction& a, const HighlightAction& b); + template<class Archive> - void HighlightAction::serialize(Archive& ar, const unsigned int /*version*/) { - ar & frontColor_; - ar & backColor_; - ar & soundFilePath_; - ar & systemNotificaitonEnabled_; + void HighlightAction::serialize(Archive& ar, const unsigned int version) { + auto inputStream = dynamic_cast<boost::archive::text_iarchive*>(&ar); + auto outputStream = dynamic_cast<boost::archive::text_oarchive*>(&ar); + + if (version == 0) { + if (inputStream) { + const boost::archive::library_version_type BoostArchiveSkipVersion(15); + auto archiveLibraryVersion = boost::archive::BOOST_ARCHIVE_VERSION(); + //Due to https://svn.boost.org/trac10/ticket/13050 the boost::optional fields may fail to load and crash the client. Therefore we skip loading the values from previous versions. + if (archiveLibraryVersion == BoostArchiveSkipVersion && archiveLibraryVersion > inputStream->get_library_version()) { + return; + } + } + ar & frontColor_; + ar & backColor_; + ar & soundFilePath_; + ar & systemNotificaitonEnabled_; + } + else if (version == 1) { + //Using a map instead of optional values that may cause a problems when serialised with boost::archive 15 version + std::map<std::string, std::string> properties; + if (outputStream) { + if (frontColor_.is_initialized()) { + properties["frontColor"] = frontColor_.get(); + } + if (backColor_.is_initialized()) { + properties["backColor"] = backColor_.get(); + } + if (soundFilePath_.is_initialized()) { + properties["soundFilePath"] = soundFilePath_.get(); + } + } + ar & properties; + ar & systemNotificaitonEnabled_; + if (inputStream) { + if (properties.find("frontColor") != properties.end()) { + frontColor_ = properties["frontColor"]; + } + if (properties.find("backColor") != properties.end()) { + backColor_ = properties["backColor"]; + } + if (properties.find("soundFilePath") != properties.end()) { + soundFilePath_ = properties["soundFilePath"]; + } + } + } } - } diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript index 0c3127c..bc5c2c0 100644 --- a/Swift/Controllers/SConscript +++ b/Swift/Controllers/SConscript @@ -93,6 +93,7 @@ if env["SCONS_STAGE"] == "build" : ]) env.Append(UNITTEST_SOURCES = [ + File("Chat/UnitTest/ChatListWindowChatTest.cpp"), File("Chat/UnitTest/ChatMessageParserTest.cpp"), File("Chat/UnitTest/ChatsManagerTest.cpp"), File("Chat/UnitTest/MUCControllerTest.cpp"), diff --git a/Swift/SConscript b/Swift/SConscript index daff755..863597a 100644 --- a/Swift/SConscript +++ b/Swift/SConscript @@ -13,5 +13,9 @@ if env["SCONS_STAGE"] == "build" : try : SConscript("QtUI/SConscript") except Exception as e: - print "Warning: %s" % str(e) + print "Warning: %s" % str(e) env["PROJECTS"].remove("Swift") +if "Swift" in env["PROJECTS"] and env["BOOST_1_64_DETECTED"] and not env.get("allow_boost_1_64") and not env.GetOption("clean") : + #Version 1.64 has some issues with the serialization of boost::optional, see https://svn.boost.org/trac10/ticket/13050 + print "Boost 1.64 has been detected. It is not recommended to use this version due to a regression within the library. Swift has been removed from the current build. You can still use this version by setting allow_boost_1_64 to true, but recent chats and highlighting rules will reset." + env["PROJECTS"].remove("Swift") |