1
0
mirror of https://github.com/qTox/qTox.git synced 2024-03-22 14:00:36 +08:00

Download and applying updates, qtox-updater

The auto-updater is essentially done, except for the GUI. For now this is only an API.
The API works, but the tools to manage the update server would need some love
This commit is contained in:
Tux3 / Mlkj / !Lev.uXFMLA 2014-11-09 23:24:23 +01:00
parent 1350fbbaf9
commit 45e20187cc
No known key found for this signature in database
GPG Key ID: 7E086DD661263264
22 changed files with 1246 additions and 45 deletions

2
.gitignore vendored
View File

@ -7,3 +7,5 @@ qrc_*
Makefile
qtox
*.qm
build-*-Release
build-*-Debug

View File

@ -57,7 +57,7 @@ contains(JENKINS,YES) {
# Rules for Windows, Mac OSX, and Linux
win32 {
RC_FILE = windows/qtox.rc
LIBS += -liphlpapi -L$$PWD/libs/lib -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lvpx -lpthread -lsodium
LIBS += -liphlpapi -L$$PWD/libs/lib -lsodium -ltoxav -ltoxcore -ltoxencryptsave -ltoxdns -lvpx -lpthread
LIBS += -L$$PWD/libs/lib -lopencv_core248 -lopencv_highgui248 -lopencv_imgproc248 -lOpenAL32 -lopus
LIBS += -lz -lopengl32 -lole32 -loleaut32 -luuid -lvfw32 -ljpeg -ltiff -lpng -ljasper -lIlmImf -lHalf -lws2_32
} else {

View File

@ -17,14 +17,25 @@
#include "src/autoupdate.h"
#include "src/misc/serialize.h"
#include "src/misc/settings.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QCoreApplication>
#include <QFile>
#include <QDir>
#include <QProcess>
#ifdef _WIN32
#ifdef Q_OS_WIN
#include <windows.h>
#include <shellapi.h>
#endif
#ifdef Q_OS_WIN
const QString AutoUpdater::platform = "win32";
const QString AutoUpdater::updaterBin = "qtox-updater.exe";
#else
const QString AutoUpdater::platform = "win32"; ///TODO: FIXME: undefine, we want an empty qstring
const QString AutoUpdater::platform;
const QString AutoUpdater::updaterBin;
#endif
const QString AutoUpdater::updateServer = "http://127.0.0.1";
const QString AutoUpdater::checkURI = AutoUpdater::updateServer+"/qtox/"+AutoUpdater::platform+"/version";
@ -96,20 +107,7 @@ QString AutoUpdater::getUpdateVersion()
return version;
}
QList<AutoUpdater::UpdateFileMeta> AutoUpdater::genUpdateDiff()
{
QList<UpdateFileMeta> diff;
// Updates only for supported platforms
if (platform.isEmpty())
return diff;
QList<UpdateFileMeta> newFlist = getUpdateFlist();
return diff;
}
QList<AutoUpdater::UpdateFileMeta> AutoUpdater::parseflist(QByteArray flistData)
QList<AutoUpdater::UpdateFileMeta> AutoUpdater::parseFlist(QByteArray flistData)
{
QList<UpdateFileMeta> flist;
@ -148,8 +146,6 @@ QList<AutoUpdater::UpdateFileMeta> AutoUpdater::parseflist(QByteArray flistData)
// Parse. We assume no errors handling needed since the signature is valid.
while (!flistData.isEmpty())
{
qDebug() << "Got "<<flistData.size()<<" bytes of data left";
UpdateFileMeta newFile;
memcpy(newFile.sig, flistData.data(), crypto_sign_BYTES);
@ -164,22 +160,15 @@ QList<AutoUpdater::UpdateFileMeta> AutoUpdater::parseflist(QByteArray flistData)
newFile.size = dataToUint64(flistData);
flistData = flistData.mid(8);
qDebug() << "AutoUpdater::parseflist: New file:";
qDebug() << "- Id: "<<newFile.id;
qDebug() << "- Install path: "<<newFile.installpath;
qDebug() << "- Size: "<<newFile.size<<" bytes";
qDebug() << "- Signature: "<<QByteArray((char*)newFile.sig, crypto_sign_BYTES).toHex();
flist += newFile;
}
qDebug() << "AutoUpdater::parseflist: Done parsing flist";
return flist;
}
QList<AutoUpdater::UpdateFileMeta> AutoUpdater::getUpdateFlist()
QByteArray AutoUpdater::getUpdateFlist()
{
QList<UpdateFileMeta> flist;
QByteArray flist;
QNetworkAccessManager *manager = new QNetworkAccessManager;
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(flistURI)));
@ -194,10 +183,212 @@ QList<AutoUpdater::UpdateFileMeta> AutoUpdater::getUpdateFlist()
return flist;
}
QByteArray data = reply->readAll();
flist = reply->readAll();
reply->deleteLater();
manager->deleteLater();
flist = parseflist(data);
return flist;
}
QByteArray AutoUpdater::getLocalFlist()
{
QByteArray flist;
QFile flistFile("flist");
if (!flistFile.open(QIODevice::ReadOnly))
{
qWarning() << "AutoUpdater::getLocalFlist: Can't open local flist";
return flist;
}
flist = flistFile.readAll();
flistFile.close();
return flist;
}
QList<AutoUpdater::UpdateFileMeta> AutoUpdater::genUpdateDiff(QList<UpdateFileMeta> updateFlist)
{
QList<UpdateFileMeta> diff;
QList<UpdateFileMeta> localFlist = parseFlist(getLocalFlist());
for (UpdateFileMeta file : updateFlist)
if (!localFlist.contains(file))
diff += file;
return diff;
}
AutoUpdater::UpdateFile AutoUpdater::getUpdateFile(UpdateFileMeta fileMeta)
{
UpdateFile file;
file.metadata = fileMeta;
QNetworkAccessManager *manager = new QNetworkAccessManager;
QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(filesURI+fileMeta.id)));
while (!reply->isFinished())
qApp->processEvents();
if (reply->error() != QNetworkReply::NoError)
{
qWarning() << "AutoUpdater: getUpdateFile: network error: "<<reply->errorString();
reply->deleteLater();
manager->deleteLater();
return file;
}
file.data = reply->readAll();
reply->deleteLater();
manager->deleteLater();
return file;
}
bool AutoUpdater::downloadUpdate()
{
// Updates only for supported platforms
if (platform.isEmpty())
return false;
// Get a list of files to update
QByteArray newFlistData = getUpdateFlist();
QList<UpdateFileMeta> newFlist = parseFlist(newFlistData);
QList<UpdateFileMeta> diff = genUpdateDiff(newFlist);
qDebug() << "AutoUpdater: Need to update "<<diff.size()<<" files";
// Create an empty directory to download updates into
QString updateDirStr = Settings::getInstance().getSettingsDirPath() + "/update/";
QDir updateDir(updateDirStr);
if (updateDir.exists())
updateDir.removeRecursively();
QDir().mkdir(updateDirStr);
updateDir = QDir(updateDirStr);
if (!updateDir.exists())
{
qWarning() << "AutoUpdater::downloadUpdate: Can't create update directory, aborting...";
return false;
}
// Write the new flist for the updater
QFile newFlistFile(updateDirStr+"flist");
if (!newFlistFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
qWarning() << "AutoUpdater::downloadUpdate: Can't save new flist file, aborting...";
return false;
}
newFlistFile.write(newFlistData);
newFlistFile.close();
// Download and write each new file
for (UpdateFileMeta fileMeta : diff)
{
qDebug() << "AutoUpdater: Downloading '"+fileMeta.installpath+"' ...";
// Create subdirs if necessary
QString fileDirStr{QFileInfo(updateDirStr+fileMeta.installpath).absolutePath()};
if (!QDir(fileDirStr).exists())
QDir().mkpath(fileDirStr);
// Download
UpdateFile file = getUpdateFile(fileMeta);
if (file.data.isNull())
{
qWarning() << "AutoUpdater::downloadUpdate: Error downloading a file, aborting...";
return false;
}
// Check signature
if (crypto_sign_verify_detached(file.metadata.sig, (unsigned char*)file.data.data(),
file.data.size(), key) != 0)
{
qCritical() << "AutoUpdater: downloadUpdate: RECEIVED FORGED FILE, aborting...";
return false;
}
// Save
QFile fileFile(updateDirStr+fileMeta.installpath);
if (!fileFile.open(QIODevice::WriteOnly | QIODevice::Truncate))
{
qWarning() << "AutoUpdater::downloadUpdate: Can't save new update file, aborting...";
return false;
}
fileFile.write(file.data);
fileFile.close();
}
return true;
}
bool AutoUpdater::isLocalUpdateReady()
{
// Updates only for supported platforms
if (platform.isEmpty())
return false;
// Check that there's an update dir in the first place, valid or not
QString updateDirStr = Settings::getInstance().getSettingsDirPath() + "/update/";
QDir updateDir(updateDirStr);
if (!updateDir.exists())
return false;
// Check that we have a flist and that every file on the diff exists
QFile updateFlistFile(updateDirStr+"flist");
if (!updateFlistFile.open(QIODevice::ReadOnly))
return false;
QByteArray updateFlistData = updateFlistFile.readAll();
updateFlistFile.close();
QList<UpdateFileMeta> updateFlist = parseFlist(updateFlistData);
QList<UpdateFileMeta> diff = genUpdateDiff(updateFlist);
for (UpdateFileMeta fileMeta : diff)
if (!QFile::exists(updateDirStr+fileMeta.installpath))
return false;
return true;
}
void AutoUpdater::installLocalUpdate()
{
qDebug() << "AutoUpdater: About to start the qTox updater to install a local update";
// Delete the update if we fail so we don't fail again.
// Updates only for supported platforms.
if (platform.isEmpty())
{
qCritical() << "AutoUpdater: Failed to start the qTox updater, removing the update and exiting";
QString updateDirStr = Settings::getInstance().getSettingsDirPath() + "/update/";
QDir(updateDirStr).removeRecursively();
exit(-1);
}
// Workaround QTBUG-7645
// QProcess fails silently when elevation is required instead of showing a UAC prompt on Win7/Vista
#ifdef Q_OS_WIN
int result = (int)::ShellExecuteA(0, "open", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL);
if (SE_ERR_ACCESSDENIED == result)
{
// Requesting elevation
result = (int)::ShellExecuteA(0, "runas", updaterBin.toUtf8().constData(), 0, 0, SW_SHOWNORMAL);
}
if (result <= 32)
{
goto fail;
}
#else
if (!QProcess::startDetached(updaterBin))
goto fail;
#endif
exit(0);
// Centralized error handling
fail:
qCritical() << "AutoUpdater: Failed to start the qTox updater, removing the update and exiting";
QString updateDirStr = Settings::getInstance().getSettingsDirPath() + "/update/";
QDir(updateDirStr).removeRecursively();
exit(-1);
}

