Moved some parts to a static lib so both the executable and the tests can link to it.

Written additional tests.
This commit is contained in:
eelke 2017-02-26 19:29:50 +01:00
parent 0a809a7288
commit d0ea9dfa0c
39 changed files with 1767 additions and 493 deletions

6
core/Core.cpp Normal file
View file

@ -0,0 +1,6 @@
#include "Core.h"
Core::Core()
{
}

12
core/Core.h Normal file
View file

@ -0,0 +1,12 @@
#ifndef CORE_H
#define CORE_H
class Core
{
public:
Core();
};
#endif // CORE_H

269
core/Expected.h Normal file
View file

@ -0,0 +1,269 @@
#ifndef EXPECTED_H
#define EXPECTED_H
#include <exception>
template <typename T>
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 <class E>
static Expected<T> fromException(const E& exception)
{
if (typeid(exception) != typeid(E)) {
throw std::invalid_argument("slicing detected");
}
return fromException(std::make_exception_ptr(exception));
}
static Expected<T> fromException(std::exception_ptr p)
{
Expected<T> result;
result.m_valid = false;
new (&result.m_error) std::exception_ptr(std::move(p));
return result;
}
static Expected<T> 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 <class E>
bool hasException() const
{
try {
if (!m_valid) {
std::rethrow_exception(m_error);
}
}
catch (const E& ) {
return true;
}
catch (...) {
}
return false;
}
template <class F>
static Expected fromCode(F fun)
{
try {
return Expected(fun());
}
catch (...) {
return fromException();
}
}
};
template <>
class Expected<void> {
std::exception_ptr m_error;
bool m_valid;
public:
Expected<void>()
: m_valid(true)
{}
Expected(const Expected& rhs)
: m_valid(rhs.m_valid)
, m_error(rhs.m_error)
{}
Expected(Expected<void> &&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 <class E>
static Expected<void> fromException(const E& exception)
{
if (typeid(exception) != typeid(E)) {
throw std::invalid_argument("slicing detected");
}
return fromException(std::make_exception_ptr(exception));
}
static Expected<void> fromException(std::exception_ptr p)
{
Expected<void> result;
result.m_valid = false;
result.m_error = std::exception_ptr(std::move(p));
return result;
}
static Expected<void> fromException()
{
return fromException(std::current_exception());
}
bool valid() const
{
return m_valid;
}
void get() const
{
if (!m_valid) {
std::rethrow_exception(m_error);
}
}
template <class E>
bool hasException() const
{
try {
if (!m_valid) {
std::rethrow_exception(m_error);
}
}
catch (const E& ) {
return true;
}
catch (...) {
}
return false;
}
template <class F>
static Expected fromCode(F fun)
{
try {
fun();
return Expected<void>();
}
catch (...) {
return fromException();
}
}
};
#endif // EXPECTED_H

244
core/PasswordManager.cpp Normal file
View file

@ -0,0 +1,244 @@
#include "PasswordManager.h"
#include <botan/filters.h>
#include <botan/pipe.h>
#include <botan/sha2_64.h>
#include <botan/hash.h>
#include <botan/hmac.h>
#include <botan/pbkdf2.h>
#include <botan/rng.h>
#include <botan/base64.h>
#include <botan/loadstor.h>
#include <botan/mem_ops.h>
#include <boost/assert.hpp>
#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<uint8_t> 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<uint8_t> 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<uint8_t> 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<bool> 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<bool>::fromException();
}
}
Expected<bool> 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<bool>::fromException();
}
}
void PasswordManager::lock()
{
m_masterKey = StrengthenedKey();
}
bool PasswordManager::locked() const
{
return m_masterKey.cipher_key.size() == 0;
}
Expected<void> PasswordManager::savePassword(const std::string &key, const std::string &password)
{
if (locked()) {
return Expected<void>::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<void>();
}
Expected<bool> PasswordManager::getPassword(const std::string &key, std::string &out)
{
if (locked()) {
return Expected<bool>::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<Botan::HashFunction> 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();
}

66
core/PasswordManager.h Normal file
View file

@ -0,0 +1,66 @@
#ifndef PASSWORDMANAGER_H
#define PASSWORDMANAGER_H
#include "Expected.h"
#include <string>
#include <botan/botan.h>
#include <botan/symkey.h>
#include <map>
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<bool> unlock(const std::string &master_password);
Expected<bool> changeMasterPassword(const std::string &master_password,
const std::string &new_master_password);
/** Forget master password
*/
void lock();
bool locked() const;
Expected<void> savePassword(const std::string &key, const std::string &password);
Expected<bool> 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<std::string, std::string>;
t_KeyPasswords m_store;
static Botan::OctetString hashStrengthenedKey(const StrengthenedKey &key, const Botan::OctetString &salt);
};
#endif // PASSWORDMANAGER_H

162
core/SqlLexer.cpp Normal file
View file

@ -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;
}

