1 /*
2   KeePass Password Safe - The Open-Source Password Manager
3   Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
4 
5   This program is free software; you can redistribute it and/or modify
6   it under the terms of the GNU General Public License as published by
7   the Free Software Foundation; either version 2 of the License, or
8   (at your option) any later version.
9 
10   This program 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
13   GNU General Public License for more details.
14 
15   You should have received a copy of the GNU General Public License
16   along with this program; if not, write to the Free Software
17   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 
20 using System;
21 using System.Collections.Generic;
22 using System.Collections.ObjectModel;
23 using System.Diagnostics;
24 using System.Drawing;
25 using System.Runtime.InteropServices;
26 using System.Text;
27 using System.Windows.Forms;
28 
29 using KeePass.Native;
30 using KeePass.Resources;
31 using KeePass.Util;
32 
33 using KeePassLib.Utility;
34 
35 namespace KeePass.UI
36 {
37 	[Flags]
38 	public enum VtdFlags
39 	{
40 		None = 0,
41 		EnableHyperlinks = 0x0001,
42 		UseHIconMain = 0x0002,
43 		UseHIconFooter = 0x0004,
44 		AllowDialogCancellation = 0x0008,
45 		UseCommandLinks = 0x0010,
46 		UseCommandLinksNoIcon = 0x0020,
47 		ExpandFooterArea = 0x0040,
48 		ExpandedByDefault = 0x0080,
49 		VerificationFlagChecked = 0x0100,
50 		ShowProgressBar = 0x0200,
51 		ShowMarqueeProgressBar = 0x0400,
52 		CallbackTimer = 0x0800,
53 		PositionRelativeToWindow = 0x1000,
54 		RtlLayout = 0x2000,
55 		NoDefaultRadioButton = 0x4000
56 	}
57 
58 	[Flags]
59 	internal enum VtdCommonButtonFlags
60 	{
61 		None = 0,
62 		OkButton = 0x0001, // Return value: IDOK = DialogResult.OK
63 		YesButton = 0x0002, // Return value: IDYES
64 		NoButton = 0x0004, // Return value: IDNO
65 		CancelButton = 0x0008, // Return value: IDCANCEL
66 		RetryButton = 0x0010, // Return value: IDRETRY
67 		CloseButton = 0x0020  // Return value: IDCLOSE
68 	}
69 
70 	public enum VtdIcon
71 	{
72 		None = 0,
73 		Warning = 0xFFFF,
74 		Error = 0xFFFE,
75 		Information = 0xFFFD,
76 		Shield = 0xFFFC
77 	}
78 
79 	public enum VtdCustomIcon
80 	{
81 		None = 0,
82 		Question = 1
83 	}
84 
85 	internal enum VtdNtf
86 	{
87 		Created = 0,
88 		Navigated = 1,
89 		ButtonClicked = 2,
90 		HyperlinkClicked = 3,
91 		Timer = 4,
92 		Destroyed = 5,
93 		RadioButtonClicked = 6,
94 		DialogConstructed = 7,
95 		VerificationClicked = 8,
96 		Help = 9,
97 		ExpandoButtonClicked = 10
98 	}
99 
100 	// Pack = 4 required for 64-bit compatibility
101 	[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
102 	internal struct VtdButton
103 	{
104 		public int ID;
105 
106 		[MarshalAs(UnmanagedType.LPWStr)]
107 		public string Text;
108 
VtdButtonKeePass.UI.VtdButton109 		public VtdButton(bool bConstruct)
110 		{
111 			Debug.Assert(bConstruct);
112 
113 			this.ID = (int)DialogResult.Cancel;
114 			this.Text = string.Empty;
115 		}
116 	}
117 
118 	// Pack = 4 required for 64-bit compatibility
119 	[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 4)]
120 	internal struct VtdConfig
121 	{
122 		public uint cbSize;
123 		public IntPtr hwndParent;
124 		public IntPtr hInstance;
125 
126 		[MarshalAs(UnmanagedType.U4)]
127 		public VtdFlags dwFlags;
128 
129 		[MarshalAs(UnmanagedType.U4)]
130 		public VtdCommonButtonFlags dwCommonButtons;
131 
132 		[MarshalAs(UnmanagedType.LPWStr)]
133 		public string pszWindowTitle;
134 
135 		public IntPtr hMainIcon;
136 
137 		[MarshalAs(UnmanagedType.LPWStr)]
138 		public string pszMainInstruction;
139 
140 		[MarshalAs(UnmanagedType.LPWStr)]
141 		public string pszContent;
142 
143 		public uint cButtons;
144 		public IntPtr pButtons;
145 		public int nDefaultButton;
146 		public uint cRadioButtons;
147 		public IntPtr pRadioButtons;
148 		public int nDefaultRadioButton;
149 
150 		[MarshalAs(UnmanagedType.LPWStr)]
151 		public string pszVerificationText;
152 
153 		[MarshalAs(UnmanagedType.LPWStr)]
154 		public string pszExpandedInformation;
155 
156 		[MarshalAs(UnmanagedType.LPWStr)]
157 		public string pszExpandedControlText;
158 
159 		[MarshalAs(UnmanagedType.LPWStr)]
160 		public string pszCollapsedControlText;
161 
162 		public IntPtr hFooterIcon;
163 
164 		[MarshalAs(UnmanagedType.LPWStr)]
165 		public string pszFooter;
166 
167 		public TaskDialogCallbackProc pfCallback;
168 		public IntPtr lpCallbackData;
169 		public uint cxWidth;
170 
VtdConfigKeePass.UI.VtdConfig171 		public VtdConfig(bool bConstruct)
172 		{
173 			Debug.Assert(bConstruct);
174 
175 			cbSize = (uint)Marshal.SizeOf(typeof(VtdConfig));
176 			hwndParent = IntPtr.Zero;
177 			hInstance = IntPtr.Zero;
178 
179 			dwFlags = VtdFlags.None;
180 			if(Program.Translation.Properties.RightToLeft) dwFlags |= VtdFlags.RtlLayout;
181 
182 			dwCommonButtons = VtdCommonButtonFlags.None;
183 			pszWindowTitle = null;
184 			hMainIcon = IntPtr.Zero;
185 			pszMainInstruction = string.Empty;
186 			pszContent = string.Empty;
187 			cButtons = 0;
188 			pButtons = IntPtr.Zero;
189 			nDefaultButton = 0;
190 			cRadioButtons = 0;
191 			pRadioButtons = IntPtr.Zero;
192 			nDefaultRadioButton = 0;
193 			pszVerificationText = null;
194 			pszExpandedInformation = null;
195 			pszExpandedControlText = null;
196 			pszCollapsedControlText = null;
197 			hFooterIcon = IntPtr.Zero;
198 			pszFooter = null;
199 			pfCallback = null;
200 			lpCallbackData = IntPtr.Zero;
201 			cxWidth = 0;
202 		}
203 	}
204 
TaskDialogCallbackProc(IntPtr hwnd, uint uNotification, UIntPtr wParam, IntPtr lParam, IntPtr lpRefData)205 	internal delegate int TaskDialogCallbackProc(IntPtr hwnd, uint uNotification,
206 		UIntPtr wParam, IntPtr lParam, IntPtr lpRefData);
207 
208 	public sealed class VistaTaskDialog
209 	{
210 		private const int VtdConfigSize32 = 96;
211 		private const int VtdConfigSize64 = 160;
212 
213 		private VtdConfig m_cfg = new VtdConfig(true);
214 		private int m_iResult = (int)DialogResult.Cancel;
215 		private bool m_bVerification = false;
216 
217 		private List<VtdButton> m_vButtons = new List<VtdButton>();
218 
219 		// private IntPtr m_hWnd = IntPtr.Zero;
220 
221 		public string WindowTitle
222 		{
223 			get { return m_cfg.pszWindowTitle; }
224 			set { m_cfg.pszWindowTitle = value; }
225 		}
226 
227 		public string MainInstruction
228 		{
229 			get { return m_cfg.pszMainInstruction; }
230 			set { m_cfg.pszMainInstruction = value; }
231 		}
232 
233 		internal ReadOnlyCollection<VtdButton> Buttons
234 		{
235 			get { return m_vButtons.AsReadOnly(); }
236 		}
237 
238 		public string Content
239 		{
240 			get { return m_cfg.pszContent; }
241 			set { m_cfg.pszContent = value; }
242 		}
243 
244 		public bool CommandLinks
245 		{
246 			get { return ((m_cfg.dwFlags & VtdFlags.UseCommandLinks) != VtdFlags.None); }
247 			set
248 			{
249 				if(value) m_cfg.dwFlags |= VtdFlags.UseCommandLinks;
250 				else m_cfg.dwFlags &= ~VtdFlags.UseCommandLinks;
251 			}
252 		}
253 
254 		public int DefaultButtonID
255 		{
256 			get { return m_cfg.nDefaultButton; }
257 			set { m_cfg.nDefaultButton = value; }
258 		}
259 
260 		public bool EnableHyperlinks
261 		{
262 			get { return ((m_cfg.dwFlags & VtdFlags.EnableHyperlinks) != VtdFlags.None); }
263 			set
264 			{
265 				if(value) m_cfg.dwFlags |= VtdFlags.EnableHyperlinks;
266 				else m_cfg.dwFlags &= ~VtdFlags.EnableHyperlinks;
267 			}
268 		}
269 
270 		public string ExpandedInformation
271 		{
272 			get { return m_cfg.pszExpandedInformation; }
273 			set { m_cfg.pszExpandedInformation = value; }
274 		}
275 
276 		public bool ExpandedByDefault
277 		{
278 			get { return ((m_cfg.dwFlags & VtdFlags.ExpandedByDefault) != VtdFlags.None); }
279 			set
280 			{
281 				if(value) m_cfg.dwFlags |= VtdFlags.ExpandedByDefault;
282 				else m_cfg.dwFlags &= ~VtdFlags.ExpandedByDefault;
283 			}
284 		}
285 
286 		public string FooterText
287 		{
288 			get { return m_cfg.pszFooter; }
289 			set { m_cfg.pszFooter = value; }
290 		}
291 
292 		public string VerificationText
293 		{
294 			get { return m_cfg.pszVerificationText; }
295 			set { m_cfg.pszVerificationText = value; }
296 		}
297 
298 		public int Result
299 		{
300 			get { return m_iResult; }
301 		}
302 
303 		public bool ResultVerificationChecked
304 		{
305 			get { return m_bVerification; }
306 		}
307 
308 		public event EventHandler<LinkClickedEventArgs> LinkClicked;
309 
VistaTaskDialog()310 		public VistaTaskDialog()
311 		{
312 		}
313 
AddButton(int iResult, string strCommand, string strDescription)314 		public void AddButton(int iResult, string strCommand, string strDescription)
315 		{
316 			if(strCommand == null) throw new ArgumentNullException("strCommand");
317 
318 			VtdButton btn = new VtdButton(true);
319 
320 			if(strDescription == null) btn.Text = strCommand;
321 			else btn.Text = strCommand + "\n" + strDescription;
322 
323 			btn.ID = iResult;
324 
325 			m_vButtons.Add(btn);
326 		}
327 
SetIcon(VtdIcon vtdIcon)328 		public void SetIcon(VtdIcon vtdIcon)
329 		{
330 			m_cfg.dwFlags &= ~VtdFlags.UseHIconMain;
331 			m_cfg.hMainIcon = new IntPtr((int)vtdIcon);
332 		}
333 
SetIcon(VtdCustomIcon vtdIcon)334 		public void SetIcon(VtdCustomIcon vtdIcon)
335 		{
336 			if(vtdIcon == VtdCustomIcon.Question)
337 				SetIcon(SystemIcons.Question.Handle);
338 		}
339 
SetIcon(IntPtr hIcon)340 		public void SetIcon(IntPtr hIcon)
341 		{
342 			m_cfg.dwFlags |= VtdFlags.UseHIconMain;
343 			m_cfg.hMainIcon = hIcon;
344 		}
345 
SetFooterIcon(VtdIcon vtdIcon)346 		public void SetFooterIcon(VtdIcon vtdIcon)
347 		{
348 			m_cfg.dwFlags &= ~VtdFlags.UseHIconFooter;
349 			m_cfg.hFooterIcon = new IntPtr((int)vtdIcon);
350 		}
351 
ButtonsToPtr()352 		private void ButtonsToPtr()
353 		{
354 			if(m_vButtons.Count == 0) { m_cfg.pButtons = IntPtr.Zero; return; }
355 
356 			int nConfigSize = Marshal.SizeOf(typeof(VtdButton));
357 			m_cfg.pButtons = Marshal.AllocHGlobal(m_vButtons.Count * nConfigSize);
358 			m_cfg.cButtons = (uint)m_vButtons.Count;
359 
360 			for(int i = 0; i < m_vButtons.Count; ++i)
361 			{
362 				long l = m_cfg.pButtons.ToInt64() + (i * nConfigSize);
363 				Marshal.StructureToPtr(m_vButtons[i], new IntPtr(l), false);
364 			}
365 		}
366 
FreeButtonsPtr()367 		private void FreeButtonsPtr()
368 		{
369 			if(m_cfg.pButtons == IntPtr.Zero) return;
370 
371 			int nConfigSize = Marshal.SizeOf(typeof(VtdButton));
372 			for(int i = 0; i < m_vButtons.Count; ++i)
373 			{
374 				long l = m_cfg.pButtons.ToInt64() + (i * nConfigSize);
375 				Marshal.DestroyStructure(new IntPtr(l), typeof(VtdButton));
376 			}
377 
378 			Marshal.FreeHGlobal(m_cfg.pButtons);
379 			m_cfg.pButtons = IntPtr.Zero;
380 			m_cfg.cButtons = 0;
381 		}
382 
ShowDialog()383 		public bool ShowDialog()
384 		{
385 			return ShowDialog(null);
386 		}
387 
ShowDialog(Form fParent)388 		public bool ShowDialog(Form fParent)
389 		{
390 			MessageService.ExternalIncrementMessageCount();
391 
392 			Form f = fParent;
393 			if(f == null) f = MessageService.GetTopForm();
394 			if(f == null) f = GlobalWindowManager.TopWindow;
395 			if(f == null) f = Program.MainForm;
396 
397 #if DEBUG
398 			if(GlobalWindowManager.TopWindow != null)
399 			{
400 				Debug.Assert(f == GlobalWindowManager.TopWindow);
401 			}
402 			if(Program.MainForm != null) // Skip check for TrlUtil
403 			{
404 				Debug.Assert((f == MessageService.GetTopForm()) || (f == Program.MainForm));
405 			}
406 #endif
407 
408 			bool bResult;
409 			if((f == null) || !f.InvokeRequired)
410 				bResult = InternalShowDialog(f);
411 			else
412 				bResult = (bool)f.Invoke(new InternalShowDialogDelegate(
413 					this.InternalShowDialog), f);
414 
415 			MessageService.ExternalDecrementMessageCount();
416 			return bResult;
417 		}
418 
InternalShowDialogDelegate(Form fParent)419 		private delegate bool InternalShowDialogDelegate(Form fParent);
420 
InternalShowDialog(Form fParent)421 		private bool InternalShowDialog(Form fParent)
422 		{
423 			if(IntPtr.Size == 4)
424 				{ Debug.Assert(Marshal.SizeOf(typeof(VtdConfig)) == VtdConfigSize32); }
425 			else if(IntPtr.Size == 8)
426 				{ Debug.Assert(Marshal.SizeOf(typeof(VtdConfig)) == VtdConfigSize64); }
427 			else { Debug.Assert(false); }
428 
429 			m_cfg.cbSize = (uint)Marshal.SizeOf(typeof(VtdConfig));
430 
431 			if(fParent == null) m_cfg.hwndParent = IntPtr.Zero;
432 			else
433 			{
434 				try { m_cfg.hwndParent = fParent.Handle; }
435 				catch(Exception)
436 				{
437 					Debug.Assert(false);
438 					m_cfg.hwndParent = IntPtr.Zero;
439 				}
440 			}
441 
442 			bool bExp = (m_cfg.pszExpandedInformation != null);
443 			m_cfg.pszExpandedControlText = (bExp ? KPRes.Details : null);
444 			m_cfg.pszCollapsedControlText = (bExp ? KPRes.Details : null);
445 
446 			int pnButton = 0, pnRadioButton = 0;
447 			bool bVerification = false;
448 
449 			try { ButtonsToPtr(); }
450 			catch(Exception) { Debug.Assert(false); return false; }
451 
452 			m_cfg.pfCallback = this.OnTaskDialogCallback;
453 
454 			try
455 			{
456 				using(EnableThemingInScope etis = new EnableThemingInScope(true))
457 				{
458 					if(NativeMethods.TaskDialogIndirect(ref m_cfg, out pnButton,
459 						out pnRadioButton, out bVerification) != 0)
460 						throw new NotSupportedException();
461 				}
462 			}
463 			catch(Exception) { return false; }
464 			finally
465 			{
466 				try
467 				{
468 					m_cfg.pfCallback = null;
469 					FreeButtonsPtr();
470 				}
471 				catch(Exception) { Debug.Assert(false); }
472 			}
473 
474 			m_iResult = pnButton;
475 			m_bVerification = bVerification;
476 			return true;
477 		}
478 
OnTaskDialogCallback(IntPtr hwnd, uint uNotification, UIntPtr wParam, IntPtr lParam, IntPtr lpRefData)479 		private int OnTaskDialogCallback(IntPtr hwnd, uint uNotification,
480 			UIntPtr wParam, IntPtr lParam, IntPtr lpRefData)
481 		{
482 			try
483 			{
484 				// if((uNotification == (uint)VtdNtf.Created) ||
485 				//	(uNotification == (uint)VtdNtf.DialogConstructed))
486 				//	UpdateHWnd(hwnd);
487 				// else if(uNotification == (uint)VtdNtf.Destroyed)
488 				//	UpdateHWnd(IntPtr.Zero);
489 
490 				if((uNotification == (uint)VtdNtf.HyperlinkClicked) && this.EnableHyperlinks &&
491 					(lParam != IntPtr.Zero))
492 				{
493 					string str = Marshal.PtrToStringUni(lParam);
494 					if(str != null)
495 					{
496 						if(str.StartsWith("http:", StrUtil.CaseIgnoreCmp) ||
497 							str.StartsWith("https:", StrUtil.CaseIgnoreCmp))
498 							WinUtil.OpenUrl(str, null);
499 						else if(this.LinkClicked != null)
500 						{
501 							LinkClickedEventArgs e = new LinkClickedEventArgs(str);
502 							this.LinkClicked(this, e);
503 						}
504 						else { Debug.Assert(false); }
505 					}
506 					else { Debug.Assert(false); }
507 				}
508 			}
509 			catch(Exception) { Debug.Assert(false); }
510 
511 			return 0;
512 		}
513 
514 		/* private void UpdateHWnd(IntPtr hWnd)
515 		{
516 			if(hWnd != m_hWnd) { } // Unregister m_hWnd
517 			// Register hWnd
518 			m_hWnd = hWnd;
519 		} */
520 
CreateLink(string strHRef, string strText)521 		public static string CreateLink(string strHRef, string strText)
522 		{
523 			string strH = (strHRef ?? string.Empty);
524 			string strT = (strText ?? string.Empty);
525 			return ("<A HREF=\"" + strH + "\">" + strT + "</A>");
526 		}
527 
Unlink(string strText)528 		internal static string Unlink(string strText)
529 		{
530 			if(string.IsNullOrEmpty(strText)) return string.Empty;
531 
532 			string str = strText;
533 			while(true)
534 			{
535 				int iS = str.IndexOf("<A HREF=\"", StrUtil.CaseIgnoreCmp);
536 				if(iS < 0) break;
537 
538 				const string strE = "\">";
539 				int iE = str.IndexOf(strE, iS, StrUtil.CaseIgnoreCmp);
540 				if(iE < 0) { Debug.Assert(false); break; }
541 
542 				const string strC = "</A>";
543 				int iC = str.IndexOf(strC, iE, StrUtil.CaseIgnoreCmp);
544 				if(iC < 0) { Debug.Assert(false); break; }
545 
546 				str = str.Remove(iC, strC.Length);
547 				str = str.Remove(iS, iE - iS + strE.Length);
548 			}
549 
550 			return str;
551 		}
552 
ShowMessageBox(string strContent, string strMainInstruction, string strWindowTitle, VtdIcon vtdIcon, Form fParent)553 		public static bool ShowMessageBox(string strContent, string strMainInstruction,
554 			string strWindowTitle, VtdIcon vtdIcon, Form fParent)
555 		{
556 			return (ShowMessageBoxEx(strContent, strMainInstruction, strWindowTitle,
557 				vtdIcon, fParent, null, 0, null, 0) >= 0);
558 		}
559 
ShowMessageBoxEx(string strContent, string strMainInstruction, string strWindowTitle, VtdIcon vtdIcon, Form fParent, string strButton1, int iResult1, string strButton2, int iResult2)560 		public static int ShowMessageBoxEx(string strContent, string strMainInstruction,
561 			string strWindowTitle, VtdIcon vtdIcon, Form fParent,
562 			string strButton1, int iResult1, string strButton2, int iResult2)
563 		{
564 			VistaTaskDialog vtd = new VistaTaskDialog();
565 
566 			vtd.CommandLinks = false;
567 
568 			if(strContent != null) vtd.Content = strContent;
569 			if(strMainInstruction != null) vtd.MainInstruction = strMainInstruction;
570 			if(strWindowTitle != null) vtd.WindowTitle = strWindowTitle;
571 
572 			vtd.SetIcon(vtdIcon);
573 
574 			bool bCustomButton = false;
575 			if(!string.IsNullOrEmpty(strButton1))
576 			{
577 				vtd.AddButton(iResult1, strButton1, null);
578 				bCustomButton = true;
579 			}
580 			if(!string.IsNullOrEmpty(strButton2))
581 			{
582 				vtd.AddButton(iResult2, strButton2, null);
583 				bCustomButton = true;
584 			}
585 
586 			if(!vtd.ShowDialog(fParent)) return -1;
587 			return (bCustomButton ? vtd.Result : 0);
588 		}
589 	}
590 }
591