View File

@ -22,7 +22,16 @@
#include <QList>
#include <sodium.h>
/// For now we only support auto updates on Windows, although extending it is not a technical issue.
/// Linux and Mac users are expected to use their package managers or update manually through official channels.
#ifdef Q_OS_WIN
#define AUTOUPDATE_ENABLED 1
#else
#define AUTOUPDATE_ENABLED 0
#endif
/// Handles checking and applying updates for qTox
/// Do *NOT* use auto update unless AUTOUPDATE_ENABLED is defined to 1
class AutoUpdater
{
public:
@ -32,6 +41,13 @@ public:
QString id; ///< Unique id of the file
QString installpath; ///< Local path including the file name. May be relative to qtox-updater or absolute
uint64_t size; ///< Size in bytes of the file
bool operator==(const UpdateFileMeta& other)
{
return (size == other.size
&& id == other.id && installpath == other.installpath
&& memcmp(sig, other.sig, crypto_sign_BYTES) == 0);
}
};
struct UpdateFile
@ -47,16 +63,33 @@ public:
/// Fetch the version string of the last update available from the qTox update server
/// Will try to follow qTox's proxy settings, may block and processEvents
static QString getUpdateVersion();
/// Generates a list of files we need to update
/// Will try to download an update, if successful returns true and qTox will apply it after a restart
/// Will try to follow qTox's proxy settings, may block and processEvents
static QList<UpdateFileMeta> genUpdateDiff();
static bool downloadUpdate();
/// Returns true if an update is downloaded and ready to be installed
/// If so, call installLocalUpdate. If not, call downloadUpdate.
/// This only checks that we downloaded an update and didn't stop in the middle, not that every file is still valid
static bool isLocalUpdateReady();
/// Launches the qTox updater to try to install the local update and exits immediately
/// Will not check that the update actually exists, use isLocalUpdateReady first for that
/// The qTox updater will restart us after the update is done
/// Note: If we fail to start the qTox updater, we will delete the update and exit
[[ noreturn ]] static void installLocalUpdate();
protected:
/// Parses and validates a flist file. Returns an empty list on error
static QList<UpdateFileMeta> parseflist(QByteArray flistData);
/// Get the update server's flist and parse it. Returns an empty list on error
static QList<UpdateFileMeta> parseFlist(QByteArray flistData);
/// Gets the update server's flist. Returns an empty array on error
/// Will try to follow qTox's proxy settings, may block and processEvents
static QList<UpdateFileMeta> getUpdateFlist();
static QByteArray getUpdateFlist();
/// Gets the local flist. Returns an empty array on error
static QByteArray getLocalFlist();
/// Generates a list of files we need to update
static QList<UpdateFileMeta> genUpdateDiff(QList<UpdateFileMeta> updateFlist);
/// Tries to fetch the file from the update server. Returns a file with a null QByteArray on error.
/// Note that a file with an empty but non-null QByteArray is not an error, merely a file of size 0.
/// Will try to follow qTox's proxy settings, may block and processEvents
static UpdateFile getUpdateFile(UpdateFileMeta fileMeta);
private:
AutoUpdater() = delete;
@ -68,6 +101,7 @@ private:
static const QString checkURI; ///< URI of the file containing the latest version string
static const QString flistURI; ///< URI of the file containing info on each file (hash, signature, size, name, ..)
static const QString filesURI; ///< URI of the actual files of the latest version
static const QString updaterBin; ///< Path to the qtox-updater binary
static unsigned char key[];
};

