From d0ea9dfa0c297f83516f7f344d6dea574db3c03a Mon Sep 17 00:00:00 2001 From: eelke Date: Sun, 26 Feb 2017 19:29:50 +0100 Subject: [PATCH] Moved some parts to a static lib so both the executable and the tests can link to it. Written additional tests. --- core/Core.cpp | 6 + core/Core.h | 12 + {src => core}/CsvWriter.cpp | 0 {src => core}/CsvWriter.h | 0 core/Expected.h | 269 ++++++++++++++++++ core/PasswordManager.cpp | 244 ++++++++++++++++ core/PasswordManager.h | 66 +++++ {src => core}/ScopeGuard.h | 0 core/SqlLexer.cpp | 162 +++++++++++ {src => core}/SqlLexer.h | 7 +- core/core.pro | 43 +++ core/my_boost_assert_handler.cpp | 20 ++ pglabAll.pro | 5 +- src/ConnectionConfig.cpp | 73 ++++- src/ConnectionConfig.h | 11 + src/ConnectionList.cpp | 148 ++++++++++ src/ConnectionList.h | 54 ++++ src/ConnectionListModel.cpp | 164 ++--------- src/ConnectionListModel.h | 24 +- src/ConnectionManagerWindow.cpp | 70 ++++- src/ConnectionManagerWindow.h | 10 +- src/ConnectionManagerWindow.ui | 7 + src/Expected.h | 156 ---------- src/MasterController.cpp | 10 +- src/MasterController.h | 8 +- src/ParamTypeDelegate.cpp | 1 + src/SqlLexer.cpp | 134 --------- src/SqlSyntaxHighlighter.cpp | 8 +- src/src.pro | 35 ++- src/typeselectionitemmodel.h | 1 - src/util.cpp | 3 + src/util.h | 18 ++ tests/auto/mycase/main.cpp | 6 +- tests/auto/mycase/mycase.pro | 22 +- tests/auto/mycase/tst_CsvWriter.h | 113 ++++++++ tests/auto/mycase/tst_PasswordManager.h | 73 +++++ .../mycase/{tst_mycase.h => tst_SqlLexer.h} | 11 +- tests/auto/mycase/tst_expected.h | 203 +++++++++++++ tests/auto/mycase/tst_scopeguard.h | 63 ++++ 39 files changed, 1767 insertions(+), 493 deletions(-) create mode 100644 core/Core.cpp create mode 100644 core/Core.h rename {src => core}/CsvWriter.cpp (100%) rename {src => core}/CsvWriter.h (100%) create mode 100644 core/Expected.h create mode 100644 core/PasswordManager.cpp create mode 100644 core/PasswordManager.h rename {src => core}/ScopeGuard.h (100%) create mode 100644 core/SqlLexer.cpp rename {src => core}/SqlLexer.h (80%) create mode 100644 core/core.pro create mode 100644 core/my_boost_assert_handler.cpp create mode 100644 src/ConnectionList.cpp create mode 100644 src/ConnectionList.h delete mode 100644 src/Expected.h delete mode 100644 src/SqlLexer.cpp create mode 100644 tests/auto/mycase/tst_CsvWriter.h create mode 100644 tests/auto/mycase/tst_PasswordManager.h rename tests/auto/mycase/{tst_mycase.h => tst_SqlLexer.h} (83%) create mode 100644 tests/auto/mycase/tst_expected.h create mode 100644 tests/auto/mycase/tst_scopeguard.h diff --git a/core/Core.cpp b/core/Core.cpp new file mode 100644 index 0000000..dd60b7d --- /dev/null +++ b/core/Core.cpp @@ -0,0 +1,6 @@ +#include "Core.h" + + +Core::Core() +{ +} diff --git a/core/Core.h b/core/Core.h new file mode 100644 index 0000000..6ecb6d3 --- /dev/null +++ b/core/Core.h @@ -0,0 +1,12 @@ +#ifndef CORE_H +#define CORE_H + + +class Core +{ + +public: + Core(); +}; + +#endif // CORE_H diff --git a/src/CsvWriter.cpp b/core/CsvWriter.cpp similarity index 100% rename from src/CsvWriter.cpp rename to core/CsvWriter.cpp diff --git a/src/CsvWriter.h b/core/CsvWriter.h similarity index 100% rename from src/CsvWriter.h rename to core/CsvWriter.h diff --git a/core/Expected.h b/core/Expected.h new file mode 100644 index 0000000..d53a19b --- /dev/null +++ b/core/Expected.h @@ -0,0 +1,269 @@ +#ifndef EXPECTED_H +#define EXPECTED_H + +#include + +template +class Expected { + union { + T m_value; + std::exception_ptr m_error; + }; + bool m_valid; + Expected() {} // internal use + +public: + + Expected(const T& rhs) + : m_value(rhs), m_valid(true) + {} + + Expected(T&& rhs) + : m_value(std::move(rhs)) + , m_valid(true) + {} + + + Expected(const Expected& rhs) + : m_valid(rhs.valid) + { + if (m_valid) { + new (&m_value) T(rhs.m_value); + } + else { + new (&m_error) std::exception_ptr(rhs.m_error); + } + } + + Expected(Expected &&rhs) + : m_valid(rhs.m_valid) + { + if (m_valid) { + new (&m_value) T(std::move(rhs.m_value)); + } + else { + new (&m_error) std::exception_ptr(std::move(rhs.m_error)); + } + } + + ~Expected() + { + if (m_valid) { + m_value.~T(); + } + else { + using std::exception_ptr; + m_error.~exception_ptr(); + } + } + +// void swap(Expected& rhs) +// { +// if (m_valid) { +// if (rhs.m_valid) { +// using std::swap; +// swap(m_value, rhs.m_value); +// } +// else { +// auto t = std::move(rhs.m_error); +// new(&rhs.m_value) T(std::move(m_value)); +// new(&m_error) std::exception_ptr(t); +// std::swap(m_valid, rhs.m_valid); +// } +// } +// else { +// if (rhs.m_valid) { +// rhs.swap(*this); +// } +// else { +// using std::swap; +// swap(m_error, rhs.m_error); +// std::swap(m_valid, rhs.m_valid); +// } +// } +// } + + template + static Expected fromException(const E& exception) + { + if (typeid(exception) != typeid(E)) { + throw std::invalid_argument("slicing detected"); + } + return fromException(std::make_exception_ptr(exception)); + } + + static Expected fromException(std::exception_ptr p) + { + Expected result; + result.m_valid = false; + new (&result.m_error) std::exception_ptr(std::move(p)); + return result; + } + + static Expected fromException() + { + return fromException(std::current_exception()); + } + + bool valid() const + { + return m_valid; + } + + T& get() + { + if (!m_valid) { + std::rethrow_exception(m_error); + } + return m_value; + } + + const T& get() const + { + if (!m_valid) { + std::rethrow_exception(m_error); + } + return m_value; + } + + template + bool hasException() const + { + try { + if (!m_valid) { + std::rethrow_exception(m_error); + } + } + catch (const E& ) { + return true; + } + catch (...) { + } + return false; + } + + template + static Expected fromCode(F fun) + { + try { + return Expected(fun()); + } + catch (...) { + return fromException(); + } + } +}; + + +template <> +class Expected { + std::exception_ptr m_error; + bool m_valid; + +public: + + Expected() + : m_valid(true) + {} + + + Expected(const Expected& rhs) + : m_valid(rhs.m_valid) + , m_error(rhs.m_error) + {} + + Expected(Expected &&rhs) + : m_valid(rhs.m_valid) + , m_error(std::move(rhs.m_error)) + {} + + ~Expected() + {} + +// void swap(Expected& rhs) +// { +// if (m_valid) { +// if (!rhs.m_valid) { +// m_error = rhs.m_error; +// std::swap(m_valid, rhs.m_valid); +// } +// } +// else { +// if (rhs.m_valid) { +// rhs.swap(*this); +// } +// else { +// using std::swap; +// swap(m_error, rhs.m_error); +// std::swap(m_valid, rhs.m_valid); +// } +// } +// } + + template + static Expected fromException(const E& exception) + { + if (typeid(exception) != typeid(E)) { + throw std::invalid_argument("slicing detected"); + } + return fromException(std::make_exception_ptr(exception)); + } + + static Expected fromException(std::exception_ptr p) + { + Expected result; + result.m_valid = false; + result.m_error = std::exception_ptr(std::move(p)); + return result; + } + + static Expected fromException() + { + return fromException(std::current_exception()); + } + + bool valid() const + { + return m_valid; + } + + void get() const + { + if (!m_valid) { + std::rethrow_exception(m_error); + } + } + + + template + bool hasException() const + { + try { + if (!m_valid) { + std::rethrow_exception(m_error); + } + } + catch (const E& ) { + return true; + } + catch (...) { + } + return false; + } + + template + static Expected fromCode(F fun) + { + try { + fun(); + return Expected(); + } + catch (...) { + return fromException(); + } + } +}; + + + +#endif // EXPECTED_H diff --git a/core/PasswordManager.cpp b/core/PasswordManager.cpp new file mode 100644 index 0000000..597e7c9 --- /dev/null +++ b/core/PasswordManager.cpp @@ -0,0 +1,244 @@ +#include "PasswordManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Core.h" + +using namespace Botan; + +namespace { + + /* + First 24 bits of SHA-256("Botan Cryptobox"), followed by 8 0 bits + for later use as flags, etc if needed + */ + const uint8_t c_PasswordVersionCode = 0x10; + + const size_t c_VersionCodeLen = 1; + + const size_t CIPHER_KEY_LEN = 32; + const size_t CIPHER_IV_LEN = 16; + const size_t MAC_KEY_LEN = 32; + const size_t MAC_OUTPUT_LEN = 20; + const size_t PBKDF_SALT_LEN = 10; + const size_t PBKDF_ITERATIONS = 8 * 1024; + + const size_t PBKDF_OUTPUT_LEN = CIPHER_KEY_LEN + CIPHER_IV_LEN + MAC_KEY_LEN; + + const char * const c_Cipher = "Serpent/CTR-BE"; + + const char * const c_IniGroupSecurity = "Security"; + + + + StrengthenedKey generateKey(const std::string &passphrase, const uint8_t *salt, int saltlength) + { + PKCS5_PBKDF2 pbkdf(new HMAC(new SHA_512)); + OctetString master_key = pbkdf.derive_key( + PBKDF_OUTPUT_LEN, + passphrase, + salt, saltlength, + PBKDF_ITERATIONS); + + const uint8_t* mk = master_key.begin(); + + return StrengthenedKey( + SymmetricKey(mk, CIPHER_KEY_LEN), + SymmetricKey(mk + CIPHER_KEY_LEN, MAC_KEY_LEN), + InitializationVector(mk + CIPHER_KEY_LEN + MAC_KEY_LEN, CIPHER_IV_LEN)); + } + +// secure_vector pbkdf_salt(PBKDF_SALT_LEN); +// rng.randomize( pbkdf_salt.data(), pbkdf_salt.size()); +// StrengthenedKey strengthened_key = generateKey(passphrase, pbkdf_salt.data(), pbkdf_salt.size()); + + + std::string encrypt(const std::string &input, + const StrengthenedKey &strengthened_key) + { + + Pipe pipe(get_cipher(c_Cipher, strengthened_key.cipher_key, + strengthened_key.iv, ENCRYPTION), + new Fork( + nullptr, + new MAC_Filter(new HMAC(new SHA_512), + strengthened_key.mac_key, MAC_OUTPUT_LEN))); + + pipe.process_msg((const uint8_t*)input.data(), input.length()); + + /* + Output format is: + mac (20 bytes) + ciphertext + */ + const size_t ciphertext_len = pipe.remaining(0); + std::vector out_buf(MAC_OUTPUT_LEN + ciphertext_len); + + BOTAN_ASSERT_EQUAL( + pipe.read(&out_buf[0], MAC_OUTPUT_LEN, 1), + MAC_OUTPUT_LEN, "MAC output"); + BOTAN_ASSERT_EQUAL( + pipe.read(&out_buf[MAC_OUTPUT_LEN], ciphertext_len, 0), + ciphertext_len, "Ciphertext size"); + + return base64_encode(out_buf.data(), out_buf.size()); + } + + std::string decrypt(const std::string &input, const StrengthenedKey &strengthened_key) + { + secure_vector ciphertext = base64_decode(input); + + if(ciphertext.size() < (MAC_OUTPUT_LEN)) { + throw Decoding_Error("Invalid encrypted password input"); + } + + Pipe pipe(new Fork( + get_cipher(c_Cipher, strengthened_key.cipher_key, strengthened_key.iv, DECRYPTION), + new MAC_Filter(new HMAC(new SHA_512), strengthened_key.mac_key, MAC_OUTPUT_LEN) + )); + + const size_t ciphertext_offset = MAC_OUTPUT_LEN; + pipe.process_msg(&ciphertext[ciphertext_offset], ciphertext.size() - ciphertext_offset); + + uint8_t computed_mac[MAC_OUTPUT_LEN]; + BOTAN_ASSERT_EQUAL(MAC_OUTPUT_LEN, pipe.read(computed_mac, MAC_OUTPUT_LEN, 1), "MAC size"); + + if(!same_mem(computed_mac, &ciphertext[0], MAC_OUTPUT_LEN)) { + throw Decoding_Error("Encrypted password integrity failure"); + } + + return pipe.read_all_as_string(0); + } + + struct constants { + const int pbkdf_salt_len; + }; + + constants v1_consts = { + 10 + }; + +} // end of unnamed namespace + +/* + * File layout: + * + * Header + * version + * key_salt + * hash_salt + * master_hash + * + * + * Passwords + * key = pw + */ + + + +PasswordManager::PasswordManager() +{ + +} + +Expected PasswordManager::unlock(const std::string &master_password) +{ + try { + bool result = false; + if (m_masterHash.length() == 0 && master_password.empty()) { + result = true; + } else { + StrengthenedKey key = generateKey(master_password, m_keySalt.begin(), m_keySalt.length()); + OctetString hash = hashStrengthenedKey(key, m_hashSalt); + + BOOST_ASSERT_MSG(hash.length() == m_masterHash.length(), "Both hashes should have the same length! Versioning error?"); + + if (same_mem(m_masterHash.begin(), hash.begin(), hash.length())) { + result = true; + m_masterKey = key; + } + } + return result; + } catch (...) { + return Expected::fromException(); + } +} + +Expected PasswordManager::changeMasterPassword(const std::string &old_master_password, + const std::string &new_master_password) +{ + try { + bool result = false; + if (m_masterHash.length() == 0 && old_master_password.empty()) { + // Nothing set yet so we initialize for first use + m_keySalt = OctetString(m_rng, v1_consts.pbkdf_salt_len); + m_masterKey = generateKey(new_master_password, m_keySalt.begin(), m_keySalt.length()); + + m_hashSalt = OctetString(m_rng, v1_consts.pbkdf_salt_len); + m_masterHash = hashStrengthenedKey(m_masterKey, m_hashSalt); + result = true; + } + return result; + } catch (...) { + return Expected::fromException(); + } +} + +void PasswordManager::lock() +{ + m_masterKey = StrengthenedKey(); +} + +bool PasswordManager::locked() const +{ + return m_masterKey.cipher_key.size() == 0; +} + +Expected PasswordManager::savePassword(const std::string &key, const std::string &password) +{ + if (locked()) { + return Expected::fromException(std::logic_error("Need to unlock the password manager first")); + } + std::string epw = encrypt(password, m_masterKey); + m_store.emplace(key, epw); + + return Expected(); +} + +Expected PasswordManager::getPassword(const std::string &key, std::string &out) +{ + if (locked()) { + return Expected::fromException(std::logic_error("Need to unlock the password manager first")); + } + auto fi = m_store.find(key); + + bool result = false; + if (fi != m_store.end()) { + out = decrypt(fi->second, m_masterKey); + result = true; + } + + return result; +} + +Botan::OctetString PasswordManager::hashStrengthenedKey(const StrengthenedKey &key, const OctetString &salt) +{ + std::unique_ptr hash3(Botan::HashFunction::create("SHA-3")); + BOOST_ASSERT_MSG(hash3 != nullptr, "SHA-3 algorithm not available"); + hash3->update(salt.begin(), salt.length()); + hash3->update(key.cipher_key.begin(), key.cipher_key.length()); + hash3->update(key.mac_key.begin(), key.mac_key.length()); + hash3->update(key.iv.begin(), key.iv.length()); + return hash3->final(); +} diff --git a/core/PasswordManager.h b/core/PasswordManager.h new file mode 100644 index 0000000..2f66884 --- /dev/null +++ b/core/PasswordManager.h @@ -0,0 +1,66 @@ +#ifndef PASSWORDMANAGER_H +#define PASSWORDMANAGER_H + +#include "Expected.h" +#include + +#include +#include + +#include + +struct StrengthenedKey { + Botan::SymmetricKey cipher_key; + Botan::SymmetricKey mac_key; + Botan::InitializationVector iv; + + StrengthenedKey() {} + StrengthenedKey(const Botan::SymmetricKey &ck, const Botan::SymmetricKey &mk, + const Botan::InitializationVector &i) + : cipher_key(ck) + , mac_key(mk) + , iv(i) + {} +}; + + +class PasswordManager { +public: + +// static PasswordManager create(const std::string &file_name); + + PasswordManager(); + /** Unlocks the passwords of the connections. + * + * \return Normally it return a bool specifying if the password was accepted. + * on rare occasions it could return an error. + */ + Expected unlock(const std::string &master_password); + + Expected changeMasterPassword(const std::string &master_password, + const std::string &new_master_password); + + /** Forget master password + */ + void lock(); + bool locked() const; + + Expected savePassword(const std::string &key, const std::string &password); + Expected getPassword(const std::string &key, std::string &out); + +private: + Botan::AutoSeeded_RNG m_rng; + Botan::OctetString m_keySalt; // salt for generating crypto key + StrengthenedKey m_masterKey; // crypto key + Botan::OctetString m_hashSalt; // salt of the hash of the passphrase + Botan::OctetString m_masterHash; // hash for checking the passphrase + + using t_KeyPasswords = std::map; + + t_KeyPasswords m_store; + + static Botan::OctetString hashStrengthenedKey(const StrengthenedKey &key, const Botan::OctetString &salt); +}; + + +#endif // PASSWORDMANAGER_H diff --git a/src/ScopeGuard.h b/core/ScopeGuard.h similarity index 100% rename from src/ScopeGuard.h rename to core/ScopeGuard.h diff --git a/core/SqlLexer.cpp b/core/SqlLexer.cpp new file mode 100644 index 0000000..bbc8deb --- /dev/null +++ b/core/SqlLexer.cpp @@ -0,0 +1,162 @@ +#include "SqlLexer.h" + +SqlLexer::SqlLexer(const QString &block, LexerState currentstate) + : m_block(block) + , m_state(currentstate) +{} + +QChar SqlLexer::nextChar() +{ + QChar result = QChar::Null; + if (m_pos < m_block.size()) { + result = m_block.at(m_pos++); + } + return result; +} + +QChar SqlLexer::peekChar() +{ + QChar result = QChar::Null; + if (m_pos < m_block.size()) { + result = m_block.at(m_pos); + } + return result; +} + +/** + * @brief NextBasicToken + * @param in + * @param ofs + * @param start + * @param length + * @return false when input seems invalid, it will return what it did recognize but something wasn't right, parser should try to recover + */ +bool SqlLexer::nextBasicToken(int &startpos, int &length, BasicTokenType &tokentype, QString &out) +{ + // Basically chops based on white space + // it does also recognize comments and quoted strings/identifiers + while (true) { + startpos = m_pos; + QChar c = nextChar(); +// if (LexerState::Null == m_state) { + if (c.isSpace()) { + // Just skip whitespace + } + else if (c == '-' && peekChar() == '-') { // two dashes, start of comment + // Loop till end of line or end of block + c = nextChar(); + for (;;) { + c = peekChar(); + if (c != QChar::Null && c != '\n') + nextChar(); + else + break; + } + length = m_pos - startpos; + tokentype = BasicTokenType::Comment; + return true; + } + else if (c == '\'') { + // Single quoted string so it's an SQL text literal + while (true) { + c = peekChar(); + if (c == QChar::Null || c == '\n') { + // unexpected end, pretend nothings wrong + length = m_pos - startpos; + tokentype = BasicTokenType::QuotedString; + return true; + } + else { + nextChar(); + if (c == '\'') { + // maybe end of string literal + if (peekChar() == '\'') { + // Nope, just double quote to escape quote + nextChar(); // eat it + } + else { + length = m_pos - startpos; + tokentype = BasicTokenType::QuotedString; + return true; + } + } + } + } + } + else if (c == '"') { + // Double quoted identifier + while (true) { + c = peekChar(); + if (c == QChar::Null || c == '\n') { + // unexpected end, pretend nothings wrong + length = m_pos - startpos; + tokentype = BasicTokenType::QuotedIdentifier; + return true; + } + else { + nextChar(); + if (c == '"') { + // maybe end of string literal + if (peekChar() == '"') { + // Nope, just double quote to escape quote + nextChar(); // eat it + } + else { + length = m_pos - startpos; + tokentype = BasicTokenType::QuotedIdentifier; + return true; + } + } + } + } + } +// else if (c == '/' && peekChar() == '*') { +// nextChar(); +// m_state = LexerState::InBlockComment; +// } + else if (c == QChar::Null) { + break; + } + else { + // Undetermined symbol + for (;;) { + c = peekChar(); + if (c.isLetterOrNumber() || c == '_') + nextChar(); + else + break; + } + length = m_pos - startpos; + tokentype = BasicTokenType::Symbol; + QStringRef sr(&m_block, startpos, length); + out = sr.toString(); + return true; + } +// } +// else if (LexerState::InBlockComment == m_state) { +// if (c == QChar::Null) { +// // eof current buffer, we need to return state so +// if (m_pos == startpos) { +// break; +// } +// else { +// length = m_pos - startpos; +// tokentype = BasicTokenType::OpenBlockComment; +// return true; +// } +// } +// else if (c == '*') { +// nextChar(); +// if (peekChar() == '/') { +// nextChar(); +// length = m_pos - startpos; +// tokentype = BasicTokenType::BlockComment; +// m_state = LexerState::Null; +// return true; +// } +// } +// } + } + return false; +} + diff --git a/src/SqlLexer.h b/core/SqlLexer.h similarity index 80% rename from src/SqlLexer.h rename to core/SqlLexer.h index 666e121..c3e51f8 100644 --- a/src/SqlLexer.h +++ b/core/SqlLexer.h @@ -8,6 +8,8 @@ enum class BasicTokenType { End, // End of input Symbol, // can be many things, keyword, object name, operator, .. Comment, + BlockComment, + OpenBlockComment, // Busy with a block comment end not detected before end of current input QuotedString, DollarQuotedString, QuotedIdentifier @@ -15,7 +17,8 @@ enum class BasicTokenType { enum class LexerState { Null, - InDollarQuotedString + InDollarQuotedString, + InBlockComment }; @@ -34,11 +37,13 @@ public: */ bool nextBasicToken(int &startpos, int &length, BasicTokenType &tokentype, QString &out); + LexerState currentState() const { return m_state; } private: QString m_block; int m_pos = 0; LexerState m_state; + }; #endif // SQLLEXER_H diff --git a/core/core.pro b/core/core.pro new file mode 100644 index 0000000..1456d6d --- /dev/null +++ b/core/core.pro @@ -0,0 +1,43 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2017-02-26T10:51:14 +# +#------------------------------------------------- + +QT -= gui + +TARGET = core +TEMPLATE = lib +CONFIG += staticlib c++11 + +INCLUDEPATH += C:\prog\include C:\VSproj\boost_1_63_0 +DEFINES += WIN32_LEAN_AND_MEAN NOMINMAX +LIBS += /LIBPATH:C:\VSproj\boost_1_63_0\stage\lib /LIBPATH:c:\prog\lib\ libpq.lib fmt.lib User32.lib ws2_32.lib + + +# The following define makes your compiler emit warnings if you use +# any feature of Qt which as been marked as deprecated (the exact warnings +# depend on your compiler). Please consult the documentation of the +# deprecated API in order to know how to port your code away from it. +DEFINES += QT_DEPRECATED_WARNINGS + +# You can also make your code fail to compile if you use deprecated APIs. +# In order to do so, uncomment the following line. +# You can also select to disable deprecated APIs only up to a certain version of Qt. +#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 + +SOURCES += Core.cpp \ + my_boost_assert_handler.cpp \ + SqlLexer.cpp \ + PasswordManager.cpp \ + CsvWriter.cpp + +HEADERS += Core.h \ + PasswordManager.h \ + SqlLexer.h \ + ScopeGuard.h \ + CsvWriter.h +unix { + target.path = /usr/lib + INSTALLS += target +} diff --git a/core/my_boost_assert_handler.cpp b/core/my_boost_assert_handler.cpp new file mode 100644 index 0000000..fda7bba --- /dev/null +++ b/core/my_boost_assert_handler.cpp @@ -0,0 +1,20 @@ +#include +#include + +namespace boost +{ + void assertion_failed(char const * expr, char const * function, char const * file, long line) + { + std::ostringstream out; + out << "Assertion failure int " << function << " " << file << ":" << line; + throw std::runtime_error(out.str()); + } + + void assertion_failed_msg(char const * expr, char const * msg, char const * function, char const * file, long line) + { + std::ostringstream out; + out << "Assertion failure int " << function << " " << file << ":" << line << "\n"<< msg; + throw std::runtime_error(out.str()); + } +} + diff --git a/pglabAll.pro b/pglabAll.pro index 265ed96..14311a6 100644 --- a/pglabAll.pro +++ b/pglabAll.pro @@ -1,6 +1,9 @@ TEMPLATE = subdirs -SUBDIRS += src +DEFINES += BOOST_ENABLE_ASSERT_HANDLER + +SUBDIRS += src \ + core CONFIG(debug, debug|release) { SUBDIRS += tests diff --git a/src/ConnectionConfig.cpp b/src/ConnectionConfig.cpp index bcd961a..ae7fe88 100644 --- a/src/ConnectionConfig.cpp +++ b/src/ConnectionConfig.cpp @@ -57,7 +57,10 @@ ConnectionConfig::ConnectionConfig() void ConnectionConfig::setName(std::string desc) { - m_name = std::move(desc); + if (m_name != desc) { + m_dirty = true; + m_name = std::move(desc); + } } const std::string& ConnectionConfig::name() const @@ -67,7 +70,10 @@ const std::string& ConnectionConfig::name() const void ConnectionConfig::setHost(std::string host) { - m_host = std::move(host); + if (m_host != host) { + m_dirty = true; + m_host = std::move(host); + } } const std::string& ConnectionConfig::host() const @@ -77,7 +83,10 @@ const std::string& ConnectionConfig::host() const void ConnectionConfig::setHostAddr(std::string v) { - m_hostaddr = std::move(v); + if (m_hostaddr != v) { + m_dirty = true; + m_hostaddr = std::move(v); + } } const std::string& ConnectionConfig::hostAddr() const @@ -87,7 +96,11 @@ const std::string& ConnectionConfig::hostAddr() const void ConnectionConfig::setPort(unsigned short port) { - m_port = std::to_string(port); + auto p = std::to_string(port); + if (m_port != p) { + m_dirty = true; + m_port = p; + } } unsigned short ConnectionConfig::port() const @@ -97,7 +110,11 @@ unsigned short ConnectionConfig::port() const void ConnectionConfig::setUser(std::string v) { - m_user = std::move(v); + if (m_user != v) { + m_dirty = true; + m_user = std::move(v); + } + } const std::string& ConnectionConfig::user() const @@ -107,7 +124,10 @@ const std::string& ConnectionConfig::user() const void ConnectionConfig::setPassword(std::string v) { - m_password = std::move(v); + if (m_password != v) { + m_dirty = true; + m_password = std::move(v); + } } const std::string& ConnectionConfig::password() const @@ -117,7 +137,10 @@ const std::string& ConnectionConfig::password() const void ConnectionConfig::setDbname(std::string v) { - m_dbname = std::move(v); + if (m_dbname != v) { + m_dirty = true; + m_dbname = std::move(v); + } } const std::string& ConnectionConfig::dbname() const @@ -127,7 +150,11 @@ const std::string& ConnectionConfig::dbname() const void ConnectionConfig::setSslMode(SslMode m) { - m_sslMode = SslModeToString(m); + auto v = SslModeToString(m); + if (m_sslMode != v) { + m_dirty = true; + m_sslMode = v; + } } SslMode ConnectionConfig::sslMode() const @@ -137,7 +164,10 @@ SslMode ConnectionConfig::sslMode() const void ConnectionConfig::setSslCert(std::string v) { - m_sslCert = std::move(v); + if (m_sslCert != v) { + m_dirty = true; + m_sslCert = std::move(v); + } } const std::string& ConnectionConfig::sslCert() const @@ -147,7 +177,10 @@ const std::string& ConnectionConfig::sslCert() const void ConnectionConfig::setSslKey(std::string v) { - m_sslKey = std::move(v); + if (m_sslKey != v) { + m_dirty = true; + m_sslKey = std::move(v); + } } const std::string& ConnectionConfig::sslKey() const @@ -157,7 +190,10 @@ const std::string& ConnectionConfig::sslKey() const void ConnectionConfig::setSslRootCert(std::string v) { - m_sslRootCert = std::move(v); + if (m_sslRootCert != v) { + m_dirty = true; + m_sslRootCert = std::move(v); + } } const std::string& ConnectionConfig::sslRootCert() const @@ -167,7 +203,10 @@ const std::string& ConnectionConfig::sslRootCert() const void ConnectionConfig::setSslCrl(std::string v) { - m_sslCrl = std::move(v); + if (m_sslCrl != v) { + m_dirty = true; + m_sslCrl = std::move(v); + } } const std::string& ConnectionConfig::sslCrl() const @@ -210,3 +249,13 @@ bool ConnectionConfig::isSameDatabase(const ConnectionConfig &rhs) const && m_password == rhs.m_password && m_dbname == rhs.m_dbname; } + +bool ConnectionConfig::dirty() const +{ + return m_dirty; +} + +void ConnectionConfig::clean() +{ + m_dirty = false; +} diff --git a/src/ConnectionConfig.h b/src/ConnectionConfig.h index 2e12228..831ca76 100644 --- a/src/ConnectionConfig.h +++ b/src/ConnectionConfig.h @@ -13,6 +13,12 @@ enum class SslMode { verify_full=5 }; +enum class PasswordMode { + Unsave, + Encrypted, + DontSave +}; + class ConnectionConfig { public: ConnectionConfig(); @@ -57,6 +63,9 @@ public: const char * const * getValues() const; bool isSameDatabase(const ConnectionConfig &rhs) const; + + bool dirty() const; + void clean(); private: std::string m_name; std::string m_host; @@ -75,6 +84,8 @@ private: std::string m_applicationName; + bool m_dirty = false; + static std::vector s_keywords; mutable std::vector m_values; }; diff --git a/src/ConnectionList.cpp b/src/ConnectionList.cpp new file mode 100644 index 0000000..39eb6ed --- /dev/null +++ b/src/ConnectionList.cpp @@ -0,0 +1,148 @@ +#include "ConnectionList.h" +#include "scopeguard.h" +#include "util.h" +#include +#include +#include + + +namespace { + + /** Saves a connection configuration. + + Before calling this you may want to call beginGroup. + */ + void SaveConnectionConfig(QSettings &settings, const ConnectionConfig &cc) + { + settings.setValue("name", stdStrToQ(cc.name())); + settings.setValue("host", stdStrToQ(cc.host())); + settings.setValue("hostaddr", stdStrToQ(cc.hostAddr())); + settings.setValue("port", cc.port()); + settings.setValue("user", stdStrToQ(cc.user())); + settings.setValue("password", stdStrToQ(cc.password())); + settings.setValue("dbname", stdStrToQ(cc.dbname())); + settings.setValue("sslmode", (int)cc.sslMode()); + settings.setValue("sslcert", stdStrToQ(cc.sslCert())); + settings.setValue("sslkey", stdStrToQ(cc.sslKey())); + settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert())); + settings.setValue("sslcrl", stdStrToQ(cc.sslCrl())); + } + + void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc) + { + cc.setName(qvarToStdStr(settings.value("name"))); + cc.setHost(qvarToStdStr(settings.value("host"))); + cc.setHostAddr(qvarToStdStr(settings.value("hostaddr"))); + cc.setPort(settings.value("port", 5432).toInt()); + cc.setUser(qvarToStdStr(settings.value("user"))); + // std::string encpw = qvarToStdStr(settings.value("encryptedpw")); + // if (encpw.empty()) { + cc.setPassword(qvarToStdStr(settings.value("password"))); + // } + // else { + // cc.setEncryptedPassword(encpw); + // } + cc.setDbname(qvarToStdStr(settings.value("dbname"))); + cc.setSslMode((SslMode)settings.value("sslmode").toInt()); + cc.setSslCert(qvarToStdStr(settings.value("sslcert"))); + cc.setSslKey(qvarToStdStr(settings.value("sslkey"))); + cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert"))); + cc.setSslCrl(qvarToStdStr(settings.value("sslcrl"))); + } + + + +} // end of unnamed namespace + +/// \todo should return an expected as creation of the folder can fail +QString ConnectionList::iniFileName() +{ + QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); + QDir dir(path); + if (!dir.exists()) { + dir.mkpath("."); + } + path += "/connections.ini"; + return path; +} + + +ConnectionList::ConnectionList() +{ + +} + +int ConnectionList::createNew() +{ + m_connections.emplace_back(QUuid::createUuid(), ConnectionConfig()); + return m_connections.size()-1; +} + +void ConnectionList::remove(int idx, int count) +{ + auto f = m_connections.begin() + idx; + auto l = f + count; + deleteFromIni(f, l); + m_connections.erase(f, l); +} + +void ConnectionList::deleteFromIni(t_Connections::iterator begin, t_Connections::iterator end) +{ + QString file_name = iniFileName(); + QSettings settings(file_name, QSettings::IniFormat); + for (auto i = begin; i != end; ++i) { + settings.remove(i->m_uuid.toString()); + } +} + +void ConnectionList::load() +{ + QString file_name = iniFileName(); + QSettings settings(file_name, QSettings::IniFormat); + auto groups = settings.childGroups(); + for (auto grp : groups) { + if (grp == "c_IniGroupSecurity") { + // Read security settings + + } else { + QUuid uuid(grp); + if ( ! uuid.isNull() ) { + settings.beginGroup(grp); + SCOPE_EXIT { settings.endGroup(); }; + + ConnectionConfig cc; + LoadConnectionConfig(settings, cc); + m_connections.emplace_back(uuid, cc); + } + } + } +} + +void ConnectionList::save() +{ + QString file_name = iniFileName(); + QSettings settings(file_name, QSettings::IniFormat); + for (auto& e : m_connections) { + settings.beginGroup(e.m_uuid.toString()); + SCOPE_EXIT { settings.endGroup(); }; + + SaveConnectionConfig(settings, e.m_config); + e.m_config.clean(); + } + settings.sync(); +} + +void ConnectionList::save(int index) +{ + if (index >= 0 && index < (int)m_connections.size()) { + auto& e = m_connections[index]; + if (e.m_config.dirty()) { + QString file_name = iniFileName(); + QSettings settings(file_name, QSettings::IniFormat); + settings.beginGroup(e.m_uuid.toString()); + SaveConnectionConfig(settings, e.m_config); + e.m_config.clean(); + settings.sync(); + } + } +} diff --git a/src/ConnectionList.h b/src/ConnectionList.h new file mode 100644 index 0000000..f9ba131 --- /dev/null +++ b/src/ConnectionList.h @@ -0,0 +1,54 @@ +#ifndef CONNECTIONLIST_H +#define CONNECTIONLIST_H + +#include "ConnectionConfig.h" + +#include +#include +#include +#include "Expected.h" + + +class ConnectionList { +private: + static QString iniFileName(); + +public: + + ConnectionList(); + int size() const { return m_connections.size(); } + + ConnectionConfig& getConfigByIdx(int idx) + { + return m_connections.at(idx).m_config; + } + + int createNew(); + + void remove(int idx, int count); + + void load(); + void save(); + void save(int index); + + +private: + class LijstElem { + public: + QUuid m_uuid; + ConnectionConfig m_config; + + LijstElem(const QUuid id, const ConnectionConfig &cfg) + : m_uuid(id), m_config(cfg) + {} + }; + + using t_Connections = std::vector; + t_Connections m_connections; + + void deleteFromIni(t_Connections::iterator begin, t_Connections::iterator end); + + +}; + +#endif // CONNECTIONLIST_H diff --git a/src/ConnectionListModel.cpp b/src/ConnectionListModel.cpp index cf85a07..fef8bda 100644 --- a/src/ConnectionListModel.cpp +++ b/src/ConnectionListModel.cpp @@ -1,74 +1,27 @@ -#include "connectionlistmodel.h" -#include -#include -#include +#include "ConnectionListModel.h" +#include "ConnectionList.h" #include "scopeguard.h" +#include "util.h" -inline QString stdStrToQ(const std::string &s) -{ - return QString::fromUtf8(s.c_str()); -} - -inline std::string qStrToStd(const QString &s) -{ - return std::string(s.toUtf8().data()); -} - -inline std::string qvarToStdStr(const QVariant &c) -{ - return qStrToStd(c.toString()); -} +#include -/** Saves a connection configuration. - -Before calling this you may want to call beginGroup. -*/ -void SaveConnectionConfig(QSettings &settings, const ConnectionConfig &cc) -{ - settings.setValue("name", stdStrToQ(cc.name())); - settings.setValue("host", stdStrToQ(cc.host())); - settings.setValue("hostaddr", stdStrToQ(cc.hostAddr())); - settings.setValue("port", cc.port()); - settings.setValue("user", stdStrToQ(cc.user())); - settings.setValue("password", stdStrToQ(cc.password())); - settings.setValue("dbname", stdStrToQ(cc.dbname())); - settings.setValue("sslmode", (int)cc.sslMode()); - settings.setValue("sslcert", stdStrToQ(cc.sslCert())); - settings.setValue("sslkey", stdStrToQ(cc.sslKey())); - settings.setValue("sslrootcert", stdStrToQ(cc.sslRootCert())); - settings.setValue("sslcrl", stdStrToQ(cc.sslCrl())); -} - -void LoadConnectionConfig(QSettings &settings, ConnectionConfig &cc) -{ - cc.setName(qvarToStdStr(settings.value("name"))); - cc.setHost(qvarToStdStr(settings.value("host"))); - cc.setHostAddr(qvarToStdStr(settings.value("hostaddr"))); - cc.setPort(settings.value("port", 5432).toInt()); - cc.setUser(qvarToStdStr(settings.value("user"))); - cc.setPassword(qvarToStdStr(settings.value("password"))); - cc.setDbname(qvarToStdStr(settings.value("dbname"))); - cc.setSslMode((SslMode)settings.value("sslmode").toInt()); - cc.setSslCert(qvarToStdStr(settings.value("sslcert"))); - cc.setSslKey(qvarToStdStr(settings.value("sslkey"))); - cc.setSslRootCert(qvarToStdStr(settings.value("sslrootcert"))); - cc.setSslCrl(qvarToStdStr(settings.value("sslcrl"))); -} - - - -ConnectionListModel::ConnectionListModel(QObject *parent) +ConnectionListModel::ConnectionListModel(ConnectionList *conns, QObject *parent) : QAbstractListModel(parent) + , m_connections(conns) { - load(); +} + +ConnectionListModel::~ConnectionListModel() +{ + delete m_connections; } int ConnectionListModel::rowCount(const QModelIndex &parent) const { int result = 0; if (parent == QModelIndex()) { - result = m_connections.size(); + result = m_connections->size(); } return result; } @@ -84,7 +37,7 @@ QVariant ConnectionListModel::data(const QModelIndex &index, int role) const if (role == Qt::DisplayRole || role == Qt::EditRole) { int row = index.row(); int col = index.column(); - const ConnectionConfig& cfg = m_connections.at(row).m_config; + const ConnectionConfig& cfg = m_connections->getConfigByIdx(row); switch (col) { case 0: result = makeLongDescription(cfg); @@ -119,9 +72,10 @@ bool ConnectionListModel::setData(const QModelIndex &index, const QVariant &valu if (role == Qt::EditRole) { int row = index.row(); int col = index.column(); - auto& elem = m_connections.at(row); - elem.m_dirty = true; - ConnectionConfig& cfg = elem.m_config; +// auto& elem = m_connections.at(row); +// elem.m_dirty = true; +// ConnectionConfig& cfg = elem.m_config; + ConnectionConfig& cfg = m_connections->getConfigByIdx(row); if (col > 0) { result = true; } @@ -158,7 +112,7 @@ Qt::ItemFlags ConnectionListModel::flags(const QModelIndex &index) const { Qt::ItemFlags result; int row = index.row(); - if (row >= 0 && row < (int)m_connections.size()) { + if (row >= 0 && row < (int)m_connections->size()) { result = Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled; } return result; @@ -180,17 +134,17 @@ QString ConnectionListModel::makeLongDescription(const ConnectionConfig &cfg) return stdStrToQ(result); } -void ConnectionListModel::add(const ConnectionConfig &cfg) +void ConnectionListModel::newItem() { - m_connections.emplace_back(QUuid::createUuid(), cfg); - auto idx = createIndex(m_connections.size()-1, 0); + int i = m_connections->createNew(); + auto idx = createIndex(i, 0); emit dataChanged(idx, idx); } Expected ConnectionListModel::get(int row) { - if (row >= 0 && row < (int)m_connections.size()) { - return m_connections.at(row).m_config; + if (row >= 0 && row < (int)m_connections->size()) { + return m_connections->getConfigByIdx(row); } else { return Expected::fromException(std::out_of_range("Invalid row")); @@ -201,85 +155,29 @@ Expected ConnectionListModel::get(int row) bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &parent) { bool result = false; - if (row >= 0 && row < (int)m_connections.size()) { + if (row >= 0 && row < (int)m_connections->size()) { beginRemoveRows(parent, row, row + count -1); SCOPE_EXIT { endRemoveRows(); }; - auto f = m_connections.begin() + row; - auto l = f + count; - deleteFromIni(f, l); - m_connections.erase(f, l); + m_connections->remove(row, count); result = true; - } return result; } -/// \todo should return an expected as creation of the folder can fail -QString ConnectionListModel::iniFileName() -{ - QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); - QDir dir(path); - if (!dir.exists()) { - dir.mkpath("."); - } - path += "/connections.ini"; - return path; -} - -void ConnectionListModel::load() -{ - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - auto groups = settings.childGroups(); - for (auto grp : groups) { - QUuid uuid(grp); - if ( ! uuid.isNull() ) { - settings.beginGroup(grp); - SCOPE_EXIT { settings.endGroup(); }; - - ConnectionConfig cc; - LoadConnectionConfig(settings, cc); - m_connections.emplace_back(uuid, cc); - } - } -} +//void ConnectionListModel::load() +//{ +// m_connections->load(); +//} void ConnectionListModel::save() { - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - for (auto& e : m_connections) { - settings.beginGroup(e.m_uuid.toString()); - SCOPE_EXIT { settings.endGroup(); }; - - SaveConnectionConfig(settings, e.m_config); - } + m_connections->save(); } void ConnectionListModel::save(int index) { - if (index >= 0 && index < (int)m_connections.size()) { - auto& e = m_connections[index]; - if (e.m_dirty) { - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - settings.beginGroup(e.m_uuid.toString()); - SaveConnectionConfig(settings, e.m_config); - settings.sync(); - e.m_dirty = false; - } - } + m_connections->save(index); } - -void ConnectionListModel::deleteFromIni(t_Connections::iterator begin, t_Connections::iterator end) -{ - QString file_name = iniFileName(); - QSettings settings(file_name, QSettings::IniFormat); - for (auto i = begin; i != end; ++i) { - settings.remove(i->m_uuid.toString()); - } -} - diff --git a/src/ConnectionListModel.h b/src/ConnectionListModel.h index d25c0a9..acbfa31 100644 --- a/src/ConnectionListModel.h +++ b/src/ConnectionListModel.h @@ -5,11 +5,11 @@ #include #include -#include #include "connectionconfig.h" #include "expected.h" +class ConnectionList; /** \brief Model class for the list of connections. * @@ -18,7 +18,9 @@ class ConnectionListModel : public QAbstractListModel { Q_OBJECT public: - ConnectionListModel(QObject *parent); + ConnectionListModel(ConnectionList *conns, QObject *parent); + ConnectionListModel(const ConnectionListModel&) = delete; + ~ConnectionListModel(); virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override; virtual int columnCount(const QModelIndex &/*parent*/) const override; @@ -27,30 +29,16 @@ public: virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - void add(const ConnectionConfig &cfg); + void newItem(); Expected get(int row); virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; - void load(); void save(); void save(int index); private: - class LijstElem { - public: - QUuid m_uuid; - bool m_dirty = false; - ConnectionConfig m_config; - LijstElem(const QUuid id, const ConnectionConfig &cfg) - : m_uuid(id), m_config(cfg) - {} - }; + ConnectionList *m_connections; - using t_Connections = std::vector; - t_Connections m_connections; - - void deleteFromIni(t_Connections::iterator begin, t_Connections::iterator end); - static QString iniFileName(); static QString makeLongDescription(const ConnectionConfig &cfg); }; diff --git a/src/ConnectionManagerWindow.cpp b/src/ConnectionManagerWindow.cpp index 2a23929..f4a9529 100644 --- a/src/ConnectionManagerWindow.cpp +++ b/src/ConnectionManagerWindow.cpp @@ -37,12 +37,12 @@ ConnectionManagerWindow::~ConnectionManagerWindow() void ConnectionManagerWindow::on_actionAdd_Connection_triggered() { - ConnectionConfig c; - c.setName("new"); +// ConnectionConfig c; +// c.setName("new"); //m_listModel->add(c); auto clm = m_masterController->getConnectionListModel(); - clm->add(c); + clm->newItem(); // Select the new row auto idx = clm->index(clm->rowCount() - 1, 0); @@ -110,3 +110,67 @@ void ConnectionManagerWindow::on_actionManage_server_triggered() auto ci = ui->listView->selectionModel()->currentIndex(); m_masterController->openServerWindowForConnection(ci.row()); } + +#include +//#include +//#include +//#include +//#include +#include + +void ConnectionManagerWindow::on_testButton_clicked() +{ + std::string error = Botan::runtime_version_check(BOTAN_VERSION_MAJOR, + BOTAN_VERSION_MINOR, + BOTAN_VERSION_PATCH); + if (error.empty()) { +// Botan::AutoSeeded_RNG rng; +// Botan::secure_vector salt = +// //{ 0x3f, 0x0a, 0xb0, 0x11, 0x44, 0xfe, 0x9d, 0xf7, 0x85, 0xd3, 0x11, 0x38, 0xe2, 0xdf, 0x31, 0x42 }; +// rng.random_vec(16); +// // salt should be random and saved with encrypted data so it can be used when we decrypt + +// std::string password = "Hello kitty"; +// std::unique_ptr pbkdf(Botan::get_pbkdf("PBKDF2(SHA-256)")); +// Botan::OctetString aes256_key = pbkdf->derive_key(32, password, salt.data(), salt.size(), 10000); + +// std::string plaintext("Your great-grandfather gave this watch to your granddad for good luck. Unfortunately, Dane's luck wasn't as good as his old man's."); +// Botan::secure_vector pt(plaintext.data(),plaintext.data()+plaintext.length()); + +// std::unique_ptr enc(Botan::get_cipher_mode("AES-256/CBC/PKCS7", Botan::ENCRYPTION)); +// enc->set_key(aes256_key); + +// //generate fresh nonce (IV) +// //std::unique_ptr rng(new Botan::AutoSeeded_RNG); +// std::vector iv(enc->default_nonce_length()); +// rng.randomize(iv.data(), iv.size()); +// enc->start(iv); +// enc->finish(pt); +// //std::cout << std::endl << enc->name() << " with iv " << Botan::hex_encode(iv) << std::endl << Botan::hex_encode(pt); + + + //std::string s = aes256_key.as_string();// + "\n" + t.format_string(); + + std::string passphrase = "my passphrase"; + std::string plaintext("password1234"); + try { + Botan::AutoSeeded_RNG rng; + std::string encrypted = Botan::CryptoBox::encrypt((const uint8_t*)plaintext.data(), plaintext.length(), passphrase, rng); + + std::string decrypted = Botan::CryptoBox::decrypt(encrypted, passphrase); + + std::string s = encrypted + "\n" + decrypted; + QMessageBox::information(this, "pglab", + QString::fromUtf8(s.c_str()), QMessageBox::Yes); + } + catch (Botan::Decoding_Error &e) { + QMessageBox::information(this, "pglab", + tr("Failure to decrypt"), QMessageBox::Yes); + + } + } + else { + QMessageBox ::information(this, "pglab", + QString::fromUtf8(error.c_str()), QMessageBox::Yes); + } +} diff --git a/src/ConnectionManagerWindow.h b/src/ConnectionManagerWindow.h index df9c4dd..7bad733 100644 --- a/src/ConnectionManagerWindow.h +++ b/src/ConnectionManagerWindow.h @@ -15,10 +15,8 @@ class QStandardItemModel; /** \brief Class that holds glue code for the ConnectionManager UI. * */ -class ConnectionManagerWindow : public QMainWindow -{ +class ConnectionManagerWindow : public QMainWindow { Q_OBJECT - public: explicit ConnectionManagerWindow(MasterController *master, QWidget *parent = 0); ~ConnectionManagerWindow(); @@ -27,15 +25,13 @@ private slots: void on_actionAdd_Connection_triggered(); void on_currentChanged(const QModelIndex ¤t, const QModelIndex &previous); void on_actionDelete_connection_triggered(); - void on_actionConnect_triggered(); - void on_actionQuit_application_triggered(); - void on_actionBackup_database_triggered(); - void on_actionManage_server_triggered(); + void on_testButton_clicked(); + private: Ui::ConnectionManagerWindow *ui; QDataWidgetMapper *m_mapper = nullptr; diff --git a/src/ConnectionManagerWindow.ui b/src/ConnectionManagerWindow.ui index dbded97..c98eafd 100644 --- a/src/ConnectionManagerWindow.ui +++ b/src/ConnectionManagerWindow.ui @@ -182,6 +182,13 @@ + + + + PushButton + + + diff --git a/src/Expected.h b/src/Expected.h deleted file mode 100644 index 7b864e3..0000000 --- a/src/Expected.h +++ /dev/null @@ -1,156 +0,0 @@ -#ifndef EXPECTED_H -#define EXPECTED_H - -template -class Expected { - union { - T m_value; - std::exception_ptr m_error; - }; - bool m_valid; - Expected() {} // internal use - -public: - - Expected(const T& rhs) - : m_value(rhs), m_valid(true) - {} - - Expected(T&& rhs) - : m_value(std::move(rhs)) - , m_valid(true) - {} - - - Expected(const Expected& rhs) - : m_valid(rhs.valid) - { - if (m_valid) { - new (&m_value) T(rhs.m_value); - } - else { - new (&m_error) std::exception_ptr(rhs.m_error); - } - } - - Expected(Expected &&rhs) - : m_valid(rhs.m_valid) - { - if (m_valid) { - new (&m_value) T(std::move(rhs.m_value)); - } - else { - new (&m_error) std::exception_ptr(std::move(rhs.m_error)); - } - } - - ~Expected() - { - if (m_valid) { - m_value.~T(); - } - else { - using std::exception_ptr; - m_error.~exception_ptr(); - } - } - - void swap(Expected& rhs) - { - if (m_valid) { - if (rhs.m_valid) { - using std::swamp; - swap(m_value, rhs.m_value); - } - else { - auto t = std::move(rhs.m_error); - new(&rhs.m_value) T(std::move(m_value)); - new(&m_error) std::exception_ptr(t); - std::swap(m_valid, rhs.m_valid); - } - } - else { - if (rhs.m_valid) { - rhs.swap(*this); - } - else { - m_error.swap(rhs.m_error); - std::swap(m_valid, rhs.m_valid); - } - } - } - - template - static Expected fromException(const E& exception) - { - if (typeid(exception) != typeid(E)) { - throw std::invalid_argument("slicing detected"); - } - return fromException(std::make_exception_ptr(exception)); - } - - static Expected fromException(std::exception_ptr p) - { - Expected result; - result.m_valid = false; - new (&result.m_error) std::exception_ptr(std::move(p)); - return result; - } - - static Expected fromException() - { - return fromException(std::current_exception()); - } - - bool valid() const - { - return m_valid; - } - - T& get() - { - if (!m_valid) { - std::rethrow_exception(m_error); - } - return m_value; - } - - const T& get() const - { - if (!m_valid) { - std::rethrow_exception(m_error); - } - return m_value; - } - - template - bool hasException() const - { - try { - if (!m_valid) { - std::rethrow_exception(m_error); - } - } - catch (const E& object) { - return true; - } - catch (...) { - } - return false; - } - - template - static Expected fromCode(F fun) - { - try { - return Expected(fun()); - } - catch (...) { - return fromException(); - } - } -}; - - - -#endif // EXPECTED_H diff --git a/src/MasterController.cpp b/src/MasterController.cpp index 63a0f3f..7e523f2 100644 --- a/src/MasterController.cpp +++ b/src/MasterController.cpp @@ -1,6 +1,7 @@ #include "MasterController.h" -#include "connectionmanagerwindow.h" -#include "connectionlistmodel.h" +#include "ConnectionManagerWindow.h" +#include "ConnectionList.h" +#include "ConnectionListModel.h" #include "MainWindow.h" #include "ServerWindow.h" @@ -12,11 +13,14 @@ MasterController::~MasterController() { delete m_connectionManagerWindow; delete m_connectionListModel; + delete m_connectionList; } void MasterController::init() { - m_connectionListModel = new ConnectionListModel(this); + m_connectionList = new ConnectionList; + m_connectionList->load(); + m_connectionListModel = new ConnectionListModel(m_connectionList, this); m_connectionManagerWindow = new ConnectionManagerWindow(this, nullptr); m_connectionManagerWindow->show(); diff --git a/src/MasterController.h b/src/MasterController.h index 0e95e79..7157673 100644 --- a/src/MasterController.h +++ b/src/MasterController.h @@ -5,16 +5,18 @@ #include class ConnectionConfig; +class ConnectionList; class ConnectionListModel; class ConnectionManagerWindow; /** \brief Controller class responsible for all things global. */ -class MasterController : public QObject -{ +class MasterController : public QObject { Q_OBJECT public: explicit MasterController(QObject *parent = 0); + MasterController(const MasterController&) = delete; + MasterController &operator=(const MasterController&) = delete; ~MasterController(); void init(); @@ -33,7 +35,7 @@ signals: public slots: private: - + ConnectionList *m_connectionList = nullptr; ConnectionListModel *m_connectionListModel = nullptr; ConnectionManagerWindow *m_connectionManagerWindow = nullptr; }; diff --git a/src/ParamTypeDelegate.cpp b/src/ParamTypeDelegate.cpp index 595eda9..da0d3f2 100644 --- a/src/ParamTypeDelegate.cpp +++ b/src/ParamTypeDelegate.cpp @@ -22,6 +22,7 @@ QWidget *ParamTypeDelegate::createEditor(QWidget *parent, QWidget *w = nullptr; QComboBox *cmbbx = new QComboBox(parent); + cmbbx->setMaxVisibleItems(32); cmbbx->setModel(m_typeSelectionModel); w = cmbbx; diff --git a/src/SqlLexer.cpp b/src/SqlLexer.cpp deleted file mode 100644 index aac2fa0..0000000 --- a/src/SqlLexer.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include "SqlLexer.h" - -SqlLexer::SqlLexer(const QString &block, LexerState currentstate) - : m_block(block) - , m_state(currentstate) -{} - -QChar SqlLexer::nextChar() -{ - QChar result = QChar::Null; - if (m_pos < m_block.size()) { - result = m_block.at(m_pos++); - } - return result; -} - -QChar SqlLexer::peekChar() -{ - QChar result = QChar::Null; - if (m_pos < m_block.size()) { - result = m_block.at(m_pos); - } - return result; -} - -/** - * @brief NextBasicToken - * @param in - * @param ofs - * @param start - * @param length - * @return false when input seems invalid, it will return what it did recognize but something wasn't right, parser should try to recover - */ -bool SqlLexer::nextBasicToken(int &startpos, int &length, BasicTokenType &tokentype, QString &out) -{ - // Basically chops based on white space - // it does also recognize comments and quoted strings/identifiers - bool result = false; - while (true) { - startpos = m_pos; - QChar c = nextChar(); - if (c.isSpace()) { - // Just skip whitespace - } - else if (c == '-' && peekChar() == '-') { // two dashes, start of comment - // Loop till end of line or end of block - c = nextChar(); - for (;;) { - c = peekChar(); - if (c != QChar::Null && c != '\n') - nextChar(); - else - break; - } - length = m_pos - startpos; - tokentype = BasicTokenType::Comment; - return true; - } - else if (c == '\'') { - // Single quoted string so it's an SQL text literal - while (true) { - c = peekChar(); - if (c == QChar::Null || c == '\n') { - // unexpected end, pretend nothings wrong - length = m_pos - startpos; - tokentype = BasicTokenType::QuotedString; - return true; - } - else { - nextChar(); - if (c == '\'') { - // maybe end of string literal - if (peekChar() == '\'') { - // Nope, just double quote to escape quote - nextChar(); // eat it - } - else { - length = m_pos - startpos; - tokentype = BasicTokenType::QuotedString; - return true; - } - } - } - } - } - else if (c == '"') { - // Double quoted identifier - while (true) { - c = peekChar(); - if (c == QChar::Null || c == '\n') { - // unexpected end, pretend nothings wrong - length = m_pos - startpos; - tokentype = BasicTokenType::QuotedIdentifier; - return true; - } - else { - nextChar(); - if (c == '"') { - // maybe end of string literal - if (peekChar() == '"') { - // Nope, just double quote to escape quote - nextChar(); // eat it - } - else { - length = m_pos - startpos; - tokentype = BasicTokenType::QuotedIdentifier; - return true; - } - } - } - } - } - else if (c == QChar::Null) { - break; - } - else { - // Undetermined symbol - for (;;) { - c = peekChar(); - if (c.isLetterOrNumber() || c == '_') - nextChar(); - else - break; - } - length = m_pos - startpos; - tokentype = BasicTokenType::Symbol; - QStringRef sr(&m_block, startpos, length); - out = sr.toString(); - return true; - } - } - return false; -} - diff --git a/src/SqlSyntaxHighlighter.cpp b/src/SqlSyntaxHighlighter.cpp index 3d0263b..92961ee 100644 --- a/src/SqlSyntaxHighlighter.cpp +++ b/src/SqlSyntaxHighlighter.cpp @@ -133,7 +133,10 @@ void SqlSyntaxHighlighter::setTypes(const PgTypeContainer *types) void SqlSyntaxHighlighter::highlightBlock(const QString &text) { - SqlLexer lexer(text, LexerState::Null); + int previous_state = previousBlockState(); + if (previous_state <= 0) + previous_state = 0; + SqlLexer lexer(text, (LexerState)previous_state); int startpos, length; BasicTokenType tokentype; QString s; @@ -151,6 +154,9 @@ void SqlSyntaxHighlighter::highlightBlock(const QString &text) setFormat(startpos, length, m_typeFormat); } break; + case BasicTokenType::OpenBlockComment: + setCurrentBlockState((int)lexer.currentState()); + case BasicTokenType::BlockComment: case BasicTokenType::Comment: setFormat(startpos, length, m_commentFormat); break; diff --git a/src/src.pro b/src/src.pro index 42a339f..99a7152 100644 --- a/src/src.pro +++ b/src/src.pro @@ -4,6 +4,7 @@ # #------------------------------------------------- +CONFIG += c++11 QT += core gui greaterThan(QT_MAJOR_VERSION, 4): QT += widgets sql @@ -11,9 +12,17 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets sql TARGET = pglab TEMPLATE = app -INCLUDEPATH += C:\prog\include +INCLUDEPATH += C:\prog\include C:\VSproj\boost_1_63_0 DEFINES += WIN32_LEAN_AND_MEAN NOMINMAX -LIBS += c:\prog\lib\libpq.lib c:\prog\lib\fmt.lib User32.lib ws2_32.lib +LIBS += /LIBPATH:C:\VSproj\boost_1_63_0\stage\lib /LIBPATH:c:\prog\lib\ libpq.lib fmt.lib User32.lib ws2_32.lib + +debug { +LIBS += c:\prog\lib\botand_imp.lib +} + +release { +# LIBS += c:\prog\lib\botan.lib +} win32:RC_ICONS += pglab.ico @@ -29,7 +38,6 @@ SOURCES += main.cpp\ tsqueue.cpp \ win32event.cpp \ waithandlelist.cpp \ - CsvWriter.cpp \ DatabaseWindow.cpp \ ConnectionManagerWindow.cpp \ ConnectionListModel.cpp \ @@ -53,7 +61,6 @@ SOURCES += main.cpp\ ParamListModel.cpp \ MainWindow.cpp \ SqlSyntaxHighlighter.cpp \ - SqlLexer.cpp \ ServerWindow.cpp \ ASyncWindow.cpp \ DatabasesTableModel.cpp \ @@ -65,7 +72,8 @@ SOURCES += main.cpp\ PgAuthIdContainer.cpp \ Pgsql_Result.cpp \ Pgsql_Row.cpp \ - Pgsql_Value.cpp + Pgsql_Value.cpp \ + ConnectionList.cpp HEADERS += \ sqlparser.h \ @@ -78,13 +86,10 @@ HEADERS += \ tsqueue.h \ win32event.h \ waithandlelist.h \ - CsvWriter.h \ DatabaseWindow.h \ ConnectionManagerWindow.h \ ConnectionListModel.h \ ConnectionConfig.h \ - ScopeGuard.h \ - Expected.h \ QueryTab.h \ stopwatch.h \ util.h \ @@ -117,7 +122,8 @@ HEADERS += \ PgAuthIdContainer.h \ Pgsql_Result.h \ Pgsql_Row.h \ - Pgsql_Value.h + Pgsql_Value.h \ + ConnectionList.h FORMS += mainwindow.ui \ DatabaseWindow.ui \ @@ -132,3 +138,14 @@ RESOURCES += \ resources.qrc QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS,5.01 + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../core/release/ -lcore +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../core/debug/ -lcore + +INCLUDEPATH += $$PWD/../core +DEPENDPATH += $$PWD/../core + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/libcore.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/libcore.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/release/core.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../core/debug/core.lib diff --git a/src/typeselectionitemmodel.h b/src/typeselectionitemmodel.h index f9622ce..ce43ff3 100644 --- a/src/typeselectionitemmodel.h +++ b/src/typeselectionitemmodel.h @@ -20,7 +20,6 @@ public: QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; private: -// const PgTypeContainer* m_types; std::vector m_types; }; diff --git a/src/util.cpp b/src/util.cpp index f5a16d6..749c7cc 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -4,6 +4,7 @@ #include #include #include +#include // Supported range from microseconds to seconds // min:sec to hours::min::sec @@ -134,3 +135,5 @@ QString ConvertToMultiLineCString(const QString &in) out.append('"'); return out; } + + diff --git a/src/util.h b/src/util.h index 146184b..e25c4f1 100644 --- a/src/util.h +++ b/src/util.h @@ -1,6 +1,7 @@ #ifndef UTIL_H #define UTIL_H +#include #include #include @@ -9,6 +10,23 @@ void copySelectionToClipboard(const QTableView *view); QString ConvertToMultiLineCString(const QString &in); void exportTable(const QTableView *view, QTextStream &out); +inline QString stdStrToQ(const std::string &s) +{ + return QString::fromUtf8(s.c_str()); +} + +inline std::string qStrToStd(const QString &s) +{ + return std::string(s.toUtf8().data()); +} + +inline std::string qvarToStdStr(const QVariant &c) +{ + return qStrToStd(c.toString()); +} + + + namespace std { template <> diff --git a/tests/auto/mycase/main.cpp b/tests/auto/mycase/main.cpp index 76530cd..046652f 100644 --- a/tests/auto/mycase/main.cpp +++ b/tests/auto/mycase/main.cpp @@ -1,4 +1,8 @@ -#include "tst_mycase.h" +#include "tst_CsvWriter.h" +#include "tst_expected.h" +#include "tst_PasswordManager.h" +#include "tst_scopeguard.h" +#include "tst_SqlLexer.h" #include diff --git a/tests/auto/mycase/mycase.pro b/tests/auto/mycase/mycase.pro index eddc4c3..8572e21 100644 --- a/tests/auto/mycase/mycase.pro +++ b/tests/auto/mycase/mycase.pro @@ -8,6 +8,24 @@ CONFIG += qt QT += core -HEADERS += tst_mycase.h +HEADERS += \ + tst_expected.h \ + tst_SqlLexer.h \ + tst_scopeguard.h \ + tst_CsvWriter.h \ + tst_PasswordManager.h -SOURCES += main.cpp ../../../src/SqlLexer.cpp +SOURCES += main.cpp + +win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../../../core/release/ -lcore +else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../../../core/debug/ -lcore + +INCLUDEPATH += C:\prog\include C:\VSproj\boost_1_63_0 +INCLUDEPATH += $$PWD/../../../core +DEPENDPATH += $$PWD/../../../core +LIBS += c:\prog\lib\botand_imp.lib + +win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../../core/release/libcore.a +else:win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../../core/debug/libcore.a +else:win32:!win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../../core/release/core.lib +else:win32:!win32-g++:CONFIG(debug, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../../../core/debug/core.lib diff --git a/tests/auto/mycase/tst_CsvWriter.h b/tests/auto/mycase/tst_CsvWriter.h new file mode 100644 index 0000000..ac48b9d --- /dev/null +++ b/tests/auto/mycase/tst_CsvWriter.h @@ -0,0 +1,113 @@ +#include +#include +#include "CsvWriter.h" +#include +#include + +using namespace testing; + + +TEST(CsvWriter, one_row_two_numbers) +{ + QString result; + QTextStream stream(&result); + CsvWriter writer(&stream); + writer.setQuote('"'); + writer.setSeperator(','); + writer.writeField("1"); + writer.writeField("2"); + writer.nextRow(); + + QString expected = QString::fromUtf8("1,2\n"); + ASSERT_THAT(result, Eq(expected)); +} + +TEST(CsvWriter, one_row_one_number_one_unquoted_string) +{ + QString result; + QTextStream stream(&result); + CsvWriter writer(&stream); + writer.setQuote('"'); + writer.setSeperator(','); + writer.writeField("1"); + writer.writeField("hello"); + writer.nextRow(); + + QString expected = QString::fromUtf8("1,hello\n"); + ASSERT_THAT(result, Eq(expected)); +} + +TEST(CsvWriter, one_row_one_number_one_quoted_string) +{ + QString result; + QTextStream stream(&result); + CsvWriter writer(&stream); + writer.setQuote('"'); + writer.setSeperator(','); + writer.writeField("1"); + writer.writeField("hel,lo"); + writer.nextRow(); + + QString expected = QString::fromUtf8("1,\"hel,lo\"\n"); + ASSERT_THAT(result, Eq(expected)); +} + +TEST(CsvWriter, newline_in_field) +{ + QString result; + QTextStream stream(&result); + CsvWriter writer(&stream); + writer.setQuote('"'); + writer.setSeperator(','); + writer.writeField("1"); + writer.writeField("hel\nlo"); + writer.nextRow(); + + QString expected = QString::fromUtf8("1,\"hel\nlo\"\n"); + ASSERT_THAT(result, Eq(expected)); +} + +TEST(CsvWriter, escape_quote) +{ + QString result; + QTextStream stream(&result); + CsvWriter writer(&stream); + writer.setQuote('"'); + writer.setSeperator(','); + writer.writeField("1"); + writer.writeField("hel\"lo"); + writer.nextRow(); + + QString expected = QString::fromUtf8("1,\"hel\"\"lo\"\n"); + ASSERT_THAT(result, Eq(expected)); +} + +TEST(CsvWriter, non_default_seperator) +{ + QString result; + QTextStream stream(&result); + CsvWriter writer(&stream); + writer.setQuote('"'); + writer.setSeperator('\t'); + writer.writeField("1"); + writer.writeField("hel,lo"); + writer.nextRow(); + + QString expected = QString::fromUtf8("1\thel,lo\n"); + ASSERT_THAT(result, Eq(expected)); +} + +TEST(CsvWriter, non_default_quote) +{ + QString result; + QTextStream stream(&result); + CsvWriter writer(&stream); + writer.setQuote('*'); + writer.setSeperator('\t'); + writer.writeField("1"); + writer.writeField("hel\tlo"); + writer.nextRow(); + + QString expected = QString::fromUtf8("1\t*hel\tlo*\n"); + ASSERT_THAT(result, Eq(expected)); +} diff --git a/tests/auto/mycase/tst_PasswordManager.h b/tests/auto/mycase/tst_PasswordManager.h new file mode 100644 index 0000000..a3da28b --- /dev/null +++ b/tests/auto/mycase/tst_PasswordManager.h @@ -0,0 +1,73 @@ +#include +#include +#include "PasswordManager.h" + +using namespace testing; + + +TEST(PasswordManager, initial_changeMasterPassword_returns_true) +{ + PasswordManager pwm; + + auto res = pwm.changeMasterPassword("", "my test passphrase"); + ASSERT_NO_THROW( res.get() ); + ASSERT_THAT( res.get(), Eq(true) ); +} + +TEST(PasswordManager, unlock_succeeds) +{ + PasswordManager pwm; + + std::string passphrase = "my test passphrase"; + + auto res = pwm.changeMasterPassword("", passphrase); + ASSERT_NO_THROW( res.get() ); + ASSERT_THAT( res.get(), Eq(true) ); + + auto res2 = pwm.unlock(passphrase); + ASSERT_NO_THROW( res2.get() ); + ASSERT_THAT( res2.get(), Eq(true) ); +} + +TEST(PasswordManager, unlock_fails) +{ + PasswordManager pwm; + + std::string passphrase = "my test passphrase"; + + auto res = pwm.changeMasterPassword("", passphrase); + ASSERT_NO_THROW( res.get() ); + ASSERT_THAT( res.get(), Eq(true) ); + + auto res2 = pwm.unlock(passphrase + "2"); + ASSERT_NO_THROW( res2.get() ); + ASSERT_THAT( res2.get(), Eq(false) ); +} + +TEST(PasswordManager, test_save_get) +{ + PasswordManager pwm; + + std::string passphrase = "my test passphrase"; + + auto res = pwm.changeMasterPassword("", passphrase); + ASSERT_NO_THROW( res.get() ); + ASSERT_THAT( res.get(), Eq(true) ); + +// auto res2 = pwm.unlock(passphrase + "2"); +// ASSERT_NO_THROW( res2.get() ); +// ASSERT_THAT( res2.get(), Eq(false) ); + + const std::string password = "password123"; + const std::string key = "abc"; + + auto res2 = pwm.savePassword(key, password); + ASSERT_THAT( res2.valid(), Eq(true) ); + + std::string result; + auto res3 = pwm.getPassword(key, result); + ASSERT_THAT( res3.valid(), Eq(true) ); + ASSERT_THAT( res3.get(), Eq(true) ); + ASSERT_THAT( result, Eq(password) ); + +} diff --git a/tests/auto/mycase/tst_mycase.h b/tests/auto/mycase/tst_SqlLexer.h similarity index 83% rename from tests/auto/mycase/tst_mycase.h rename to tests/auto/mycase/tst_SqlLexer.h index bb6df80..25d6535 100644 --- a/tests/auto/mycase/tst_mycase.h +++ b/tests/auto/mycase/tst_SqlLexer.h @@ -1,16 +1,11 @@ #include #include -#include "../../../src/SqlLexer.h" +#include "SqlLexer.h" using namespace testing; -TEST(mycase, myset) -{ - EXPECT_EQ(1, 1); - ASSERT_THAT(0, Eq(0)); -} -TEST(mycase, lexer) +TEST(SqlLexer, lexer) { QString input = " SELECT "; SqlLexer lexer(input, LexerState::Null); @@ -26,7 +21,7 @@ TEST(mycase, lexer) ASSERT_THAT( out, Eq(QString("SELECT")) ); } -TEST(mycase, lexer_quote_in_string) +TEST(SqlLexer, lexer_quote_in_string) { QString input = " 'abc''def' "; SqlLexer lexer(input, LexerState::Null); diff --git a/tests/auto/mycase/tst_expected.h b/tests/auto/mycase/tst_expected.h new file mode 100644 index 0000000..8632ff8 --- /dev/null +++ b/tests/auto/mycase/tst_expected.h @@ -0,0 +1,203 @@ +#include +#include +#include "Expected.h" + +using namespace testing; + +Expected getAnswerToEverything() { return 42; } + +TEST(expected, valid_when_valid_returns_true) +{ + Expected v = getAnswerToEverything(); + ASSERT_THAT(v.valid(), Eq(true)); +} + +TEST(expected, get_when_valid_returns_value) +{ + Expected v = getAnswerToEverything(); + ASSERT_THAT(v.get(), Eq(42)); +} + +TEST(expected, hasException_when_valid_returns_false) +{ + Expected v = getAnswerToEverything(); + ASSERT_THAT(v.hasException(), Eq(false)); +} + +TEST(expected, T_fromException_is_not_valid) +{ + auto e = Expected::fromException(std::runtime_error("hello")); + ASSERT_THAT(e.valid(), Eq(false)); +} + +TEST(expected, T_fromException_get_thows) +{ + auto e = Expected::fromException(std::runtime_error("hello")); + ASSERT_THROW (e.get(), std::runtime_error); +} + +TEST(expected, T_fromException_has_exception_true) +{ + auto e = Expected::fromException(std::runtime_error("hello")); + ASSERT_THAT(e.hasException(), Eq(true)); +} + +TEST(expected, T_fromException_has_exception_false) +{ + auto e = Expected::fromException(std::runtime_error("hello")); + ASSERT_THAT(e.hasException(), Eq(false)); +} + +TEST(expected, T_fromException_has_derived_exception) +{ + auto e = Expected::fromException(std::runtime_error("hello")); + ASSERT_THAT(e.hasException(), Eq(true)); +} + +TEST(expected, T_fromCode_is_valid) +{ + auto e = Expected::fromCode([]() -> int { return 42; }); + ASSERT_THAT(e.valid(), Eq(true)); +} + +TEST(expected, T_fromCode_get) +{ + auto e = Expected::fromCode([]() -> int { return 42; }); + ASSERT_THAT(e.get(), Eq(42)); +} + + +TEST(expected, T_fromCode_E_is_not_valid) +{ + auto e = Expected::fromCode([]() -> int { throw std::runtime_error("hello"); }); + ASSERT_THAT(e.valid(), Eq(false)); +} + +TEST(expected, T_fromCode_E_get_thows) +{ + auto e = Expected::fromCode([]() -> int { throw std::runtime_error("hello"); }); + ASSERT_THROW (e.get(), std::runtime_error); +} + +TEST(expected, T_fromCode_E_has_exception_true) +{ + auto e = Expected::fromCode([]() -> int { throw std::runtime_error("hello"); }); + ASSERT_THAT(e.hasException(), Eq(true)); +} + +TEST(expected, T_fromCode_E_has_exception_false) +{ + auto e = Expected::fromCode([]() -> int { throw std::runtime_error("hello"); }); + ASSERT_THAT(e.hasException(), Eq(false)); +} + +TEST(expected, T_fromCode_E_has_derived_exception) +{ + auto e = Expected::fromCode([]() -> int { throw std::runtime_error("hello"); }); + ASSERT_THAT(e.hasException(), Eq(true)); +} + +//Expected getIntWithStdRuntimeError() { return Expected(); } + +Expected getNothing() { return Expected(); } + + +TEST(expected_void, valid_when_valid_returns_true) +{ + Expected v = getNothing(); + ASSERT_THAT(v.valid(), Eq(true)); +} + +TEST(expected_void, get_when_valid_returns_value) +{ + Expected v = getNothing(); + ASSERT_NO_THROW(v.get()); +} + +TEST(expected_void, hasException_when_valid_returns_false) +{ + Expected v = getNothing(); + ASSERT_THAT(v.hasException(), Eq(false)); +} + + + + + + +TEST(expected_void, void_fromException_is_not_valid) +{ + auto e = Expected::fromException(std::runtime_error("hello")); + ASSERT_THAT(e.valid(), Eq(false)); +} + +TEST(expected_void, void_fromException_get_thows) +{ + auto e = Expected::fromException(std::runtime_error("hello")); + ASSERT_THROW (e.get(), std::runtime_error); +} + +TEST(expected_void, void_fromException_has_exception_true) +{ + auto e = Expected::fromException(std::runtime_error("hello")); + ASSERT_THAT(e.hasException(), Eq(true)); +} + +TEST(expected_void, void_fromException_has_exception_false) +{ + auto e = Expected::fromException(std::runtime_error("hello")); + ASSERT_THAT(e.hasException(), Eq(false)); +} + +TEST(expected_void, void_fromException_has_derived_exception) +{ + auto e = Expected::fromException(std::runtime_error("hello")); + ASSERT_THAT(e.hasException(), Eq(true)); +} + +TEST(expected_void, void_fromCode_is_valid) +{ + auto e = Expected::fromCode([]() -> void { }); + ASSERT_THAT(e.valid(), Eq(true)); +} + +TEST(expected_void, void_fromCode_get) +{ + auto e = Expected::fromCode([]() -> void { }); + ASSERT_NO_THROW(e.get()); +} + +void expected_void_throws_func() +{ + throw std::runtime_error("hello"); +} + +TEST(expected_void, void_fromCode_E_is_not_valid) +{ + auto e = Expected::fromCode(expected_void_throws_func); + ASSERT_THAT(e.valid(), Eq(false)); +} + +TEST(expected_void, void_fromCode_E_get_thows) +{ + auto e = Expected::fromCode(expected_void_throws_func); + ASSERT_THROW (e.get(), std::runtime_error); +} + +TEST(expected_void, void_fromCode_E_has_exception_true) +{ + auto e = Expected::fromCode(expected_void_throws_func); + ASSERT_THAT(e.hasException(), Eq(true)); +} + +TEST(expected_void, void_fromCode_E_has_exception_false) +{ + auto e = Expected::fromCode(expected_void_throws_func); + ASSERT_THAT(e.hasException(), Eq(false)); +} + +TEST(expected_void, void_fromCode_E_has_derived_exception) +{ + auto e = Expected::fromCode(expected_void_throws_func); + ASSERT_THAT(e.hasException(), Eq(true)); +} diff --git a/tests/auto/mycase/tst_scopeguard.h b/tests/auto/mycase/tst_scopeguard.h new file mode 100644 index 0000000..431e484 --- /dev/null +++ b/tests/auto/mycase/tst_scopeguard.h @@ -0,0 +1,63 @@ +#include +#include +#include "ScopeGuard.h" + +using namespace testing; + + +TEST(ScopeGuard, normal_run_fun_on_destruction_1) +{ + bool result = false; + auto sg = scopeGuard([&result]() { result = true; }); + ASSERT_THAT(result, Eq(false)); +} + +TEST(ScopeGuard, normal_run_fun_on_destruction_2) +{ + bool result = false; + { + auto sg = scopeGuard([&result]() { result = true; }); + } + + ASSERT_THAT(result, Eq(true)); +} + +TEST(ScopeGuard, dismiss) +{ + bool result = false; + { + auto sg = scopeGuard([&result]() { result = true; }); + sg.dismiss(); + } + + ASSERT_THAT(result, Eq(false)); +} + +TEST(ScopeGuard, SCOPE_EXIT_macro_1) +{ + bool result = false; + { + SCOPE_EXIT { result = true; }; + ASSERT_THAT(result, Eq(false)); // prove previous statement hasn't run yet + } + +} + +TEST(ScopeGuard, SCOPE_EXIT_macro_2) +{ + bool result = false; + { + SCOPE_EXIT { result = true; }; + } + + ASSERT_THAT(result, Eq(true)); +} + + + +//TEST(expected, get_when_valid_returns_value) +//{ +// Expected v = getAnswerToEverything(); +// ASSERT_THAT(v.get(), Eq(42)); +//} +