OSDN Git Service

f996bb6c49e5ca5125365a5dd971654b665030a2
[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 CHECK_CANCELLED() do \
226 { \
227         if(MUTILS_BOOLIFY(m_cancelled)) \
228         { \
229                 m_success.fetchAndStoreOrdered(0); \
230                 log("", "Update check has been cancelled by user!"); \
231                 setProgress(m_maxProgress); \
232                 setStatus(UpdateStatus_CancelledByUser); \
233                 return; \
234         } \
235 } \
236 while(0)
237
238 ////////////////////////////////////////////////////////////
239 // Helper Functions
240 ////////////////////////////////////////////////////////////
241
242 static int getMaxProgress(void)
243 {
244         int counter = 0;
245         while (update_mirrors[counter])
246         {
247                 counter++;
248         }
249         counter += MIN_CONNSCORE + QUICK_MIRRORS + 2;
250         return counter; ;
251 }
252
253 static QStringList buildRandomList(const char *const values[])
254 {
255         QStringList list;
256         for (int index = 0; values[index]; index++)
257         {
258                 const int pos = next_rand_u32() % (index + 1);
259                 list.insert(pos, QString::fromLatin1(values[index]));
260         }
261         return list;
262 }
263
264 ////////////////////////////////////////////////////////////
265 // Update Info Class
266 ////////////////////////////////////////////////////////////
267
268 UpdateCheckerInfo::UpdateCheckerInfo(void)
269 {
270         resetInfo();
271 }
272         
273 void UpdateCheckerInfo::resetInfo(void)
274 {
275         m_buildNo = 0;
276         m_buildDate.setDate(1900, 1, 1);
277         m_downloadSite.clear();
278         m_downloadAddress.clear();
279         m_downloadFilename.clear();
280         m_downloadFilecode.clear();
281         m_downloadChecksum.clear();
282 }
283
284 bool UpdateCheckerInfo::isComplete(void)
285 {
286         if(this->m_buildNo < 1)                return false;
287         if(this->m_buildDate.year() < 2010)    return false;
288         if(this->m_downloadSite.isEmpty())     return false;
289         if(this->m_downloadAddress.isEmpty())  return false;
290         if(this->m_downloadFilename.isEmpty()) return false;
291         if(this->m_downloadFilecode.isEmpty()) return false;
292         if(this->m_downloadChecksum.isEmpty()) return false;
293
294         return true;
295 }
296
297 ////////////////////////////////////////////////////////////
298 // Constructor & Destructor
299 ////////////////////////////////////////////////////////////
300
301 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)
302 :
303         m_updateInfo(new UpdateCheckerInfo()),
304         m_binaryWGet(binWGet),
305         m_binaryMCat(binMCat),
306         m_binaryGnuPG(binGnuPG),
307         m_binaryKeys(binKeys),
308         m_applicationId(applicationId),
309         m_installedBuildNo(installedBuildNo),
310         m_betaUpdates(betaUpdates),
311         m_testMode(testMode),
312         m_maxProgress(getMaxProgress())
313 {
314         m_status = UpdateStatus_NotStartedYet;
315         m_progress = 0;
316
317         if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty())
318         {
319                 MUTILS_THROW("Tools not initialized correctly!");
320         }
321 }
322
323 UpdateChecker::~UpdateChecker(void)
324 {
325 }
326
327 ////////////////////////////////////////////////////////////
328 // Public slots
329 ////////////////////////////////////////////////////////////
330
331 void UpdateChecker::start(Priority priority)
332 {
333         m_success.fetchAndStoreOrdered(0);
334         m_cancelled.fetchAndStoreOrdered(0);
335         QThread::start(priority);
336 }
337
338 ////////////////////////////////////////////////////////////
339 // Protected functions
340 ////////////////////////////////////////////////////////////
341
342 void UpdateChecker::run(void)
343 {
344         qDebug("Update checker thread started!");
345         MUTILS_EXCEPTION_HANDLER(m_testMode ? testKnownHosts() : checkForUpdates());
346         qDebug("Update checker thread completed.");
347 }
348
349 void UpdateChecker::checkForUpdates(void)
350 {
351         // ----- Initialization ----- //
352
353         m_updateInfo->resetInfo();
354         setProgress(0);
355
356         // ----- Test Internet Connection ----- //
357
358         log("Checking internet connection...", "");
359         setStatus(UpdateStatus_CheckingConnection);
360
361         const int networkStatus = OS::network_status();
362         if(networkStatus == OS::NETWORK_TYPE_NON)
363         {
364                 log("Operating system reports that the computer is currently offline !!!");
365                 setProgress(m_maxProgress);
366                 setStatus(UpdateStatus_ErrorNoConnection);
367                 return;
368         }
369         
370         setProgress(1);
371
372         // ----- Test Known Hosts Connectivity ----- //
373
374         int connectionScore = 0;
375         QStringList mirrorList = buildRandomList(known_hosts);
376
377         for(int connectionTimout = 250; connectionTimout <= MAX_CONN_TIMEOUT; connectionTimout *= 2)
378         {
379                 QElapsedTimer elapsedTimer;
380                 elapsedTimer.start();
381                 const int globalTimout = 2 * MIN_CONNSCORE * connectionTimout;
382                 while (!elapsedTimer.hasExpired(globalTimout))
383                 {
384                         const QString hostName = mirrorList.takeFirst();
385                         if (tryContactHost(hostName, connectionTimout))
386                         {
387                                 connectionScore += 1;
388                                 setProgress(qBound(1, connectionScore + 1, MIN_CONNSCORE + 1));
389                                 elapsedTimer.restart();
390                                 if (connectionScore >= MIN_CONNSCORE)
391                                 {
392                                         goto endLoop; /*success*/
393                                 }
394                         }
395                         else
396                         {
397                                 mirrorList.append(hostName); /*re-schedule*/
398                         }
399                         CHECK_CANCELLED();
400                         msleep(1);
401                 }
402         }
403
404 endLoop:
405         if(connectionScore < MIN_CONNSCORE)
406         {
407                 log("", "Connectivity test has failed: Internet connection appears to be broken!");
408                 setProgress(m_maxProgress);
409                 setStatus(UpdateStatus_ErrorConnectionTestFailed);
410                 return;
411         }
412
413         // ----- Fetch Update Info From Server ----- //
414
415         log("----", "", "Checking for updates online...");
416         setStatus(UpdateStatus_FetchingUpdates);
417
418         int mirrorCount = 0;
419         mirrorList = buildRandomList(update_mirrors);
420
421         while(!mirrorList.isEmpty())
422         {
423                 setProgress(m_progress + 1);
424                 const QString currentMirror = mirrorList.takeFirst();
425                 const bool isQuick = (mirrorCount++ < QUICK_MIRRORS);
426                 if(tryUpdateMirror(m_updateInfo.data(), currentMirror, isQuick))
427                 {
428                         m_success.ref(); /*success*/
429                         break;
430                 }
431                 if (isQuick)
432                 {
433                         mirrorList.append(currentMirror); /*re-schedule*/
434                 }
435                 CHECK_CANCELLED();
436                 msleep(1);
437         }
438         
439         while (m_progress < m_maxProgress)
440         {
441                 msleep(16);
442                 setProgress(m_progress + 1);
443                 CHECK_CANCELLED();
444         }
445
446         // ----- Generate final result ----- //
447
448         if(MUTILS_BOOLIFY(m_success))
449         {
450                 if(m_updateInfo->m_buildNo > m_installedBuildNo)
451                 {
452                         setStatus(UpdateStatus_CompletedUpdateAvailable);
453                 }
454                 else if(m_updateInfo->m_buildNo == m_installedBuildNo)
455                 {
456                         setStatus(UpdateStatus_CompletedNoUpdates);
457                 }
458                 else
459                 {
460                         setStatus(UpdateStatus_CompletedNewVersionOlder);
461                 }
462         }
463         else
464         {
465                 setStatus(UpdateStatus_ErrorFetchUpdateInfo);
466         }
467 }
468
469 void UpdateChecker::testKnownHosts(void)
470 {
471         QStringList hostList;
472         for(int i = 0; known_hosts[i]; i++)
473         {
474                 hostList << QString::fromLatin1(known_hosts[i]);
475         }
476
477         qDebug("\n[Known Hosts]");
478         log("Testing all known hosts...", "", "---");
479
480         int hostCount = hostList.count();
481         while(!hostList.isEmpty())
482         {
483                 QString currentHost = hostList.takeFirst();
484                 qDebug("Testing: %s", currentHost.toLatin1().constData());
485                 log("", "Testing:", currentHost, "");
486                 if (!tryContactHost(currentHost, DOWNLOAD_TIMEOUT))
487                 {
488                         qWarning("\nConnectivity test FAILED on the following host:\n%s\n", currentHost.toLatin1().constData());
489                 }
490                 log("", "---");
491         }
492 }
493
494 ////////////////////////////////////////////////////////////
495 // PRIVATE FUNCTIONS
496 ////////////////////////////////////////////////////////////
497
498 void UpdateChecker::setStatus(const int status)
499 {
500         if(m_status != status)
501         {
502                 m_status = status;
503                 emit statusChanged(status);
504         }
505 }
506
507 void UpdateChecker::setProgress(const int progress)
508 {
509         if(m_progress != progress)
510         {
511                 m_progress = progress;
512                 emit progressChanged(progress);
513         }
514 }
515
516 void UpdateChecker::log(const QString &str1, const QString &str2, const QString &str3, const QString &str4)
517 {
518         if(!str1.isNull()) emit messageLogged(str1);
519         if(!str2.isNull()) emit messageLogged(str2);
520         if(!str3.isNull()) emit messageLogged(str3);
521         if(!str4.isNull()) emit messageLogged(str4);
522 }
523
524 bool UpdateChecker::tryUpdateMirror(UpdateCheckerInfo *updateInfo, const QString &url, const bool &quick)
525 {
526         bool success = false;
527         log("", "Trying mirror:", url, "");
528
529         if (!tryContactHost(QUrl(url).host(), quick ? (MAX_CONN_TIMEOUT / 10) : MAX_CONN_TIMEOUT))
530         {
531                 log("", quick ? "Mirror is too slow!" :"Mirror is unreachable!");
532                 return false;
533         }
534
535         const QString randPart = next_rand_str();
536         const QString outFileVers = QString("%1/%2.ver").arg(temp_folder(), randPart);
537         const QString outFileSign = QString("%1/%2.sig").arg(temp_folder(), randPart);
538
539         if (getUpdateInfo(url, outFileVers, outFileSign))
540         {
541                 log("", "Download okay, checking signature:");
542                 if (checkSignature(outFileVers, outFileSign))
543                 {
544                         log("", "Signature okay, parsing info:", "");
545                         success = parseVersionInfo(outFileVers, updateInfo);
546                 }
547                 else
548                 {
549                         log("", "Bad signature, take care!");
550                 }
551         }
552         else
553         {
554                 log("", "Download has failed!");
555         }
556
557         QFile::remove(outFileVers);
558         QFile::remove(outFileSign);
559         
560         return success;
561 }
562
563 bool UpdateChecker::getUpdateInfo(const QString &url, const QString &outFileVers, const QString &outFileSign)
564 {
565         log("Downloading update info:", "");
566         if(getFile(QString("%1%2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileVers))
567         {
568                 if (!m_cancelled)
569                 {
570                         log("", "Downloading signature:", "");
571                         if (getFile(QString("%1%2.sig2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileSign))
572                         {
573                                 return true;
574                         }
575                 }
576         }
577         return false;
578 }
579
580 bool UpdateChecker::parseVersionInfo(const QString &file, UpdateCheckerInfo *updateInfo)
581 {
582         QRegExp value("^(\\w+)=(.+)$");
583         QRegExp section("^\\[(.+)\\]$");
584
585         QDate updateInfoDate;
586         updateInfo->resetInfo();
587
588         QFile data(file);
589         if(!data.open(QIODevice::ReadOnly))
590         {
591                 qWarning("Cannot open update info file for reading!");
592                 return false;
593         }
594         
595         bool inHdr = false;
596         bool inSec = false;
597         
598         while(!data.atEnd())
599         {
600                 QString line = QString::fromLatin1(data.readLine()).trimmed();
601                 if(section.indexIn(line) >= 0)
602                 {
603                         log(QString("Sec: [%1]").arg(section.cap(1)));
604                         inSec = (section.cap(1).compare(m_applicationId, Qt::CaseInsensitive) == 0);
605                         inHdr = (section.cap(1).compare(QString::fromLatin1(header_id), Qt::CaseInsensitive) == 0);
606                         continue;
607                 }
608                 if(inSec && (value.indexIn(line) >= 0))
609                 {
610                         log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
611                         if(value.cap(1).compare("BuildNo", Qt::CaseInsensitive) == 0)
612                         {
613                                 bool ok = false;
614                                 const unsigned int temp = value.cap(2).toUInt(&ok);
615                                 if(ok) updateInfo->m_buildNo = temp;
616                         }
617                         else if(value.cap(1).compare("BuildDate", Qt::CaseInsensitive) == 0)
618                         {
619                                 const QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
620                                 if(temp.isValid()) updateInfo->m_buildDate = temp;
621                         }
622                         else if(value.cap(1).compare("DownloadSite", Qt::CaseInsensitive) == 0)
623                         {
624                                 updateInfo->m_downloadSite = value.cap(2).trimmed();
625                         }
626                         else if(value.cap(1).compare("DownloadAddress", Qt::CaseInsensitive) == 0)
627                         {
628                                 updateInfo->m_downloadAddress = value.cap(2).trimmed();
629                         }
630                         else if(value.cap(1).compare("DownloadFilename", Qt::CaseInsensitive) == 0)
631                         {
632                                 updateInfo->m_downloadFilename = value.cap(2).trimmed();
633                         }
634                         else if(value.cap(1).compare("DownloadFilecode", Qt::CaseInsensitive) == 0)
635                         {
636                                 updateInfo->m_downloadFilecode = value.cap(2).trimmed();
637                         }
638                         else if(value.cap(1).compare("DownloadChecksum", Qt::CaseInsensitive) == 0)
639                         {
640                                 updateInfo->m_downloadChecksum = value.cap(2).trimmed();
641                         }
642                 }
643                 if(inHdr && (value.indexIn(line) >= 0))
644                 {
645                         log(QString("Val: '%1' ==> '%2").arg(value.cap(1), value.cap(2)));
646                         if(value.cap(1).compare("TimestampCreated", Qt::CaseInsensitive) == 0)
647                         {
648                                 QDate temp = QDate::fromString(value.cap(2).trimmed(), Qt::ISODate);
649                                 if(temp.isValid()) updateInfoDate = temp;
650                         }
651                 }
652         }
653
654         if(!updateInfoDate.isValid())
655         {
656                 updateInfo->resetInfo();
657                 log("WARNING: Version info timestamp is missing!");
658                 return false;
659         }
660         
661         const QDate currentDate = OS::current_date();
662         if(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS) < currentDate)
663         {
664                 updateInfo->resetInfo();
665                 log(QString::fromLatin1("WARNING: This version info has expired at %1!").arg(updateInfoDate.addMonths(VERSION_INFO_EXPIRES_MONTHS).toString(Qt::ISODate)));
666                 return false;
667         }
668         else if(currentDate < updateInfoDate)
669         {
670                 log("Version info is from the future, take care!");
671                 qWarning("Version info is from the future, take care!");
672         }
673         
674         if(!updateInfo->isComplete())
675         {
676                 log("WARNING: Version info is incomplete!");
677                 return false;
678         }
679
680         return true;
681 }
682
683 //----------------------------------------------------------
684 // EXTERNAL TOOLS
685 //----------------------------------------------------------
686
687 bool UpdateChecker::getFile(const QString &url, const QString &outFile, const unsigned int maxRedir)
688 {
689         for (int i = 0; i < 2; i++)
690         {
691                 if (getFile(url, (i > 0), outFile, maxRedir))
692                 {
693                         return true;
694                 }
695                 if (MUTILS_BOOLIFY(m_cancelled))
696                 {
697                         break; /*cancelled*/
698                 }
699         }
700         return false;
701 }
702
703 bool UpdateChecker::getFile(const QString &url, const bool forceIp4, const QString &outFile, const unsigned int maxRedir)
704 {
705         QFileInfo output(outFile);
706         output.setCaching(false);
707
708         if (output.exists())
709         {
710                 QFile::remove(output.canonicalFilePath());
711                 if (output.exists())
712                 {
713                         return false;
714                 }
715         }
716
717         QProcess process;
718         init_process(process, output.absolutePath());
719
720         QStringList args;
721         if (forceIp4)
722         {
723                 args << "-4";
724         }
725
726         args << "--no-config" << "--no-cache" << "--no-dns-cache" << "--no-check-certificate" << "--no-hsts";
727         args << QString().sprintf("--max-redirect=%u", maxRedir) << QString().sprintf("--timeout=%u", DOWNLOAD_TIMEOUT / 1000);
728         args << QString("--referer=%1://%2/").arg(QUrl(url).scheme(), QUrl(url).host()) << "-U" << USER_AGENT_STR;
729         args << "-O" << output.fileName() << url;
730
731         QEventLoop loop;
732         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
733         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
734         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
735
736         QTimer timer;
737         timer.setSingleShot(true);
738         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
739
740         const QRegExp httpResponseOK("200 OK$");
741
742         process.start(m_binaryWGet, args);
743
744         if (!process.waitForStarted())
745         {
746                 return false;
747         }
748
749         timer.start(DOWNLOAD_TIMEOUT);
750
751         while (process.state() != QProcess::NotRunning)
752         {
753                 loop.exec();
754                 const bool bTimeOut = (!timer.isActive());
755                 while (process.canReadLine())
756                 {
757                         const QString line = QString::fromLatin1(process.readLine()).simplified();
758                         log(line);
759                 }
760                 if (bTimeOut || MUTILS_BOOLIFY(m_cancelled))
761                 {
762                         qWarning("WGet process timed out <-- killing!");
763                         process.kill();
764                         process.waitForFinished();
765                         log(bTimeOut ? "!!! TIMEOUT !!!": "!!! CANCELLED !!!");
766                         return false;
767                 }
768         }
769
770         timer.stop();
771         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
772
773         log(QString().sprintf("Exited with code %d", process.exitCode()));
774         return (process.exitCode() == 0) && output.exists() && output.isFile();
775 }
776
777 bool UpdateChecker::tryContactHost(const QString &hostname, const int &timeoutMsec)
778 {
779         log(QString("Connecting to host: %1").arg(hostname), "");
780
781         QProcess process;
782         init_process(process, temp_folder());
783
784         QStringList args;
785         args << "--retry" << QString::number(3) << hostname << QString::number(80);
786
787         QEventLoop loop;
788         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
789         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
790         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
791
792         QTimer timer;
793         timer.setSingleShot(true);
794         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
795
796         process.start(m_binaryMCat, args);
797
798         if (!process.waitForStarted())
799         {
800                 return false;
801         }
802
803         timer.start(timeoutMsec);
804
805         while (process.state() != QProcess::NotRunning)
806         {
807                 loop.exec();
808                 const bool bTimeOut = (!timer.isActive());
809                 while (process.canReadLine())
810                 {
811                         QString line = QString::fromLatin1(process.readLine()).simplified();
812                         log(line);
813                 }
814                 if (bTimeOut || MUTILS_BOOLIFY(m_cancelled))
815                 {
816                         qWarning("MCat process timed out <-- killing!");
817                         process.kill();
818                         process.waitForFinished();
819                         log(bTimeOut ? "!!! TIMEOUT !!!" : "!!! CANCELLED !!!");
820                         return false;
821                 }
822         }
823
824         timer.stop();
825         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
826
827         if (process.exitCode() != 0)
828         {
829                 log("Connection has failed!");
830         }
831
832         log(QString().sprintf("Exited with code %d", process.exitCode()), "");
833         return (process.exitCode() == 0);
834 }
835
836 bool UpdateChecker::checkSignature(const QString &file, const QString &signature)
837 {
838         if (QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0)
839         {
840                 qWarning("CheckSignature: File and signature should be in same folder!");
841                 return false;
842         }
843
844         QString keyRingPath(m_binaryKeys);
845         bool removeKeyring = false;
846         if (QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0)
847         {
848                 keyRingPath = make_temp_file(QFileInfo(file).absolutePath(), "gpg");
849                 removeKeyring = true;
850                 if (!QFile::copy(m_binaryKeys, keyRingPath))
851                 {
852                         qWarning("CheckSignature: Failed to copy the key-ring file!");
853                         return false;
854                 }
855         }
856
857         QProcess process;
858         init_process(process, QFileInfo(file).absolutePath());
859
860         QEventLoop loop;
861         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
862         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
863         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
864
865         process.start(m_binaryGnuPG, QStringList() << "--homedir" << "." << "--keyring" << QFileInfo(keyRingPath).fileName() << QFileInfo(signature).fileName() << QFileInfo(file).fileName());
866
867         if (!process.waitForStarted())
868         {
869                 if (removeKeyring)
870                 {
871                         remove_file(keyRingPath);
872                 }
873                 return false;
874         }
875
876         while (process.state() == QProcess::Running)
877         {
878                 loop.exec();
879                 while (process.canReadLine())
880                 {
881                         log(QString::fromLatin1(process.readLine()).simplified());
882                 }
883         }
884
885         if (removeKeyring)
886         {
887                 remove_file(keyRingPath);
888         }
889
890         log(QString().sprintf("Exited with code %d", process.exitCode()));
891         return (process.exitCode() == 0);
892 }
893
894 ////////////////////////////////////////////////////////////
895 // SLOTS
896 ////////////////////////////////////////////////////////////
897
898 /*NONE*/
899
900 ////////////////////////////////////////////////////////////
901 // EVENTS
902 ////////////////////////////////////////////////////////////
903
904 /*NONE*/