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.ComponentModel;
23 using System.Diagnostics;
24 using System.Drawing;
25 using System.Reflection;
26 using System.Text;
27 using System.Windows.Forms;
28 using System.Xml;
29 using System.Xml.Serialization;
30 
31 using KeePass.UI;
32 using KeePass.Util;
33 using KeePass.Util.XmlSerialization;
34 
35 using KeePassLib;
36 using KeePassLib.Delegates;
37 using KeePassLib.Native;
38 using KeePassLib.Serialization;
39 using KeePassLib.Utility;
40 
41 namespace KeePass.App.Configuration
42 {
43 	[XmlType(AppConfigEx.StrXmlTypeName)]
44 	public sealed class AppConfigEx
45 	{
46 		internal const string StrXmlTypeName = "Configuration";
47 
48 		private string m_strSearchString = null;
49 
AppConfigEx()50 		public AppConfigEx()
51 		{
52 		}
53 
54 #if DEBUG
~AppConfigEx()55 		~AppConfigEx()
56 		{
57 			Debug.Assert(m_strSearchString == null);
58 		}
59 #endif
60 
61 		private AceMeta m_meta = null;
62 		public AceMeta Meta
63 		{
64 			get
65 			{
66 				if(m_meta == null) m_meta = new AceMeta();
67 				return m_meta;
68 			}
69 			set
70 			{
71 				if(value == null) throw new ArgumentNullException("value");
72 				m_meta = value;
73 			}
74 		}
75 
76 		private AceApplication m_aceApp = null;
77 		public AceApplication Application
78 		{
79 			get
80 			{
81 				if(m_aceApp == null) m_aceApp = new AceApplication();
82 				return m_aceApp;
83 			}
84 			set
85 			{
86 				if(value == null) throw new ArgumentNullException("value");
87 				m_aceApp = value;
88 			}
89 		}
90 
91 		private AceLogging m_aceLogging = null;
92 		public AceLogging Logging
93 		{
94 			get
95 			{
96 				if(m_aceLogging == null) m_aceLogging = new AceLogging();
97 				return m_aceLogging;
98 			}
99 			set
100 			{
101 				if(value == null) throw new ArgumentNullException("value");
102 				m_aceLogging = value;
103 			}
104 		}
105 
106 		private AceMainWindow m_uiMainWindow = null;
107 		public AceMainWindow MainWindow
108 		{
109 			get
110 			{
111 				if(m_uiMainWindow == null) m_uiMainWindow = new AceMainWindow();
112 				return m_uiMainWindow;
113 			}
114 			set
115 			{
116 				if(value == null) throw new ArgumentNullException("value");
117 				m_uiMainWindow = value;
118 			}
119 		}
120 
121 		private AceUI m_aceUI = null;
122 		public AceUI UI
123 		{
124 			get
125 			{
126 				if(m_aceUI == null) m_aceUI = new AceUI();
127 				return m_aceUI;
128 			}
129 			set
130 			{
131 				if(value == null) throw new ArgumentNullException("value");
132 				m_aceUI = value;
133 			}
134 		}
135 
136 		private AceSecurity m_sec = null;
137 		public AceSecurity Security
138 		{
139 			get
140 			{
141 				if(m_sec == null) m_sec = new AceSecurity();
142 				return m_sec;
143 			}
144 			set
145 			{
146 				if(value == null) throw new ArgumentNullException("value");
147 				m_sec = value;
148 			}
149 		}
150 
151 		private AceNative m_native = null;
152 		public AceNative Native
153 		{
154 			get
155 			{
156 				if(m_native == null) m_native = new AceNative();
157 				return m_native;
158 			}
159 			set
160 			{
161 				if(value == null) throw new ArgumentNullException("value");
162 				m_native = value;
163 			}
164 		}
165 
166 		private AcePasswordGenerator m_pwGen = null;
167 		public AcePasswordGenerator PasswordGenerator
168 		{
169 			get
170 			{
171 				if(m_pwGen == null) m_pwGen = new AcePasswordGenerator();
172 				return m_pwGen;
173 			}
174 			set
175 			{
176 				if(value == null) throw new ArgumentNullException("value");
177 				m_pwGen = value;
178 			}
179 		}
180 
181 		private AceSearch m_aceSearch = null;
182 		public AceSearch Search
183 		{
184 			get
185 			{
186 				if(m_aceSearch == null) m_aceSearch = new AceSearch();
187 				return m_aceSearch;
188 			}
189 			set
190 			{
191 				if(value == null) throw new ArgumentNullException("value");
192 				m_aceSearch = value;
193 			}
194 		}
195 
196 		private AceDefaults m_def = null;
197 		public AceDefaults Defaults
198 		{
199 			get
200 			{
201 				if(m_def == null) m_def = new AceDefaults();
202 				return m_def;
203 			}
204 			set
205 			{
206 				if(value == null) throw new ArgumentNullException("value");
207 				m_def = value;
208 			}
209 		}
210 
211 		private AceIntegration m_int = null;
212 		public AceIntegration Integration
213 		{
214 			get
215 			{
216 				if(m_int == null) m_int = new AceIntegration();
217 				return m_int;
218 			}
219 			set
220 			{
221 				if(value == null) throw new ArgumentNullException("value");
222 				m_int = value;
223 			}
224 		}
225 
226 		private AceCustomConfig m_cc = null;
227 		[XmlIgnore]
228 		public AceCustomConfig CustomConfig
229 		{
230 			get
231 			{
232 				if(m_cc == null) m_cc = new AceCustomConfig();
233 				return m_cc;
234 			}
235 			set
236 			{
237 				if(value == null) throw new ArgumentNullException("value");
238 				m_cc = value;
239 			}
240 		}
241 
242 		[XmlArray("Custom")]
243 		[XmlArrayItem("Item")]
244 		public AceKvp[] CustomSerialized
245 		{
246 			get
247 			{
248 				return this.CustomConfig.Serialize(); // m_cc might be null
249 			}
250 			set
251 			{
252 				if(value == null) throw new ArgumentNullException("value");
253 				this.CustomConfig.Deserialize(value); // m_cc might be null
254 			}
255 		}
256 
257 		/// <summary>
258 		/// Prepare for saving the configuration to disk. None of the
259 		/// modifications in this method need to be rolled back
260 		/// (for rollback, use <c>OnSavePre</c> / <c>OnSavePost</c>).
261 		/// </summary>
PrepareSave()262 		private void PrepareSave()
263 		{
264 			AceMeta aceMeta = this.Meta; // m_meta might be null
265 			AceApplication aceApp = this.Application; // m_aceApp might be null
266 			AceSearch aceSearch = this.Search; // m_aceSearch might be null
267 			AceDefaults aceDef = this.Defaults; // m_def might be null
268 
269 			aceMeta.OmitItemsWithDefaultValues = true;
270 			aceMeta.DpiFactorX = DpiUtil.FactorX; // For new (not loaded) cfgs.
271 			aceMeta.DpiFactorY = DpiUtil.FactorY;
272 
273 			aceApp.LastUsedFile.ClearCredentials(true);
274 
275 			foreach(IOConnectionInfo iocMru in aceApp.MostRecentlyUsed.Items)
276 				iocMru.ClearCredentials(true);
277 
278 			if(aceDef.RememberKeySources == false)
279 				aceDef.KeySources.Clear();
280 
281 			aceApp.TriggerSystem = Program.TriggerSystem;
282 
283 			SearchUtil.PrepareForSerialize(aceSearch.LastUsedProfile);
284 			foreach(SearchParameters sp in aceSearch.UserProfiles)
285 				SearchUtil.PrepareForSerialize(sp);
286 
287 			const int m = 64; // Maximum number of compatibility items
288 			List<string> l = aceApp.PluginCompatibility;
289 			if(l.Count > m) l.RemoveRange(m, l.Count - m); // See reg.
290 		}
291 
OnLoad()292 		internal void OnLoad()
293 		{
294 			AceMainWindow aceMW = this.MainWindow; // m_uiMainWindow might be null
295 			AceSearch aceSearch = this.Search; // m_aceSearch might be null
296 			AceDefaults aceDef = this.Defaults; // m_def might be null
297 
298 			// aceInt.UrlSchemeOverrides.SetDefaultsIfEmpty();
299 
300 			ObfuscateCred(false);
301 			ChangePathsRelAbs(true);
302 
303 			// Remove invalid columns
304 			List<AceColumn> lColumns = aceMW.EntryListColumns;
305 			int i = 0;
306 			while(i < lColumns.Count)
307 			{
308 				if(((int)lColumns[i].Type < 0) || ((int)lColumns[i].Type >=
309 					(int)AceColumnType.Count))
310 					lColumns.RemoveAt(i);
311 				else ++i;
312 			}
313 
314 			SearchUtil.FinishDeserialize(aceSearch.LastUsedProfile);
315 			foreach(SearchParameters sp in aceSearch.UserProfiles)
316 				SearchUtil.FinishDeserialize(sp);
317 
318 			DpiScale();
319 
320 			if(aceMW.EscMinimizesToTray) // For backward compatibility
321 			{
322 				aceMW.EscMinimizesToTray = false; // Default value
323 				aceMW.EscAction = AceEscAction.MinimizeToTray;
324 			}
325 
326 			if(NativeLib.IsUnix())
327 			{
328 				this.Security.MasterKeyOnSecureDesktop = false;
329 
330 				AceIntegration aceInt = this.Integration;
331 				aceInt.HotKeyGlobalAutoType = (long)Keys.None;
332 				aceInt.HotKeyGlobalAutoTypePassword = (long)Keys.None;
333 				aceInt.HotKeySelectedAutoType = (long)Keys.None;
334 				aceInt.HotKeyShowWindow = (long)Keys.None;
335 				aceInt.HotKeyEntryMenu = (long)Keys.None;
336 			}
337 
338 			if(MonoWorkarounds.IsRequired(1378))
339 			{
340 				AceWorkspaceLocking aceWL = this.Security.WorkspaceLocking;
341 				aceWL.LockOnSessionSwitch = false;
342 				aceWL.LockOnSuspend = false;
343 				aceWL.LockOnRemoteControlChange = false;
344 			}
345 
346 			if(MonoWorkarounds.IsRequired(1418))
347 			{
348 				aceMW.MinimizeAfterOpeningDatabase = false;
349 				this.Application.Start.MinimizedAndLocked = false;
350 			}
351 
352 			if(MonoWorkarounds.IsRequired(1976))
353 			{
354 				aceMW.FocusQuickFindOnRestore = false;
355 				aceMW.FocusQuickFindOnUntray = false;
356 			}
357 		}
358 
OnSavePre()359 		internal void OnSavePre()
360 		{
361 			PrepareSave();
362 
363 			ChangePathsRelAbs(false);
364 			ObfuscateCred(true);
365 			RemoveSensitiveInfo(true);
366 		}
367 
OnSavePost()368 		internal void OnSavePost()
369 		{
370 			RemoveSensitiveInfo(false);
371 			ObfuscateCred(false);
372 			ChangePathsRelAbs(true);
373 		}
374 
ChangePathsRelAbs(bool bMakeAbsolute)375 		private void ChangePathsRelAbs(bool bMakeAbsolute)
376 		{
377 			AceApplication aceApp = this.Application; // m_aceApp might be null
378 			AceDefaults aceDef = this.Defaults; // m_def might be null
379 
380 			ChangePathRelAbs(aceApp.LastUsedFile, bMakeAbsolute);
381 
382 			foreach(IOConnectionInfo iocMru in aceApp.MostRecentlyUsed.Items)
383 				ChangePathRelAbs(iocMru, bMakeAbsolute);
384 
385 			List<string> lWDKeys = aceApp.GetWorkingDirectoryContexts();
386 			foreach(string strWDKey in lWDKeys)
387 				aceApp.SetWorkingDirectory(strWDKey, ChangePathRelAbsStr(
388 					aceApp.GetWorkingDirectory(strWDKey), bMakeAbsolute));
389 
390 			foreach(AceKeyAssoc kfp in aceDef.KeySources)
391 			{
392 				kfp.DatabasePath = ChangePathRelAbsStr(kfp.DatabasePath, bMakeAbsolute);
393 				kfp.KeyFilePath = ChangePathRelAbsStr(kfp.KeyFilePath, bMakeAbsolute);
394 			}
395 		}
396 
ChangePathRelAbs(IOConnectionInfo ioc, bool bMakeAbsolute)397 		private static void ChangePathRelAbs(IOConnectionInfo ioc, bool bMakeAbsolute)
398 		{
399 			if(ioc == null) { Debug.Assert(false); return; }
400 
401 			if(!ioc.IsLocalFile()) return;
402 
403 			// Update path separators for current system
404 			ioc.Path = UrlUtil.ConvertSeparators(ioc.Path);
405 
406 			string strBase = WinUtil.GetExecutable();
407 			bool bIsAbs = UrlUtil.IsAbsolutePath(ioc.Path);
408 
409 			if(bMakeAbsolute && !bIsAbs)
410 				ioc.Path = UrlUtil.MakeAbsolutePath(strBase, ioc.Path);
411 			else if(!bMakeAbsolute && bIsAbs)
412 				ioc.Path = UrlUtil.MakeRelativePath(strBase, ioc.Path);
413 		}
414 
ChangePathRelAbsStr(string strPath, bool bMakeAbsolute)415 		private static string ChangePathRelAbsStr(string strPath, bool bMakeAbsolute)
416 		{
417 			if(strPath == null) { Debug.Assert(false); return string.Empty; }
418 			if(strPath.Length == 0) return strPath;
419 
420 			IOConnectionInfo ioc = IOConnectionInfo.FromPath(strPath);
421 			ChangePathRelAbs(ioc, bMakeAbsolute);
422 			return ioc.Path;
423 		}
424 
ObfuscateCred(bool bObf)425 		private void ObfuscateCred(bool bObf)
426 		{
427 			AceApplication aceApp = this.Application; // m_aceApp might be null
428 			AceIntegration aceInt = this.Integration; // m_int might be null
429 
430 			if(aceApp.LastUsedFile == null) { Debug.Assert(false); }
431 			else aceApp.LastUsedFile.Obfuscate(bObf);
432 
433 			foreach(IOConnectionInfo iocMru in aceApp.MostRecentlyUsed.Items)
434 			{
435 				if(iocMru == null) { Debug.Assert(false); }
436 				else iocMru.Obfuscate(bObf);
437 			}
438 
439 			if(bObf) aceInt.ProxyUserName = StrUtil.Obfuscate(aceInt.ProxyUserName);
440 			else aceInt.ProxyUserName = StrUtil.Deobfuscate(aceInt.ProxyUserName);
441 
442 			if(bObf) aceInt.ProxyPassword = StrUtil.Obfuscate(aceInt.ProxyPassword);
443 			else aceInt.ProxyPassword = StrUtil.Deobfuscate(aceInt.ProxyPassword);
444 		}
445 
RemoveSensitiveInfo(bool bRemove)446 		private void RemoveSensitiveInfo(bool bRemove)
447 		{
448 			SearchParameters sp = this.Search.LastUsedProfile;
449 
450 			if(bRemove)
451 			{
452 				Debug.Assert(m_strSearchString == null);
453 				m_strSearchString = sp.SearchString;
454 				sp.SearchString = string.Empty;
455 			}
456 			else
457 			{
458 				if(m_strSearchString != null)
459 				{
460 					sp.SearchString = m_strSearchString;
461 					m_strSearchString = null;
462 				}
463 				else { Debug.Assert(false); }
464 			}
465 		}
466 
DpiScale()467 		private void DpiScale()
468 		{
469 			AceMeta aceMeta = this.Meta; // m_meta might be null
470 			double dCfgX = aceMeta.DpiFactorX, dCfgY = aceMeta.DpiFactorY;
471 			double dScrX = DpiUtil.FactorX, dScrY = DpiUtil.FactorY;
472 
473 			if((dScrX == dCfgX) && (dScrY == dCfgY)) return;
474 
475 			// When this method returns, all positions and sizes are in pixels
476 			// for the current screen DPI
477 			aceMeta.DpiFactorX = dScrX;
478 			aceMeta.DpiFactorY = dScrY;
479 
480 			// Backward compatibility; configuration files created by KeePass
481 			// 2.37 and earlier do not contain DpiFactor* values, they default
482 			// to 0.0 and all positions and sizes are in pixels for the current
483 			// screen DPI; so, do not perform any DPI scaling in this case
484 			if((dCfgX == 0.0) || (dCfgY == 0.0)) return;
485 
486 			double sX = dScrX / dCfgX, sY = dScrY / dCfgY;
487 			GFunc<int, int> fX = delegate(int x)
488 			{
489 				return (int)Math.Round((double)x * sX);
490 			};
491 			GFunc<int, int> fY = delegate(int y)
492 			{
493 				return (int)Math.Round((double)y * sY);
494 			};
495 			GFunc<string, string> fWsr = delegate(string strRect)
496 			{
497 				return UIUtil.ScaleWindowScreenRect(strRect, sX, sY);
498 			};
499 			GFunc<string, string> fVX = delegate(string strArray)
500 			{
501 				if(string.IsNullOrEmpty(strArray)) return strArray;
502 
503 				try
504 				{
505 					int[] v = StrUtil.DeserializeIntArray(strArray);
506 					if(v == null) { Debug.Assert(false); return strArray; }
507 
508 					for(int i = 0; i < v.Length; ++i)
509 						v[i] = (int)Math.Round((double)v[i] * sX);
510 
511 					return StrUtil.SerializeIntArray(v);
512 				}
513 				catch(Exception) { Debug.Assert(false); }
514 
515 				return strArray;
516 			};
517 			Action<AceFont> fFont = delegate(AceFont f)
518 			{
519 				if(f == null) { Debug.Assert(false); return; }
520 
521 				if(f.GraphicsUnit == GraphicsUnit.Pixel)
522 					f.Size = (float)(f.Size * sY);
523 			};
524 
525 			AceMainWindow mw = this.MainWindow;
526 			AceUI ui = this.UI;
527 
528 			if(mw.X != AppDefs.InvalidWindowValue) mw.X = fX(mw.X);
529 			if(mw.Y != AppDefs.InvalidWindowValue) mw.Y = fY(mw.Y);
530 			if(mw.Width != AppDefs.InvalidWindowValue) mw.Width = fX(mw.Width);
531 			if(mw.Height != AppDefs.InvalidWindowValue) mw.Height = fY(mw.Height);
532 
533 			foreach(AceColumn c in mw.EntryListColumns)
534 			{
535 				if(c.Width >= 0) c.Width = fX(c.Width);
536 			}
537 
538 			ui.DataViewerRect = fWsr(ui.DataViewerRect);
539 			ui.DataEditorRect = fWsr(ui.DataEditorRect);
540 			ui.CharPickerRect = fWsr(ui.CharPickerRect);
541 			ui.AutoTypeCtxRect = fWsr(ui.AutoTypeCtxRect);
542 			ui.AutoTypeCtxColumnWidths = fVX(ui.AutoTypeCtxColumnWidths);
543 
544 			fFont(ui.StandardFont);
545 			fFont(ui.PasswordFont);
546 			fFont(ui.DataEditorFont);
547 		}
548 
549 		private static Dictionary<object, string> m_dictXmlPathCache =
550 			new Dictionary<object, string>();
IsOptionEnforced(object pContainer, PropertyInfo pi)551 		public static bool IsOptionEnforced(object pContainer, PropertyInfo pi)
552 		{
553 			if(pContainer == null) { Debug.Assert(false); return false; }
554 			if(pi == null) { Debug.Assert(false); return false; }
555 
556 			XmlDocument xdEnforced = AppConfigSerializer.EnforcedConfigXml;
557 			if(xdEnforced == null) return false;
558 
559 			string strObjPath;
560 			if(!m_dictXmlPathCache.TryGetValue(pContainer, out strObjPath))
561 			{
562 				strObjPath = XmlUtil.GetObjectXmlPath(Program.Config, pContainer);
563 				if(string.IsNullOrEmpty(strObjPath)) { Debug.Assert(false); return false; }
564 
565 				m_dictXmlPathCache[pContainer] = strObjPath;
566 			}
567 
568 			string strProp = XmlSerializerEx.GetXmlName(pi);
569 			if(string.IsNullOrEmpty(strProp)) { Debug.Assert(false); return false; }
570 
571 			string strPre = strObjPath;
572 			if(!strPre.EndsWith("/")) strPre += "/";
573 			string strXPath = strPre + strProp;
574 
575 			XmlNode xn = xdEnforced.SelectSingleNode(strXPath);
576 			if(xn == null) return false;
577 
578 			XmContext ctx = new XmContext(null, AppConfigEx.GetNodeOptions,
579 				AppConfigEx.GetNodeKey);
580 			return XmlUtil.IsAlwaysEnforced(xn, strXPath, ctx);
581 		}
582 
IsOptionEnforced(object pContainer, string strPropertyName)583 		public static bool IsOptionEnforced(object pContainer, string strPropertyName)
584 		{
585 			if(pContainer == null) { Debug.Assert(false); return false; }
586 			if(string.IsNullOrEmpty(strPropertyName)) { Debug.Assert(false); return false; }
587 
588 			// To improve performance (avoid type queries), check here, too
589 			XmlDocument xdEnforced = AppConfigSerializer.EnforcedConfigXml;
590 			if(xdEnforced == null) return false;
591 
592 			Type tContainer = pContainer.GetType();
593 			PropertyInfo pi = tContainer.GetProperty(strPropertyName);
594 			return IsOptionEnforced(pContainer, pi);
595 		}
596 
ClearXmlPathCache()597 		public static void ClearXmlPathCache()
598 		{
599 			m_dictXmlPathCache.Clear();
600 		}
601 
Apply(AceApplyFlags f)602 		public void Apply(AceApplyFlags f)
603 		{
604 			AceApplication aceApp = this.Application; // m_aceApp might be null
605 			AceSecurity aceSec = this.Security; // m_sec might be null
606 			AceIntegration aceInt = this.Integration; // m_int might be null
607 
608 			if((f & AceApplyFlags.Proxy) != AceApplyFlags.None)
609 				IOConnection.SetProxy(aceInt.ProxyType, aceInt.ProxyAddress,
610 					aceInt.ProxyPort, aceInt.ProxyAuthType, aceInt.ProxyUserName,
611 					aceInt.ProxyPassword);
612 
613 			if((f & AceApplyFlags.Ssl) != AceApplyFlags.None)
614 				IOConnection.SslCertsAcceptInvalid = aceSec.SslCertsAcceptInvalid;
615 
616 			if((f & AceApplyFlags.FileTransactions) != AceApplyFlags.None)
617 				FileTransactionEx.ExtraSafe = aceApp.FileTxExtra;
618 		}
619 
GetNodeOptions(XmNodeOptions o, string strXPath)620 		internal static void GetNodeOptions(XmNodeOptions o, string strXPath)
621 		{
622 			if(o == null) { Debug.Assert(false); return; }
623 			if(string.IsNullOrEmpty(strXPath)) { Debug.Assert(false); return; }
624 			Debug.Assert(strXPath.IndexOf('[') < 0);
625 
626 			switch(strXPath)
627 			{
628 				// Sync. with documentation
629 
630 				case "/Configuration/Application/PluginCompatibility":
631 				case "/Configuration/Meta/DpiFactorX":
632 				case "/Configuration/Meta/DpiFactorY":
633 					o.NodeMode = XmNodeMode.None;
634 					break;
635 
636 				case "/Configuration/Application/TriggerSystem/Triggers/Trigger":
637 				case "/Configuration/Defaults/KeySources/Association":
638 				case "/Configuration/PasswordGenerator/AutoGeneratedPasswordsProfile":
639 				case "/Configuration/PasswordGenerator/LastUsedProfile":
640 				case "/Configuration/PasswordGenerator/UserProfiles/Profile":
641 				case "/Configuration/Search/LastUsedProfile":
642 				case "/Configuration/Search/UserProfiles/Profile":
643 					o.ContentMode = XmContentMode.Replace;
644 					break;
645 
646 				// Nodes that do not have child elements:
647 				// case "/Configuration/Application/WorkingDirectories/Item":
648 				// case "/Configuration/Integration/AutoTypeAbortOnWindows/Window":
649 
650 				// Nodes where the mode 'Merge' may be more useful:
651 				// case "/Configuration/Application/MostRecentlyUsed/Items/ConnectionInfo":
652 				//     (allow users to save credentials)
653 				// case "/Configuration/Custom/Item":
654 				//     (empty Value explicitly only)
655 				// case "/Configuration/Defaults/SearchParameters":
656 				//     (admin might only want to turn off case-sensitivity)
657 				// case "/Configuration/Integration/UrlSchemeOverrides/CustomOverrides/Override":
658 				//     (allow users to enable/disable the item)
659 				// case "/Configuration/MainWindow/EntryListColumnCollection/Column":
660 				//     (allow users to change the width)
661 
662 				default: break;
663 			}
664 		}
665 
GetNodeKey(XmlNode xn, string strXPath)666 		internal static string GetNodeKey(XmlNode xn, string strXPath)
667 		{
668 			if(xn == null) { Debug.Assert(false); return null; }
669 			if(string.IsNullOrEmpty(strXPath)) { Debug.Assert(false); return null; }
670 
671 			Debug.Assert(xn is XmlElement);
672 			Debug.Assert((strXPath == xn.Name) || strXPath.EndsWith("/" + xn.Name));
673 			Debug.Assert(strXPath.IndexOf('[') < 0);
674 
675 			string strA = null, strB = null;
676 			switch(strXPath)
677 			{
678 				// Sync. with documentation
679 
680 				case "/Configuration/Application/PluginCompatibility/Item":
681 				case "/Configuration/Application/WorkingDirectories/Item":
682 				case "/Configuration/Integration/AutoTypeAbortOnWindows/Window":
683 					strA = XmlUtil.SafeInnerXml(xn);
684 					break;
685 
686 				case "/Configuration/Application/MostRecentlyUsed/Items/ConnectionInfo":
687 					strA = XmlUtil.SafeInnerXml(xn, "Path");
688 					strB = XmlUtil.SafeInnerXml(xn, "UserName"); // Cf. MRU display name
689 					break;
690 
691 				case "/Configuration/Application/TriggerSystem/Triggers/Trigger":
692 					strA = XmlUtil.SafeInnerXml(xn, "Guid");
693 					break;
694 
695 				case "/Configuration/Custom/Item":
696 					strA = XmlUtil.SafeInnerXml(xn, "Key");
697 					break;
698 
699 				case "/Configuration/Defaults/KeySources/Association":
700 					strA = XmlUtil.SafeInnerXml(xn, "DatabasePath");
701 					break;
702 
703 				case "/Configuration/Integration/UrlSchemeOverrides/CustomOverrides/Override":
704 					strA = XmlUtil.SafeInnerXml(xn, "Scheme");
705 					strB = XmlUtil.SafeInnerXml(xn, "UrlOverride");
706 					break;
707 
708 				case "/Configuration/MainWindow/EntryListColumnCollection/Column":
709 					strA = XmlUtil.SafeInnerXml(xn, "Type");
710 					strB = XmlUtil.SafeInnerXml(xn, "CustomName");
711 					break;
712 
713 				case "/Configuration/PasswordGenerator/UserProfiles/Profile":
714 				case "/Configuration/Search/UserProfiles/Profile":
715 					strA = XmlUtil.SafeInnerXml(xn, "Name");
716 					break;
717 
718 				default: break;
719 			}
720 
721 			Debug.Assert((strA == null) || (strA.IndexOf('<') < 0));
722 			Debug.Assert((strB == null) || (strB.IndexOf('<') < 0));
723 			Debug.Assert((strB == null) || (strA != null)); // B => A
724 			Debug.Assert((strA == null) || (strA.Length != 0));
725 
726 			if(strB != null) return ((strA ?? string.Empty) + " <> " + strB);
727 			return strA;
728 		}
729 
GetEmptyConfigXml()730 		internal static string GetEmptyConfigXml()
731 		{
732 			return ("<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" +
733 				"<" + StrXmlTypeName + " xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\r\n" +
734 				"\t<Meta />\r\n" +
735 				"</" + StrXmlTypeName + ">");
736 		}
737 	}
738 
739 	[Flags]
740 	public enum AceApplyFlags
741 	{
742 		None = 0,
743 		Proxy = 0x1,
744 		Ssl = 0x2,
745 		FileTransactions = 0x4,
746 
747 		All = 0x7FFF
748 	}
749 
750 	public sealed class AceMeta
751 	{
AceMeta()752 		public AceMeta()
753 		{
754 		}
755 
756 		private bool m_bPrefLocalCfg = false;
757 		public bool PreferUserConfiguration
758 		{
759 			get { return m_bPrefLocalCfg; }
760 			set { m_bPrefLocalCfg = value; }
761 		}
762 
763 		// private bool m_bIsEnforced = false;
764 		// [XmlIgnore]
765 		// public bool IsEnforcedConfiguration
766 		// {
767 		//	get { return m_bIsEnforced; }
768 		//	set { m_bIsEnforced = value; }
769 		// }
770 
771 		private bool m_bOmitDefaultValues = true;
772 		// Informational property only (like an XML comment);
773 		// currently doesn't have any effect (the XmlSerializer
774 		// always omits default values, independent of this
775 		// property)
776 		public bool OmitItemsWithDefaultValues
777 		{
778 			get { return m_bOmitDefaultValues; }
779 			set { m_bOmitDefaultValues = value; }
780 		}
781 
782 		private double m_dDpiFactorX = 0.0; // See AppConfigEx.DpiScale()
783 		[DefaultValue(0.0)]
784 		public double DpiFactorX
785 		{
786 			get { return m_dDpiFactorX; }
787 			set { m_dDpiFactorX = value; }
788 		}
789 
790 		private double m_dDpiFactorY = 0.0; // See AppConfigEx.DpiScale()
791 		[DefaultValue(0.0)]
792 		public double DpiFactorY
793 		{
794 			get { return m_dDpiFactorY; }
795 			set { m_dDpiFactorY = value; }
796 		}
797 	}
798 }
799