X-Git-Url: http://git.osdn.net/view?a=blobdiff_plain;f=src%2FUpdateChecker.cpp;h=bff5814dd52dec592f2583da71250a78e60763bc;hb=a2e57f2d45097f395d400ca088e732da2742db44;hp=cff804fcbc8d59612313e1fc56746d17fa6aab86;hpb=b012aa0dbb604a06afc4b3827068bf8b6d9e9735;p=mutilities%2FMUtilities.git diff --git a/src/UpdateChecker.cpp b/src/UpdateChecker.cpp index cff804f..bff5814 100644 --- a/src/UpdateChecker.cpp +++ b/src/UpdateChecker.cpp @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// // MuldeR's Utilities for Qt -// Copyright (C) 2004-2014 LoRd_MuldeR +// Copyright (C) 2004-2018 LoRd_MuldeR // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public @@ -27,165 +27,94 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include -using namespace MUtils; +#include "Mirrors.h" /////////////////////////////////////////////////////////////////////////////// // CONSTANTS /////////////////////////////////////////////////////////////////////////////// -static const char *header_id = "!Update"; -static const char *section_id = "LameXP"; +static const char *HEADER_ID = "!Update"; -static const char *mirror_url_postfix[] = +static const char *MIRROR_URL_POSTFIX[] = { "update.ver", "update_beta.ver", NULL }; -static const char *update_mirrors_prim[] = -{ - "http://muldersoft.com/", - "http://mulder.bplaced.net/", - "http://mulder.cwsurf.de/", - "http://mulder.6te.net/", - "http://mulder.webuda.com/", - "http://mulder.byethost13.com/", - "http://muldersoft.kilu.de/", - "http://mulder.pe.hu/", - "http://muldersoft.zxq.net/", - "http://lamexp.sourceforge.net/", - "http://lordmulder.github.io/LameXP/", - "http://lord_mulder.bitbucket.org/", - "http://www.tricksoft.de/", - NULL -}; +static const int MIN_CONNSCORE = 5; +static const int QUICK_MIRRORS = 3; +static const int MAX_CONN_TIMEOUT = 16000; +static const int DOWNLOAD_TIMEOUT = 30000; -static const char *update_mirrors_back[] = -{ - "http://mplayer.savedonthe.net/", - NULL -}; +static const int VERSION_INFO_EXPIRES_MONTHS = 6; +static char *USER_AGENT_STR = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0"; /*use something innocuous*/ + +#define CHECK_CANCELLED() do \ +{ \ + if(MUTILS_BOOLIFY(m_cancelled)) \ + { \ + m_success.fetchAndStoreOrdered(0); \ + log("", "Update check has been cancelled by user!"); \ + setProgress(m_maxProgress); \ + setStatus(UpdateStatus_CancelledByUser); \ + return; \ + } \ +} \ +while(0) + +#define LOG_MESSAGE_HELPER(X) do \ +{ \ + if (!(X).isNull()) \ + { \ + emit messageLogged((X)); \ + } \ +} \ +while(0) + +#define STRICMP(X,Y) ((X).compare((Y), Qt::CaseInsensitive) == 0) -static const char *known_hosts[] = //Taken form: http://www.alexa.com/topsites !!! -{ - "http://www.163.com/", - "http://www.7-zip.org/", - "http://www.ac3filter.net/", - "http://www.amazon.com/", - "http://antergos.com/", - "http://www.aol.com/", - "http://www.apache.org/", - "http://www.apple.com/", - "http://www.adobe.com/", - "http://www.avidemux.org/", - "http://www.babylon.com/", - "http://www.baidu.com/", - "http://bandcamp.com/", - "http://www.bbc.co.uk/", - "http://www.berlios.de/", - "http://www.bing.com/", - "http://www.cnet.com/", - "http://cnzz.com/", - "http://www.codeplex.com/", - "http://qt.digia.com/", - "http://www.ebay.com/", - "http://www.equation.com/", - "http://fc2.com/", - "http://fedoraproject.org/", - "http://www.ffmpeg.org/", - "http://blog.flickr.net/en", - "http://free-codecs.com/", - "http://blog.gitorious.org/", - "http://git-scm.com/", - "http://www.gmx.net/", - "http://www.gnome.org/", - "http://www.gnu.org/", - "http://go.com/", - "http://code.google.com/", - "http://www.heise.de/", - "http://www.huffingtonpost.co.uk/", - "http://www.iana.org/", - "http://www.imdb.com/", - "http://www.imgburn.com/", - "http://imgur.com/", - "http://en.jd.com/", - "http://kannmanumdieuhrzeitschonnbierchentrinken.de/", - "http://mirrors.kernel.org/", - "http://lame.sourceforge.net/", - "http://www.libav.org/", - "http://www.linkedin.com/about-us", - "http://www.linuxmint.com/", - "http://www.livedoor.com/", - "http://www.livejournal.com/", - "http://go.mail.ru/", - "http://www.mediafire.com/", - "http://ftp.mozilla.org/", - "http://mplayerhq.hu/", - "http://www.msn.com/?st=1", - "http://oss.netfarm.it/", - "http://www.nytimes.com/", - "http://www.opera.com/", - "http://pastie.org/", - "http://www.portablefreeware.com/", - "http://qt-project.org/", - "http://www.quakelive.com/", - "http://www.seamonkey-project.org/", - "http://www.sina.com.cn/", - "http://www.sohu.com/", - "http://www.soso.com/", - "http://sourceforge.net/", - "http://www.spiegel.de/", - "http://tdm-gcc.tdragon.net/", - "http://www.tdrsmusic.com/", - "http://www.ubuntu.com/", - "http://status.twitter.com/", - "http://www.uol.com.br/", - "http://www.videohelp.com/", - "http://www.videolan.org/", - "http://virtualdub.org/", - "http://blog.virustotal.com/", - "http://weibo.com/login.php", - "http://www.wikipedia.org/", - "http://www.winamp.com/", - "http://wordpress.com/about/", - "http://xiph.org/", - "http://us.mail.yahoo.com/", - "http://www.yandex.ru/", - "http://www.youtube.com/yt/about/", - "http://www.zedo.com/", - "http://ffmpeg.zeranoe.com/", - NULL -}; +//////////////////////////////////////////////////////////// +// Helper Functions +//////////////////////////////////////////////////////////// -static const int MIN_CONNSCORE = 8; -static const int VERSION_INFO_EXPIRES_MONTHS = 6; -static char *USER_AGENT_STR = "Mozilla/5.0 (X11; Linux i686; rv:7.0.1) Gecko/20111106 IceCat/7.0.1"; +static QStringList buildRandomList(const char *const values[]) +{ + QStringList list; + for (int index = 0; values[index]; index++) + { + const int pos = MUtils::next_rand_u32() % (index + 1); + list.insert(pos, QString::fromLatin1(values[index])); + } + return list; +} -//Helper function -static int getMaxProgress(void) +static const QHash *initEnvVars(void) { - int counter = MIN_CONNSCORE + 2; - for(int i = 0; update_mirrors_prim[i]; i++) counter++; - for(int i = 0; update_mirrors_back[i]; i++) counter++; - return counter; + QHash *const environment = new QHash(); + environment->insert(QLatin1String("CURL_HOME"), QDir::toNativeSeparators(MUtils::temp_folder())); + return environment; } //////////////////////////////////////////////////////////// // Update Info Class //////////////////////////////////////////////////////////// -UpdateCheckerInfo::UpdateCheckerInfo(void) +MUtils::UpdateCheckerInfo::UpdateCheckerInfo(void) { resetInfo(); } -void UpdateCheckerInfo::resetInfo(void) +void MUtils::UpdateCheckerInfo::resetInfo(void) { m_buildNo = 0; m_buildDate.setDate(1900, 1, 1); @@ -193,97 +122,131 @@ void UpdateCheckerInfo::resetInfo(void) m_downloadAddress.clear(); m_downloadFilename.clear(); m_downloadFilecode.clear(); + m_downloadChecksum.clear(); +} + +bool MUtils::UpdateCheckerInfo::isComplete(void) +{ + return (this->m_buildNo > 0) && + (this->m_buildDate.year() >= 2010) && + (!this->m_downloadSite.isEmpty()) && + (!this->m_downloadAddress.isEmpty()) && + (!this->m_downloadFilename.isEmpty()) && + (!this->m_downloadFilecode.isEmpty()) && + (!this->m_downloadChecksum.isEmpty()); } //////////////////////////////////////////////////////////// // Constructor & Destructor //////////////////////////////////////////////////////////// -UpdateChecker::UpdateChecker(const QString &binWGet, const QString &binGnuPG, const QString &binKeys, const quint32 &installedBuildNo, const bool betaUpdates, const bool testMode) +MUtils::UpdateChecker::UpdateChecker(const QString &binCurl, const QString &binGnuPG, const QString &binKeys, const QString &applicationId, const quint32 &installedBuildNo, const bool betaUpdates, const bool testMode) : m_updateInfo(new UpdateCheckerInfo()), - m_binaryWGet(binWGet), + m_binaryCurl(binCurl), m_binaryGnuPG(binGnuPG), m_binaryKeys(binKeys), + m_applicationId(applicationId), m_installedBuildNo(installedBuildNo), m_betaUpdates(betaUpdates), m_testMode(testMode), - m_maxProgress(getMaxProgress()) + m_maxProgress(MIN_CONNSCORE + 5), + m_environment(initEnvVars()) { - m_success = false; m_status = UpdateStatus_NotStartedYet; m_progress = 0; - if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty()) + if(m_binaryCurl.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty()) { MUTILS_THROW("Tools not initialized correctly!"); } } -UpdateChecker::~UpdateChecker(void) +MUtils::UpdateChecker::~UpdateChecker(void) { - delete m_updateInfo; +} + +//////////////////////////////////////////////////////////// +// Public slots +//////////////////////////////////////////////////////////// + +void MUtils::UpdateChecker::start(Priority priority) +{ + m_success.fetchAndStoreOrdered(0); + m_cancelled.fetchAndStoreOrdered(0); + QThread::start(priority); } //////////////////////////////////////////////////////////// // Protected functions //////////////////////////////////////////////////////////// -void UpdateChecker::run(void) +void MUtils::UpdateChecker::run(void) { qDebug("Update checker thread started!"); - MUTILS_EXCEPTION_HANDLER(m_testMode ? testKnownHosts() : checkForUpdates()); + MUTILS_EXCEPTION_HANDLER(m_testMode ? testMirrorsList() : checkForUpdates()); qDebug("Update checker thread completed."); } -void UpdateChecker::checkForUpdates(void) +void MUtils::UpdateChecker::checkForUpdates(void) { // ----- Initialization ----- // - m_success = false; m_updateInfo->resetInfo(); - seed_rand(); setProgress(0); // ----- Test Internet Connection ----- // - int connectionScore = 0; - int maxConnectTries = (3 * MIN_CONNSCORE) / 2; - - log("Checking internet connection..."); + log("Checking your Internet connection...", ""); setStatus(UpdateStatus_CheckingConnection); - const int networkStatus = lamexp_network_status(); - if(networkStatus == lamexp_network_non) + const int networkStatus = OS::network_status(); + if(networkStatus == OS::NETWORK_TYPE_NON) { - log("", "Operating system reports that the computer is currently offline !!!"); - setProgress(m_maxProgress); - setStatus(UpdateStatus_ErrorNoConnection); - return; + if (!MUtils::OS::arguments().contains("ignore-network-status")) + { + log("Operating system reports that the computer is currently offline !!!"); + setProgress(m_maxProgress); + setStatus(UpdateStatus_ErrorNoConnection); + return; + } } + msleep(333); setProgress(1); // ----- Test Known Hosts Connectivity ----- // - QStringList hostList; - for(int i = 0; known_hosts[i]; i++) - { - hostList << QString::fromLatin1(known_hosts[i]); - } + int connectionScore = 0; + QStringList mirrorList = buildRandomList(known_hosts); - while(!(hostList.isEmpty() || (connectionScore >= MIN_CONNSCORE) || (maxConnectTries < 1))) + for(int connectionTimout = 1000; connectionTimout <= MAX_CONN_TIMEOUT; connectionTimout *= 2) { - switch(tryContactHost(hostList.takeAt(next_rand32() % hostList.count()))) + QElapsedTimer elapsedTimer; + elapsedTimer.start(); + const int globalTimout = 2 * MIN_CONNSCORE * connectionTimout; + while (!elapsedTimer.hasExpired(globalTimout)) { - case 01: connectionScore += 1; break; - case 02: connectionScore += 2; break; - default: maxConnectTries -= 1; break; + const QString hostName = mirrorList.takeFirst(); + if (tryContactHost(hostName, connectionTimout)) + { + setProgress(1 + (connectionScore += 1)); + elapsedTimer.restart(); + if (connectionScore >= MIN_CONNSCORE) + { + goto endLoop; /*success*/ + } + } + else + { + mirrorList.append(hostName); /*re-schedule*/ + } + CHECK_CANCELLED(); + msleep(1); } - setProgress(qBound(1, connectionScore + 1, MIN_CONNSCORE + 1)); - msleep(64); } +endLoop: if(connectionScore < MIN_CONNSCORE) { log("", "Connectivity test has failed: Internet connection appears to be broken!"); @@ -292,53 +255,37 @@ void UpdateChecker::checkForUpdates(void) return; } - // ----- Build Mirror List ----- // + // ----- Fetch Update Info From Server ----- // - log("", "----", "", "Checking for updates online..."); + log("----", "", "Internet connection is operational, checking for updates online..."); setStatus(UpdateStatus_FetchingUpdates); - QStringList mirrorList; - for(int index = 0; update_mirrors_prim[index]; index++) - { - mirrorList << QString::fromLatin1(update_mirrors_prim[index]); - } - - if(const int len = mirrorList.count()) - { - const int rounds = len * 1097; - for(int i = 0; i < rounds; i++) - { - mirrorList.swap(i % len, next_rand32() % len); - } - } - - for(int index = 0; update_mirrors_back[index]; index++) - { - mirrorList << QString::fromLatin1(update_mirrors_back[index]); - } - - // ----- Fetch Update Info From Server ----- // + int mirrorCount = 0; + mirrorList = buildRandomList(update_mirrors); while(!mirrorList.isEmpty()) { - QString currentMirror = mirrorList.takeFirst(); - setProgress(m_progress + 1); - if(!m_success) + const QString currentMirror = mirrorList.takeFirst(); + const bool isQuick = (mirrorCount++ < QUICK_MIRRORS); + if(tryUpdateMirror(m_updateInfo.data(), currentMirror, isQuick)) { - if(tryUpdateMirror(m_updateInfo, currentMirror)) - { - m_success = true; - } + m_success.ref(); /*success*/ + break; } - else + if (isQuick) { - msleep(64); + mirrorList.append(currentMirror); /*re-schedule*/ } + CHECK_CANCELLED(); + msleep(1); } - - setProgress(m_maxProgress); - if(m_success) + msleep(333); + setProgress(MIN_CONNSCORE + 5); + + // ----- Generate final result ----- // + + if(MUTILS_BOOLIFY(m_success)) { if(m_updateInfo->m_buildNo > m_installedBuildNo) { @@ -359,38 +306,63 @@ void UpdateChecker::checkForUpdates(void) } } -void UpdateChecker::testKnownHosts(void) +void MUtils::UpdateChecker::testMirrorsList(void) { - QStringList hostList; - for(int i = 0; known_hosts[i]; i++) + QStringList mirrorList; + for(int i = 0; update_mirrors[i]; i++) { - hostList << QString::fromLatin1(known_hosts[i]); + mirrorList << QString::fromLatin1(update_mirrors[i]); } - qDebug("\n[Known Hosts]"); - log("Testing all known hosts...", "", "---"); + // ----- Test update mirrors ----- // + + qDebug("\n[Mirror Sites]"); + log("Testing all known mirror sites...", "", "---"); - int hostCount = hostList.count(); - while(!hostList.isEmpty()) + UpdateCheckerInfo updateInfo; + while (!mirrorList.isEmpty()) { - QString currentHost = hostList.takeFirst(); - qDebug("Testing: %s", currentHost.toLatin1().constData()); - log("", "Testing:", currentHost, ""); - QString outFile = QString("%1/%2.htm").arg(temp_folder(), rand_str()); - bool httpOk = false; - if(!getFile(currentHost, outFile, 0, &httpOk)) + const QString currentMirror = mirrorList.takeFirst(); + bool success = false; + qDebug("Testing: %s", MUTILS_L1STR(currentMirror)); + log("", "Testing:", currentMirror, ""); + for (quint8 attempt = 0; attempt < 3; ++attempt) { - if(httpOk) - { - qWarning("\nConnectivity test was SLOW on the following site:\n%s\n", currentHost.toLatin1().constData()); - } - else + updateInfo.resetInfo(); + if (tryUpdateMirror(&updateInfo, currentMirror, (!attempt))) { - qWarning("\nConnectivity test FAILED on the following site:\n%s\n", currentHost.toLatin1().constData()); + success = true; + break; } } + if (!success) + { + qWarning("\nUpdate mirror seems to be unavailable:\n%s\n", MUTILS_L1STR(currentMirror)); + } log("", "---"); - QFile::remove(outFile); + } + + // ----- Test known hosts ----- // + + QStringList knownHostList; + for (int i = 0; known_hosts[i]; i++) + { + knownHostList << QString::fromLatin1(known_hosts[i]); + } + + qDebug("\n[Known Hosts]"); + log("Testing all known hosts...", "", "---"); + + while(!knownHostList.isEmpty()) + { + const QString currentHost = knownHostList.takeFirst(); + qDebug("Testing: %s", MUTILS_L1STR(currentHost)); + log(QLatin1String(""), "Testing:", currentHost, ""); + if (!tryContactHost(currentHost, DOWNLOAD_TIMEOUT)) + { + qWarning("\nConnectivity test FAILED on the following host:\n%s\n", MUTILS_L1STR(currentHost)); + } + log("---"); } } @@ -398,7 +370,7 @@ void UpdateChecker::testKnownHosts(void) // PRIVATE FUNCTIONS //////////////////////////////////////////////////////////// -void UpdateChecker::setStatus(const int status) +void MUtils::UpdateChecker::setStatus(const int status) { if(m_status != status) { @@ -407,323 +379,410 @@ void UpdateChecker::setStatus(const int status) } } -void UpdateChecker::setProgress(const int progress) +void MUtils::UpdateChecker::setProgress(const int progress) { - if(m_progress != progress) + const int value = qBound(0, progress, m_maxProgress); + if(m_progress != value) { - m_progress = progress; - emit progressChanged(progress); + emit progressChanged(m_progress = value); } } -void UpdateChecker::log(const QString &str1, const QString &str2, const QString &str3, const QString &str4) +void MUtils::UpdateChecker::log(const QString &str1, const QString &str2, const QString &str3, const QString &str4) { - if(!str1.isNull()) emit messageLogged(str1); - if(!str2.isNull()) emit messageLogged(str2); - if(!str3.isNull()) emit messageLogged(str3); - if(!str4.isNull()) emit messageLogged(str4); + LOG_MESSAGE_HELPER(str1); + LOG_MESSAGE_HELPER(str2); + LOG_MESSAGE_HELPER(str3); + LOG_MESSAGE_HELPER(str4); } -int UpdateChecker::tryContactHost(const QString &url) +bool MUtils::UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString &url, const bool &quick) { - int result = -1; bool httpOkay = false; - const QString outFile = QString("%1/%2.htm").arg(temp_folder(), rand_str()); - log("", "Testing host:", url); + bool success = false; + log("", "Trying update mirror:", url, ""); - if(getFile(url, outFile, 0, &httpOkay)) + if (quick) + { + setProgress(MIN_CONNSCORE + 1); + if (!tryContactHost(QUrl(url).host(), (MAX_CONN_TIMEOUT / 8))) { - log("Connection to host was established successfully."); - result = 2; + log("", "Mirror is too slow, skipping!"); + return false; } - else + } + + const QString randPart = next_rand_str(); + const QString outFileVers = QString("%1/%2.ver").arg(temp_folder(), randPart); + const QString outFileSign = QString("%1/%2.sig").arg(temp_folder(), randPart); + + if (!getUpdateInfo(url, outFileVers, outFileSign)) + { + log("", "Oops: Download of update information has failed!"); + goto cleanUp; + } + + log("Download completed, verifying signature:", ""); + setProgress(MIN_CONNSCORE + 4); + if (!checkSignature(outFileVers, outFileSign)) + { + log("", "Bad signature detected, take care !!!"); + goto cleanUp; + } + + log("", "Signature is valid, parsing update information:", ""); + success = parseVersionInfo(outFileVers, updateInfo); + +cleanUp: + QFile::remove(outFileVers); + QFile::remove(outFileSign); + return success; +} + +bool MUtils::UpdateChecker::getUpdateInfo(const QString &url, const QString &outFileVers, const QString &outFileSign) +{ + log("Downloading update information:", ""); + setProgress(MIN_CONNSCORE + 2); + if(getFile(QUrl(QString("%1%2").arg(url, MIRROR_URL_POSTFIX[m_betaUpdates ? 1 : 0])), outFileVers)) + { + if (!m_cancelled) { - if(httpOkay) - { - log("Connection to host timed out after HTTP OK was received."); - result = 1; - } - else + log( "Downloading signature file:", ""); + setProgress(MIN_CONNSCORE + 3); + if (getFile(QUrl(QString("%1%2.sig2").arg(url, MIRROR_URL_POSTFIX[m_betaUpdates ? 1 : 0])), outFileSign)) { - log("Connection failed: The host could not be reached!"); - result = 0; + return true; /*completed*/ } } - - QFile::remove(outFile); - return result; + } + return false; } -bool UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString &url) +//---------------------------------------------------------- +// PARSE UPDATE INFO +//---------------------------------------------------------- + +bool MUtils::UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *const updateInfo) { - bool success = false; - log("", "Trying mirror:", url); + updateInfo->resetInfo(); - const QString randPart = rand_str(); - const QString outFileVers = QString("%1/%2.ver").arg(temp_folder(), randPart); - const QString outFileSign = QString("%1/%2.sig").arg(temp_folder(), randPart); + QFile data(file); + if(!data.open(QIODevice::ReadOnly)) + { + qWarning("Cannot open update info file for reading!"); + return false; + } + + QDate updateInfoDate; + int sectionId = 0; + QRegExp regex_sec("^\\[(.+)\\]$"), regex_val("^([^=]+)=(.+)$"); - if(getUpdateInfo(url, outFileVers, outFileSign)) + while(!data.atEnd()) { - log("", "Download okay, checking signature:"); - if(checkSignature(outFileVers, outFileSign)) + QString line = QString::fromLatin1(data.readLine()).trimmed(); + if (regex_sec.indexIn(line) >= 0) { - log("", "Signature okay, parsing info:"); - success = parseVersionInfo(outFileVers, updateInfo); + sectionId = parseSectionHeaderStr(regex_sec.cap(1).trimmed()); + continue; } - else + if (regex_val.indexIn(line) >= 0) { - log("", "Bad signature, take care!"); + const QString key = regex_val.cap(1).trimmed(); + const QString val = regex_val.cap(2).trimmed(); + switch (sectionId) + { + case 1: + parseHeaderValue(key, val, updateInfoDate); + break; + case 2: + parseUpdateInfoValue(key, val, updateInfo); + break; + } + } + } + + if(updateInfoDate.isValid()) + { + const QDate expiredDate = updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS); + if (expiredDate < OS::current_date()) + { + log(QString("WARNING: Update information has expired at %1!").arg(expiredDate.toString(Qt::ISODate))); + goto cleanUp; } } else { - log("", "Download has failed!"); + log("WARNING: Timestamp is missing from update information header!"); + goto cleanUp; } - - QFile::remove(outFileVers); - QFile::remove(outFileSign); - return success; + if(!updateInfo->isComplete()) + { + log("WARNING: Update information is incomplete!"); + goto cleanUp; + } + + log("", "Success: Update information is complete."); + return true; /*success*/ + +cleanUp: + updateInfo->resetInfo(); + return false; } -bool UpdateChecker::getUpdateInfo(const QString &url, const QString &outFileVers, const QString &outFileSign) +int MUtils::UpdateChecker::parseSectionHeaderStr(const QString &name) { - log("", "Downloading update info:"); - if(!getFile(QString("%1%2" ).arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileVers)) + log(QString("Sec: [%1]").arg(name)); + + if (STRICMP(name, HEADER_ID)) { - return false; + return 1; } - - log("", "Downloading signature:"); - if(!getFile(QString("%1%2.sig").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileSign)) + if (STRICMP(name, m_applicationId)) { - return false; + return 2; } - return true; + //Unknonw section encountered! + return 0; } -bool UpdateChecker::getFile(const QString &url, const QString &outFile, unsigned int maxRedir, bool *httpOk) +void MUtils::UpdateChecker::parseHeaderValue(const QString &key, const QString &val, QDate &updateInfoDate) { - QFileInfo output(outFile); - output.setCaching(false); - if(httpOk) *httpOk = false; + log(QString("Hdr: \"%1\"=\"%2\"").arg(key, val)); - if(output.exists()) + if (STRICMP(key, "TimestampCreated")) { - QFile::remove(output.canonicalFilePath()); - if(output.exists()) + const QDate temp = QDate::fromString(val, Qt::ISODate); + if (temp.isValid()) { - return false; + updateInfoDate = temp; } + return; } - QProcess process; - init_process(process, output.absolutePath()); - - QStringList args; - args << "-T" << "15" << "--no-cache" << "--no-dns-cache" << QString().sprintf("--max-redirect=%u", maxRedir); - args << QString("--referer=%1://%2/").arg(QUrl(url).scheme(), QUrl(url).host()) << "-U" << USER_AGENT_STR; - args << "-O" << output.fileName() << url; - - QEventLoop loop; - connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); - connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit())); - connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit())); + //Unknown entry encountered! + qWarning("Unknown header value: %s", MUTILS_L1STR(key)); +} - QTimer timer; - timer.setSingleShot(true); - timer.setInterval(25000); - connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); +void MUtils::UpdateChecker::parseUpdateInfoValue(const QString &key, const QString &val, UpdateCheckerInfo *const updateInfo) +{ + log(QString("Val: \"%1\"=\"%2\"").arg(key, val)); - const QRegExp httpResponseOK("200 OK$"); - - process.start(m_binaryWGet, args); - - if(!process.waitForStarted()) + if (STRICMP(key, "BuildNo")) { - return false; + bool ok = false; + const unsigned int temp = val.toUInt(&ok); + if (ok) + { + updateInfo->m_buildNo = temp; + } + return; } - - timer.start(); - - while(process.state() != QProcess::NotRunning) + if (STRICMP(key, "BuildDate")) { - loop.exec(); - const bool bTimeOut = (!timer.isActive()); - while(process.canReadLine()) + const QDate temp = QDate::fromString(val, Qt::ISODate); + if (temp.isValid()) { - QString line = QString::fromLatin1(process.readLine()).simplified(); - if(line.contains(httpResponseOK)) - { - line.append(" [OK]"); - if(httpOk) *httpOk = true; - } - log(line); + updateInfo->m_buildDate = temp; } - if(bTimeOut) + return; + } + if (STRICMP(key, "DownloadSite")) + { + updateInfo->m_downloadSite = val; + return; + } + if (STRICMP(key, "DownloadAddress")) + { + updateInfo->m_downloadAddress = val; + return; + } + if (STRICMP(key, "DownloadFilename")) + { + updateInfo->m_downloadFilename = val; + return; + } + if (STRICMP(key, "DownloadFilecode")) + { + updateInfo->m_downloadFilecode = val; + return; + } + if (STRICMP(key, "DownloadChecksum")) + { + updateInfo->m_downloadChecksum = val; + return; + } + + //Unknown entry encountered! + qWarning("Unknown update value: %s", MUTILS_L1STR(key)); +} + +//---------------------------------------------------------- +// EXTERNAL TOOLS +//---------------------------------------------------------- + +bool MUtils::UpdateChecker::getFile(const QUrl &url, const QString &outFile, const unsigned int maxRedir) +{ + QFileInfo output(outFile); + output.setCaching(false); + + if (output.exists()) + { + QFile::remove(output.canonicalFilePath()); + if (output.exists()) { - qWarning("WGet process timed out <-- killing!"); - process.kill(); - process.waitForFinished(); - log("!!! TIMEOUT !!!"); + qWarning("Existing output file could not be found!"); return false; } } - timer.stop(); - timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + QStringList args(QLatin1String("-vsSNqkfL")); + args << "-m" << QString::number(DOWNLOAD_TIMEOUT / 1000); + args << "--max-redirs" << QString::number(maxRedir); + args << "-A" << USER_AGENT_STR; + args << "-e" << QString("%1://%2/;auto").arg(url.scheme(), url.host()); + args << "-o" << output.fileName() << url.toString(); + + return execCurl(args, output.absolutePath(), DOWNLOAD_TIMEOUT); +} + +bool MUtils::UpdateChecker::tryContactHost(const QString &hostname, const int &timeoutMsec) +{ + log(QString("Connecting to host: %1").arg(hostname), ""); - log(QString().sprintf("Exited with code %d", process.exitCode())); - return (process.exitCode() == 0) && output.exists() && output.isFile(); + QStringList args(QLatin1String("-vsSNqkI")); + args << "-m" << QString::number(qMax(1, timeoutMsec / 1000)); + args << "-A" << USER_AGENT_STR; + args << "-o" << OS::null_device() << QString("http://%1/").arg(hostname); + + return execCurl(args, temp_folder(), timeoutMsec); } -bool UpdateChecker::checkSignature(const QString &file, const QString &signature) +bool MUtils::UpdateChecker::checkSignature(const QString &file, const QString &signature) { - if(QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0) + if (QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0) { qWarning("CheckSignature: File and signature should be in same folder!"); return false; } - if(QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0) + + QString keyRingPath(m_binaryKeys); + bool removeKeyring = false; + if (QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0) { - qWarning("CheckSignature: File and keyring should be in same folder!"); - return false; + keyRingPath = make_temp_file(QFileInfo(file).absolutePath(), "gpg"); + removeKeyring = true; + if (!QFile::copy(m_binaryKeys, keyRingPath)) + { + qWarning("CheckSignature: Failed to copy the key-ring file!"); + return false; + } } - QProcess process; - init_process(process, QFileInfo(file).absolutePath()); + QStringList args; + args << QStringList() << "--homedir" << "."; + args << "--keyring" << QFileInfo(keyRingPath).fileName(); + args << QFileInfo(signature).fileName(); + args << QFileInfo(file).fileName(); - QEventLoop loop; - connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); - connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), &loop, SLOT(quit())); - connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit())); - - process.start(m_binaryGnuPG, QStringList() << "--homedir" << "." << "--keyring" << QFileInfo(m_binaryKeys).fileName() << QFileInfo(signature).fileName() << QFileInfo(file).fileName()); + const int exitCode = execProcess(m_binaryGnuPG, args, QFileInfo(file).absolutePath(), DOWNLOAD_TIMEOUT); + if (exitCode != INT_MAX) + { + log(QString().sprintf("Exited with code %d", exitCode)); + } - if(!process.waitForStarted()) + if (removeKeyring) { - return false; + remove_file(keyRingPath); } - while(process.state() == QProcess::Running) + return (exitCode == 0); /*completed*/ +} + +bool MUtils::UpdateChecker::execCurl(const QStringList &args, const QString &workingDir, const int timeout) +{ + const int exitCode = execProcess(m_binaryCurl, args, workingDir, timeout + (timeout / 2)); + if (exitCode != INT_MAX) { - loop.exec(); - while(process.canReadLine()) + switch (exitCode) { - log(QString::fromLatin1(process.readLine()).simplified()); + case -1: + case 0: log(QLatin1String("DONE: Transfer completed successfully."), ""); break; + case 6: log(QLatin1String("ERROR: Remote host could not be resolved!"), ""); break; + case 7: log(QLatin1String("ERROR: Connection to remote host could not be established!"), ""); break; + case 22: log(QLatin1String("ERROR: Requested URL was not found or returned an error!"), ""); break; + case 28: log(QLatin1String("ERROR: Operation timed out !!!"), ""); break; + default: log(QString().sprintf("ERROR: Terminated with unknown code %d", exitCode), ""); break; } } - - log(QString().sprintf("Exited with code %d", process.exitCode())); - return (process.exitCode() == 0); + + return (exitCode == 0); /*completed*/ } -bool UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *updateInfo) +int MUtils::UpdateChecker::execProcess(const QString &programFile, const QStringList &args, const QString &workingDir, const int timeout) { - QRegExp value("^(\\w+)=(.+)$"); - QRegExp section("^\\[(.+)\\]$"); + QProcess process; + init_process(process, workingDir, true, NULL, m_environment.data()); - QDate updateInfoDate; - updateInfo->resetInfo(); + QEventLoop loop; + connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit())); + connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit())); + connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit())); - QFile data(file); - if(!data.open(QIODevice::ReadOnly)) + QTimer timer; + timer.setSingleShot(true); + connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + + process.start(programFile, args); + if (!process.waitForStarted()) { - qWarning("Cannot open update info file for reading!"); - return false; + log("PROCESS FAILED TO START !!!", ""); + qWarning("WARNING: %s process could not be created!", MUTILS_UTF8(QFileInfo(programFile).fileName())); + return INT_MAX; /*failed to start*/ } - - bool inHeader = false; - bool inSection = false; - - while(!data.atEnd()) + + bool bAborted = false; + timer.start(qMax(timeout, 1500)); + + while (process.state() != QProcess::NotRunning) { - QString line = QString::fromLatin1(data.readLine()).trimmed(); - if(section.indexIn(line) >= 0) - { - log(QString("Sec: [%1]").arg(section.cap(1))); - inSection = (section.cap(1).compare(section_id, Qt::CaseInsensitive) == 0); - inHeader = (section.cap(1).compare(header_id, Qt::CaseInsensitive) == 0); - continue; - } - if(inSection && (value.indexIn(line) >= 0)) + loop.exec(); + while (process.canReadLine()) { - log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2))); - if(value.cap(1).compare("BuildNo", Qt::CaseInsensitive) == 0) - { - bool ok = false; - unsigned int temp = value.cap(2).toUInt(&ok); - if(ok) updateInfo->m_buildNo = temp; - } - else if(value.cap(1).compare("BuildDate", Qt::CaseInsensitive) == 0) - { - QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate); - if(temp.isValid()) updateInfo->m_buildDate = temp; - } - else if(value.cap(1).compare("DownloadSite", Qt::CaseInsensitive) == 0) - { - updateInfo->m_downloadSite = value.cap(2).trimmed(); - } - else if(value.cap(1).compare("DownloadAddress", Qt::CaseInsensitive) == 0) - { - updateInfo->m_downloadAddress = value.cap(2).trimmed(); - } - else if(value.cap(1).compare("DownloadFilename", Qt::CaseInsensitive) == 0) - { - updateInfo->m_downloadFilename = value.cap(2).trimmed(); - } - else if(value.cap(1).compare("DownloadFilecode", Qt::CaseInsensitive) == 0) + const QString line = QString::fromLatin1(process.readLine()).simplified(); + if (line.length() > 1) { - updateInfo->m_downloadFilecode = value.cap(2).trimmed(); + log(line); } } - if(inHeader && (value.indexIn(line) >= 0)) + const bool bCancelled = MUTILS_BOOLIFY(m_cancelled); + if (bAborted = (bCancelled || ((!timer.isActive()) && (!process.waitForFinished(125))))) { - log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2))); - if(value.cap(1).compare("TimestampCreated", Qt::CaseInsensitive) == 0) - { - QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate); - if(temp.isValid()) updateInfoDate = temp; - } + log(bCancelled ? "CANCELLED BY USER !!!" : "PROCESS TIMEOUT !!!", ""); + qWarning("WARNING: %s process %s!", MUTILS_UTF8(QFileInfo(programFile).fileName()), bCancelled ? "cancelled" : "timed out"); + break; /*abort process*/ } } - if(!updateInfoDate.isValid()) - { - updateInfo->resetInfo(); - log("WARNING: Version info timestamp is missing!"); - return false; - } - - const QDate currentDate = OS::current_date(); - if(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS) < currentDate) - { - updateInfo->resetInfo(); - log(QString::fromLatin1("WARNING: This version info has expired at %1!").arg(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS).toString(Qt::ISODate))); - return false; - } - else if(currentDate < updateInfoDate) + timer.stop(); + timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); + + if (bAborted) { - log("Version info is from the future, take care!"); - qWarning("Version info is from the future, take care!"); + process.kill(); + process.waitForFinished(-1); } - bool complete = true; - - if(!(updateInfo->m_buildNo > 0)) complete = false; - if(!(updateInfo->m_buildDate.year() >= 2010)) complete = false; - if(updateInfo->m_downloadSite.isEmpty()) complete = false; - if(updateInfo->m_downloadAddress.isEmpty()) complete = false; - if(updateInfo->m_downloadFilename.isEmpty()) complete = false; - if(updateInfo->m_downloadFilecode.isEmpty()) complete = false; - - if(!complete) + while (process.canReadLine()) { - log("WARNING: Version info is incomplete!"); + const QString line = QString::fromLatin1(process.readLine()).simplified(); + if (line.length() > 1) + { + log(line); + } } - return complete; + return bAborted ? INT_MAX : process.exitCode(); } ////////////////////////////////////////////////////////////