OSDN Git Service

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