View file

@ -8,6 +8,8 @@ enum class BasicTokenType {
End, // End of input End, // End of input
Symbol, // can be many things, keyword, object name, operator, .. Symbol, // can be many things, keyword, object name, operator, ..
Comment, Comment,
BlockComment,
OpenBlockComment, // Busy with a block comment end not detected before end of current input
QuotedString, QuotedString,
DollarQuotedString, DollarQuotedString,
QuotedIdentifier QuotedIdentifier
@ -15,7 +17,8 @@ enum class BasicTokenType {
enum class LexerState { enum class LexerState {
Null, Null,
InDollarQuotedString InDollarQuotedString,
InBlockComment
}; };
@ -34,11 +37,13 @@ public:
*/ */
bool nextBasicToken(int &startpos, int &length, BasicTokenType &tokentype, QString &out); bool nextBasicToken(int &startpos, int &length, BasicTokenType &tokentype, QString &out);
LexerState currentState() const { return m_state; }
private: private:
QString m_block; QString m_block;
int m_pos = 0; int m_pos = 0;
LexerState m_state; LexerState m_state;
}; };
#endif // SQLLEXER_H #endif // SQLLEXER_H

43
core/core.pro Normal file
View file

@ -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
}

View file

@ -0,0 +1,20 @@
#include <sstream>
#include <stdexcept>
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());
}
}

View file

@ -1,6 +1,9 @@
TEMPLATE = subdirs TEMPLATE = subdirs
SUBDIRS += src DEFINES += BOOST_ENABLE_ASSERT_HANDLER
SUBDIRS += src \
core
CONFIG(debug, debug|release) { CONFIG(debug, debug|release) {
SUBDIRS += tests SUBDIRS += tests

View file

@ -57,7 +57,10 @@ ConnectionConfig::ConnectionConfig()
void ConnectionConfig::setName(std::string desc) 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 const std::string& ConnectionConfig::name() const
@ -67,7 +70,10 @@ const std::string& ConnectionConfig::name() const
void ConnectionConfig::setHost(std::string host) 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 const std::string& ConnectionConfig::host() const
@ -77,7 +83,10 @@ const std::string& ConnectionConfig::host() const
void ConnectionConfig::setHostAddr(std::string v) 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 const std::string& ConnectionConfig::hostAddr() const
@ -87,7 +96,11 @@ const std::string& ConnectionConfig::hostAddr() const
void ConnectionConfig::setPort(unsigned short port) 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 unsigned short ConnectionConfig::port() const
@ -97,7 +110,11 @@ unsigned short ConnectionConfig::port() const
void ConnectionConfig::setUser(std::string v) 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 const std::string& ConnectionConfig::user() const
@ -107,7 +124,10 @@ const std::string& ConnectionConfig::user() const
void ConnectionConfig::setPassword(std::string v) 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 const std::string& ConnectionConfig::password() const
@ -117,7 +137,10 @@ const std::string& ConnectionConfig::password() const
void ConnectionConfig::setDbname(std::string v) 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 const std::string& ConnectionConfig::dbname() const
@ -127,7 +150,11 @@ const std::string& ConnectionConfig::dbname() const
void ConnectionConfig::setSslMode(SslMode m) 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 SslMode ConnectionConfig::sslMode() const
@ -137,7 +164,10 @@ SslMode ConnectionConfig::sslMode() const
void ConnectionConfig::setSslCert(std::string v) 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 const std::string& ConnectionConfig::sslCert() const
@ -147,7 +177,10 @@ const std::string& ConnectionConfig::sslCert() const
void ConnectionConfig::setSslKey(std::string v) 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 const std::string& ConnectionConfig::sslKey() const
@ -157,7 +190,10 @@ const std::string& ConnectionConfig::sslKey() const
void ConnectionConfig::setSslRootCert(std::string v) 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 const std::string& ConnectionConfig::sslRootCert() const
@ -167,7 +203,10 @@ const std::string& ConnectionConfig::sslRootCert() const
void ConnectionConfig::setSslCrl(std::string v) 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 const std::string& ConnectionConfig::sslCrl() const
@ -210,3 +249,13 @@ bool ConnectionConfig::isSameDatabase(const ConnectionConfig &rhs) const
&& m_password == rhs.m_password && m_password == rhs.m_password
&& m_dbname == rhs.m_dbname; && m_dbname == rhs.m_dbname;
} }
bool ConnectionConfig::dirty() const
{
return m_dirty;
}
void ConnectionConfig::clean()
{
m_dirty = false;
}

View file