View File

@ -19,6 +19,7 @@
#include "src/ipc.h"
#include "src/widget/toxuri.h"
#include "src/widget/toxsave.h"
#include "src/autoupdate.h"
#include <QApplication>
#include <QFontDatabase>
#include <QDebug>
@ -87,6 +88,12 @@ int main(int argc, char *argv[])
// Install Unicode 6.1 supporting font
QFontDatabase::addApplicationFont("://DejaVuSans.ttf");
// Check whether we have an update waiting to be installed
#if AUTOUPDATE_ENABLED
if (AutoUpdater::isLocalUpdateReady())
AutoUpdater::installLocalUpdate(); ///< NORETURN
#endif
// Inter-process communication
IPC ipc;
ipc.registerEventHandler(&toxURIEventHandler);

View File

@ -196,16 +196,6 @@ void Widget::init()
addFriendForm = new AddFriendForm;
settingsWidget = new SettingsWidget();
// Check for updates
{
QString newVersion = AutoUpdater::getUpdateVersion();
if (!newVersion.isEmpty() && newVersion != GIT_VERSION)
{
qWarning() << "New update:"<<newVersion;
AutoUpdater::genUpdateDiff();
}
}
connect(core, &Core::connected, this, &Widget::onConnected);
connect(core, &Core::disconnected, this, &Widget::onDisconnected);
connect(core, &Core::failedToStart, this, &Widget::onFailedToStartCore);

