OSDN Git Service

Some code refactoring.
[mutilities/MUtilities.git] / src / UpdateChecker.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // MuldeR's Utilities for Qt
3 // Copyright (C) 2004-2017 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 //
19 // http://www.gnu.org/licenses/lgpl-2.1.txt
20 //////////////////////////////////////////////////////////////////////////////////
21
22 #include <MUtils/Global.h>
23 #include <MUtils/UpdateChecker.h>
24 #include <MUtils/OSSupport.h>
25 #include <MUtils/Exception.h>
26
27 #include <QStringList>
28 #include <QFile>
29 #include <QFileInfo>
30 #include <QProcess>
31 #include <QUrl>
32 #include <QEventLoop>
33 #include <QTimer>
34 #include <QElapsedTimer>
35
36 using namespace MUtils;
37
38 ///////////////////////////////////////////////////////////////////////////////
39 // CONSTANTS
40 ///////////////////////////////////////////////////////////////////////////////
41
42 static const char *header_id = "!Update";
43
44 static const char *mirror_url_postfix[] = 
45 {
46         "update.ver",
47         "update_beta.ver",
48         NULL
49 };
50
51 static const char *update_mirrors[] =
52 {
53         "http://muldersoft.com/",
54         "http://mulder.bplaced.net/",
55         "http://mulder.6te.net/",
56         "http://mulder.webuda.com/",
57         "http://mulder.pe.hu/",
58         "http://muldersoft.square7.ch/",
59         "http://muldersoft.co.nf/",
60         "http://muldersoft.eu.pn/",
61         "http://muldersoft.lima-city.de/",
62         "http://www.muldersoft.keepfree.de/",
63         "http://lamexp.sourceforge.net/",
64         "http://lordmulder.github.io/LameXP/",
65         "http://muldersoft.bitbucket.io/",      //http://lord_mulder.bitbucket.org/
66         "http://www.tricksoft.de/",
67         "http://repo.or.cz/LameXP.git/blob_plain/gh-pages:/",
68         "http://gitlab.com/lamexp/lamexp/raw/gh-pages/",
69         NULL
70 };
71
72 static const char *known_hosts[] =              //Taken form: http://www.alexa.com/topsites !!!
73 {
74         "www.163.com",
75         "www.7-zip.org",
76         "www.ac3filter.net",
77         "clbianco.altervista.org",
78         "status.aws.amazon.com",
79         "build.antergos.com",
80         "www.aol.com",
81         "www.apache.org",
82         "www.apple.com",
83         "www.adobe.com",
84         "archive.org",
85         "www.artlebedev.ru",
86         "web.audacityteam.org",
87         "status.automattic.com",
88         "www.avidemux.org",
89         "www.babylon.com",
90         "www.baidu.com",
91         "bandcamp.com",
92         "www.bbc.co.uk",
93         "www.berlios.de",
94         "www.bing.com",
95         "www.bingeandgrab.com",
96         "www.bucketheadpikes.com",
97         "www.buckethead-coop.com",
98         "www.buzzfeed.com",
99         "www.cam.ac.uk",
100         "www.ccc.de",
101         "home.cern",
102         "www.citizeninsomniac.com",
103         "www.cnet.com",
104         "cnzz.com",
105         "www.cuhk.edu.hk",
106         "www.codeplex.com",
107         "www.codeproject.com",
108         "www.der-postillon.com",
109         "www.ebay.com",
110         "www.equation.com",
111         "www.farbrausch.de",
112         "fc2.com",
113         "fedoraproject.org",
114         "blog.fefe.de",
115         "www.ffmpeg.org",
116         "blog.flickr.net",
117         "www.fraunhofer.de",
118         "free-codecs.com",
119         "git-scm.com",
120         "doc.gitlab.com",
121         "www.gmx.net",
122         "news.gnome.org",
123         "www.gnu.org",
124         "go.com",
125         "code.google.com",
126         "haali.su",
127         "www.harvard.edu",
128         "www.heise.de",
129         "www.helmholtz.de",
130         "www.huffingtonpost.co.uk",
131         "www.hu-berlin.de",
132         "www.iana.org",
133         "www.imdb.com",
134         "www.imgburn.com",
135         "imgur.com",
136         "www.iuj.ac.jp",
137         "www.jd.com",
138         "www.jiscdigitalmedia.ac.uk",
139         "kannmanumdieuhrzeitschonnbierchentrinken.de",
140         "mirrors.kernel.org",
141         "komisar.gin.by",
142         "lame.sourceforge.net",
143         "www.libav.org",
144         "blog.linkedin.com",
145         "www.linuxmint.com",
146         "www.livedoor.com",
147         "www.livejournal.com",
148         "longplayer.org",
149         "go.mail.ru",
150         "marknelson.us",
151         "www.mediafire.com",
152         "web.mit.edu",
153         "www.mod-technologies.com",
154         "ftp.mozilla.org",
155         "www.mpg.de",
156         "mplayerhq.hu",
157         "www.msn.com",
158         "wiki.multimedia.cx",
159         "www.nch.com.au",
160         "neocities.org",
161         "mirror.netcologne.de",
162         "oss.netfarm.it",
163         "blog.netflix.com",
164         "netrenderer.de",
165         "www.nytimes.com",
166         "www.opera.com",
167         "www.oxford.gov.uk",
168         "www.partha.com",
169         "pastebin.com",
170         "pastie.org",
171         "portableapps.com",
172         "www.portablefreeware.com",
173         "support.proboards.com",
174         "www.qq.com",
175         "www.qt.io",
176         "www.quakelive.com",
177         "rationalqm.us",
178         "www.rwth-aachen.de",
179         "www.seamonkey-project.org",
180         "selfhtml.org",
181         "www.sina.com.cn",
182         "www.sohu.com",
183         "help.sogou.com",
184         "sourceforge.net",
185         "www.spiegel.de",
186         "www.sputnikmusic.com",
187         "stackoverflow.com",
188         "www.stanford.edu",
189         "www.t-online.de",
190         "www.tagesschau.de",
191         "tdm-gcc.tdragon.net",
192         "www.tdrsmusic.com",
193         "tu-dresden.de",
194         "www.ubuntu.com",
195         "www.uol.com.br",
196         "www.videohelp.com",
197         "www.videolan.org",
198         "virtualdub.org",
199         "blog.virustotal.com",
200         "www.vkgoeswild.com",
201         "www.warr.org",
202         "www.weibo.com",
203         "status.wikimedia.org",
204         "www.winamp.com",
205         "www.winhoros.de",
206         "wpde.org",
207         "x265.org",
208         "xhmikosr.1f0.de",
209         "xiph.org",
210         "us.mail.yahoo.com",
211         "www.youtube.com",
212         "www.zedo.com",
213         "ffmpeg.zeranoe.com",
214         NULL
215 };
216
217 static const int MIN_CONNSCORE = 5;
218 static const int QUICK_MIRRORS = 3;
219 static const int MAX_CONN_TIMEOUT =  8000;
220 static const int DOWNLOAD_TIMEOUT = 30000;
221
222 static const int VERSION_INFO_EXPIRES_MONTHS = 6;
223 static char *USER_AGENT_STR = "Mozilla/5.0 (X11; Linux i686; rv:10.0) Gecko/20100101 Firefox/10.0"; /*use something innocuous*/
224
225 #define IS_CANCELLED (!(!m_cancelled))
226 #define CHECK_CANCELLED() do \
227 { \
228         if(IS_CANCELLED) \
229         { \
230                 m_success = false; \
231                 log("", "Update check has been cancelled by user!"); \
232                 setProgress(m_maxProgress); \
233                 setStatus(UpdateStatus_CancelledByUser); \
234                 return; \
235         } \
236 } \
237 while(0)
238
239 ////////////////////////////////////////////////////////////
240 // Helper Functions
241 ////////////////////////////////////////////////////////////
242
243 static int getMaxProgress(void)
244 {
245         int counter = 0;
246         while (update_mirrors[counter])
247         {
248                 counter++;
249         }
250         counter += MIN_CONNSCORE + QUICK_MIRRORS + 2;
251         return counter; ;
252 }
253
254 static QStringList buildRandomList(const char *const values[])
255 {
256         QStringList list;
257         for (int index = 0; values[index]; index++)
258         {
259                 const int pos = next_rand_u32() % (index + 1);
260                 list.insert(pos, QString::fromLatin1(values[index]));
261         }
262         return list;
263 }
264
265 ////////////////////////////////////////////////////////////
266 // Update Info Class
267 ////////////////////////////////////////////////////////////
268
269 UpdateCheckerInfo::UpdateCheckerInfo(void)
270 {
271         resetInfo();
272 }
273         
274 void UpdateCheckerInfo::resetInfo(void)
275 {
276         m_buildNo = 0;
277         m_buildDate.setDate(1900, 1, 1);
278         m_downloadSite.clear();
279         m_downloadAddress.clear();
280         m_downloadFilename.clear();
281         m_downloadFilecode.clear();
282         m_downloadChecksum.clear();
283 }
284
285 bool UpdateCheckerInfo::isComplete(void)
286 {
287         if(this->m_buildNo < 1)                return false;
288         if(this->m_buildDate.year() < 2010)    return false;
289         if(this->m_downloadSite.isEmpty())     return false;
290         if(this->m_downloadAddress.isEmpty())  return false;
291         if(this->m_downloadFilename.isEmpty()) return false;
292         if(this->m_downloadFilecode.isEmpty()) return false;
293         if(this->m_downloadChecksum.isEmpty()) return false;
294
295         return true;
296 }
297
298 ////////////////////////////////////////////////////////////
299 // Constructor & Destructor
300 ////////////////////////////////////////////////////////////
301
302 UpdateChecker::UpdateChecker(const QString &binWGet, const QString &binMCat, const QString &binGnuPG, const QString &binKeys, const QString &applicationId, const quint32 &installedBuildNo, const bool betaUpdates, const bool testMode)
303 :
304         m_updateInfo(new UpdateCheckerInfo()),
305         m_binaryWGet(binWGet),
306         m_binaryMCat(binMCat),
307         m_binaryGnuPG(binGnuPG),
308         m_binaryKeys(binKeys),
309         m_applicationId(applicationId),
310         m_installedBuildNo(installedBuildNo),
311         m_betaUpdates(betaUpdates),
312         m_testMode(testMode),
313         m_maxProgress(getMaxProgress())
314 {
315         m_success = false;
316         m_status = UpdateStatus_NotStartedYet;
317         m_progress = 0;
318
319         if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty())
320         {
321                 MUTILS_THROW("Tools not initialized correctly!");
322         }
323 }
324
325 UpdateChecker::~UpdateChecker(void)
326 {
327 }
328
329 ////////////////////////////////////////////////////////////
330 // Public slots
331 ////////////////////////////////////////////////////////////
332
333 void UpdateChecker::start(Priority priority)
334 {
335         m_success = false;
336         m_cancelled.fetchAndStoreOrdered(0);
337         QThread::start(priority);
338 }
339
340 ////////////////////////////////////////////////////////////
341 // Protected functions
342 ////////////////////////////////////////////////////////////
343
344 void UpdateChecker::run(void)
345 {
346         qDebug("Update checker thread started!");
347         MUTILS_EXCEPTION_HANDLER(m_testMode ? testKnownHosts() : checkForUpdates());
348         qDebug("Update checker thread completed.");
349 }
350
351 void UpdateChecker::checkForUpdates(void)
352 {
353         // ----- Initialization ----- //
354
355         m_updateInfo->resetInfo();
356         setProgress(0);
357
358         // ----- Test Internet Connection ----- //
359
360         log("Checking internet connection...", "");
361         setStatus(UpdateStatus_CheckingConnection);
362
363         const int networkStatus = OS::network_status();
364         if(networkStatus == OS::NETWORK_TYPE_NON)
365         {
366                 log("Operating system reports that the computer is currently offline !!!");
367                 setProgress(m_maxProgress);
368                 setStatus(UpdateStatus_ErrorNoConnection);
369                 return;
370         }
371         
372         setProgress(1);
373
374         // ----- Test Known Hosts Connectivity ----- //
375
376         int connectionScore = 0;
377         QStringList mirrorList = buildRandomList(known_hosts);
378
379         for(int connectionTimout = 250; connectionTimout <= MAX_CONN_TIMEOUT; connectionTimout *= 2)
380         {
381                 QElapsedTimer elapsedTimer;
382                 elapsedTimer.start();
383                 const int globalTimout = 2 * MIN_CONNSCORE * connectionTimout;
384                 while (!elapsedTimer.hasExpired(globalTimout))
385                 {
386                         const QString hostName = mirrorList.takeFirst();
387                         if (tryContactHost(hostName, connectionTimout))
388                         {
389                                 connectionScore += 1;
390                                 setProgress(qBound(1, connectionScore + 1, MIN_CONNSCORE + 1));
391                                 elapsedTimer.restart();
392                                 if (connectionScore >= MIN_CONNSCORE)
393                                 {
394                                         goto endLoop; /*success*/
395                                 }
396                         }
397                         else
398                         {
399                                 mirrorList.append(hostName); /*re-schedule*/
400                         }
401                         CHECK_CANCELLED();
402                         msleep(1);
403                 }
404         }
405
406 endLoop:
407         if(connectionScore < MIN_CONNSCORE)
408         {
409                 log("", "Connectivity test has failed: Internet connection appears to be broken!");
410                 setProgress(m_maxProgress);
411                 setStatus(UpdateStatus_ErrorConnectionTestFailed);
412                 return;
413         }
414
415         // ----- Fetch Update Info From Server ----- //
416
417         log("----", "", "Checking for updates online...");
418         setStatus(UpdateStatus_FetchingUpdates);
419
420         int mirrorCount = 0;
421         mirrorList = buildRandomList(update_mirrors);
422
423         while(!mirrorList.isEmpty())
424         {
425                 setProgress(m_progress + 1);
426                 const QString currentMirror = mirrorList.takeFirst();
427                 const bool isQuick = (mirrorCount++ < QUICK_MIRRORS);
428                 if(tryUpdateMirror(m_updateInfo.data(), currentMirror, isQuick))
429                 {
430                         m_success = true; /*success*/
431                         break;
432                 }
433                 if (isQuick)
434                 {
435                         mirrorList.append(currentMirror); /*re-schedule*/
436                 }
437                 CHECK_CANCELLED();
438                 msleep(1);
439         }
440         
441         while (m_progress < m_maxProgress)
442         {
443                 msleep(16);
444                 setProgress(m_progress + 1);
445                 CHECK_CANCELLED();
446         }
447
448         // ----- Generate final result ----- //
449
450         if(m_success)
451         {
452                 if(m_updateInfo->m_buildNo > m_installedBuildNo)
453                 {
454                         setStatus(UpdateStatus_CompletedUpdateAvailable);
455                 }
456                 else if(m_updateInfo->m_buildNo == m_installedBuildNo)
457                 {
458                         setStatus(UpdateStatus_CompletedNoUpdates);
459                 }
460                 else
461                 {
462                         setStatus(UpdateStatus_CompletedNewVersionOlder);
463                 }
464         }
465         else
466         {
467                 setStatus(UpdateStatus_ErrorFetchUpdateInfo);
468         }
469 }
470
471 void UpdateChecker::testKnownHosts(void)
472 {
473         QStringList hostList;
474         for(int i = 0; known_hosts[i]; i++)
475         {
476                 hostList << QString::fromLatin1(known_hosts[i]);
477         }
478
479         qDebug("\n[Known Hosts]");
480         log("Testing all known hosts...", "", "---");
481
482         int hostCount = hostList.count();
483         while(!hostList.isEmpty())
484         {
485                 QString currentHost = hostList.takeFirst();
486                 qDebug("Testing: %s", currentHost.toLatin1().constData());
487                 log("", "Testing:", currentHost, "");
488                 if (!tryContactHost(currentHost, DOWNLOAD_TIMEOUT))
489                 {
490                         qWarning("\nConnectivity test FAILED on the following host:\n%s\n", currentHost.toLatin1().constData());
491                 }
492                 log("", "---");
493         }
494 }
495
496 ////////////////////////////////////////////////////////////
497 // PRIVATE FUNCTIONS
498 ////////////////////////////////////////////////////////////
499
500 void UpdateChecker::setStatus(const int status)
501 {
502         if(m_status != status)
503         {
504                 m_status = status;
505                 emit statusChanged(status);
506         }
507 }
508
509 void UpdateChecker::setProgress(const int progress)
510 {
511         if(m_progress != progress)
512         {
513                 m_progress = progress;
514                 emit progressChanged(progress);
515         }
516 }
517
518 void UpdateChecker::log(const QString &str1, const QString &str2, const QString &str3, const QString &str4)
519 {
520         if(!str1.isNull()) emit messageLogged(str1);
521         if(!str2.isNull()) emit messageLogged(str2);
522         if(!str3.isNull()) emit messageLogged(str3);
523         if(!str4.isNull()) emit messageLogged(str4);
524 }
525
526 bool UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString &url, const bool &quick)
527 {
528         bool success = false;
529         log("", "Trying mirror:", url, "");
530
531         if (!tryContactHost(QUrl(url).host(), quick ? (MAX_CONN_TIMEOUT / 10) : MAX_CONN_TIMEOUT))
532         {
533                 log("", quick ? "Mirror is too slow!" :"Mirror is unreachable!");
534                 return false;
535         }
536
537         const QString randPart = next_rand_str();
538         const QString outFileVers = QString("%1/%2.ver").arg(temp_folder(), randPart);
539         const QString outFileSign = QString("%1/%2.sig").arg(temp_folder(), randPart);
540
541         if (getUpdateInfo(url, outFileVers, outFileSign))
542         {
543                 log("", "Download okay, checking signature:");
544                 if (checkSignature(outFileVers, outFileSign))
545                 {
546                         log("", "Signature okay, parsing info:", "");
547                         success = parseVersionInfo(outFileVers, updateInfo);
548                 }
549                 else
550                 {
551                         log("", "Bad signature, take care!");
552                 }
553         }
554         else
555         {
556                 log("", "Download has failed!");
557         }
558
559         QFile::remove(outFileVers);
560         QFile::remove(outFileSign);
561         
562         return success;
563 }
564
565 bool UpdateChecker::getUpdateInfo(const QString &url, const QString &outFileVers, const QString &outFileSign)
566 {
567         log("Downloading update info:", "");
568         if(getFile(QString("%1%2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileVers))
569         {
570                 if (!m_cancelled)
571                 {
572                         log("", "Downloading signature:", "");
573                         if (getFile(QString("%1%2.sig2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileSign))
574                         {
575                                 return true;
576                         }
577                 }
578         }
579         return false;
580 }
581
582 bool UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *updateInfo)
583 {
584         QRegExp value("^(\\w+)=(.+)$");
585         QRegExp section("^\\[(.+)\\]$");
586
587         QDate updateInfoDate;
588         updateInfo->resetInfo();
589
590         QFile data(file);
591         if(!data.open(QIODevice::ReadOnly))
592         {
593                 qWarning("Cannot open update info file for reading!");
594                 return false;
595         }
596         
597         bool inHdr = false;
598         bool inSec = false;
599         
600         while(!data.atEnd())
601         {
602                 QString line = QString::fromLatin1(data.readLine()).trimmed();
603                 if(section.indexIn(line) >= 0)
604                 {
605                         log(QString("Sec: [%1]").arg(section.cap(1)));
606                         inSec = (section.cap(1).compare(m_applicationId, Qt::CaseInsensitive) == 0);
607                         inHdr = (section.cap(1).compare(QString::fromLatin1(header_id), Qt::CaseInsensitive) == 0);
608                         continue;
609                 }
610                 if(inSec && (value.indexIn(line) >= 0))
611                 {
612                         log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
613                         if(value.cap(1).compare("BuildNo", Qt::CaseInsensitive) == 0)
614                         {
615                                 bool ok = false;
616                                 const unsigned int temp = value.cap(2).toUInt(&ok);
617                                 if(ok) updateInfo->m_buildNo = temp;
618                         }
619                         else if(value.cap(1).compare("BuildDate", Qt::CaseInsensitive) == 0)
620                         {
621                                 const QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
622                                 if(temp.isValid()) updateInfo->m_buildDate = temp;
623                         }
624                         else if(value.cap(1).compare("DownloadSite", Qt::CaseInsensitive) == 0)
625                         {
626                                 updateInfo->m_downloadSite = value.cap(2).trimmed();
627                         }
628                         else if(value.cap(1).compare("DownloadAddress", Qt::CaseInsensitive) == 0)
629                         {
630                                 updateInfo->m_downloadAddress = value.cap(2).trimmed();
631                         }
632                         else if(value.cap(1).compare("DownloadFilename", Qt::CaseInsensitive) == 0)
633                         {
634                                 updateInfo->m_downloadFilename = value.cap(2).trimmed();
635                         }
636                         else if(value.cap(1).compare("DownloadFilecode", Qt::CaseInsensitive) == 0)
637                         {
638                                 updateInfo->m_downloadFilecode = value.cap(2).trimmed();
639                         }
640                         else if(value.cap(1).compare("DownloadChecksum", Qt::CaseInsensitive) == 0)
641                         {
642                                 updateInfo->m_downloadChecksum = value.cap(2).trimmed();
643                         }
644                 }
645                 if(inHdr && (value.indexIn(line) >= 0))
646                 {
647                         log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
648                         if(value.cap(1).compare("TimestampCreated", Qt::CaseInsensitive) == 0)
649                         {
650                                 QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
651                                 if(temp.isValid()) updateInfoDate = temp;
652                         }
653                 }
654         }
655
656         if(!updateInfoDate.isValid())
657         {
658                 updateInfo->resetInfo();
659                 log("WARNING: Version info timestamp is missing!");
660                 return false;
661         }
662         
663         const QDate currentDate = OS::current_date();
664         if(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS) < currentDate)
665         {
666                 updateInfo->resetInfo();
667                 log(QString::fromLatin1("WARNING: This version info has expired at %1!").arg(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS).toString(Qt::ISODate)));
668                 return false;
669         }
670         else if(currentDate < updateInfoDate)
671         {
672                 log("Version info is from the future, take care!");
673                 qWarning("Version info is from the future, take care!");
674         }
675         
676         if(!updateInfo->isComplete())
677         {
678                 log("WARNING: Version info is incomplete!");
679                 return false;
680         }
681
682         return true;
683 }
684
685 //----------------------------------------------------------
686 // EXTERNAL TOOLS
687 //----------------------------------------------------------
688
689 bool UpdateChecker::getFile(const QString &url, const QString &outFile, const unsigned int maxRedir)
690 {
691         for (int i = 0; i < 2; i++)
692         {
693                 if (getFile(url, (i > 0), outFile, maxRedir))
694                 {
695                         return true;
696                 }
697                 if (IS_CANCELLED)
698                 {
699                         break; /*cancelled*/
700                 }
701         }
702         return false;
703 }
704
705 bool UpdateChecker::getFile(const QString &url, const bool forceIp4, const QString &outFile, const unsigned int maxRedir)
706 {
707         QFileInfo output(outFile);
708         output.setCaching(false);
709
710         if (output.exists())
711         {
712                 QFile::remove(output.canonicalFilePath());
713                 if (output.exists())
714                 {
715                         return false;
716                 }
717         }
718
719         QProcess process;
720         init_process(process, output.absolutePath());
721
722         QStringList args;
723         if (forceIp4)
724         {
725                 args << "-4";
726         }
727
728         args << "--no-config" << "--no-cache" << "--no-dns-cache" << "--no-check-certificate" << "--no-hsts";
729         args << QString().sprintf("--max-redirect=%u", maxRedir) << QString().sprintf("--timeout=%u", DOWNLOAD_TIMEOUT / 1000);
730         args << QString("--referer=%1://%2/").arg(QUrl(url).scheme(), QUrl(url).host()) << "-U" << USER_AGENT_STR;
731         args << "-O" << output.fileName() << url;
732
733         QEventLoop loop;
734         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
735         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
736         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
737
738         QTimer timer;
739         timer.setSingleShot(true);
740         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
741
742         const QRegExp httpResponseOK("200 OK$");
743
744         process.start(m_binaryWGet, args);
745
746         if (!process.waitForStarted())
747         {
748                 return false;
749         }
750
751         timer.start(DOWNLOAD_TIMEOUT);
752
753         while (process.state() != QProcess::NotRunning)
754         {
755                 loop.exec();
756                 const bool bTimeOut = (!timer.isActive());
757                 while (process.canReadLine())
758                 {
759                         const QString line = QString::fromLatin1(process.readLine()).simplified();
760                         log(line);
761                 }
762                 if (bTimeOut || IS_CANCELLED)
763                 {
764                         qWarning("WGet process timed out <-- killing!");
765                         process.kill();
766                         process.waitForFinished();
767                         log(bTimeOut ? "!!! TIMEOUT !!!": "!!! CANCELLED !!!");
768                         return false;
769                 }
770         }
771
772         timer.stop();
773         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
774
775         log(QString().sprintf("Exited with code %d", process.exitCode()));
776         return (process.exitCode() == 0) && output.exists() && output.isFile();
777 }
778
779 bool UpdateChecker::tryContactHost(const QString &hostname, const int &timeoutMsec)
780 {
781         log(QString("Connecting to host: %1").arg(hostname), "");
782
783         QProcess process;
784         init_process(process, temp_folder());
785
786         QStringList args;
787         args << "--retry" << QString::number(3) << hostname << QString::number(80);
788
789         QEventLoop loop;
790         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
791         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
792         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
793
794         QTimer timer;
795         timer.setSingleShot(true);
796         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
797
798         process.start(m_binaryMCat, args);
799
800         if (!process.waitForStarted())
801         {
802                 return false;
803         }
804
805         timer.start(timeoutMsec);
806
807         while (process.state() != QProcess::NotRunning)
808         {
809                 loop.exec();
810                 const bool bTimeOut = (!timer.isActive());
811                 while (process.canReadLine())
812                 {
813                         QString line = QString::fromLatin1(process.readLine()).simplified();
814                         log(line);
815                 }
816                 if (bTimeOut || IS_CANCELLED)
817                 {
818                         qWarning("MCat process timed out <-- killing!");
819                         process.kill();
820                         process.waitForFinished();
821                         log(bTimeOut ? "!!! TIMEOUT !!!" : "!!! CANCELLED !!!");
822                         return false;
823                 }
824         }
825
826         timer.stop();
827         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
828
829         if (process.exitCode() != 0)
830         {
831                 log("Connection has failed!");
832         }
833
834         log(QString().sprintf("Exited with code %d", process.exitCode()), "");
835         return (process.exitCode() == 0);
836 }
837
838 bool UpdateChecker::checkSignature(const QString &file, const QString &signature)
839 {
840         if (QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0)
841         {
842                 qWarning("CheckSignature: File and signature should be in same folder!");
843                 return false;
844         }
845
846         QString keyRingPath(m_binaryKeys);
847         bool removeKeyring = false;
848         if (QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0)
849         {
850                 keyRingPath = make_temp_file(QFileInfo(file).absolutePath(), "gpg");
851                 removeKeyring = true;
852                 if (!QFile::copy(m_binaryKeys, keyRingPath))
853                 {
854                         qWarning("CheckSignature: Failed to copy the key-ring file!");
855                         return false;
856                 }
857         }
858
859         QProcess process;
860         init_process(process, QFileInfo(file).absolutePath());
861
862         QEventLoop loop;
863         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
864         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
865         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
866
867         process.start(m_binaryGnuPG, QStringList() << "--homedir" << "." << "--keyring" << QFileInfo(keyRingPath).fileName() << QFileInfo(signature).fileName() << QFileInfo(file).fileName());
868
869         if (!process.waitForStarted())
870         {
871                 if (removeKeyring)
872                 {
873                         remove_file(keyRingPath);
874                 }
875                 return false;
876         }
877
878         while (process.state() == QProcess::Running)
879         {
880                 loop.exec();
881                 while (process.canReadLine())
882                 {
883                         log(QString::fromLatin1(process.readLine()).simplified());
884                 }
885         }
886
887         if (removeKeyring)
888         {
889                 remove_file(keyRingPath);
890         }
891
892         log(QString().sprintf("Exited with code %d", process.exitCode()));
893         return (process.exitCode() == 0);
894 }
895
896 ////////////////////////////////////////////////////////////
897 // SLOTS
898 ////////////////////////////////////////////////////////////
899
900 /*NONE*/
901
902 ////////////////////////////////////////////////////////////
903 // EVENTS
904 ////////////////////////////////////////////////////////////
905
906 /*NONE*/