@ -13,6 +13,12 @@ enum class SslMode {
verify_full=5 verify_full=5
}; };
enum class PasswordMode {
Unsave,
Encrypted,
DontSave
};
class ConnectionConfig { class ConnectionConfig {
public: public:
ConnectionConfig(); ConnectionConfig();
@ -57,6 +63,9 @@ public:
const char * const * getValues() const; const char * const * getValues() const;
bool isSameDatabase(const ConnectionConfig &rhs) const; bool isSameDatabase(const ConnectionConfig &rhs) const;
bool dirty() const;
void clean();
private: private:
std::string m_name; std::string m_name;
std::string m_host; std::string m_host;
@ -75,6 +84,8 @@ private:
std::string m_applicationName; std::string m_applicationName;
bool m_dirty = false;
static std::vector<const char*> s_keywords; static std::vector<const char*> s_keywords;
mutable std::vector<const char*> m_values; mutable std::vector<const char*> m_values;
}; };

148
src/ConnectionList.cpp Normal file
View file

@ -0,0 +1,148 @@
#include "ConnectionList.h"
#include "scopeguard.h"
#include "util.h"
#include <QDir>
#include <QStandardPaths>
#include <QSettings>
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();
}
}
}

54
src/ConnectionList.h Normal file
View file

@ -0,0 +1,54 @@
#ifndef CONNECTIONLIST_H
#define CONNECTIONLIST_H
#include "ConnectionConfig.h"
#include <QString>
#include <QUuid>
#include <vector>
#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<LijstElem>;
t_Connections m_connections;
void deleteFromIni(t_Connections::iterator begin, t_Connections::iterator end);
};
#endif // CONNECTIONLIST_H

View file

