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.Text;
26 using System.Threading;
27 using System.Windows.Forms;
28 
29 using KeePass.App;
30 using KeePass.App.Configuration;
31 using KeePass.Resources;
32 using KeePass.UI;
33 
34 using KeePassLib;
35 using KeePassLib.Delegates;
36 using KeePassLib.Security;
37 using KeePassLib.Utility;
38 
39 namespace KeePass.Forms
40 {
AceColumnDelegate(AceColumn c)41 	public delegate void AceColumnDelegate(AceColumn c);
42 	// public delegate void UpdateUIDelegate(bool bGuiToInternal);
43 
44 	public partial class ColumnsForm : Form
45 	{
46 		private bool m_bIgnoreHideCheckEvent = false;
47 
ColumnsForm()48 		public ColumnsForm()
49 		{
50 			InitializeComponent();
51 			GlobalWindowManager.InitializeForm(this);
52 		}
53 
OnFormLoad(object sender, EventArgs e)54 		private void OnFormLoad(object sender, EventArgs e)
55 		{
56 			GlobalWindowManager.AddWindow(this);
57 
58 			BannerFactory.CreateBannerEx(this, m_bannerImage,
59 				Properties.Resources.B48x48_View_Detailed,
60 				KPRes.ConfigureColumns, KPRes.ConfigureColumnsDesc);
61 			this.Icon = AppIcons.Default;
62 			this.Text = KPRes.ConfigureColumns;
63 
64 			float fWidth = (float)(m_lvColumns.ClientRectangle.Width -
65 				UIUtil.GetVScrollBarWidth()) / 5.0f;
66 			m_lvColumns.Columns.Add(KPRes.Column, (int)(fWidth * 3.0f));
67 			m_lvColumns.Columns.Add(KPRes.Asterisks + " ***", (int)fWidth);
68 			m_lvColumns.Columns.Add(KPRes.Toggle + " ***", (int)fWidth);
69 
70 			UIUtil.SetExplorerTheme(m_lvColumns, false);
71 
72 			UpdateColumnPropInfo();
73 
74 			ThreadPool.QueueUserWorkItem(new WaitCallback(FillColumnsList));
75 		}
76 
AddAceColumnTh(AceColumn c)77 		private void AddAceColumnTh(AceColumn c)
78 		{
79 			string strLvgName = KPRes.StandardFields;
80 			if(c.Type == AceColumnType.CustomString)
81 				strLvgName = KPRes.CustomFields;
82 			else if(c.Type == AceColumnType.PluginExt)
83 				strLvgName = KPRes.PluginProvided;
84 			else if((c.Type == AceColumnType.Size) || (c.Type == AceColumnType.LastPasswordModTime))
85 				strLvgName = KPRes.More;
86 
87 			ListViewGroup lvgContainer = null;
88 			foreach(ListViewGroup lvg in m_lvColumns.Groups)
89 			{
90 				if(lvg.Header == strLvgName) { lvgContainer = lvg; break; }
91 			}
92 			if(lvgContainer == null)
93 			{
94 				lvgContainer = new ListViewGroup(strLvgName);
95 				m_lvColumns.Groups.Add(lvgContainer);
96 			}
97 
98 			ListViewItem lvi = new ListViewItem(c.GetDisplayName());
99 			lvi.Tag = c;
100 
101 			lvi.SubItems.Add(c.HideWithAsterisks ? KPRes.Yes : KPRes.No);
102 
103 			if(c.Type == AceColumnType.Password)
104 				lvi.SubItems.Add(UIUtil.GetKeysName(Keys.Control | Keys.H));
105 			else if(c.Type == AceColumnType.UserName)
106 				lvi.SubItems.Add(UIUtil.GetKeysName(Keys.Control | Keys.J));
107 			else lvi.SubItems.Add(string.Empty);
108 
109 			bool bChecked = false;
110 			List<AceColumn> lCur = Program.Config.MainWindow.EntryListColumns;
111 			foreach(AceColumn cCur in lCur)
112 			{
113 				if(cCur.Type != c.Type) continue;
114 
115 				if((c.Type != AceColumnType.CustomString) &&
116 					(c.Type != AceColumnType.PluginExt))
117 				{
118 					bChecked = true;
119 					break;
120 				}
121 				else if((c.Type == AceColumnType.CustomString) &&
122 					(cCur.CustomName == c.CustomName))
123 				{
124 					bChecked = true;
125 					break;
126 				}
127 				else if((c.Type == AceColumnType.PluginExt) &&
128 					(cCur.CustomName == c.CustomName))
129 				{
130 					bChecked = true;
131 					break;
132 				}
133 			}
134 			lvi.Checked = bChecked;
135 
136 			lvi.Group = lvgContainer;
137 			m_lvColumns.Items.Add(lvi);
138 		}
139 
AddAceColumn(List<AceColumn> lContainer, AceColumn c)140 		private void AddAceColumn(List<AceColumn> lContainer, AceColumn c)
141 		{
142 			m_lvColumns.Invoke(new AceColumnDelegate(AddAceColumnTh), c);
143 			lContainer.Add(c);
144 		}
145 
AddStdAceColumn(List<AceColumn> lContainer, AceColumnType colType)146 		private void AddStdAceColumn(List<AceColumn> lContainer, AceColumnType colType)
147 		{
148 			bool bHide = (colType == AceColumnType.Password); // Passwords hidden by default
149 			int nWidth = -1;
150 			List<AceColumn> lCur = Program.Config.MainWindow.EntryListColumns;
151 			foreach(AceColumn cCur in lCur)
152 			{
153 				if(cCur.Type == colType)
154 				{
155 					bHide = cCur.HideWithAsterisks;
156 					nWidth = cCur.Width;
157 					break;
158 				}
159 			}
160 
161 			AddAceColumn(lContainer, new AceColumn(colType, string.Empty, bHide, nWidth));
162 		}
163 
FillColumnsList(object state)164 		private void FillColumnsList(object state)
165 		{
166 			List<AceColumn> l = new List<AceColumn>();
167 
168 			AddStdAceColumn(l, AceColumnType.Title);
169 			AddStdAceColumn(l, AceColumnType.UserName);
170 			AddStdAceColumn(l, AceColumnType.Password);
171 			AddStdAceColumn(l, AceColumnType.Url);
172 			AddStdAceColumn(l, AceColumnType.Notes);
173 			AddStdAceColumn(l, AceColumnType.ExpiryTime);
174 			AddStdAceColumn(l, AceColumnType.ExpiryTimeDateOnly);
175 
176 			AddStdAceColumn(l, AceColumnType.Attachment);
177 			AddStdAceColumn(l, AceColumnType.AttachmentCount);
178 
179 			AddStdAceColumn(l, AceColumnType.Tags);
180 			AddStdAceColumn(l, AceColumnType.OverrideUrl);
181 			AddStdAceColumn(l, AceColumnType.Uuid);
182 
183 			AddStdAceColumn(l, AceColumnType.AutoTypeEnabled);
184 			AddStdAceColumn(l, AceColumnType.AutoTypeSequences);
185 
186 			AddStdAceColumn(l, AceColumnType.CreationTime);
187 			AddStdAceColumn(l, AceColumnType.LastModificationTime);
188 			if((Program.Config.UI.UIFlags & (ulong)AceUIFlags.ShowLastAccessTime) != 0)
189 				AddStdAceColumn(l, AceColumnType.LastAccessTime);
190 			AddStdAceColumn(l, AceColumnType.HistoryCount);
191 
192 			AddStdAceColumn(l, AceColumnType.Size);
193 			AddStdAceColumn(l, AceColumnType.LastPasswordModTime);
194 
195 			SortedDictionary<string, AceColumn> d =
196 				new SortedDictionary<string, AceColumn>(StrUtil.CaseIgnoreComparer);
197 			List<AceColumn> lCur = Program.Config.MainWindow.EntryListColumns;
198 			foreach(AceColumn cCur in lCur)
199 			{
200 				if((cCur.Type == AceColumnType.CustomString) &&
201 					!d.ContainsKey(cCur.CustomName))
202 				{
203 					d[cCur.CustomName] = new AceColumn(AceColumnType.CustomString,
204 						cCur.CustomName, cCur.HideWithAsterisks, cCur.Width);
205 				}
206 			}
207 
208 			foreach(PwDocument pwDoc in Program.MainForm.DocumentManager.Documents)
209 			{
210 				if(string.IsNullOrEmpty(pwDoc.LockedIoc.Path) && pwDoc.Database.IsOpen)
211 				{
212 					EntryHandler eh = delegate(PwEntry pe)
213 					{
214 						foreach(KeyValuePair<string, ProtectedString> kvp in pe.Strings)
215 						{
216 							if(PwDefs.IsStandardField(kvp.Key)) continue;
217 							if(d.ContainsKey(kvp.Key)) continue;
218 
219 							d[kvp.Key] = new AceColumn(AceColumnType.CustomString,
220 								kvp.Key, kvp.Value.IsProtected, -1);
221 						}
222 
223 						return true;
224 					};
225 
226 					pwDoc.Database.RootGroup.TraverseTree(TraversalMethod.PreOrder, null, eh);
227 				}
228 			}
229 
230 			foreach(KeyValuePair<string, AceColumn> kvpCustom in d)
231 			{
232 				AddAceColumn(l, kvpCustom.Value);
233 			}
234 
235 			d.Clear();
236 			// Add active plugin columns (including those of uninstalled plugins)
237 			foreach(AceColumn cCur in lCur)
238 			{
239 				if(cCur.Type != AceColumnType.PluginExt) continue;
240 				if(d.ContainsKey(cCur.CustomName)) { Debug.Assert(false); continue; }
241 
242 				d[cCur.CustomName] = new AceColumn(AceColumnType.PluginExt,
243 					cCur.CustomName, cCur.HideWithAsterisks, cCur.Width);
244 			}
245 
246 			// Add unused plugin columns
247 			string[] vPlgExtNames = Program.ColumnProviderPool.GetColumnNames();
248 			foreach(string strPlgName in vPlgExtNames)
249 			{
250 				if(d.ContainsKey(strPlgName)) continue; // Do not overwrite
251 
252 				d[strPlgName] = new AceColumn(AceColumnType.PluginExt, strPlgName,
253 					false, -1);
254 			}
255 
256 			foreach(KeyValuePair<string, AceColumn> kvpExt in d)
257 			{
258 				AddAceColumn(l, kvpExt.Value);
259 			}
260 
261 			// m_lvColumns.Invoke(new UpdateUIDelegate(UpdateListEx), false);
262 		}
263 
UpdateListEx(bool bGuiToInternal)264 		private void UpdateListEx(bool bGuiToInternal)
265 		{
266 			if(bGuiToInternal)
267 			{
268 			}
269 			else // Internal to GUI
270 			{
271 				foreach(ListViewItem lvi in m_lvColumns.Items)
272 				{
273 					AceColumn c = (lvi.Tag as AceColumn);
274 					if(c == null) { Debug.Assert(false); continue; }
275 
276 					string str = (c.HideWithAsterisks ? KPRes.Yes : KPRes.No);
277 					lvi.SubItems[1].Text = str;
278 				}
279 			}
280 		}
281 
UpdateColumnPropInfo()282 		private void UpdateColumnPropInfo()
283 		{
284 			ListView.SelectedListViewItemCollection lvsic = m_lvColumns.SelectedItems;
285 			if((lvsic == null) || (lvsic.Count != 1) || (lvsic[0] == null))
286 			{
287 				m_grpColumn.Text = KPRes.SelectedColumn + ": (" + KPRes.None + ")";
288 				m_cbHide.Checked = false;
289 				m_cbHide.Enabled = false;
290 			}
291 			else
292 			{
293 				ListViewItem lvi = lvsic[0];
294 				AceColumn c = (lvi.Tag as AceColumn);
295 				if(c == null) { Debug.Assert(false); return; }
296 
297 				m_grpColumn.Text = KPRes.SelectedColumn + ": " + c.GetDisplayName();
298 				m_cbHide.Enabled = true;
299 
300 				m_bIgnoreHideCheckEvent = true;
301 				UIUtil.SetChecked(m_cbHide, c.HideWithAsterisks);
302 				m_bIgnoreHideCheckEvent = false;
303 			}
304 		}
305 
OnFormClosed(object sender, FormClosedEventArgs e)306 		private void OnFormClosed(object sender, FormClosedEventArgs e)
307 		{
308 			GlobalWindowManager.RemoveWindow(this);
309 		}
310 
OnBtnOK(object sender, EventArgs e)311 		private void OnBtnOK(object sender, EventArgs e)
312 		{
313 			AceMainWindow mw = Program.Config.MainWindow;
314 			List<AceColumn> l = mw.EntryListColumns;
315 			List<AceColumn> lOld = new List<AceColumn>(l);
316 
317 			l.Clear();
318 			foreach(ListViewItem lvi in m_lvColumns.Items)
319 			{
320 				if(!lvi.Checked) continue;
321 
322 				AceColumn c = (lvi.Tag as AceColumn);
323 				if(c == null) { Debug.Assert(false); continue; }
324 
325 				l.Add(c);
326 			}
327 
328 			mw.EntryListColumnDisplayOrder = ComputeNewDisplayOrder(l,
329 				lOld, mw.EntryListColumnDisplayOrder);
330 		}
331 
OnBtnCancel(object sender, EventArgs e)332 		private void OnBtnCancel(object sender, EventArgs e)
333 		{
334 		}
335 
OnColumnsSelectedIndexChanged(object sender, EventArgs e)336 		private void OnColumnsSelectedIndexChanged(object sender, EventArgs e)
337 		{
338 			UpdateColumnPropInfo();
339 		}
340 
OnHideCheckedChanged(object sender, EventArgs e)341 		private void OnHideCheckedChanged(object sender, EventArgs e)
342 		{
343 			if(m_bIgnoreHideCheckEvent) return;
344 
345 			bool bChecked = m_cbHide.Checked;
346 			foreach(ListViewItem lvi in m_lvColumns.SelectedItems)
347 			{
348 				AceColumn c = (lvi.Tag as AceColumn);
349 				if(c == null) { Debug.Assert(false); continue; }
350 
351 				if((c.Type == AceColumnType.Password) && c.HideWithAsterisks &&
352 					!AppPolicy.Try(AppPolicyId.UnhidePasswords))
353 				{
354 					// Do not change c.HideWithAsterisks
355 				}
356 				else c.HideWithAsterisks = bChecked;
357 			}
358 
359 			UpdateListEx(false);
360 		}
361 
362 		private sealed class AceColumnWithTag
363 		{
364 			public readonly AceColumn Column;
365 			public readonly string TypeNameEx;
366 
367 			public long Tag;
368 
AceColumnWithTag(AceColumn c, long lTag)369 			public AceColumnWithTag(AceColumn c, long lTag)
370 			{
371 				this.Column = c;
372 				this.TypeNameEx = c.GetTypeNameEx();
373 				this.Tag = lTag;
374 			}
375 
376 #if DEBUG
ToString()377 			public override string ToString()
378 			{
379 				return (this.TypeNameEx + ", " + this.Tag.ToString());
380 			}
381 #endif
382 
CompareByTags(AceColumnWithTag x, AceColumnWithTag y)383 			public static int CompareByTags(AceColumnWithTag x, AceColumnWithTag y)
384 			{
385 				return x.Tag.CompareTo(y.Tag);
386 			}
387 		}
388 
ComputeNewDisplayOrder(List<AceColumn> lNew, List<AceColumn> lOld, string strOldOrder)389 		private static string ComputeNewDisplayOrder(List<AceColumn> lNew,
390 			List<AceColumn> lOld, string strOldOrder)
391 		{
392 			if((lNew == null) || (lOld == null)) { Debug.Assert(false); return string.Empty; }
393 			if(string.IsNullOrEmpty(strOldOrder)) return string.Empty;
394 
395 			List<AceColumnWithTag> lOldS = new List<AceColumnWithTag>();
396 			try
397 			{
398 				int[] vOld = StrUtil.DeserializeIntArray(strOldOrder);
399 				if((vOld == null) || (vOld.Length != lOld.Count))
400 				{
401 					Debug.Assert(false);
402 					return string.Empty;
403 				}
404 
405 				for(int i = 0; i < vOld.Length; ++i)
406 				{
407 					if(lOld[i] == null) { Debug.Assert(false); return string.Empty; }
408 					lOldS.Add(new AceColumnWithTag(lOld[i], vOld[i]));
409 				}
410 
411 				lOldS.Sort(AceColumnWithTag.CompareByTags);
412 			}
413 			catch(Exception) { Debug.Assert(false); return string.Empty; }
414 
415 			List<AceColumnWithTag> l = new List<AceColumnWithTag>();
416 			foreach(AceColumn c in lNew)
417 			{
418 				if(c != null) l.Add(new AceColumnWithTag(c, 0));
419 				else { Debug.Assert(false); return string.Empty; }
420 			}
421 
422 			long m = Math.Max(lNew.Count, lOld.Count);
423 
424 			// Preserve order of previous columns
425 			for(int i = 0; i < lOldS.Count; ++i)
426 			{
427 				string strOldName = lOldS[i].TypeNameEx;
428 
429 				foreach(AceColumnWithTag ct in l)
430 				{
431 					if(ct.TypeNameEx == strOldName)
432 					{
433 						ct.Tag = m * i;
434 						break;
435 					}
436 				}
437 			}
438 
439 			// Insert new columns based on their default position
440 			for(int i = 1; i < l.Count; ++i)
441 			{
442 				if(l[i].Tag == 0) l[i].Tag = l[i - 1].Tag + 1;
443 			}
444 
445 			l.Sort(AceColumnWithTag.CompareByTags);
446 
447 			int[] v = new int[lNew.Count];
448 			for(int i = 0; i < v.Length; ++i)
449 			{
450 				for(int j = 0; j < l.Count; ++j)
451 				{
452 					if(object.ReferenceEquals(l[j].Column, lNew[i]))
453 					{
454 						v[i] = j;
455 						break;
456 					}
457 				}
458 			}
459 			Debug.Assert(Array.IndexOf(v, 0) == Array.LastIndexOf(v, 0));
460 
461 			return StrUtil.SerializeIntArray(v);
462 		}
463 	}
464 }
465