OSDN Git Service

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