28
updater/main.cpp Normal file
View File

@ -0,0 +1,28 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program 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.
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 COPYING file for more details.
*/
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}

5
updater/res.qrc Normal file
View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>res/qtox-256x256.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

254
updater/serialize.cpp Normal file
View File

@ -0,0 +1,254 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program 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.
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 COPYING file for more details.
*/
#include "serialize.h"
QByteArray doubleToData(double num)
{
union
{
char tab[8];
double n;
} castUnion;
//char n[8];
//*((double*) n) = num;
castUnion.n=num;
return QByteArray(castUnion.tab,8);
}
QByteArray floatToData(float num)
{
union
{
char tab[4];
float n;
} castUnion;
castUnion.n=num;
return QByteArray(castUnion.tab,4);
}
float dataToFloat(QByteArray data)
{
union
{
char tab[4];
float n;
} castUnion;
castUnion.tab[0]=data.data()[0];
castUnion.tab[1]=data.data()[1];
castUnion.tab[2]=data.data()[2];
castUnion.tab[3]=data.data()[3];
return castUnion.n;
}
// Converts a string into PNet string data
QByteArray stringToData(QString str)
{
QByteArray data(4,0);
// Write the size in a Uint of variable lenght (8-32 bits)
int i=0;
uint num1 = (uint)str.toUtf8().size();
while (num1 >= 0x80)
{
data[i] = (unsigned char)(num1 | 0x80); i++;
num1 = num1 >> 7;
}
data[i]=num1;
data.resize(i+1);
data+=str.toUtf8();
return data;
}
QString dataToString(QByteArray data)
{
// Variable UInt32
unsigned char num3;
int num = 0;
int num2 = 0;
int i=0;
do
{
num3 = data[i]; i++;
num |= (num3 & 0x7f) << num2;
num2 += 7;
} while ((num3 & 0x80) != 0);
unsigned int strlen = (uint) num;
if (!strlen)
return QString();
data = data.right(data.size()-i); // Remove the strlen
data.truncate(strlen);
return QString(data);
}
float dataToRangedSingle(float min, float max, int numberOfBits, QByteArray data)
{
uint endvalue=0;
uint value=0;
if (numberOfBits <= 8)
{
endvalue = (uchar)data[0];
goto done;
}
value = (uchar)data[0];
numberOfBits -= 8;
if (numberOfBits <= 8)
{
endvalue = (value | ((uint) ((uchar)data[1]) << 8));
goto done;
}
value |= (uint) (((uchar)data[1]) << 8);
numberOfBits -= 8;
if (numberOfBits <= 8)
{
uint num2 = (uint) (((uchar)data[2]) << 0x10);
endvalue = (value | num2);
goto done;
}
value |= (uint) (((uchar)data[2]) << 0x10);
numberOfBits -= 8;
endvalue = (value | ((uint) (((uchar)data[3]) << 0x18)));
goto done;
done:
float num = max - min;
int num2 = (((int) 1) << numberOfBits) - 1;
float num3 = endvalue;
float num4 = num3 / ((float) num2);
return (min + (num4 * num));
}
QByteArray rangedSingleToData(float value, float min, float max, int numberOfBits)
{
QByteArray data;
float num = max - min;
float num2 = (value - min) / num;
int num3 = (((int) 1) << numberOfBits) - 1;
uint source = num3 * num2;
if (numberOfBits <= 8)
{
data += (unsigned char)source;
return data;
}
data += (unsigned char)source;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
data += (unsigned char)source>>8;
return data;
}
data += (unsigned char)source>>8;
numberOfBits -= 8;
if (numberOfBits <= 8)
{
data += (unsigned char)source>>16;
return data;
}
data += (unsigned char)source>>16;
data += (unsigned char)source>>24;
return data;
}
uint8_t dataToUint8(QByteArray data)
{
return (uint8_t)data[0];
}
uint16_t dataToUint16(QByteArray data)
{
return ((uint16_t)(uint8_t)data[0])
+(((uint16_t)(uint8_t)data[1])<<8);
}
uint32_t dataToUint32(QByteArray data)
{
return ((uint32_t)(uint8_t)data[0])
+(((uint32_t)(uint8_t)data[1])<<8)
+(((uint32_t)(uint8_t)data[2])<<16)
+(((uint32_t)(uint8_t)data[3])<<24);
}
uint64_t dataToUint64(QByteArray data)
{
return ((uint64_t)(uint8_t)data[0])
+(((uint64_t)(uint8_t)data[1])<<8)
+(((uint64_t)(uint8_t)data[2])<<16)
+(((uint64_t)(uint8_t)data[3])<<24)
+(((uint64_t)(uint8_t)data[4])<<32)
+(((uint64_t)(uint8_t)data[5])<<40)
+(((uint64_t)(uint8_t)data[6])<<48)
+(((uint64_t)(uint8_t)data[7])<<56);
}
unsigned getVUint32Size(QByteArray data)
{
unsigned lensize=0;
{
unsigned char num3;
do {
num3 = data[lensize];
lensize++;
} while ((num3 & 0x80) != 0);
}
return lensize;
}
QByteArray uint8ToData(uint8_t num)
{
QByteArray data(1,0);
data[0] = (uint8_t)num;
return data;
}
QByteArray uint16ToData(uint16_t num)
{
QByteArray data(2,0);
data[0] = (uint8_t)(num & 0xFF);
data[1] = (uint8_t)((num>>8) & 0xFF);
return data;
}
QByteArray uint32ToData(uint32_t num)
{
QByteArray data(4,0);
data[0] = (uint8_t)(num & 0xFF);
data[1] = (uint8_t)((num>>8) & 0xFF);
data[2] = (uint8_t)((num>>16) & 0xFF);
data[3] = (uint8_t)((num>>24) & 0xFF);
return data;
}
QByteArray uint64ToData(uint64_t num)
{
QByteArray data(8,0);
data[0] = (uint8_t)(num & 0xFF);
data[1] = (uint8_t)((num>>8) & 0xFF);
data[2] = (uint8_t)((num>>16) & 0xFF);
data[3] = (uint8_t)((num>>24) & 0xFF);
data[4] = (uint8_t)((num>>32) & 0xFF);
data[5] = (uint8_t)((num>>40) & 0xFF);
data[6] = (uint8_t)((num>>48) & 0xFF);
data[7] = (uint8_t)((num>>56) & 0xFF);
return data;
}

