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.Diagnostics;
23 using System.Media;
24 using System.Security;
25 using System.Text;
26 using System.Text.RegularExpressions;
27 using System.Threading;
28 using System.Windows.Forms;
29 
30 using KeePass.App;
31 using KeePass.Forms;
32 using KeePass.Native;
33 using KeePass.Resources;
34 using KeePass.UI;
35 using KeePass.Util.Spr;
36 
37 using KeePassLib;
38 using KeePassLib.Collections;
39 using KeePassLib.Delegates;
40 using KeePassLib.Resources;
41 using KeePassLib.Security;
42 using KeePassLib.Utility;
43 
44 using NativeLib = KeePassLib.Native.NativeLib;
45 
46 namespace KeePass.Util
47 {
48 	public sealed class AutoTypeEventArgs : EventArgs
49 	{
50 		private string m_strSeq; // Never null
51 		public string Sequence
52 		{
53 			get { return m_strSeq; }
54 			set
55 			{
56 				if(value == null) throw new ArgumentNullException("value");
57 				m_strSeq = value;
58 			}
59 		}
60 
61 		public bool SendObfuscated { get; set; }
62 		public PwEntry Entry { get; private set; }
63 		public PwDatabase Database { get; private set; }
64 
AutoTypeEventArgs(string strSequence, bool bObfuscated, PwEntry pe, PwDatabase pd)65 		public AutoTypeEventArgs(string strSequence, bool bObfuscated, PwEntry pe,
66 			PwDatabase pd)
67 		{
68 			if(strSequence == null) throw new ArgumentNullException("strSequence");
69 			// pe may be null
70 
71 			m_strSeq = strSequence;
72 			this.SendObfuscated = bObfuscated;
73 			this.Entry = pe;
74 			this.Database = pd;
75 		}
76 	}
77 
78 	public static class AutoType
79 	{
80 		private const int TargetActivationDelay = 100;
81 
82 		public static event EventHandler<AutoTypeEventArgs> FilterCompilePre;
83 		public static event EventHandler<AutoTypeEventArgs> FilterSendPre;
84 		public static event EventHandler<AutoTypeEventArgs> FilterSend;
85 
86 		public static event EventHandler<AutoTypeEventArgs> SendPost;
87 
88 		public static event EventHandler<SequenceQueryEventArgs> SequenceQueryPre;
89 		public static event EventHandler<SequenceQueryEventArgs> SequenceQuery;
90 		public static event EventHandler<SequenceQueryEventArgs> SequenceQueryPost;
91 
92 		public static event EventHandler<SequenceQueriesEventArgs> SequenceQueriesBegin;
93 		public static event EventHandler<SequenceQueriesEventArgs> SequenceQueriesEnd;
94 
95 		private static int m_iEventID = 0;
GetNextEventID()96 		public static int GetNextEventID()
97 		{
98 			return Interlocked.Increment(ref m_iEventID);
99 		}
100 
InitStatic()101 		internal static void InitStatic()
102 		{
103 			try
104 			{
105 				// SendKeys is not used anymore, thus the following is
106 				// not required:
107 				// // Enable new SendInput method; see
108 				// // https://msdn.microsoft.com/en-us/library/system.windows.forms.sendkeys.aspx
109 				// ConfigurationManager.AppSettings.Set("SendKeys", "SendInput");
110 			}
111 			catch(Exception) { Debug.Assert(false); }
112 		}
113 
IsMatchWindow(string strWindow, string strFilter)114 		internal static bool IsMatchWindow(string strWindow, string strFilter)
115 		{
116 			if(strWindow == null) { Debug.Assert(false); return false; }
117 			if(strFilter == null) { Debug.Assert(false); return false; }
118 
119 			Debug.Assert(NormalizeWindowText(strWindow) == strWindow); // Should be done by caller
120 			string strF = NormalizeWindowText(strFilter);
121 
122 			int ccF = strF.Length;
123 			if((ccF > 4) && (strF[0] == '/') && (strF[1] == '/') &&
124 				(strF[ccF - 2] == '/') && (strF[ccF - 1] == '/'))
125 			{
126 				try
127 				{
128 					string strRx = strF.Substring(2, ccF - 4);
129 					return Regex.IsMatch(strWindow, strRx, RegexOptions.IgnoreCase);
130 				}
131 				catch(Exception) { return false; }
132 			}
133 
134 			return StrUtil.SimplePatternMatch(strF, strWindow, StrUtil.CaseIgnoreCmp);
135 		}
136 
IsMatchSub(string strWindow, string strSub)137 		private static bool IsMatchSub(string strWindow, string strSub)
138 		{
139 			if(strWindow == null) { Debug.Assert(false); return false; }
140 			if(strSub == null) { Debug.Assert(false); return false; }
141 
142 			Debug.Assert(NormalizeWindowText(strWindow) == strWindow); // Should be done by caller
143 			string strS = NormalizeWindowText(strSub);
144 
145 			// If strS is empty, return false, because strS is a substring
146 			// that must occur, not a filter
147 			if(strS.Length == 0) return false;
148 
149 			return (strWindow.IndexOf(strS, StrUtil.CaseIgnoreCmp) >= 0);
150 		}
151 
Execute(AutoTypeCtx ctx)152 		private static bool Execute(AutoTypeCtx ctx)
153 		{
154 			if(ctx == null) { Debug.Assert(false); return false; }
155 
156 			string strSeq = ctx.Sequence;
157 			PwEntry pweData = ctx.Entry;
158 			if(pweData == null) { Debug.Assert(false); return false; }
159 
160 			if(!pweData.GetAutoTypeEnabled()) return false;
161 			if(!AppPolicy.Try(AppPolicyId.AutoType)) return false;
162 
163 			if(NativeLib.IsUnix())
164 			{
165 				if(!NativeMethods.TryXDoTool() && !NativeLib.IsWayland())
166 				{
167 					MessageService.ShowWarning(KPRes.AutoTypeXDoToolRequired,
168 						KPRes.PackageInstallHint);
169 					return false;
170 				}
171 			}
172 
173 			PwDatabase pwDatabase = ctx.Database;
174 
175 			bool bObfuscate = (pweData.AutoType.ObfuscationOptions !=
176 				AutoTypeObfuscationOptions.None);
177 			AutoTypeEventArgs args = new AutoTypeEventArgs(strSeq, bObfuscate,
178 				pweData, pwDatabase);
179 
180 			if(AutoType.FilterCompilePre != null) AutoType.FilterCompilePre(null, args);
181 
182 			args.Sequence = SprEngine.Compile(args.Sequence, new SprContext(
183 				pweData, pwDatabase, SprCompileFlags.All, true, false));
184 
185 			// string strError = ValidateAutoTypeSequence(args.Sequence);
186 			// if(!string.IsNullOrEmpty(strError))
187 			// {
188 			//	MessageService.ShowWarning(args.Sequence +
189 			//		MessageService.NewParagraph + strError);
190 			//	return false;
191 			// }
192 
193 			Application.DoEvents();
194 
195 			if(AutoType.FilterSendPre != null) AutoType.FilterSendPre(null, args);
196 			if(AutoType.FilterSend != null) AutoType.FilterSend(null, args);
197 
198 			if(args.Sequence.Length > 0)
199 			{
200 				string strError = null;
201 				try { SendInputEx.SendKeysWait(args.Sequence, args.SendObfuscated); }
202 				catch(SecurityException exSec) { strError = exSec.Message; }
203 				catch(Exception ex)
204 				{
205 					strError = args.Sequence + MessageService.NewParagraph +
206 						ex.Message;
207 				}
208 
209 				if(AutoType.SendPost != null) AutoType.SendPost(null, args);
210 
211 				if(!string.IsNullOrEmpty(strError))
212 				{
213 					try
214 					{
215 						MainForm mfP = Program.MainForm;
216 						if(mfP != null)
217 							mfP.EnsureVisibleForegroundWindow(false, false);
218 					}
219 					catch(Exception) { Debug.Assert(false); }
220 
221 					MessageService.ShowWarning(strError);
222 				}
223 			}
224 
225 			pweData.Touch(false);
226 			EntryUtil.ExpireTanEntryIfOption(pweData, pwDatabase);
227 
228 			MainForm mf = Program.MainForm;
229 			if(mf != null)
230 			{
231 				// Always refresh entry list (e.g. {NEWPASSWORD} might
232 				// have changed data)
233 				mf.RefreshEntriesList();
234 
235 				// SprEngine.Compile might have modified the database;
236 				// pd.Modified is set by SprEngine
237 				mf.UpdateUI(false, null, false, null, false, null, false);
238 
239 				if(Program.Config.MainWindow.MinimizeAfterAutoType &&
240 					mf.IsCommandTypeInvokable(null, MainForm.AppCommandType.Window))
241 					UIUtil.SetWindowState(mf, FormWindowState.Minimized);
242 			}
243 
244 			return true;
245 		}
246 
PerformInternal(AutoTypeCtx ctx, string strWindow)247 		private static bool PerformInternal(AutoTypeCtx ctx, string strWindow)
248 		{
249 			if(ctx == null) { Debug.Assert(false); return false; }
250 
251 			AutoTypeCtx ctxNew = ctx.Clone();
252 
253 			if(Program.Config.Integration.AutoTypePrependInitSequenceForIE &&
254 				WinUtil.IsInternetExplorer7Window(strWindow))
255 			{
256 				ctxNew.Sequence = @"{DELAY 50}1{DELAY 50}{BACKSPACE}" +
257 					ctxNew.Sequence;
258 			}
259 
260 			return AutoType.Execute(ctxNew);
261 		}
262 
GetSequencesForWindowBegin( IntPtr hWnd, string strWindow)263 		private static SequenceQueriesEventArgs GetSequencesForWindowBegin(
264 			IntPtr hWnd, string strWindow)
265 		{
266 			SequenceQueriesEventArgs e = new SequenceQueriesEventArgs(
267 				GetNextEventID(), hWnd, strWindow);
268 
269 			if(AutoType.SequenceQueriesBegin != null)
270 				AutoType.SequenceQueriesBegin(null, e);
271 
272 			return e;
273 		}
274 
GetSequencesForWindowEnd(SequenceQueriesEventArgs e)275 		private static void GetSequencesForWindowEnd(SequenceQueriesEventArgs e)
276 		{
277 			if(AutoType.SequenceQueriesEnd != null)
278 				AutoType.SequenceQueriesEnd(null, e);
279 		}
280 
281 		// Multiple calls of this method are wrapped in
282 		// GetSequencesForWindowBegin and GetSequencesForWindowEnd
GetSequencesForWindow(PwEntry pwe, IntPtr hWnd, string strWindow, PwDatabase pdContext, int iEventID)283 		private static List<string> GetSequencesForWindow(PwEntry pwe,
284 			IntPtr hWnd, string strWindow, PwDatabase pdContext, int iEventID)
285 		{
286 			List<string> l = new List<string>();
287 
288 			if(pwe == null) { Debug.Assert(false); return l; }
289 			if(strWindow == null) { Debug.Assert(false); return l; } // May be empty
290 
291 			if(!pwe.GetAutoTypeEnabled()) return l;
292 
293 			SprContext sprCtx = new SprContext(pwe, pdContext,
294 				SprCompileFlags.NonActive);
295 
296 			RaiseSequenceQueryEvent(AutoType.SequenceQueryPre, iEventID,
297 				hWnd, strWindow, pwe, pdContext, l);
298 
299 			// Specifically defined sequences must match before the title,
300 			// in order to allow selecting the first item as default one
301 			foreach(AutoTypeAssociation a in pwe.AutoType.Associations)
302 			{
303 				string strFilter = SprEngine.Compile(a.WindowName, sprCtx);
304 				if(IsMatchWindow(strWindow, strFilter))
305 				{
306 					string strSeq = a.Sequence;
307 					if(string.IsNullOrEmpty(strSeq))
308 						strSeq = pwe.GetAutoTypeSequence();
309 					AddSequence(l, strSeq);
310 				}
311 			}
312 
313 			RaiseSequenceQueryEvent(AutoType.SequenceQuery, iEventID,
314 				hWnd, strWindow, pwe, pdContext, l);
315 
316 			if(Program.Config.Integration.AutoTypeMatchByTitle)
317 			{
318 				string strTitle = SprEngine.Compile(pwe.Strings.ReadSafe(
319 					PwDefs.TitleField), sprCtx);
320 				if(IsMatchSub(strWindow, strTitle))
321 					AddSequence(l, pwe.GetAutoTypeSequence());
322 			}
323 
324 			string strCmpUrl = null; // To cache the compiled URL
325 			if(Program.Config.Integration.AutoTypeMatchByUrlInTitle)
326 			{
327 				strCmpUrl = SprEngine.Compile(pwe.Strings.ReadSafe(
328 					PwDefs.UrlField), sprCtx);
329 				if(IsMatchSub(strWindow, strCmpUrl))
330 					AddSequence(l, pwe.GetAutoTypeSequence());
331 			}
332 
333 			if(Program.Config.Integration.AutoTypeMatchByUrlHostInTitle)
334 			{
335 				if(strCmpUrl == null)
336 					strCmpUrl = SprEngine.Compile(pwe.Strings.ReadSafe(
337 						PwDefs.UrlField), sprCtx);
338 
339 				string strCleanUrl = StrUtil.RemovePlaceholders(strCmpUrl).Trim();
340 				string strHost = UrlUtil.GetHost(strCleanUrl);
341 
342 				if(strHost.StartsWith("www.", StrUtil.CaseIgnoreCmp) &&
343 					(strCleanUrl.StartsWith("http:", StrUtil.CaseIgnoreCmp) ||
344 					strCleanUrl.StartsWith("https:", StrUtil.CaseIgnoreCmp)))
345 					strHost = strHost.Substring(4);
346 
347 				if(IsMatchSub(strWindow, strHost))
348 					AddSequence(l, pwe.GetAutoTypeSequence());
349 			}
350 
351 			if(Program.Config.Integration.AutoTypeMatchByTagInTitle)
352 			{
353 				foreach(string strTag in pwe.Tags)
354 				{
355 					if(IsMatchSub(strWindow, strTag))
356 					{
357 						AddSequence(l, pwe.GetAutoTypeSequence());
358 						break;
359 					}
360 				}
361 			}
362 
363 			RaiseSequenceQueryEvent(AutoType.SequenceQueryPost, iEventID,
364 				hWnd, strWindow, pwe, pdContext, l);
365 
366 			return l;
367 		}
368 
RaiseSequenceQueryEvent( EventHandler<SequenceQueryEventArgs> f, int iEventID, IntPtr hWnd, string strWindow, PwEntry pwe, PwDatabase pdContext, List<string> lSeq)369 		private static void RaiseSequenceQueryEvent(
370 			EventHandler<SequenceQueryEventArgs> f, int iEventID, IntPtr hWnd,
371 			string strWindow, PwEntry pwe, PwDatabase pdContext,
372 			List<string> lSeq)
373 		{
374 			if(f == null) return;
375 
376 			SequenceQueryEventArgs e = new SequenceQueryEventArgs(iEventID,
377 				hWnd, strWindow, pwe, pdContext);
378 			f(null, e);
379 
380 			foreach(string strSeq in e.Sequences)
381 				AddSequence(lSeq, strSeq);
382 		}
383 
AddSequence(List<string> lSeq, string strSeq)384 		private static void AddSequence(List<string> lSeq, string strSeq)
385 		{
386 			if(strSeq == null) { Debug.Assert(false); return; }
387 
388 			string strCanSeq = CanonicalizeSeq(strSeq);
389 
390 			for(int i = 0; i < lSeq.Count; ++i)
391 			{
392 				string strCanEx = CanonicalizeSeq(lSeq[i]);
393 				if(strCanEx.Equals(strCanSeq)) return; // Exists already
394 			}
395 
396 			lSeq.Add(strSeq); // Non-canonical version
397 		}
398 
399 		private const string StrBraceOpen = @"{1E1F63AB-2F63-4B60-ADBA-7F38B8D7778E}";
400 		private const string StrBraceClose = @"{34D698D7-CEBF-4AF0-87BF-DC1B1F5E95A0}";
CanonicalizeSeq(string strSeq)401 		private static string CanonicalizeSeq(string strSeq)
402 		{
403 			// Preprocessing: balance braces
404 			strSeq = strSeq.Replace(@"{{}", StrBraceOpen);
405 			strSeq = strSeq.Replace(@"{}}", StrBraceClose);
406 
407 			StringBuilder sb = new StringBuilder();
408 
409 			bool bInPlh = false;
410 			for(int i = 0; i < strSeq.Length; ++i)
411 			{
412 				char ch = strSeq[i];
413 
414 				if(ch == '{') bInPlh = true;
415 				else if(ch == '}') bInPlh = false;
416 				else if(bInPlh) ch = char.ToUpper(ch);
417 
418 				sb.Append(ch);
419 			}
420 
421 			strSeq = sb.ToString();
422 
423 			// Postprocessing: restore braces
424 			strSeq = strSeq.Replace(StrBraceOpen, @"{{}");
425 			strSeq = strSeq.Replace(StrBraceClose, @"{}}");
426 
427 			return strSeq;
428 		}
429 
IsValidAutoTypeWindow(IntPtr hWnd, bool bBeepIfNot)430 		public static bool IsValidAutoTypeWindow(IntPtr hWnd, bool bBeepIfNot)
431 		{
432 			bool bValid = !GlobalWindowManager.HasWindowMW(hWnd);
433 
434 			if(!bValid && bBeepIfNot) SystemSounds.Beep.Play();
435 
436 			return bValid;
437 		}
438 
PerformGlobal(List<PwDatabase> lSources, ImageList ilIcons)439 		public static bool PerformGlobal(List<PwDatabase> lSources,
440 			ImageList ilIcons)
441 		{
442 			return PerformGlobal(lSources, ilIcons, null);
443 		}
444 
PerformGlobal(List<PwDatabase> lSources, ImageList ilIcons, string strSeq)445 		internal static bool PerformGlobal(List<PwDatabase> lSources,
446 			ImageList ilIcons, string strSeq)
447 		{
448 			if(lSources == null) { Debug.Assert(false); return false; }
449 
450 			if(NativeLib.IsUnix())
451 			{
452 				if(!NativeMethods.TryXDoTool(true) && !NativeLib.IsWayland())
453 				{
454 					MessageService.ShowWarning(KPRes.AutoTypeXDoToolRequiredGlobalVer);
455 					return false;
456 				}
457 			}
458 
459 			IntPtr hWnd;
460 			string strWindow;
461 			GetForegroundWindowInfo(out hWnd, out strWindow);
462 
463 			// if(string.IsNullOrEmpty(strWindow)) return false;
464 			if(strWindow == null) { Debug.Assert(false); return false; }
465 			if(!IsValidAutoTypeWindow(hWnd, true)) return false;
466 
467 			SequenceQueriesEventArgs evQueries = GetSequencesForWindowBegin(
468 				hWnd, strWindow);
469 
470 			List<AutoTypeCtx> lCtxs = new List<AutoTypeCtx>();
471 			PwDatabase pdCurrent = null;
472 			bool bExpCanMatch = Program.Config.Integration.AutoTypeExpiredCanMatch;
473 			DateTime dtNow = DateTime.UtcNow;
474 
475 			EntryHandler eh = delegate(PwEntry pe)
476 			{
477 				if(!bExpCanMatch && pe.Expires && (pe.ExpiryTime <= dtNow))
478 					return true; // Ignore expired entries
479 
480 				List<string> lSeq = GetSequencesForWindow(pe, hWnd, strWindow,
481 					pdCurrent, evQueries.EventID);
482 
483 				if(!string.IsNullOrEmpty(strSeq) && (lSeq.Count != 0))
484 					lCtxs.Add(new AutoTypeCtx(strSeq, pe, pdCurrent));
485 				else
486 				{
487 					foreach(string strSeqCand in lSeq)
488 					{
489 						lCtxs.Add(new AutoTypeCtx(strSeqCand, pe, pdCurrent));
490 					}
491 				}
492 
493 				return true;
494 			};
495 
496 			foreach(PwDatabase pd in lSources)
497 			{
498 				if((pd == null) || !pd.IsOpen) continue;
499 				pdCurrent = pd;
500 				pd.RootGroup.TraverseTree(TraversalMethod.PreOrder, null, eh);
501 			}
502 
503 			GetSequencesForWindowEnd(evQueries);
504 
505 			bool bForceDlg = Program.Config.Integration.AutoTypeAlwaysShowSelDialog;
506 
507 			if((lCtxs.Count >= 2) || bForceDlg)
508 			{
509 				AutoTypeCtxForm dlg = new AutoTypeCtxForm();
510 				dlg.InitEx(lCtxs, ilIcons);
511 
512 				bool bOK = (dlg.ShowDialog() == DialogResult.OK);
513 				AutoTypeCtx ctx = (bOK ? dlg.SelectedCtx : null);
514 				UIUtil.DestroyForm(dlg);
515 
516 				if(ctx != null)
517 				{
518 					try { NativeMethods.EnsureForegroundWindow(hWnd); }
519 					catch(Exception) { Debug.Assert(false); }
520 
521 					int nActDelayMS = TargetActivationDelay;
522 					string strWindowT = strWindow.Trim();
523 
524 					// https://sourceforge.net/p/keepass/discussion/329220/thread/3681f343/
525 					// This apparently is only required here (after showing the
526 					// auto-type entry selection dialog), not when using the
527 					// context menu command in the main window
528 					if(strWindowT.EndsWith("Microsoft Edge", StrUtil.CaseIgnoreCmp))
529 					{
530 						// 700 skips the first 1-2 characters,
531 						// 750 sometimes skips the first character
532 						nActDelayMS = 1000;
533 					}
534 
535 					// Allow target window to handle its activation
536 					// (required by some applications, e.g. Edge)
537 					Application.DoEvents();
538 					Thread.Sleep(nActDelayMS);
539 					Application.DoEvents();
540 
541 					AutoType.PerformInternal(ctx, strWindow);
542 				}
543 			}
544 			else if(lCtxs.Count == 1)
545 				AutoType.PerformInternal(lCtxs[0], strWindow);
546 
547 			return true;
548 		}
549 
550 		[Obsolete]
PerformIntoPreviousWindow(Form fCurrent, PwEntry pe)551 		public static bool PerformIntoPreviousWindow(Form fCurrent, PwEntry pe)
552 		{
553 			return PerformIntoPreviousWindow(fCurrent, pe,
554 				Program.MainForm.DocumentManager.SafeFindContainerOf(pe), null);
555 		}
556 
PerformIntoPreviousWindow(Form fCurrent, PwEntry pe, PwDatabase pdContext)557 		public static bool PerformIntoPreviousWindow(Form fCurrent, PwEntry pe,
558 			PwDatabase pdContext)
559 		{
560 			return PerformIntoPreviousWindow(fCurrent, pe, pdContext, null);
561 		}
562 
PerformIntoPreviousWindow(Form fCurrent, PwEntry pe, PwDatabase pdContext, string strSeq)563 		public static bool PerformIntoPreviousWindow(Form fCurrent, PwEntry pe,
564 			PwDatabase pdContext, string strSeq)
565 		{
566 			if(pe == null) { Debug.Assert(false); return false; }
567 			if(!pe.GetAutoTypeEnabled()) return false;
568 			if(!AppPolicy.Try(AppPolicyId.AutoTypeWithoutContext)) return false;
569 
570 			bool bTopMost = ((fCurrent != null) ? fCurrent.TopMost : false);
571 			if(bTopMost) fCurrent.TopMost = false;
572 
573 			try
574 			{
575 				if(!NativeMethods.LoseFocus(fCurrent, true)) { Debug.Assert(false); }
576 
577 				return PerformIntoCurrentWindow(pe, pdContext, strSeq);
578 			}
579 			finally
580 			{
581 				if(bTopMost) fCurrent.TopMost = true;
582 			}
583 		}
584 
585 		[Obsolete]
PerformIntoCurrentWindow(PwEntry pe)586 		public static bool PerformIntoCurrentWindow(PwEntry pe)
587 		{
588 			return PerformIntoCurrentWindow(pe,
589 				Program.MainForm.DocumentManager.SafeFindContainerOf(pe), null);
590 		}
591 
PerformIntoCurrentWindow(PwEntry pe, PwDatabase pdContext)592 		public static bool PerformIntoCurrentWindow(PwEntry pe, PwDatabase pdContext)
593 		{
594 			return PerformIntoCurrentWindow(pe, pdContext, null);
595 		}
596 
PerformIntoCurrentWindow(PwEntry pe, PwDatabase pdContext, string strSeq)597 		public static bool PerformIntoCurrentWindow(PwEntry pe, PwDatabase pdContext,
598 			string strSeq)
599 		{
600 			if(pe == null) { Debug.Assert(false); return false; }
601 			if(!pe.GetAutoTypeEnabled()) return false;
602 			if(!AppPolicy.Try(AppPolicyId.AutoTypeWithoutContext)) return false;
603 
604 			Thread.Sleep(TargetActivationDelay);
605 
606 			IntPtr hWnd;
607 			string strWindow;
608 			GetForegroundWindowInfo(out hWnd, out strWindow);
609 
610 			if(!NativeLib.IsUnix())
611 			{
612 				if(strWindow == null) { Debug.Assert(false); return false; }
613 			}
614 			else strWindow = string.Empty;
615 
616 			if(strSeq == null)
617 			{
618 				SequenceQueriesEventArgs evQueries = GetSequencesForWindowBegin(
619 					hWnd, strWindow);
620 
621 				List<string> lSeq = GetSequencesForWindow(pe, hWnd, strWindow,
622 					pdContext, evQueries.EventID);
623 
624 				GetSequencesForWindowEnd(evQueries);
625 
626 				if(lSeq.Count == 0) strSeq = pe.GetAutoTypeSequence();
627 				else strSeq = lSeq[0];
628 			}
629 
630 			AutoTypeCtx ctx = new AutoTypeCtx(strSeq, pe, pdContext);
631 			return AutoType.PerformInternal(ctx, strWindow);
632 		}
633 
634 		// ValidateAutoTypeSequence is not required anymore, because
635 		// SendInputEx now validates the sequence
636 		/* private static string ValidateAutoTypeSequence(string strSequence)
637 		{
638 			Debug.Assert(strSequence != null);
639 
640 			string strSeq = strSequence;
641 			strSeq = strSeq.Replace(@"{{}", string.Empty);
642 			strSeq = strSeq.Replace(@"{}}", string.Empty);
643 
644 			int cBrackets = 0;
645 			for(int c = 0; c < strSeq.Length; ++c)
646 			{
647 				if(strSeq[c] == '{') ++cBrackets;
648 				else if(strSeq[c] == '}') --cBrackets;
649 
650 				if((cBrackets < 0) || (cBrackets > 1))
651 					return KPRes.AutoTypeSequenceInvalid;
652 			}
653 			if(cBrackets != 0) return KPRes.AutoTypeSequenceInvalid;
654 
655 			if(strSeq.IndexOf(@"{}") >= 0) return KPRes.AutoTypeSequenceInvalid;
656 
657 			try
658 			{
659 				Regex r = new Regex(@"\{[^\{\}]+\}", RegexOptions.CultureInvariant);
660 				MatchCollection matches = r.Matches(strSeq);
661 
662 				foreach(Match m in matches)
663 				{
664 					string strValue = m.Value;
665 					if(strValue.StartsWith(@"{s:", StrUtil.CaseIgnoreCmp))
666 						return (KPRes.AutoTypeUnknownPlaceholder +
667 							MessageService.NewLine + strValue);
668 				}
669 			}
670 			catch(Exception ex) { Debug.Assert(false); return ex.Message; }
671 
672 			return null;
673 		} */
674 
675 		private static readonly char[] g_vNormToHyphen = new char[] {
676 			// Sync with UI option name
677 			'\u2010', // Hyphen
678 			'\u2011', // Non-breaking hyphen
679 			'\u2012', // Figure dash
680 			'\u2013', // En dash
681 			'\u2014', // Em dash
682 			'\u2015', // Horizontal bar
683 			'\u2212' // Minus sign
684 		};
NormalizeWindowText(string str)685 		internal static string NormalizeWindowText(string str)
686 		{
687 			if(string.IsNullOrEmpty(str)) return string.Empty;
688 
689 			str = str.Trim();
690 
691 			if(Program.Config.Integration.AutoTypeMatchNormDashes &&
692 				(str.IndexOfAny(g_vNormToHyphen) >= 0))
693 			{
694 				for(int i = 0; i < g_vNormToHyphen.Length; ++i)
695 					str = str.Replace(g_vNormToHyphen[i], '-');
696 			}
697 
698 			return str;
699 		}
700 
GetForegroundWindowInfo(out IntPtr hWnd, out string strWindow)701 		private static void GetForegroundWindowInfo(out IntPtr hWnd, out string strWindow)
702 		{
703 			try
704 			{
705 				NativeMethods.GetForegroundWindowInfo(out hWnd, out strWindow, false);
706 			}
707 			catch(Exception)
708 			{
709 				Debug.Assert(false);
710 				hWnd = IntPtr.Zero;
711 				strWindow = null;
712 				return;
713 			}
714 
715 			strWindow = NormalizeWindowText(strWindow);
716 		}
717 
718 		// Cf. PwEntry.GetAutoTypeEnabled
GetEnabledText(PwEntry pe, out object oBlocker)719 		internal static string GetEnabledText(PwEntry pe, out object oBlocker)
720 		{
721 			oBlocker = null;
722 			if(pe == null) { Debug.Assert(false); return string.Empty; }
723 
724 			if(!pe.AutoType.Enabled)
725 			{
726 				oBlocker = pe;
727 				return (KPRes.No + " (" + KLRes.EntryLower + ")");
728 			}
729 
730 			PwGroup pg = pe.ParentGroup;
731 			while(pg != null)
732 			{
733 				if(pg.EnableAutoType.HasValue)
734 				{
735 					if(pg.EnableAutoType.Value) break;
736 
737 					oBlocker = pg;
738 					return (KPRes.No + " (" + KLRes.GroupLower + " '" + pg.Name + "')");
739 				}
740 
741 				pg = pg.ParentGroup;
742 			}
743 
744 			// Options like 'Expired entries can match' influence the global
745 			// auto-type matching only, not commands like 'Perform Auto-Type'
746 
747 			return KPRes.Yes;
748 		}
749 
GetSequencesText(PwEntry pe)750 		internal static string GetSequencesText(PwEntry pe)
751 		{
752 			if(pe == null) { Debug.Assert(false); return string.Empty; }
753 
754 			string strSeq = pe.GetAutoTypeSequence();
755 			Debug.Assert(strSeq.Length != 0);
756 			string str = ((strSeq.Length != 0) ? strSeq : ("(" + KPRes.Empty + ")"));
757 
758 			int cAssoc = pe.AutoType.AssociationsCount;
759 			if(cAssoc != 0)
760 			{
761 				Dictionary<string, bool> d = new Dictionary<string, bool>();
762 				d[strSeq] = true;
763 
764 				foreach(AutoTypeAssociation a in pe.AutoType.Associations)
765 				{
766 					string strAssocSeq = a.Sequence;
767 					if(strAssocSeq.Length != 0) d[strAssocSeq] = true;
768 				}
769 
770 				int c = d.Count;
771 				str += ((c >= 2) ? (" " + KPRes.MoreAnd.Replace(@"{PARAM}",
772 					(c - 1).ToString())) : string.Empty) + " (" +
773 					cAssoc.ToString() + " " + KPRes.AssociationsLower + ")";
774 			}
775 
776 			return str;
777 		}
778 	}
779 }
780