@ -1,74 +1,27 @@
#include "connectionlistmodel.h" #include "ConnectionListModel.h"
#include <QDir> #include "ConnectionList.h"
#include <QStandardPaths>
#include <QSettings>
#include "scopeguard.h" #include "scopeguard.h"
#include "util.h"
inline QString stdStrToQ(const std::string &s) #include <botan/cryptobox.h>
{
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());
}
/** Saves a connection configuration. ConnectionListModel::ConnectionListModel(ConnectionList *conns, QObject *parent)
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)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, m_connections(conns)
{ {
load(); }
ConnectionListModel::~ConnectionListModel()
{
delete m_connections;
} }
int ConnectionListModel::rowCount(const QModelIndex &parent) const int ConnectionListModel::rowCount(const QModelIndex &parent) const
{ {
int result = 0; int result = 0;
if (parent == QModelIndex()) { if (parent == QModelIndex()) {
result = m_connections.size(); result = m_connections->size();
} }
return result; return result;
} }
@ -84,7 +37,7 @@ QVariant ConnectionListModel::data(const QModelIndex &index, int role) const
if (role == Qt::DisplayRole || role == Qt::EditRole) { if (role == Qt::DisplayRole || role == Qt::EditRole) {
int row = index.row(); int row = index.row();
int col = index.column(); int col = index.column();
const ConnectionConfig& cfg = m_connections.at(row).m_config; const ConnectionConfig& cfg = m_connections->getConfigByIdx(row);
switch (col) { switch (col) {
case 0: case 0:
result = makeLongDescription(cfg); result = makeLongDescription(cfg);
@ -119,9 +72,10 @@ bool ConnectionListModel::setData(const QModelIndex &index, const QVariant &valu
if (role == Qt::EditRole) { if (role == Qt::EditRole) {
int row = index.row(); int row = index.row();
int col = index.column(); int col = index.column();
auto& elem = m_connections.at(row); // auto& elem = m_connections.at(row);
elem.m_dirty = true; // elem.m_dirty = true;
ConnectionConfig& cfg = elem.m_config; // ConnectionConfig& cfg = elem.m_config;
ConnectionConfig& cfg = m_connections->getConfigByIdx(row);
if (col > 0) { if (col > 0) {
result = true; result = true;
} }
@ -158,7 +112,7 @@ Qt::ItemFlags ConnectionListModel::flags(const QModelIndex &index) const
{ {
Qt::ItemFlags result; Qt::ItemFlags result;
int row = index.row(); 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; result = Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled;
} }
return result; return result;
@ -180,17 +134,17 @@ QString ConnectionListModel::makeLongDescription(const ConnectionConfig &cfg)
return stdStrToQ(result); return stdStrToQ(result);
} }
void ConnectionListModel::add(const ConnectionConfig &cfg) void ConnectionListModel::newItem()
{ {
m_connections.emplace_back(QUuid::createUuid(), cfg); int i = m_connections->createNew();
auto idx = createIndex(m_connections.size()-1, 0); auto idx = createIndex(i, 0);
emit dataChanged(idx, idx); emit dataChanged(idx, idx);
} }
Expected<ConnectionConfig> ConnectionListModel::get(int row) Expected<ConnectionConfig> ConnectionListModel::get(int row)
{ {
if (row >= 0 && row < (int)m_connections.size()) { if (row >= 0 && row < (int)m_connections->size()) {
return m_connections.at(row).m_config; return m_connections->getConfigByIdx(row);
} }
else { else {
return Expected<ConnectionConfig>::fromException(std::out_of_range("Invalid row")); return Expected<ConnectionConfig>::fromException(std::out_of_range("Invalid row"));
@ -201,85 +155,29 @@ Expected<ConnectionConfig> ConnectionListModel::get(int row)
bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &parent) bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &parent)
{ {
bool result = false; 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); beginRemoveRows(parent, row, row + count -1);
SCOPE_EXIT { endRemoveRows(); }; SCOPE_EXIT { endRemoveRows(); };
auto f = m_connections.begin() + row; m_connections->remove(row, count);
auto l = f + count;
deleteFromIni(f, l);
m_connections.erase(f, l);
result = true; result = true;
} }
return result; 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()
void ConnectionListModel::load() //{
{ // m_connections->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::save() void ConnectionListModel::save()
{ {
QString file_name = iniFileName(); m_connections->save();
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);
}
} }
void ConnectionListModel::save(int index) void ConnectionListModel::save(int index)
{ {
if (index >= 0 && index < (int)m_connections.size()) { m_connections->save(index);
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;
}
}
} }
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());
}
}

View file

@ -5,11 +5,11 @@
#include <memory> #include <memory>
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QUuid>
#include "connectionconfig.h" #include "connectionconfig.h"
#include "expected.h" #include "expected.h"
class ConnectionList;
/** \brief Model class for the list of connections. /** \brief Model class for the list of connections.
* *
@ -18,7 +18,9 @@
class ConnectionListModel : public QAbstractListModel { class ConnectionListModel : public QAbstractListModel {
Q_OBJECT Q_OBJECT
public: 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 rowCount(const QModelIndex &parent = QModelIndex()) const override;
virtual int columnCount(const QModelIndex &/*parent*/) 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 bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
virtual Qt::ItemFlags flags(const QModelIndex &index) const override; virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
void add(const ConnectionConfig &cfg); void newItem();
Expected<ConnectionConfig> get(int row); Expected<ConnectionConfig> get(int row);
virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override; virtual bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
void load();
void save(); void save();
void save(int index); void save(int index);
private: private:
class LijstElem {
public:
QUuid m_uuid;
bool m_dirty = false;
ConnectionConfig m_config;
LijstElem(const QUuid id, const ConnectionConfig &cfg) ConnectionList *m_connections;
: m_uuid(id), m_config(cfg)
{}
};
using t_Connections = std::vector<LijstElem>;
t_Connections m_connections;
void deleteFromIni(t_Connections::iterator begin, t_Connections::iterator end);
static QString iniFileName();
static QString makeLongDescription(const ConnectionConfig &cfg); static QString makeLongDescription(const ConnectionConfig &cfg);
}; };

View file

@ -37,12 +37,12 @@ ConnectionManagerWindow::~ConnectionManagerWindow()
void ConnectionManagerWindow::on_actionAdd_Connection_triggered() void ConnectionManagerWindow::on_actionAdd_Connection_triggered()
{ {
ConnectionConfig c; // ConnectionConfig c;
c.setName("new"); // c.setName("new");
//m_listModel->add(c); //m_listModel->add(c);
auto clm = m_masterController->getConnectionListModel(); auto clm = m_masterController->getConnectionListModel();
clm->add(c); clm->newItem();
// Select the new row // Select the new row
auto idx = clm->index(clm->rowCount() - 1, 0); auto idx = clm->index(clm->rowCount() - 1, 0);
@ -110,3 +110,67 @@ void ConnectionManagerWindow::on_actionManage_server_triggered()
auto ci = ui->listView->selectionModel()->currentIndex(); auto ci = ui->listView->selectionModel()->currentIndex();
m_masterController->openServerWindowForConnection(ci.row()); m_masterController->openServerWindowForConnection(ci.row());
} }
#include <botan/botan.h>
//#include <botan/base64.h>
//#include <botan/pbkdf.h>
//#include <botan/block_cipher.h>
//#include <botan/hex.h>
#include <botan/cryptobox.h>
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<Botan::byte> 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<Botan::PBKDF> 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<uint8_t> pt(plaintext.data(),plaintext.data()+plaintext.length());
// std::unique_ptr<Botan::Cipher_Mode> enc(Botan::get_cipher_mode("AES-256/CBC/PKCS7", Botan::ENCRYPTION));
// enc->set_key(aes256_key);
// //generate fresh nonce (IV)
// //std::unique_ptr<Botan::RandomNumberGenerator> rng(new Botan::AutoSeeded_RNG);
// std::vector<uint8_t> 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);
}
}