45
updater/serialize.h Normal file
View File

@ -0,0 +1,45 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program 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.
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 COPYING file for more details.
*/
#ifndef SERIALIZE_H
#define SERIALIZE_H
#include <cstdint>
#include <QByteArray>
#include <QString>
/// Most of those functions are unsafe unless otherwise specified
/// Do not use them on untrusted data (e.g. check a signature first)
QByteArray doubleToData(double num);
QByteArray floatToData(float num);
float dataToFloat(QByteArray data);
QByteArray stringToData(QString str);
QString dataToString(QByteArray data);
float dataToRangedSingle(float min, float max, int numberOfBits, QByteArray data);
QByteArray rangedSingleToData(float value, float min, float max, int numberOfBits);
uint8_t dataToUint8(QByteArray data);
uint16_t dataToUint16(QByteArray data);
uint32_t dataToUint32(QByteArray data);
uint64_t dataToUint64(QByteArray data);
unsigned getVUint32Size(QByteArray data);
QByteArray uint8ToData(uint8_t num);
QByteArray uint16ToData(uint16_t num);
QByteArray uint32ToData(uint32_t num);
QByteArray uint64ToData(uint64_t num);
#endif // SERIALIZE_H

52
updater/settingsDir.cpp Normal file
View File

@ -0,0 +1,52 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program 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.
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 COPYING file for more details.
*/
#include "settingsDir.h"
#include <QFile>
#include <QSettings>
#include <QDir>
#include <QStandardPaths>
const QString FILENAME = "settings.ini";
QString getSettingsDirPath()
{
if (isToxPortableEnabled())
return ".";
#ifdef Q_OS_WIN
return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)
+ QDir::separator() + "AppData" + QDir::separator() + "Roaming" + QDir::separator() + "tox");
#else
return QDir::cleanPath(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QDir::separator() + "tox");
#endif
}
bool isToxPortableEnabled()
{
QFile portableSettings(FILENAME);
if (portableSettings.exists())
{
QSettings ps(FILENAME, QSettings::IniFormat);
ps.beginGroup("General");
return ps.value("makeToxPortable", false).toBool();
}
else
{
return false;
}
}

