OSDN Git Service

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