View file

@ -15,10 +15,8 @@ class QStandardItemModel;
/** \brief Class that holds glue code for the ConnectionManager UI. /** \brief Class that holds glue code for the ConnectionManager UI.
* *
*/ */
class ConnectionManagerWindow : public QMainWindow class ConnectionManagerWindow : public QMainWindow {
{
Q_OBJECT Q_OBJECT
public: public:
explicit ConnectionManagerWindow(MasterController *master, QWidget *parent = 0); explicit ConnectionManagerWindow(MasterController *master, QWidget *parent = 0);
~ConnectionManagerWindow(); ~ConnectionManagerWindow();
@ -27,15 +25,13 @@ private slots:
void on_actionAdd_Connection_triggered(); void on_actionAdd_Connection_triggered();
void on_currentChanged(const QModelIndex &current, const QModelIndex &previous); void on_currentChanged(const QModelIndex &current, const QModelIndex &previous);
void on_actionDelete_connection_triggered(); void on_actionDelete_connection_triggered();
void on_actionConnect_triggered(); void on_actionConnect_triggered();
void on_actionQuit_application_triggered(); void on_actionQuit_application_triggered();
void on_actionBackup_database_triggered(); void on_actionBackup_database_triggered();
void on_actionManage_server_triggered(); void on_actionManage_server_triggered();
void on_testButton_clicked();
private: private:
Ui::ConnectionManagerWindow *ui; Ui::ConnectionManagerWindow *ui;
QDataWidgetMapper *m_mapper = nullptr; QDataWidgetMapper *m_mapper = nullptr;

View file

@ -182,6 +182,13 @@
<item row="10" column="1"> <item row="10" column="1">
<widget class="QLineEdit" name="edtCrl"/> <widget class="QLineEdit" name="edtCrl"/>
</item> </item>
<item row="11" column="0">
<widget class="QPushButton" name="testButton">
<property name="text">
<string>PushButton</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>

View file

@ -1,156 +0,0 @@
#ifndef EXPECTED_H
#define EXPECTED_H
template <typename T>
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 <class E>
static Expected<T> fromException(const E& exception)
{
if (typeid(exception) != typeid(E)) {
throw std::invalid_argument("slicing detected");
}
return fromException(std::make_exception_ptr(exception));
}
static Expected<T> fromException(std::exception_ptr p)
{
Expected<T> result;
result.m_valid = false;
new (&result.m_error) std::exception_ptr(std::move(p));
return result;
}
static Expected<T> 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 <class E>
bool hasException() const
{
try {
if (!m_valid) {
std::rethrow_exception(m_error);
}
}
catch (const E& object) {
return true;
}
catch (...) {
}
return false;
}
template <class F>
static Expected fromCode(F fun)
{
try {
return Expected(fun());
}
catch (...) {
return fromException();
}
}
};
#endif // EXPECTED_H

View file

@ -1,6 +1,7 @@
#include "MasterController.h" #include "MasterController.h"
#include "connectionmanagerwindow.h" #include "ConnectionManagerWindow.h"
#include "connectionlistmodel.h" #include "ConnectionList.h"
#include "ConnectionListModel.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "ServerWindow.h" #include "ServerWindow.h"
@ -12,11 +13,14 @@ MasterController::~MasterController()
{ {
delete m_connectionManagerWindow; delete m_connectionManagerWindow;
delete m_connectionListModel; delete m_connectionListModel;
delete m_connectionList;
} }
void MasterController::init() 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 = new ConnectionManagerWindow(this, nullptr);
m_connectionManagerWindow->show(); m_connectionManagerWindow->show();

View file

@ -5,16 +5,18 @@
#include <map> #include <map>
class ConnectionConfig; class ConnectionConfig;
class ConnectionList;
class ConnectionListModel; class ConnectionListModel;
class ConnectionManagerWindow; class ConnectionManagerWindow;
/** \brief Controller class responsible for all things global. /** \brief Controller class responsible for all things global.
*/ */
class MasterController : public QObject class MasterController : public QObject {
{
Q_OBJECT Q_OBJECT
public: public:
explicit MasterController(QObject *parent = 0); explicit MasterController(QObject *parent = 0);
MasterController(const MasterController&) = delete;
MasterController &operator=(const MasterController&) = delete;
~MasterController(); ~MasterController();
void init(); void init();
@ -33,7 +35,7 @@ signals:
public slots: public slots:
private: private:
ConnectionList *m_connectionList = nullptr;
ConnectionListModel *m_connectionListModel = nullptr; ConnectionListModel *m_connectionListModel = nullptr;
ConnectionManagerWindow *m_connectionManagerWindow = nullptr; ConnectionManagerWindow *m_connectionManagerWindow = nullptr;
}; };

View file

@ -22,6 +22,7 @@ QWidget *ParamTypeDelegate::createEditor(QWidget *parent,
QWidget *w = nullptr; QWidget *w = nullptr;
QComboBox *cmbbx = new QComboBox(parent); QComboBox *cmbbx = new QComboBox(parent);
cmbbx->setMaxVisibleItems(32);
cmbbx->setModel(m_typeSelectionModel); cmbbx->setModel(m_typeSelectionModel);
w = cmbbx; w = cmbbx;

View file

@ -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;
}

View file

@ -133,7 +133,10 @@ void SqlSyntaxHighlighter::setTypes(const PgTypeContainer *types)
void SqlSyntaxHighlighter::highlightBlock(const QString &text) 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; int startpos, length;
BasicTokenType tokentype; BasicTokenType tokentype;
QString s; QString s;
@ -151,6 +154,9 @@ void SqlSyntaxHighlighter::highlightBlock(const QString &text)
setFormat(startpos, length, m_typeFormat); setFormat(startpos, length, m_typeFormat);
} }
break; break;
case BasicTokenType::OpenBlockComment:
setCurrentBlockState((int)lexer.currentState());
case BasicTokenType::BlockComment:
case BasicTokenType::Comment: case BasicTokenType::Comment:
setFormat(startpos, length, m_commentFormat); setFormat(startpos, length, m_commentFormat);
break; break;