27
updater/settingsDir.h Normal file
View File

@ -0,0 +1,27 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program 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.
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 COPYING file for more details.
*/
#ifndef SETTINGSDIR_H
#define SETTINGSDIR_H
#include <QString>
QString getSettingsDirPath();
bool isToxPortableEnabled();
#endif // SETTINGSDIR_H

115
updater/update.cpp Normal file
View File

@ -0,0 +1,115 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program 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.
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 COPYING file for more details.
*/
#include "update.h"
#include "serialize.h"
#include <QFile>
#include <QDebug>
unsigned char key[crypto_sign_PUBLICKEYBYTES] =
{
0xa5, 0x80, 0xf3, 0xb7, 0xd0, 0x10, 0xc0, 0xf9, 0xd6, 0xcf, 0x48, 0x15, 0x99, 0x70, 0x92, 0x49,
0xf6, 0xe8, 0xe5, 0xe2, 0x6c, 0x73, 0x8c, 0x48, 0x25, 0xed, 0x01, 0x72, 0xf7, 0x6c, 0x17, 0x28
};
QByteArray getLocalFlist()
{
QByteArray flist;
QFile flistFile("flist");
if (!flistFile.open(QIODevice::ReadOnly))
{
qWarning() << "getLocalFlist: Can't open local flist";
return flist;
}
flist = flistFile.readAll();
flistFile.close();
return flist;
}
QList<UpdateFileMeta> genUpdateDiff(QList<UpdateFileMeta> updateFlist)
{
QList<UpdateFileMeta> diff;
QList<UpdateFileMeta> localFlist = parseFlist(getLocalFlist());
for (UpdateFileMeta file : updateFlist)
if (!localFlist.contains(file))
diff += file;
return diff;
}
QList<UpdateFileMeta> parseFlist(QByteArray flistData)
{
QList<UpdateFileMeta> flist;
if (flistData.isEmpty())
{
qWarning() << "AutoUpdater::parseflist: Empty data";
return flist;
}
// Check version
if (flistData[0] != '1')
{
qWarning() << "AutoUpdater: parseflist: Bad version "<<(uint8_t)flistData[0];
return flist;
}
flistData = flistData.mid(1);
// Check signature
if (flistData.size() < (int)(crypto_sign_BYTES))
{
qWarning() << "AutoUpdater::parseflist: Truncated data";
return flist;
}
else
{
QByteArray msgData = flistData.mid(crypto_sign_BYTES);
unsigned char* msg = (unsigned char*)msgData.data();
if (crypto_sign_verify_detached((unsigned char*)flistData.data(), msg, msgData.size(), key) != 0)
{
qCritical() << "AutoUpdater: parseflist: FORGED FLIST FILE";
return flist;
}
flistData = flistData.mid(crypto_sign_BYTES);
}
// Parse. We assume no errors handling needed since the signature is valid.
while (!flistData.isEmpty())
{
UpdateFileMeta newFile;
memcpy(newFile.sig, flistData.data(), crypto_sign_BYTES);
flistData = flistData.mid(crypto_sign_BYTES);
newFile.id = dataToString(flistData);
flistData = flistData.mid(newFile.id.size() + getVUint32Size(flistData));
newFile.installpath = dataToString(flistData);
flistData = flistData.mid(newFile.installpath.size() + getVUint32Size(flistData));
newFile.size = dataToUint64(flistData);
flistData = flistData.mid(8);
flist += newFile;
}
return flist;
}

