2015-06-06 09:40:08 +08:00
|
|
|
/*
|
|
|
|
Copyright © 2015 by The qTox Project
|
|
|
|
|
|
|
|
This file is part of qTox, a Qt-based graphical interface for Tox.
|
|
|
|
|
|
|
|
qTox is libre 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.
|
|
|
|
|
|
|
|
qTox 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 qTox. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2015-06-04 05:20:47 +08:00
|
|
|
#include "profile.h"
|
2015-06-04 07:30:17 +08:00
|
|
|
#include "profilelocker.h"
|
2015-06-06 07:44:47 +08:00
|
|
|
#include "src/persistence/settings.h"
|
|
|
|
#include "src/persistence/historykeeper.h"
|
2015-12-17 18:24:01 +08:00
|
|
|
#include "src/core/core.h"
|
2015-06-04 22:36:08 +08:00
|
|
|
#include "src/widget/gui.h"
|
2015-06-04 23:09:13 +08:00
|
|
|
#include "src/widget/widget.h"
|
|
|
|
#include "src/nexus.h"
|
2015-06-04 05:20:47 +08:00
|
|
|
#include <cassert>
|
|
|
|
#include <QDir>
|
|
|
|
#include <QFileInfo>
|
2015-06-04 16:56:48 +08:00
|
|
|
#include <QSaveFile>
|
2015-06-04 07:30:17 +08:00
|
|
|
#include <QThread>
|
|
|
|
#include <QObject>
|
|
|
|
#include <QDebug>
|
2015-12-06 03:07:59 +08:00
|
|
|
#include <sodium.h>
|
2015-06-06 07:44:47 +08:00
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @class Profile
|
|
|
|
* @brief Manages user profiles.
|
|
|
|
*
|
|
|
|
* @var bool Profile::newProfile
|
|
|
|
* @brief True if this is a newly created profile, with no .tox save file yet.
|
|
|
|
*
|
|
|
|
* @var bool Profile::isRemoved
|
|
|
|
* @brief True if the profile has been removed by remove().
|
|
|
|
*
|
|
|
|
* @var static constexpr int Profile::encryptHeaderSize = 8
|
|
|
|
* @brief How much data we need to read to check if the file is encrypted.
|
|
|
|
* @note Must be >= TOX_ENC_SAVE_MAGIC_LENGTH (8), which isn't publicly defined.
|
|
|
|
*/
|
2016-07-27 06:20:54 +08:00
|
|
|
|
2015-06-04 05:20:47 +08:00
|
|
|
QVector<QString> Profile::profiles;
|
|
|
|
|
2016-06-18 12:49:40 +08:00
|
|
|
Profile::Profile(QString name, const QString &password, bool isNewProfile)
|
2015-06-04 18:43:28 +08:00
|
|
|
: name{name}, password{password},
|
|
|
|
newProfile{isNewProfile}, isRemoved{false}
|
2015-06-04 05:20:47 +08:00
|
|
|
{
|
2015-10-09 08:10:51 +08:00
|
|
|
if (!password.isEmpty())
|
|
|
|
passkey = *core->createPasskey(password);
|
2015-06-28 03:14:35 +08:00
|
|
|
|
2015-06-06 00:53:27 +08:00
|
|
|
Settings& s = Settings::getInstance();
|
|
|
|
s.setCurrentProfile(name);
|
2015-06-06 21:54:58 +08:00
|
|
|
s.saveGlobal();
|
2015-12-17 18:24:01 +08:00
|
|
|
|
2016-01-17 03:08:43 +08:00
|
|
|
// At this point it's too early to load the personal settings (Nexus will do it), so we always load
|
2015-12-17 18:24:01 +08:00
|
|
|
// the history, and if it fails we can't change the setting now, but we keep a nullptr
|
|
|
|
history.reset(new History{name, password});
|
|
|
|
if (!history->isValid())
|
|
|
|
{
|
|
|
|
qWarning() << "Failed to open history for profile"<<name;
|
|
|
|
GUI::showError(QObject::tr("Error"), QObject::tr("qTox couldn't open your chat logs, they will be disabled."));
|
|
|
|
history.release();
|
|
|
|
}
|
2015-06-04 20:19:18 +08:00
|
|
|
|
2015-06-04 07:30:17 +08:00
|
|
|
coreThread = new QThread();
|
|
|
|
coreThread->setObjectName("qTox Core");
|
|
|
|
core = new Core(coreThread, *this);
|
|
|
|
core->moveToThread(coreThread);
|
|
|
|
QObject::connect(coreThread, &QThread::started, core, &Core::start);
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Locks and loads an existing profile and creates the associate Core* instance.
|
|
|
|
* @param name Profile name.
|
|
|
|
* @param password Profile password.
|
|
|
|
* @return Returns a nullptr on error. Profile pointer otherwise.
|
|
|
|
*
|
|
|
|
* @example If the profile is already in use return nullptr.
|
|
|
|
*/
|
2016-06-18 12:49:40 +08:00
|
|
|
Profile* Profile::loadProfile(QString name, const QString &password)
|
2015-06-04 07:30:17 +08:00
|
|
|
{
|
|
|
|
if (ProfileLocker::hasLock())
|
|
|
|
{
|
2015-06-04 08:10:06 +08:00
|
|
|
qCritical() << "Tried to load profile "<<name<<", but another profile is already locked!";
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ProfileLocker::lock(name))
|
|
|
|
{
|
|
|
|
qWarning() << "Failed to lock profile "<<name;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-06-28 03:14:35 +08:00
|
|
|
// Check password
|
|
|
|
{
|
|
|
|
QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox";
|
|
|
|
QFile saveFile(path);
|
|
|
|
qDebug() << "Loading tox save "<<path;
|
|
|
|
|
|
|
|
if (!saveFile.exists())
|
|
|
|
{
|
|
|
|
qWarning() << "The tox save file "<<path<<" was not found";
|
|
|
|
ProfileLocker::unlock();
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!saveFile.open(QIODevice::ReadOnly))
|
|
|
|
{
|
|
|
|
qCritical() << "The tox save file " << path << " couldn't' be opened";
|
|
|
|
ProfileLocker::unlock();
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
qint64 fileSize = saveFile.size();
|
|
|
|
if (fileSize <= 0)
|
|
|
|
{
|
|
|
|
qWarning() << "The tox save file"<<path<<" is empty!";
|
|
|
|
ProfileLocker::unlock();
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
QByteArray data = saveFile.readAll();
|
|
|
|
if (tox_is_data_encrypted((uint8_t*)data.data()))
|
|
|
|
{
|
|
|
|
if (password.isEmpty())
|
|
|
|
{
|
|
|
|
qCritical() << "The tox save file is encrypted, but we don't have a password!";
|
|
|
|
ProfileLocker::unlock();
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t salt[TOX_PASS_SALT_LENGTH];
|
|
|
|
tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt);
|
|
|
|
auto tmpkey = *Core::createPasskey(password, salt);
|
|
|
|
|
|
|
|
data = Core::decryptData(data, tmpkey);
|
|
|
|
if (data.isEmpty())
|
|
|
|
{
|
|
|
|
qCritical() << "Failed to decrypt the tox save file";
|
|
|
|
ProfileLocker::unlock();
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!password.isEmpty())
|
|
|
|
qWarning() << "We have a password, but the tox save file is not encrypted";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-12-17 18:24:01 +08:00
|
|
|
Profile* p = new Profile(name, password, false);
|
|
|
|
if (p->history && HistoryKeeper::isFileExist(!password.isEmpty()))
|
|
|
|
p->history->import(*HistoryKeeper::getInstance(*p));
|
|
|
|
return p;
|
2015-06-04 08:10:06 +08:00
|
|
|
}
|
2015-06-04 07:30:17 +08:00
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Creates a new profile and the associated Core* instance.
|
|
|
|
* @param name Username.
|
|
|
|
* @param password If password is not empty, the profile will be encrypted.
|
|
|
|
* @return Returns a nullptr on error. Profile pointer otherwise.
|
|
|
|
*
|
|
|
|
* @example If the profile is already in use return nullptr.
|
|
|
|
*/
|
2015-06-04 08:10:06 +08:00
|
|
|
Profile* Profile::createProfile(QString name, QString password)
|
|
|
|
{
|
|
|
|
if (ProfileLocker::hasLock())
|
|
|
|
{
|
|
|
|
qCritical() << "Tried to create profile "<<name<<", but another profile is already locked!";
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-06-05 18:44:22 +08:00
|
|
|
if (exists(name))
|
2015-06-04 08:10:06 +08:00
|
|
|
{
|
|
|
|
qCritical() << "Tried to create profile "<<name<<", but it already exists!";
|
|
|
|
return nullptr;
|
2015-06-04 07:30:17 +08:00
|
|
|
}
|
2015-06-04 05:20:47 +08:00
|
|
|
|
2015-06-04 07:30:17 +08:00
|
|
|
if (!ProfileLocker::lock(name))
|
|
|
|
{
|
|
|
|
qWarning() << "Failed to lock profile "<<name;
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2015-06-04 08:10:06 +08:00
|
|
|
Settings::getInstance().createPersonal(name);
|
|
|
|
return new Profile(name, password, true);
|
2015-06-04 05:20:47 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
Profile::~Profile()
|
|
|
|
{
|
2015-06-04 18:43:28 +08:00
|
|
|
if (!isRemoved && core->isReady())
|
|
|
|
saveToxSave();
|
2015-06-04 07:30:17 +08:00
|
|
|
delete core;
|
|
|
|
delete coreThread;
|
2015-09-24 07:00:22 +08:00
|
|
|
if (!isRemoved)
|
|
|
|
{
|
|
|
|
Settings::getInstance().savePersonal(this);
|
|
|
|
Settings::getInstance().sync();
|
|
|
|
ProfileLocker::assertLock();
|
|
|
|
assert(ProfileLocker::getCurLockName() == name);
|
|
|
|
ProfileLocker::unlock();
|
|
|
|
}
|
2015-06-04 05:20:47 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Lists all the files in the config dir with a given extension
|
|
|
|
* @param extension Raw extension, e.g. "jpeg" not ".jpeg".
|
|
|
|
* @return Vector of filenames.
|
|
|
|
*/
|
2015-06-04 05:20:47 +08:00
|
|
|
QVector<QString> Profile::getFilesByExt(QString extension)
|
|
|
|
{
|
|
|
|
QDir dir(Settings::getInstance().getSettingsDirPath());
|
|
|
|
QVector<QString> out;
|
|
|
|
dir.setFilter(QDir::Files | QDir::NoDotAndDotDot);
|
|
|
|
dir.setNameFilters(QStringList("*."+extension));
|
|
|
|
QFileInfoList list = dir.entryInfoList();
|
|
|
|
out.reserve(list.size());
|
|
|
|
for (QFileInfo file : list)
|
|
|
|
out += file.completeBaseName();
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Scan for profile, automatically importing them if needed.
|
|
|
|
* @warning NOT thread-safe.
|
|
|
|
*/
|
2015-06-04 05:20:47 +08:00
|
|
|
void Profile::scanProfiles()
|
|
|
|
{
|
|
|
|
profiles.clear();
|
|
|
|
QVector<QString> toxfiles = getFilesByExt("tox"), inifiles = getFilesByExt("ini");
|
|
|
|
for (QString toxfile : toxfiles)
|
|
|
|
{
|
|
|
|
if (!inifiles.contains(toxfile))
|
2016-04-21 22:48:17 +08:00
|
|
|
Settings::getInstance().createPersonal(toxfile);
|
2015-06-04 05:20:47 +08:00
|
|
|
profiles.append(toxfile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QVector<QString> Profile::getProfiles()
|
|
|
|
{
|
|
|
|
return profiles;
|
|
|
|
}
|
2015-06-04 07:30:17 +08:00
|
|
|
|
|
|
|
Core* Profile::getCore()
|
|
|
|
{
|
|
|
|
return core;
|
|
|
|
}
|
|
|
|
|
2015-12-17 18:24:01 +08:00
|
|
|
QString Profile::getName() const
|
2015-06-04 08:43:07 +08:00
|
|
|
{
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Starts the Core thread
|
|
|
|
*/
|
2015-06-04 07:30:17 +08:00
|
|
|
void Profile::startCore()
|
|
|
|
{
|
|
|
|
coreThread->start();
|
|
|
|
}
|
|
|
|
|
2015-06-04 08:43:07 +08:00
|
|
|
bool Profile::isNewProfile()
|
|
|
|
{
|
|
|
|
return newProfile;
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Loads the profile's .tox save from file, unencrypted
|
|
|
|
* @return Byte array of loaded profile save.
|
|
|
|
*/
|
2015-06-04 07:30:17 +08:00
|
|
|
QByteArray Profile::loadToxSave()
|
|
|
|
{
|
2015-06-04 18:43:28 +08:00
|
|
|
assert(!isRemoved);
|
2015-06-04 07:30:17 +08:00
|
|
|
QByteArray data;
|
|
|
|
|
2015-06-05 22:24:47 +08:00
|
|
|
QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox";
|
2015-06-04 07:30:17 +08:00
|
|
|
QFile saveFile(path);
|
|
|
|
qint64 fileSize;
|
|
|
|
qDebug() << "Loading tox save "<<path;
|
|
|
|
|
|
|
|
if (!saveFile.exists())
|
|
|
|
{
|
|
|
|
qWarning() << "The tox save file "<<path<<" was not found";
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!saveFile.open(QIODevice::ReadOnly))
|
|
|
|
{
|
|
|
|
qCritical() << "The tox save file " << path << " couldn't' be opened";
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
fileSize = saveFile.size();
|
|
|
|
if (fileSize <= 0)
|
|
|
|
{
|
|
|
|
qWarning() << "The tox save file"<<path<<" is empty!";
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
data = saveFile.readAll();
|
|
|
|
if (tox_is_data_encrypted((uint8_t*)data.data()))
|
|
|
|
{
|
|
|
|
if (password.isEmpty())
|
|
|
|
{
|
|
|
|
qCritical() << "The tox save file is encrypted, but we don't have a password!";
|
2015-06-04 08:43:07 +08:00
|
|
|
data.clear();
|
2015-06-04 07:30:17 +08:00
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t salt[TOX_PASS_SALT_LENGTH];
|
|
|
|
tox_get_salt(reinterpret_cast<uint8_t *>(data.data()), salt);
|
2015-06-28 03:14:35 +08:00
|
|
|
passkey = *core->createPasskey(password, salt);
|
2015-06-04 07:30:17 +08:00
|
|
|
|
2015-06-28 03:14:35 +08:00
|
|
|
data = core->decryptData(data, passkey);
|
2015-06-04 07:30:17 +08:00
|
|
|
if (data.isEmpty())
|
|
|
|
qCritical() << "Failed to decrypt the tox save file";
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (!password.isEmpty())
|
|
|
|
qWarning() << "We have a password, but the tox save file is not encrypted";
|
|
|
|
}
|
|
|
|
|
|
|
|
fail:
|
|
|
|
saveFile.close();
|
|
|
|
return data;
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Saves the profile's .tox save, encrypted if needed.
|
|
|
|
* @warning Invalid on deleted profiles.
|
|
|
|
*/
|
2015-06-04 17:37:54 +08:00
|
|
|
void Profile::saveToxSave()
|
|
|
|
{
|
2015-06-04 18:43:28 +08:00
|
|
|
assert(core->isReady());
|
|
|
|
QByteArray data = core->getToxSaveData();
|
|
|
|
assert(data.size());
|
|
|
|
saveToxSave(data);
|
2015-06-04 17:37:54 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Write the .tox save, encrypted if needed.
|
|
|
|
* @param data Byte array of profile save.
|
|
|
|
* @warning Invalid on deleted profiles.
|
|
|
|
*/
|
2015-06-04 16:56:48 +08:00
|
|
|
void Profile::saveToxSave(QByteArray data)
|
|
|
|
{
|
2015-06-04 18:43:28 +08:00
|
|
|
assert(!isRemoved);
|
2015-06-04 16:56:48 +08:00
|
|
|
ProfileLocker::assertLock();
|
|
|
|
assert(ProfileLocker::getCurLockName() == name);
|
|
|
|
|
2015-06-05 22:24:47 +08:00
|
|
|
QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox";
|
2015-06-04 17:42:49 +08:00
|
|
|
qDebug() << "Saving tox save to "<<path;
|
2015-06-04 16:56:48 +08:00
|
|
|
QSaveFile saveFile(path);
|
|
|
|
if (!saveFile.open(QIODevice::WriteOnly))
|
|
|
|
{
|
|
|
|
qCritical() << "Tox save file " << path << " couldn't be opened";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!password.isEmpty())
|
|
|
|
{
|
2015-06-28 03:14:35 +08:00
|
|
|
passkey = *core->createPasskey(password);
|
|
|
|
data = core->encryptData(data, passkey);
|
2015-06-04 16:56:48 +08:00
|
|
|
if (data.isEmpty())
|
|
|
|
{
|
|
|
|
qCritical() << "Failed to encrypt, can't save!";
|
|
|
|
saveFile.cancelWriting();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
saveFile.write(data);
|
2016-03-24 08:10:09 +08:00
|
|
|
|
|
|
|
// check if everything got written
|
2016-07-07 18:08:32 +08:00
|
|
|
if (saveFile.flush())
|
2016-03-24 08:10:09 +08:00
|
|
|
{
|
|
|
|
saveFile.commit();
|
|
|
|
newProfile = false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
saveFile.cancelWriting();
|
|
|
|
qCritical() << "Failed to write, can't save!";
|
|
|
|
}
|
2015-06-04 16:56:48 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Gets the path of the avatar file cached by this profile and corresponding to this owner ID.
|
|
|
|
* @param ownerId Path to avatar of friend with this ID will returned.
|
|
|
|
* @param forceUnencrypted If true, return the path to the plaintext file even if this is an encrypted profile.
|
|
|
|
* @return Path to the avatar.
|
|
|
|
*/
|
2015-12-06 03:07:59 +08:00
|
|
|
QString Profile::avatarPath(const QString &ownerId, bool forceUnencrypted)
|
2015-12-06 02:08:28 +08:00
|
|
|
{
|
2015-12-06 03:07:59 +08:00
|
|
|
if (password.isEmpty() || forceUnencrypted)
|
|
|
|
return Settings::getInstance().getSettingsDirPath() + "avatars/" + ownerId + ".png";
|
|
|
|
|
|
|
|
QByteArray idData = ownerId.toUtf8();
|
2015-12-21 17:14:23 +08:00
|
|
|
QByteArray pubkeyData = core->getSelfId().publicKey.toUtf8();
|
|
|
|
constexpr int hashSize = TOX_PUBLIC_KEY_SIZE;
|
2015-12-06 03:07:59 +08:00
|
|
|
static_assert(hashSize >= crypto_generichash_BYTES_MIN
|
|
|
|
&& hashSize <= crypto_generichash_BYTES_MAX, "Hash size not supported by libsodium");
|
2015-12-21 17:14:23 +08:00
|
|
|
static_assert(hashSize >= crypto_generichash_KEYBYTES_MIN
|
|
|
|
&& hashSize <= crypto_generichash_KEYBYTES_MAX, "Key size not supported by libsodium");
|
2015-12-06 03:07:59 +08:00
|
|
|
QByteArray hash(hashSize, 0);
|
2015-12-21 17:14:23 +08:00
|
|
|
crypto_generichash((uint8_t*)hash.data(), hashSize, (uint8_t*)idData.data(), idData.size(), (uint8_t*)pubkeyData.data(), pubkeyData.size());
|
2015-12-06 03:07:59 +08:00
|
|
|
return Settings::getInstance().getSettingsDirPath() + "avatars/" + hash.toHex().toUpper() + ".png";
|
2015-12-06 02:08:28 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Get our avatar from cache.
|
|
|
|
* @return Avatar as QPixmap.
|
|
|
|
*/
|
2015-12-06 02:08:28 +08:00
|
|
|
QPixmap Profile::loadAvatar()
|
|
|
|
{
|
|
|
|
return loadAvatar(core->getSelfId().publicKey);
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Get a contact's avatar from cache.
|
|
|
|
* @param ownerId Friend ID to load avatar.
|
|
|
|
* @return Avatar as QPixmap.
|
|
|
|
*/
|
2015-12-06 02:08:28 +08:00
|
|
|
QPixmap Profile::loadAvatar(const QString &ownerId)
|
|
|
|
{
|
|
|
|
QPixmap pic;
|
|
|
|
pic.loadFromData(loadAvatarData(ownerId));
|
|
|
|
return pic;
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Get a contact's avatar from cache
|
|
|
|
* @param ownerId Friend ID to load avatar.
|
|
|
|
* @return Avatar as QByteArray.
|
|
|
|
*/
|
2015-12-06 02:08:28 +08:00
|
|
|
QByteArray Profile::loadAvatarData(const QString &ownerId)
|
2015-12-15 11:35:12 +08:00
|
|
|
{
|
|
|
|
return loadAvatarData(ownerId, password);
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Get a contact's avatar from cache, with a specified profile password.
|
|
|
|
* @param ownerId Friend ID to load avatar.
|
|
|
|
* @param password Profile password to decrypt data.
|
|
|
|
* @return Avatar as QByteArray.
|
|
|
|
*/
|
2015-12-15 11:35:12 +08:00
|
|
|
QByteArray Profile::loadAvatarData(const QString &ownerId, const QString &password)
|
2015-12-06 02:08:28 +08:00
|
|
|
{
|
2015-12-06 03:07:59 +08:00
|
|
|
QString path = avatarPath(ownerId);
|
|
|
|
bool encrypted = !password.isEmpty();
|
|
|
|
|
|
|
|
// If the encrypted avatar isn't found, try loading the unencrypted one for the same ID
|
|
|
|
if (!password.isEmpty() && !QFile::exists(path))
|
|
|
|
{
|
|
|
|
encrypted = false;
|
|
|
|
path = avatarPath(ownerId, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
QFile file(path);
|
2015-12-06 02:08:28 +08:00
|
|
|
if (!file.open(QIODevice::ReadOnly))
|
|
|
|
return {};
|
2015-12-06 03:07:59 +08:00
|
|
|
|
|
|
|
QByteArray pic = file.readAll();
|
2015-12-15 06:27:12 +08:00
|
|
|
if (encrypted && !pic.isEmpty())
|
2015-12-06 03:07:59 +08:00
|
|
|
{
|
|
|
|
uint8_t salt[TOX_PASS_SALT_LENGTH];
|
|
|
|
tox_get_salt(reinterpret_cast<uint8_t *>(pic.data()), salt);
|
|
|
|
auto passkey = core->createPasskey(password, salt);
|
|
|
|
pic = core->decryptData(pic, *passkey);
|
|
|
|
}
|
|
|
|
return pic;
|
2015-12-06 02:08:28 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Save an avatar to cache.
|
|
|
|
* @param pic Picture to save.
|
|
|
|
* @param ownerId ID of avatar owner.
|
|
|
|
*/
|
2015-12-06 02:08:28 +08:00
|
|
|
void Profile::saveAvatar(QByteArray pic, const QString &ownerId)
|
|
|
|
{
|
2015-12-17 18:24:01 +08:00
|
|
|
if (!password.isEmpty() && !pic.isEmpty())
|
2015-12-06 03:07:59 +08:00
|
|
|
pic = core->encryptData(pic, passkey);
|
|
|
|
|
2015-12-06 02:08:28 +08:00
|
|
|
QString path = avatarPath(ownerId);
|
2015-12-06 05:01:36 +08:00
|
|
|
QDir(Settings::getInstance().getSettingsDirPath()).mkdir("avatars");
|
2015-12-19 23:43:09 +08:00
|
|
|
if (pic.isEmpty())
|
2015-12-06 02:08:28 +08:00
|
|
|
{
|
2015-12-19 23:43:09 +08:00
|
|
|
QFile::remove(path);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
QSaveFile file(path);
|
|
|
|
if (!file.open(QIODevice::WriteOnly))
|
|
|
|
{
|
|
|
|
qWarning() << "Tox avatar " << path << " couldn't be saved";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
file.write(pic);
|
|
|
|
file.commit();
|
2015-12-06 02:08:28 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Get the tox hash of a cached avatar.
|
|
|
|
* @param ownerId Friend ID to get hash.
|
|
|
|
* @return Avatar tox hash.
|
|
|
|
*/
|
2015-12-06 02:08:28 +08:00
|
|
|
QByteArray Profile::getAvatarHash(const QString &ownerId)
|
|
|
|
{
|
|
|
|
QByteArray pic = loadAvatarData(ownerId);
|
|
|
|
QByteArray avatarHash(TOX_HASH_LENGTH, 0);
|
|
|
|
tox_hash((uint8_t*)avatarHash.data(), (uint8_t*)pic.data(), pic.size());
|
|
|
|
return avatarHash;
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Removes our own avatar.
|
|
|
|
*/
|
2015-12-06 02:08:28 +08:00
|
|
|
void Profile::removeAvatar()
|
|
|
|
{
|
|
|
|
removeAvatar(core->getSelfId().publicKey);
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Checks that the history is enabled in the settings, and loaded successfully for this profile.
|
|
|
|
* @return True if enabled, false otherwise.
|
|
|
|
*/
|
2015-12-17 18:24:01 +08:00
|
|
|
bool Profile::isHistoryEnabled()
|
|
|
|
{
|
|
|
|
return Settings::getInstance().getEnableLogging() && history;
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Get chat history.
|
|
|
|
* @return May return a nullptr if the history failed to load.
|
|
|
|
*/
|
2015-12-17 18:24:01 +08:00
|
|
|
History *Profile::getHistory()
|
|
|
|
{
|
|
|
|
return history.get();
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Removes a cached avatar.
|
|
|
|
* @param ownerId Friend ID whose avater to delete.
|
|
|
|
*/
|
2015-12-06 02:08:28 +08:00
|
|
|
void Profile::removeAvatar(const QString &ownerId)
|
|
|
|
{
|
|
|
|
QFile::remove(avatarPath(ownerId));
|
2015-12-19 23:28:57 +08:00
|
|
|
if (ownerId == core->getSelfId().publicKey)
|
|
|
|
core->setAvatar({});
|
2015-12-06 02:08:28 +08:00
|
|
|
}
|
|
|
|
|
2015-06-05 18:44:22 +08:00
|
|
|
bool Profile::exists(QString name)
|
2015-06-04 08:10:06 +08:00
|
|
|
{
|
2015-06-05 22:24:47 +08:00
|
|
|
QString path = Settings::getInstance().getSettingsDirPath() + name;
|
2016-04-21 22:48:17 +08:00
|
|
|
return QFile::exists(path+".tox");
|
2015-06-04 08:10:06 +08:00
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Checks, if profile has a password.
|
|
|
|
* @return True if we have a password set (doesn't check the actual file on disk).
|
|
|
|
*/
|
2015-12-17 18:24:01 +08:00
|
|
|
bool Profile::isEncrypted() const
|
2015-06-04 21:16:25 +08:00
|
|
|
{
|
|
|
|
return !password.isEmpty();
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Checks if profile is encrypted.
|
|
|
|
* @note Checks the actual file on disk.
|
|
|
|
* @param name Profile name.
|
|
|
|
* @return True if profile is encrypted, false otherwise.
|
|
|
|
*/
|
2015-06-04 21:16:25 +08:00
|
|
|
bool Profile::isEncrypted(QString name)
|
2015-06-04 07:30:17 +08:00
|
|
|
{
|
|
|
|
uint8_t data[encryptHeaderSize] = {0};
|
2015-06-05 22:24:47 +08:00
|
|
|
QString path = Settings::getInstance().getSettingsDirPath() + name + ".tox";
|
2015-06-04 07:30:17 +08:00
|
|
|
QFile saveFile(path);
|
|
|
|
if (!saveFile.open(QIODevice::ReadOnly))
|
|
|
|
{
|
|
|
|
qWarning() << "Couldn't open tox save "<<path;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
saveFile.read((char*)data, encryptHeaderSize);
|
|
|
|
saveFile.close();
|
|
|
|
|
|
|
|
return tox_is_data_encrypted(data);
|
|
|
|
}
|
2015-06-04 18:43:28 +08:00
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Removes the profile permanently.
|
|
|
|
* Updates the profiles vector.
|
|
|
|
* @return Vector of filenames that could not be removed.
|
|
|
|
* @warning It is invalid to call loadToxSave or saveToxSave on a deleted profile.
|
|
|
|
*/
|
2016-04-30 05:10:21 +08:00
|
|
|
QVector<QString> Profile::remove()
|
2015-06-04 18:43:28 +08:00
|
|
|
{
|
|
|
|
if (isRemoved)
|
|
|
|
{
|
2016-04-27 10:44:52 +08:00
|
|
|
qWarning() << "Profile " << name << " is already removed!";
|
2016-04-30 05:10:21 +08:00
|
|
|
return {};
|
2015-06-04 18:43:28 +08:00
|
|
|
}
|
|
|
|
isRemoved = true;
|
|
|
|
|
2016-04-27 10:44:52 +08:00
|
|
|
qDebug() << "Removing profile" << name;
|
2015-06-05 04:35:29 +08:00
|
|
|
for (int i=0; i<profiles.size(); i++)
|
|
|
|
{
|
|
|
|
if (profiles[i] == name)
|
|
|
|
{
|
|
|
|
profiles.removeAt(i);
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
}
|
2015-06-05 22:24:47 +08:00
|
|
|
QString path = Settings::getInstance().getSettingsDirPath() + name;
|
2015-09-24 07:00:22 +08:00
|
|
|
ProfileLocker::unlock();
|
2015-06-04 18:43:28 +08:00
|
|
|
|
2016-04-27 10:44:52 +08:00
|
|
|
QFile profileMain {path + ".tox"};
|
|
|
|
QFile profileConfig {path + ".ini"};
|
|
|
|
QFile historyLegacyUnencrypted {HistoryKeeper::getHistoryPath(name, 0)};
|
|
|
|
QFile historyLegacyEncrypted {HistoryKeeper::getHistoryPath(name, 1)};
|
|
|
|
|
2016-04-30 05:10:21 +08:00
|
|
|
QVector<QString> ret;
|
2016-04-29 13:38:15 +08:00
|
|
|
|
2016-07-07 18:08:32 +08:00
|
|
|
if (!profileMain.remove() && profileMain.exists())
|
2016-04-27 10:44:52 +08:00
|
|
|
{
|
2016-04-30 05:10:21 +08:00
|
|
|
ret.push_back(profileMain.fileName());
|
2016-04-27 10:44:52 +08:00
|
|
|
qWarning() << "Could not remove file " << profileMain.fileName();
|
|
|
|
}
|
2016-07-07 18:08:32 +08:00
|
|
|
if (!profileConfig.remove() && profileConfig.exists())
|
2016-04-27 10:44:52 +08:00
|
|
|
{
|
2016-04-30 05:10:21 +08:00
|
|
|
ret.push_back(profileConfig.fileName());
|
2016-04-27 10:44:52 +08:00
|
|
|
qWarning() << "Could not remove file " << profileConfig.fileName();
|
|
|
|
}
|
|
|
|
|
2016-07-07 18:08:32 +08:00
|
|
|
if (!historyLegacyUnencrypted.remove() && historyLegacyUnencrypted.exists())
|
2016-04-27 10:44:52 +08:00
|
|
|
{
|
2016-04-30 05:10:21 +08:00
|
|
|
ret.push_back(historyLegacyUnencrypted.fileName());
|
2016-04-27 10:44:52 +08:00
|
|
|
qWarning() << "Could not remove file " << historyLegacyUnencrypted.fileName();
|
|
|
|
}
|
2016-07-07 18:08:32 +08:00
|
|
|
if (!historyLegacyEncrypted.remove() && historyLegacyEncrypted.exists())
|
2016-04-27 10:44:52 +08:00
|
|
|
{
|
2016-04-30 05:10:21 +08:00
|
|
|
ret.push_back(historyLegacyEncrypted.fileName());
|
2016-04-27 10:44:52 +08:00
|
|
|
qWarning() << "Could not remove file " << historyLegacyUnencrypted.fileName();
|
|
|
|
}
|
|
|
|
|
2015-12-19 23:51:15 +08:00
|
|
|
if (history)
|
|
|
|
{
|
2016-07-07 18:08:32 +08:00
|
|
|
if (!history->remove() && QFile::exists(History::getDbPath(name)))
|
2016-04-27 10:44:52 +08:00
|
|
|
{
|
2016-04-30 05:10:21 +08:00
|
|
|
ret.push_back(History::getDbPath(name));
|
2016-04-27 10:44:52 +08:00
|
|
|
qWarning() << "Could not remove file " << History::getDbPath(name);
|
|
|
|
}
|
2015-12-19 23:51:15 +08:00
|
|
|
history.release();
|
|
|
|
}
|
2016-04-29 13:38:15 +08:00
|
|
|
|
2016-04-30 05:10:21 +08:00
|
|
|
return ret;
|
2015-06-04 18:43:28 +08:00
|
|
|
}
|
2015-06-04 19:01:30 +08:00
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Tries to rename the profile.
|
|
|
|
* @param newName New name for the profile.
|
|
|
|
* @return False on error, true otherwise.
|
|
|
|
*/
|
2015-06-04 19:01:30 +08:00
|
|
|
bool Profile::rename(QString newName)
|
|
|
|
{
|
2015-06-05 22:24:47 +08:00
|
|
|
QString path = Settings::getInstance().getSettingsDirPath() + name,
|
|
|
|
newPath = Settings::getInstance().getSettingsDirPath() + newName;
|
2015-06-04 19:01:30 +08:00
|
|
|
|
|
|
|
if (!ProfileLocker::lock(newName))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
QFile::rename(path+".tox", newPath+".tox");
|
|
|
|
QFile::rename(path+".ini", newPath+".ini");
|
2015-12-17 18:24:01 +08:00
|
|
|
if (history)
|
|
|
|
history->rename(newName);
|
2015-06-04 19:01:30 +08:00
|
|
|
bool resetAutorun = Settings::getInstance().getAutorun();
|
|
|
|
Settings::getInstance().setAutorun(false);
|
|
|
|
Settings::getInstance().setCurrentProfile(newName);
|
|
|
|
if (resetAutorun)
|
|
|
|
Settings::getInstance().setAutorun(true); // fixes -p flag in autostart command line
|
|
|
|
|
|
|
|
name = newName;
|
|
|
|
return true;
|
|
|
|
}
|
2015-06-04 20:19:18 +08:00
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Checks whether the password is valid.
|
|
|
|
* @return True, if password is valid, false otherwise.
|
|
|
|
*/
|
2015-06-04 20:19:18 +08:00
|
|
|
bool Profile::checkPassword()
|
|
|
|
{
|
|
|
|
if (isRemoved)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return !loadToxSave().isEmpty();
|
|
|
|
}
|
2015-06-04 21:16:25 +08:00
|
|
|
|
2015-12-17 18:24:01 +08:00
|
|
|
QString Profile::getPassword() const
|
2015-06-04 21:16:25 +08:00
|
|
|
{
|
|
|
|
return password;
|
|
|
|
}
|
2015-06-04 22:36:08 +08:00
|
|
|
|
2015-12-17 18:24:01 +08:00
|
|
|
const TOX_PASS_KEY& Profile::getPasskey() const
|
2015-06-28 03:14:35 +08:00
|
|
|
{
|
|
|
|
return passkey;
|
|
|
|
}
|
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Delete core and restart a new one
|
|
|
|
*/
|
2015-06-04 22:36:08 +08:00
|
|
|
void Profile::restartCore()
|
|
|
|
{
|
|
|
|
GUI::setEnabled(false); // Core::reset re-enables it
|
|
|
|
if (!isRemoved && core->isReady())
|
|
|
|
saveToxSave();
|
|
|
|
QMetaObject::invokeMethod(core, "reset");
|
|
|
|
}
|
2015-06-04 23:09:13 +08:00
|
|
|
|
2016-07-27 06:20:54 +08:00
|
|
|
/**
|
2016-08-01 16:21:23 +08:00
|
|
|
* @brief Changes the encryption password and re-saves everything with it
|
|
|
|
* @param newPassword Password for encryption.
|
|
|
|
*/
|
2016-06-18 12:49:40 +08:00
|
|
|
void Profile::setPassword(const QString &newPassword)
|
2015-06-04 23:09:13 +08:00
|
|
|
{
|
2015-12-06 05:35:16 +08:00
|
|
|
QByteArray avatar = loadAvatarData(core->getSelfId().publicKey);
|
2015-12-15 11:35:12 +08:00
|
|
|
QString oldPassword = password;
|
2015-06-04 23:09:13 +08:00
|
|
|
password = newPassword;
|
2015-06-28 03:14:35 +08:00
|
|
|
passkey = *core->createPasskey(password);
|
2015-06-04 23:09:13 +08:00
|
|
|
saveToxSave();
|
|
|
|
|
2015-12-17 18:24:01 +08:00
|
|
|
if (history)
|
|
|
|
{
|
|
|
|
history->setPassword(newPassword);
|
|
|
|
Nexus::getDesktopGUI()->reloadHistory();
|
|
|
|
}
|
2015-12-06 05:35:16 +08:00
|
|
|
saveAvatar(avatar, core->getSelfId().publicKey);
|
2015-12-15 11:35:12 +08:00
|
|
|
|
|
|
|
QVector<uint32_t> friendList = core->getFriendList();
|
|
|
|
QVectorIterator<uint32_t> i(friendList);
|
|
|
|
while (i.hasNext())
|
|
|
|
{
|
|
|
|
QString friendPublicKey = core->getFriendPublicKey(i.next());
|
|
|
|
saveAvatar(loadAvatarData(friendPublicKey,oldPassword),friendPublicKey);
|
|
|
|
}
|
2015-06-04 23:09:13 +08:00
|
|
|
}
|