View file

@ -4,6 +4,7 @@
# #
#------------------------------------------------- #-------------------------------------------------
CONFIG += c++11
QT += core gui QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets sql greaterThan(QT_MAJOR_VERSION, 4): QT += widgets sql
@ -11,9 +12,17 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets sql
TARGET = pglab TARGET = pglab
TEMPLATE = app TEMPLATE = app
INCLUDEPATH += C:\prog\include INCLUDEPATH += C:\prog\include C:\VSproj\boost_1_63_0
DEFINES += WIN32_LEAN_AND_MEAN NOMINMAX 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 win32:RC_ICONS += pglab.ico
@ -29,7 +38,6 @@ SOURCES += main.cpp\
tsqueue.cpp \ tsqueue.cpp \
win32event.cpp \ win32event.cpp \
waithandlelist.cpp \ waithandlelist.cpp \
CsvWriter.cpp \
DatabaseWindow.cpp \ DatabaseWindow.cpp \
ConnectionManagerWindow.cpp \ ConnectionManagerWindow.cpp \
ConnectionListModel.cpp \ ConnectionListModel.cpp \
@ -53,7 +61,6 @@ SOURCES += main.cpp\
ParamListModel.cpp \ ParamListModel.cpp \
MainWindow.cpp \ MainWindow.cpp \
SqlSyntaxHighlighter.cpp \ SqlSyntaxHighlighter.cpp \
SqlLexer.cpp \
ServerWindow.cpp \ ServerWindow.cpp \
ASyncWindow.cpp \ ASyncWindow.cpp \
DatabasesTableModel.cpp \ DatabasesTableModel.cpp \
@ -65,7 +72,8 @@ SOURCES += main.cpp\
PgAuthIdContainer.cpp \ PgAuthIdContainer.cpp \
Pgsql_Result.cpp \ Pgsql_Result.cpp \
Pgsql_Row.cpp \ Pgsql_Row.cpp \
Pgsql_Value.cpp Pgsql_Value.cpp \
ConnectionList.cpp
HEADERS += \ HEADERS += \
sqlparser.h \ sqlparser.h \
@ -78,13 +86,10 @@ HEADERS += \
tsqueue.h \ tsqueue.h \
win32event.h \ win32event.h \
waithandlelist.h \ waithandlelist.h \
CsvWriter.h \
DatabaseWindow.h \ DatabaseWindow.h \
ConnectionManagerWindow.h \ ConnectionManagerWindow.h \
ConnectionListModel.h \ ConnectionListModel.h \
ConnectionConfig.h \ ConnectionConfig.h \
ScopeGuard.h \
Expected.h \
QueryTab.h \ QueryTab.h \
stopwatch.h \ stopwatch.h \
util.h \ util.h \
@ -117,7 +122,8 @@ HEADERS += \
PgAuthIdContainer.h \ PgAuthIdContainer.h \
Pgsql_Result.h \ Pgsql_Result.h \
Pgsql_Row.h \ Pgsql_Row.h \
Pgsql_Value.h Pgsql_Value.h \
ConnectionList.h
FORMS += mainwindow.ui \ FORMS += mainwindow.ui \
DatabaseWindow.ui \ DatabaseWindow.ui \
@ -132,3 +138,14 @@ RESOURCES += \
resources.qrc resources.qrc
QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS,5.01 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

