mark as deleted and zero content on deletion instead of shifting everything
This commit is contained in:
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#define ASSERT(cond) \
|
#define ASSERT(cond) \
|
||||||
do { \
|
do { \
|
||||||
@@ -25,11 +24,3 @@ static_assert(sizeof(float) * 8 == 32);
|
|||||||
using f32 = float;
|
using f32 = float;
|
||||||
static_assert(sizeof(double) * 8 == 64);
|
static_assert(sizeof(double) * 8 == 64);
|
||||||
using f64 = double;
|
using f64 = double;
|
||||||
|
|
||||||
template <typename T> constexpr char *to_char_ptr(T *ptr) noexcept {
|
|
||||||
return reinterpret_cast<char *>(ptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T> constexpr const char *to_char_ptr(const T *ptr) noexcept {
|
|
||||||
return reinterpret_cast<const char *>(ptr);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -230,13 +230,15 @@ void MainWindow::file_context_menu(const QPoint &pos) {
|
|||||||
|
|
||||||
QMenu menu(this);
|
QMenu menu(this);
|
||||||
|
|
||||||
QAction *preview_action = menu.addAction(
|
QAction *preview_action =
|
||||||
style()->standardIcon(QStyle::SP_FileDialogContentsView), "Preview");
|
menu.addAction(style()->standardIcon(QStyle::SP_FileDialogContentsView),
|
||||||
|
"Preview as text");
|
||||||
connect(preview_action, &QAction::triggered, this,
|
connect(preview_action, &QAction::triggered, this,
|
||||||
[this, item]() { preview_file(item->text(0).toStdString()); });
|
[this, item]() { preview_file(item->text(0).toStdString()); });
|
||||||
|
|
||||||
QAction *edit_action = menu.addAction(
|
QAction *edit_action =
|
||||||
style()->standardIcon(QStyle::SP_FileDialogDetailedView), "Edit");
|
menu.addAction(style()->standardIcon(QStyle::SP_FileDialogDetailedView),
|
||||||
|
"Edit in external app");
|
||||||
connect(edit_action, &QAction::triggered, this,
|
connect(edit_action, &QAction::triggered, this,
|
||||||
[this, item]() { edit_file(item->text(0).toStdString()); });
|
[this, item]() { edit_file(item->text(0).toStdString()); });
|
||||||
|
|
||||||
|
|||||||
75
src/vault.cc
75
src/vault.cc
@@ -2,7 +2,18 @@
|
|||||||
#include "common.h"
|
#include "common.h"
|
||||||
#include "crypto.h"
|
#include "crypto.h"
|
||||||
#include <botan/auto_rng.h>
|
#include <botan/auto_rng.h>
|
||||||
#include <filesystem>
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
template <typename T> constexpr char *to_char_ptr(T *ptr) noexcept {
|
||||||
|
return reinterpret_cast<char *>(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> constexpr const char *to_char_ptr(const T *ptr) noexcept {
|
||||||
|
return reinterpret_cast<const char *>(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
Vault::Vault(std::string path, const std::string &password)
|
Vault::Vault(std::string path, const std::string &password)
|
||||||
: m_path(std::move(path)) {
|
: m_path(std::move(path)) {
|
||||||
@@ -57,7 +68,9 @@ std::vector<FileHeader> Vault::read_file_headers() {
|
|||||||
while (true) {
|
while (true) {
|
||||||
auto header = read_file_header();
|
auto header = read_file_header();
|
||||||
if (header) {
|
if (header) {
|
||||||
|
if (!header->deleted) {
|
||||||
headers.push_back(header.value());
|
headers.push_back(header.value());
|
||||||
|
}
|
||||||
m_file.seekg(static_cast<i64>(header->content_ciphertext_size),
|
m_file.seekg(static_cast<i64>(header->content_ciphertext_size),
|
||||||
std::ios::cur);
|
std::ios::cur);
|
||||||
} else {
|
} else {
|
||||||
@@ -78,7 +91,7 @@ std::optional<std::string> Vault::read_file(const std::string &filename) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header->name == filename) {
|
if (!header->deleted && header->name == filename) {
|
||||||
Botan::secure_vector<u8> ciphertext;
|
Botan::secure_vector<u8> ciphertext;
|
||||||
ciphertext.resize(header->content_ciphertext_size);
|
ciphertext.resize(header->content_ciphertext_size);
|
||||||
if (!m_file.read(to_char_ptr(ciphertext.data()),
|
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);
|
auto ciphertext = Crypto::encrypt(m_key, content_sv);
|
||||||
u64 ciphertext_size = ciphertext.size();
|
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_size), sizeof(u64)));
|
||||||
ASSERT(m_file.write(to_char_ptr(filename_ciphertext.data()),
|
ASSERT(m_file.write(to_char_ptr(filename_ciphertext.data()),
|
||||||
static_cast<i64>(filename_ciphertext_size)));
|
static_cast<i64>(filename_ciphertext_size)));
|
||||||
@@ -123,9 +137,6 @@ void Vault::delete_file(const std::string &filename) {
|
|||||||
m_file.clear();
|
m_file.clear();
|
||||||
m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg);
|
m_file.seekg(AFTER_HEADER_OFFSET, std::ios::beg);
|
||||||
|
|
||||||
i64 entry_start = -1;
|
|
||||||
u64 entry_total_size = 0;
|
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
i64 current_pos = m_file.tellg();
|
i64 current_pos = m_file.tellg();
|
||||||
|
|
||||||
@@ -134,49 +145,21 @@ void Vault::delete_file(const std::string &filename) {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (header->name == filename) {
|
if (!header->deleted && header->name == filename) {
|
||||||
entry_start = current_pos;
|
// zero the ciphertext and mark as deleted
|
||||||
entry_total_size = sizeof(u64) + header->name_ciphertext_size +
|
m_file.seekp(m_file.tellg());
|
||||||
sizeof(u64) + header->content_ciphertext_size;
|
for (size_t i = 0; i < header->content_ciphertext_size; i++) {
|
||||||
m_file.seekg(static_cast<i64>(header->content_ciphertext_size),
|
m_file.write(to_char_ptr("\0"), 1);
|
||||||
std::ios::cur);
|
}
|
||||||
|
|
||||||
|
m_file.seekp(current_pos, std::ios::beg);
|
||||||
|
m_file.write(to_char_ptr("\1"), 1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_file.seekg(static_cast<i64>(header->content_ciphertext_size),
|
m_file.seekg(static_cast<i64>(header->content_ciphertext_size),
|
||||||
std::ios::cur);
|
std::ios::cur);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry_start != -1) {
|
|
||||||
i64 entry_end = entry_start + static_cast<i64>(entry_total_size);
|
|
||||||
|
|
||||||
i64 read_pos = entry_end;
|
|
||||||
i64 write_pos = entry_start;
|
|
||||||
std::array<u8, 65536> 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,
|
void Vault::update_file(const std::string &filename,
|
||||||
@@ -188,6 +171,10 @@ void Vault::update_file(const std::string &filename,
|
|||||||
std::optional<FileHeader> Vault::read_file_header() {
|
std::optional<FileHeader> Vault::read_file_header() {
|
||||||
FileHeader 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))) {
|
if (!m_file.read(to_char_ptr(&header.name_ciphertext_size), sizeof(u64))) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
@@ -201,10 +188,14 @@ std::optional<FileHeader> Vault::read_file_header() {
|
|||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!header.deleted) {
|
||||||
auto name = Crypto::decrypt(m_key, name_ciphertext);
|
auto name = Crypto::decrypt(m_key, name_ciphertext);
|
||||||
header.name = std::string(name.begin(), name.end());
|
header.name = std::string(name.begin(), name.end());
|
||||||
|
}
|
||||||
|
|
||||||
if (!m_file.read(to_char_ptr(&header.content_ciphertext_size), sizeof(u64))) {
|
if (!m_file.read(to_char_ptr(&header.content_ciphertext_size), sizeof(u64))) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ constexpr u64 AFTER_HEADER_OFFSET = 68;
|
|||||||
|
|
||||||
// !!! REMEMBER TO UPDATE entry_total_size IN Vault::delete_file
|
// !!! REMEMBER TO UPDATE entry_total_size IN Vault::delete_file
|
||||||
struct FileHeader {
|
struct FileHeader {
|
||||||
|
bool deleted;
|
||||||
u64 name_ciphertext_size;
|
u64 name_ciphertext_size;
|
||||||
std::string name;
|
std::string name;
|
||||||
u64 content_ciphertext_size;
|
u64 content_ciphertext_size;
|
||||||
|
|||||||
Reference in New Issue
Block a user