OSDN Git Service

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