some work towards encryption
This commit is contained in:
parent
297e08ba41
commit
a7d1a28f29
21 changed files with 129 additions and 81 deletions
|
@ -2,12 +2,14 @@ set(SOURCE_FILES
|
|||
networkaccess.cpp
|
||||
clientcache.cpp
|
||||
urlstorage.cpp
|
||||
archive.cpp
|
||||
)
|
||||
|
||||
set(HEADER_FILES
|
||||
networkaccess.h
|
||||
clientcache.h
|
||||
urlstorage.h
|
||||
archive.h
|
||||
)
|
||||
|
||||
target_sources(squawk PRIVATE
|
||||
|
|
420
core/components/archive.cpp
Normal file
420
core/components/archive.cpp
Normal file
|
@ -0,0 +1,420 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "archive.h"
|
||||
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
Core::Archive::Archive(const QString& account, const QString& p_jid, QObject* parent):
|
||||
QObject(parent),
|
||||
jid(p_jid),
|
||||
account(account),
|
||||
opened(false),
|
||||
db(account + "/" + jid),
|
||||
messages(db.addStorage<QString, Shared::Message>("messages")),
|
||||
order(db.addStorage<uint64_t, QString>("order")),
|
||||
stats(db.addStorage<QString, QVariant>("stats")),
|
||||
avatars(db.addStorage<QString, AvatarInfo>("avatars")),
|
||||
stanzaIdToId(db.addStorage<QString, QString>("stanzaIdToId")),
|
||||
cursor(order->createCursor())
|
||||
{}
|
||||
|
||||
Core::Archive::~Archive() {
|
||||
close();
|
||||
}
|
||||
|
||||
void Core::Archive::open() {
|
||||
db.open();
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
|
||||
AvatarInfo info;
|
||||
bool hasAvatar = false;
|
||||
try {
|
||||
avatars->getRecord(jid, info, txn);
|
||||
hasAvatar = true;
|
||||
} catch (const LMDBAL::NotFound& e) {}
|
||||
|
||||
if (!hasAvatar)
|
||||
return;
|
||||
|
||||
QFile ava(db.getPath() + "/" + jid + "." + info.type);
|
||||
if (ava.exists())
|
||||
return;
|
||||
|
||||
try {
|
||||
avatars->removeRecord(jid, txn);
|
||||
txn.commit();
|
||||
} catch (const std::exception& e) {
|
||||
qDebug() << e.what();
|
||||
qDebug() << "error opening archive" << jid << "for account" << account
|
||||
<< ". There is supposed to be avatar but the file doesn't exist, couldn't even drop it, it surely will lead to an error";
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Archive::close() {
|
||||
db.close();
|
||||
}
|
||||
|
||||
bool Core::Archive::addElement(const Shared::Message& message) {
|
||||
QString id = message.getId();
|
||||
qDebug() << "Adding message with id " << id;
|
||||
|
||||
try {
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
messages->addRecord(id, message, txn);
|
||||
order->addRecord(message.getTime().toMSecsSinceEpoch(), id, txn);
|
||||
QString stanzaId = message.getStanzaId();
|
||||
if (!stanzaId.isEmpty())
|
||||
stanzaIdToId->addRecord(stanzaId, id, txn);
|
||||
|
||||
txn.commit();
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
qDebug() << "Could not add message with id " + id;
|
||||
qDebug() << e.what();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Core::Archive::clear() {
|
||||
db.drop();
|
||||
}
|
||||
|
||||
Shared::Message Core::Archive::getElement(const QString& id) const {
|
||||
return messages->getRecord(id);
|
||||
}
|
||||
|
||||
bool Core::Archive::hasElement(const QString& id) const {
|
||||
return messages->checkRecord(id);
|
||||
}
|
||||
|
||||
void Core::Archive::changeMessage(const QString& id, const QMap<QString, QVariant>& data) {
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
Shared::Message msg = messages->getRecord(id, txn);
|
||||
|
||||
bool hadStanzaId = !msg.getStanzaId().isEmpty();
|
||||
QDateTime oTime = msg.getTime();
|
||||
bool idChange = msg.change(data);
|
||||
QString newId = msg.getId();
|
||||
QDateTime nTime = msg.getTime();
|
||||
|
||||
bool orderChange = oTime != nTime;
|
||||
if (idChange || orderChange) {
|
||||
if (idChange)
|
||||
messages->removeRecord(id, txn);
|
||||
|
||||
if (orderChange)
|
||||
order->removeRecord(oTime.toMSecsSinceEpoch(), txn);
|
||||
|
||||
order->forceRecord(nTime.toMSecsSinceEpoch(), newId, txn);
|
||||
}
|
||||
|
||||
QString sid = msg.getStanzaId();
|
||||
if (!sid.isEmpty() && (idChange || !hadStanzaId))
|
||||
stanzaIdToId->forceRecord(sid, newId, txn);
|
||||
|
||||
messages->forceRecord(newId, msg, txn);
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
Shared::Message Core::Archive::newest() const {
|
||||
LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
|
||||
|
||||
try {
|
||||
cursor.open(txn);
|
||||
while (true) {
|
||||
std::pair<uint64_t, QString> pair = cursor.prev();
|
||||
Shared::Message msg = messages->getRecord(pair.second, txn);
|
||||
if (msg.serverStored()) {
|
||||
cursor.close();
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
cursor.close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
QString Core::Archive::newestId() const {
|
||||
Shared::Message msg = newest();
|
||||
return msg.getId();
|
||||
}
|
||||
|
||||
QString Core::Archive::oldestId() const {
|
||||
Shared::Message msg = oldest();
|
||||
return msg.getId();
|
||||
}
|
||||
|
||||
Shared::Message Core::Archive::oldest() const {
|
||||
LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
|
||||
|
||||
try {
|
||||
cursor.open(txn);
|
||||
while (true) {
|
||||
std::pair<uint64_t, QString> pair = cursor.next();
|
||||
Shared::Message msg = messages->getRecord(pair.second, txn);
|
||||
if (msg.serverStored()) {
|
||||
cursor.close();
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
} catch (...) {
|
||||
cursor.close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int Core::Archive::addElements(const std::list<Shared::Message>& messages) {
|
||||
unsigned int success = 0;
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
for (const Shared::Message& message : messages) {
|
||||
QString id = message.getId();
|
||||
bool added = false;
|
||||
try {
|
||||
Core::Archive::messages->addRecord(id, message, txn);
|
||||
added = true;
|
||||
} catch (const LMDBAL::Exist& e) {}
|
||||
|
||||
if (!added)
|
||||
continue;
|
||||
|
||||
order->addRecord(message.getTime().toMSecsSinceEpoch(), id, txn);
|
||||
|
||||
QString sid = message.getStanzaId();
|
||||
if (!sid.isEmpty())
|
||||
stanzaIdToId->addRecord(sid, id, txn);
|
||||
|
||||
++success;
|
||||
}
|
||||
txn.commit();
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
long unsigned int Core::Archive::size() const {
|
||||
return order->count();
|
||||
}
|
||||
|
||||
std::list<Shared::Message> Core::Archive::getBefore(unsigned int count, const QString& id) {
|
||||
LMDBAL::Transaction txn = db.beginReadOnlyTransaction();
|
||||
std::list<Shared::Message> res;
|
||||
try {
|
||||
cursor.open(txn);
|
||||
if (!id.isEmpty()) {
|
||||
Shared::Message reference = messages->getRecord(id, txn);
|
||||
uint64_t stamp = reference.getTime().toMSecsSinceEpoch();
|
||||
cursor.set(stamp);
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < count; ++i) {
|
||||
std::pair<uint64_t, QString> pair;
|
||||
cursor.prev(pair.first, pair.second);
|
||||
|
||||
res.emplace_back();
|
||||
Shared::Message& msg = res.back();
|
||||
messages->getRecord(pair.second, msg, txn);
|
||||
}
|
||||
cursor.close();
|
||||
|
||||
return res;
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
cursor.close();
|
||||
if (res.empty())
|
||||
throw e;
|
||||
else
|
||||
return res;
|
||||
} catch (...) {
|
||||
cursor.close();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::Archive::isFromTheBeginning() const {
|
||||
try {
|
||||
return stats->getRecord("fromTheBeginning").toBool();
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Archive::setFromTheBeginning(bool is) {
|
||||
stats->forceRecord("fromTheBeginning", is);
|
||||
}
|
||||
|
||||
Shared::EncryptionProtocol Core::Archive::encryption() const {
|
||||
try {
|
||||
return stats->getRecord("encryption").value<Shared::EncryptionProtocol>();
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
return Shared::EncryptionProtocol::none;
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::Archive::setEncryption(Shared::EncryptionProtocol is) {
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
Shared::EncryptionProtocol current = Shared::EncryptionProtocol::none;
|
||||
try {
|
||||
current = stats->getRecord("encryption", txn).value<Shared::EncryptionProtocol>();
|
||||
} catch (const LMDBAL::NotFound& e) {}
|
||||
|
||||
if (is != current) {
|
||||
stats->forceRecord("encryption", static_cast<uint8_t>(is), txn);
|
||||
txn.commit();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
QString Core::Archive::idByStanzaId(const QString& stanzaId) const {
|
||||
return stanzaIdToId->getRecord(stanzaId);
|
||||
}
|
||||
|
||||
QString Core::Archive::stanzaIdById(const QString& id) const {
|
||||
try {
|
||||
Shared::Message msg = getElement(id);
|
||||
return msg.getStanzaId();
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::Archive::setAvatar(const QByteArray& data, AvatarInfo& newInfo, bool generated, const QString& resource) {
|
||||
LMDBAL::WriteTransaction txn = db.beginTransaction();
|
||||
AvatarInfo oldInfo;
|
||||
bool haveAvatar = false;
|
||||
QString res = resource.isEmpty() ? jid : resource;
|
||||
try {
|
||||
avatars->getRecord(res, oldInfo, txn);
|
||||
haveAvatar = true;
|
||||
} catch (const LMDBAL::NotFound& e) {}
|
||||
|
||||
if (data.size() == 0) {
|
||||
if (!haveAvatar)
|
||||
return false;
|
||||
|
||||
avatars->removeRecord(res, txn);
|
||||
txn.commit();
|
||||
return true;
|
||||
} else {
|
||||
QString currentPath = db.getPath();
|
||||
bool needToRemoveOld = false;
|
||||
QCryptographicHash hash(QCryptographicHash::Sha1);
|
||||
hash.addData(data);
|
||||
QByteArray newHash(hash.result());
|
||||
if (haveAvatar) {
|
||||
if (!generated && !oldInfo.autogenerated && oldInfo.hash == newHash)
|
||||
return false;
|
||||
|
||||
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type);
|
||||
if (oldAvatar.exists()) {
|
||||
if (oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type + ".bak")) {
|
||||
needToRemoveOld = true;
|
||||
} else {
|
||||
qDebug() << "Can't change avatar: couldn't get rid of the old avatar" << oldAvatar.fileName();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
QMimeDatabase mimedb;
|
||||
QMimeType type = mimedb.mimeTypeForData(data);
|
||||
QString ext = type.preferredSuffix();
|
||||
QFile newAvatar(currentPath + "/" + res + "." + ext);
|
||||
if (newAvatar.open(QFile::WriteOnly)) {
|
||||
newAvatar.write(data);
|
||||
newAvatar.close();
|
||||
|
||||
newInfo.type = ext;
|
||||
newInfo.hash = newHash;
|
||||
newInfo.autogenerated = generated;
|
||||
try {
|
||||
avatars->forceRecord(res, newInfo, txn);
|
||||
txn.commit();
|
||||
} catch (...) {
|
||||
qDebug() << "Can't change avatar: couldn't store changes to database for" << newAvatar.fileName() << "rolling back to the previous state";
|
||||
if (needToRemoveOld) {
|
||||
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
|
||||
oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (needToRemoveOld) {
|
||||
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
|
||||
oldAvatar.remove();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
qDebug() << "Can't change avatar: cant open file to write" << newAvatar.fileName() << "rolling back to the previous state";
|
||||
if (needToRemoveOld) {
|
||||
QFile oldAvatar(currentPath + "/" + res + "." + oldInfo.type + ".bak");
|
||||
oldAvatar.rename(currentPath + "/" + res + "." + oldInfo.type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Core::Archive::readAvatarInfo(Core::Archive::AvatarInfo& target, const QString& resource) const {
|
||||
try {
|
||||
avatars->getRecord(resource.isEmpty() ? jid : resource, target);
|
||||
return true;
|
||||
} catch (const LMDBAL::NotFound& e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void Core::Archive::readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const {
|
||||
avatars->readAll(data);
|
||||
}
|
||||
|
||||
Core::Archive::AvatarInfo Core::Archive::getAvatarInfo(const QString& resource) const {
|
||||
return avatars->getRecord(resource);
|
||||
}
|
||||
|
||||
Core::Archive::AvatarInfo::AvatarInfo():
|
||||
type(),
|
||||
hash(),
|
||||
autogenerated(false)
|
||||
{}
|
||||
|
||||
Core::Archive::AvatarInfo::AvatarInfo(const QString& p_type, const QByteArray& p_hash, bool p_autogenerated):
|
||||
type(p_type),
|
||||
hash(p_hash),
|
||||
autogenerated(p_autogenerated)
|
||||
{}
|
||||
|
||||
QDataStream & operator<<(QDataStream& out, const Core::Archive::AvatarInfo& info) {
|
||||
out << info.type;
|
||||
out << info.hash;
|
||||
out << info.autogenerated;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
QDataStream & operator>>(QDataStream& in, Core::Archive::AvatarInfo& info) {
|
||||
in >> info.type;
|
||||
in >> info.hash;
|
||||
in >> info.autogenerated;
|
||||
|
||||
return in;
|
||||
}
|
105
core/components/archive.h
Normal file
105
core/components/archive.h
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Squawk messenger.
|
||||
* Copyright (C) 2019 Yury Gubich <blue@macaw.me>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef CORE_ARCHIVE_H
|
||||
#define CORE_ARCHIVE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QCryptographicHash>
|
||||
#include <QMimeDatabase>
|
||||
#include <QMimeType>
|
||||
#include <QDataStream>
|
||||
|
||||
#include "shared/enums.h"
|
||||
#include "shared/message.h"
|
||||
#include "shared/exception.h"
|
||||
#include <lmdb.h>
|
||||
#include <list>
|
||||
|
||||
#include <lmdbal/base.h>
|
||||
#include <lmdbal/storage.h>
|
||||
#include <lmdbal/cursor.h>
|
||||
|
||||
namespace Core {
|
||||
|
||||
class Archive : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
class AvatarInfo;
|
||||
|
||||
Archive(const QString& account, const QString& jid, QObject* parent = 0);
|
||||
~Archive();
|
||||
|
||||
void open();
|
||||
void close();
|
||||
|
||||
bool addElement(const Shared::Message& message);
|
||||
unsigned int addElements(const std::list<Shared::Message>& messages);
|
||||
Shared::Message getElement(const QString& id) const;
|
||||
bool hasElement(const QString& id) const;
|
||||
void changeMessage(const QString& id, const QMap<QString, QVariant>& data);
|
||||
Shared::Message oldest() const;
|
||||
QString oldestId() const;
|
||||
Shared::Message newest() const;
|
||||
QString newestId() const;
|
||||
void clear();
|
||||
long unsigned int size() const;
|
||||
std::list<Shared::Message> getBefore(unsigned int count, const QString& id);
|
||||
bool isFromTheBeginning() const;
|
||||
void setFromTheBeginning(bool is);
|
||||
Shared::EncryptionProtocol encryption() const;
|
||||
bool setEncryption(Shared::EncryptionProtocol value); //returns true if changed, false otherwise
|
||||
bool setAvatar(const QByteArray& data, AvatarInfo& info, bool generated = false, const QString& resource = "");
|
||||
AvatarInfo getAvatarInfo(const QString& resource = "") const;
|
||||
bool readAvatarInfo(AvatarInfo& target, const QString& resource = "") const;
|
||||
void readAllResourcesAvatars(std::map<QString, AvatarInfo>& data) const;
|
||||
QString idByStanzaId(const QString& stanzaId) const;
|
||||
QString stanzaIdById(const QString& id) const;
|
||||
|
||||
public:
|
||||
const QString jid;
|
||||
const QString account;
|
||||
|
||||
public:
|
||||
class AvatarInfo {
|
||||
public:
|
||||
AvatarInfo();
|
||||
AvatarInfo(const QString& type, const QByteArray& hash, bool autogenerated);
|
||||
|
||||
QString type;
|
||||
QByteArray hash;
|
||||
bool autogenerated;
|
||||
};
|
||||
|
||||
private:
|
||||
bool opened;
|
||||
LMDBAL::Base db;
|
||||
LMDBAL::Storage<QString, Shared::Message>* messages;
|
||||
LMDBAL::Storage<uint64_t, QString>* order;
|
||||
LMDBAL::Storage<QString, QVariant>* stats;
|
||||
LMDBAL::Storage<QString, AvatarInfo>* avatars;
|
||||
LMDBAL::Storage<QString, QString>* stanzaIdToId;
|
||||
mutable LMDBAL::Cursor<uint64_t, QString> cursor;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
QDataStream& operator << (QDataStream &out, const Core::Archive::AvatarInfo& info);
|
||||
QDataStream& operator >> (QDataStream &in, Core::Archive::AvatarInfo& info);
|
||||
|
||||
#endif // CORE_ARCHIVE_H
|
Loading…
Add table
Add a link
Reference in a new issue