source: filezilla/trunk/fuentes/src/interface/updater.cpp @ 130

Last change on this file since 130 was 130, checked in by jrpelegrina, 3 years ago

First release to xenial

File size: 21.5 KB
Line 
1#include <filezilla.h>
2
3#if FZ_MANUALUPDATECHECK
4
5#include "buildinfo.h"
6#include "updater.h"
7#include "Options.h"
8#include "file_utils.h"
9#include <local_filesys.h>
10#include <wx/base64.h>
11#include <wx/tokenzr.h>
12#include <string>
13
14#include <string>
15
16// This is ugly but does the job
17#define SHA512_STANDALONE
18typedef unsigned int uint32;
19namespace {
20#include "../putty/int64.h"
21#include "../putty/sshsh512.c"
22}
23
24BEGIN_EVENT_TABLE(CUpdater, wxEvtHandler)
25EVT_FZ_NOTIFICATION(wxID_ANY, CUpdater::OnEngineEvent)
26EVT_TIMER(wxID_ANY, CUpdater::OnTimer)
27END_EVENT_TABLE()
28
29// BASE-64 encoded DER without the BEGIN/END CERTIFICATE
30static char s_update_cert[] = "\
31MIIFsTCCA5ugAwIBAgIESnXLbzALBgkqhkiG9w0BAQ0wSTELMAkGA1UEBhMCREUx\n\
32GjAYBgNVBAoTEUZpbGVaaWxsYSBQcm9qZWN0MR4wHAYDVQQDExVmaWxlemlsbGEt\n\
33cHJvamVjdC5vcmcwHhcNMDkwODAyMTcyMjU2WhcNMzEwNjI4MTcyMjU4WjBJMQsw\n\
34CQYDVQQGEwJERTEaMBgGA1UEChMRRmlsZVppbGxhIFByb2plY3QxHjAcBgNVBAMT\n\
35FWZpbGV6aWxsYS1wcm9qZWN0Lm9yZzCCAh8wCwYJKoZIhvcNAQEBA4ICDgAwggIJ\n\
36AoICAJqWXy7YzVP5pOk8VB9bd/ROC9SVbAxJiFHh0I0/JmyW+jSfzFCYWr1DKGVv\n\
37Oui+qiUsaSgjWTh/UusnVu4Q4Lb00k7INRF6MFcGFkGNmOZPk4Qt0uuWMtsxiFek\n\
389QMPWSYs+bxk+M0u0rNOdAblsIzeV16yhfUQDtrJxPWbRpuLgp9/4/oNbixet7YM\n\
39pvwlns2o1KXcsNcBcXraux5QmnD4oJVYbTY2qxdMVyreA7dxd40c55F6FvA+L36L\n\
40Nv54VwRFSqY12KBG4I9Up+c9OQ9HMN0zm0FhYtYeKWzdMIRk06EKAxO7MUIcip3q\n\
417v9eROPnKM8Zh4dzkWnCleirW8EKFEm+4+A8pDqirMooiQqkkMesaJDV361UCoVo\n\
42fRhqfK+Prx0BaJK/5ZHN4tmgU5Tmq+z2m7aIKwOImj6VF3somVvmh0G/othnU2MH\n\
43GB7qFrIUMZc5VhrAwmmSA2Z/w4+0ToiR+IrdGmDKz3cVany3EZAzWRJUARaId9FH\n\
44v/ymA1xcFAKmfxsjGNlNpXd7b8UElS8+ccKL9m207k++IIjc0jUPgrM70rU3cv5M\n\
45Kevp971eHLhpWa9vrjbz/urDzBg3Dm8XEN09qwmABfIEnhm6f7oz2bYXjz73ImYj\n\
46rZsogz+Jsx3NWhHFUD42iA4ZnxHIEgchD/TAihpbdrEhgmdvAgMBAAGjgacwgaQw\n\
47EgYDVR0TAQH/BAgwBgEB/wIBAjAmBgNVHREEHzAdgRthZG1pbkBmaWxlemlsbGEt\n\
48cHJvamVjdC5vcmcwDwYDVR0PAQH/BAUDAwcGADAdBgNVHQ4EFgQUd4w2verFjXAn\n\
49CrNLor39nFtemNswNgYDVR0fBC8wLTAroCmgJ4YlaHR0cHM6Ly9jcmwuZmlsZXpp\n\
50bGxhLXByb2plY3Qub3JnL2NybDALBgkqhkiG9w0BAQ0DggIBAF3fmV/Bs4amV78d\n\
51uhe5PkW7yTO6iCfKJVDB22kXPvL0rzZn4SkIZNoac8Xl5vOoRd6k+06i3aJ78w+W\n\
529Z0HK1jUdjW7taYo4bU58nAp3Li+JwjE/lUBNqSKSescPjdZW0KzIIZls91W30yt\n\
53tGq85oWAuyVprHPlr2uWLg1q4eUdF6ZAz4cZ0+9divoMuk1HiWxi1Y/1fqPRzUFf\n\
54UGK0K36iPPz2ktzT7qJYXRfC5QDoX7tCuoDcO5nccVjDypRKxy45O5Ucm/fywiQW\n\
55NQfz/yQAmarQSCfDjNcHD1rdJ0lx9VWP6xi+Z8PGSlR9eDuMaqPVAE1DLHwMMTTZ\n\
5693PbfP2nvgbElgEki28LUalyVuzvrKcu/rL1LnCJA4jStgE/xjDofpYwgtG4ZSnE\n\
57KgNy48eStvNZbGhwn2YvrxyKmw58WSQG9ArOCHoLcWnpedSZuTrPTLfgNUx7DNbo\n\
58qJU36tgxiO0XLRRSetl7jkSIO6U1okVH0/tvstrXEWp4XwdlmoZf92VVBrkg3San\n\
59fA5hBaI2gpQwtpyOJzwLzsd43n4b1YcPiyzhifJGcqRCBZA1uArNsH5iG6z/qHXp\n\
60KjuMxZu8aM8W2gp8Yg8QZfh5St/nut6hnXb5A8Qr+Ixp97t34t264TBRQD6MuZc3\n\
61PqQuF7sJR6POArUVYkRD/2LIWsB7\n\
62";
63
64void version_information::update_available()
65{
66        if( !nightly_.url_.empty() && COptions::Get()->GetOptionVal(OPTION_UPDATECHECK_CHECKBETA) == 2 ) {
67                available_ = nightly_;
68        }
69        else if( !beta_.version_.empty() && COptions::Get()->GetOptionVal(OPTION_UPDATECHECK_CHECKBETA) != 0 ) {
70                available_ = beta_;
71        }
72        else if( !stable_.version_.empty() ) {
73                available_ = stable_;
74        }
75        else {
76                available_ = build();
77        }
78}
79
80static CUpdater* instance = 0;
81
82CUpdater::CUpdater(CUpdateHandler& parent, CFileZillaEngineContext& engine_context)
83        : state_(UpdaterState::idle)
84        , engine_(new CFileZillaEngine(engine_context))
85{
86        AddHandler(parent);
87        engine_->Init(this);
88}
89
90void CUpdater::Init()
91{
92        if( state_ == UpdaterState::checking || state_ == UpdaterState::newversion_downloading ) {
93                return;
94        }
95
96        raw_version_information_ = COptions::Get()->GetOption( OPTION_UPDATECHECK_NEWVERSION );
97
98        UpdaterState s = ProcessFinishedData(FZ_AUTOUPDATECHECK);
99
100        SetState(s);
101
102        AutoRunIfNeeded();
103
104        update_timer_.SetOwner(this);
105        update_timer_.Start(1000 * 3600);
106
107        if( !instance ) {
108                instance = this;
109        }
110}
111
112CUpdater::~CUpdater()
113{
114        if( instance == this ) {
115                instance  =0;
116        }
117        delete engine_;
118}
119
120CUpdater* CUpdater::GetInstance()
121{
122        return instance;
123}
124
125void CUpdater::AutoRunIfNeeded()
126{
127#if FZ_AUTOUPDATECHECK
128        if( state_ == UpdaterState::failed || state_ == UpdaterState::idle ) {
129                if( !COptions::Get()->GetOptionVal(OPTION_DEFAULT_DISABLEUPDATECHECK) && COptions::Get()->GetOptionVal(OPTION_UPDATECHECK) != 0 && LongTimeSinceLastCheck() ) {
130                        Run();
131                }
132        }
133#endif
134}
135
136void CUpdater::RunIfNeeded()
137{
138        build const b = AvailableBuild();
139        if( state_ == UpdaterState::idle || state_ == UpdaterState::failed ||
140                LongTimeSinceLastCheck() || (state_ == UpdaterState::newversion && !b.url_.empty()) ||
141                (state_ == UpdaterState::newversion_ready && !VerifyChecksum( DownloadedFile(), b.size_, b.hash_ ) ) )
142        {
143                Run();
144        }
145}
146
147bool CUpdater::LongTimeSinceLastCheck() const
148{
149        wxString const lastCheckStr = COptions::Get()->GetOption(OPTION_UPDATECHECK_LASTDATE);
150        if (lastCheckStr.empty())
151                return true;
152
153        CDateTime lastCheck(lastCheckStr, CDateTime::utc);
154        if (!lastCheck.IsValid())
155                return true;
156
157        auto const span = CDateTime::Now() - lastCheck;
158
159        if (span.get_seconds() < 0)
160                // Last check in future
161                return true;
162
163        int days = 1;
164        if (!CBuildInfo::IsUnstable())
165                days = COptions::Get()->GetOptionVal(OPTION_UPDATECHECK_INTERVAL);
166        return span.get_days() >= days;
167}
168
169wxString CUpdater::GetUrl()
170{
171        wxString host = CBuildInfo::GetHostname();
172        if (host.empty())
173                host = _T("unknown");
174
175        wxString version(PACKAGE_VERSION, wxConvLocal);
176        version.Replace(_T(" "), _T("%20"));
177
178        wxString url = wxString::Format(_T("https://update.filezilla-project.org/update.php?platform=%s&version=%s"), host, version);
179#if defined(__WXMSW__) || defined(__WXMAC__)
180        // Makes not much sense to submit OS version on Linux, *BSD and the likes, too many flavours.
181        wxString osVersion = wxString::Format(_T("&osversion=%d.%d"), wxPlatformInfo::Get().GetOSMajorVersion(), wxPlatformInfo::Get().GetOSMinorVersion());
182        url += osVersion;
183#endif
184
185#ifdef __WXMSW__
186        if (wxIsPlatform64Bit())
187                url += _T("&osarch=64");
188        else
189                url += _T("&osarch=32");
190#endif
191
192        wxString const cpuCaps = CBuildInfo::GetCPUCaps(',');
193        if (!cpuCaps.empty()) {
194                url += _T("&cpuid=") + cpuCaps;
195        }
196
197        return url;
198}
199
200bool CUpdater::Run()
201{
202        if( state_ != UpdaterState::idle && state_ != UpdaterState::failed &&
203                state_ != UpdaterState::newversion && state_ != UpdaterState::newversion_ready )
204        {
205                return false;
206        }
207
208        auto  const t = CDateTime::Now();
209        COptions::Get()->SetOption(OPTION_UPDATECHECK_LASTDATE, t.Format(_T("%Y-%m-%d %H:%M:%S"), CDateTime::utc));
210
211        local_file_.clear();
212        log_ = wxString::Format(_("Started update check on %s\n"), t.Format(_T("%Y-%m-%d %H:%M:%S"), CDateTime::local));
213
214        wxString build = CBuildInfo::GetBuildType();
215        if( build.empty() ) {
216                build = _("custom");
217        }
218        log_ += wxString::Format(_("Own build type: %s\n"), build);
219
220        SetState(UpdaterState::checking);
221
222        m_use_internal_rootcert = true;
223        int res = Download(GetUrl(), wxString());
224
225        if (res != FZ_REPLY_WOULDBLOCK) {
226                SetState(UpdaterState::failed);
227        }
228        raw_version_information_.clear();
229
230        return state_ == UpdaterState::checking;
231}
232
233int CUpdater::Download(wxString const& url, wxString const& local_file)
234{
235        wxASSERT(pending_commands_.empty());
236        pending_commands_.clear();
237        pending_commands_.emplace_back(new CDisconnectCommand);
238        if (!CreateConnectCommand(url) || !CreateTransferCommand(url, local_file)) {
239                return FZ_REPLY_ERROR;
240        }
241
242        return ContinueDownload();
243}
244
245int CUpdater::ContinueDownload()
246{
247        if (pending_commands_.empty()) {
248                return FZ_REPLY_OK;
249        }
250
251        int res = engine_->Execute(*pending_commands_.front());
252        if (res == FZ_REPLY_OK) {
253                pending_commands_.pop_front();
254                return ContinueDownload();
255        }
256
257        return res;
258}
259
260bool CUpdater::CreateConnectCommand(wxString const& url)
261{
262        CServer s;
263        CServerPath path;
264        wxString error;
265        if( !s.ParseUrl( url, 0, wxString(), wxString(), error, path ) || (s.GetProtocol() != HTTP && s.GetProtocol() != HTTPS) ) {
266                return false;
267        }
268
269        pending_commands_.emplace_back(new CConnectCommand(s));
270        return true;
271}
272
273bool CUpdater::CreateTransferCommand(wxString const& url, wxString const& local_file)
274{
275        CFileTransferCommand::t_transferSettings transferSettings;
276
277        CServer s;
278        CServerPath path;
279        wxString error;
280        if( !s.ParseUrl( url, 0, wxString(), wxString(), error, path ) || (s.GetProtocol() != HTTP && s.GetProtocol() != HTTPS) ) {
281                return false;
282        }
283        wxString file = path.GetLastSegment();
284        path = path.GetParent();
285
286        pending_commands_.emplace_back(new CFileTransferCommand(local_file, path, file, true, transferSettings));
287        return true;
288}
289
290void CUpdater::OnEngineEvent(wxFzEvent& event)
291{
292        if (!engine_ || engine_ != event.engine_)
293                return;
294
295        std::unique_ptr<CNotification> notification;
296        while( (notification = engine_->GetNextNotification()) ) {
297                ProcessNotification(std::move(notification));
298        }
299}
300
301void CUpdater::ProcessNotification(std::unique_ptr<CNotification> && notification)
302{
303        if (state_ != UpdaterState::checking && state_ != UpdaterState::newversion_downloading) {
304                return;
305        }
306
307        switch (notification->GetID())
308        {
309        case nId_asyncrequest:
310                {
311                        auto pData = unique_static_cast<CAsyncRequestNotification>(std::move(notification));
312                        if (pData->GetRequestID() == reqId_fileexists) {
313                                static_cast<CFileExistsNotification *>(pData.get())->overwriteAction = CFileExistsNotification::resume;
314                        }
315                        else if (pData->GetRequestID() == reqId_certificate) {
316                                auto & certNotification = static_cast<CCertificateNotification &>(*pData.get());
317                                if (m_use_internal_rootcert) {
318                                        auto certs = certNotification.GetCertificates();
319                                        if( certs.size() > 1 ) {
320                                                auto ca = certs.back();
321
322                                                unsigned int ca_data_length{};
323                                                unsigned char const* ca_data = ca.GetRawData(ca_data_length);
324
325                                                wxMemoryBuffer updater_root = wxBase64Decode(s_update_cert, wxNO_LEN, wxBase64DecodeMode_SkipWS);
326                                                if( ca_data_length == updater_root.GetDataLen() && !memcmp(ca_data, updater_root.GetData(), ca_data_length) ) {
327                                                        certNotification.m_trusted = true;
328                                                }
329                                        }
330                                }
331                                else {
332                                        certNotification.m_trusted = true;
333                                }
334                        }
335                        engine_->SetAsyncRequestReply(std::move(pData));
336                }
337                break;
338        case nId_data:
339                ProcessData(static_cast<CDataNotification&>(*notification.get()));
340                break;
341        case nId_operation:
342                ProcessOperation(static_cast<COperationNotification const&>(*notification.get()));
343                break;
344        case nId_logmsg:
345                {
346                        auto const& msg = static_cast<CLogmsgNotification const&>(*notification.get());
347                        log_ += msg.msg + _T("\n");
348                }
349                break;
350        default:
351                break;
352        }
353}
354
355UpdaterState CUpdater::ProcessFinishedData(bool can_download)
356{
357        UpdaterState s = UpdaterState::failed;
358
359        ParseData();
360
361        if( version_information_.available_.version_.empty() ) {
362                s = UpdaterState::idle;
363        }
364        else if( !version_information_.available_.url_.empty() ) {
365
366                wxString const temp = GetTempFile();
367                wxString const local_file = GetLocalFile(version_information_.available_, true);
368                if( !local_file.empty() && CLocalFileSystem::GetFileType(local_file) != CLocalFileSystem::unknown) {
369                        local_file_ = local_file;
370                        log_ += wxString::Format(_("Local file is %s\n"), local_file);
371                        s = UpdaterState::newversion_ready;
372                }
373                else {
374                        // We got a checksum over a secure channel already.
375                        m_use_internal_rootcert = false;
376
377                        if( temp.empty() || local_file.empty() ) {
378                                s = UpdaterState::newversion;
379                        }
380                        else {
381                                s = UpdaterState::newversion_downloading;
382                                auto size = CLocalFileSystem::GetSize(temp);
383                                if (size >= 0 && size >= version_information_.available_.size_) {
384                                        s = ProcessFinishedDownload();
385                                }
386                                else if( !can_download || Download( version_information_.available_.url_, GetTempFile() ) != FZ_REPLY_WOULDBLOCK ) {
387                                        s = UpdaterState::newversion;
388                                }
389                        }
390                }
391        }
392        else {
393                s = UpdaterState::newversion;
394        }
395
396        return s;
397}
398
399void CUpdater::ProcessOperation(COperationNotification const& operation)
400{
401        if( state_ != UpdaterState::checking && state_ != UpdaterState::newversion_downloading ) {
402                return;
403        }
404
405        if (pending_commands_.empty()) {
406                SetState(UpdaterState::failed);
407                return;
408        }
409
410
411        UpdaterState s = UpdaterState::failed;
412
413        int res = operation.nReplyCode;
414        if (res == FZ_REPLY_OK || (operation.commandId == Command::disconnect && res & FZ_REPLY_DISCONNECTED)) {
415                pending_commands_.pop_front();
416                res = ContinueDownload();
417                if (res == FZ_REPLY_WOULDBLOCK) {
418                        return;
419                }
420        }
421
422        if (res != FZ_REPLY_OK) {
423                if (state_ != UpdaterState::checking) {
424                        s = UpdaterState::newversion;
425                }
426        }
427        else if( state_ == UpdaterState::checking ) {
428                s = ProcessFinishedData(true);
429        }
430        else {
431                s = ProcessFinishedDownload();
432        }
433        SetState(s);
434}
435
436UpdaterState CUpdater::ProcessFinishedDownload()
437{
438        UpdaterState s = UpdaterState::newversion;
439
440        wxString const temp = GetTempFile();
441        if( temp.empty() ) {
442                s = UpdaterState::newversion;
443        }
444        else if( !VerifyChecksum( temp, version_information_.available_.size_, version_information_.available_.hash_ ) ) {
445                wxLogNull log;
446                wxRemoveFile(temp);
447                s = UpdaterState::newversion;
448        }
449        else {
450                s = UpdaterState::newversion_ready;
451
452                wxString local_file = GetLocalFile( version_information_.available_, false );
453
454                wxLogNull log;
455                if (local_file.empty() || !wxRenameFile( temp, local_file, false ) ) {
456                        s = UpdaterState::newversion;
457                        wxRemoveFile( temp );
458                        log_ += wxString::Format(_("Could not create local file %s\n"), local_file);
459                }
460                else {
461                        local_file_ = local_file;
462                        log_ += wxString::Format(_("Local file is %s\n"), local_file);
463                }
464        }
465        return s;
466}
467
468wxString CUpdater::GetLocalFile(build const& b, bool allow_existing)
469{
470        wxString const fn = GetFilename( b.url_ );
471        wxString const dl = GetDownloadDir().GetPath();
472
473        int i = 1;
474        wxString f = dl + fn;
475
476        while( CLocalFileSystem::GetFileType(f) != CLocalFileSystem::unknown && (!allow_existing || !VerifyChecksum(f, b.size_, b.hash_))) {
477                if( ++i > 99 ) {
478                        return wxString();
479                }
480                wxString ext;
481                int pos;
482                if( !fn.Right(8).CmpNoCase(_T(".tar.bz2")) ) {
483                        pos = fn.size() - 8;
484                }
485                else {
486                        pos = fn.Find('.', true);
487                }
488
489                if( pos == -1 ) {
490                        f = dl + fn + wxString::Format(_T(" (%d)"), i);
491                }
492                else {
493                        f = dl + fn.Left(pos) + wxString::Format(_T(" (%d)"), i) + fn.Mid(pos);
494                }
495        }
496
497        return f;
498}
499
500void CUpdater::ProcessData(CDataNotification& dataNotification)
501{
502        if( state_ != UpdaterState::checking ) {
503                return;
504        }
505
506        int len;
507        char* data = dataNotification.Detach(len);
508
509        if( COptions::Get()->GetOptionVal(OPTION_LOGGING_DEBUGLEVEL) == 4 ) {
510                log_ += wxString::Format(_T("ProcessData %d\n"), len);
511        }
512
513        if( raw_version_information_.size() + len > 131072 ) {
514                log_ += _("Received version information is too large");
515                engine_->Cancel();
516                SetState(UpdaterState::failed);
517        }
518        else {
519                for (int i = 0; i < len; ++i) {
520                        if (data[i] < 10 || (unsigned char)data[i] > 127) {
521                                log_ += _("Received invalid character in version information");
522                                SetState(UpdaterState::failed);
523                                engine_->Cancel();
524                                break;
525                        }
526                }
527        }
528
529        if( state_ == UpdaterState::checking ) {
530                raw_version_information_ += wxString(data, wxConvUTF8, len);
531        }
532        delete [] data;
533}
534
535void CUpdater::ParseData()
536{
537        int64_t const ownVersionNumber = CBuildInfo::ConvertToVersionNumber(CBuildInfo::GetVersion().c_str());
538        version_information_ = version_information();
539
540        wxString raw_version_information = raw_version_information_;
541
542        log_ += wxString::Format(_("Parsing %d bytes of version information.\n"), static_cast<int>(raw_version_information.size()));
543
544        while( !raw_version_information.empty() ) {
545                wxString line;
546                int pos = raw_version_information.Find('\n');
547                if (pos != -1) {
548                        line = raw_version_information.Left(pos);
549                        raw_version_information = raw_version_information.Mid(pos + 1);
550                }
551                else {
552                        line = raw_version_information;
553                        raw_version_information.clear();
554                }
555
556                wxStringTokenizer tokens(line, _T(" \t\n"),  wxTOKEN_STRTOK);
557                if( !tokens.CountTokens() ) {
558                        // After empty line, changelog follows
559                        version_information_.changelog = raw_version_information;
560                        version_information_.changelog.Trim(true);
561                        version_information_.changelog.Trim(false);
562
563                        if( COptions::Get()->GetOptionVal(OPTION_LOGGING_DEBUGLEVEL) == 4 ) {
564                                log_ += wxString::Format(_T("Changelog: %s\n"), version_information_.changelog);
565                        }
566                        break;
567                }
568
569                if( tokens.CountTokens() != 2 && tokens.CountTokens() != 6 ) {
570                        if( COptions::Get()->GetOptionVal(OPTION_LOGGING_DEBUGLEVEL) == 4 ) {
571                                log_ += wxString::Format(_T("Skipping line with %d tokens\n"), static_cast<int>(tokens.CountTokens()));
572                        }
573                        continue;
574                }
575
576                wxString type = tokens.GetNextToken();
577                wxString versionOrDate = tokens.GetNextToken();
578
579                if (type == _T("nightly")) {
580                        CDateTime nightlyDate(versionOrDate, CDateTime::utc);
581                        if (!nightlyDate.IsValid()) {
582                                if (COptions::Get()->GetOptionVal(OPTION_LOGGING_DEBUGLEVEL) == 4) {
583                                        log_ += _T("Could not parse nightly date\n");
584                                }
585                                continue;
586                        }
587
588                        CDateTime buildDate = CBuildInfo::GetBuildDate();
589                        if (!buildDate.IsValid() || !nightlyDate.IsValid() || nightlyDate <= buildDate) {
590                                if( COptions::Get()->GetOptionVal(OPTION_LOGGING_DEBUGLEVEL) == 4 ) {
591                                        log_ += _T("Nightly isn't newer\n");
592                                }
593                                continue;
594                        }
595                }
596                else {
597                        int64_t v = CBuildInfo::ConvertToVersionNumber(versionOrDate.c_str());
598                        if (v <= ownVersionNumber)
599                                continue;
600                }
601
602                build* b = 0;
603                if( type == _T("nightly") && UpdatableBuild() ) {
604                        b = &version_information_.nightly_;
605                }
606                else if( type == _T("release") ) {
607                        b = &version_information_.stable_;
608                }
609                else if( type == _T("beta") ) {
610                        b = &version_information_.beta_;
611                }
612
613                if( b ) {
614                        b->version_ = versionOrDate;
615
616                        if( UpdatableBuild() && tokens.CountTokens() == 4 ) {
617                                wxString const url = tokens.GetNextToken();
618                                wxString const sizestr = tokens.GetNextToken();
619                                wxString const hash_algo = tokens.GetNextToken();
620                                wxString const hash = tokens.GetNextToken();
621
622                                if( GetFilename(url).empty() ) {
623                                        if( COptions::Get()->GetOptionVal(OPTION_LOGGING_DEBUGLEVEL) == 4 ) {
624                                                log_ += wxString::Format(_T("Could not extract filename from URL: %s\n"), url);
625                                        }
626                                        continue;
627                                }
628
629                                if( hash_algo.CmpNoCase(_T("sha512")) ) {
630                                        continue;
631                                }
632
633                                unsigned long long l = 0;
634                                if( !sizestr.ToULongLong(&l) ) {
635                                        if( COptions::Get()->GetOptionVal(OPTION_LOGGING_DEBUGLEVEL) == 4 ) {
636                                                log_ += wxString::Format(_T("Could not parse size: %s"), sizestr);
637                                        }
638                                        continue;
639                                }
640
641                                b->url_ = url;
642                                b->size_ = l;
643                                b->hash_ = hash;
644
645                                // @translator: Two examples: Found new nightly 2014-04-03\n, Found new release 3.9.0.1\n
646                                log_ += wxString::Format(_("Found new %s %s\n"), type, b->version_);
647                        }
648                }
649        }
650
651        version_information_.update_available();
652
653        COptions::Get()->SetOption( OPTION_UPDATECHECK_NEWVERSION, raw_version_information_ );
654}
655
656void CUpdater::OnTimer(wxTimerEvent&)
657{
658        AutoRunIfNeeded();
659}
660
661bool CUpdater::VerifyChecksum(wxString const& file, int64_t size, wxString const& checksum)
662{
663        if (file.empty() || checksum.empty()) {
664                return false;
665        }
666
667        auto filesize = CLocalFileSystem::GetSize(file);
668        if (filesize < 0 || filesize != size) {
669                return false;
670        }
671
672        SHA512_State state;
673        SHA512_Init(&state);
674
675        {
676                wxLogNull null;
677                wxFile f;
678                if (!f.Open(file)) {
679                        return false;
680                }
681                char buffer[65536];
682                ssize_t read;
683                while ((read = f.Read(buffer, sizeof(buffer))) > 0) {
684                        SHA512_Bytes(&state, buffer, read);
685                }
686                if (read < 0) {
687                        return false;
688                }
689        }
690
691        unsigned char raw_digest[64];
692        SHA512_Final(&state, raw_digest);
693
694        wxString digest;
695        for (unsigned int i = 0; i < sizeof(raw_digest); ++i) {
696                unsigned char l = raw_digest[i] >> 4;
697                unsigned char r = raw_digest[i] & 0x0F;
698
699                if (l > 9)
700                        digest += 'a' + l - 10;
701                else
702                        digest += '0' + l;
703
704                if (r > 9)
705                        digest += 'a' + r - 10;
706                else
707                        digest += '0' + r;
708        }
709
710        if (checksum.CmpNoCase(digest)) {
711                log_ += wxString::Format(_("Checksum mismatch on file %s\n"), file);
712                return false;
713        }
714
715        log_ += wxString::Format(_("Checksum match on file %s\n"), file);
716        return true;
717}
718
719wxString CUpdater::GetTempFile() const
720{
721        wxASSERT( !version_information_.available_.hash_.empty() );
722        wxString ret = wxFileName::GetTempDir();
723        if (!ret.empty()) {
724                if (ret.Last() != wxFileName::GetPathSeparator()) {
725                        ret += wxFileName::GetPathSeparator();
726                }
727
728                ret += _T("fzupdate_") + version_information_.available_.hash_.Left(16) + _T(".tmp");
729        }
730
731        return ret;
732}
733
734wxString CUpdater::GetFilename( wxString const& url) const
735{
736        wxString ret;
737        int pos = url.Find('/', true);
738        if (pos != -1) {
739                ret = url.Mid(pos + 1);
740        }
741        size_t p = ret.find_first_of(_T("?#"));
742        if( p != std::string::npos ) {
743                ret = ret.substr(0, p);
744        }
745#ifdef __WXMSW__
746        ret.Replace(_T(":"), _T("_"));
747#endif
748
749        return ret;
750}
751
752void CUpdater::SetState( UpdaterState s )
753{
754        if( s != state_ ) {
755                state_ = s;
756
757                if (s != UpdaterState::checking && s != UpdaterState::newversion_downloading) {
758                        pending_commands_.clear();
759                }
760                build b = version_information_.available_;
761                for (auto const& handler : handlers_ ) {
762                        if( handler ) {
763                                handler->UpdaterStateChanged( s, b );
764                        }
765                }
766        }
767}
768
769wxString CUpdater::DownloadedFile() const
770{
771        wxString ret;
772        if( state_ == UpdaterState::newversion_ready ) {
773                ret = local_file_;
774        }
775        return ret;
776}
777
778void CUpdater::AddHandler( CUpdateHandler& handler )
779{
780        for( auto const& h : handlers_ ) {
781                if (h == &handler) {
782                        return;
783                }
784        }
785        for( auto& h : handlers_ ) {
786                if( !h ) {
787                        h = &handler;
788                        return;
789                }
790        }
791        handlers_.push_back(&handler);
792}
793
794void CUpdater::RemoveHandler( CUpdateHandler& handler )
795{
796        for (auto& h : handlers_) {
797                if (h == &handler) {
798                        // Set to 0 instead of removing from list to avoid issues with reentrancy.
799                        h = 0;
800                        return;
801                }
802        }
803}
804
805int64_t CUpdater::BytesDownloaded() const
806{
807        int64_t ret{-1};
808        if (state_ == UpdaterState::newversion_ready) {
809                if (!local_file_.empty()) {
810                        ret = CLocalFileSystem::GetSize(local_file_);
811                }
812        }
813        else if( state_ == UpdaterState::newversion_downloading ) {
814                wxString const temp = GetTempFile();
815                if( !temp.empty() ) {
816                        ret = CLocalFileSystem::GetSize(temp);
817                }
818        }
819        return ret;
820}
821
822bool CUpdater::UpdatableBuild() const
823{
824        return CBuildInfo::GetBuildType() == _T("nightly") || CBuildInfo::GetBuildType() == _T("official");
825}
826
827#endif
Note: See TracBrowser for help on using the repository browser.