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.IO;
24 using System.Text;
25 using System.Threading;
26 using System.Windows.Forms;
27 using System.Xml;
28 
29 using KeePass.App;
30 using KeePass.DataExchange.Formats;
31 using KeePass.Ecas;
32 using KeePass.Forms;
33 using KeePass.Native;
34 using KeePass.Resources;
35 using KeePass.UI;
36 using KeePass.Util;
37 
38 using KeePassLib;
39 using KeePassLib.Interfaces;
40 using KeePassLib.Keys;
41 using KeePassLib.Resources;
42 using KeePassLib.Security;
43 using KeePassLib.Serialization;
44 using KeePassLib.Utility;
45 
46 namespace KeePass.DataExchange
47 {
48 	public static class ImportUtil
49 	{
Import(PwDatabase pwStorage, out bool bAppendedToRootOnly, Form fParent)50 		public static bool? Import(PwDatabase pwStorage, out bool bAppendedToRootOnly,
51 			Form fParent)
52 		{
53 			bAppendedToRootOnly = false;
54 
55 			if(pwStorage == null) throw new ArgumentNullException("pwStorage");
56 			if(!pwStorage.IsOpen) return null;
57 			if(!AppPolicy.Try(AppPolicyId.Import)) return null;
58 
59 			ExchangeDataForm dlgFmt = new ExchangeDataForm();
60 			dlgFmt.InitEx(false, pwStorage, pwStorage.RootGroup);
61 
62 			if(UIUtil.ShowDialogNotValue(dlgFmt, DialogResult.OK)) return null;
63 
64 			Debug.Assert(dlgFmt.ResultFormat != null);
65 			if(dlgFmt.ResultFormat == null)
66 			{
67 				MessageService.ShowWarning(KPRes.ImportFailed);
68 				UIUtil.DestroyForm(dlgFmt);
69 				return false;
70 			}
71 
72 			bAppendedToRootOnly = dlgFmt.ResultFormat.ImportAppendsToRootGroupOnly;
73 			FileFormatProvider ffp = dlgFmt.ResultFormat;
74 
75 			List<IOConnectionInfo> lConnections = new List<IOConnectionInfo>();
76 			foreach(string strSelFile in dlgFmt.ResultFiles)
77 				lConnections.Add(IOConnectionInfo.FromPath(strSelFile));
78 
79 			UIUtil.DestroyForm(dlgFmt);
80 			return Import(pwStorage, ffp, lConnections.ToArray(),
81 				false, null, false, fParent);
82 		}
83 
Import(PwDatabase pwDatabase, FileFormatProvider fmtImp, IOConnectionInfo[] vConnections, bool bSynchronize, IUIOperations uiOps, bool bForceSave, Form fParent)84 		public static bool? Import(PwDatabase pwDatabase, FileFormatProvider fmtImp,
85 			IOConnectionInfo[] vConnections, bool bSynchronize, IUIOperations uiOps,
86 			bool bForceSave, Form fParent)
87 		{
88 			if(pwDatabase == null) throw new ArgumentNullException("pwDatabase");
89 			if(!pwDatabase.IsOpen) return null;
90 			if(fmtImp == null) throw new ArgumentNullException("fmtImp");
91 			if(vConnections == null) throw new ArgumentNullException("vConnections");
92 
93 			if(!AppPolicy.Try(AppPolicyId.Import)) return false;
94 			if(!fmtImp.TryBeginImport()) return false;
95 
96 			bool bUseTempDb = (fmtImp.SupportsUuids || fmtImp.RequiresKey);
97 			bool bAllSuccess = true;
98 			MainForm mf = Program.MainForm; // Null for KPScript
99 
100 			// if(bSynchronize) { Debug.Assert(vFiles.Length == 1); }
101 
102 			IStatusLogger dlgStatus;
103 			if(Program.Config.UI.ShowImportStatusDialog ||
104 				((mf != null) && !mf.HasFormLoaded))
105 				dlgStatus = new OnDemandStatusDialog(false, fParent);
106 			else dlgStatus = new UIBlockerStatusLogger(fParent);
107 
108 			dlgStatus.StartLogging(PwDefs.ShortProductName + " - " + (bSynchronize ?
109 				KPRes.Synchronizing : KPRes.ImportingStatusMsg), false);
110 			dlgStatus.SetText(bSynchronize ? KPRes.Synchronizing :
111 				KPRes.ImportingStatusMsg, LogStatusType.Info);
112 
113 			if(vConnections.Length == 0)
114 			{
115 				try { fmtImp.Import(pwDatabase, null, dlgStatus); }
116 				catch(Exception exSingular)
117 				{
118 					if(!string.IsNullOrEmpty(exSingular.Message))
119 					{
120 						// slf.SetText(exSingular.Message, LogStatusType.Warning);
121 						MessageService.ShowWarning(exSingular);
122 					}
123 				}
124 
125 				dlgStatus.EndLogging();
126 				return true;
127 			}
128 
129 			foreach(IOConnectionInfo iocIn in vConnections)
130 			{
131 				Stream s = null;
132 
133 				try { s = IOConnection.OpenRead(iocIn); }
134 				catch(Exception exFile)
135 				{
136 					MessageService.ShowWarning(iocIn.GetDisplayName(), exFile);
137 					bAllSuccess = false;
138 					continue;
139 				}
140 				if(s == null) { Debug.Assert(false); bAllSuccess = false; continue; }
141 
142 				PwDatabase pwImp;
143 				if(bUseTempDb)
144 				{
145 					pwImp = new PwDatabase();
146 					pwImp.New(new IOConnectionInfo(), pwDatabase.MasterKey);
147 					pwImp.MemoryProtection = pwDatabase.MemoryProtection.CloneDeep();
148 				}
149 				else pwImp = pwDatabase;
150 
151 				if(fmtImp.RequiresKey && !bSynchronize)
152 				{
153 					KeyPromptForm kpf = new KeyPromptForm();
154 					kpf.InitEx(iocIn, false, true);
155 
156 					if(UIUtil.ShowDialogNotValue(kpf, DialogResult.OK)) { s.Close(); continue; }
157 
158 					pwImp.MasterKey = kpf.CompositeKey;
159 					UIUtil.DestroyForm(kpf);
160 				}
161 				else if(bSynchronize) pwImp.MasterKey = pwDatabase.MasterKey;
162 
163 				dlgStatus.SetText((bSynchronize ? KPRes.Synchronizing :
164 					KPRes.ImportingStatusMsg) + " (" + iocIn.GetDisplayName() +
165 					")", LogStatusType.Info);
166 
167 				try { fmtImp.Import(pwImp, s, dlgStatus); }
168 				catch(Exception excpFmt)
169 				{
170 					string strMsgEx = excpFmt.Message;
171 					if(bSynchronize && (excpFmt is InvalidCompositeKeyException))
172 						strMsgEx = KLRes.InvalidCompositeKey + MessageService.NewParagraph +
173 							KPRes.SynchronizingHint;
174 
175 					MessageService.ShowWarning(iocIn.GetDisplayName(),
176 						KPRes.FileImportFailed, strMsgEx);
177 
178 					bAllSuccess = false;
179 					continue;
180 				}
181 				finally { s.Close(); }
182 
183 				if(bUseTempDb)
184 				{
185 					PwMergeMethod mm;
186 					if(!fmtImp.SupportsUuids) mm = PwMergeMethod.CreateNewUuids;
187 					else if(bSynchronize) mm = PwMergeMethod.Synchronize;
188 					else
189 					{
190 						ImportMethodForm imf = new ImportMethodForm();
191 						if(UIUtil.ShowDialogNotValue(imf, DialogResult.OK)) continue;
192 						mm = imf.MergeMethod;
193 						UIUtil.DestroyForm(imf);
194 					}
195 
196 					try { pwDatabase.MergeIn(pwImp, mm, dlgStatus); }
197 					catch(Exception exMerge)
198 					{
199 						MessageService.ShowWarning(iocIn.GetDisplayName(),
200 							KPRes.ImportFailed, exMerge);
201 
202 						bAllSuccess = false;
203 						continue;
204 					}
205 				}
206 			}
207 
208 			if(bSynchronize && bAllSuccess)
209 			{
210 				Debug.Assert(uiOps != null);
211 				if(uiOps == null) throw new ArgumentNullException("uiOps");
212 
213 				dlgStatus.SetText(KPRes.Synchronizing + " (" +
214 					KPRes.SavingDatabase + ")", LogStatusType.Info);
215 
216 				if(mf != null)
217 				{
218 					try { mf.DocumentManager.ActiveDatabase = pwDatabase; }
219 					catch(Exception) { Debug.Assert(false); }
220 				}
221 
222 				if(uiOps.UIFileSave(bForceSave))
223 				{
224 					foreach(IOConnectionInfo ioc in vConnections)
225 					{
226 						try
227 						{
228 							// dlgStatus.SetText(KPRes.Synchronizing + " (" +
229 							//	KPRes.SavingDatabase + " " + ioc.GetDisplayName() +
230 							//	")", LogStatusType.Info);
231 
232 							string strSource = pwDatabase.IOConnectionInfo.Path;
233 							if(!string.Equals(ioc.Path, strSource, StrUtil.CaseIgnoreCmp))
234 							{
235 								bool bSaveAs = true;
236 
237 								if(pwDatabase.IOConnectionInfo.IsLocalFile() &&
238 									ioc.IsLocalFile())
239 								{
240 									// Do not try to copy an encrypted file;
241 									// https://sourceforge.net/p/keepass/discussion/329220/thread/9c9eb989/
242 									// https://msdn.microsoft.com/en-us/library/windows/desktop/aa363851.aspx
243 									if((long)(File.GetAttributes(strSource) &
244 										FileAttributes.Encrypted) == 0)
245 									{
246 										File.Copy(strSource, ioc.Path, true);
247 										bSaveAs = false;
248 									}
249 								}
250 
251 								if(bSaveAs) pwDatabase.SaveAs(ioc, false, null);
252 							}
253 							// else { } // No assert (sync on save)
254 
255 							if(mf != null)
256 								mf.FileMruList.AddItem(ioc.GetDisplayName(),
257 									ioc.CloneDeep());
258 						}
259 						catch(Exception exSync)
260 						{
261 							MessageService.ShowWarning(KPRes.SyncFailed,
262 								pwDatabase.IOConnectionInfo.GetDisplayName() +
263 								MessageService.NewLine + ioc.GetDisplayName(), exSync);
264 
265 							bAllSuccess = false;
266 							continue;
267 						}
268 					}
269 				}
270 				else
271 				{
272 					MessageService.ShowWarning(KPRes.SyncFailed,
273 						pwDatabase.IOConnectionInfo.GetDisplayName());
274 
275 					bAllSuccess = false;
276 				}
277 			}
278 
279 			dlgStatus.EndLogging();
280 			return bAllSuccess;
281 		}
282 
Import(PwDatabase pd, FileFormatProvider fmtImp, IOConnectionInfo iocImp, PwMergeMethod mm, CompositeKey cmpKey)283 		public static bool? Import(PwDatabase pd, FileFormatProvider fmtImp,
284 			IOConnectionInfo iocImp, PwMergeMethod mm, CompositeKey cmpKey)
285 		{
286 			if(pd == null) { Debug.Assert(false); return false; }
287 			if(fmtImp == null) { Debug.Assert(false); return false; }
288 			if(iocImp == null) { Debug.Assert(false); return false; }
289 			if(cmpKey == null) cmpKey = new CompositeKey();
290 
291 			if(!AppPolicy.Try(AppPolicyId.Import)) return false;
292 			if(!fmtImp.TryBeginImport()) return false;
293 
294 			PwDatabase pdImp = new PwDatabase();
295 			pdImp.New(new IOConnectionInfo(), cmpKey);
296 			pdImp.MemoryProtection = pd.MemoryProtection.CloneDeep();
297 
298 			Stream s = IOConnection.OpenRead(iocImp);
299 			if(s == null)
300 				throw new FileNotFoundException(iocImp.GetDisplayName() +
301 					MessageService.NewLine + KPRes.FileNotFoundError);
302 
303 			try { fmtImp.Import(pdImp, s, null); }
304 			finally { s.Close(); }
305 
306 			pd.MergeIn(pdImp, mm);
307 			return true;
308 		}
309 
Synchronize(PwDatabase pwStorage, IUIOperations uiOps, bool bOpenFromUrl, Form fParent)310 		public static bool? Synchronize(PwDatabase pwStorage, IUIOperations uiOps,
311 			bool bOpenFromUrl, Form fParent)
312 		{
313 			if(pwStorage == null) throw new ArgumentNullException("pwStorage");
314 			if(!pwStorage.IsOpen) return null;
315 			if(!AppPolicy.Try(AppPolicyId.Import)) return null;
316 
317 			List<IOConnectionInfo> vConnections = new List<IOConnectionInfo>();
318 			if(bOpenFromUrl == false)
319 			{
320 				OpenFileDialogEx ofd = UIUtil.CreateOpenFileDialog(KPRes.Synchronize,
321 					UIUtil.CreateFileTypeFilter(AppDefs.FileExtension.FileExt,
322 					KPRes.KdbxFiles, true), 1, null, true,
323 					AppDefs.FileDialogContext.Sync);
324 
325 				if(ofd.ShowDialog() != DialogResult.OK) return null;
326 
327 				foreach(string strSelFile in ofd.FileNames)
328 					vConnections.Add(IOConnectionInfo.FromPath(strSelFile));
329 			}
330 			else // Open URL
331 			{
332 				IOConnectionForm iocf = new IOConnectionForm();
333 				iocf.InitEx(false, null, true, true);
334 
335 				if(UIUtil.ShowDialogNotValue(iocf, DialogResult.OK)) return null;
336 
337 				vConnections.Add(iocf.IOConnectionInfo);
338 				UIUtil.DestroyForm(iocf);
339 			}
340 
341 			return Import(pwStorage, new KeePassKdb2x(), vConnections.ToArray(),
342 				true, uiOps, false, fParent);
343 		}
344 
Synchronize(PwDatabase pwStorage, IUIOperations uiOps, IOConnectionInfo iocSyncWith, bool bForceSave, Form fParent)345 		public static bool? Synchronize(PwDatabase pwStorage, IUIOperations uiOps,
346 			IOConnectionInfo iocSyncWith, bool bForceSave, Form fParent)
347 		{
348 			if(pwStorage == null) throw new ArgumentNullException("pwStorage");
349 			if(!pwStorage.IsOpen) return null; // No assert or throw
350 			if(iocSyncWith == null) throw new ArgumentNullException("iocSyncWith");
351 			if(!AppPolicy.Try(AppPolicyId.Import)) return null;
352 
353 			Program.TriggerSystem.RaiseEvent(EcasEventIDs.SynchronizingDatabaseFile,
354 				EcasProperty.Database, pwStorage);
355 
356 			List<IOConnectionInfo> vConnections = new List<IOConnectionInfo>();
357 			vConnections.Add(iocSyncWith);
358 
359 			bool? ob = Import(pwStorage, new KeePassKdb2x(), vConnections.ToArray(),
360 				true, uiOps, bForceSave, fParent);
361 
362 			// Always raise the post event, such that the event pair can
363 			// for instance be used to turn off/on other triggers
364 			Program.TriggerSystem.RaiseEvent(EcasEventIDs.SynchronizedDatabaseFile,
365 				EcasProperty.Database, pwStorage);
366 
367 			return ob;
368 		}
369 
CountQuotes(string str, int posMax)370 		public static int CountQuotes(string str, int posMax)
371 		{
372 			int i = 0, n = 0;
373 
374 			while(true)
375 			{
376 				i = str.IndexOf('\"', i);
377 				if(i < 0) return n;
378 
379 				++i;
380 				if(i > posMax) return n;
381 
382 				++n;
383 			}
384 		}
385 
SplitCsvLine(string strLine, string strDelimiter)386 		public static List<string> SplitCsvLine(string strLine, string strDelimiter)
387 		{
388 			List<string> list = new List<string>();
389 
390 			int nOffset = 0;
391 			while(true)
392 			{
393 				int i = strLine.IndexOf(strDelimiter, nOffset);
394 				if(i < 0) break;
395 
396 				int nQuotes = CountQuotes(strLine, i);
397 				if((nQuotes & 1) == 0)
398 				{
399 					list.Add(strLine.Substring(0, i));
400 					strLine = strLine.Remove(0, i + strDelimiter.Length);
401 					nOffset = 0;
402 				}
403 				else
404 				{
405 					nOffset = i + strDelimiter.Length;
406 					if(nOffset >= strLine.Length) break;
407 				}
408 			}
409 
410 			list.Add(strLine);
411 			return list;
412 		}
413 
SetStatus(IStatusLogger slLogger, uint uPercent)414 		public static bool SetStatus(IStatusLogger slLogger, uint uPercent)
415 		{
416 			if(slLogger != null) return slLogger.SetProgress(uPercent);
417 			return true;
418 		}
419 
420 		private static readonly string[] m_vTitles = {
421 			"title", "system", "account", "entry",
422 			"item", "itemname", "item name", "subject",
423 			"service", "servicename", "service name",
424 			"head", "heading", "card", "product", "provider", "bank",
425 			"type",
426 
427 			// Non-English names
428 			"seite"
429 		};
430 
431 		private static readonly string[] m_vTitlesSubstr = {
432 			"title", "system", "account", "entry",
433 			"item", "subject", "service", "head"
434 		};
435 
436 		private static readonly string[] m_vUserNames = {
437 			"user", "name", "username", "user name", "login name",
438 			"login", "form_loginname", "wpname", "mail",
439 			"email", "e-mail", "id", "userid", "user id",
440 			"loginid", "login id", "log", "uin",
441 			"first name", "last name", "card#", "account #",
442 			"member", "member #", "owner",
443 
444 			// Non-English names
445 			"nom", "benutzername"
446 		};
447 
448 		private static readonly string[] m_vUserNamesSubstr = {
449 			"user", "name", "login", "mail", "owner"
450 		};
451 
452 		private static readonly string[] m_vPasswords = {
453 			"password", "pass word", "passphrase", "pass phrase",
454 			"pass", "code", "code word", "codeword",
455 			"secret", "secret word",
456 			"key", "keyword", "key word", "keyphrase", "key phrase",
457 			"form_pw", "wppassword", "pin", "pwd", "pw", "pword",
458 			"p", "serial", "serial#", "license key", "reg #",
459 
460 			// Non-English names
461 			"passwort", "kennwort"
462 		};
463 
464 		private static readonly string[] m_vPasswordsSubstr = {
465 			"pass", "code",	"secret", "key", "pw", "pin"
466 		};
467 
468 		private static readonly string[] m_vUrls = {
469 			"url", "hyper link", "hyperlink", "link",
470 			"host", "hostname", "host name", "server", "address",
471 			"hyper ref", "href", "web", "website", "web site", "site",
472 			"web-site",
473 
474 			// Non-English names
475 			"ort", "adresse", "webseite"
476 		};
477 
478 		private static readonly string[] m_vUrlsSubstr = {
479 			"url", "link", "host", "address", "hyper ref", "href",
480 			"web", "site"
481 		};
482 
483 		private static readonly string[] m_vNotes = {
484 			"note", "notes", "comment", "comments", "memo",
485 			"description", "free form", "freeform",
486 			"free text", "freetext", "free",
487 
488 			// Non-English names
489 			"kommentar", "hinweis"
490 		};
491 
492 		private static readonly string[] m_vNotesSubstr = {
493 			"note", "comment", "memo", "description", "free"
494 		};
495 
MapNameToStandardField(string strName, bool bAllowFuzzy)496 		public static string MapNameToStandardField(string strName, bool bAllowFuzzy)
497 		{
498 			if(strName == null) { Debug.Assert(false); return string.Empty; }
499 
500 			string strFind = strName.Trim().ToLower();
501 
502 			if(Array.IndexOf<string>(m_vTitles, strFind) >= 0)
503 				return PwDefs.TitleField;
504 			if(Array.IndexOf<string>(m_vUserNames, strFind) >= 0)
505 				return PwDefs.UserNameField;
506 			if(Array.IndexOf<string>(m_vPasswords, strFind) >= 0)
507 				return PwDefs.PasswordField;
508 			if(Array.IndexOf<string>(m_vUrls, strFind) >= 0)
509 				return PwDefs.UrlField;
510 			if(Array.IndexOf<string>(m_vNotes, strFind) >= 0)
511 				return PwDefs.NotesField;
512 
513 			if(strFind.Equals(KPRes.Title, StrUtil.CaseIgnoreCmp))
514 				return PwDefs.TitleField;
515 			if(strFind.Equals(KPRes.UserName, StrUtil.CaseIgnoreCmp))
516 				return PwDefs.UserNameField;
517 			if(strFind.Equals(KPRes.Password, StrUtil.CaseIgnoreCmp))
518 				return PwDefs.PasswordField;
519 			if(strFind.Equals(KPRes.Url, StrUtil.CaseIgnoreCmp))
520 				return PwDefs.UrlField;
521 			if(strFind.Equals(KPRes.Notes, StrUtil.CaseIgnoreCmp))
522 				return PwDefs.NotesField;
523 
524 			if(!bAllowFuzzy) return string.Empty;
525 
526 			// Check for passwords first, then user names ("vb_login_password")
527 			foreach(string strSub in m_vPasswordsSubstr)
528 			{
529 				if(strFind.Contains(strSub)) return PwDefs.PasswordField;
530 			}
531 			foreach(string strSub in m_vUserNamesSubstr)
532 			{
533 				if(strFind.Contains(strSub)) return PwDefs.UserNameField;
534 			}
535 			foreach(string strSub in m_vUrlsSubstr)
536 			{
537 				if(strFind.Contains(strSub)) return PwDefs.UrlField;
538 			}
539 			foreach(string strSub in m_vNotesSubstr)
540 			{
541 				if(strFind.Contains(strSub)) return PwDefs.NotesField;
542 			}
543 			foreach(string strSub in m_vTitlesSubstr)
544 			{
545 				if(strFind.Contains(strSub)) return PwDefs.TitleField;
546 			}
547 
548 			return string.Empty;
549 		}
550 
AppendToField(PwEntry pe, string strName, string strValue, PwDatabase pdContext)551 		public static void AppendToField(PwEntry pe, string strName, string strValue,
552 			PwDatabase pdContext)
553 		{
554 			AppendToField(pe, strName, strValue, pdContext, null, false);
555 		}
556 
AppendToField(PwEntry pe, string strName, string strValue, PwDatabase pdContext, string strSeparator, bool bOnlyIfNotDup)557 		public static void AppendToField(PwEntry pe, string strName, string strValue,
558 			PwDatabase pdContext, string strSeparator, bool bOnlyIfNotDup)
559 		{
560 			if(pe == null) { Debug.Assert(false); return; }
561 			if(string.IsNullOrEmpty(strName)) { Debug.Assert(false); return; }
562 
563 			if(strValue == null) { Debug.Assert(false); strValue = string.Empty; }
564 
565 			if(strSeparator == null)
566 			{
567 				if(PwDefs.IsStandardField(strName) && (strName != PwDefs.NotesField))
568 					strSeparator = ", ";
569 				else strSeparator = MessageService.NewLine;
570 			}
571 
572 			ProtectedString psPrev = pe.Strings.Get(strName);
573 			if((psPrev == null) || psPrev.IsEmpty)
574 			{
575 				MemoryProtectionConfig mpc = ((pdContext != null) ?
576 					pdContext.MemoryProtection : new MemoryProtectionConfig());
577 				bool bProtect = mpc.GetProtection(strName);
578 
579 				pe.Strings.Set(strName, new ProtectedString(bProtect, strValue));
580 			}
581 			else if(strValue.Length != 0)
582 			{
583 				bool bAppend = true;
584 				if(bOnlyIfNotDup)
585 				{
586 					ProtectedString psValue = new ProtectedString(false, strValue);
587 					bAppend = !psPrev.Equals(psValue, false);
588 				}
589 
590 				if(bAppend)
591 					pe.Strings.Set(strName, psPrev + (strSeparator + strValue));
592 			}
593 		}
594 
EntryEquals(PwEntry pe1, PwEntry pe2)595 		public static bool EntryEquals(PwEntry pe1, PwEntry pe2)
596 		{
597 			if(pe1.ParentGroup == null) return false;
598 			if(pe2.ParentGroup == null) return false;
599 			if(pe1.ParentGroup.Name != pe2.ParentGroup.Name)
600 				return false;
601 
602 			return pe1.Strings.EqualsDictionary(pe2.Strings,
603 				PwCompareOptions.NullEmptyEquivStd, MemProtCmpMode.None);
604 		}
605 
GuiSendRetrieve(string strSendPrefix)606 		internal static string GuiSendRetrieve(string strSendPrefix)
607 		{
608 			if(strSendPrefix.Length > 0)
609 				GuiSendKeysPrc(strSendPrefix);
610 
611 			return GuiRetrieveDataField();
612 		}
613 
GuiRetrieveDataField()614 		private static string GuiRetrieveDataField()
615 		{
616 			ClipboardUtil.Clear();
617 			Application.DoEvents();
618 
619 			GuiSendKeysPrc(@"^c");
620 
621 			try
622 			{
623 				if(ClipboardUtil.ContainsText())
624 					return (ClipboardUtil.GetText() ?? string.Empty);
625 			}
626 			catch(Exception) { Debug.Assert(false); } // Opened by other process
627 
628 			return string.Empty;
629 		}
630 
GuiSendKeysPrc(string strSend)631 		internal static void GuiSendKeysPrc(string strSend)
632 		{
633 			if(strSend.Length > 0)
634 				SendInputEx.SendKeysWait(strSend, false);
635 
636 			Application.DoEvents();
637 			Thread.Sleep(100);
638 			Application.DoEvents();
639 		}
640 
GuiSendWaitWindowChange(string strSend)641 		internal static void GuiSendWaitWindowChange(string strSend)
642 		{
643 			IntPtr ptrCur = NativeMethods.GetForegroundWindowHandle();
644 
645 			ImportUtil.GuiSendKeysPrc(strSend);
646 
647 			int nRound = 0;
648 			while(true)
649 			{
650 				Application.DoEvents();
651 
652 				IntPtr ptr = NativeMethods.GetForegroundWindowHandle();
653 				if(ptr != ptrCur) break;
654 
655 				++nRound;
656 				if(nRound > 1000)
657 					throw new InvalidOperationException();
658 
659 				Thread.Sleep(50);
660 			}
661 
662 			Thread.Sleep(100);
663 			Application.DoEvents();
664 		}
665 
FixUrl(string strUrl)666 		internal static string FixUrl(string strUrl)
667 		{
668 			strUrl = (strUrl ?? string.Empty).Trim();
669 
670 			if((strUrl.Length > 0) && (strUrl.IndexOf('.') >= 0) &&
671 				(strUrl.IndexOf(':') < 0) && (strUrl.IndexOf('@') < 0))
672 			{
673 				string strNew = ("https://" + strUrl.ToLower());
674 				if(strUrl.IndexOf('/') < 0) strNew += "/";
675 				return strNew;
676 			}
677 
678 			return strUrl;
679 		}
680 	}
681 }
682