Add statistics
This commit is contained in:
parent
b9af93adbd
commit
2c856e3976
20
io/entry.cpp
20
io/entry.cpp
|
@ -420,6 +420,17 @@ NodeEntry *NodeEntry::clone() const
|
||||||
return new NodeEntry(*this);
|
return new NodeEntry(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Accumulates the statistics for this node entry and its children.
|
||||||
|
*/
|
||||||
|
void NodeEntry::accumulateStatistics(EntryStatistics &stats) const
|
||||||
|
{
|
||||||
|
++stats.nodeCount;
|
||||||
|
for (const auto *children : children()) {
|
||||||
|
children->accumulateStatistics(stats);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \class AccountEntry
|
* \class AccountEntry
|
||||||
* \brief The exception that is thrown when a parsing error occurs.
|
* \brief The exception that is thrown when a parsing error occurs.
|
||||||
|
@ -501,4 +512,13 @@ AccountEntry *AccountEntry::clone() const
|
||||||
{
|
{
|
||||||
return new AccountEntry(*this);
|
return new AccountEntry(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Accumulates the statistics for this account entry and its fields.
|
||||||
|
*/
|
||||||
|
void AccountEntry::accumulateStatistics(EntryStatistics &stats) const
|
||||||
|
{
|
||||||
|
stats.accountCount += 1;
|
||||||
|
stats.fieldCount += fields().size();
|
||||||
|
}
|
||||||
} // namespace Io
|
} // namespace Io
|
||||||
|
|
21
io/entry.h
21
io/entry.h
|
@ -20,6 +20,12 @@ enum class EntryType : int {
|
||||||
Account /**< denotes an AccountEntry */
|
Account /**< denotes an AccountEntry */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct EntryStatistics {
|
||||||
|
std::size_t nodeCount = 0;
|
||||||
|
std::size_t accountCount = 0;
|
||||||
|
std::size_t fieldCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
class NodeEntry;
|
class NodeEntry;
|
||||||
|
|
||||||
class PASSWORD_FILE_EXPORT Entry {
|
class PASSWORD_FILE_EXPORT Entry {
|
||||||
|
@ -40,6 +46,8 @@ public:
|
||||||
void path(std::list<std::string> &res) const;
|
void path(std::list<std::string> &res) const;
|
||||||
virtual void make(std::ostream &stream) const = 0;
|
virtual void make(std::ostream &stream) const = 0;
|
||||||
virtual Entry *clone() const = 0;
|
virtual Entry *clone() const = 0;
|
||||||
|
EntryStatistics computeStatistics() const;
|
||||||
|
virtual void accumulateStatistics(EntryStatistics &stats) const = 0;
|
||||||
static Entry *parse(std::istream &stream);
|
static Entry *parse(std::istream &stream);
|
||||||
static bool denotesNodeEntry(byte version);
|
static bool denotesNodeEntry(byte version);
|
||||||
static constexpr EntryType denotedEntryType(byte version);
|
static constexpr EntryType denotedEntryType(byte version);
|
||||||
|
@ -93,6 +101,17 @@ inline int Entry::index() const
|
||||||
return m_index;
|
return m_index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Computes statistics for this entry.
|
||||||
|
* \remarks Takes the current instance and children into account but not parents.
|
||||||
|
*/
|
||||||
|
inline EntryStatistics Entry::computeStatistics() const
|
||||||
|
{
|
||||||
|
EntryStatistics stats;
|
||||||
|
accumulateStatistics(stats);
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
class PASSWORD_FILE_EXPORT NodeEntry : public Entry {
|
class PASSWORD_FILE_EXPORT NodeEntry : public Entry {
|
||||||
friend class Entry;
|
friend class Entry;
|
||||||
|
|
||||||
|
@ -112,6 +131,7 @@ public:
|
||||||
void setExpandedByDefault(bool expandedByDefault);
|
void setExpandedByDefault(bool expandedByDefault);
|
||||||
virtual void make(std::ostream &stream) const;
|
virtual void make(std::ostream &stream) const;
|
||||||
virtual NodeEntry *clone() const;
|
virtual NodeEntry *clone() const;
|
||||||
|
void accumulateStatistics(EntryStatistics &stats) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Entry *> m_children;
|
std::vector<Entry *> m_children;
|
||||||
|
@ -161,6 +181,7 @@ public:
|
||||||
std::vector<Field> &fields();
|
std::vector<Field> &fields();
|
||||||
virtual void make(std::ostream &stream) const;
|
virtual void make(std::ostream &stream) const;
|
||||||
virtual AccountEntry *clone() const;
|
virtual AccountEntry *clone() const;
|
||||||
|
void accumulateStatistics(EntryStatistics &stats) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Field> m_fields;
|
std::vector<Field> m_fields;
|
||||||
|
|
|
@ -44,6 +44,9 @@ const unsigned int aes256cbcIvSize = 16U;
|
||||||
PasswordFile::PasswordFile()
|
PasswordFile::PasswordFile()
|
||||||
: m_freader(BinaryReader(&m_file))
|
: m_freader(BinaryReader(&m_file))
|
||||||
, m_fwriter(BinaryWriter(&m_file))
|
, m_fwriter(BinaryWriter(&m_file))
|
||||||
|
, m_version(0)
|
||||||
|
, m_openOptions(PasswordFileOpenFlags::None)
|
||||||
|
, m_saveOptions(PasswordFileSaveFlags::None)
|
||||||
{
|
{
|
||||||
m_file.exceptions(ios_base::failbit | ios_base::badbit);
|
m_file.exceptions(ios_base::failbit | ios_base::badbit);
|
||||||
clearPassword();
|
clearPassword();
|
||||||
|
@ -55,6 +58,9 @@ PasswordFile::PasswordFile()
|
||||||
PasswordFile::PasswordFile(const string &path, const string &password)
|
PasswordFile::PasswordFile(const string &path, const string &password)
|
||||||
: m_freader(BinaryReader(&m_file))
|
: m_freader(BinaryReader(&m_file))
|
||||||
, m_fwriter(BinaryWriter(&m_file))
|
, m_fwriter(BinaryWriter(&m_file))
|
||||||
|
, m_version(0)
|
||||||
|
, m_openOptions(PasswordFileOpenFlags::None)
|
||||||
|
, m_saveOptions(PasswordFileSaveFlags::None)
|
||||||
{
|
{
|
||||||
m_file.exceptions(ios_base::failbit | ios_base::badbit);
|
m_file.exceptions(ios_base::failbit | ios_base::badbit);
|
||||||
setPath(path);
|
setPath(path);
|
||||||
|
@ -72,6 +78,9 @@ PasswordFile::PasswordFile(const PasswordFile &other)
|
||||||
, m_encryptedExtendedHeader(other.m_encryptedExtendedHeader)
|
, m_encryptedExtendedHeader(other.m_encryptedExtendedHeader)
|
||||||
, m_freader(BinaryReader(&m_file))
|
, m_freader(BinaryReader(&m_file))
|
||||||
, m_fwriter(BinaryWriter(&m_file))
|
, m_fwriter(BinaryWriter(&m_file))
|
||||||
|
, m_version(other.m_version)
|
||||||
|
, m_openOptions(other.m_openOptions)
|
||||||
|
, m_saveOptions(other.m_saveOptions)
|
||||||
{
|
{
|
||||||
m_file.exceptions(ios_base::failbit | ios_base::badbit);
|
m_file.exceptions(ios_base::failbit | ios_base::badbit);
|
||||||
}
|
}
|
||||||
|
@ -88,6 +97,9 @@ PasswordFile::PasswordFile(PasswordFile &&other)
|
||||||
, m_file(move(other.m_file))
|
, m_file(move(other.m_file))
|
||||||
, m_freader(BinaryReader(&m_file))
|
, m_freader(BinaryReader(&m_file))
|
||||||
, m_fwriter(BinaryWriter(&m_file))
|
, m_fwriter(BinaryWriter(&m_file))
|
||||||
|
, m_version(other.m_version)
|
||||||
|
, m_openOptions(other.m_openOptions)
|
||||||
|
, m_saveOptions(other.m_saveOptions)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,6 +177,8 @@ void PasswordFile::load()
|
||||||
open();
|
open();
|
||||||
}
|
}
|
||||||
m_file.seekg(0);
|
m_file.seekg(0);
|
||||||
|
m_version = 0;
|
||||||
|
m_saveOptions = PasswordFileSaveFlags::None;
|
||||||
|
|
||||||
// check magic number
|
// check magic number
|
||||||
if (m_freader.readUInt32LE() != 0x7770616DU) {
|
if (m_freader.readUInt32LE() != 0x7770616DU) {
|
||||||
|
@ -172,26 +186,35 @@ void PasswordFile::load()
|
||||||
}
|
}
|
||||||
|
|
||||||
// check version and flags (used in version 0x3 only)
|
// check version and flags (used in version 0x3 only)
|
||||||
const auto version = m_freader.readUInt32LE();
|
m_version = m_freader.readUInt32LE();
|
||||||
if (version > 0x6U) {
|
if (m_version > 0x6U) {
|
||||||
throw ParsingException(argsToString("Version \"", version, "\" is unknown. Only versions 0 to 6 are supported."));
|
throw ParsingException(argsToString("Version \"", m_version, "\" is unknown. Only versions 0 to 6 are supported."));
|
||||||
|
}
|
||||||
|
if (m_version >= 0x6U) {
|
||||||
|
m_saveOptions |= PasswordFileSaveFlags::PasswordHashing;
|
||||||
}
|
}
|
||||||
bool decrypterUsed, ivUsed, compressionUsed;
|
bool decrypterUsed, ivUsed, compressionUsed;
|
||||||
if (version >= 0x3U) {
|
if (m_version >= 0x3U) {
|
||||||
const auto flags = m_freader.readByte();
|
const auto flags = m_freader.readByte();
|
||||||
decrypterUsed = flags & 0x80;
|
if ((decrypterUsed = flags & 0x80)) {
|
||||||
|
m_saveOptions |= PasswordFileSaveFlags::Encryption;
|
||||||
|
}
|
||||||
|
if ((compressionUsed = flags & 0x20)) {
|
||||||
|
m_saveOptions |= PasswordFileSaveFlags::Compression;
|
||||||
|
}
|
||||||
ivUsed = flags & 0x40;
|
ivUsed = flags & 0x40;
|
||||||
compressionUsed = flags & 0x20;
|
|
||||||
} else {
|
} else {
|
||||||
decrypterUsed = version >= 0x1U;
|
if ((decrypterUsed = m_version >= 0x1U)) {
|
||||||
ivUsed = version == 0x2U;
|
m_saveOptions |= PasswordFileSaveFlags::Encryption;
|
||||||
|
}
|
||||||
compressionUsed = false;
|
compressionUsed = false;
|
||||||
|
ivUsed = m_version == 0x2U;
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip extended header
|
// skip extended header
|
||||||
// (the extended header might be used in further versions to
|
// (the extended header might be used in further versions to
|
||||||
// add additional information without breaking compatibility)
|
// add additional information without breaking compatibility)
|
||||||
if (version >= 0x4U) {
|
if (m_version >= 0x4U) {
|
||||||
uint16 extendedHeaderSize = m_freader.readUInt16BE();
|
uint16 extendedHeaderSize = m_freader.readUInt16BE();
|
||||||
m_extendedHeader = m_freader.readString(extendedHeaderSize);
|
m_extendedHeader = m_freader.readString(extendedHeaderSize);
|
||||||
} else {
|
} else {
|
||||||
|
@ -206,7 +229,7 @@ void PasswordFile::load()
|
||||||
|
|
||||||
// read hash count
|
// read hash count
|
||||||
uint32_t hashCount = 0U;
|
uint32_t hashCount = 0U;
|
||||||
if (version >= 0x6U && decrypterUsed) {
|
if ((m_saveOptions & PasswordFileSaveFlags::PasswordHashing) && decrypterUsed) {
|
||||||
if (remainingSize < 4) {
|
if (remainingSize < 4) {
|
||||||
throw ParsingException("Hash count truncated.");
|
throw ParsingException("Hash count truncated.");
|
||||||
}
|
}
|
||||||
|
@ -323,7 +346,7 @@ void PasswordFile::load()
|
||||||
#else
|
#else
|
||||||
decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(), static_cast<streamsize>(remainingSize));
|
decryptedStream.rdbuf()->pubsetbuf(decryptedData.data(), static_cast<streamsize>(remainingSize));
|
||||||
#endif
|
#endif
|
||||||
if (version >= 0x5u) {
|
if (m_version >= 0x5u) {
|
||||||
BinaryReader reader(&decryptedStream);
|
BinaryReader reader(&decryptedStream);
|
||||||
const auto extendedHeaderSize = reader.readUInt16BE();
|
const auto extendedHeaderSize = reader.readUInt16BE();
|
||||||
m_encryptedExtendedHeader = reader.readString(extendedHeaderSize);
|
m_encryptedExtendedHeader = reader.readString(extendedHeaderSize);
|
||||||
|
@ -342,6 +365,7 @@ void PasswordFile::load()
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Returns the minimum file version required to write the current instance with the specified \a options.
|
* \brief Returns the minimum file version required to write the current instance with the specified \a options.
|
||||||
|
* \remarks This version will be used by save() and write() when passing the same \a options.
|
||||||
*/
|
*/
|
||||||
uint32 PasswordFile::mininumVersion(PasswordFileSaveFlags options) const
|
uint32 PasswordFile::mininumVersion(PasswordFileSaveFlags options) const
|
||||||
{
|
{
|
||||||
|
@ -785,4 +809,66 @@ size_t PasswordFile::size()
|
||||||
m_file.seekg(0, ios::end);
|
m_file.seekg(0, ios::end);
|
||||||
return static_cast<size_t>(m_file.tellg());
|
return static_cast<size_t>(m_file.tellg());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns a summary about the file (version, used features, statistics).
|
||||||
|
*/
|
||||||
|
string PasswordFile::summary(PasswordFileSaveFlags saveOptions) const
|
||||||
|
{
|
||||||
|
string result = "<table>";
|
||||||
|
if (!m_path.empty()) {
|
||||||
|
result += argsToString("<tr><td>Path:</td><td>", m_path, "</td></tr>");
|
||||||
|
}
|
||||||
|
result += argsToString("<tr><td>Version:</td><td>", m_version, "</td></tr>");
|
||||||
|
const auto minVersion = mininumVersion(saveOptions);
|
||||||
|
if (m_version != minVersion) {
|
||||||
|
result += argsToString("<tr><td></td><td>(on disk, after saving: ", minVersion, ")</td></tr>");
|
||||||
|
}
|
||||||
|
result += argsToString("<tr><td>Features:</td><td>", flagsToString(m_saveOptions), "</td></tr>");
|
||||||
|
if (m_saveOptions != saveOptions) {
|
||||||
|
result += argsToString("<tr><td></td><td>(on disk, after saving: ", flagsToString(saveOptions), ")</td></tr>");
|
||||||
|
}
|
||||||
|
const auto stats = m_rootEntry ? m_rootEntry->computeStatistics() : EntryStatistics();
|
||||||
|
result += argsToString("<tr><td>Number of categories:</td><td>", stats.nodeCount, "</td></tr><tr><td>Number of accounts:</td><td>",
|
||||||
|
stats.accountCount, "</td></tr><tr><td>Number of fields:</td><td>", stats.fieldCount, "</td></tr></table>");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns a comma-separated string for the specified \a flags.
|
||||||
|
*/
|
||||||
|
string flagsToString(PasswordFileOpenFlags flags)
|
||||||
|
{
|
||||||
|
vector<string> options;
|
||||||
|
if (flags & PasswordFileOpenFlags::ReadOnly) {
|
||||||
|
options.emplace_back("read-only");
|
||||||
|
}
|
||||||
|
if (options.empty()) {
|
||||||
|
options.emplace_back("none");
|
||||||
|
}
|
||||||
|
return joinStrings(options, ", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns a comma-separated string for the specified \a flags.
|
||||||
|
*/
|
||||||
|
string flagsToString(PasswordFileSaveFlags flags)
|
||||||
|
{
|
||||||
|
vector<string> options;
|
||||||
|
options.reserve(3);
|
||||||
|
if (flags & PasswordFileSaveFlags::Encryption) {
|
||||||
|
options.emplace_back("encryption");
|
||||||
|
}
|
||||||
|
if (flags & PasswordFileSaveFlags::Compression) {
|
||||||
|
options.emplace_back("compression");
|
||||||
|
}
|
||||||
|
if (flags & PasswordFileSaveFlags::PasswordHashing) {
|
||||||
|
options.emplace_back("password hashing");
|
||||||
|
}
|
||||||
|
if (options.empty()) {
|
||||||
|
options.emplace_back("none");
|
||||||
|
}
|
||||||
|
return joinStrings(options, ", ");
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Io
|
} // namespace Io
|
||||||
|
|
|
@ -22,6 +22,8 @@ enum class PasswordFileOpenFlags : uint64 {
|
||||||
Default = None,
|
Default = None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::string PASSWORD_FILE_EXPORT flagsToString(PasswordFileOpenFlags flags);
|
||||||
|
|
||||||
constexpr PasswordFileOpenFlags operator|(PasswordFileOpenFlags lhs, PasswordFileOpenFlags rhs)
|
constexpr PasswordFileOpenFlags operator|(PasswordFileOpenFlags lhs, PasswordFileOpenFlags rhs)
|
||||||
{
|
{
|
||||||
return static_cast<PasswordFileOpenFlags>(
|
return static_cast<PasswordFileOpenFlags>(
|
||||||
|
@ -48,6 +50,8 @@ enum class PasswordFileSaveFlags : uint64 {
|
||||||
Default = Encryption | Compression | PasswordHashing,
|
Default = Encryption | Compression | PasswordHashing,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::string PASSWORD_FILE_EXPORT flagsToString(PasswordFileSaveFlags flags);
|
||||||
|
|
||||||
constexpr PasswordFileSaveFlags operator|(PasswordFileSaveFlags lhs, PasswordFileSaveFlags rhs)
|
constexpr PasswordFileSaveFlags operator|(PasswordFileSaveFlags lhs, PasswordFileSaveFlags rhs)
|
||||||
{
|
{
|
||||||
return static_cast<PasswordFileSaveFlags>(
|
return static_cast<PasswordFileSaveFlags>(
|
||||||
|
@ -104,6 +108,10 @@ public:
|
||||||
std::string &encryptedExtendedHeader();
|
std::string &encryptedExtendedHeader();
|
||||||
const std::string &encryptedExtendedHeader() const;
|
const std::string &encryptedExtendedHeader() const;
|
||||||
std::size_t size();
|
std::size_t size();
|
||||||
|
uint32 version() const;
|
||||||
|
PasswordFileOpenFlags openOptions() const;
|
||||||
|
PasswordFileSaveFlags saveOptions() const;
|
||||||
|
std::string summary(PasswordFileSaveFlags saveOptions) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string m_path;
|
std::string m_path;
|
||||||
|
@ -114,6 +122,9 @@ private:
|
||||||
IoUtilities::NativeFileStream m_file;
|
IoUtilities::NativeFileStream m_file;
|
||||||
IoUtilities::BinaryReader m_freader;
|
IoUtilities::BinaryReader m_freader;
|
||||||
IoUtilities::BinaryWriter m_fwriter;
|
IoUtilities::BinaryWriter m_fwriter;
|
||||||
|
uint32 m_version;
|
||||||
|
PasswordFileOpenFlags m_openOptions;
|
||||||
|
PasswordFileSaveFlags m_saveOptions;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -124,6 +135,31 @@ inline IoUtilities::NativeFileStream &PasswordFile::fileStream()
|
||||||
return m_file;
|
return m_file;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns the file version used the last time when saving the file (the version of the file as it is on the disk).
|
||||||
|
* \remarks The version might change when re-saving with different options. See mininumVersion().
|
||||||
|
*/
|
||||||
|
inline uint32 PasswordFile::version() const
|
||||||
|
{
|
||||||
|
return m_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns the options used to open the file.
|
||||||
|
*/
|
||||||
|
inline PasswordFileOpenFlags PasswordFile::openOptions() const
|
||||||
|
{
|
||||||
|
return m_openOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns the save options used the last time when saving the file.
|
||||||
|
*/
|
||||||
|
inline PasswordFileSaveFlags PasswordFile::saveOptions() const
|
||||||
|
{
|
||||||
|
return m_saveOptions;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Io
|
} // namespace Io
|
||||||
|
|
||||||
#endif // PASSWORD_FILE_IO_PASSWORD_FILE_H
|
#endif // PASSWORD_FILE_IO_PASSWORD_FILE_H
|
||||||
|
|
Loading…
Reference in New Issue