From 48fc35e16c4e6d139b47654af7a7a3b752c495dc Mon Sep 17 00:00:00 2001 From: Toni Date: Sat, 25 Apr 2026 13:37:53 +0200 Subject: [PATCH] mark as deleted and zero content on deletion instead of shifting everything --- src/common.h | 9 ------ src/mainwindow.cc | 10 +++--- src/vault.cc | 81 +++++++++++++++++++++-------------------------- src/vault.h | 1 + 4 files changed, 43 insertions(+), 58 deletions(-) diff --git a/src/common.h b/src/common.h index 0b7a6ef..4195a4d 100644 --- a/src/common.h +++ b/src/common.h @@ -2,7 +2,6 @@ #include #include -#include #define ASSERT(cond) \ do { \ @@ -25,11 +24,3 @@ static_assert(sizeof(float) * 8 == 32); using f32 = float; static_assert(sizeof(double) * 8 == 64); using f64 = double; - -template constexpr char *to_char_ptr(T *ptr) noexcept { - return reinterpret_cast(ptr); -} - -template constexpr const char *to_char_ptr(const T *ptr) noexcept { - return reinterpret_cast(ptr); -} diff --git a/src/mainwindow.cc b/src/mainwindow.cc index 41d136d..6f6bac8 100644 --- a/src/mainwindow.cc +++ b/src/mainwindow.cc @@ -230,13 +230,15 @@ void MainWindow::file_context_menu(const QPoint &pos) { QMenu menu(this); - QAction *preview_action = menu.addAction( - style()->standardIcon(QStyle::SP_FileDialogContentsView), "Preview"); + QAction *preview_action = + menu.addAction(style()->standardIcon(QStyle::SP_FileDialogContentsView), + "Preview as text"); 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"); + QAction *edit_action = + menu.addAction(style()->standardIcon(QStyle::SP_FileDialogDetailedView), + "Edit in external app"); connect(edit_action, &QAction::triggered, this, [this, item]() { edit_file(item->text(0).toStdString()); }); diff --git a/src/vault.cc b/src/vault.cc index c2531e6..ba78504 100644 --- a/src/vault.cc +++ b/src/vault.cc @@ -2,7 +2,18 @@ #include "common.h" #include "crypto.h" #include -#include + +namespace { + +template constexpr char *to_char_ptr(T *ptr) noexcept { + return reinterpret_cast(ptr); +} + +template constexpr const char *to_char_ptr(const T *ptr) noexcept { + return reinterpret_cast(ptr); +} + +} // namespace Vault::Vault(std::string path, const std::string &password) : m_path(std::move(path)) { @@ -57,7 +68,9 @@ std::vector Vault::read_file_headers() { while (true) { auto header = read_file_header(); if (header) { - headers.push_back(header.value()); + if (!header->deleted) { + headers.push_back(header.value()); + } m_file.seekg(static_cast(header->content_ciphertext_size), std::ios::cur); } else { @@ -78,7 +91,7 @@ std::optional Vault::read_file(const std::string &filename) { break; } - if (header->name == filename) { + if (!header->deleted && header->name == filename) { Botan::secure_vector ciphertext; ciphertext.resize(header->content_ciphertext_size); if (!m_file.read(to_char_ptr(ciphertext.data()), @@ -110,6 +123,7 @@ void Vault::create_file(const std::string &filename, auto ciphertext = Crypto::encrypt(m_key, content_sv); u64 ciphertext_size = ciphertext.size(); + ASSERT(m_file.write("\0", 1)); // deleted flag ASSERT(m_file.write(to_char_ptr(&filename_ciphertext_size), sizeof(u64))); ASSERT(m_file.write(to_char_ptr(filename_ciphertext.data()), static_cast(filename_ciphertext_size))); @@ -123,9 +137,6 @@ void Vault::delete_file(const std::string &filename) { m_file.clear(); m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg); - i64 entry_start = -1; - u64 entry_total_size = 0; - while (true) { i64 current_pos = m_file.tellg(); @@ -134,49 +145,21 @@ void Vault::delete_file(const std::string &filename) { break; } - if (header->name == filename) { - entry_start = current_pos; - entry_total_size = sizeof(u64) + header->name_ciphertext_size + - sizeof(u64) + header->content_ciphertext_size; - m_file.seekg(static_cast(header->content_ciphertext_size), - std::ios::cur); + if (!header->deleted && header->name == filename) { + // zero the ciphertext and mark as deleted + m_file.seekp(m_file.tellg()); + for (size_t i = 0; i < header->content_ciphertext_size; i++) { + m_file.write(to_char_ptr("\0"), 1); + } + + m_file.seekp(current_pos, std::ios::beg); + m_file.write(to_char_ptr("\1"), 1); break; } m_file.seekg(static_cast(header->content_ciphertext_size), std::ios::cur); } - - if (entry_start != -1) { - i64 entry_end = entry_start + static_cast(entry_total_size); - - i64 read_pos = entry_end; - i64 write_pos = entry_start; - std::array buffer{}; - while (true) { - m_file.clear(); - m_file.seekg(read_pos); - m_file.read(to_char_ptr(buffer.data()), buffer.size()); - auto bytes = m_file.gcount(); - if (bytes == 0) { - break; - } - - m_file.seekp(write_pos); - ASSERT(m_file.write(to_char_ptr(buffer.data()), bytes)); - - read_pos += bytes; - write_pos += bytes; - } - - m_file.flush(); - m_file.close(); - - std::filesystem::resize_file(m_path, write_pos); - - m_file.open(m_path, std::ios::in | std::ios::out | std::ios::binary); - ASSERT(m_file.good()); - } } void Vault::update_file(const std::string &filename, @@ -188,6 +171,10 @@ void Vault::update_file(const std::string &filename, std::optional Vault::read_file_header() { FileHeader header{}; + if (!m_file.read(to_char_ptr(&header.deleted), 1)) { + return std::nullopt; + } + if (!m_file.read(to_char_ptr(&header.name_ciphertext_size), sizeof(u64))) { return std::nullopt; } @@ -201,10 +188,14 @@ std::optional Vault::read_file_header() { return std::nullopt; } - auto name = Crypto::decrypt(m_key, name_ciphertext); - header.name = std::string(name.begin(), name.end()); + if (!header.deleted) { + auto name = Crypto::decrypt(m_key, name_ciphertext); + header.name = std::string(name.begin(), name.end()); + } + if (!m_file.read(to_char_ptr(&header.content_ciphertext_size), sizeof(u64))) { return std::nullopt; } + return header; } diff --git a/src/vault.h b/src/vault.h index d7e26fd..d71cd56 100644 --- a/src/vault.h +++ b/src/vault.h @@ -10,6 +10,7 @@ constexpr u64 AFTER_HEADER_OFFSET = 68; // !!! REMEMBER TO UPDATE entry_total_size IN Vault::delete_file struct FileHeader { + bool deleted; u64 name_ciphertext_size; std::string name; u64 content_ciphertext_size;