55
updater/update.h Normal file
View File

@ -0,0 +1,55 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program 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.
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 COPYING file for more details.
*/
#ifndef UPDATE_H
#define UPDATE_H
#include <QByteArray>
#include <QString>
#include <sodium.h>
struct UpdateFileMeta
{
unsigned char sig[crypto_sign_BYTES]; ///< Signature of the file (ed25519)
QString id; ///< Unique id of the file
QString installpath; ///< Local path including the file name. May be relative to qtox-updater or absolute
uint64_t size; ///< Size in bytes of the file
bool operator==(const UpdateFileMeta& other)
{
return (size == other.size
&& id == other.id && installpath == other.installpath
&& memcmp(sig, other.sig, crypto_sign_BYTES) == 0);
}
};
struct UpdateFile
{
UpdateFileMeta metadata;
QByteArray data;
};
/// Gets the local flist. Returns an empty array on error
QByteArray getLocalFlist();
/// Parses and validates a flist file. Returns an empty list on error
QList<UpdateFileMeta> parseFlist(QByteArray flistData);
/// Generates a list of files we need to update
QList<UpdateFileMeta> genUpdateDiff(QList<UpdateFileMeta> updateFlist);
extern unsigned char key[crypto_sign_PUBLICKEYBYTES];
#endif // UPDATE_H

36
updater/updater.pro Normal file
View File

@ -0,0 +1,36 @@
#-------------------------------------------------
#
# Project created by QtCreator 2014-11-09T21:09:08
#
#-------------------------------------------------
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
TARGET = qtox-updater
TEMPLATE = app
CONFIG += c++11
SOURCES += main.cpp\
widget.cpp \
settingsDir.cpp \
update.cpp \
serialize.cpp
HEADERS += widget.h \
settingsDir.h \
update.h \
serialize.h
FORMS += widget.ui
RESOURCES += \
res.qrc
INCLUDEPATH += libs/include
RC_FILE = windows/updater.rc
LIBS += -L$$PWD/libs/lib/ -lsodium

156
updater/widget.cpp Normal file
View File