View file

@ -20,7 +20,6 @@ public:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
private: private:
// const PgTypeContainer* m_types;
std::vector<QString> m_types; std::vector<QString> m_types;
}; };

View file

@ -4,6 +4,7 @@
#include <QTextStream> #include <QTextStream>
#include <QClipboard> #include <QClipboard>
#include <sstream> #include <sstream>
#include <stdexcept>
// Supported range from microseconds to seconds // Supported range from microseconds to seconds
// min:sec to hours::min::sec // min:sec to hours::min::sec
@ -134,3 +135,5 @@ QString ConvertToMultiLineCString(const QString &in)
out.append('"'); out.append('"');
return out; return out;
} }

View file

@ -1,6 +1,7 @@
#ifndef UTIL_H #ifndef UTIL_H
#define UTIL_H #define UTIL_H
#include <string>
#include <QString> #include <QString>
#include <QTableView> #include <QTableView>
@ -9,6 +10,23 @@ void copySelectionToClipboard(const QTableView *view);
QString ConvertToMultiLineCString(const QString &in); QString ConvertToMultiLineCString(const QString &in);
void exportTable(const QTableView *view, QTextStream &out); 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 { namespace std {
template <> template <>

View file

@ -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 <gtest/gtest.h> #include <gtest/gtest.h>

View file

@ -8,6 +8,24 @@ CONFIG += qt
QT += core 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

View file

@ -0,0 +1,113 @@
#include <gtest/gtest.h>
#include <gmock/gmock-matchers.h>
#include "CsvWriter.h"
#include <QTextStream>
#include <QByteArray>
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));
}

View file

@ -0,0 +1,73 @@
#include <gtest/gtest.h>
#include <gmock/gmock-matchers.h>
#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) );
}

View file

@ -1,16 +1,11 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <gmock/gmock-matchers.h> #include <gmock/gmock-matchers.h>
#include "../../../src/SqlLexer.h" #include "SqlLexer.h"
using namespace testing; using namespace testing;
TEST(mycase, myset)
{
EXPECT_EQ(1, 1);
ASSERT_THAT(0, Eq(0));
}
TEST(mycase, lexer) TEST(SqlLexer, lexer)
{ {
QString input = " SELECT "; QString input = " SELECT ";
SqlLexer lexer(input, LexerState::Null); SqlLexer lexer(input, LexerState::Null);
@ -26,7 +21,7 @@ TEST(mycase, lexer)
ASSERT_THAT( out, Eq(QString("SELECT")) ); ASSERT_THAT( out, Eq(QString("SELECT")) );
} }
TEST(mycase, lexer_quote_in_string) TEST(SqlLexer, lexer_quote_in_string)
{ {
QString input = " 'abc''def' "; QString input = " 'abc''def' ";
SqlLexer lexer(input, LexerState::Null); SqlLexer lexer(input, LexerState::Null);

View file

@ -0,0 +1,203 @@
#include <gtest/gtest.h>
#include <gmock/gmock-matchers.h>
#include "Expected.h"
using namespace testing;
Expected<int> getAnswerToEverything() { return 42; }
TEST(expected, valid_when_valid_returns_true)
{
Expected<int> v = getAnswerToEverything();
ASSERT_THAT(v.valid(), Eq(true));
}
TEST(expected, get_when_valid_returns_value)
{
Expected<int> v = getAnswerToEverything();
ASSERT_THAT(v.get(), Eq(42));
}
TEST(expected, hasException_when_valid_returns_false)
{
Expected<int> v = getAnswerToEverything();
ASSERT_THAT(v.hasException<std::exception>(), Eq(false));
}
TEST(expected, T_fromException_is_not_valid)
{
auto e = Expected<int>::fromException(std::runtime_error("hello"));
ASSERT_THAT(e.valid(), Eq(false));
}
TEST(expected, T_fromException_get_thows)
{
auto e = Expected<int>::fromException(std::runtime_error("hello"));
ASSERT_THROW (e.get(), std::runtime_error);
}
TEST(expected, T_fromException_has_exception_true)
{
auto e = Expected<int>::fromException(std::runtime_error("hello"));
ASSERT_THAT(e.hasException<std::runtime_error>(), Eq(true));
}
TEST(expected, T_fromException_has_exception_false)
{
auto e = Expected<int>::fromException(std::runtime_error("hello"));
ASSERT_THAT(e.hasException<std::logic_error>(), Eq(false));
}
TEST(expected, T_fromException_has_derived_exception)
{
auto e = Expected<int>::fromException(std::runtime_error("hello"));
ASSERT_THAT(e.hasException<std::exception>(), Eq(true));
}
TEST(expected, T_fromCode_is_valid)
{
auto e = Expected<int>::fromCode([]() -> int { return 42; });
ASSERT_THAT(e.valid(), Eq(true));
}
TEST(expected, T_fromCode_get)
{
auto e = Expected<int>::fromCode([]() -> int { return 42; });
ASSERT_THAT(e.get(), Eq(42));
}
TEST(expected, T_fromCode_E_is_not_valid)
{
auto e = Expected<int>::fromCode([]() -> int { throw std::runtime_error("hello"); });
ASSERT_THAT(e.valid(), Eq(false));
}
TEST(expected, T_fromCode_E_get_thows)
{
auto e = Expected<int>::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<int>::fromCode([]() -> int { throw std::runtime_error("hello"); });
ASSERT_THAT(e.hasException<std::runtime_error>(), Eq(true));
}
TEST(expected, T_fromCode_E_has_exception_false)
{
auto e = Expected<int>::fromCode([]() -> int { throw std::runtime_error("hello"); });
ASSERT_THAT(e.hasException<std::logic_error>(), Eq(false));
}
TEST(expected, T_fromCode_E_has_derived_exception)
{
auto e = Expected<int>::fromCode([]() -> int { throw std::runtime_error("hello"); });
ASSERT_THAT(e.hasException<std::exception>(), Eq(true));
}
//Expected<int> getIntWithStdRuntimeError() { return Expected<void>(); }
Expected<void> getNothing() { return Expected<void>(); }
TEST(expected_void, valid_when_valid_returns_true)
{
Expected<void> v = getNothing();
ASSERT_THAT(v.valid(), Eq(true));
}
TEST(expected_void, get_when_valid_returns_value)
{
Expected<void> v = getNothing();
ASSERT_NO_THROW(v.get());
}
TEST(expected_void, hasException_when_valid_returns_false)
{
Expected<void> v = getNothing();
ASSERT_THAT(v.hasException<std::exception>(), Eq(false));
}
TEST(expected_void, void_fromException_is_not_valid)
{
auto e = Expected<void>::fromException(std::runtime_error("hello"));
ASSERT_THAT(e.valid(), Eq(false));
}
TEST(expected_void, void_fromException_get_thows)
{
auto e = Expected<void>::fromException(std::runtime_error("hello"));
ASSERT_THROW (e.get(), std::runtime_error);
}
TEST(expected_void, void_fromException_has_exception_true)
{
auto e = Expected<void>::fromException(std::runtime_error("hello"));
ASSERT_THAT(e.hasException<std::runtime_error>(), Eq(true));
}
TEST(expected_void, void_fromException_has_exception_false)
{
auto e = Expected<void>::fromException(std::runtime_error("hello"));
ASSERT_THAT(e.hasException<std::logic_error>(), Eq(false));
}
TEST(expected_void, void_fromException_has_derived_exception)
{
auto e = Expected<void>::fromException(std::runtime_error("hello"));
ASSERT_THAT(e.hasException<std::exception>(), Eq(true));
}
TEST(expected_void, void_fromCode_is_valid)
{
auto e = Expected<void>::fromCode([]() -> void { });
ASSERT_THAT(e.valid(), Eq(true));
}
TEST(expected_void, void_fromCode_get)
{
auto e = Expected<void>::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<void>::fromCode(expected_void_throws_func);
ASSERT_THAT(e.valid(), Eq(false));
}
TEST(expected_void, void_fromCode_E_get_thows)
{
auto e = Expected<void>::fromCode(expected_void_throws_func);
ASSERT_THROW (e.get(), std::runtime_error);
}
TEST(expected_void, void_fromCode_E_has_exception_true)
{
auto e = Expected<void>::fromCode(expected_void_throws_func);
ASSERT_THAT(e.hasException<std::runtime_error>(), Eq(true));
}
TEST(expected_void, void_fromCode_E_has_exception_false)
{
auto e = Expected<void>::fromCode(expected_void_throws_func);
ASSERT_THAT(e.hasException<std::logic_error>(), Eq(false));
}
TEST(expected_void, void_fromCode_E_has_derived_exception)
{
auto e = Expected<void>::fromCode(expected_void_throws_func);
ASSERT_THAT(e.hasException<std::exception>(), Eq(true));
}

View file

@ -0,0 +1,63 @@
#include <gtest/gtest.h>
#include <gmock/gmock-matchers.h>
#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<int> v = getAnswerToEverything();
// ASSERT_THAT(v.get(), Eq(42));
//}