Use correct length for buffer
[TortoiseGit.git] / src / TortoiseProc / CheckForUpdatesDlg.cpp
bloba4515c93dc7560482ff0228041801155f0ea4e26
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2003-2008 - TortoiseSVN
4 // Copyright (C) 2008-2016 - TortoiseGit
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software Foundation,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 #include "stdafx.h"
21 #include "TortoiseProc.h"
22 #include "Git.h"
23 #include "LoglistCommonResource.h"
24 #include "..\version.h"
25 #include "MessageBox.h"
26 #include "CheckForUpdatesDlg.h"
27 #include "registry.h"
28 #include "AppUtils.h"
29 #include "TempFile.h"
30 #include "SmartHandle.h"
31 #include "SysInfo.h"
32 #include "PathUtils.h"
33 #include "DirFileEnum.h"
34 #include "UnicodeUtils.h"
35 #include "UpdateCrypto.h"
36 #include "Win7.h"
37 #include <MsiDefs.h>
38 #include <MsiQuery.h>
40 #pragma comment(lib, "msi.lib")
42 #define SIGNATURE_FILE_ENDING _T(".rsa.asc")
44 #define WM_USER_DISPLAYSTATUS (WM_USER + 1)
45 #define WM_USER_ENDDOWNLOAD (WM_USER + 2)
46 #define WM_USER_FILLCHANGELOG (WM_USER + 3)
48 IMPLEMENT_DYNAMIC(CCheckForUpdatesDlg, CResizableStandAloneDialog)
49 CCheckForUpdatesDlg::CCheckForUpdatesDlg(CWnd* pParent /*=nullptr*/)
50 : CResizableStandAloneDialog(CCheckForUpdatesDlg::IDD, pParent)
51 , m_bShowInfo(FALSE)
52 , m_bForce(FALSE)
53 , m_bVisible(FALSE)
54 , m_pDownloadThread(nullptr)
55 , m_bThreadRunning(FALSE)
56 , m_updateDownloader(nullptr)
58 m_sUpdateDownloadLink = _T("https://tortoisegit.org/download");
61 CCheckForUpdatesDlg::~CCheckForUpdatesDlg()
65 void CCheckForUpdatesDlg::DoDataExchange(CDataExchange* pDX)
67 CResizableStandAloneDialog::DoDataExchange(pDX);
68 DDX_Control(pDX, IDC_LINK, m_link);
69 DDX_Control(pDX, IDC_PROGRESSBAR, m_progress);
70 DDX_Control(pDX, IDC_LIST_DOWNLOADS, m_ctrlFiles);
71 DDX_Control(pDX, IDC_BUTTON_UPDATE, m_ctrlUpdate);
72 DDX_Control(pDX, IDC_LOGMESSAGE, m_cLogMessage);
75 BEGIN_MESSAGE_MAP(CCheckForUpdatesDlg, CResizableStandAloneDialog)
76 ON_WM_TIMER()
77 ON_WM_WINDOWPOSCHANGING()
78 ON_WM_SETCURSOR()
79 ON_WM_DESTROY()
80 ON_BN_CLICKED(IDC_BUTTON_UPDATE, OnBnClickedButtonUpdate)
81 ON_MESSAGE(WM_USER_DISPLAYSTATUS, OnDisplayStatus)
82 ON_MESSAGE(WM_USER_ENDDOWNLOAD, OnEndDownload)
83 ON_MESSAGE(WM_USER_FILLCHANGELOG, OnFillChangelog)
84 ON_REGISTERED_MESSAGE(WM_TASKBARBTNCREATED, OnTaskbarBtnCreated)
85 ON_BN_CLICKED(IDC_DONOTASKAGAIN, &CCheckForUpdatesDlg::OnBnClickedDonotaskagain)
86 END_MESSAGE_MAP()
88 BOOL CCheckForUpdatesDlg::OnInitDialog()
90 CResizableStandAloneDialog::OnInitDialog();
91 CAppUtils::MarkWindowAsUnpinnable(m_hWnd);
93 CString temp;
94 temp.Format(IDS_CHECKNEWER_YOURVERSION, TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, TGIT_VERBUILD);
95 SetDlgItemText(IDC_YOURVERSION, temp);
97 DialogEnableWindow(IDOK, FALSE);
99 m_pTaskbarList.Release();
100 if (FAILED(m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList)))
101 m_pTaskbarList = nullptr;
103 // hide download controls
104 m_ctrlFiles.ShowWindow(SW_HIDE);
105 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_HIDE);
106 RECT rectWindow, rectGroupDownloads, rectOKButton;
107 GetWindowRect(&rectWindow);
108 GetDlgItem(IDC_GROUP_DOWNLOADS)->GetWindowRect(&rectGroupDownloads);
109 GetDlgItem(IDOK)->GetWindowRect(&rectOKButton);
110 LONG bottomDistance = rectWindow.bottom - rectOKButton.bottom;
111 OffsetRect(&rectOKButton, 0, rectGroupDownloads.top - rectOKButton.top);
112 rectWindow.bottom = rectOKButton.bottom + bottomDistance;
113 SetMinTrackSize(CSize(rectWindow.right - rectWindow.left, rectWindow.bottom - rectWindow.top));
114 MoveWindow(&rectWindow);
115 ::MapWindowPoints(nullptr, GetSafeHwnd(), (LPPOINT)&rectOKButton, 2);
116 GetDlgItem(IDOK)->MoveWindow(&rectOKButton);
118 temp.LoadString(IDS_STATUSLIST_COLFILE);
119 m_ctrlFiles.InsertColumn(0, temp, 0, -1);
120 m_ctrlFiles.InsertColumn(1, temp, 0, -1);
121 m_ctrlFiles.SetExtendedStyle(LVS_EX_DOUBLEBUFFER | LVS_EX_CHECKBOXES);
122 m_ctrlFiles.SetColumnWidth(0, 350);
123 m_ctrlFiles.SetColumnWidth(1, 200);
125 m_cLogMessage.Init(-1);
126 m_cLogMessage.SetFont((CString)CRegString(_T("Software\\TortoiseGit\\LogFontName"), _T("Courier New")), (DWORD)CRegDWORD(_T("Software\\TortoiseGit\\LogFontSize"), 8));
127 m_cLogMessage.Call(SCI_SETREADONLY, TRUE);
129 m_updateDownloader = new CUpdateDownloader(GetSafeHwnd(), m_bForce == TRUE, WM_USER_DISPLAYSTATUS, &m_eventStop);
131 if (!AfxBeginThread(CheckThreadEntry, this))
133 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
136 AddAnchor(IDC_YOURVERSION, TOP_LEFT, TOP_RIGHT);
137 AddAnchor(IDC_CURRENTVERSION, TOP_LEFT, TOP_RIGHT);
138 AddAnchor(IDC_CHECKRESULT, TOP_LEFT, TOP_RIGHT);
139 AddAnchor(IDC_LINK, TOP_LEFT, TOP_RIGHT);
140 AddAnchor(IDC_GROUP_CHANGELOG, TOP_LEFT, BOTTOM_RIGHT);
141 AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, BOTTOM_RIGHT);
142 AddAnchor(IDC_GROUP_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
143 AddAnchor(IDC_LIST_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
144 AddAnchor(IDC_PROGRESSBAR, BOTTOM_LEFT, BOTTOM_RIGHT);
145 AddAnchor(IDC_BUTTON_UPDATE, BOTTOM_RIGHT);
146 AddAnchor(IDOK, BOTTOM_CENTER);
148 SetTimer(100, 1000, nullptr);
149 return TRUE;
152 void CCheckForUpdatesDlg::OnDestroy()
154 for (int i = 0; i < m_ctrlFiles.GetItemCount(); ++i)
155 delete (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
157 delete m_updateDownloader;
159 CResizableStandAloneDialog::OnDestroy();
162 void CCheckForUpdatesDlg::OnOK()
164 if (m_bThreadRunning || m_pDownloadThread)
165 return; // Don't exit while downloading
167 CResizableStandAloneDialog::OnOK();
170 void CCheckForUpdatesDlg::OnCancel()
172 if (m_bThreadRunning || m_pDownloadThread)
173 return; // Don't exit while downloading
174 CResizableStandAloneDialog::OnCancel();
177 UINT CCheckForUpdatesDlg::CheckThreadEntry(LPVOID pVoid)
179 return ((CCheckForUpdatesDlg*)pVoid)->CheckThread();
182 UINT CCheckForUpdatesDlg::CheckThread()
184 m_bThreadRunning = TRUE;
186 CString temp;
187 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
189 bool official = false;
191 CRegString checkurluser = CRegString(_T("Software\\TortoiseGit\\UpdateCheckURL"), _T(""));
192 CRegString checkurlmachine = CRegString(_T("Software\\TortoiseGit\\UpdateCheckURL"), _T(""), FALSE, HKEY_LOCAL_MACHINE);
193 CString sCheckURL = checkurluser;
194 if (sCheckURL.IsEmpty())
196 sCheckURL = checkurlmachine;
197 if (sCheckURL.IsEmpty())
199 official = true;
200 bool checkPreview = false;
201 #if PREVIEW
202 checkPreview = true;
203 #else
204 CRegStdDWORD regCheckPreview(L"Software\\TortoiseGit\\VersionCheckPreview", FALSE);
205 if (DWORD(regCheckPreview))
206 checkPreview = true;
207 #endif
208 if (checkPreview)
210 sCheckURL = _T("https://versioncheck.tortoisegit.org/version-preview.txt");
211 SetDlgItemText(IDC_SOURCE, _T("Using preview release channel"));
213 else
214 sCheckURL = _T("https://versioncheck.tortoisegit.org/version.txt");
218 if (!official && sCheckURL.Find(_T("://versioncheck.tortoisegit.org/")) > 0)
219 official = true;
221 if (!official)
222 SetDlgItemText(IDC_SOURCE, _T("Using (unofficial) release channel: ") + sCheckURL);
224 CString ver;
225 CAutoConfig versioncheck(true);
226 CString errorText;
227 DWORD ret = m_updateDownloader->DownloadFile(sCheckURL, tempfile, false);
228 if (!ret && official)
230 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
231 ret = m_updateDownloader->DownloadFile(sCheckURL + SIGNATURE_FILE_ENDING, signatureTempfile, false);
232 if (ret || VerifyIntegrity(tempfile, signatureTempfile, m_updateDownloader))
234 CString error = _T("Could not verify digital signature.");
235 if (ret)
236 error += _T("\r\nError: ") + GetWinINetError(ret) + _T(" (on ") + sCheckURL + SIGNATURE_FILE_ENDING + _T(")");
237 SetDlgItemText(IDC_CHECKRESULT, error);
238 DeleteUrlCacheEntry(sCheckURL);
239 DeleteUrlCacheEntry(sCheckURL + SIGNATURE_FILE_ENDING);
240 goto finish;
243 else if (ret)
245 DeleteUrlCacheEntry(sCheckURL);
246 if (CRegDWORD(_T("Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings\\GlobalUserOffline"), 0))
247 errorText.LoadString(IDS_OFFLINEMODE); // offline mode enabled
248 else
249 errorText.Format(IDS_CHECKNEWER_NETERROR_FORMAT, (LPCTSTR)(GetWinINetError(ret) + _T(" URL: ") + sCheckURL), ret);
250 SetDlgItemText(IDC_CHECKRESULT, errorText);
251 goto finish;
254 git_config_add_file_ondisk(versioncheck, CUnicodeUtils::GetUTF8(tempfile), GIT_CONFIG_LEVEL_GLOBAL, 0);
256 unsigned int major, minor, micro, build;
257 major = minor = micro = build = 0;
258 unsigned __int64 version = 0;
260 if (!versioncheck.GetString(_T("tortoisegit.version"), ver))
262 CString vertemp = ver;
263 major = _ttoi(vertemp);
264 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
265 minor = _ttoi(vertemp);
266 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
267 micro = _ttoi(vertemp);
268 vertemp = vertemp.Mid(vertemp.Find('.') + 1);
269 build = _ttoi(vertemp);
270 version = major;
271 version <<= 16;
272 version += minor;
273 version <<= 16;
274 version += micro;
275 version <<= 16;
276 version += build;
278 if (version == 0)
280 temp.LoadString(IDS_CHECKNEWER_NETERROR);
281 SetDlgItemText(IDC_CHECKRESULT, temp);
282 goto finish;
285 // another versionstring for the filename can be provided
286 // this is needed for preview releases
287 vertemp.Empty();
288 versioncheck.GetString(_T("tortoisegit.versionstring"), vertemp);
289 if (!vertemp.IsEmpty())
290 ver = vertemp;
292 else
294 errorText = _T("Could not parse version check file: ") + g_Git.GetLibGit2LastErr();
295 SetDlgItemText(IDC_CHECKRESULT, errorText);
296 DeleteUrlCacheEntry(sCheckURL);
297 goto finish;
301 BOOL bNewer = FALSE;
302 if (m_bForce)
303 bNewer = TRUE;
304 else if (major > TGIT_VERMAJOR)
305 bNewer = TRUE;
306 else if ((minor > TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
307 bNewer = TRUE;
308 else if ((micro > TGIT_VERMICRO) && (minor == TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
309 bNewer = TRUE;
310 else if ((build > TGIT_VERBUILD) && (micro == TGIT_VERMICRO) && (minor == TGIT_VERMINOR) && (major == TGIT_VERMAJOR))
311 bNewer = TRUE;
313 m_sNewVersionNumber.Format(L"%u.%u.%u.%u", major, minor, micro, build);
314 if (m_sNewVersionNumber != ver)
316 CString versionstr = m_sNewVersionNumber + L" (" + ver + ")";
317 temp.Format(IDS_CHECKNEWER_CURRENTVERSION, (LPCTSTR)versionstr);
319 else
320 temp.Format(IDS_CHECKNEWER_CURRENTVERSION, (LPCTSTR)m_sNewVersionNumber);
321 SetDlgItemText(IDC_CURRENTVERSION, temp);
323 if (bNewer)
325 versioncheck.GetString(_T("tortoisegit.infotext"), temp);
326 if (!temp.IsEmpty())
328 CString tempLink;
329 versioncheck.GetString(_T("tortoisegit.infotexturl"), tempLink);
330 if (!tempLink.IsEmpty()) // find out the download link-URL, if any
331 m_sUpdateDownloadLink = tempLink;
333 else
334 temp.LoadString(IDS_CHECKNEWER_NEWERVERSIONAVAILABLE);
335 SetDlgItemText(IDC_CHECKRESULT, temp);
337 FillChangelog(versioncheck, official);
338 FillDownloads(versioncheck, ver);
340 RemoveAnchor(IDC_GROUP_CHANGELOG);
341 RemoveAnchor(IDC_LOGMESSAGE);
342 RemoveAnchor(IDC_GROUP_DOWNLOADS);
343 RemoveAnchor(IDC_LIST_DOWNLOADS);
344 RemoveAnchor(IDC_PROGRESSBAR);
345 RemoveAnchor(IDC_BUTTON_UPDATE);
346 RemoveAnchor(IDOK);
348 // Show download controls
349 RECT rectWindow, rectProgress, rectGroupDownloads, rectOKButton;
350 GetWindowRect(&rectWindow);
351 m_progress.GetWindowRect(&rectProgress);
352 GetDlgItem(IDC_GROUP_DOWNLOADS)->GetWindowRect(&rectGroupDownloads);
353 GetDlgItem(IDOK)->GetWindowRect(&rectOKButton);
354 LONG bottomDistance = rectWindow.bottom - rectOKButton.bottom;
355 OffsetRect(&rectOKButton, 0, (rectGroupDownloads.bottom + (rectGroupDownloads.bottom - rectProgress.bottom)) - rectOKButton.top);
356 rectWindow.bottom = rectOKButton.bottom + bottomDistance;
357 SetMinTrackSize(CSize(rectWindow.right - rectWindow.left, rectWindow.bottom - rectWindow.top));
358 MoveWindow(&rectWindow);
359 ::MapWindowPoints(nullptr, GetSafeHwnd(), (LPPOINT)&rectOKButton, 2);
360 if (CRegDWORD(_T("Software\\TortoiseGit\\VersionCheck"), TRUE) != FALSE && !m_bForce && !m_bShowInfo)
362 GetDlgItem(IDC_DONOTASKAGAIN)->ShowWindow(SW_SHOW);
363 rectOKButton.left += 60;
364 temp.LoadString(IDS_REMINDMELATER);
365 GetDlgItem(IDOK)->SetWindowText(temp);
366 rectOKButton.right += 160;
368 GetDlgItem(IDOK)->MoveWindow(&rectOKButton);
369 AddAnchor(IDC_GROUP_CHANGELOG, TOP_LEFT, BOTTOM_RIGHT);
370 AddAnchor(IDC_LOGMESSAGE, TOP_LEFT, BOTTOM_RIGHT);
371 AddAnchor(IDC_GROUP_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
372 AddAnchor(IDC_LIST_DOWNLOADS, BOTTOM_LEFT, BOTTOM_RIGHT);
373 AddAnchor(IDC_PROGRESSBAR, BOTTOM_LEFT, BOTTOM_RIGHT);
374 AddAnchor(IDC_BUTTON_UPDATE, BOTTOM_RIGHT);
375 AddAnchor(IDOK, BOTTOM_CENTER);
376 m_ctrlFiles.ShowWindow(SW_SHOW);
377 GetDlgItem(IDC_GROUP_DOWNLOADS)->ShowWindow(SW_SHOW);
378 CenterWindow();
379 m_bShowInfo = TRUE;
381 else if (m_bShowInfo)
383 temp.LoadString(IDS_CHECKNEWER_YOURUPTODATE);
384 SetDlgItemText(IDC_CHECKRESULT, temp);
385 FillChangelog(versioncheck, official);
389 finish:
390 if (!m_sUpdateDownloadLink.IsEmpty())
392 m_link.ShowWindow(SW_SHOW);
393 m_link.SetURL(m_sUpdateDownloadLink);
395 m_bThreadRunning = FALSE;
396 DialogEnableWindow(IDOK, TRUE);
397 return 0;
400 void CCheckForUpdatesDlg::FillDownloads(CAutoConfig& versioncheck, const CString version)
402 #if WIN64
403 const CString x86x64 = _T("64");
404 #else
405 const CString x86x64 = _T("32");
406 #endif
408 versioncheck.GetString(_T("tortoisegit.baseurl"), m_sFilesURL);
409 if (m_sFilesURL.IsEmpty())
410 m_sFilesURL.Format(_T("http://updater.download.tortoisegit.org/tgit/%s/"), (LPCTSTR)version);
412 bool isHotfix = false;
413 versioncheck.GetBool(_T("tortoisegit.hotfix"), isHotfix);
415 if (isHotfix)
416 m_ctrlFiles.InsertItem(0, _T("TortoiseGit Hotfix"));
417 else
418 m_ctrlFiles.InsertItem(0, _T("TortoiseGit"));
419 CString filenameMain, filenamePattern;
420 versioncheck.GetString(_T("tortoisegit.mainfilename"), filenamePattern);
421 if (filenamePattern.IsEmpty())
422 filenamePattern = _T("TortoiseGit-%1!s!-%2!s!bit.msi");
423 filenameMain.FormatMessage(filenamePattern, version, x86x64);
424 m_ctrlFiles.SetItemData(0, (DWORD_PTR)(new CUpdateListCtrl::Entry(filenameMain, CUpdateListCtrl::STATUS_NONE)));
425 m_ctrlFiles.SetCheck(0 , TRUE);
427 if (isHotfix)
429 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
430 return;
433 struct LangPack
435 CString m_PackName;
436 CString m_LangName;
437 DWORD m_LocaleID;
438 CString m_LangCode;
439 bool m_Installed;
441 struct LanguagePacks
443 std::vector<LangPack> availableLangs;
444 std::vector<DWORD> installedLangs;
445 } languagePacks;
447 // set up the language selecting combobox
448 CString path = CPathUtils::GetAppParentDirectory();
449 path = path + _T("Languages\\");
450 CSimpleFileFind finder(path, _T("*.dll"));
451 while (finder.FindNextFileNoDirectories())
453 CString file = finder.GetFilePath();
454 CString filename = finder.GetFileName();
455 if (filename.Left(12).CompareNoCase(_T("TortoiseProc")) == 0)
457 CString sVer = _T(STRPRODUCTVER);
458 sVer = sVer.Left(sVer.ReverseFind('.'));
459 CString sFileVer = CPathUtils::GetVersionFromFile(file);
460 sFileVer = sFileVer.Left(sFileVer.ReverseFind('.'));
461 CString sLoc = filename.Mid(12);
462 sLoc = sLoc.Left(sLoc.GetLength() - 4); // cut off ".dll"
463 if ((sLoc.Left(2) == L"32") && (sLoc.GetLength() > 5))
464 continue;
465 DWORD loc = _tstoi(filename.Mid(12));
466 languagePacks.installedLangs.push_back(loc);
471 git_config_get_multivar_foreach(versioncheck, "tortoisegit.langs", nullptr, [](const git_config_entry* configentry, void* payload) -> int
473 LanguagePacks* languagePacks = (LanguagePacks*)payload;
474 CString langs = CUnicodeUtils::GetUnicode(configentry->value);
476 int nextTokenPos = langs.Find(_T(";"), 5); // be extensible for the future
477 if (nextTokenPos > 0)
478 langs = langs.Left(nextTokenPos);
479 CString sLang = _T("TortoiseGit Language Pack ") + langs.Mid(5);
481 DWORD loc = _tstoi(langs.Mid(0, 4));
482 TCHAR buf[MAX_PATH] = { 0 };
483 GetLocaleInfo(loc, LOCALE_SNATIVELANGNAME, buf, _countof(buf));
484 CString sLang2(buf);
485 GetLocaleInfo(loc, LOCALE_SNATIVECTRYNAME, buf, _countof(buf));
486 if (buf[0])
488 sLang2 += _T(" (");
489 sLang2 += buf;
490 sLang2 += _T(")");
493 bool installed = std::find(languagePacks->installedLangs.cbegin(), languagePacks->installedLangs.cend(), loc) != languagePacks->installedLangs.cend();
494 LangPack pack = { sLang, sLang2, loc, langs.Mid(5), installed };
495 languagePacks->availableLangs.push_back(pack);
497 return 0;
498 }, &languagePacks);
499 std::stable_sort(languagePacks.availableLangs.begin(), languagePacks.availableLangs.end(), [&](const LangPack& a, const LangPack& b) -> int
501 return (a.m_Installed && !b.m_Installed) ? 1 : (!a.m_Installed && b.m_Installed) ? 0 : (a.m_PackName.Compare(b.m_PackName) < 0);
503 filenamePattern.Empty();
504 versioncheck.GetString(_T("tortoisegit.languagepackfilename"), filenamePattern);
505 if (filenamePattern.IsEmpty())
506 filenamePattern = _T("TortoiseGit-LanguagePack-%1!s!-%2!s!bit-%3!s!.msi");
507 for (const auto& langs : languagePacks.availableLangs)
509 int pos = m_ctrlFiles.InsertItem(m_ctrlFiles.GetItemCount(), langs.m_PackName);
510 m_ctrlFiles.SetItemText(pos, 1, langs.m_LangName);
512 CString filename;
513 filename.FormatMessage(filenamePattern, version, x86x64, langs.m_LangCode, langs.m_LocaleID);
514 m_ctrlFiles.SetItemData(pos, (DWORD_PTR)(new CUpdateListCtrl::Entry(filename, CUpdateListCtrl::STATUS_NONE)));
516 if (langs.m_Installed)
517 m_ctrlFiles.SetCheck(pos , TRUE);
519 DialogEnableWindow(IDC_BUTTON_UPDATE, TRUE);
522 void CCheckForUpdatesDlg::FillChangelog(CAutoConfig& versioncheck, bool official)
524 ProjectProperties pp;
525 pp.lProjectLanguage = -1;
526 if (versioncheck.GetString(_T("tortoisegit.issuesurl"), pp.sUrl))
527 pp.sUrl = _T("https://tortoisegit.org/issue/%BUGID%");
528 if (!pp.sUrl.IsEmpty())
530 pp.SetCheckRe(_T("[Ii]ssues?:?(\\s*(,|and)?\\s*#?\\d+)+"));
531 pp.SetBugIDRe(_T("(\\d+)"));
533 m_cLogMessage.Init(pp);
535 CString sChangelogURL;
536 versioncheck.GetString(_T("TortoiseGit.changelogurl"), sChangelogURL);
537 if (sChangelogURL.IsEmpty())
538 sChangelogURL = _T("https://versioncheck.tortoisegit.org/changelog.txt");
539 else
541 CString tmp(sChangelogURL);
542 sChangelogURL.FormatMessage(tmp, TGIT_VERMAJOR, TGIT_VERMINOR, TGIT_VERMICRO, m_updateDownloader->m_sWindowsPlatform, m_updateDownloader->m_sWindowsVersion, m_updateDownloader->m_sWindowsServicePack);
545 CString tempchangelogfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
546 DWORD err;
547 if ((err = m_updateDownloader->DownloadFile(sChangelogURL, tempchangelogfile, false)) != ERROR_SUCCESS)
549 CString msg = _T("Could not load changelog.\r\nError: ") + GetWinINetError(err) + _T(" (on ") + sChangelogURL + _T(")");
550 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)msg));
551 return;
553 if (official)
555 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
556 if ((err = m_updateDownloader->DownloadFile(sChangelogURL + SIGNATURE_FILE_ENDING, signatureTempfile, false)) != ERROR_SUCCESS || VerifyIntegrity(tempchangelogfile, signatureTempfile, m_updateDownloader))
558 CString error = _T("Could not verify digital signature.");
559 if (err)
560 error += _T("\r\nError: ") + GetWinINetError(err) + _T(" (on ") + sChangelogURL + SIGNATURE_FILE_ENDING + _T(")");
561 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)error));
562 DeleteUrlCacheEntry(sChangelogURL);
563 DeleteUrlCacheEntry(sChangelogURL + SIGNATURE_FILE_ENDING);
564 return;
568 CString temp;
569 CStdioFile file;
570 if (file.Open(tempchangelogfile, CFile::modeRead | CFile::typeBinary))
572 auto buf = std::make_unique<BYTE[]>((UINT)file.GetLength());
573 UINT read = file.Read(buf.get(), (UINT)file.GetLength());
574 bool skipBom = read >= 3 && buf[0] == 0xEF && buf[1] == 0xBB && buf[2] == 0xBF;
575 CGit::StringAppend(&temp, buf.get() + (skipBom ? 3 : 0), CP_UTF8, read - (skipBom ? 3 : 0));
577 else
578 temp = _T("Could not open downloaded changelog file.");
579 ::SendMessage(m_hWnd, WM_USER_FILLCHANGELOG, 0, reinterpret_cast<LPARAM>((LPCTSTR)temp));
582 void CCheckForUpdatesDlg::OnTimer(UINT_PTR nIDEvent)
584 if (nIDEvent == 100)
586 if (m_bThreadRunning == FALSE)
588 KillTimer(100);
589 if (m_bShowInfo)
591 m_bVisible = TRUE;
592 ShowWindow(SW_SHOWNORMAL);
594 else
595 EndDialog(0);
598 CResizableStandAloneDialog::OnTimer(nIDEvent);
601 void CCheckForUpdatesDlg::OnWindowPosChanging(WINDOWPOS* lpwndpos)
603 CResizableStandAloneDialog::OnWindowPosChanging(lpwndpos);
604 if (m_bVisible == FALSE)
605 lpwndpos->flags &= ~SWP_SHOWWINDOW;
608 BOOL CCheckForUpdatesDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
610 HCURSOR hCur = LoadCursor(nullptr, IDC_ARROW);
611 SetCursor(hCur);
612 return CResizableStandAloneDialog::OnSetCursor(pWnd, nHitTest, message);
615 void CCheckForUpdatesDlg::OnBnClickedButtonUpdate()
617 CString title;
618 m_ctrlUpdate.GetWindowText(title);
619 if (!m_pDownloadThread && title == CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)))
621 bool isOneSelected = false;
622 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
624 if (m_ctrlFiles.GetCheck(i))
626 isOneSelected = true;
627 break;
630 if (!isOneSelected)
631 return;
633 m_eventStop.ResetEvent();
635 m_pDownloadThread = ::AfxBeginThread(DownloadThreadEntry, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
636 if (m_pDownloadThread)
638 m_pDownloadThread->m_bAutoDelete = FALSE;
639 m_pDownloadThread->ResumeThread();
641 GetDlgItem(IDC_BUTTON_UPDATE)->SetWindowText(CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)));
643 else
644 CMessageBox::Show(GetSafeHwnd(), IDS_ERR_THREADSTARTFAILED, IDS_APPNAME, MB_OK | MB_ICONERROR);
646 else if (title == CString(MAKEINTRESOURCE(IDS_ABORTBUTTON)))
648 // Abort
649 m_eventStop.SetEvent();
651 else
653 CString folder = GetDownloadsDirectory();
654 if (m_ctrlUpdate.GetCurrentEntry() == 0)
656 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
658 CUpdateListCtrl::Entry *data = (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
659 if (m_ctrlFiles.GetCheck(i) == TRUE)
660 ShellExecute(GetSafeHwnd(), _T("open"), folder + data->m_filename, nullptr, nullptr, SW_SHOWNORMAL);
662 CResizableStandAloneDialog::OnOK();
664 else if (m_ctrlUpdate.GetCurrentEntry() == 1)
665 ShellExecute(GetSafeHwnd(), _T("open"), folder, nullptr, nullptr, SW_SHOWNORMAL);
667 m_ctrlUpdate.SetCurrentEntry(0);
671 UINT CCheckForUpdatesDlg::DownloadThreadEntry(LPVOID pVoid)
673 return ((CCheckForUpdatesDlg*)pVoid)->DownloadThread();
676 bool CCheckForUpdatesDlg::VerifyUpdateFile(const CString& filename, const CString& filenameSignature, const CString& reportingFilename)
678 if (VerifyIntegrity(filename, filenameSignature, m_updateDownloader) != 0)
680 m_sErrors += reportingFilename + SIGNATURE_FILE_ENDING + L": Invalid digital signature.\r\n";
681 return false;
684 MSIHANDLE hSummary;
685 DWORD ret = 0;
686 if ((ret = MsiGetSummaryInformation(NULL, filename, 0, &hSummary)) != 0)
688 CString sFileVer = CPathUtils::GetVersionFromFile(filename);
689 sFileVer.Trim();
690 if (sFileVer.IsEmpty())
692 m_sErrors.AppendFormat(L"%s: Invalid filetype found (neither executable nor MSI).\r\n", (LPCTSTR)reportingFilename);
693 CTraceToOutputDebugString::Instance()(_T(__FUNCTION__) _T(": MsiGetSummaryInformation reported: %s\n"), (LPCTSTR)CFormatMessageWrapper(ret));
694 return false;
696 else if (sFileVer == m_sNewVersionNumber)
697 return true;
699 m_sErrors.AppendFormat(L"%s: Version number of downloaded file doesn't match (expected: \"%s\", got: \"%s\").\r\n", (LPCTSTR)reportingFilename, (LPCTSTR)m_sNewVersionNumber, (LPCTSTR)sFileVer);
700 return false;
702 SCOPE_EXIT{ MsiCloseHandle(hSummary); };
704 UINT uiDataType = 0;
705 DWORD cchValue = 4096;
706 CString buffer;
707 if (MsiSummaryInfoGetProperty(hSummary, PID_SUBJECT, &uiDataType, nullptr, nullptr, CStrBuf(buffer, cchValue + 1), &cchValue))
709 m_sErrors.AppendFormat(L"%s: Error obtaining version of MSI file (%s).\r\n", (LPCTSTR)reportingFilename, (LPCTSTR)CFormatMessageWrapper(ret));
710 return false;
713 if (VT_LPSTR != uiDataType)
715 m_sErrors.AppendFormat(L"%s: Error obtaining version of MSI file (invalid data type returned).\r\n", (LPCTSTR)reportingFilename);
716 return false;
719 CString sFileVer = buffer.Right(m_sNewVersionNumber.GetLength() + 1);
720 if (sFileVer != L"v" + m_sNewVersionNumber)
722 m_sErrors.AppendFormat(L"%s: Version number of downloaded file doesn't match (expected: \"v%s\", got: \"%s\").\r\n", (LPCTSTR)reportingFilename, (LPCTSTR)m_sNewVersionNumber, (LPCTSTR)sFileVer);
723 return false;
726 return true;
729 bool CCheckForUpdatesDlg::Download(CString filename)
731 CString url = m_sFilesURL + filename;
732 CString destFilename = GetDownloadsDirectory() + filename;
733 if (PathFileExists(destFilename) && PathFileExists(destFilename + SIGNATURE_FILE_ENDING))
735 if (VerifyUpdateFile(destFilename, destFilename + SIGNATURE_FILE_ENDING, destFilename))
736 return true;
737 else
739 DeleteFile(destFilename);
740 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
741 DeleteUrlCacheEntry(url);
742 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
746 m_progress.SetRange32(0, 1);
747 m_progress.SetPos(0);
748 m_progress.ShowWindow(SW_SHOW);
749 if (m_pTaskbarList)
751 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
752 m_pTaskbarList->SetProgressValue(m_hWnd, 0, 1);
755 CString tempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
756 CString signatureTempfile = CTempFiles::Instance().GetTempFilePath(true).GetWinPathString();
757 DWORD ret = m_updateDownloader->DownloadFile(url, tempfile, true);
758 if (!ret)
760 ret = m_updateDownloader->DownloadFile(url + SIGNATURE_FILE_ENDING, signatureTempfile, true);
761 if (ret)
762 m_sErrors += url + SIGNATURE_FILE_ENDING + _T(": ") + GetWinINetError(ret) + _T("\r\n");
763 m_progress.SetPos(m_progress.GetPos() + 1);
764 if (m_pTaskbarList)
766 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
767 int minValue, maxValue;
768 m_progress.GetRange(minValue, maxValue);
769 m_pTaskbarList->SetProgressValue(m_hWnd, m_progress.GetPos(), maxValue);
772 else
773 m_sErrors += url + _T(": ") + GetWinINetError(ret) + _T("\r\n");
774 if (!ret)
776 if (VerifyUpdateFile(tempfile, signatureTempfile, url))
778 DeleteFile(destFilename);
779 DeleteFile(destFilename + SIGNATURE_FILE_ENDING);
780 if (!MoveFile(tempfile, destFilename))
782 m_sErrors.AppendFormat(L"Could not move \"%s\" to \"%s\".\r\n", (LPCTSTR)filename, (LPCTSTR)tempfile, (LPCTSTR)destFilename);
783 return false;
785 if (!MoveFile(signatureTempfile, destFilename + SIGNATURE_FILE_ENDING))
787 m_sErrors.AppendFormat(L"Could not move \"%s\" to \"%s\".\r\n", (LPCTSTR)filename, (LPCTSTR)tempfile, (LPCTSTR)destFilename);
788 return false;
790 return true;
792 DeleteUrlCacheEntry(url);
793 DeleteUrlCacheEntry(url + SIGNATURE_FILE_ENDING);
795 return false;
798 UINT CCheckForUpdatesDlg::DownloadThread()
800 m_ctrlFiles.SetExtendedStyle(m_ctrlFiles.GetExtendedStyle() & ~LVS_EX_CHECKBOXES);
801 m_sErrors.Empty();
802 BOOL result = TRUE;
803 for (int i = 0; i < (int)m_ctrlFiles.GetItemCount(); ++i)
805 m_ctrlFiles.EnsureVisible(i, FALSE);
806 CRect rect;
807 m_ctrlFiles.GetItemRect(i, &rect, LVIR_BOUNDS);
808 CUpdateListCtrl::Entry *data = (CUpdateListCtrl::Entry *)m_ctrlFiles.GetItemData(i);
809 if (m_ctrlFiles.GetCheck(i) == TRUE)
811 data->m_status = CUpdateListCtrl::STATUS_DOWNLOADING;
812 m_ctrlFiles.InvalidateRect(rect);
813 if (Download(data->m_filename))
814 data->m_status = CUpdateListCtrl::STATUS_SUCCESS;
815 else
817 data->m_status = CUpdateListCtrl::STATUS_FAIL;
818 result = FALSE;
821 else
822 data->m_status = CUpdateListCtrl::STATUS_IGNORE;
823 m_ctrlFiles.InvalidateRect(rect);
826 ::PostMessage(GetSafeHwnd(), WM_USER_ENDDOWNLOAD, 0, 0);
828 return result;
831 LRESULT CCheckForUpdatesDlg::OnEndDownload(WPARAM, LPARAM)
833 ASSERT(m_pDownloadThread);
835 // wait until the thread terminates
836 DWORD dwExitCode;
837 if (::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode) && dwExitCode == STILL_ACTIVE)
838 ::WaitForSingleObject(m_pDownloadThread->m_hThread, INFINITE);
840 // make sure we always have the correct exit code
841 ::GetExitCodeThread(m_pDownloadThread->m_hThread, &dwExitCode);
843 delete m_pDownloadThread;
844 m_pDownloadThread = nullptr;
846 m_progress.ShowWindow(SW_HIDE);
848 if (dwExitCode == TRUE)
850 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_PROC_INSTALL)));
851 m_ctrlUpdate.AddEntry(CString(MAKEINTRESOURCE(IDS_CHECKUPDATE_DESTFOLDER)));
852 m_ctrlUpdate.Invalidate();
853 if (m_pTaskbarList)
854 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
856 else
858 m_ctrlUpdate.SetWindowText(CString(MAKEINTRESOURCE(IDS_PROC_DOWNLOAD)));
859 if (m_pTaskbarList)
860 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_ERROR);
861 CString tmp;
862 tmp.LoadString(IDS_ERR_FAILEDUPDATEDOWNLOAD);
863 if (!m_sErrors.IsEmpty())
864 tmp += _T("\r\n\r\nErrors:\r\n") + m_sErrors;
865 CMessageBox::Show(GetSafeHwnd(), tmp, L"TortoiseGit", MB_ICONERROR);
866 if (m_pTaskbarList)
867 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NOPROGRESS);
870 return 0;
873 LRESULT CCheckForUpdatesDlg::OnFillChangelog(WPARAM, LPARAM lParam)
875 ASSERT(lParam);
877 LPCTSTR changelog = reinterpret_cast<LPCTSTR>(lParam);
878 m_cLogMessage.Call(SCI_SETREADONLY, FALSE);
879 m_cLogMessage.SetText(changelog);
880 m_cLogMessage.Call(SCI_SETREADONLY, TRUE);
881 m_cLogMessage.Call(SCI_GOTOPOS, 0);
883 return 0;
886 CString CCheckForUpdatesDlg::GetDownloadsDirectory()
888 CString folder;
890 PWSTR wcharPtr = nullptr;
891 if (SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Downloads, KF_FLAG_CREATE, nullptr, &wcharPtr)))
893 folder = wcharPtr;
894 CoTaskMemFree(wcharPtr);
895 return folder.TrimRight(_T("\\")) + _T("\\");
898 return folder;
901 LRESULT CCheckForUpdatesDlg::OnDisplayStatus(WPARAM, LPARAM lParam)
903 const CUpdateDownloader::DOWNLOADSTATUS *const pDownloadStatus = reinterpret_cast<CUpdateDownloader::DOWNLOADSTATUS *>(lParam);
904 if (pDownloadStatus)
906 ASSERT(::AfxIsValidAddress(pDownloadStatus, sizeof(CUpdateDownloader::DOWNLOADSTATUS)));
908 m_progress.SetRange32(0, pDownloadStatus->ulProgressMax);
909 m_progress.SetPos(pDownloadStatus->ulProgress);
910 if (m_pTaskbarList)
912 m_pTaskbarList->SetProgressState(m_hWnd, TBPF_NORMAL);
913 m_pTaskbarList->SetProgressValue(m_hWnd, pDownloadStatus->ulProgress, pDownloadStatus->ulProgressMax);
917 return 0;
920 LRESULT CCheckForUpdatesDlg::OnTaskbarBtnCreated(WPARAM /*wParam*/, LPARAM /*lParam*/)
922 m_pTaskbarList.Release();
923 m_pTaskbarList.CoCreateInstance(CLSID_TaskbarList);
924 return 0;
927 CString CCheckForUpdatesDlg::GetWinINetError(DWORD err)
929 CString readableError = CFormatMessageWrapper(err);
930 if (readableError.IsEmpty())
932 for (const CString& module : { _T("wininet.dll"), _T("urlmon.dll") })
934 LPTSTR buffer;
935 FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS | FORMAT_MESSAGE_FROM_HMODULE, GetModuleHandle(module), err, 0, (LPTSTR)&buffer, 0, nullptr);
936 readableError = buffer;
937 LocalFree(buffer);
938 if (!readableError.IsEmpty())
939 break;
942 return readableError.Trim();
945 void CCheckForUpdatesDlg::OnBnClickedDonotaskagain()
947 if (CMessageBox::Show(GetSafeHwnd(), IDS_DISABLEUPDATECHECKS, IDS_APPNAME, 2, IDI_QUESTION, IDS_DISABLEUPDATECHECKSBUTTON, IDS_ABORTBUTTON) == 1)
949 CRegDWORD(_T("Software\\TortoiseGit\\VersionCheck")) = FALSE;
950 OnOK();