Builds on windows again
This commit is contained in:
parent
33cf39b799
commit
bebb3391c3
160 changed files with 138 additions and 117 deletions
100
core/BackupFormatModel.cpp
Normal file
100
core/BackupFormatModel.cpp
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#include "BackupFormatModel.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
class BackupFormatItem {
|
||||
public:
|
||||
const QString shortFlag;
|
||||
const QString longFlag;
|
||||
const QString description;
|
||||
|
||||
BackupFormatItem(QString s, QString l, QString d)
|
||||
: shortFlag(std::move(s))
|
||||
, longFlag(std::move(l))
|
||||
, description(std::move(d))
|
||||
{}
|
||||
};
|
||||
|
||||
using t_BackupFormatItemVector = std::vector<BackupFormatItem>;
|
||||
|
||||
|
||||
t_BackupFormatItemVector g_BackupFormats = {
|
||||
BackupFormatItem{ "p", "plain", "Output a plaintext SQL script, restore with psql" },
|
||||
BackupFormatItem{ "c", "custom", "Postgresql's own format most flexible and compressed, restore with pg_restore" },
|
||||
BackupFormatItem{ "d", "directory", "Generates a directory with a file for each table or blob" },
|
||||
BackupFormatItem{ "t", "tar", "Similar to directory if untarred it results in a valid directory backup" }
|
||||
};
|
||||
|
||||
} // end of unnamed namespace
|
||||
|
||||
|
||||
|
||||
BackupFormatModel::BackupFormatModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
//QVariant BackupFormatModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
//{
|
||||
// QVariant result;
|
||||
|
||||
// if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
|
||||
// switch (section) {
|
||||
// case Column::Short:
|
||||
// result = tr("Short");
|
||||
// break;
|
||||
// case Column::Long:
|
||||
// result = tr("Long");
|
||||
// break;
|
||||
// case Column::Description:
|
||||
// result = tr("Description");
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// return result;
|
||||
//}
|
||||
|
||||
|
||||
int BackupFormatModel::rowCount(const QModelIndex &) const
|
||||
{
|
||||
int size = g_BackupFormats.size();
|
||||
return size;
|
||||
}
|
||||
|
||||
int BackupFormatModel::columnCount(const QModelIndex &) const
|
||||
{
|
||||
return 3;
|
||||
|
||||
}
|
||||
|
||||
QVariant BackupFormatModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
QVariant result;
|
||||
if (index.isValid()) {
|
||||
const int row = index.row();
|
||||
const int col = index.column();
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
const auto &item = g_BackupFormats.at(row);
|
||||
switch (col) {
|
||||
case ColumnShort:
|
||||
result = item.shortFlag;
|
||||
break;
|
||||
case ColumnLong:
|
||||
result = item.longFlag;
|
||||
break;
|
||||
case ColumnDescription:
|
||||
result = item.description;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (role == Qt::ToolTipRole) {
|
||||
const auto &item = g_BackupFormats.at(row);
|
||||
result = item.description;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
26
core/BackupFormatModel.h
Normal file
26
core/BackupFormatModel.h
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef BACKUPFORMATMODEL_H
|
||||
#define BACKUPFORMATMODEL_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
class BackupFormatModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Column { ColumnShort=1, ColumnLong=0, ColumnDescription=2 };
|
||||
|
||||
explicit BackupFormatModel(QObject *parent);
|
||||
|
||||
// Header:
|
||||
// QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
// Basic functionality:
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
#endif // BACKUPFORMATMODEL_H
|
||||
41
core/CMakeLists.txt
Normal file
41
core/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
|
||||
add_library(core STATIC
|
||||
BackupFormatModel.cpp
|
||||
CsvWriter.cpp
|
||||
my_boost_assert_handler.cpp
|
||||
PasswordManager.cpp
|
||||
SqlLexer.cpp)
|
||||
|
||||
target_link_libraries(core PUBLIC
|
||||
boost
|
||||
botan
|
||||
Qt5::Core
|
||||
)
|
||||
|
||||
target_include_directories(core INTERFACE
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
)
|
||||
|
||||
set_target_properties(core PROPERTIES
|
||||
CXX_STANDARD 14
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
POSITION_INDEPENDENT_CODE True
|
||||
)
|
||||
|
||||
add_executable(runtests
|
||||
test/main.cpp
|
||||
test/tst_CsvWriter.cpp
|
||||
test/tst_expected.cpp
|
||||
test/tst_PasswordManager.cpp
|
||||
test/tst_scopeguard.cpp
|
||||
test/tst_SqlLexer.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(runtests
|
||||
core
|
||||
gtest
|
||||
Threads::Threads
|
||||
)
|
||||
|
||||
add_test(tests runtests)
|
||||
64
core/CsvWriter.cpp
Normal file
64
core/CsvWriter.cpp
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
#include "CsvWriter.h"
|
||||
|
||||
CsvWriter::CsvWriter()
|
||||
{}
|
||||
|
||||
CsvWriter::CsvWriter(QTextStream *output)
|
||||
: m_output(output)
|
||||
{}
|
||||
|
||||
void CsvWriter::setDestination(QTextStream *output)
|
||||
{
|
||||
m_output = output;
|
||||
m_column = 0;
|
||||
}
|
||||
|
||||
void CsvWriter::setSeperator(QChar ch)
|
||||
{
|
||||
m_seperator = ch;
|
||||
}
|
||||
|
||||
void CsvWriter::setQuote(QChar ch)
|
||||
{
|
||||
m_quote = ch;
|
||||
}
|
||||
|
||||
void CsvWriter::writeField(QString field)
|
||||
{
|
||||
QTextStream &out = *m_output;
|
||||
if (m_column > 0) {
|
||||
out << m_seperator;
|
||||
}
|
||||
// if field contains any of seperator, quote or newline then it needs to be quoted
|
||||
// when quoted quotes need to be doubled to escape them
|
||||
bool needs_quotes = false;
|
||||
for (auto ch : field) {
|
||||
if (ch == '\n' || ch == m_seperator || ch == m_quote) {
|
||||
needs_quotes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_quotes) {
|
||||
out << m_quote;
|
||||
for (auto ch : field) {
|
||||
if (ch == m_quote)
|
||||
out << m_quote;
|
||||
out << ch;
|
||||
}
|
||||
out << m_quote;
|
||||
}
|
||||
else {
|
||||
out << field;
|
||||
}
|
||||
++m_column;
|
||||
}
|
||||
|
||||
|
||||
void CsvWriter::nextRow()
|
||||
{
|
||||
QTextStream &out = *m_output;
|
||||
out << '\n';
|
||||
m_column = 0;
|
||||
}
|
||||
|
||||
23
core/CsvWriter.h
Normal file
23
core/CsvWriter.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
#ifndef CSVWRITER_H
|
||||
#define CSVWRITER_H
|
||||
|
||||
#include <ostream>
|
||||
#include <QTextStream>
|
||||
|
||||
class CsvWriter {
|
||||
public:
|
||||
CsvWriter();
|
||||
explicit CsvWriter(QTextStream *output);
|
||||
void setDestination(QTextStream *output);
|
||||
void setSeperator(QChar ch);
|
||||
void setQuote(QChar ch);
|
||||
void writeField(QString field);
|
||||
void nextRow();
|
||||
private:
|
||||
QChar m_seperator = ',';
|
||||
QChar m_quote = '"';
|
||||
QTextStream *m_output = nullptr;
|
||||
int m_column = 0;
|
||||
};
|
||||
|
||||
#endif // CSVWRITER_H
|
||||
271
core/Expected.h
Normal file
271
core/Expected.h
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
#ifndef EXPECTED_H
|
||||
#define EXPECTED_H
|
||||
|
||||
#include <stdexcept>
|
||||
#include <typeinfo>
|
||||
#include <utility>
|
||||
|
||||
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.m_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_error(rhs.m_error)
|
||||
, m_valid(rhs.m_valid)
|
||||
{}
|
||||
|
||||
Expected(Expected<void> &&rhs)
|
||||
: m_error(std::move(rhs.m_error))
|
||||
, m_valid(rhs.m_valid)
|
||||
{}
|
||||
|
||||
~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
|
||||
243
core/PasswordManager.cpp
Normal file
243
core/PasswordManager.cpp
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
#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>
|
||||
|
||||
|
||||
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
66
core/PasswordManager.h
Normal 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
|
||||
1
core/QueuedBackgroundTask.cpp
Normal file
1
core/QueuedBackgroundTask.cpp
Normal file
|
|
@ -0,0 +1 @@
|
|||
#include "QueuedBackgroundTask.h"
|
||||
52
core/QueuedBackgroundTask.h
Normal file
52
core/QueuedBackgroundTask.h
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
#pragma once
|
||||
|
||||
#include <QRunnable>
|
||||
#include <QVector>
|
||||
|
||||
/** Base class for an object that can be queued for execution in the background.
|
||||
*
|
||||
* It is meaned for long running processes the end user might want to abort, pause and check progress on.
|
||||
*/
|
||||
class QueuedBackgroundTask: public QRunnable
|
||||
{
|
||||
/** Task should exit it's run method but remember it's state, it might be resumed later.
|
||||
*/
|
||||
void requestPause();
|
||||
|
||||
/** Request to stop running and forget progress
|
||||
*/
|
||||
void requestAbort();
|
||||
|
||||
QString getProgressInfo();
|
||||
};
|
||||
|
||||
class BackgroundTaskInfo {
|
||||
public:
|
||||
};
|
||||
|
||||
/** Manages objects of type QueuedBackgroundTask
|
||||
*
|
||||
* The basic operation of this queue is hand everything directly of to the default
|
||||
* threadpool and that will decide when to run each task. But extra functions are
|
||||
* provided to control tasks to allow the program to have a UI for controlling the tasks
|
||||
*/
|
||||
class BackgroundTaskQueue {
|
||||
|
||||
public:
|
||||
|
||||
/** Returns a list of tasks in the queue and their progress.
|
||||
*
|
||||
* When building a UI to show realtime activity then make sure to first register the update events
|
||||
* then get the snapshot. After that you can use the events to update the contents of the UI.
|
||||
*/
|
||||
QVector<BackgroundTaskInfo> getTaskInfoSnapshort();
|
||||
|
||||
void pauseTask( );
|
||||
|
||||
/** Resume a paused task
|
||||
*/
|
||||
void resumeTask( );
|
||||
|
||||
void abortTask( );
|
||||
|
||||
};
|
||||
69
core/ScopeGuard.h
Normal file
69
core/ScopeGuard.h
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#ifndef SCOPEGUARD_H
|
||||
#define SCOPEGUARD_H
|
||||
|
||||
/** \brief Template class for executing code at scope exit.
|
||||
*
|
||||
* By default the object will be an active mode and execute the function
|
||||
* passed to the constructor when the object is destructed. You can however
|
||||
* cancel this action by calling dismiss().
|
||||
*
|
||||
* There is a clever macro that allows you to write something like
|
||||
* SCOPE_EXIT { foo(); };
|
||||
*/
|
||||
template<class Fun>
|
||||
class ScopeGuard {
|
||||
Fun f_;
|
||||
bool active_;
|
||||
public:
|
||||
ScopeGuard(Fun f)
|
||||
: f_(std::move(f))
|
||||
, active_(true) {
|
||||
}
|
||||
|
||||
~ScopeGuard() { if(active_) f_(); }
|
||||
|
||||
void dismiss() { active_=false; }
|
||||
|
||||
ScopeGuard() = delete;
|
||||
ScopeGuard(const ScopeGuard&) = delete;
|
||||
ScopeGuard& operator=(const ScopeGuard&) = delete;
|
||||
ScopeGuard(ScopeGuard&& rhs)
|
||||
: f_(std::move(rhs.f_))
|
||||
, active_(rhs.active_)
|
||||
{
|
||||
rhs.dismiss();
|
||||
}
|
||||
};
|
||||
|
||||
template<class Fun>
|
||||
ScopeGuard<Fun> scopeGuard(Fun f)
|
||||
{
|
||||
return ScopeGuard<Fun>(std::move(f));
|
||||
}
|
||||
|
||||
namespace ScopeGuard_detail {
|
||||
|
||||
enum class ScopeGuardOnExit {};
|
||||
|
||||
template<typename Fun>
|
||||
ScopeGuard<Fun> operator+(ScopeGuardOnExit, Fun&& fn) {
|
||||
return ScopeGuard<Fun>(std::forward<Fun>(fn));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#define CONCATENATE_IMPL(s1, s2) s1##s2
|
||||
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
|
||||
#ifdef __COUNTER__
|
||||
#define ANONYMOUS_VARIABLE(str) \
|
||||
CONCATENATE(str,__COUNTER__)
|
||||
#else
|
||||
#define ANONYMOUS_VARIABLE(str) \
|
||||
CONCATENATE(str,__LINE__)
|
||||
#endif
|
||||
|
||||
#define SCOPE_EXIT \
|
||||
auto ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE) \
|
||||
= ::ScopeGuard_detail::ScopeGuardOnExit() + [&]()
|
||||
|
||||
#endif // SCOPEGUARD_H
|
||||
203
core/SqlLexer.cpp
Normal file
203
core/SqlLexer.cpp
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
#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 if (c == '$') {
|
||||
c = nextChar();
|
||||
if (c.isDigit()) {
|
||||
for (;;) {
|
||||
c = peekChar();
|
||||
if (c.isDigit())
|
||||
nextChar();
|
||||
else
|
||||
break;
|
||||
}
|
||||
tokentype = BasicTokenType::Parameter;
|
||||
length = m_pos - startpos;
|
||||
QStringRef sr(&m_block, startpos, length);
|
||||
out = sr.toString();
|
||||
return true;
|
||||
}
|
||||
else if (c.isLetter()) {
|
||||
// is this a dollar quote?
|
||||
while (true) {
|
||||
c = nextChar();
|
||||
if (c == '$') {
|
||||
// Found valid dollar quote
|
||||
tokentype = BasicTokenType::DollarQuote;
|
||||
length = m_pos - startpos;
|
||||
QStringRef sr(&m_block, startpos, length);
|
||||
out = sr.toString();
|
||||
return true;
|
||||
}
|
||||
else if (!c.isLetter()) {
|
||||
// ERROR, unallowed character
|
||||
tokentype = BasicTokenType::None;
|
||||
length = m_pos - startpos;
|
||||
QStringRef sr(&m_block, startpos, length);
|
||||
out = sr.toString();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
50
core/SqlLexer.h
Normal file
50
core/SqlLexer.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef SQLLEXER_H
|
||||
#define SQLLEXER_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
enum class BasicTokenType {
|
||||
None,
|
||||
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,
|
||||
DollarQuote, // Return the dollar quote tag, do not consume the entire string (potentially long)
|
||||
QuotedIdentifier,
|
||||
Parameter
|
||||
};
|
||||
|
||||
enum class LexerState {
|
||||
Null,
|
||||
InDollarQuotedString,
|
||||
InBlockComment
|
||||
};
|
||||
|
||||
|
||||
class SqlLexer {
|
||||
public:
|
||||
SqlLexer(const QString &block, LexerState currentstate);
|
||||
QChar nextChar();
|
||||
QChar peekChar();
|
||||
/**
|
||||
* @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 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
|
||||
47
core/core.pro
Normal file
47
core/core.pro
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
#-------------------------------------------------
|
||||
#
|
||||
# Project created by QtCreator 2017-02-26T10:51:14
|
||||
#
|
||||
#-------------------------------------------------
|
||||
|
||||
QT -= gui
|
||||
|
||||
TARGET = core
|
||||
TEMPLATE = lib
|
||||
CONFIG += staticlib c++14
|
||||
|
||||
INCLUDEPATH += C:\prog\include C:\VSproj\boost32\include\boost-1_65_1
|
||||
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 += my_boost_assert_handler.cpp \
|
||||
SqlLexer.cpp \
|
||||
PasswordManager.cpp \
|
||||
CsvWriter.cpp \
|
||||
BackupFormatModel.cpp \
|
||||
QueuedBackgroundTask.cpp
|
||||
|
||||
HEADERS += PasswordManager.h \
|
||||
SqlLexer.h \
|
||||
ScopeGuard.h \
|
||||
CsvWriter.h \
|
||||
BackupFormatModel.h \
|
||||
QueuedBackgroundTask.h \
|
||||
Expected.h
|
||||
|
||||
unix {
|
||||
target.path = /usr/lib
|
||||
INSTALLS += target
|
||||
}
|
||||
20
core/my_boost_assert_handler.cpp
Normal file
20
core/my_boost_assert_handler.cpp
Normal 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());
|
||||
}
|
||||
}
|
||||
|
||||
8
core/test/main.cpp
Normal file
8
core/test/main.cpp
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#include <gtest/gtest.h>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
113
core/test/tst_CsvWriter.cpp
Normal file
113
core/test/tst_CsvWriter.cpp
Normal 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_EQ(result, 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_EQ(result, 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_EQ(result, 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_EQ(result, 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_EQ(result, 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_EQ(result, 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_EQ(result, expected);
|
||||
}
|
||||
72
core/test/tst_PasswordManager.cpp
Normal file
72
core/test/tst_PasswordManager.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#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_TRUE(res.get());
|
||||
}
|
||||
|
||||
TEST(PasswordManager, unlock_succeeds)
|
||||
{
|
||||
PasswordManager pwm;
|
||||
|
||||
std::string passphrase = "my test passphrase";
|
||||
|
||||
auto res = pwm.changeMasterPassword("", passphrase);
|
||||
ASSERT_NO_THROW( res.get() );
|
||||
ASSERT_TRUE(res.get());
|
||||
|
||||
auto res2 = pwm.unlock(passphrase);
|
||||
ASSERT_NO_THROW( res2.get() );
|
||||
ASSERT_TRUE(res2.get());
|
||||
}
|
||||
|
||||
TEST(PasswordManager, unlock_fails)
|
||||
{
|
||||
PasswordManager pwm;
|
||||
|
||||
std::string passphrase = "my test passphrase";
|
||||
|
||||
auto res = pwm.changeMasterPassword("", passphrase);
|
||||
ASSERT_NO_THROW(res.get());
|
||||
ASSERT_TRUE(res.get());
|
||||
|
||||
auto res2 = pwm.unlock(passphrase + "2");
|
||||
ASSERT_NO_THROW(res2.get());
|
||||
ASSERT_FALSE(res2.get());
|
||||
}
|
||||
|
||||
TEST(PasswordManager, test_save_get)
|
||||
{
|
||||
PasswordManager pwm;
|
||||
|
||||
std::string passphrase = "my test passphrase";
|
||||
|
||||
auto res = pwm.changeMasterPassword("", passphrase);
|
||||
ASSERT_NO_THROW( res.get() );
|
||||
ASSERT_TRUE(res.get());
|
||||
|
||||
// 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_TRUE(res2.valid());
|
||||
|
||||
std::string result;
|
||||
auto res3 = pwm.getPassword(key, result);
|
||||
ASSERT_TRUE(res3.valid());
|
||||
ASSERT_TRUE(res3.get());
|
||||
ASSERT_EQ(result, password);
|
||||
}
|
||||
38
core/test/tst_SqlLexer.cpp
Normal file
38
core/test/tst_SqlLexer.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#include <gtest/gtest.h>
|
||||
#include <gmock/gmock-matchers.h>
|
||||
#include "SqlLexer.h"
|
||||
|
||||
using namespace testing;
|
||||
|
||||
|
||||
TEST(SqlLexer, lexer)
|
||||
{
|
||||
QString input = " SELECT ";
|
||||
SqlLexer lexer(input, LexerState::Null);
|
||||
|
||||
int startpos, length;
|
||||
BasicTokenType tokentype;
|
||||
QString out;
|
||||
lexer.nextBasicToken(startpos, length, tokentype, out);
|
||||
|
||||
ASSERT_EQ(startpos, 1);
|
||||
ASSERT_EQ(length, 6);
|
||||
ASSERT_EQ(tokentype, BasicTokenType::Symbol);
|
||||
ASSERT_EQ( out, QString("SELECT") );
|
||||
}
|
||||
|
||||
TEST(SqlLexer, lexer_quote_in_string)
|
||||
{
|
||||
QString input = " 'abc''def' ";
|
||||
SqlLexer lexer(input, LexerState::Null);
|
||||
|
||||
int startpos, length;
|
||||
BasicTokenType tokentype;
|
||||
QString out;
|
||||
lexer.nextBasicToken(startpos, length, tokentype, out);
|
||||
|
||||
ASSERT_EQ(startpos, 1);
|
||||
ASSERT_EQ(length, 10);
|
||||
ASSERT_EQ(tokentype, BasicTokenType::QuotedString);
|
||||
}
|
||||
|
||||
226
core/test/tst_expected.cpp
Normal file
226
core/test/tst_expected.cpp
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
#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_TRUE(v.valid());
|
||||
}
|
||||
|
||||
TEST(expected, get_when_valid_returns_value)
|
||||
{
|
||||
Expected<int> v = getAnswerToEverything();
|
||||
ASSERT_EQ(v.get(), 42);
|
||||
}
|
||||
|
||||
TEST(expected, get_when_valid_returns_value_copycon)
|
||||
{
|
||||
Expected<int> t = getAnswerToEverything();
|
||||
Expected<int> v(t);
|
||||
ASSERT_TRUE(v.valid());
|
||||
ASSERT_EQ(v.get(), 42);
|
||||
}
|
||||
|
||||
TEST(expected, hasException_when_valid_returns_false)
|
||||
{
|
||||
Expected<int> v = getAnswerToEverything();
|
||||
ASSERT_FALSE(v.hasException<std::exception>());
|
||||
}
|
||||
|
||||
TEST(expected, T_fromException_is_not_valid)
|
||||
{
|
||||
auto e = Expected<int>::fromException(std::runtime_error("hello"));
|
||||
ASSERT_FALSE(e.valid());
|
||||
}
|
||||
|
||||
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_get_thows_copycon)
|
||||
{
|
||||
auto f = Expected<int>::fromException(std::runtime_error("hello"));
|
||||
auto e(f);
|
||||
ASSERT_THROW (e.get(), std::runtime_error);
|
||||
}
|
||||
|
||||
TEST(expected, T_fromException_has_exception_true)
|
||||
{
|
||||
auto e = Expected<int>::fromException(std::runtime_error("hello"));
|
||||
ASSERT_TRUE(e.hasException<std::runtime_error>());
|
||||
}
|
||||
|
||||
TEST(expected, T_fromException_has_exception_false)
|
||||
{
|
||||
auto e = Expected<int>::fromException(std::runtime_error("hello"));
|
||||
ASSERT_FALSE(e.hasException<std::logic_error>());
|
||||
}
|
||||
|
||||
TEST(expected, T_fromException_has_derived_exception)
|
||||
{
|
||||
auto e = Expected<int>::fromException(std::runtime_error("hello"));
|
||||
ASSERT_TRUE(e.hasException<std::exception>());
|
||||
}
|
||||
|
||||
TEST(expected, T_fromCode_is_valid)
|
||||
{
|
||||
auto e = Expected<int>::fromCode([]() -> int { return 42; });
|
||||
ASSERT_TRUE(e.valid());
|
||||
}
|
||||
|
||||
TEST(expected, T_fromCode_get)
|
||||
{
|
||||
auto e = Expected<int>::fromCode([]() -> int { return 42; });
|
||||
ASSERT_EQ(e.get(), 42);
|
||||
}
|
||||
|
||||
|
||||
TEST(expected, T_fromCode_E_is_not_valid)
|
||||
{
|
||||
auto e = Expected<int>::fromCode([]() -> int { throw std::runtime_error("hello"); });
|
||||
ASSERT_FALSE(e.valid());
|
||||
}
|
||||
|
||||
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_TRUE(e.hasException<std::runtime_error>());
|
||||
}
|
||||
|
||||
TEST(expected, T_fromCode_E_has_exception_false)
|
||||
{
|
||||
auto e = Expected<int>::fromCode([]() -> int { throw std::runtime_error("hello"); });
|
||||
ASSERT_FALSE(e.hasException<std::logic_error>());
|
||||
}
|
||||
|
||||
TEST(expected, T_fromCode_E_has_derived_exception)
|
||||
{
|
||||
auto e = Expected<int>::fromCode([]() -> int { throw std::runtime_error("hello"); });
|
||||
ASSERT_TRUE(e.hasException<std::exception>());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//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_TRUE(v.valid());
|
||||
}
|
||||
|
||||
TEST(expected_void, get_when_valid_returns_value)
|
||||
{
|
||||
Expected<void> v = getNothing();
|
||||
ASSERT_NO_THROW(v.get());
|
||||
}
|
||||
|
||||
TEST(expected_void, get_when_valid_returns_value_copycon)
|
||||
{
|
||||
Expected<void> t = getNothing();
|
||||
auto v(t);
|
||||
ASSERT_TRUE(v.valid());
|
||||
ASSERT_NO_THROW(v.get());
|
||||
}
|
||||
|
||||
TEST(expected_void, hasException_when_valid_returns_false)
|
||||
{
|
||||
Expected<void> v = getNothing();
|
||||
ASSERT_FALSE(v.hasException<std::exception>());
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
63
core/test/tst_scopeguard.cpp
Normal file
63
core/test/tst_scopeguard.cpp
Normal 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_FALSE(result);
|
||||
}
|
||||
|
||||
TEST(ScopeGuard, normal_run_fun_on_destruction_2)
|
||||
{
|
||||
bool result = false;
|
||||
{
|
||||
auto sg = scopeGuard([&result]() { result = true; });
|
||||
}
|
||||
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
TEST(ScopeGuard, dismiss)
|
||||
{
|
||||
bool result = false;
|
||||
{
|
||||
auto sg = scopeGuard([&result]() { result = true; });
|
||||
sg.dismiss();
|
||||
}
|
||||
|
||||
ASSERT_FALSE(result);
|
||||
}
|
||||
|
||||
TEST(ScopeGuard, SCOPE_EXIT_macro_1)
|
||||
{
|
||||
bool result = false;
|
||||
{
|
||||
SCOPE_EXIT { result = true; };
|
||||
ASSERT_FALSE(result); // prove previous statement hasn't run yet
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST(ScopeGuard, SCOPE_EXIT_macro_2)
|
||||
{
|
||||
bool result = false;
|
||||
{
|
||||
SCOPE_EXIT { result = true; };
|
||||
}
|
||||
|
||||
ASSERT_TRUE(result);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//TEST(expected, get_when_valid_returns_value)
|
||||
//{
|
||||
// Expected<int> v = getAnswerToEverything();
|
||||
// ASSERT_THAT(v.get(), Eq(42));
|
||||
//}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue