diff --git a/.clang-tidy b/.clang-tidy index 4757d19..c1ce8d6 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1 +1 @@ -Checks: '*,clang-analyzer-*,-llvmlibc-*,-fuchsia-*,-altera-*,-abseil-*,-android-*,-modernize-use-trailing-return-type,-readability-identifier-length,-*-readability-todo,-*-magic-numbers,-readability-function-cognitive-complexity,-*-easily-swappable-parameters,-*-pro-type-reinterpret-cast,-*-owning-memory,-concurrency-mt-unsafe,-cert-env33-c' \ No newline at end of file +Checks: '*,clang-analyzer-*,-llvmlibc-*,-fuchsia-*,-altera-*,-abseil-*,-android-*,-modernize-use-trailing-return-type,-readability-identifier-length,-*-readability-todo,-*-magic-numbers,-readability-function-cognitive-complexity,-*-easily-swappable-parameters,-*-pro-type-reinterpret-cast,-*-owning-memory,-concurrency-mt-unsafe,-cert-env33-c,-*-avoid-do-while' \ No newline at end of file diff --git a/.gitignore b/.gitignore index 07adc22..4224ab8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /.cache -/build \ No newline at end of file +/build +*.py +*.dull \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 851d13d..bfb4d2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,6 +5,7 @@ set(CMAKE_CXX_STANDARD 20) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) find_package(Qt6 REQUIRED COMPONENTS Core Widgets) +find_package(PkgConfig REQUIRED) pkg_check_modules(BOTAN REQUIRED botan-3) set(CMAKE_AUTOMOC ON) diff --git a/src/common.h b/src/common.h index c06d4c5..06d4c01 100644 --- a/src/common.h +++ b/src/common.h @@ -5,11 +5,14 @@ #include #define ASSERT(cond) \ - if (!(cond)) { \ - std::cerr << "ASSERTION FAILED at " << __FILE__ << ":" << __LINE__ << "\n" \ - << #cond << std::endl; \ - abort(); \ - } + do { \ + if (!(cond)) { \ + std::cerr << "ASSERTION FAILED at " << __FILE__ << ":" << __LINE__ \ + << "\n" \ + << #cond << std::endl; \ + abort(); \ + } \ + } while (0) using u8 = unsigned char; using i16 = int16_t; diff --git a/src/crypto.h b/src/crypto.h index f97c256..69704c7 100644 --- a/src/crypto.h +++ b/src/crypto.h @@ -15,6 +15,8 @@ encrypt_xchacha20_poly1305(const Botan::secure_vector &plaintext, ASSERT(key.size() == 32); ASSERT(nonce.size() == 24); + // XChaCha20 is selected automatically based on the nonce size + // https://github.com/randombit/botan/blob/master/src/lib/stream/chacha/chacha.cpp#L375 auto cipher = Botan::AEAD_Mode::create_or_throw( "ChaCha20Poly1305", Botan::Cipher_Dir::Encryption); diff --git a/src/mainwindow.cc b/src/mainwindow.cc index 14c2d1d..985d4d0 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -6,9 +6,11 @@ #include MainWindow::MainWindow(QWidget *parent) - : QMainWindow(parent), ui(new Ui::MainWindow) { + : QMainWindow(parent), ui(std::make_unique()) { ui->setupUi(this); + ui->filePreview->setVisible(false); + connect(ui->actionNew, &QAction::triggered, this, [this]() { QString path = QFileDialog::getSaveFileName(this, "Choose vault location", QDir::currentPath(), @@ -33,10 +35,9 @@ MainWindow::MainWindow(QWidget *parent) reload_fs_tree(); }); - connect(ui->fsTreeWidget, &QTreeWidget::itemClicked, - [this](QTreeWidgetItem *item, int column) { - preview_file(item->text(column).toStdString()); - }); + connect( + ui->fsTreeWidget, &QTreeWidget::itemClicked, + [this](QTreeWidgetItem *, int) { ui->filePreview->setVisible(false); }); connect(ui->fsTreeWidget, &QTreeWidget::customContextMenuRequested, this, &MainWindow::file_context_menu); @@ -54,7 +55,7 @@ MainWindow::MainWindow(QWidget *parent) std::string content((std::istreambuf_iterator(file)), std::istreambuf_iterator()); - m_vault->write_file(path_to_filename(path.toStdString()), content); + m_vault->create_file(path_to_filename(path.toStdString()), content); } reload_fs_tree(); @@ -62,6 +63,7 @@ MainWindow::MainWindow(QWidget *parent) } void MainWindow::reload_fs_tree() { + ui->menuFiles->setEnabled(true); ui->fsTreeWidget->clear(); auto headers = m_vault->read_file_headers(); @@ -69,11 +71,14 @@ void MainWindow::reload_fs_tree() { auto *item = new QTreeWidgetItem(ui->fsTreeWidget); item->setText(0, QString::fromStdString(header.name)); item->setText(1, QString::number(header.size)); + item->setText(2, QString::number(header.offset)); } ui->fsTreeWidget->resizeColumnToContents(0); } void MainWindow::preview_file(const std::string &filename) { + ui->filePreview->setVisible(true); + auto content = m_vault->read_file(filename); if (content) { ui->filePreview->setText(QString::fromStdString(content.value())); @@ -102,6 +107,8 @@ void MainWindow::edit_file(const std::string &filename) { QTextStream in(&temp_file); m_vault->update_file(filename, in.readAll().toStdString()); reload_fs_tree(); + + // QTemporaryFile gets deleted when it goes out of scope } else { qWarning() << "File to edit not found"; } @@ -115,6 +122,11 @@ void MainWindow::file_context_menu(const QPoint &pos) { QMenu menu(this); + QAction *preview_action = menu.addAction( + style()->standardIcon(QStyle::SP_FileDialogContentsView), "Preview"); + connect(preview_action, &QAction::triggered, this, + [this, item]() { preview_file(item->text(0).toStdString()); }); + QAction *edit_action = menu.addAction( style()->standardIcon(QStyle::SP_FileDialogDetailedView), "Edit"); connect(edit_action, &QAction::triggered, this, diff --git a/src/mainwindow.h b/src/mainwindow.h index 1f5c7ca..0754843 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -12,7 +12,7 @@ public: explicit MainWindow(QWidget *parent = nullptr); private: - Ui::MainWindow *ui; + std::unique_ptr ui; std::unique_ptr m_vault; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 2d953ab..37c283e 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -6,7 +6,7 @@ 0 0 - 800 + 900 600 @@ -33,6 +33,11 @@ Size + + + Offset + + @@ -46,10 +51,10 @@ 0 0 800 - 30 + 32 - + Vault @@ -57,12 +62,15 @@ + + false + Files - + diff --git a/src/vault.cc b/src/vault.cc index 763fa0f..048e50a 100644 --- a/src/vault.cc +++ b/src/vault.cc @@ -19,7 +19,7 @@ Vault::Vault(std::string path) : m_path(std::move(path)) { ASSERT(static_cast(m_file.read(header.data(), header.size()))); ASSERT(std::string_view(header.data(), header.size()) == "DULL"); - std::int16_t version = 0; + i16 version = 0; ASSERT(m_file.read(reinterpret_cast(&version), sizeof(version))); ASSERT(version == VERSION); } @@ -31,19 +31,13 @@ std::vector Vault::read_file_headers() { m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg); while (true) { - u64 name_size = 0; - if (!m_file.read(reinterpret_cast(&name_size), sizeof(u64))) { + auto header = read_file_header(m_file); + if (header) { + headers.push_back(header.value()); + m_file.seekg(static_cast(header->size), std::ios::cur); + } else { break; } - - FileHeader header{}; - header.name.resize(name_size); - m_file.read(header.name.data(), static_cast(name_size)); - - m_file.read(reinterpret_cast(&header.size), sizeof(u64)); - m_file.seekg(static_cast(header.size), std::ios::cur); - - headers.push_back(header); } return headers; @@ -54,46 +48,42 @@ std::optional Vault::read_file(const std::string &filename) { m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg); while (true) { - u64 name_size = 0; - if (!m_file.read(reinterpret_cast(&name_size), sizeof(u64))) { + auto header = read_file_header(m_file); + if (!header) { break; } - std::string name; - name.resize(name_size); - m_file.read(name.data(), static_cast(name_size)); - - u64 content_size = 0; - m_file.read(reinterpret_cast(&content_size), sizeof(u64)); - - if (name == filename) { - std::string content(content_size, '\0'); - m_file.read(content.data(), static_cast(content_size)); + if (header->name == filename) { + std::string content(header->size, '\0'); + if (!m_file.read(content.data(), static_cast(header->size))) { + break; + } return content; } - m_file.seekg(static_cast(content_size), std::ios::cur); + m_file.seekg(static_cast(header->size), std::ios::cur); } return std::nullopt; } -void Vault::write_file(const std::string &filename, - const std::string &content) { +void Vault::create_file(const std::string &filename, + const std::string &content) { m_file.clear(); m_file.seekp(0, std::ios::end); u64 filename_size = filename.size(); u64 content_size = content.size(); - m_file.write(reinterpret_cast(&filename_size), sizeof(u64)); - m_file.write(filename.data(), static_cast(filename_size)); - m_file.write(reinterpret_cast(&content_size), sizeof(u64)); - m_file.write(content.data(), static_cast(content_size)); + ASSERT(m_file.write(reinterpret_cast(&filename_size), + sizeof(u64))); + ASSERT(m_file.write(filename.data(), static_cast(filename_size))); + ASSERT( + m_file.write(reinterpret_cast(&content_size), sizeof(u64))); + ASSERT(m_file.write(content.data(), static_cast(content_size))); m_file.flush(); } -void Vault::update_file(const std::string &filename, - const std::string &content) { +void Vault::delete_file(const std::string &filename) { m_file.clear(); m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg); @@ -103,26 +93,20 @@ void Vault::update_file(const std::string &filename, while (true) { i64 current_pos = m_file.tellg(); - u64 name_size = 0; - if (!m_file.read(reinterpret_cast(&name_size), sizeof(u64))) { + auto header = read_file_header(m_file); + if (!header) { break; } - std::string name; - name.resize(name_size); - m_file.read(name.data(), static_cast(name_size)); - - u64 content_size = 0; - m_file.read(reinterpret_cast(&content_size), sizeof(u64)); - - if (name == filename) { + if (header->name == filename) { entry_start = current_pos; - entry_total_size = sizeof(u64) + name_size + sizeof(u64) + content_size; - m_file.seekg(static_cast(content_size), std::ios::cur); + entry_total_size = + sizeof(u64) + header->name.length() + sizeof(u64) + header->size; + m_file.seekg(static_cast(header->size), std::ios::cur); break; } - m_file.seekg(static_cast(content_size), std::ios::cur); + m_file.seekg(static_cast(header->size), std::ios::cur); } if (entry_start != -1) { @@ -136,7 +120,7 @@ void Vault::update_file(const std::string &filename, m_file.clear(); m_file.seekp(entry_start, std::ios::beg); - m_file.write(remaining.data(), static_cast(remaining.size())); + ASSERT(m_file.write(remaining.data(), static_cast(remaining.size()))); i64 new_size = entry_start + static_cast(remaining.size()); m_file.flush(); @@ -147,6 +131,32 @@ void Vault::update_file(const std::string &filename, m_file.open(m_path, std::ios::in | std::ios::out | std::ios::binary); ASSERT(m_file.good()); } +} - write_file(filename, content); -} \ No newline at end of file +void Vault::update_file(const std::string &filename, + const std::string &content) { + delete_file(filename); + create_file(filename, content); +} + +std::optional Vault::read_file_header(std::fstream &file) { + FileHeader header{}; + header.offset = file.tellg(); + + u64 name_size = 0; + if (!file.read(reinterpret_cast(&name_size), sizeof(u64))) { + return std::nullopt; + } + + ASSERT(name_size < 10000); + + header.name.resize(name_size); + if (!file.read(header.name.data(), static_cast(name_size))) { + return std::nullopt; + } + + if (!file.read(reinterpret_cast(&header.size), sizeof(u64))) { + return std::nullopt; + } + return header; +} diff --git a/src/vault.h b/src/vault.h index 9a3ffc5..3d5a9d9 100644 --- a/src/vault.h +++ b/src/vault.h @@ -11,6 +11,7 @@ constexpr u64 AFTER_HEADER_OFFSET = 6; struct FileHeader { std::string name; u64 size; + u64 offset; }; class Vault { @@ -19,9 +20,12 @@ public: std::vector read_file_headers(); std::optional read_file(const std::string &name); - void write_file(const std::string &name, const std::string &content); + void create_file(const std::string &name, const std::string &content); + void delete_file(const std::string &name); void update_file(const std::string &name, const std::string &content); + static std::optional read_file_header(std::fstream &file); + private: std::string m_path; std::fstream m_file;