Drop unnecessary code
[TortoiseGit.git] / src / Utils / Hooks.cpp
blobf65afc7ae044cf26f5177db9e8d0208877c8b72d
1 // TortoiseGit - a Windows shell extension for easy version control
3 // Copyright (C) 2011-2016, 2018-2019 - TortoiseGit
4 // Copyright (C) 2007-2008 - TortoiseSVN
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 "Hooks.h"
22 #include "registry.h"
23 #include "StringUtils.h"
24 #include "TempFile.h"
25 #include "SmartHandle.h"
26 #include "Git.h"
27 #include "resource.h"
28 #include <afxtaskdialog.h>
29 #include <WinCrypt.h>
31 CHooks* CHooks::m_pInstance;
32 CTGitPath CHooks::m_RootPath;
34 static CString CalcSHA256(const CString& text)
36 HCRYPTPROV hProv = 0;
37 if (!CryptAcquireContext(&hProv, nullptr, nullptr, PROV_RSA_AES, CRYPT_VERIFYCONTEXT))
38 return L"";
39 SCOPE_EXIT{ CryptReleaseContext(hProv, 0); };
41 HCRYPTHASH hHash = 0;
42 if (!CryptCreateHash(hProv, CALG_SHA_256, 0, 0, &hHash))
43 return L"";
44 SCOPE_EXIT{ CryptDestroyHash(hHash); };
46 CStringA textA = CUnicodeUtils::GetUTF8(text);
47 if (!CryptHashData(hHash, (LPBYTE)(LPCSTR)textA, textA.GetLength(), 0))
48 return L"";
50 CString hash;
51 BYTE rgbHash[32];
52 DWORD cbHash = _countof(rgbHash);
53 if (!CryptGetHashParam(hHash, HP_HASHVAL, rgbHash, &cbHash, 0))
54 return L"";
56 for (DWORD i = 0; i < cbHash; i++)
58 BYTE hi = rgbHash[i] >> 4;
59 BYTE lo = rgbHash[i] & 0xf;
60 hash.AppendChar(hi + (hi > 9 ? 87 : 48));
61 hash.AppendChar(lo + (lo > 9 ? 87 : 48));
64 return hash;
67 CHooks::CHooks()
71 CHooks::~CHooks()
75 bool CHooks::Create()
77 if (!m_pInstance)
78 m_pInstance = new CHooks();
79 CRegString reghooks(L"Software\\TortoiseGit\\hooks");
80 CString strhooks = reghooks;
81 ParseHookString(strhooks, false);
82 return true;
85 CHooks& CHooks::Instance()
87 return *m_pInstance;
90 void CHooks::Destroy()
92 delete m_pInstance;
95 bool CHooks::Save()
97 CString strhooks;
98 for (hookiterator it = begin(); it != end(); ++it)
100 if (it->second.bLocal)
101 continue;
102 strhooks += GetHookTypeString(it->first.htype);
103 strhooks += '\n';
104 if (!it->second.bEnabled)
105 strhooks += '!';
106 strhooks += it->first.path.GetWinPathString();
107 strhooks += '\n';
108 strhooks += it->second.commandline;
109 strhooks += '\n';
110 strhooks += (it->second.bWait ? L"true" : L"false");
111 strhooks += '\n';
112 strhooks += (it->second.bShow ? L"show" : L"hide");
113 strhooks += '\n';
115 CRegString reghooks(L"Software\\TortoiseGit\\hooks");
116 reghooks = strhooks;
117 if (reghooks.GetLastError())
118 return false;
120 if (g_Git.m_CurrentDir.IsEmpty() || GitAdminDir::IsBareRepo(g_Git.m_CurrentDir))
121 return true;
123 // load the .tgitconfig file
124 CAutoConfig gitconfig(true);
125 git_config_add_file_ondisk(gitconfig, CGit::GetGitPathStringA(g_Git.CombinePath(L".tgitconfig")), GIT_CONFIG_LEVEL_LOCAL, nullptr, FALSE); // this needs to have the second highest priority
127 // delete all existing local hooks
128 for (const auto& hook : { PROJECTPROPNAME_STARTCOMMITHOOK, PROJECTPROPNAME_PRECOMMITHOOK, PROJECTPROPNAME_POSTCOMMITHOOK, PROJECTPROPNAME_PREPUSHHOOK, PROJECTPROPNAME_POSTPUSHHOOK, PROJECTPROPNAME_PREREBASEHOOK })
130 CStringA hookA = CUnicodeUtils::GetUTF8(hook);
131 for (const auto& val : { "cmdline", "wait", "show" })
133 git_config_delete_entry(gitconfig, hookA + val);
137 // now save the local hooks to .tgitconfig
138 for (const auto& hook : CHooks::Instance())
140 if (!hook.second.bLocal)
141 continue;
143 CStringA sHookPropName;
144 switch (hook.first.htype)
146 case start_commit_hook:
147 sHookPropName = CUnicodeUtils::GetUTF8(PROJECTPROPNAME_STARTCOMMITHOOK);
148 break;
149 case pre_commit_hook:
150 sHookPropName = CUnicodeUtils::GetUTF8(PROJECTPROPNAME_PRECOMMITHOOK);
151 break;
152 case post_commit_hook:
153 sHookPropName = CUnicodeUtils::GetUTF8(PROJECTPROPNAME_POSTCOMMITHOOK);
154 break;
155 case pre_push_hook:
156 sHookPropName = CUnicodeUtils::GetUTF8(PROJECTPROPNAME_PREPUSHHOOK);
157 break;
158 case post_push_hook:
159 sHookPropName = CUnicodeUtils::GetUTF8(PROJECTPROPNAME_POSTPUSHHOOK);
160 break;
161 case pre_rebase_hook:
162 sHookPropName = CUnicodeUtils::GetUTF8(PROJECTPROPNAME_PREREBASEHOOK);
163 break;
165 git_config_set_string(gitconfig, sHookPropName + "cmdline", CUnicodeUtils::GetUTF8(hook.second.commandline));
166 git_config_set_bool(gitconfig, sHookPropName + "wait", hook.second.bWait);
167 git_config_set_bool(gitconfig, sHookPropName + "show", hook.second.bShow);
170 return true;
173 bool CHooks::Remove(const hookkey &key)
175 return (erase(key) > 0);
178 void CHooks::Add(hooktype ht, const CTGitPath& Path, LPCTSTR szCmd, bool bWait, bool bShow, bool bEnabled, bool bLocal)
180 hookkey key;
181 key.htype = ht;
182 key.path = Path;
183 hookiterator it = find(key);
184 if (it!=end())
185 erase(it);
187 hookcmd cmd;
188 cmd.commandline = szCmd;
189 cmd.bWait = bWait;
190 cmd.bShow = bShow;
191 cmd.bEnabled = bEnabled;
192 cmd.bLocal = bLocal;
193 insert(std::pair<hookkey, hookcmd>(key, cmd));
196 bool CHooks::SetEnabled(const hookkey& k, bool bEnabled)
198 auto it = find(k);
199 if (it == end())
200 return false;
201 if (it->second.bEnabled == bEnabled)
202 return false;
203 it->second.bEnabled = bEnabled;
204 return true;
207 CString CHooks::GetHookTypeString(hooktype t)
209 switch (t)
211 case start_commit_hook:
212 return L"start_commit_hook";
213 case pre_commit_hook:
214 return L"pre_commit_hook";
215 case post_commit_hook:
216 return L"post_commit_hook";
217 case pre_push_hook:
218 return L"pre_push_hook";
219 case post_push_hook:
220 return L"post_push_hook";
221 case pre_rebase_hook:
222 return L"pre_rebase_hook";
224 return L"";
227 hooktype CHooks::GetHookType(const CString& s)
229 if (s.Compare(L"start_commit_hook") == 0)
230 return start_commit_hook;
231 if (s.Compare(L"pre_commit_hook") == 0)
232 return pre_commit_hook;
233 if (s.Compare(L"post_commit_hook") == 0)
234 return post_commit_hook;
235 if (s.Compare(L"pre_push_hook") == 0)
236 return pre_push_hook;
237 if (s.Compare(L"post_push_hook") == 0)
238 return post_push_hook;
239 if (s.Compare(L"pre_rebase_hook") == 0)
240 return pre_rebase_hook;
242 return unknown_hook;
245 void CHooks::SetProjectProperties(const CTGitPath& Path, const ProjectProperties& pp)
247 m_RootPath = Path;
249 CString sHookString;
250 if (!pp.sPreCommitHook.IsEmpty())
251 sHookString += GetHookTypeString(pre_commit_hook) + L"\n?\n" + pp.sPreCommitHook + L"\n";
252 if (!pp.sStartCommitHook.IsEmpty())
253 sHookString += GetHookTypeString(start_commit_hook) + L"\n?\n" + pp.sStartCommitHook + L"\n";
254 if (!pp.sPostCommitHook.IsEmpty())
255 sHookString += GetHookTypeString(post_commit_hook) + L"\n?\n" + pp.sPostCommitHook + L"\n";
256 if (!pp.sPrePushHook.IsEmpty())
257 sHookString += GetHookTypeString(pre_push_hook) + L"\n?\n" + pp.sPrePushHook + L"\n";
258 if (!pp.sPostPushHook.IsEmpty())
259 sHookString += GetHookTypeString(post_push_hook) + L"\n?\n" + pp.sPostPushHook + L"\n";
260 if (!pp.sPreRebaseHook.IsEmpty())
261 sHookString += GetHookTypeString(pre_rebase_hook) + L"\n?\n" + pp.sPreRebaseHook + L"\n";
262 ParseHookString(sHookString, true);
265 void CHooks::AddParam(CString& sCmd, const CString& param)
267 sCmd += L" \"";
268 sCmd += param;
269 sCmd += L'"';
272 void CHooks::AddPathParam(CString& sCmd, const CTGitPathList& pathList)
274 CTGitPath temppath = CTempFiles::Instance().GetTempFilePath(true);
275 pathList.WriteToFile(temppath.GetWinPathString(), true);
276 AddParam(sCmd, temppath.GetWinPathString());
279 void CHooks::AddCWDParam(CString& sCmd, const CString& workingTree)
281 AddParam(sCmd, workingTree);
284 void CHooks::AddErrorParam(CString& sCmd, const CString& error)
286 CTGitPath tempPath;
287 tempPath = CTempFiles::Instance().GetTempFilePath(true);
288 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)error);
289 AddParam(sCmd, tempPath.GetWinPathString());
292 CTGitPath CHooks::AddMessageFileParam(CString& sCmd, const CString& message)
294 CTGitPath tempPath;
295 tempPath = CTempFiles::Instance().GetTempFilePath(true);
296 CStringUtils::WriteStringToTextFile(tempPath.GetWinPath(), (LPCTSTR)message);
297 AddParam(sCmd, tempPath.GetWinPathString());
298 return tempPath;
301 bool CHooks::StartCommit(HWND hWnd, const CString& workingTree, const CTGitPathList& pathList, CString& message, DWORD& exitcode, CString& error)
303 auto it = FindItem(start_commit_hook, workingTree);
304 if (it == end())
305 return false;
306 if (!ApproveHook(hWnd, it, exitcode))
308 error.LoadString(IDS_USERCANCELLED);
309 return true;
311 CString sCmd = it->second.commandline;
312 sCmd.Replace(L"%root%", m_RootPath.GetWinPathString());
313 AddPathParam(sCmd, pathList);
314 CTGitPath temppath = AddMessageFileParam(sCmd, message);
315 AddCWDParam(sCmd, workingTree);
316 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
317 if (!exitcode && !temppath.IsEmpty())
319 CStringUtils::ReadStringFromTextFile(temppath.GetWinPathString(), message);
321 return true;
324 bool CHooks::PreCommit(HWND hWnd, const CString& workingTree, const CTGitPathList& pathList, CString& message, DWORD& exitcode, CString& error)
326 auto it = FindItem(pre_commit_hook, workingTree);
327 if (it == end())
328 return false;
329 if (!ApproveHook(hWnd, it, exitcode))
331 error.LoadString(IDS_USERCANCELLED);
332 return true;
334 CString sCmd = it->second.commandline;
335 sCmd.Replace(L"%root%", m_RootPath.GetWinPathString());
336 AddPathParam(sCmd, pathList);
337 CTGitPath temppath = AddMessageFileParam(sCmd, message);
338 AddCWDParam(sCmd, workingTree);
339 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
340 if (!exitcode && !temppath.IsEmpty())
341 CStringUtils::ReadStringFromTextFile(temppath.GetWinPathString(), message);
342 return true;
345 bool CHooks::PostCommit(HWND hWnd, const CString& workingTree, bool amend, DWORD& exitcode, CString& error)
347 auto it = FindItem(post_commit_hook, workingTree);
348 if (it == end())
349 return false;
350 if (!ApproveHook(hWnd, it, exitcode))
352 error.LoadString(IDS_USERCANCELLED);
353 return true;
355 CString sCmd = it->second.commandline;
356 sCmd.Replace(L"%root%", m_RootPath.GetWinPathString());
357 AddCWDParam(sCmd, workingTree);
358 if (amend)
359 AddParam(sCmd, L"true");
360 else
361 AddParam(sCmd, L"false");
362 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
363 return true;
366 bool CHooks::PrePush(HWND hWnd, const CString& workingTree, DWORD& exitcode, CString& error)
368 auto it = FindItem(pre_push_hook, workingTree);
369 if (it == end())
370 return false;
371 if (!ApproveHook(hWnd, it, exitcode))
373 error.LoadString(IDS_USERCANCELLED);
374 return true;
376 CString sCmd = it->second.commandline;
377 sCmd.Replace(L"%root%", m_RootPath.GetWinPathString());
378 AddErrorParam(sCmd, error);
379 AddCWDParam(sCmd, workingTree);
380 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
381 return true;
384 bool CHooks::PostPush(HWND hWnd, const CString& workingTree, DWORD& exitcode, CString& error)
386 auto it = FindItem(post_push_hook, workingTree);
387 if (it == end())
388 return false;
389 if (!ApproveHook(hWnd, it, exitcode))
391 error.LoadString(IDS_USERCANCELLED);
392 return true;
394 CString sCmd = it->second.commandline;
395 sCmd.Replace(L"%root%", m_RootPath.GetWinPathString());
396 AddErrorParam(sCmd, error);
397 AddCWDParam(sCmd, workingTree);
398 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
399 return true;
402 bool CHooks::PreRebase(HWND hWnd, const CString& workingTree, const CString& upstream, const CString& rebasedBranch, DWORD& exitcode, CString& error)
404 auto it = FindItem(pre_rebase_hook, workingTree);
405 if (it == end())
406 return false;
407 if (!ApproveHook(hWnd, it, exitcode))
409 error.LoadString(IDS_USERCANCELLED);
410 return true;
412 CString sCmd = it->second.commandline;
413 sCmd.Replace(L"%root%", m_RootPath.GetWinPathString());
414 AddParam(sCmd, upstream);
415 AddParam(sCmd, rebasedBranch);
416 AddErrorParam(sCmd, error);
417 AddCWDParam(sCmd, workingTree);
418 exitcode = RunScript(sCmd, workingTree, error, it->second.bWait, it->second.bShow);
419 return true;
422 bool CHooks::IsHookPresent(hooktype t, const CString& workingTree)
424 auto it = FindItem(t, workingTree);
425 return it != end();
428 hookiterator CHooks::FindItem(hooktype t, const CString& workingTree)
430 hookkey key;
431 CTGitPath path = workingTree;
434 key.htype = t;
435 key.path = path;
436 auto it = find(key);
437 if (it != end() && it->second.bEnabled)
438 return it;
439 path = path.GetContainingDirectory();
440 } while(!path.IsEmpty());
442 /* if this ever gets called with something different than the workingTree root,
443 * recheck whether it is necessary to add a check for "key.path = m_RootPath"
444 * (e.g., for sparse checkouts or other hook types).
446 ATLASSERT(CTGitPath(workingTree).IsWCRoot());
448 // look for a script with a path as '*'
449 key.htype = t;
450 key.path = CTGitPath(L"*");
451 auto it = find(key);
452 if (it != end() && it->second.bEnabled)
454 return it;
457 return end();
460 void CHooks::ParseHookString(CString strhooks, bool bLocal)
462 // now fill the map with all the hooks defined in the string
463 // the string consists of multiple lines, where one hook script is defined
464 // as four lines:
465 // line 1: the hook type
466 // line 2: path to working copy where to apply the hook script,
467 // if it starts with "!" this hook is disabled (this should provide backward and forward compatibility)
468 // if it starts with "?" this hook is for the current project folder
469 // line 3: command line to execute
470 // line 4: 'true' or 'false' for waiting for the script to finish
471 // line 5: 'show' or 'hide' on how to start the hook script
472 hookkey key;
473 int pos = 0;
474 hookcmd cmd;
475 cmd.bLocal = bLocal;
476 cmd.bApproved = !bLocal; // user configured scripts are pre-approved
477 cmd.bStored = false;
478 while ((pos = strhooks.Find(L'\n')) >= 0)
480 // line 1
481 key.htype = GetHookType(strhooks.Left(pos));
482 if (pos + 1 < strhooks.GetLength())
483 strhooks = strhooks.Mid(pos + 1);
484 else
485 strhooks.Empty();
486 bool bComplete = false;
487 if ((pos = strhooks.Find(L'\n')) >= 0)
489 // line 2
490 cmd.bEnabled = true;
491 if (strhooks[0] == L'!' && pos > 1)
493 cmd.bEnabled = false;
494 strhooks = strhooks.Mid((int)wcslen(L"!"));
495 --pos;
497 if (strhooks[0] == L'?' && pos > 0)
498 key.path = CTGitPath(m_RootPath);
499 else
500 key.path = CTGitPath(strhooks.Left(pos));
501 if (pos + 1 < strhooks.GetLength())
502 strhooks = strhooks.Mid(pos + 1);
503 else
504 strhooks.Empty();
505 if ((pos = strhooks.Find(L'\n')) >= 0)
507 // line 3
508 cmd.commandline = strhooks.Left(pos);
509 if (pos + 1 < strhooks.GetLength())
510 strhooks = strhooks.Mid(pos + 1);
511 else
512 strhooks.Empty();
513 if ((pos = strhooks.Find(L'\n')) >= 0)
515 // line 4
516 cmd.bWait = (strhooks.Left(pos).CompareNoCase(L"true") == 0);
517 if (pos + 1 < strhooks.GetLength())
518 strhooks = strhooks.Mid(pos + 1);
519 else
520 strhooks.Empty();
521 if ((pos = strhooks.Find(L'\n')) >= 0)
523 // line 5
524 cmd.bShow = (strhooks.Left(pos).CompareNoCase(L"show") == 0);
525 if (pos + 1 < strhooks.GetLength())
526 strhooks = strhooks.Mid(pos + 1);
527 else
528 strhooks.Empty();
529 if (cmd.bLocal)
531 CString temp;
532 temp.Format(L"%s|%d%s", m_RootPath.GetWinPath(), (int)key.htype, (LPCTSTR)cmd.commandline);
534 cmd.sRegKey = L"Software\\TortoiseGit\\approvedhooks\\" + CalcSHA256(temp);
535 CRegDWORD reg(cmd.sRegKey, 0);
536 cmd.bApproved = (DWORD(reg) != 0);
537 cmd.bStored = reg.exists();
540 bComplete = true;
545 if (bComplete)
546 m_pInstance->insert(std::pair<hookkey, hookcmd>(key, cmd));
550 DWORD CHooks::RunScript(CString cmd, LPCTSTR currentDir, CString& error, bool bWait, bool bShow)
552 DWORD exitcode = 0;
553 SECURITY_ATTRIBUTES sa = { 0 };
554 sa.nLength = sizeof(sa);
555 sa.bInheritHandle = TRUE;
557 CAutoFile hOut ;
558 CAutoFile hRedir;
559 CAutoFile hErr;
561 // clear the error string
562 error.Empty();
564 // Create Temp File for redirection
565 TCHAR szTempPath[MAX_PATH] = {0};
566 TCHAR szOutput[MAX_PATH] = {0};
567 TCHAR szErr[MAX_PATH] = {0};
568 GetTortoiseGitTempPath(_countof(szTempPath), szTempPath);
569 GetTempFileName(szTempPath, L"git", 0, szErr);
571 // setup redirection handles
572 // output handle must be WRITE mode, share READ
573 // redirect handle must be READ mode, share WRITE
574 hErr = CreateFile(szErr, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, nullptr);
576 if (!hErr)
578 error = CFormatMessageWrapper();
579 return (DWORD)-1;
582 hRedir = CreateFile(szErr, GENERIC_READ, FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr);
584 if (!hRedir)
586 error = CFormatMessageWrapper();
587 return (DWORD)-1;
590 GetTempFileName(szTempPath, L"git", 0, szOutput);
591 hOut = CreateFile(szOutput, GENERIC_WRITE, FILE_SHARE_READ, &sa, CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY, nullptr);
593 if (!hOut)
595 error = CFormatMessageWrapper();
596 return (DWORD)-1;
599 // setup startup info, set std out/err handles
600 // hide window
601 STARTUPINFO si = { 0 };
602 si.cb = sizeof(si);
603 si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
604 si.hStdOutput = hOut;
605 si.hStdError = hErr;
606 si.wShowWindow = bShow ? SW_SHOW : SW_HIDE;
608 PROCESS_INFORMATION pi = { 0 };
609 if (!CreateProcess(nullptr, cmd.GetBuffer(), nullptr, nullptr, TRUE, CREATE_UNICODE_ENVIRONMENT, nullptr, currentDir, &si, &pi))
611 const DWORD err = GetLastError(); // preserve the CreateProcess error
612 error = CFormatMessageWrapper(err);
613 SetLastError(err);
614 cmd.ReleaseBuffer();
615 return (DWORD)-1;
617 cmd.ReleaseBuffer();
619 CloseHandle(pi.hThread);
621 // wait for process to finish, capture redirection and
622 // send it to the parent window/console
623 if (bWait)
625 DWORD dw;
626 char buf[256] = { 0 };
629 while (ReadFile(hRedir, &buf, sizeof(buf) - 1, &dw, nullptr))
631 if (dw == 0)
632 break;
633 error += CString(CStringA(buf,dw));
635 Sleep(150);
636 } while (WaitForSingleObject(pi.hProcess, 0) != WAIT_OBJECT_0);
638 // perform any final flushing
639 while (ReadFile(hRedir, &buf, sizeof(buf) - 1, &dw, nullptr))
641 if (dw == 0)
642 break;
644 error += CString(CStringA(buf, dw));
646 WaitForSingleObject(pi.hProcess, INFINITE);
647 GetExitCodeProcess(pi.hProcess, &exitcode);
649 CloseHandle(pi.hProcess);
650 DeleteFile(szOutput);
651 DeleteFile(szErr);
653 return exitcode;
656 bool CHooks::ApproveHook(HWND hWnd, hookiterator it, DWORD& exitcode)
658 if (it->second.bApproved || it->second.bStored)
660 exitcode = 0;
661 return it->second.bApproved;
664 CString sQuestion;
665 sQuestion.Format(IDS_HOOKS_APPROVE_TASK1, (LPCWSTR)it->second.commandline);
666 CTaskDialog taskdlg(sQuestion, CString(MAKEINTRESOURCE(IDS_HOOKS_APPROVE_TASK2)), L"TortoiseGit", 0, TDF_USE_COMMAND_LINKS | TDF_ALLOW_DIALOG_CANCELLATION | TDF_POSITION_RELATIVE_TO_WINDOW | TDF_SIZE_TO_CONTENT);
667 taskdlg.AddCommandControl(101, CString(MAKEINTRESOURCE(IDS_HOOKS_APPROVE_TASK3)));
668 taskdlg.AddCommandControl(102, CString(MAKEINTRESOURCE(IDS_HOOKS_APPROVE_TASK4)));
669 taskdlg.SetCommonButtons(TDCBF_CANCEL_BUTTON);
670 taskdlg.SetVerificationCheckboxText(CString(MAKEINTRESOURCE(IDS_HOOKS_APPROVE_TASK5)));
671 taskdlg.SetVerificationCheckbox(false);
672 taskdlg.SetDefaultCommandControl(102);
673 taskdlg.SetMainIcon(TD_WARNING_ICON);
674 taskdlg.SetFooterText(CString(MAKEINTRESOURCE(IDS_HOOKS_APPROVE_SECURITYHINT)));
675 auto ret = taskdlg.DoModal(hWnd);
676 if (ret == IDCANCEL)
678 exitcode = 1;
679 return false;
681 bool bApproved = (ret == 101);
682 bool bDoNotAskAgain = !!taskdlg.GetVerificationCheckboxState();
684 if (bDoNotAskAgain)
686 CRegDWORD reg(it->second.sRegKey, 0);
687 reg = bApproved ? 1 : 0;
688 it->second.bStored = true;
689 it->second.bApproved = bApproved;
691 exitcode = 0;
692 return bApproved;