@ -0,0 +1,156 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program 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.
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 COPYING file for more details.
*/
#include "widget.h"
#include "ui_widget.h"
#include <QDir>
#include <QFile>
#include <QProcess>
#include <QMessageBox>
#include <QMetaObject>
#include "settingsDir.h"
#include "update.h"
#ifdef Q_OS_WIN
const bool supported = true;
const QString QTOX_PATH = "qtox.exe";
#else
const bool supported = false;
const QString QTOX_PATH;
#endif
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
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);
qApp->processEvents();
}
void Widget::fatalError(QString message)
{
QMessageBox::critical(this,tr("Error"), message+'\n'+tr("qTox will restart now."));
deleteUpdate();
startQToxAndExit();
}
void Widget::deleteUpdate()
{
QDir updateDir(getSettingsDirPath()+"/update/");
updateDir.removeRecursively();
}
void Widget::startQToxAndExit()
{
QProcess::startDetached(QTOX_PATH);
exit(0);
}
void Widget::update()
{
/// 1. Find and parse the update (0-5%)
// Check that the dir exists
QString updateDirStr = getSettingsDirPath()+"/update/";
QDir updateDir(updateDirStr);
if (!updateDir.exists())
fatalError(tr("No update found."));
// Check that we have a flist and that every file on the diff exists
QFile updateFlistFile(updateDirStr+"flist");
if (!updateFlistFile.open(QIODevice::ReadOnly))
fatalError(tr("The update is incomplete."));
QByteArray updateFlistData = updateFlistFile.readAll();
updateFlistFile.close();
setProgress(1);
QList<UpdateFileMeta> updateFlist = parseFlist(updateFlistData);
setProgress(2);
QList<UpdateFileMeta> diff = genUpdateDiff(updateFlist);
setProgress(4);
for (UpdateFileMeta fileMeta : diff)
if (!QFile::exists(updateDirStr+fileMeta.installpath))
fatalError(tr("The update is incomplete."));
setProgress(5);
/// 2. Check the update (5-50%)
float checkProgressStep = 45/diff.size();
float checkProgress = 5;
for (UpdateFileMeta fileMeta : diff)
{
UpdateFile file;
file.metadata = fileMeta;
QFile fileFile(updateDirStr+fileMeta.installpath);
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(),
file.data.size(), key) != 0)
fatalError(tr("Update files are corrupted."));
checkProgress += checkProgressStep;
setProgress(checkProgress);
}
setProgress(50);
/// 3. Install the update (50-95%)
float installProgressStep = 45/diff.size();
float installProgress = 50;
for (UpdateFileMeta fileMeta : diff)
{
QFile fileFile(updateDirStr+fileMeta.installpath);
if (!fileFile.copy(fileMeta.installpath))
fatalError(tr("Unable to copy the update's files."));
installProgress += installProgressStep;
setProgress(installProgress);
}
setProgress(95);
/// 4. Delete the update (95-100%)
deleteUpdate();
setProgress(100);
/// 5. Start qTox and exit
startQToxAndExit();
}

51
updater/widget.h Normal file
View File

@ -0,0 +1,51 @@
/*
Copyright (C) 2014 by Project Tox <https://tox.im>
This file is part of qTox, a Qt-based graphical interface for Tox.
This program 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.
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 COPYING file for more details.
*/
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
// Utilities
void setProgress(int value);
// Noreturn
void fatalError(QString message); ///< Calls deleteUpdate and startQToxAndExit
void deleteUpdate();
void startQToxAndExit();
public slots:
// Finds and applies the update
void update();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H

139
updater/widget.ui Normal file
View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Widget</class>
<widget class="QWidget" name="Widget">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>401</width>
<height>224</height>
</rect>
</property>
<property name="windowTitle">
<string>qTox Updater</string>
</property>
<property name="windowIcon">
<iconset resource="res.qrc">
<normaloff>:/res/qtox-256x256.png</normaloff>:/res/qtox-256x256.png</iconset>
</property>
<widget class="QLabel" name="label">
<property name="geometry">
<rect>
<x>0</x>
<y>13</y>
<width>191</width>
<height>191</height>
</rect>
</property>
<property name="text">
<string/>
</property>
<property name="pixmap">
<pixmap resource="res.qrc">:/res/qtox-256x256.png</pixmap>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
</widget>
<widget class="QProgressBar" name="progress">
<property name="geometry">
<rect>
<x>206</x>
<y>95</y>
<width>171</width>
<height>20</height>
</rect>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="value">
<number>0</number>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
</widget>
<widget class="QLabel" name="label_2">
<property name="geometry">
<rect>
<x>205</x>
<y>115</y>
<width>171</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>Updating qTox ...</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QLabel" name="label_3">
<property name="geometry">
<rect>
<x>201</x>
<y>170</y>
<width>181</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>&lt;a href=&quot;https://tox.im&quot;&gt;https://tox.im&lt;/a&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
<widget class="QLabel" name="label_4">
<property name="geometry">
<rect>
<x>200</x>
<y>183</y>
<width>181</width>
<height>20</height>
</rect>
</property>
<property name="text">
<string>&lt;a href=&quot;https://github.com/tux3/qtox&quot;&gt;https://github.com/tux3/qtox&lt;/a&gt;</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
<widget class="QLabel" name="label_5">
<property name="geometry">
<rect>
<x>195</x>
<y>32</y>
<width>191</width>
<height>31</height>
</rect>
</property>
<property name="font">
<font>
<pointsize>14</pointsize>
</font>
</property>
<property name="text">
<string>qTox Update</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
<include location="res.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges>
<requestedExecutionLevel
level="requireAdministrator"
uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

BIN
updater/windows/updater.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

@ -0,0 +1,2 @@
ID_ICON ICON DISCARDABLE "updater.ico"
1 24 "updater.exe.manifest"