2016-01-16 00:40:12 +08:00
|
|
|
/*
|
2019-06-24 22:01:18 +08:00
|
|
|
Copyright © 2014-2019 by The qTox Project Contributors
|
2016-01-16 00:40:12 +08:00
|
|
|
|
|
|
|
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
|
2019-06-24 22:01:18 +08:00
|
|
|
along with qTox. If not, see <http://www.gnu.org/licenses/>.
|
2016-01-16 00:40:12 +08:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include "widget.h"
|
|
|
|
#include "ui_widget.h"
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <QDebug>
|
2016-01-16 00:40:12 +08:00
|
|
|
#include <QDir>
|
|
|
|
#include <QFile>
|
|
|
|
#include <QMessageBox>
|
|
|
|
#include <QMetaObject>
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <QProcess>
|
2016-01-16 00:40:12 +08:00
|
|
|
#include <QSettings>
|
|
|
|
|
|
|
|
#include "update.h"
|
|
|
|
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
#ifdef _WIN32_WINNT
|
|
|
|
#undef _WIN32_WINNT
|
|
|
|
#endif
|
|
|
|
#define _WIN32_WINNT 0x0600 // Vista for SHGetKnownFolderPath
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <exdisp.h>
|
2016-01-16 00:40:12 +08:00
|
|
|
#include <shldisp.h>
|
|
|
|
#include <shlobj.h>
|
2017-02-26 19:52:45 +08:00
|
|
|
#include <windows.h>
|
2016-01-16 00:40:12 +08:00
|
|
|
|
|
|
|
const bool supported = true;
|
|
|
|
const QString QTOX_PATH = "qtox.exe";
|
|
|
|
#else
|
|
|
|
const bool supported = false;
|
|
|
|
const QString QTOX_PATH;
|
|
|
|
#endif
|
|
|
|
const QString SETTINGS_FILE = "settings.ini";
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
Widget::Widget(const Settings& s)
|
|
|
|
: QWidget(nullptr)
|
|
|
|
, ui(new Ui::Widget)
|
|
|
|
, settings{s}
|
2016-01-16 00:40:12 +08:00
|
|
|
{
|
|
|
|
ui->setupUi(this);
|
|
|
|
|
|
|
|
// Updates only for supported platforms
|
|
|
|
if (!supported)
|
|
|
|
fatalError(tr("The qTox updater is not supported on this platform."));
|
|
|
|
|
|
|
|
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
|
|
|
|
}
|
|
|
|
|
|
|
|
Widget::~Widget()
|
|
|
|
{
|
|
|
|
delete ui;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::setProgress(int value)
|
|
|
|
{
|
|
|
|
ui->progress->setValue(value);
|
|
|
|
ui->progress->repaint();
|
|
|
|
qApp->processEvents();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::fatalError(QString message)
|
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
qCritical() << "Update aborted with error:" << message;
|
|
|
|
QMessageBox::critical(this, tr("Error"), message + '\n' + tr("qTox will restart now."));
|
2016-01-16 00:40:12 +08:00
|
|
|
deleteUpdate();
|
|
|
|
restoreBackups();
|
|
|
|
startQToxAndExit();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::deleteUpdate()
|
|
|
|
{
|
2017-02-26 19:52:45 +08:00
|
|
|
QDir updateDir(settings.getSettingsDirPath() + "/update/");
|
2016-01-16 00:40:12 +08:00
|
|
|
updateDir.removeRecursively();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::startQToxAndExit()
|
|
|
|
{
|
|
|
|
#ifdef Q_OS_WIN
|
|
|
|
// Try to restart qTox as the actual user with our unelevated token
|
|
|
|
STARTUPINFOW si;
|
|
|
|
PROCESS_INFORMATION pi;
|
|
|
|
SecureZeroMemory(&si, sizeof(si));
|
|
|
|
SecureZeroMemory(&pi, sizeof(pi));
|
|
|
|
si.cb = sizeof(si);
|
|
|
|
|
|
|
|
bool unelevateOk = true;
|
|
|
|
|
|
|
|
auto advapi32H = LoadLibrary(TEXT("advapi32.dll"));
|
2017-02-26 19:52:45 +08:00
|
|
|
if ((unelevateOk = (advapi32H != nullptr))) {
|
|
|
|
auto CreateProcessWithTokenWH =
|
|
|
|
(decltype(&CreateProcessWithTokenW))GetProcAddress(advapi32H,
|
|
|
|
"CreateProcessWithTokenW");
|
|
|
|
if ((unelevateOk = (CreateProcessWithTokenWH != nullptr))) {
|
2016-03-12 06:27:32 +08:00
|
|
|
if (!CreateProcessWithTokenWH(settings.getPrimaryToken(), 0,
|
|
|
|
QTOX_PATH.toStdWString().c_str(), 0, 0, 0,
|
2017-02-26 19:52:45 +08:00
|
|
|
QApplication::applicationDirPath().toStdWString().c_str(),
|
|
|
|
&si, &pi))
|
2016-01-16 00:40:12 +08:00
|
|
|
unelevateOk = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseHandle(pi.hProcess);
|
|
|
|
CloseHandle(pi.hThread);
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!unelevateOk) {
|
2016-01-16 00:40:12 +08:00
|
|
|
qWarning() << "Failed to start unelevated qTox";
|
|
|
|
QProcess::startDetached(QTOX_PATH);
|
|
|
|
}
|
|
|
|
|
|
|
|
#else
|
|
|
|
QProcess::startDetached(QTOX_PATH);
|
|
|
|
#endif
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::deleteBackups()
|
|
|
|
{
|
|
|
|
for (QString file : backups)
|
2017-02-26 19:52:45 +08:00
|
|
|
QFile(file + ".bak").remove();
|
2016-01-16 00:40:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::restoreBackups()
|
|
|
|
{
|
|
|
|
for (QString file : backups)
|
2017-02-26 19:52:45 +08:00
|
|
|
QFile(file + ".bak").rename(file);
|
2016-01-16 00:40:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void Widget::update()
|
|
|
|
{
|
|
|
|
/// 1. Find and parse the update (0-5%)
|
|
|
|
// Check that the dir exists
|
2017-02-26 19:52:45 +08:00
|
|
|
QString updateDirStr = settings.getSettingsDirPath() + "/update/";
|
2016-01-16 00:40:12 +08:00
|
|
|
QDir updateDir(updateDirStr);
|
|
|
|
if (!updateDir.exists())
|
|
|
|
fatalError(tr("No update found."));
|
|
|
|
|
|
|
|
setProgress(2);
|
|
|
|
|
|
|
|
// Check that we have a flist and that every file on the diff exists
|
2017-02-26 19:52:45 +08:00
|
|
|
QFile updateFlistFile(updateDirStr + "flist");
|
2016-01-16 00:40:12 +08:00
|
|
|
if (!updateFlistFile.open(QIODevice::ReadOnly))
|
2016-03-12 06:27:32 +08:00
|
|
|
fatalError(tr("The update is incomplete!"));
|
2016-01-16 00:40:12 +08:00
|
|
|
|
|
|
|
QByteArray updateFlistData = updateFlistFile.readAll();
|
|
|
|
updateFlistFile.close();
|
|
|
|
|
|
|
|
QList<UpdateFileMeta> updateFlist = parseFlist(updateFlistData);
|
|
|
|
setProgress(5);
|
|
|
|
|
|
|
|
/// 2. Generate a diff (5-50%)
|
|
|
|
QList<UpdateFileMeta> diff = genUpdateDiff(updateFlist, this);
|
|
|
|
for (UpdateFileMeta fileMeta : diff)
|
2017-02-26 19:52:45 +08:00
|
|
|
if (!QFile::exists(updateDirStr + fileMeta.installpath))
|
2016-01-16 00:40:12 +08:00
|
|
|
fatalError(tr("The update is incomplete."));
|
|
|
|
|
|
|
|
if (diff.size() == 0)
|
2017-02-26 19:52:45 +08:00
|
|
|
fatalError(tr("The update is empty!"));
|
2016-01-16 00:40:12 +08:00
|
|
|
setProgress(50);
|
2017-02-26 19:52:45 +08:00
|
|
|
qDebug() << "Diff generated," << diff.size() << "files to update";
|
2016-01-16 00:40:12 +08:00
|
|
|
|
|
|
|
/// 2. Check the update (50-75%)
|
2017-02-26 19:52:45 +08:00
|
|
|
float checkProgressStep = 25.0 / (float)diff.size();
|
2016-01-16 00:40:12 +08:00
|
|
|
float checkProgress = 50;
|
2017-02-26 19:52:45 +08:00
|
|
|
for (UpdateFileMeta fileMeta : diff) {
|
2016-01-16 00:40:12 +08:00
|
|
|
UpdateFile file;
|
|
|
|
file.metadata = fileMeta;
|
|
|
|
|
2017-02-26 19:52:45 +08:00
|
|
|
QFile fileFile(updateDirStr + fileMeta.installpath);
|
2016-01-16 00:40:12 +08:00
|
|
|
if (!fileFile.open(QIODevice::ReadOnly))
|
|
|
|
fatalError(tr("Update files are unreadable."));
|
|
|
|
|
|
|
|
file.data = fileFile.readAll();
|
|
|
|
fileFile.close();
|
|
|
|
|
|
|
|
if (file.data.size() != (int)fileMeta.size)
|
|
|
|
fatalError(tr("Update files are corrupted."));
|
|
|
|
|
|
|
|
if (crypto_sign_verify_detached(file.metadata.sig, (unsigned char*)file.data.data(),
|
2017-02-26 19:52:45 +08:00
|
|
|
file.data.size(), key)
|
|
|
|
!= 0)
|
2016-01-16 00:40:12 +08:00
|
|
|
fatalError(tr("Update files are corrupted."));
|
|
|
|
|
|
|
|
checkProgress += checkProgressStep;
|
|
|
|
setProgress(checkProgress);
|
|
|
|
}
|
|
|
|
setProgress(75);
|
|
|
|
qDebug() << "Update files signature verified, installing";
|
|
|
|
|
|
|
|
/// 3. Install the update (75-95%)
|
2017-02-26 19:52:45 +08:00
|
|
|
float installProgressStep = 20.0 / (float)diff.size();
|
2016-01-16 00:40:12 +08:00
|
|
|
float installProgress = 75;
|
2017-02-26 19:52:45 +08:00
|
|
|
for (UpdateFileMeta fileMeta : diff) {
|
2016-01-16 00:40:12 +08:00
|
|
|
// Backup old files
|
2017-02-26 19:52:45 +08:00
|
|
|
if (QFile(fileMeta.installpath).exists()) {
|
|
|
|
QFile(fileMeta.installpath + ".bak").remove();
|
|
|
|
QFile(fileMeta.installpath).rename(fileMeta.installpath + ".bak");
|
2016-01-16 00:40:12 +08:00
|
|
|
backups.append(fileMeta.installpath);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Install new ones
|
|
|
|
QDir().mkpath(QFileInfo(fileMeta.installpath).absolutePath());
|
2017-02-26 19:52:45 +08:00
|
|
|
QFile fileFile(updateDirStr + fileMeta.installpath);
|
2016-01-16 00:40:12 +08:00
|
|
|
if (!fileFile.copy(fileMeta.installpath))
|
2017-02-26 19:52:45 +08:00
|
|
|
fatalError(tr("Unable to copy the update's files from ")
|
|
|
|
+ (updateDirStr + fileMeta.installpath) + " to " + fileMeta.installpath);
|
2016-01-16 00:40:12 +08:00
|
|
|
installProgress += installProgressStep;
|
|
|
|
setProgress(installProgress);
|
|
|
|
}
|
|
|
|
setProgress(95);
|
|
|
|
|
|
|
|
/// 4. Delete the update and backups (95-100%)
|
|
|
|
deleteUpdate();
|
|
|
|
setProgress(97);
|
|
|
|
deleteBackups();
|
|
|
|
setProgress(100);
|
|
|
|
|
|
|
|
/// 5. Start qTox and exit
|
|
|
|
qDebug() << "Update applied, restarting qTox!";
|
|
|
|
startQToxAndExit();
|
|
|
|
}
|