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 
24 #if !KeePassUAP
25 using System.Drawing;
26 #endif
27 
28 using KeePassLib.Collections;
29 using KeePassLib.Interfaces;
30 using KeePassLib.Security;
31 using KeePassLib.Utility;
32 
33 namespace KeePassLib
34 {
35 	/// <summary>
36 	/// A class representing a password entry. A password entry consists of several
37 	/// fields like title, user name, password, etc. Each password entry has a
38 	/// unique ID (UUID).
39 	/// </summary>
40 	public sealed class PwEntry : ITimeLogger, IStructureItem, IDeepCloneable<PwEntry>
41 	{
42 		private PwUuid m_uuid = PwUuid.Zero;
43 		private PwGroup m_pParentGroup = null;
44 		private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow;
45 		private PwUuid m_puPrevParentGroup = PwUuid.Zero;
46 
47 		private ProtectedStringDictionary m_dStrings = new ProtectedStringDictionary();
48 		private ProtectedBinaryDictionary m_dBinaries = new ProtectedBinaryDictionary();
49 		private AutoTypeConfig m_cfgAutoType = new AutoTypeConfig();
50 		private PwObjectList<PwEntry> m_lHistory = new PwObjectList<PwEntry>();
51 
52 		private PwIcon m_pwIcon = PwIcon.Key;
53 		private PwUuid m_puCustomIcon = PwUuid.Zero;
54 
55 		private Color m_clrForeground = Color.Empty;
56 		private Color m_clrBackground = Color.Empty;
57 
58 		private DateTime m_tCreation = PwDefs.DtDefaultNow;
59 		private DateTime m_tLastMod = PwDefs.DtDefaultNow;
60 		private DateTime m_tLastAccess = PwDefs.DtDefaultNow;
61 		private DateTime m_tExpire = PwDefs.DtDefaultNow;
62 		private bool m_bExpires = false;
63 		private ulong m_uUsageCount = 0;
64 
65 		private string m_strOverrideUrl = string.Empty;
66 		private bool m_bQualityCheck = true;
67 
68 		private List<string> m_lTags = new List<string>();
69 
70 		private StringDictionaryEx m_dCustomData = new StringDictionaryEx();
71 
72 		/// <summary>
73 		/// UUID of this entry.
74 		/// </summary>
75 		public PwUuid Uuid
76 		{
77 			get { return m_uuid; }
78 			set
79 			{
80 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
81 				m_uuid = value;
82 			}
83 		}
84 
85 		/// <summary>
86 		/// Reference to a group which contains the current entry.
87 		/// </summary>
88 		public PwGroup ParentGroup
89 		{
90 			get { return m_pParentGroup; }
91 
92 			// Plugins: use <c>PwGroup.AddEntry</c> instead.
93 			internal set { m_pParentGroup = value; }
94 		}
95 
96 		/// <summary>
97 		/// The date/time when the location of the object was last changed.
98 		/// </summary>
99 		public DateTime LocationChanged
100 		{
101 			get { return m_tParentGroupLastMod; }
102 			set { m_tParentGroupLastMod = value; }
103 		}
104 
105 		public PwUuid PreviousParentGroup
106 		{
107 			get { return m_puPrevParentGroup; }
108 			set
109 			{
110 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
111 				m_puPrevParentGroup = value;
112 			}
113 		}
114 
115 		/// <summary>
116 		/// Get or set all entry strings.
117 		/// </summary>
118 		public ProtectedStringDictionary Strings
119 		{
120 			get { return m_dStrings; }
121 			set
122 			{
123 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
124 				m_dStrings = value;
125 			}
126 		}
127 
128 		/// <summary>
129 		/// Get or set all entry binaries.
130 		/// </summary>
131 		public ProtectedBinaryDictionary Binaries
132 		{
133 			get { return m_dBinaries; }
134 			set
135 			{
136 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
137 				m_dBinaries = value;
138 			}
139 		}
140 
141 		/// <summary>
142 		/// Get or set all auto-type window/keystroke sequence associations.
143 		/// </summary>
144 		public AutoTypeConfig AutoType
145 		{
146 			get { return m_cfgAutoType; }
147 			set
148 			{
149 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
150 				m_cfgAutoType = value;
151 			}
152 		}
153 
154 		/// <summary>
155 		/// Get all previous versions of this entry (backups).
156 		/// </summary>
157 		public PwObjectList<PwEntry> History
158 		{
159 			get { return m_lHistory; }
160 			set
161 			{
162 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
163 				m_lHistory = value;
164 			}
165 		}
166 
167 		/// <summary>
168 		/// Image ID specifying the icon that will be used for this entry.
169 		/// </summary>
170 		public PwIcon IconId
171 		{
172 			get { return m_pwIcon; }
173 			set { m_pwIcon = value; }
174 		}
175 
176 		/// <summary>
177 		/// Get the custom icon ID. This value is 0, if no custom icon is
178 		/// being used (i.e. the icon specified by the <c>IconID</c> property
179 		/// should be displayed).
180 		/// </summary>
181 		public PwUuid CustomIconUuid
182 		{
183 			get { return m_puCustomIcon; }
184 			set
185 			{
186 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
187 				m_puCustomIcon = value;
188 			}
189 		}
190 
191 		/// <summary>
192 		/// Get or set the foreground color of this entry.
193 		/// </summary>
194 		public Color ForegroundColor
195 		{
196 			get { return m_clrForeground; }
197 			set { m_clrForeground = value; }
198 		}
199 
200 		/// <summary>
201 		/// Get or set the background color of this entry.
202 		/// </summary>
203 		public Color BackgroundColor
204 		{
205 			get { return m_clrBackground; }
206 			set { m_clrBackground = value; }
207 		}
208 
209 		/// <summary>
210 		/// The date/time when this entry was created.
211 		/// </summary>
212 		public DateTime CreationTime
213 		{
214 			get { return m_tCreation; }
215 			set { m_tCreation = value; }
216 		}
217 
218 		/// <summary>
219 		/// The date/time when this entry was last modified.
220 		/// </summary>
221 		public DateTime LastModificationTime
222 		{
223 			get { return m_tLastMod; }
224 			set { m_tLastMod = value; }
225 		}
226 
227 		/// <summary>
228 		/// The date/time when this entry was last accessed (read).
229 		/// </summary>
230 		public DateTime LastAccessTime
231 		{
232 			get { return m_tLastAccess; }
233 			set { m_tLastAccess = value; }
234 		}
235 
236 		/// <summary>
237 		/// The date/time when this entry expires. Use the <c>Expires</c> property
238 		/// to specify if the entry does actually expire or not.
239 		/// </summary>
240 		public DateTime ExpiryTime
241 		{
242 			get { return m_tExpire; }
243 			set { m_tExpire = value; }
244 		}
245 
246 		/// <summary>
247 		/// Specifies whether the entry expires or not.
248 		/// </summary>
249 		public bool Expires
250 		{
251 			get { return m_bExpires; }
252 			set { m_bExpires = value; }
253 		}
254 
255 		/// <summary>
256 		/// Get or set the usage count of the entry. To increase the usage
257 		/// count by one, use the <c>Touch</c> function.
258 		/// </summary>
259 		public ulong UsageCount
260 		{
261 			get { return m_uUsageCount; }
262 			set { m_uUsageCount = value; }
263 		}
264 
265 		/// <summary>
266 		/// Entry-specific override URL.
267 		/// </summary>
268 		public string OverrideUrl
269 		{
270 			get { return m_strOverrideUrl; }
271 			set
272 			{
273 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
274 				m_strOverrideUrl = value;
275 			}
276 		}
277 
278 		public bool QualityCheck
279 		{
280 			get { return m_bQualityCheck; }
281 			set { m_bQualityCheck = value; }
282 		}
283 
284 		/// <summary>
285 		/// List of tags associated with this entry.
286 		/// </summary>
287 		public List<string> Tags
288 		{
289 			get { StrUtil.NormalizeTags(m_lTags); return m_lTags; }
290 			set
291 			{
292 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
293 				m_lTags = value;
294 			}
295 		}
296 
297 		/// <summary>
298 		/// Custom data container that can be used by plugins to store
299 		/// own data in KeePass entries.
300 		/// The data is stored in the encrypted part of encrypted
301 		/// database files.
302 		/// Use unique names for your items, e.g. "PluginName_ItemName".
303 		/// </summary>
304 		public StringDictionaryEx CustomData
305 		{
306 			get { return m_dCustomData; }
307 			internal set
308 			{
309 				if(value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
310 				m_dCustomData = value;
311 			}
312 		}
313 
314 		public static EventHandler<ObjectTouchedEventArgs> EntryTouched;
315 		public EventHandler<ObjectTouchedEventArgs> Touched;
316 
317 		/// <summary>
318 		/// Construct a new, empty password entry. Member variables will be initialized
319 		/// to their default values.
320 		/// </summary>
321 		/// <param name="bCreateNewUuid">If <c>true</c>, a new UUID will be created
322 		/// for this entry. If <c>false</c>, the UUID is zero and you must set it
323 		/// manually later.</param>
324 		/// <param name="bSetTimes">If <c>true</c>, the creation, last modification
325 		/// and last access times will be set to the current system time.</param>
PwEntry(bool bCreateNewUuid, bool bSetTimes)326 		public PwEntry(bool bCreateNewUuid, bool bSetTimes)
327 		{
328 			if(bCreateNewUuid) m_uuid = new PwUuid(true);
329 
330 			if(bSetTimes)
331 			{
332 				DateTime dtNow = DateTime.UtcNow;
333 				m_tCreation = dtNow;
334 				m_tLastMod = dtNow;
335 				m_tLastAccess = dtNow;
336 				m_tParentGroupLastMod = dtNow;
337 			}
338 		}
339 
340 		/// <summary>
341 		/// Construct a new, empty password entry. Member variables will be initialized
342 		/// to their default values.
343 		/// </summary>
344 		/// <param name="pwParentGroup">Reference to the containing group, this
345 		/// parameter may be <c>null</c> and set later manually.</param>
346 		/// <param name="bCreateNewUuid">If <c>true</c>, a new UUID will be created
347 		/// for this entry. If <c>false</c>, the UUID is zero and you must set it
348 		/// manually later.</param>
349 		/// <param name="bSetTimes">If <c>true</c>, the creation, last modification
350 		/// and last access times will be set to the current system time.</param>
351 		[Obsolete("Use a different constructor. To add an entry to a group, use AddEntry of PwGroup.")]
PwEntry(PwGroup pwParentGroup, bool bCreateNewUuid, bool bSetTimes)352 		public PwEntry(PwGroup pwParentGroup, bool bCreateNewUuid, bool bSetTimes)
353 		{
354 			m_pParentGroup = pwParentGroup;
355 
356 			if(bCreateNewUuid) m_uuid = new PwUuid(true);
357 
358 			if(bSetTimes)
359 			{
360 				DateTime dtNow = DateTime.UtcNow;
361 				m_tCreation = dtNow;
362 				m_tLastMod = dtNow;
363 				m_tLastAccess = dtNow;
364 				m_tParentGroupLastMod = dtNow;
365 			}
366 		}
367 
368 #if DEBUG
369 		// For display in debugger
ToString()370 		public override string ToString()
371 		{
372 			return ("PwEntry '" + m_dStrings.ReadSafe(PwDefs.TitleField) + "'");
373 		}
374 #endif
375 
376 		/// <summary>
377 		/// Clone the current entry. The returned entry is an exact value copy
378 		/// of the current entry (including UUID and parent group reference).
379 		/// All mutable members are cloned.
380 		/// </summary>
381 		/// <returns>Exact value clone. All references to mutable values changed.</returns>
CloneDeep()382 		public PwEntry CloneDeep()
383 		{
384 			PwEntry peNew = new PwEntry(false, false);
385 
386 			peNew.m_uuid = m_uuid; // PwUuid is immutable
387 			peNew.m_pParentGroup = m_pParentGroup;
388 			peNew.m_tParentGroupLastMod = m_tParentGroupLastMod;
389 			peNew.m_puPrevParentGroup = m_puPrevParentGroup;
390 
391 			peNew.m_dStrings = m_dStrings.CloneDeep();
392 			peNew.m_dBinaries = m_dBinaries.CloneDeep();
393 			peNew.m_cfgAutoType = m_cfgAutoType.CloneDeep();
394 			peNew.m_lHistory = m_lHistory.CloneDeep();
395 
396 			peNew.m_pwIcon = m_pwIcon;
397 			peNew.m_puCustomIcon = m_puCustomIcon;
398 
399 			peNew.m_clrForeground = m_clrForeground;
400 			peNew.m_clrBackground = m_clrBackground;
401 
402 			peNew.m_tCreation = m_tCreation;
403 			peNew.m_tLastMod = m_tLastMod;
404 			peNew.m_tLastAccess = m_tLastAccess;
405 			peNew.m_tExpire = m_tExpire;
406 			peNew.m_bExpires = m_bExpires;
407 			peNew.m_uUsageCount = m_uUsageCount;
408 
409 			peNew.m_strOverrideUrl = m_strOverrideUrl;
410 			peNew.m_bQualityCheck = m_bQualityCheck;
411 
412 			peNew.m_lTags.AddRange(m_lTags);
413 
414 			peNew.m_dCustomData = m_dCustomData.CloneDeep();
415 
416 			return peNew;
417 		}
418 
CloneStructure()419 		public PwEntry CloneStructure()
420 		{
421 			PwEntry peNew = new PwEntry(false, false);
422 
423 			peNew.m_uuid = m_uuid; // PwUuid is immutable
424 			peNew.m_tParentGroupLastMod = m_tParentGroupLastMod;
425 			// Do not assign m_pParentGroup
426 
427 			return peNew;
428 		}
429 
BuildCmpOpt(bool bIgnoreParentGroup, bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup)430 		private static PwCompareOptions BuildCmpOpt(bool bIgnoreParentGroup,
431 			bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory,
432 			bool bIgnoreThisLastBackup)
433 		{
434 			PwCompareOptions pwOpt = PwCompareOptions.None;
435 			if(bIgnoreParentGroup) pwOpt |= PwCompareOptions.IgnoreParentGroup;
436 			if(bIgnoreLastMod) pwOpt |= PwCompareOptions.IgnoreLastMod;
437 			if(bIgnoreLastAccess) pwOpt |= PwCompareOptions.IgnoreLastAccess;
438 			if(bIgnoreHistory) pwOpt |= PwCompareOptions.IgnoreHistory;
439 			if(bIgnoreThisLastBackup) pwOpt |= PwCompareOptions.IgnoreLastBackup;
440 			return pwOpt;
441 		}
442 
443 		[Obsolete]
EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup)444 		public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod,
445 			bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup)
446 		{
447 			return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod,
448 				bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup),
449 				MemProtCmpMode.None);
450 		}
451 
452 		[Obsolete]
EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod, bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup, MemProtCmpMode mpCmpStr)453 		public bool EqualsEntry(PwEntry pe, bool bIgnoreParentGroup, bool bIgnoreLastMod,
454 			bool bIgnoreLastAccess, bool bIgnoreHistory, bool bIgnoreThisLastBackup,
455 			MemProtCmpMode mpCmpStr)
456 		{
457 			return EqualsEntry(pe, BuildCmpOpt(bIgnoreParentGroup, bIgnoreLastMod,
458 				bIgnoreLastAccess, bIgnoreHistory, bIgnoreThisLastBackup), mpCmpStr);
459 		}
460 
EqualsEntry(PwEntry pe, PwCompareOptions pwOpt, MemProtCmpMode mpCmpStr)461 		public bool EqualsEntry(PwEntry pe, PwCompareOptions pwOpt,
462 			MemProtCmpMode mpCmpStr)
463 		{
464 			if(pe == null) { Debug.Assert(false); return false; }
465 
466 			bool bNeEqStd = ((pwOpt & PwCompareOptions.NullEmptyEquivStd) !=
467 				PwCompareOptions.None);
468 			bool bIgnoreLastAccess = ((pwOpt & PwCompareOptions.IgnoreLastAccess) !=
469 				PwCompareOptions.None);
470 			bool bIgnoreLastMod = ((pwOpt & PwCompareOptions.IgnoreLastMod) !=
471 				PwCompareOptions.None);
472 
473 			if(!m_uuid.Equals(pe.m_uuid)) return false;
474 			if((pwOpt & PwCompareOptions.IgnoreParentGroup) == PwCompareOptions.None)
475 			{
476 				if(m_pParentGroup != pe.m_pParentGroup) return false;
477 				if(!bIgnoreLastMod && (m_tParentGroupLastMod != pe.m_tParentGroupLastMod))
478 					return false;
479 				if(!m_puPrevParentGroup.Equals(pe.m_puPrevParentGroup))
480 					return false;
481 			}
482 
483 			if(!m_dStrings.EqualsDictionary(pe.m_dStrings, pwOpt, mpCmpStr))
484 				return false;
485 			if(!m_dBinaries.EqualsDictionary(pe.m_dBinaries)) return false;
486 
487 			if(!m_cfgAutoType.Equals(pe.m_cfgAutoType)) return false;
488 
489 			if((pwOpt & PwCompareOptions.IgnoreHistory) == PwCompareOptions.None)
490 			{
491 				bool bIgnoreLastBackup = ((pwOpt & PwCompareOptions.IgnoreLastBackup) !=
492 					PwCompareOptions.None);
493 
494 				if(!bIgnoreLastBackup && (m_lHistory.UCount != pe.m_lHistory.UCount))
495 					return false;
496 				if(bIgnoreLastBackup && (m_lHistory.UCount == 0))
497 				{
498 					Debug.Assert(false);
499 					return false;
500 				}
501 				if(bIgnoreLastBackup && ((m_lHistory.UCount - 1) != pe.m_lHistory.UCount))
502 					return false;
503 
504 				PwCompareOptions cmpSub = PwCompareOptions.IgnoreParentGroup;
505 				if(bNeEqStd) cmpSub |= PwCompareOptions.NullEmptyEquivStd;
506 				if(bIgnoreLastMod) cmpSub |= PwCompareOptions.IgnoreLastMod;
507 				if(bIgnoreLastAccess) cmpSub |= PwCompareOptions.IgnoreLastAccess;
508 
509 				for(uint uHist = 0; uHist < pe.m_lHistory.UCount; ++uHist)
510 				{
511 					if(!m_lHistory.GetAt(uHist).EqualsEntry(pe.m_lHistory.GetAt(
512 						uHist), cmpSub, MemProtCmpMode.None))
513 						return false;
514 				}
515 			}
516 
517 			if(m_pwIcon != pe.m_pwIcon) return false;
518 			if(!m_puCustomIcon.Equals(pe.m_puCustomIcon)) return false;
519 
520 			if(m_clrForeground != pe.m_clrForeground) return false;
521 			if(m_clrBackground != pe.m_clrBackground) return false;
522 
523 			if(m_tCreation != pe.m_tCreation) return false;
524 			if(!bIgnoreLastMod && (m_tLastMod != pe.m_tLastMod)) return false;
525 			if(!bIgnoreLastAccess && (m_tLastAccess != pe.m_tLastAccess)) return false;
526 			if(m_tExpire != pe.m_tExpire) return false;
527 			if(m_bExpires != pe.m_bExpires) return false;
528 			if(!bIgnoreLastAccess && (m_uUsageCount != pe.m_uUsageCount)) return false;
529 
530 			if(m_strOverrideUrl != pe.m_strOverrideUrl) return false;
531 			if(m_bQualityCheck != pe.m_bQualityCheck) return false;
532 
533 			// The Tags property normalizes
534 			if(!MemUtil.ListsEqual<string>(this.Tags, pe.Tags)) return false;
535 
536 			if(!m_dCustomData.Equals(pe.m_dCustomData)) return false;
537 
538 			return true;
539 		}
540 
541 		/// <summary>
542 		/// Assign properties to the current entry based on a template entry.
543 		/// </summary>
544 		/// <param name="peTemplate">Template entry. Must not be <c>null</c>.</param>
545 		/// <param name="bOnlyIfNewer">Only set the properties of the template entry
546 		/// if it is newer than the current one.</param>
547 		/// <param name="bIncludeHistory">If <c>true</c>, the history will be
548 		/// copied, too.</param>
549 		/// <param name="bAssignLocationChanged">If <c>true</c>, the
550 		/// <c>LocationChanged</c> property is copied, otherwise not.</param>
AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer, bool bIncludeHistory, bool bAssignLocationChanged)551 		public void AssignProperties(PwEntry peTemplate, bool bOnlyIfNewer,
552 			bool bIncludeHistory, bool bAssignLocationChanged)
553 		{
554 			if(peTemplate == null) { Debug.Assert(false); throw new ArgumentNullException("peTemplate"); }
555 
556 			if(bOnlyIfNewer && (TimeUtil.Compare(peTemplate.m_tLastMod,
557 				m_tLastMod, true) < 0))
558 				return;
559 
560 			// Template UUID should be the same as the current one
561 			Debug.Assert(m_uuid.Equals(peTemplate.m_uuid));
562 			m_uuid = peTemplate.m_uuid;
563 
564 			if(bAssignLocationChanged)
565 			{
566 				m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod;
567 				m_puPrevParentGroup = peTemplate.m_puPrevParentGroup;
568 			}
569 
570 			m_dStrings = peTemplate.m_dStrings.CloneDeep();
571 			m_dBinaries = peTemplate.m_dBinaries.CloneDeep();
572 			m_cfgAutoType = peTemplate.m_cfgAutoType.CloneDeep();
573 			if(bIncludeHistory)
574 				m_lHistory = peTemplate.m_lHistory.CloneDeep();
575 
576 			m_pwIcon = peTemplate.m_pwIcon;
577 			m_puCustomIcon = peTemplate.m_puCustomIcon; // Immutable
578 
579 			m_clrForeground = peTemplate.m_clrForeground;
580 			m_clrBackground = peTemplate.m_clrBackground;
581 
582 			m_tCreation = peTemplate.m_tCreation;
583 			m_tLastMod = peTemplate.m_tLastMod;
584 			m_tLastAccess = peTemplate.m_tLastAccess;
585 			m_tExpire = peTemplate.m_tExpire;
586 			m_bExpires = peTemplate.m_bExpires;
587 			m_uUsageCount = peTemplate.m_uUsageCount;
588 
589 			m_strOverrideUrl = peTemplate.m_strOverrideUrl;
590 			m_bQualityCheck = peTemplate.m_bQualityCheck;
591 
592 			m_lTags = new List<string>(peTemplate.m_lTags);
593 
594 			m_dCustomData = peTemplate.m_dCustomData.CloneDeep();
595 		}
596 
597 		/// <summary>
598 		/// Touch the entry. This function updates the internal last access
599 		/// time. If the <paramref name="bModified" /> parameter is <c>true</c>,
600 		/// the last modification time gets updated, too.
601 		/// </summary>
602 		/// <param name="bModified">Modify last modification time.</param>
Touch(bool bModified)603 		public void Touch(bool bModified)
604 		{
605 			Touch(bModified, true);
606 		}
607 
608 		/// <summary>
609 		/// Touch the entry. This function updates the internal last access
610 		/// time. If the <paramref name="bModified" /> parameter is <c>true</c>,
611 		/// the last modification time gets updated, too.
612 		/// </summary>
613 		/// <param name="bModified">Modify last modification time.</param>
614 		/// <param name="bTouchParents">If <c>true</c>, all parent objects
615 		/// get touched, too.</param>
Touch(bool bModified, bool bTouchParents)616 		public void Touch(bool bModified, bool bTouchParents)
617 		{
618 			m_tLastAccess = DateTime.UtcNow;
619 			++m_uUsageCount;
620 
621 			if(bModified) m_tLastMod = m_tLastAccess;
622 
623 			if(this.Touched != null)
624 				this.Touched(this, new ObjectTouchedEventArgs(this,
625 					bModified, bTouchParents));
626 			if(PwEntry.EntryTouched != null)
627 				PwEntry.EntryTouched(this, new ObjectTouchedEventArgs(this,
628 					bModified, bTouchParents));
629 
630 			if(bTouchParents && (m_pParentGroup != null))
631 				m_pParentGroup.Touch(bModified, true);
632 		}
633 
634 		/// <summary>
635 		/// Create a backup of this entry. The backup item doesn't contain any
636 		/// history items.
637 		/// </summary>
638 		[Obsolete]
CreateBackup()639 		public void CreateBackup()
640 		{
641 			CreateBackup(null);
642 		}
643 
644 		/// <summary>
645 		/// Create a backup of this entry. The backup item doesn't contain any
646 		/// history items.
647 		/// <param name="pwHistMntcSettings">If this parameter isn't <c>null</c>,
648 		/// the history list is maintained automatically (i.e. old backups are
649 		/// deleted if there are too many or the history size is too large).
650 		/// This parameter may be <c>null</c> (no maintenance then).</param>
651 		/// </summary>
CreateBackup(PwDatabase pwHistMntcSettings)652 		public void CreateBackup(PwDatabase pwHistMntcSettings)
653 		{
654 			PwEntry peCopy = CloneDeep();
655 			peCopy.m_lHistory.Clear();
656 
657 			m_lHistory.Add(peCopy); // Must be added at end, see EqualsEntry
658 
659 			if(pwHistMntcSettings != null) MaintainBackups(pwHistMntcSettings);
660 		}
661 
662 		/// <summary>
663 		/// Restore an entry snapshot from backups.
664 		/// </summary>
665 		/// <param name="uBackupIndex">Index of the backup item, to which
666 		/// should be reverted.</param>
667 		[Obsolete]
RestoreFromBackup(uint uBackupIndex)668 		public void RestoreFromBackup(uint uBackupIndex)
669 		{
670 			RestoreFromBackup(uBackupIndex, null);
671 		}
672 
673 		/// <summary>
674 		/// Restore an entry snapshot from backups.
675 		/// </summary>
676 		/// <param name="uBackupIndex">Index of the backup item, to which
677 		/// should be reverted.</param>
678 		/// <param name="pwHistMntcSettings">If this parameter isn't <c>null</c>,
679 		/// the history list is maintained automatically (i.e. old backups are
680 		/// deleted if there are too many or the history size is too large).
681 		/// This parameter may be <c>null</c> (no maintenance then).</param>
RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings)682 		public void RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings)
683 		{
684 			if(uBackupIndex >= m_lHistory.UCount)
685 			{
686 				Debug.Assert(false);
687 				throw new ArgumentOutOfRangeException("uBackupIndex");
688 			}
689 
690 			PwEntry pe = m_lHistory.GetAt(uBackupIndex);
691 			if(pe == null) { Debug.Assert(false); throw new InvalidOperationException(); }
692 
693 			CreateBackup(pwHistMntcSettings); // Backup current data before restoring
694 			AssignProperties(pe, false, false, false);
695 		}
696 
HasBackupOfData(PwEntry peData, bool bIgnoreLastMod, bool bIgnoreLastAccess)697 		public bool HasBackupOfData(PwEntry peData, bool bIgnoreLastMod,
698 			bool bIgnoreLastAccess)
699 		{
700 			if(peData == null) { Debug.Assert(false); return false; }
701 
702 			PwCompareOptions cmpOpt = (PwCompareOptions.IgnoreParentGroup |
703 				PwCompareOptions.IgnoreHistory | PwCompareOptions.NullEmptyEquivStd);
704 			if(bIgnoreLastMod) cmpOpt |= PwCompareOptions.IgnoreLastMod;
705 			if(bIgnoreLastAccess) cmpOpt |= PwCompareOptions.IgnoreLastAccess;
706 
707 			foreach(PwEntry pe in m_lHistory)
708 			{
709 				if(pe.EqualsEntry(peData, cmpOpt, MemProtCmpMode.None)) return true;
710 			}
711 
712 			return false;
713 		}
714 
715 		/// <summary>
716 		/// Delete old history entries if there are too many or the
717 		/// history size is too large.
718 		/// <returns>If one or more history entries have been deleted,
719 		/// <c>true</c> is returned. Otherwise <c>false</c>.</returns>
720 		/// </summary>
MaintainBackups(PwDatabase pwSettings)721 		public bool MaintainBackups(PwDatabase pwSettings)
722 		{
723 			if(pwSettings == null) { Debug.Assert(false); return false; }
724 
725 			// Fix UUIDs of history entries; should not be necessary
726 			PwUuid pu = m_uuid;
727 			foreach(PwEntry pe in m_lHistory)
728 			{
729 				if(!pe.Uuid.Equals(pu)) { Debug.Assert(false); pe.Uuid = pu; }
730 			}
731 
732 			bool bDeleted = false;
733 
734 			int nMaxItems = pwSettings.HistoryMaxItems;
735 			if(nMaxItems >= 0)
736 			{
737 				while(m_lHistory.UCount > (uint)nMaxItems)
738 				{
739 					RemoveOldestBackup();
740 					bDeleted = true;
741 				}
742 			}
743 
744 			long lMaxSize = pwSettings.HistoryMaxSize;
745 			if(lMaxSize >= 0)
746 			{
747 				while(true)
748 				{
749 					ulong uHistSize = 0;
750 					foreach(PwEntry pe in m_lHistory) { uHistSize += pe.GetSize(); }
751 
752 					if(uHistSize > (ulong)lMaxSize)
753 					{
754 						RemoveOldestBackup();
755 						bDeleted = true;
756 					}
757 					else break;
758 				}
759 			}
760 
761 			return bDeleted;
762 		}
763 
RemoveOldestBackup()764 		private void RemoveOldestBackup()
765 		{
766 			DateTime dtMin = TimeUtil.SafeMaxValueUtc;
767 			uint idxRemove = uint.MaxValue;
768 
769 			for(uint u = 0; u < m_lHistory.UCount; ++u)
770 			{
771 				PwEntry pe = m_lHistory.GetAt(u);
772 				if(TimeUtil.Compare(pe.LastModificationTime, dtMin, true) < 0)
773 				{
774 					idxRemove = u;
775 					dtMin = pe.LastModificationTime;
776 				}
777 			}
778 
779 			if(idxRemove != uint.MaxValue) m_lHistory.RemoveAt(idxRemove);
780 		}
781 
782 		// Cf. AutoType.GetEnabledText
GetAutoTypeEnabled()783 		public bool GetAutoTypeEnabled()
784 		{
785 			if(!m_cfgAutoType.Enabled) return false;
786 
787 			if(m_pParentGroup != null)
788 				return m_pParentGroup.GetAutoTypeEnabledInherited();
789 
790 			return PwGroup.DefaultAutoTypeEnabled;
791 		}
792 
GetAutoTypeSequence()793 		public string GetAutoTypeSequence()
794 		{
795 			string strSeq = m_cfgAutoType.DefaultSequence;
796 
797 			PwGroup pg = m_pParentGroup;
798 			while(pg != null)
799 			{
800 				if(strSeq.Length != 0) break;
801 
802 				strSeq = pg.DefaultAutoTypeSequence;
803 				pg = pg.ParentGroup;
804 			}
805 
806 			if(strSeq.Length != 0) return strSeq;
807 
808 			if(PwDefs.IsTanEntry(this)) return PwDefs.DefaultAutoTypeSequenceTan;
809 			return PwDefs.DefaultAutoTypeSequence;
810 		}
811 
GetSearchingEnabled()812 		public bool GetSearchingEnabled()
813 		{
814 			if(m_pParentGroup != null)
815 				return m_pParentGroup.GetSearchingEnabledInherited();
816 
817 			return PwGroup.DefaultSearchingEnabled;
818 		}
819 
820 		/// <summary>
821 		/// Approximate the total size (in process memory) of this entry
822 		/// in bytes (including strings, binaries and history entries).
823 		/// </summary>
824 		/// <returns>Size in bytes.</returns>
GetSize()825 		public ulong GetSize()
826 		{
827 			// This method assumes 64-bit pointers/references and Unicode
828 			// strings (i.e. 2 bytes per character)
829 
830 			ulong cb = 276; // Number of bytes; approx. fixed length data
831 			ulong cc = 0; // Number of characters
832 
833 			cb += (ulong)m_dStrings.UCount * 40;
834 			foreach(KeyValuePair<string, ProtectedString> kvpStr in m_dStrings)
835 				cc += (ulong)kvpStr.Key.Length + (ulong)kvpStr.Value.Length;
836 
837 			cb += (ulong)m_dBinaries.UCount * 65;
838 			foreach(KeyValuePair<string, ProtectedBinary> kvpBin in m_dBinaries)
839 			{
840 				cc += (ulong)kvpBin.Key.Length;
841 				cb += (ulong)kvpBin.Value.Length;
842 			}
843 
844 			cc += (ulong)m_cfgAutoType.DefaultSequence.Length;
845 			cb += (ulong)m_cfgAutoType.AssociationsCount * 24;
846 			foreach(AutoTypeAssociation a in m_cfgAutoType.Associations)
847 				cc += (ulong)a.WindowName.Length + (ulong)a.Sequence.Length;
848 
849 			cb += (ulong)m_lHistory.UCount * 8;
850 			foreach(PwEntry peHistory in m_lHistory)
851 				cb += peHistory.GetSize();
852 
853 			cc += (ulong)m_strOverrideUrl.Length;
854 
855 			cb += (ulong)m_lTags.Count * 8;
856 			foreach(string strTag in m_lTags)
857 				cc += (ulong)strTag.Length;
858 
859 			cb += (ulong)m_dCustomData.Count * 16;
860 			foreach(KeyValuePair<string, string> kvp in m_dCustomData)
861 				cc += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length;
862 
863 			return (cb + (cc << 1));
864 		}
865 
HasTag(string strTag)866 		public bool HasTag(string strTag)
867 		{
868 			if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
869 
870 			// this.Tags normalizes
871 			return this.Tags.Contains(StrUtil.NormalizeTag(strTag));
872 		}
873 
AddTag(string strTag)874 		public bool AddTag(string strTag)
875 		{
876 			if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
877 
878 			strTag = StrUtil.NormalizeTag(strTag);
879 			if(this.Tags.Contains(strTag)) return false; // this.Tags normalizes
880 
881 			m_lTags.Add(strTag);
882 			return true;
883 		}
884 
RemoveTag(string strTag)885 		public bool RemoveTag(string strTag)
886 		{
887 			if(string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
888 
889 			// this.Tags normalizes
890 			return this.Tags.Remove(StrUtil.NormalizeTag(strTag));
891 		}
892 
GetTagsInherited()893 		internal List<string> GetTagsInherited()
894 		{
895 			List<string> l = ((m_pParentGroup != null) ?
896 				m_pParentGroup.GetTagsInherited(false) : new List<string>());
897 			l.AddRange(this.Tags);
898 			StrUtil.NormalizeTags(l);
899 			return l;
900 		}
901 
IsContainedIn(PwGroup pgContainer)902 		public bool IsContainedIn(PwGroup pgContainer)
903 		{
904 			PwGroup pgCur = m_pParentGroup;
905 			while(pgCur != null)
906 			{
907 				if(pgCur == pgContainer) return true;
908 
909 				pgCur = pgCur.ParentGroup;
910 			}
911 
912 			return false;
913 		}
914 
SetUuid(PwUuid pwNewUuid, bool bAlsoChangeHistoryUuids)915 		public void SetUuid(PwUuid pwNewUuid, bool bAlsoChangeHistoryUuids)
916 		{
917 			this.Uuid = pwNewUuid;
918 
919 			if(bAlsoChangeHistoryUuids)
920 			{
921 				foreach(PwEntry peHist in m_lHistory)
922 					peHist.Uuid = pwNewUuid;
923 			}
924 		}
925 
SetCreatedNow()926 		public void SetCreatedNow()
927 		{
928 			DateTime dt = DateTime.UtcNow;
929 
930 			m_tCreation = dt;
931 			m_tLastAccess = dt;
932 		}
933 
Duplicate()934 		public PwEntry Duplicate()
935 		{
936 			PwEntry pe = CloneDeep();
937 
938 			pe.SetUuid(new PwUuid(true), true);
939 			pe.SetCreatedNow();
940 
941 			return pe;
942 		}
943 	}
944 
945 	public sealed class PwEntryComparer : IComparer<PwEntry>
946 	{
947 		private string m_strFieldName;
948 		private bool m_bCaseInsensitive;
949 		private bool m_bCompareNaturally;
950 
PwEntryComparer(string strFieldName, bool bCaseInsensitive, bool bCompareNaturally)951 		public PwEntryComparer(string strFieldName, bool bCaseInsensitive,
952 			bool bCompareNaturally)
953 		{
954 			if(strFieldName == null) throw new ArgumentNullException("strFieldName");
955 
956 			m_strFieldName = strFieldName;
957 			m_bCaseInsensitive = bCaseInsensitive;
958 			m_bCompareNaturally = bCompareNaturally;
959 		}
960 
Compare(PwEntry a, PwEntry b)961 		public int Compare(PwEntry a, PwEntry b)
962 		{
963 			string strA = a.Strings.ReadSafe(m_strFieldName);
964 			string strB = b.Strings.ReadSafe(m_strFieldName);
965 
966 			if(m_bCompareNaturally) return StrUtil.CompareNaturally(strA, strB);
967 
968 			return string.Compare(strA, strB, m_bCaseInsensitive);
969 		}
970 	}
971 }
972