diff --git a/.gitignore b/.gitignore index 3727c045d..84ee6540d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,5 @@ qrc_* Makefile qtox *.qm +build-*-Release +build-*-Debug diff --git a/qtox.pro b/qtox.pro index 61393fae3..2b0f79d19 100644 --- a/qtox.pro +++ b/qtox.pro @@ -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 { diff --git a/src/autoupdate.cpp b/src/autoupdate.cpp index 6e932f86d..f1ab56136 100644 --- a/src/autoupdate.cpp +++ b/src/autoupdate.cpp @@ -17,14 +17,25 @@ #include "src/autoupdate.h" #include "src/misc/serialize.h" +#include "src/misc/settings.h" #include #include #include +#include +#include +#include -#ifdef _WIN32 +#ifdef Q_OS_WIN +#include +#include +#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::genUpdateDiff() -{ - QList diff; - - // Updates only for supported platforms - if (platform.isEmpty()) - return diff; - - QList newFlist = getUpdateFlist(); - - return diff; -} - -QList AutoUpdater::parseflist(QByteArray flistData) +QList AutoUpdater::parseFlist(QByteArray flistData) { QList flist; @@ -148,8 +146,6 @@ QList AutoUpdater::parseflist(QByteArray flistData) // Parse. We assume no errors handling needed since the signature is valid. while (!flistData.isEmpty()) { - qDebug() << "Got "< AutoUpdater::parseflist(QByteArray flistData) newFile.size = dataToUint64(flistData); flistData = flistData.mid(8); - qDebug() << "AutoUpdater::parseflist: New file:"; - qDebug() << "- Id: "< AutoUpdater::getUpdateFlist() +QByteArray AutoUpdater::getUpdateFlist() { - QList flist; + QByteArray flist; QNetworkAccessManager *manager = new QNetworkAccessManager; QNetworkReply* reply = manager->get(QNetworkRequest(QUrl(flistURI))); @@ -194,10 +183,212 @@ QList 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::genUpdateDiff(QList updateFlist) +{ + QList diff; + QList 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: "<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 newFlist = parseFlist(newFlistData); + QList diff = genUpdateDiff(newFlist); + + qDebug() << "AutoUpdater: Need to update "< updateFlist = parseFlist(updateFlistData); + QList 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); +} diff --git a/src/autoupdate.h b/src/autoupdate.h index 30502c9bc..07424dbc7 100644 --- a/src/autoupdate.h +++ b/src/autoupdate.h @@ -22,7 +22,16 @@ #include #include +/// 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 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 parseflist(QByteArray flistData); - /// Get the update server's flist and parse it. Returns an empty list on error + static QList 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 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 genUpdateDiff(QList 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[]; }; diff --git a/src/main.cpp b/src/main.cpp index e014ea128..b737a699e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,7 @@ #include "src/ipc.h" #include "src/widget/toxuri.h" #include "src/widget/toxsave.h" +#include "src/autoupdate.h" #include #include #include @@ -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); diff --git a/src/widget/widget.cpp b/src/widget/widget.cpp index a3482ef14..28a30f448 100644 --- a/src/widget/widget.cpp +++ b/src/widget/widget.cpp @@ -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:"< + + 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 + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + Widget w; + w.show(); + + return a.exec(); +} diff --git a/updater/res.qrc b/updater/res.qrc new file mode 100644 index 000000000..bdcb8e3d3 --- /dev/null +++ b/updater/res.qrc @@ -0,0 +1,5 @@ + + + res/qtox-256x256.png + + diff --git a/updater/res/qtox-256x256.png b/updater/res/qtox-256x256.png new file mode 100644 index 000000000..ea5cefa74 Binary files /dev/null and b/updater/res/qtox-256x256.png differ diff --git a/updater/serialize.cpp b/updater/serialize.cpp new file mode 100644 index 000000000..d16f68f60 --- /dev/null +++ b/updater/serialize.cpp @@ -0,0 +1,254 @@ +/* + Copyright (C) 2014 by Project Tox + + 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; +} diff --git a/updater/serialize.h b/updater/serialize.h new file mode 100644 index 000000000..54747fe68 --- /dev/null +++ b/updater/serialize.h @@ -0,0 +1,45 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 +#include +#include + +/// 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 diff --git a/updater/settingsDir.cpp b/updater/settingsDir.cpp new file mode 100644 index 000000000..2a5581b44 --- /dev/null +++ b/updater/settingsDir.cpp @@ -0,0 +1,52 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 +#include +#include +#include + +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; + } +} diff --git a/updater/settingsDir.h b/updater/settingsDir.h new file mode 100644 index 000000000..b695fe29f --- /dev/null +++ b/updater/settingsDir.h @@ -0,0 +1,27 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 getSettingsDirPath(); + +bool isToxPortableEnabled(); + +#endif // SETTINGSDIR_H diff --git a/updater/update.cpp b/updater/update.cpp new file mode 100644 index 000000000..a2c8e7da4 --- /dev/null +++ b/updater/update.cpp @@ -0,0 +1,115 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 +#include + +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 genUpdateDiff(QList updateFlist) +{ + QList diff; + QList localFlist = parseFlist(getLocalFlist()); + + for (UpdateFileMeta file : updateFlist) + if (!localFlist.contains(file)) + diff += file; + + return diff; +} + +QList parseFlist(QByteArray flistData) +{ + QList 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; +} diff --git a/updater/update.h b/updater/update.h new file mode 100644 index 000000000..aa2ce0c7e --- /dev/null +++ b/updater/update.h @@ -0,0 +1,55 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 +#include +#include + +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 parseFlist(QByteArray flistData); +/// Generates a list of files we need to update +QList genUpdateDiff(QList updateFlist); + +extern unsigned char key[crypto_sign_PUBLICKEYBYTES]; + +#endif // UPDATE_H diff --git a/updater/updater.pro b/updater/updater.pro new file mode 100644 index 000000000..e67e149c6 --- /dev/null +++ b/updater/updater.pro @@ -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 diff --git a/updater/widget.cpp b/updater/widget.cpp new file mode 100644 index 000000000..a87813204 --- /dev/null +++ b/updater/widget.cpp @@ -0,0 +1,156 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 +#include +#include +#include +#include + +#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 updateFlist = parseFlist(updateFlistData); + setProgress(2); + QList 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(); +} diff --git a/updater/widget.h b/updater/widget.h new file mode 100644 index 000000000..c757fa7a0 --- /dev/null +++ b/updater/widget.h @@ -0,0 +1,51 @@ +/* + Copyright (C) 2014 by Project Tox + + 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 + +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 diff --git a/updater/widget.ui b/updater/widget.ui new file mode 100644 index 000000000..25eec0c22 --- /dev/null +++ b/updater/widget.ui @@ -0,0 +1,139 @@ + + + Widget + + + + 0 + 0 + 401 + 224 + + + + qTox Updater + + + + :/res/qtox-256x256.png:/res/qtox-256x256.png + + + + + 0 + 13 + 191 + 191 + + + + + + + :/res/qtox-256x256.png + + + true + + + + + + 206 + 95 + 171 + 20 + + + + 0 + + + 0 + + + Qt::AlignCenter + + + false + + + + + + 205 + 115 + 171 + 20 + + + + Updating qTox ... + + + Qt::AlignCenter + + + + + + 201 + 170 + 181 + 20 + + + + <a href="https://tox.im">https://tox.im</a> + + + Qt::AlignCenter + + + true + + + + + + 200 + 183 + 181 + 20 + + + + <a href="https://github.com/tux3/qtox">https://github.com/tux3/qtox</a> + + + Qt::AlignCenter + + + + + + 195 + 32 + 191 + 31 + + + + + 14 + + + + qTox Update + + + Qt::AlignCenter + + + + + + + + + diff --git a/updater/windows/updater.exe.manifest b/updater/windows/updater.exe.manifest new file mode 100644 index 000000000..626d284d9 --- /dev/null +++ b/updater/windows/updater.exe.manifest @@ -0,0 +1,12 @@ + + + + + + + + + + \ No newline at end of file diff --git a/updater/windows/updater.ico b/updater/windows/updater.ico new file mode 100644 index 000000000..b322a59aa Binary files /dev/null and b/updater/windows/updater.ico differ diff --git a/updater/windows/updater.rc b/updater/windows/updater.rc new file mode 100644 index 000000000..e47abcc22 --- /dev/null +++ b/updater/windows/updater.rc @@ -0,0 +1,2 @@ +ID_ICON ICON DISCARDABLE "updater.ico" +1 24 "updater.exe.manifest" \ No newline at end of file