passwordfile/io/passwordfile.cpp

681 lines
21 KiB
C++
Raw Normal View History

2016-08-29 15:42:07 +02:00
#include "./passwordfile.h"
#include "./cryptoexception.h"
#include "./entry.h"
2017-05-01 03:25:30 +02:00
#include "./parsingexception.h"
2016-08-29 15:42:07 +02:00
#include <c++utilities/conversion/stringbuilder.h>
2018-12-08 19:09:17 +01:00
#include <c++utilities/conversion/stringconversion.h>
2016-08-29 15:42:07 +02:00
#include <c++utilities/io/catchiofailure.h>
#include <openssl/conf.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
#include <zlib.h>
#include <cstring>
#include <functional>
2018-09-08 19:38:15 +02:00
#include <limits>
2017-05-01 03:25:30 +02:00
#include <memory>
#include <sstream>
#include <streambuf>
2016-08-29 15:42:07 +02:00
using namespace std;
using namespace ConversionUtilities;
2016-08-29 15:42:07 +02:00
using namespace IoUtilities;
namespace Io {
const unsigned int aes256cbcIvSize = 16U;
/*!
* \class PasswordFile
* \brief The PasswordFile class holds account information in the form of Entry and Field instances
* and provides methods to read and write these information to encrypted files using OpenSSL.
*/
/*!
* \brief Constructs a new password file.
*/
2017-05-01 03:25:30 +02:00
PasswordFile::PasswordFile()
: m_freader(BinaryReader(&m_file))
, m_fwriter(BinaryWriter(&m_file))
2016-08-29 15:42:07 +02:00
{
m_file.exceptions(ios_base::failbit | ios_base::badbit);
clearPassword();
}
/*!
* \brief Constructs a new password file with the specified \a path and \a password.
*/
2017-05-01 03:25:30 +02:00
PasswordFile::PasswordFile(const string &path, const string &password)
: m_freader(BinaryReader(&m_file))
, m_fwriter(BinaryWriter(&m_file))
2016-08-29 15:42:07 +02:00
{
m_file.exceptions(ios_base::failbit | ios_base::badbit);
setPath(path);
setPassword(password);
}
/*!
* \brief Constructs a copy of another password file.
*/
2017-05-01 03:25:30 +02:00
PasswordFile::PasswordFile(const PasswordFile &other)
: m_path(other.m_path)
, m_rootEntry(other.m_rootEntry ? make_unique<NodeEntry>(*other.m_rootEntry) : nullptr)
, m_extendedHeader(other.m_extendedHeader)
, m_encryptedExtendedHeader(other.m_encryptedExtendedHeader)
2017-05-01 03:25:30 +02:00
, m_freader(BinaryReader(&m_file))
, m_fwriter(BinaryWriter(&m_file))
2016-08-29 15:42:07 +02:00
{
m_file.exceptions(ios_base::failbit | ios_base::badbit);
memcpy(m_password, other.m_password, 32);
}
/*!
* \brief Moves the password file.
*/
PasswordFile::PasswordFile(PasswordFile &&other)
: m_path(move(other.m_path))
, m_rootEntry(move(other.m_rootEntry))
, m_extendedHeader(move(other.m_extendedHeader))
, m_encryptedExtendedHeader(move(other.m_encryptedExtendedHeader))
, m_file(move(other.m_file))
, m_freader(BinaryReader(&m_file))
, m_fwriter(BinaryWriter(&m_file))
{
2016-08-29 15:42:07 +02:00
memcpy(m_password, other.m_password, 32);
}
/*!
* \brief Closes the file if still opened and destroys the instance.
*/
PasswordFile::~PasswordFile()
{
}
/*!
* \brief Opens the file. Does not load the contents (see load()).
* \throws Throws ios_base::failure when an IO error occurs.
*/
void PasswordFile::open(bool readOnly)
{
close();
2017-05-01 03:25:30 +02:00
if (m_path.empty()) {
2016-08-29 15:42:07 +02:00
throwIoFailure("Unable to open file because path is emtpy.");
}
m_file.open(m_path, readOnly ? ios_base::in | ios_base::binary : ios_base::in | ios_base::out | ios_base::binary);
opened();
}
/*!
* \brief Handles the file being opened.
*
* Call this method after opening a file directly via the underlying fileStream().
*/
void PasswordFile::opened()
{
2016-08-29 15:42:07 +02:00
m_file.seekg(0, ios_base::end);
2017-05-01 03:25:30 +02:00
if (m_file.tellg() == 0) {
2016-08-29 15:42:07 +02:00
throwIoFailure("File is empty.");
} else {
m_file.seekg(0);
}
}
/*!
* \brief Generates a new root entry for the file.
*/
void PasswordFile::generateRootEntry()
{
2017-05-01 03:25:30 +02:00
if (!m_rootEntry) {
2016-08-29 15:42:07 +02:00
m_rootEntry.reset(new NodeEntry("accounts"));
}
}
/*!
* \brief Creates the file. Does not generate a new root element (see generateRootElement()).
* \throws Throws ios_base::failure when an IO error occurs.
*/
void PasswordFile::create()
{
close();
2017-05-01 03:25:30 +02:00
if (m_path.empty()) {
2016-08-29 15:42:07 +02:00
throwIoFailure("Unable to create file because path is empty.");
}
m_file.open(m_path, fstream::out | fstream::trunc | fstream::binary);
}
/*!
* \brief Reads the contents of the file. Opens the file if not already opened. Replaces
* the current root entry with the new one constructed from the file contents.
* \throws Throws ios_base::failure when an IO error occurs.
* \throws Throws Io::ParsingException when a parsing error occurs.
* \throws Throws Io::CryptoException when a decryption error occurs.
* \throws Throws ConversionUtilities::ConversionException when a conversion error occurs.
*/
void PasswordFile::load()
{
2017-05-01 03:25:30 +02:00
if (!m_file.is_open()) {
2016-08-29 15:42:07 +02:00
open();
}
m_file.seekg(0);
2018-09-08 19:38:15 +02:00
2016-08-29 15:42:07 +02:00
// check magic number
2017-05-01 03:25:30 +02:00
if (m_freader.readUInt32LE() != 0x7770616DU) {
2016-08-29 15:42:07 +02:00
throw ParsingException("Signature not present.");
}
2018-09-08 19:38:15 +02:00
2016-08-29 15:42:07 +02:00
// check version and flags (used in version 0x3 only)
2018-09-08 19:38:15 +02:00
const auto version = m_freader.readUInt32LE();
2017-05-01 03:25:30 +02:00
if (version != 0x0U && version != 0x1U && version != 0x2U && version != 0x3U && version != 0x4U && version != 0x5U) {
2016-08-29 15:42:07 +02:00
throw ParsingException("Version is unknown.");
}
2018-09-08 19:38:15 +02:00
bool decrypterUsed, ivUsed, compressionUsed;
2017-05-01 03:25:30 +02:00
if (version == 0x3U) {
2018-09-08 19:38:15 +02:00
const auto flags = m_freader.readByte();
2016-08-29 15:42:07 +02:00
decrypterUsed = flags & 0x80;
ivUsed = flags & 0x40;
compressionUsed = flags & 0x20;
} else {
decrypterUsed = version >= 0x1U;
ivUsed = version == 0x2U;
compressionUsed = false;
}
2018-09-08 19:38:15 +02:00
2016-08-29 15:42:07 +02:00
// skip extended header
2018-09-08 19:38:15 +02:00
// (the extended header might be used in further versions to
// add additional information without breaking compatibility)
2017-05-01 03:25:30 +02:00
if (version >= 0x4U) {
2016-08-29 15:42:07 +02:00
uint16 extendedHeaderSize = m_freader.readUInt16BE();
m_extendedHeader = m_freader.readString(extendedHeaderSize);
}
2018-09-08 19:38:15 +02:00
2016-08-29 15:42:07 +02:00
// get length
2018-09-08 19:38:15 +02:00
const auto headerSize = static_cast<size_t>(m_file.tellg());
2016-08-29 15:42:07 +02:00
m_file.seekg(0, ios_base::end);
2018-09-08 19:38:15 +02:00
auto remainingSize = static_cast<size_t>(m_file.tellg()) - headerSize;
2018-09-07 01:14:21 +02:00
m_file.seekg(static_cast<streamoff>(headerSize), ios_base::beg);
2018-09-08 19:38:15 +02:00
2016-08-29 15:42:07 +02:00
// read file
2017-05-01 03:25:30 +02:00
unsigned char iv[aes256cbcIvSize] = { 0 };
if (decrypterUsed && ivUsed) {
2018-09-08 19:38:15 +02:00
if (remainingSize < aes256cbcIvSize) {
throw ParsingException("Initiation vector is truncated.");
2016-08-29 15:42:07 +02:00
}
m_file.read(reinterpret_cast<char *>(iv), aes256cbcIvSize);
2018-09-08 19:38:15 +02:00
remainingSize -= aes256cbcIvSize;
2016-08-29 15:42:07 +02:00
}
2018-09-08 19:38:15 +02:00
if (!remainingSize) {
2016-08-29 15:42:07 +02:00
throw ParsingException("No contents found.");
}
2018-09-08 19:38:15 +02:00
2016-08-29 15:42:07 +02:00
// decrypt contents
2018-09-08 19:38:15 +02:00
vector<char> rawData;
m_freader.read(rawData, static_cast<streamoff>(remainingSize));
vector<char> decryptedData;
2017-05-01 03:25:30 +02:00
if (decrypterUsed) {
2018-09-08 19:38:15 +02:00
if (remainingSize > numeric_limits<int>::max()) {
2018-09-07 01:14:21 +02:00
throw CryptoException("Size exceeds limit.");
}
2018-09-08 19:38:15 +02:00
// initiate ctx, decrypt data
2016-08-29 15:42:07 +02:00
EVP_CIPHER_CTX *ctx = nullptr;
2018-09-08 19:38:15 +02:00
decryptedData.resize(remainingSize + 32);
2016-08-29 15:42:07 +02:00
int outlen1, outlen2;
if ((ctx = EVP_CIPHER_CTX_new()) == nullptr
2017-05-01 03:25:30 +02:00
|| EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<unsigned const char *>(m_password), iv) != 1
2018-09-08 19:38:15 +02:00
|| EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()), &outlen1,
reinterpret_cast<unsigned char *>(rawData.data()), static_cast<int>(remainingSize))
2017-05-01 03:25:30 +02:00
!= 1
2018-09-08 19:38:15 +02:00
|| EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(decryptedData.data()) + outlen1, &outlen2) != 1) {
// handle decryption error
2017-05-01 03:25:30 +02:00
if (ctx) {
2016-08-29 15:42:07 +02:00
EVP_CIPHER_CTX_free(ctx);
}
string msg;
2018-09-07 01:14:21 +02:00
auto errorCode = ERR_get_error();
while (errorCode) {
2017-05-01 03:25:30 +02:00
if (!msg.empty()) {
2016-08-29 15:42:07 +02:00
msg += "\n";
}
2018-09-07 01:14:21 +02:00
msg += ERR_error_string(errorCode, nullptr);
2016-08-29 15:42:07 +02:00
errorCode = ERR_get_error();
}
throw CryptoException(msg);
}
2018-09-08 19:38:15 +02:00
if (ctx) {
EVP_CIPHER_CTX_free(ctx);
}
const auto decryptedSize = outlen1 + outlen2;
if (decryptedSize < 0) {
throw CryptoException("Decrypted size is negative.");
}
remainingSize = static_cast<size_t>(decryptedSize);
if (!remainingSize) {
throw ParsingException("Decrypted buffer is empty.");
}
2018-09-08 19:38:15 +02:00
} else {
// use raw data directly if not encrypted
decryptedData.swap(rawData);
2016-08-29 15:42:07 +02:00
}
2018-09-08 19:38:15 +02:00
2016-08-29 15:42:07 +02:00
// decompress
2017-05-01 03:25:30 +02:00
if (compressionUsed) {
2018-09-08 19:38:15 +02:00
if (remainingSize < 8) {
2016-08-29 15:42:07 +02:00
throw ParsingException("File is truncated (decompressed size expected).");
}
2018-09-08 19:38:15 +02:00
uLongf decompressedSize = ConversionUtilities::LE::toUInt64(decryptedData.data());
rawData.resize(decompressedSize);
switch (uncompress(
reinterpret_cast<Bytef *>(rawData.data()), &decompressedSize, reinterpret_cast<Bytef *>(decryptedData.data() + 8), remainingSize - 8)) {
2016-08-29 15:42:07 +02:00
case Z_MEM_ERROR:
throw ParsingException("Decompressing failed. The source buffer was too small.");
case Z_BUF_ERROR:
throw ParsingException("Decompressing failed. The destination buffer was too small.");
case Z_DATA_ERROR:
throw ParsingException("Decompressing failed. The input data was corrupted or incomplete.");
case Z_OK:
2018-09-08 19:38:15 +02:00
decryptedData.swap(rawData);
remainingSize = decompressedSize;
2016-08-29 15:42:07 +02:00
}
}
if (!remainingSize) {
throw ParsingException("Decompressed buffer is empty.");
}
2018-09-08 19:38:15 +02:00
2016-08-29 15:42:07 +02:00
// parse contents
2018-09-08 19:38:15 +02:00
stringstream decryptedStream(stringstream::in | stringstream::out | stringstream::binary);
decryptedStream.exceptions(ios_base::failbit | ios_base::badbit);
try {
#ifdef _LIBCPP_VERSION
decryptedStream.write(decryptedData.data(), static_cast<streamsize>(remainingSize));
#else
decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(), static_cast<streamsize>(remainingSize));
#endif
if (version >= 0x5u) {
const auto extendedHeaderSize = m_freader.readUInt16BE();
m_encryptedExtendedHeader = m_freader.readString(extendedHeaderSize);
}
m_rootEntry.reset(new NodeEntry(decryptedStream));
} catch (...) {
const char *const what = catchIoFailure();
if (decryptedStream.eof()) {
throw ParsingException("The file seems to be truncated.");
}
throw ParsingException(argsToString("An IO error occurred when reading internal buffer: ", what));
2016-08-29 15:42:07 +02:00
}
}
/*!
* \brief Writes the current root entry to the file under path() replacing its previous contents.
2016-08-29 15:42:07 +02:00
* \param useEncryption Specifies whether encryption should be used.
* \param useCompression Specifies whether compression should be used.
* \throws Throws ios_base::failure when an IO error occurs.
2018-06-09 18:34:46 +02:00
* \throws Throws runtime_error when no root entry is present or a compression error occurs.
* \throws Throws Io::CryptoException when an encryption error occurs.
2016-08-29 15:42:07 +02:00
*/
void PasswordFile::save(bool useEncryption, bool useCompression)
{
2017-05-01 03:25:30 +02:00
if (!m_rootEntry) {
2016-08-29 15:42:07 +02:00
throw runtime_error("Root entry has not been created.");
}
// use already opened and writable file; otherwise re-open the file
if (m_file.good() && m_file.is_open() && (m_file.flags() & ios_base::out)) {
m_file.seekp(0);
} else {
2016-08-29 15:42:07 +02:00
m_file.close();
m_file.clear();
m_file.open(m_path, ios_base::in | ios_base::out | ios_base::trunc | ios_base::binary);
}
write(useEncryption, useCompression);
m_file.flush();
}
/*!
* \brief Writes the current root entry to the file which is assumed to be opened and writeable.
* \param useEncryption Specifies whether encryption should be used.
* \param useCompression Specifies whether compression should be used.
* \throws Throws ios_base::failure when an IO error occurs.
* \throws Throws runtime_error when no root entry is present or a compression error occurs.
* \throws Throws Io::CryptoException when an encryption error occurs.
*/
void PasswordFile::write(bool useEncryption, bool useCompression)
{
if (!m_rootEntry) {
throw runtime_error("Root entry has not been created.");
2016-08-29 15:42:07 +02:00
}
// write magic number
m_fwriter.writeUInt32LE(0x7770616DU);
2016-08-29 15:42:07 +02:00
// write version, extended header requires version 4, encrypted extended header required version 5
m_fwriter.writeUInt32LE(m_extendedHeader.empty() && m_encryptedExtendedHeader.empty() ? 0x3U : (m_encryptedExtendedHeader.empty() ? 0x4U : 0x5U));
byte flags = 0x00;
2017-05-01 03:25:30 +02:00
if (useEncryption) {
2016-08-29 15:42:07 +02:00
flags |= 0x80 | 0x40;
}
2017-05-01 03:25:30 +02:00
if (useCompression) {
2016-08-29 15:42:07 +02:00
flags |= 0x20;
}
m_fwriter.writeByte(flags);
2018-09-07 01:14:21 +02:00
2016-08-29 15:42:07 +02:00
// write extened header
2017-05-01 03:25:30 +02:00
if (!m_extendedHeader.empty()) {
2018-09-07 01:14:21 +02:00
m_fwriter.writeUInt16BE(static_cast<uint16>(m_extendedHeader.size()));
2016-08-29 15:42:07 +02:00
m_fwriter.writeString(m_extendedHeader);
}
2018-09-07 01:14:21 +02:00
2016-08-29 15:42:07 +02:00
// serialize root entry and descendants
stringstream buffstr(stringstream::in | stringstream::out | stringstream::binary);
buffstr.exceptions(ios_base::failbit | ios_base::badbit);
2018-09-07 01:14:21 +02:00
2016-08-29 15:42:07 +02:00
// write encrypted extened header
2017-05-01 03:25:30 +02:00
if (!m_encryptedExtendedHeader.empty()) {
2018-09-07 01:14:21 +02:00
m_fwriter.writeUInt16BE(static_cast<uint16>(m_encryptedExtendedHeader.size()));
2016-08-29 15:42:07 +02:00
m_fwriter.writeString(m_encryptedExtendedHeader);
}
m_rootEntry->make(buffstr);
buffstr.seekp(0, ios_base::end);
2018-09-07 01:14:21 +02:00
auto size = static_cast<size_t>(buffstr.tellp());
2016-08-29 15:42:07 +02:00
// write the data to a buffer
buffstr.seekg(0);
2018-09-08 19:38:15 +02:00
vector<char> decryptedData(size, 0);
buffstr.read(decryptedData.data(), static_cast<streamoff>(size));
vector<char> encryptedData;
2018-09-07 01:14:21 +02:00
2016-08-29 15:42:07 +02:00
// compress data
2017-05-01 03:25:30 +02:00
if (useCompression) {
2016-08-29 15:42:07 +02:00
uLongf compressedSize = compressBound(size);
2018-09-08 19:38:15 +02:00
encryptedData.resize(8 + compressedSize);
ConversionUtilities::LE::getBytes(static_cast<uint64>(size), encryptedData.data());
switch (
compress(reinterpret_cast<Bytef *>(encryptedData.data() + 8), &compressedSize, reinterpret_cast<Bytef *>(decryptedData.data()), size)) {
2016-08-29 15:42:07 +02:00
case Z_MEM_ERROR:
2018-06-09 18:34:46 +02:00
throw runtime_error("Compressing failed. The source buffer was too small.");
2016-08-29 15:42:07 +02:00
case Z_BUF_ERROR:
2018-06-09 18:34:46 +02:00
throw runtime_error("Compressing failed. The destination buffer was too small.");
2016-08-29 15:42:07 +02:00
case Z_OK:
2018-09-08 19:38:15 +02:00
encryptedData.swap(decryptedData); // decompression successful
2016-08-29 15:42:07 +02:00
size = 8 + compressedSize;
}
}
2018-09-07 01:14:21 +02:00
if (size > numeric_limits<int>::max()) {
throw CryptoException("size exceeds limit");
}
// write data without encryption
if (!useEncryption) {
2016-08-29 15:42:07 +02:00
// write data to file
2018-09-08 19:38:15 +02:00
m_file.write(decryptedData.data(), static_cast<streamsize>(size));
2018-09-07 01:14:21 +02:00
return;
}
// initiate ctx, encrypt data
EVP_CIPHER_CTX *ctx = nullptr;
unsigned char iv[aes256cbcIvSize];
int outlen1, outlen2;
2018-09-08 19:38:15 +02:00
encryptedData.resize(size + 32);
2018-09-07 01:14:21 +02:00
if (RAND_bytes(iv, aes256cbcIvSize) != 1 || (ctx = EVP_CIPHER_CTX_new()) == nullptr
|| EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<unsigned const char *>(m_password), iv) != 1
2018-09-08 19:38:15 +02:00
|| EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()), &outlen1,
reinterpret_cast<unsigned char *>(decryptedData.data()), static_cast<int>(size))
2018-09-07 01:14:21 +02:00
!= 1
2018-09-08 19:38:15 +02:00
|| EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(encryptedData.data()) + outlen1, &outlen2) != 1) {
2018-09-07 01:14:21 +02:00
// handle encryption error
if (ctx) {
EVP_CIPHER_CTX_free(ctx);
}
string msg;
auto errorCode = ERR_get_error();
while (errorCode) {
if (!msg.empty()) {
msg += "\n";
}
msg += ERR_error_string(errorCode, nullptr);
errorCode = ERR_get_error();
}
throw CryptoException(msg);
2016-08-29 15:42:07 +02:00
}
2018-09-07 01:14:21 +02:00
if (ctx) {
EVP_CIPHER_CTX_free(ctx);
}
// write encrypted data to file
m_file.write(reinterpret_cast<char *>(iv), aes256cbcIvSize);
2018-09-08 19:38:15 +02:00
m_file.write(encryptedData.data(), static_cast<streamsize>(outlen1 + outlen2));
2016-08-29 15:42:07 +02:00
}
/*!
* \brief Removes the root element if one is present.
*/
void PasswordFile::clearEntries()
{
m_rootEntry.reset();
}
/*!
* \brief Closes the file if opened. Removes path, password and entries and additional information.
*/
void PasswordFile::clear()
{
close();
clearPath();
clearPassword();
clearEntries();
m_extendedHeader.clear();
m_encryptedExtendedHeader.clear();
}
/*!
* \brief Writes the current root entry to a plain text file. No encryption is used.
* \param targetPath Specifies the path of the text file.
* \throws Throws ios_base::failure when an IO error occurs.
* \throws Throws runtime_error when no root entry is present.
*/
void PasswordFile::exportToTextfile(const string &targetPath) const
{
2017-05-01 03:25:30 +02:00
if (!m_rootEntry) {
2016-08-29 15:42:07 +02:00
throw runtime_error("Root entry has not been created.");
}
fstream output(targetPath.c_str(), ios_base::out);
2018-09-07 01:14:21 +02:00
const auto printIndention = [&output](int level) {
2017-05-01 03:25:30 +02:00
for (int i = 0; i < level; ++i) {
2016-08-29 15:42:07 +02:00
output << " ";
}
};
2017-05-01 03:25:30 +02:00
function<void(const Entry *entry, int level)> printNode;
2018-09-07 01:14:21 +02:00
printNode = [&output, &printNode, &printIndention](const Entry *entry, int level) {
printIndention(level);
2016-08-29 15:42:07 +02:00
output << " - " << entry->label() << endl;
2017-05-01 03:25:30 +02:00
switch (entry->type()) {
2016-08-29 15:42:07 +02:00
case EntryType::Node:
2017-05-01 03:25:30 +02:00
for (const Entry *child : static_cast<const NodeEntry *>(entry)->children()) {
2016-08-29 15:42:07 +02:00
printNode(child, level + 1);
}
break;
case EntryType::Account:
2017-05-01 03:25:30 +02:00
for (const Field &field : static_cast<const AccountEntry *>(entry)->fields()) {
2018-09-07 01:14:21 +02:00
printIndention(level);
2016-08-29 15:42:07 +02:00
output << " " << field.name();
2018-09-07 01:14:21 +02:00
for (auto i = field.name().length(); i < 15; ++i) {
2016-08-29 15:42:07 +02:00
output << ' ';
}
output << field.value() << endl;
}
}
};
printNode(m_rootEntry.get(), 0);
output.close();
}
/*!
* \brief Creates a backup of the file. Replaces an existent backup file.
* \throws Throws ios_base::failure when an IO error occurs.
*/
void PasswordFile::doBackup()
{
2017-05-01 03:25:30 +02:00
if (!isOpen()) {
2016-08-29 15:42:07 +02:00
open();
}
2018-09-07 01:14:21 +02:00
// skip if the current file is empty anyways
if (!size()) {
return;
2016-08-29 15:42:07 +02:00
}
2018-09-07 01:14:21 +02:00
m_file.seekg(0);
fstream backupFile(m_path + ".backup", ios::out | ios::trunc | ios::binary);
backupFile.exceptions(ios_base::failbit | ios_base::badbit);
backupFile << m_file.rdbuf();
backupFile.close();
2016-08-29 15:42:07 +02:00
}
/*!
* \brief Returns an indication whether a root entry is present.
* \sa generateRootEntry()
* \sa rootEntry()
*/
bool PasswordFile::hasRootEntry() const
{
return m_rootEntry != nullptr;
}
/*!
* \brief Returns the root entry if present or nullptr otherwise.
*/
const NodeEntry *PasswordFile::rootEntry() const
{
return m_rootEntry.get();
}
/*!
* \brief Returns the root entry if present or nullptr otherwise.
*/
NodeEntry *PasswordFile::rootEntry()
{
return m_rootEntry.get();
}
/*!
* \brief Closes the file if currently opened.
*/
void PasswordFile::close()
{
2017-05-01 03:25:30 +02:00
if (m_file.is_open()) {
2016-08-29 15:42:07 +02:00
m_file.close();
}
m_file.clear();
}
/*!
* \brief Returns the current file path.
*/
const string &PasswordFile::path() const
{
return m_path;
}
/*!
2018-06-12 21:47:38 +02:00
* \brief Sets the current file path. Closes the file if currently opened.
2016-08-29 15:42:07 +02:00
*/
void PasswordFile::setPath(const string &value)
{
close();
m_path = value;
2018-06-12 21:47:38 +02:00
// support "file://" protocol
if (ConversionUtilities::startsWith(m_path, "file:")) {
m_path = m_path.substr(5);
}
2016-08-29 15:42:07 +02:00
}
/*!
* \brief Clears the current path. Causes the file to be closed if currently opened.
*/
void PasswordFile::clearPath()
{
close();
m_path.clear();
}
/*!
* \brief Returns the current password. It will be used when loading or saving using encryption.
*/
const char *PasswordFile::password() const
{
return m_password;
}
/*!
* \brief Sets the current password. It will be used when loading or saving using encryption.
*/
void PasswordFile::setPassword(const string &value)
{
clearPassword();
value.copy(m_password, 32, 0);
}
/*!
* \brief Clears the current password.
*/
void PasswordFile::clearPassword()
{
memset(m_password, 0, 32);
}
/*!
* \brief Returns an indication whether encryption is used if the file is open; returns always false otherwise.
*/
bool PasswordFile::isEncryptionUsed()
{
2017-05-01 03:25:30 +02:00
if (!isOpen()) {
2016-08-29 15:42:07 +02:00
return false;
}
m_file.seekg(0);
2018-09-07 01:14:21 +02:00
2016-08-29 15:42:07 +02:00
//check magic number
2017-05-01 03:25:30 +02:00
if (m_freader.readUInt32LE() != 0x7770616DU) {
2016-08-29 15:42:07 +02:00
return false;
}
2018-09-07 01:14:21 +02:00
2016-08-29 15:42:07 +02:00
//check version
2018-09-07 01:14:21 +02:00
const auto version = m_freader.readUInt32LE();
2017-05-01 03:25:30 +02:00
if (version == 0x1U || version == 0x2U) {
2016-08-29 15:42:07 +02:00
return true;
2017-05-01 03:25:30 +02:00
} else if (version == 0x3U) {
2016-08-29 15:42:07 +02:00
return m_freader.readByte() & 0x80;
} else {
return false;
}
}
/*!
* \brief Returns an indication whether the file is open.
*/
bool PasswordFile::isOpen() const
{
return m_file.is_open();
}
/*!
2018-09-07 01:14:21 +02:00
* \brief Returns the size of the file if the file is open; otherwise returns zero.
2016-08-29 15:42:07 +02:00
*/
size_t PasswordFile::size()
{
2017-05-01 03:25:30 +02:00
if (!isOpen()) {
2016-08-29 15:42:07 +02:00
return 0;
}
m_file.seekg(0, ios::end);
2018-09-07 01:14:21 +02:00
return static_cast<size_t>(m_file.tellg());
2016-08-29 15:42:07 +02:00
}
2018-03-20 20:11:31 +01:00
} // namespace Io