OSDN Git Service

Update checker: Try first couple of mirrors in "quick" mode (reduced connection timeo...
[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(m_cancelled) \
228         { \
229                 m_success = false; \
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 &binNC, 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_binaryNC(binNC),
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_success = m_cancelled = false;
315         m_status = UpdateStatus_NotStartedYet;
316         m_progress = 0;
317
318         if(m_binaryWGet.isEmpty() || m_binaryGnuPG.isEmpty() || m_binaryKeys.isEmpty())
319         {
320                 MUTILS_THROW("Tools not initialized correctly!");
321         }
322 }
323
324 UpdateChecker::~UpdateChecker(void)
325 {
326 }
327
328 ////////////////////////////////////////////////////////////
329 // Public slots
330 ////////////////////////////////////////////////////////////
331
332 void UpdateChecker::start(Priority priority)
333 {
334         m_cancelled = m_success = false;
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 = 125; 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 = true; /*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(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                 return false;
569         }
570
571         log("", "Downloading signature:");
572         if(!getFile(QString("%1%2.sig2").arg(url, mirror_url_postfix[m_betaUpdates ? 1 : 0]), outFileSign))
573         {
574                 return false;
575         }
576
577         return true;
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         }
696         return false;
697 }
698
699 bool UpdateChecker::getFile(const QString &url, const bool forceIp4, const QString &outFile, const unsigned int maxRedir)
700 {
701         QFileInfo output(outFile);
702         output.setCaching(false);
703
704         if (output.exists())
705         {
706                 QFile::remove(output.canonicalFilePath());
707                 if (output.exists())
708                 {
709                         return false;
710                 }
711         }
712
713         QProcess process;
714         init_process(process, output.absolutePath());
715
716         QStringList args;
717         if (forceIp4)
718         {
719                 args << "-4";
720         }
721
722         args << "--no-config" << "--no-cache" << "--no-dns-cache" << "--no-check-certificate" << "--no-hsts";
723         args << QString().sprintf("--max-redirect=%u", maxRedir) << QString().sprintf("--timeout=%u", DOWNLOAD_TIMEOUT / 1000);
724         args << QString("--referer=%1://%2/").arg(QUrl(url).scheme(), QUrl(url).host()) << "-U" << USER_AGENT_STR;
725         args << "-O" << output.fileName() << url;
726
727         QEventLoop loop;
728         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
729         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
730         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
731
732         QTimer timer;
733         timer.setSingleShot(true);
734         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
735
736         const QRegExp httpResponseOK("200 OK$");
737
738         process.start(m_binaryWGet, args);
739
740         if (!process.waitForStarted())
741         {
742                 return false;
743         }
744
745         timer.start(DOWNLOAD_TIMEOUT);
746
747         while (process.state() != QProcess::NotRunning)
748         {
749                 loop.exec();
750                 const bool bTimeOut = (!timer.isActive());
751                 while (process.canReadLine())
752                 {
753                         const QString line = QString::fromLatin1(process.readLine()).simplified();
754                         log(line);
755                 }
756                 if (bTimeOut || m_cancelled)
757                 {
758                         qWarning("WGet process timed out <-- killing!");
759                         process.kill();
760                         process.waitForFinished();
761                         log(bTimeOut ? "!!! TIMEOUT !!!": "!!! CANCELLED !!!");
762                         return false;
763                 }
764         }
765
766         timer.stop();
767         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
768
769         log(QString().sprintf("Exited with code %d", process.exitCode()));
770         return (process.exitCode() == 0) && output.exists() && output.isFile();
771 }
772
773 bool UpdateChecker::tryContactHost(const QString &hostname, const int &timeoutMsec)
774 {
775         log(QString("Connecting to host: %1").arg(hostname));
776
777         QProcess process;
778         init_process(process, temp_folder());
779
780         QStringList args;
781         args << "-z" << hostname << QString::number(80);
782
783         QEventLoop loop;
784         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
785         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
786         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
787
788         QTimer timer;
789         timer.setSingleShot(true);
790         connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
791
792         process.start(m_binaryNC, args);
793
794         if (!process.waitForStarted())
795         {
796                 return false;
797         }
798
799         timer.start(timeoutMsec);
800
801         while (process.state() != QProcess::NotRunning)
802         {
803                 loop.exec();
804                 const bool bTimeOut = (!timer.isActive());
805                 while (process.canReadLine())
806                 {
807                         QString line = QString::fromLatin1(process.readLine()).simplified();
808                         log(line);
809                 }
810                 if (bTimeOut || m_cancelled)
811                 {
812                         qWarning("NC process timed out <-- killing!");
813                         process.kill();
814                         process.waitForFinished();
815                         log(bTimeOut ? "!!! TIMEOUT !!!" : "!!! CANCELLED !!!");
816                         return false;
817                 }
818         }
819
820         timer.stop();
821         timer.disconnect(&timer, SIGNAL(timeout()), &loop, SLOT(quit()));
822
823         if (process.exitCode() != 0)
824         {
825                 log("Connection has failed!");
826         }
827
828         log(QString().sprintf("Exited with code %d", process.exitCode()), "");
829         return (process.exitCode() == 0);
830 }
831
832 bool UpdateChecker::checkSignature(const QString &file, const QString &signature)
833 {
834         if (QFileInfo(file).absolutePath().compare(QFileInfo(signature).absolutePath(), Qt::CaseInsensitive) != 0)
835         {
836                 qWarning("CheckSignature: File and signature should be in same folder!");
837                 return false;
838         }
839
840         QString keyRingPath(m_binaryKeys);
841         bool removeKeyring = false;
842         if (QFileInfo(file).absolutePath().compare(QFileInfo(m_binaryKeys).absolutePath(), Qt::CaseInsensitive) != 0)
843         {
844                 keyRingPath = make_temp_file(QFileInfo(file).absolutePath(), "gpg");
845                 removeKeyring = true;
846                 if (!QFile::copy(m_binaryKeys, keyRingPath))
847                 {
848                         qWarning("CheckSignature: Failed to copy the key-ring file!");
849                         return false;
850                 }
851         }
852
853         QProcess process;
854         init_process(process, QFileInfo(file).absolutePath());
855
856         QEventLoop loop;
857         connect(&process, SIGNAL(error(QProcess::ProcessError)), &loop, SLOT(quit()));
858         connect(&process, SIGNAL(finished(int, QProcess::ExitStatus)), &loop, SLOT(quit()));
859         connect(&process, SIGNAL(readyRead()), &loop, SLOT(quit()));
860
861         process.start(m_binaryGnuPG, QStringList() << "--homedir" << "." << "--keyring" << QFileInfo(keyRingPath).fileName() << QFileInfo(signature).fileName() << QFileInfo(file).fileName());
862
863         if (!process.waitForStarted())
864         {
865                 if (removeKeyring)
866                 {
867                         remove_file(keyRingPath);
868                 }
869                 return false;
870         }
871
872         while (process.state() == QProcess::Running)
873         {
874                 loop.exec();
875                 while (process.canReadLine())
876                 {
877                         log(QString::fromLatin1(process.readLine()).simplified());
878                 }
879         }
880
881         if (removeKeyring)
882         {
883                 remove_file(keyRingPath);
884         }
885
886         log(QString().sprintf("Exited with code %d", process.exitCode()));
887         return (process.exitCode() == 0);
888 }
889
890 ////////////////////////////////////////////////////////////
891 // SLOTS
892 ////////////////////////////////////////////////////////////
893
894 /*NONE*/
895
896 ////////////////////////////////////////////////////////////
897 // EVENTS
898 ////////////////////////////////////////////////////////////
899
900 /*NONE*/