merge changes from KeePass 2.48 with support for KDBX 4.1 and slightly improved merging behavior with respect to custom icons, closes https://github.com/PhilippC/keepass2android/issues/1670

This commit is contained in:
Philipp Crocoll
2021-05-14 08:01:34 +02:00
parent f5ee9e3955
commit 562a80132a
24 changed files with 3581 additions and 2414 deletions

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -33,8 +33,11 @@ namespace KeePassLib.Collections
private Dictionary<int, ProtectedBinary> m_d = private Dictionary<int, ProtectedBinary> m_d =
new Dictionary<int, ProtectedBinary>(); new Dictionary<int, ProtectedBinary>();
public ProtectedBinarySet() private readonly bool m_bDedupAdd;
public ProtectedBinarySet(bool bDedupAdd)
{ {
m_bDedupAdd = bDedupAdd;
} }
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
@@ -47,15 +50,10 @@ namespace KeePassLib.Collections
return m_d.GetEnumerator(); return m_d.GetEnumerator();
} }
public void Clear()
{
m_d.Clear();
}
private int GetFreeID() private int GetFreeID()
{ {
int i = m_d.Count; int i = m_d.Count;
while(m_d.ContainsKey(i)) { ++i; } while (m_d.ContainsKey(i)) { ++i; }
Debug.Assert(i == m_d.Count); // m_d.Count should be free Debug.Assert(i == m_d.Count); // m_d.Count should be free
return i; return i;
} }
@@ -63,7 +61,7 @@ namespace KeePassLib.Collections
public ProtectedBinary Get(int iID) public ProtectedBinary Get(int iID)
{ {
ProtectedBinary pb; ProtectedBinary pb;
if(m_d.TryGetValue(iID, out pb)) return pb; if (m_d.TryGetValue(iID, out pb)) return pb;
// Debug.Assert(false); // No assert // Debug.Assert(false); // No assert
return null; return null;
@@ -71,12 +69,12 @@ namespace KeePassLib.Collections
public int Find(ProtectedBinary pb) public int Find(ProtectedBinary pb)
{ {
if(pb == null) { Debug.Assert(false); return -1; } if (pb == null) { Debug.Assert(false); return -1; }
// Fast search by reference // Fast search by reference
foreach(KeyValuePair<int, ProtectedBinary> kvp in m_d) foreach (KeyValuePair<int, ProtectedBinary> kvp in m_d)
{ {
if(object.ReferenceEquals(pb, kvp.Value)) if (object.ReferenceEquals(pb, kvp.Value))
{ {
Debug.Assert(pb.Equals(kvp.Value)); Debug.Assert(pb.Equals(kvp.Value));
return kvp.Key; return kvp.Key;
@@ -84,9 +82,9 @@ namespace KeePassLib.Collections
} }
// Slow search by content // Slow search by content
foreach(KeyValuePair<int, ProtectedBinary> kvp in m_d) foreach (KeyValuePair<int, ProtectedBinary> kvp in m_d)
{ {
if(pb.Equals(kvp.Value)) return kvp.Key; if (pb.Equals(kvp.Value)) return kvp.Key;
} }
// Debug.Assert(false); // No assert // Debug.Assert(false); // No assert
@@ -95,28 +93,26 @@ namespace KeePassLib.Collections
public void Set(int iID, ProtectedBinary pb) public void Set(int iID, ProtectedBinary pb)
{ {
if(iID < 0) { Debug.Assert(false); return; } if (iID < 0) { Debug.Assert(false); return; }
if(pb == null) { Debug.Assert(false); return; } if (pb == null) { Debug.Assert(false); return; }
m_d[iID] = pb; m_d[iID] = pb;
} }
public void Add(ProtectedBinary pb) public void Add(ProtectedBinary pb)
{ {
if(pb == null) { Debug.Assert(false); return; } if (pb == null) { Debug.Assert(false); return; }
int i = Find(pb); if (m_bDedupAdd && (Find(pb) >= 0)) return; // Exists already
if(i >= 0) return; // Exists already
i = GetFreeID(); m_d[GetFreeID()] = pb;
m_d[i] = pb;
} }
public void AddFrom(ProtectedBinaryDictionary d) public void AddFrom(ProtectedBinaryDictionary d)
{ {
if(d == null) { Debug.Assert(false); return; } if (d == null) { Debug.Assert(false); return; }
foreach(KeyValuePair<string, ProtectedBinary> kvp in d) foreach (KeyValuePair<string, ProtectedBinary> kvp in d)
{ {
Add(kvp.Value); Add(kvp.Value);
} }
@@ -124,16 +120,16 @@ namespace KeePassLib.Collections
public void AddFrom(PwGroup pg) public void AddFrom(PwGroup pg)
{ {
if(pg == null) { Debug.Assert(false); return; } if (pg == null) { Debug.Assert(false); return; }
EntryHandler eh = delegate(PwEntry pe) EntryHandler eh = delegate (PwEntry pe)
{ {
if(pe == null) { Debug.Assert(false); return true; } if (pe == null) { Debug.Assert(false); return true; }
AddFrom(pe.Binaries); AddFrom(pe.Binaries);
foreach(PwEntry peHistory in pe.History) foreach (PwEntry peHistory in pe.History)
{ {
if(peHistory == null) { Debug.Assert(false); continue; } if (peHistory == null) { Debug.Assert(false); continue; }
AddFrom(peHistory.Binaries); AddFrom(peHistory.Binaries);
} }
@@ -148,9 +144,9 @@ namespace KeePassLib.Collections
int n = m_d.Count; int n = m_d.Count;
ProtectedBinary[] v = new ProtectedBinary[n]; ProtectedBinary[] v = new ProtectedBinary[n];
foreach(KeyValuePair<int, ProtectedBinary> kvp in m_d) foreach (KeyValuePair<int, ProtectedBinary> kvp in m_d)
{ {
if((kvp.Key < 0) || (kvp.Key >= n)) if ((kvp.Key < 0) || (kvp.Key >= n))
{ {
Debug.Assert(false); Debug.Assert(false);
throw new InvalidOperationException(); throw new InvalidOperationException();
@@ -159,9 +155,9 @@ namespace KeePassLib.Collections
v[kvp.Key] = kvp.Value; v[kvp.Key] = kvp.Value;
} }
for(int i = 0; i < n; ++i) for (int i = 0; i < n; ++i)
{ {
if(v[i] == null) if (v[i] == null)
{ {
Debug.Assert(false); Debug.Assert(false);
throw new InvalidOperationException(); throw new InvalidOperationException();

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -20,8 +20,8 @@
using System; using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Diagnostics; using System.Diagnostics;
using System.Text;
using KeePassLib.Interfaces; using KeePassLib.Interfaces;
@@ -34,48 +34,74 @@ namespace KeePassLib.Collections
public sealed class StringDictionaryEx : IDeepCloneable<StringDictionaryEx>, public sealed class StringDictionaryEx : IDeepCloneable<StringDictionaryEx>,
IEnumerable<KeyValuePair<string, string>>, IEquatable<StringDictionaryEx> IEnumerable<KeyValuePair<string, string>>, IEquatable<StringDictionaryEx>
{ {
private SortedDictionary<string, string> m_dict = private SortedDictionary<string, string> m_d =
new SortedDictionary<string, string>(); new SortedDictionary<string, string>();
// Non-null if and only if last mod. times should be remembered
private Dictionary<string, DateTime> m_dLastMod = null;
public int Count public int Count
{ {
get { return m_dict.Count; } get { return m_d.Count; }
} }
public StringDictionaryEx() public StringDictionaryEx()
{ {
} }
internal StringDictionaryEx(bool bRememberLastMod)
{
if (bRememberLastMod) m_dLastMod = new Dictionary<string, DateTime>();
}
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
{ {
return m_dict.GetEnumerator(); return m_d.GetEnumerator();
} }
public IEnumerator<KeyValuePair<string, string>> GetEnumerator() public IEnumerator<KeyValuePair<string, string>> GetEnumerator()
{ {
return m_dict.GetEnumerator(); return m_d.GetEnumerator();
} }
public StringDictionaryEx CloneDeep() public StringDictionaryEx CloneDeep()
{ {
StringDictionaryEx sdNew = new StringDictionaryEx(); StringDictionaryEx sdNew = new StringDictionaryEx();
foreach(KeyValuePair<string, string> kvp in m_dict) foreach (KeyValuePair<string, string> kvp in m_d)
sdNew.m_dict[kvp.Key] = kvp.Value; // Strings are immutable sdNew.m_d[kvp.Key] = kvp.Value;
if (m_dLastMod != null)
sdNew.m_dLastMod = new Dictionary<string, DateTime>(m_dLastMod);
Debug.Assert(Equals(sdNew));
return sdNew; return sdNew;
} }
public bool Equals(StringDictionaryEx sdOther) public bool Equals(StringDictionaryEx sdOther)
{ {
if(sdOther == null) { Debug.Assert(false); return false; } if (sdOther == null) { Debug.Assert(false); return false; }
if(m_dict.Count != sdOther.m_dict.Count) return false; if (m_d.Count != sdOther.m_d.Count) return false;
foreach(KeyValuePair<string, string> kvp in sdOther.m_dict) foreach (KeyValuePair<string, string> kvp in sdOther.m_d)
{ {
string str = Get(kvp.Key); string str = Get(kvp.Key);
if((str == null) || (str != kvp.Value)) return false; if ((str == null) || (str != kvp.Value)) return false;
}
int cLastModT = ((m_dLastMod != null) ? m_dLastMod.Count : -1);
int cLastModO = ((sdOther.m_dLastMod != null) ? sdOther.m_dLastMod.Count : -1);
if (cLastModT != cLastModO) return false;
if (m_dLastMod != null)
{
foreach (KeyValuePair<string, DateTime> kvp in sdOther.m_dLastMod)
{
DateTime? odt = GetLastModificationTime(kvp.Key);
if (!odt.HasValue) return false;
if (odt.Value != kvp.Value) return false;
}
} }
return true; return true;
@@ -83,48 +109,62 @@ namespace KeePassLib.Collections
public string Get(string strName) public string Get(string strName)
{ {
if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
string s; string str;
if(m_dict.TryGetValue(strName, out s)) return s; m_d.TryGetValue(strName, out str);
return str;
}
internal DateTime? GetLastModificationTime(string strName)
{
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
if (m_dLastMod == null) return null;
DateTime dt;
if (m_dLastMod.TryGetValue(strName, out dt)) return dt;
return null; return null;
} }
public bool Exists(string strName) public bool Exists(string strName)
{ {
if(strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); } if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
return m_dict.ContainsKey(strName); return m_d.ContainsKey(strName);
} }
/// <summary> public void Set(string strName, string strValue)
/// Set a string.
/// </summary>
/// <param name="strField">Identifier of the string field to modify.</param>
/// <param name="strNewValue">New value. This parameter must not be <c>null</c>.</param>
/// <exception cref="System.ArgumentNullException">Thrown if one of the input
/// parameters is <c>null</c>.</exception>
public void Set(string strField, string strNewValue)
{ {
if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); } if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
if(strNewValue == null) { Debug.Assert(false); throw new ArgumentNullException("strNewValue"); } if (strValue == null) { Debug.Assert(false); throw new ArgumentNullException("strValue"); }
m_dict[strField] = strNewValue; m_d[strName] = strValue;
if (m_dLastMod != null) m_dLastMod[strName] = DateTime.UtcNow;
} }
/// <summary> internal void Set(string strName, string strValue, DateTime? odtLastMod)
/// Delete a string.
/// </summary>
/// <param name="strField">Name of the string field to delete.</param>
/// <returns>Returns <c>true</c> if the field has been successfully
/// removed, otherwise the return value is <c>false</c>.</returns>
/// <exception cref="System.ArgumentNullException">Thrown if the input
/// parameter is <c>null</c>.</exception>
public bool Remove(string strField)
{ {
if(strField == null) { Debug.Assert(false); throw new ArgumentNullException("strField"); } if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
if (strValue == null) { Debug.Assert(false); throw new ArgumentNullException("strValue"); }
return m_dict.Remove(strField); m_d[strName] = strValue;
if (m_dLastMod != null)
{
if (odtLastMod.HasValue) m_dLastMod[strName] = odtLastMod.Value;
else m_dLastMod.Remove(strName);
}
}
public bool Remove(string strName)
{
if (strName == null) { Debug.Assert(false); throw new ArgumentNullException("strName"); }
if (m_dLastMod != null) m_dLastMod.Remove(strName);
return m_d.Remove(strName);
} }
} }
} }

View File

@@ -28,15 +28,29 @@ using KeePassLib.Resources;
namespace KeePassLib.Cryptography.Cipher namespace KeePassLib.Cryptography.Cipher
{ {
public sealed class ChaCha20Engine : ICipherEngine2 public sealed class ChaCha20Engine : ICipherEngine2
{ {
private PwUuid m_uuid = new PwUuid(new byte[] { private static PwUuid m_uuid = null;
0xD6, 0x03, 0x8A, 0x2B, 0x8B, 0x6F, 0x4C, 0xB5,
0xA5, 0x24, 0x33, 0x9A, 0x31, 0xDB, 0xB5, 0x9A internal static PwUuid ChaCha20Uuid
}); {
get
{
PwUuid pu = m_uuid;
if (pu == null)
{
pu = new PwUuid(new byte[] {
0xD6, 0x03, 0x8A, 0x2B, 0x8B, 0x6F, 0x4C, 0xB5,
0xA5, 0x24, 0x33, 0x9A, 0x31, 0xDB, 0xB5, 0x9A });
m_uuid = pu;
}
return pu;
}
}
public PwUuid CipherUuid public PwUuid CipherUuid
{ {
get { return m_uuid; } get { return ChaCha20Uuid; }
} }
public string DisplayName public string DisplayName

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -21,17 +21,22 @@ using System;
namespace KeePassLib.Interfaces namespace KeePassLib.Interfaces
{ {
public interface IStructureItem : ITimeLogger // Provides LocationChanged public interface IStructureItem : ITimeLogger // Provides LocationChanged
{ {
PwUuid Uuid PwUuid Uuid
{ {
get; get;
set; set;
} }
PwGroup ParentGroup PwGroup ParentGroup
{ {
get; get;
} }
}
} PwUuid PreviousParentGroup
{
get;
}
}
}

View File

@@ -74,6 +74,7 @@
<Compile Include="IDatabaseFormat.cs" /> <Compile Include="IDatabaseFormat.cs" />
<Compile Include="Keys\KcpKeyFile.Xml.cs" /> <Compile Include="Keys\KcpKeyFile.Xml.cs" />
<Compile Include="Kp2aLog.cs" /> <Compile Include="Kp2aLog.cs" />
<Compile Include="PwGroup.Search.cs" />
<Compile Include="Resources\Resource.designer.cs" /> <Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Resources\KLRes.Generated.cs" /> <Compile Include="Resources\KLRes.Generated.cs" />
<Compile Include="Resources\KSRes.Generated.cs" /> <Compile Include="Resources\KSRes.Generated.cs" />

View File

@@ -1,8 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2013 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
Modified to be used with Mono for Android. Changes Copyright (C) 2013 Philipp Crocoll
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -20,61 +18,106 @@
*/ */
using System; using System;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
#if !KeePassUAP
using System.Drawing; using System.Drawing;
using System.IO; #endif
using KeePassLib.Utility; using KeePassLib.Utility;
namespace KeePassLib namespace KeePassLib
{ {
/// <summary>
/// Custom icon. <c>PwCustomIcon</c> objects are immutable.
/// </summary>
public sealed class PwCustomIcon public sealed class PwCustomIcon
{ {
private PwUuid m_pwUuid; // Recommended maximum sizes, not obligatory
private byte[] m_pbImageDataPng; internal const int MaxWidth = 128;
private Android.Graphics.Bitmap m_pCachedImage; internal const int MaxHeight = 128;
private readonly PwUuid m_uuid;
private readonly byte[] m_pbImageDataPng;
private string m_strName = string.Empty;
private DateTime? m_odtLastMod = null;
private Dictionary<long, Android.Graphics.Bitmap> m_dImageCache = new Dictionary<long, Android.Graphics.Bitmap>();
public PwUuid Uuid public PwUuid Uuid
{ {
get { return m_pwUuid; } get { return m_uuid; }
} }
public byte[] ImageDataPng public byte[] ImageDataPng
{ {
get { return m_pbImageDataPng; } get { return m_pbImageDataPng; }
// When allowing 'set', do not copy the cache in 'Clone'
} }
public string Name
{
get { return m_strName; }
set
{
if (value == null) throw new ArgumentNullException("value");
m_strName = value;
}
}
public DateTime? LastModificationTime
{
get { return m_odtLastMod; }
set { m_odtLastMod = value; }
}
[Obsolete("Use GetImage instead.")]
public Android.Graphics.Bitmap Image public Android.Graphics.Bitmap Image
{ {
get { return m_pCachedImage; } get { return GetImage(); } // Backward compatibility
}
public PwCustomIcon(PwUuid pu, byte[] pbImageDataPng)
{
if (pu == null) { Debug.Assert(false); throw new ArgumentNullException("pu"); }
if (pu.Equals(PwUuid.Zero)) { Debug.Assert(false); throw new ArgumentOutOfRangeException("pu"); }
if (pbImageDataPng == null) { Debug.Assert(false); throw new ArgumentNullException("pbImageDataPng"); }
m_uuid = pu;
m_pbImageDataPng = pbImageDataPng;
} }
public PwCustomIcon(PwUuid pwUuid, byte[] pbImageDataPng) private static long GetKey(int w, int h)
{ {
Debug.Assert(pwUuid != null); return (((long)w << 32) ^ (long)h);
if(pwUuid == null) throw new ArgumentNullException("pwUuid"); }
Debug.Assert(!pwUuid.Equals(PwUuid.Zero));
if(pwUuid.Equals(PwUuid.Zero)) throw new ArgumentException("pwUuid == 0");
Debug.Assert(pbImageDataPng != null); /// <summary>
if(pbImageDataPng == null) throw new ArgumentNullException("pbImageDataPng"); /// Get the icon as an <c>Image</c> (original size).
/// </summary>
public Android.Graphics.Bitmap GetImage()
{
const long lKey = -1;
m_pwUuid = pwUuid; Android.Graphics.Bitmap img;
m_pbImageDataPng = pbImageDataPng; if (m_dImageCache.TryGetValue(lKey, out img)) return img;
#if !KeePassLibSD try { img = GfxUtil.LoadImage(m_pbImageDataPng); }
// MemoryStream ms = new MemoryStream(m_pbImageDataPng, false); catch (Exception) { Debug.Assert(false); }
// m_pCachedImage = Image.FromStream(ms);
// ms.Close(); m_dImageCache[lKey] = img;
m_pCachedImage = GfxUtil.LoadImage(m_pbImageDataPng); return img;
#else }
m_pCachedImage = null;
#endif internal PwCustomIcon Clone()
{
PwCustomIcon ico = new PwCustomIcon(m_uuid, m_pbImageDataPng);
ico.m_strName = m_strName;
ico.m_odtLastMod = m_odtLastMod;
ico.m_dImageCache = m_dImageCache; // Same image data
return ico;
} }
} }
} }

View File

@@ -97,7 +97,7 @@ namespace KeePassLib
private int m_nHistoryMaxItems = DefaultHistoryMaxItems; private int m_nHistoryMaxItems = DefaultHistoryMaxItems;
private long m_lHistoryMaxSize = DefaultHistoryMaxSize; // In bytes private long m_lHistoryMaxSize = DefaultHistoryMaxSize; // In bytes
private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); private StringDictionaryEx m_dCustomData = new StringDictionaryEx(true);
private VariantDictionary m_dPublicCustomData = new VariantDictionary(); private VariantDictionary m_dPublicCustomData = new VariantDictionary();
private byte[] m_pbHashOfFileOnDisk = null; private byte[] m_pbHashOfFileOnDisk = null;
@@ -690,9 +690,9 @@ namespace KeePassLib
public void MergeIn(PwDatabase pdSource, PwMergeMethod mm, public void MergeIn(PwDatabase pdSource, PwMergeMethod mm,
IStatusLogger slStatus) IStatusLogger slStatus)
{ {
if(pdSource == null) throw new ArgumentNullException("pdSource"); if (pdSource == null) throw new ArgumentNullException("pdSource");
if(mm == PwMergeMethod.CreateNewUuids) if (mm == PwMergeMethod.CreateNewUuids)
{ {
pdSource.RootGroup.Uuid = new PwUuid(true); pdSource.RootGroup.Uuid = new PwUuid(true);
pdSource.RootGroup.CreateNewItemUuids(true, true, true); pdSource.RootGroup.CreateNewItemUuids(true, true, true);
@@ -707,7 +707,7 @@ namespace KeePassLib
PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(m_pgRootGroup); PwObjectPoolEx ppOrg = PwObjectPoolEx.FromGroup(m_pgRootGroup);
PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pdSource.RootGroup); PwObjectPoolEx ppSrc = PwObjectPoolEx.FromGroup(pdSource.RootGroup);
GroupHandler ghSrc = delegate(PwGroup pg) GroupHandler ghSrc = delegate (PwGroup pg)
{ {
// if(pg == pdSource.m_pgRootGroup) return true; // if(pg == pdSource.m_pgRootGroup) return true;
@@ -716,11 +716,11 @@ namespace KeePassLib
// pool should not be modified) // pool should not be modified)
PwGroup pgLocal = m_pgRootGroup.FindGroup(pg.Uuid, true); PwGroup pgLocal = m_pgRootGroup.FindGroup(pg.Uuid, true);
if(pgLocal == null) if (pgLocal == null)
{ {
PwGroup pgSourceParent = pg.ParentGroup; PwGroup pgSourceParent = pg.ParentGroup;
PwGroup pgLocalContainer; PwGroup pgLocalContainer;
if(pgSourceParent == null) if (pgSourceParent == null)
{ {
// pg is the root group of pdSource, and no corresponding // pg is the root group of pdSource, and no corresponding
// local group was found; create the group within the // local group was found; create the group within the
@@ -728,17 +728,23 @@ namespace KeePassLib
Debug.Assert(pg == pdSource.m_pgRootGroup); Debug.Assert(pg == pdSource.m_pgRootGroup);
pgLocalContainer = m_pgRootGroup; pgLocalContainer = m_pgRootGroup;
} }
else if(pgSourceParent == pdSource.m_pgRootGroup) else if (pgSourceParent == pdSource.m_pgRootGroup)
pgLocalContainer = m_pgRootGroup; pgLocalContainer = m_pgRootGroup;
else else
pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true); pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true);
Debug.Assert(pgLocalContainer != null); Debug.Assert(pgLocalContainer != null);
if(pgLocalContainer == null) pgLocalContainer = m_pgRootGroup; if (pgLocalContainer == null) pgLocalContainer = m_pgRootGroup;
PwGroup pgNew = new PwGroup(false, false); PwGroup pgNew = new PwGroup(false, false);
pgNew.Uuid = pg.Uuid; pgNew.Uuid = pg.Uuid;
pgNew.AssignProperties(pg, false, true); pgNew.AssignProperties(pg, false, true);
if (!pgLocalContainer.CanAddGroup(pgNew))
{
Debug.Assert(false);
pgLocalContainer = m_pgRootGroup;
pgLocalContainer.CheckCanAddGroup(pgNew);
}
// pgLocalContainer.AddGroup(pgNew, true); // pgLocalContainer.AddGroup(pgNew, true);
InsertObjectAtBestPos<PwGroup>(pgLocalContainer.Groups, pgNew, ppSrc); InsertObjectAtBestPos<PwGroup>(pgLocalContainer.Groups, pgNew, ppSrc);
pgNew.ParentGroup = pgLocalContainer; pgNew.ParentGroup = pgLocalContainer;
@@ -747,9 +753,9 @@ namespace KeePassLib
{ {
Debug.Assert(mm != PwMergeMethod.CreateNewUuids); Debug.Assert(mm != PwMergeMethod.CreateNewUuids);
if(mm == PwMergeMethod.OverwriteExisting) if (mm == PwMergeMethod.OverwriteExisting)
pgLocal.AssignProperties(pg, false, false); pgLocal.AssignProperties(pg, false, false);
else if((mm == PwMergeMethod.OverwriteIfNewer) || else if ((mm == PwMergeMethod.OverwriteIfNewer) ||
(mm == PwMergeMethod.Synchronize)) (mm == PwMergeMethod.Synchronize))
{ {
pgLocal.AssignProperties(pg, true, false); pgLocal.AssignProperties(pg, true, false);
@@ -760,23 +766,23 @@ namespace KeePassLib
return ((slStatus != null) ? slStatus.ContinueWork() : true); return ((slStatus != null) ? slStatus.ContinueWork() : true);
}; };
EntryHandler ehSrc = delegate(PwEntry pe) EntryHandler ehSrc = delegate (PwEntry pe)
{ {
// PwEntry peLocal = m_pgRootGroup.FindEntry(pe.Uuid, true); // PwEntry peLocal = m_pgRootGroup.FindEntry(pe.Uuid, true);
PwEntry peLocal = (ppOrg.GetItemByUuid(pe.Uuid) as PwEntry); PwEntry peLocal = (ppOrg.GetItemByUuid(pe.Uuid) as PwEntry);
Debug.Assert(object.ReferenceEquals(peLocal, Debug.Assert(object.ReferenceEquals(peLocal,
m_pgRootGroup.FindEntry(pe.Uuid, true))); m_pgRootGroup.FindEntry(pe.Uuid, true)));
if(peLocal == null) if (peLocal == null)
{ {
PwGroup pgSourceParent = pe.ParentGroup; PwGroup pgSourceParent = pe.ParentGroup;
PwGroup pgLocalContainer; PwGroup pgLocalContainer;
if(pgSourceParent == pdSource.m_pgRootGroup) if (pgSourceParent == pdSource.m_pgRootGroup)
pgLocalContainer = m_pgRootGroup; pgLocalContainer = m_pgRootGroup;
else else
pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true); pgLocalContainer = m_pgRootGroup.FindGroup(pgSourceParent.Uuid, true);
Debug.Assert(pgLocalContainer != null); Debug.Assert(pgLocalContainer != null);
if(pgLocalContainer == null) pgLocalContainer = m_pgRootGroup; if (pgLocalContainer == null) pgLocalContainer = m_pgRootGroup;
PwEntry peNew = new PwEntry(false, false); PwEntry peNew = new PwEntry(false, false);
peNew.Uuid = pe.Uuid; peNew.Uuid = pe.Uuid;
@@ -796,19 +802,19 @@ namespace KeePassLib
bool bEquals = peLocal.EqualsEntry(pe, cmpOpt, MemProtCmpMode.None); bool bEquals = peLocal.EqualsEntry(pe, cmpOpt, MemProtCmpMode.None);
bool bOrgBackup = !bEquals; bool bOrgBackup = !bEquals;
if(mm != PwMergeMethod.OverwriteExisting) if (mm != PwMergeMethod.OverwriteExisting)
bOrgBackup &= (TimeUtil.CompareLastMod(pe, peLocal, true) > 0); bOrgBackup &= (TimeUtil.CompareLastMod(pe, peLocal, true) > 0);
bOrgBackup &= !pe.HasBackupOfData(peLocal, false, true); bOrgBackup &= !pe.HasBackupOfData(peLocal, false, true);
if(bOrgBackup) peLocal.CreateBackup(null); // Maintain at end if (bOrgBackup) peLocal.CreateBackup(null); // Maintain at end
bool bSrcBackup = !bEquals && (mm != PwMergeMethod.OverwriteExisting); bool bSrcBackup = !bEquals && (mm != PwMergeMethod.OverwriteExisting);
bSrcBackup &= (TimeUtil.CompareLastMod(peLocal, pe, true) > 0); bSrcBackup &= (TimeUtil.CompareLastMod(peLocal, pe, true) > 0);
bSrcBackup &= !peLocal.HasBackupOfData(pe, false, true); bSrcBackup &= !peLocal.HasBackupOfData(pe, false, true);
if(bSrcBackup) pe.CreateBackup(null); // Maintain at end if (bSrcBackup) pe.CreateBackup(null); // Maintain at end
if(mm == PwMergeMethod.OverwriteExisting) if (mm == PwMergeMethod.OverwriteExisting)
peLocal.AssignProperties(pe, false, false, false); peLocal.AssignProperties(pe, false, false, false);
else if((mm == PwMergeMethod.OverwriteIfNewer) || else if ((mm == PwMergeMethod.OverwriteIfNewer) ||
(mm == PwMergeMethod.Synchronize)) (mm == PwMergeMethod.Synchronize))
{ {
peLocal.AssignProperties(pe, true, false, false); peLocal.AssignProperties(pe, true, false, false);
@@ -822,13 +828,13 @@ namespace KeePassLib
}; };
ghSrc(pdSource.RootGroup); ghSrc(pdSource.RootGroup);
if(!pdSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, ghSrc, ehSrc)) if (!pdSource.RootGroup.TraverseTree(TraversalMethod.PreOrder, ghSrc, ehSrc))
throw new InvalidOperationException(); throw new InvalidOperationException();
IStatusLogger slPrevStatus = m_slStatus; IStatusLogger slPrevStatus = m_slStatus;
m_slStatus = slStatus; m_slStatus = slStatus;
if(mm == PwMergeMethod.Synchronize) if (mm == PwMergeMethod.Synchronize)
{ {
RelocateGroups(ppOrg, ppSrc); RelocateGroups(ppOrg, ppSrc);
RelocateEntries(ppOrg, ppSrc); RelocateEntries(ppOrg, ppSrc);
@@ -838,24 +844,24 @@ namespace KeePassLib
MergeInLocationChanged(m_pgRootGroup, ppOrg, ppSrc); MergeInLocationChanged(m_pgRootGroup, ppOrg, ppSrc);
ppOrg = null; // Pools are now invalid, because the location ppOrg = null; // Pools are now invalid, because the location
ppSrc = null; // changed times have been merged in ppSrc = null; // changed times have been merged in
// Delete *after* relocating, because relocating might
// empty some groups that are marked for deletion (and
// objects that weren't relocated yet might prevent the
// deletion)
Dictionary<PwUuid, PwDeletedObject> dOrgDel = CreateDeletedObjectsPool();
MergeInDeletionInfo(pdSource.m_vDeletedObjects, dOrgDel);
ApplyDeletions(m_pgRootGroup, dOrgDel);
// The list and the dictionary should be kept in sync
Debug.Assert(m_vDeletedObjects.UCount == (uint)dOrgDel.Count);
} }
// Delete *after* relocating, because relocating might empty
// some groups that are marked for deletion (and objects
// that weren't relocated yet might prevent the deletion)
Dictionary<PwUuid, PwDeletedObject> dDel = CreateDeletedObjectsPool();
if (mm == PwMergeMethod.Synchronize)
MergeInDeletionInfo(pdSource.m_vDeletedObjects, dDel);
ApplyDeletions(m_pgRootGroup, dDel);
// The list and the dictionary should be kept in sync
Debug.Assert(m_vDeletedObjects.UCount == (uint)dDel.Count);
// Must be called *after* merging groups, because group UUIDs // Must be called *after* merging groups, because group UUIDs
// are required for recycle bin and entry template UUIDs // are required for recycle bin and entry template UUIDs
MergeInDbProperties(pdSource, mm); MergeInDbProperties(pdSource, mm);
MergeInCustomIcons(pdSource); MergeInCustomIcons(pdSource, dDel);
Debug.Assert(m_vDeletedObjects.UCount == (uint)dDel.Count);
MaintainBackups(); MaintainBackups();
@@ -863,15 +869,79 @@ namespace KeePassLib
m_slStatus = slPrevStatus; m_slStatus = slPrevStatus;
} }
private void MergeInCustomIcons(PwDatabase pdSource)
{
foreach(PwCustomIcon pwci in pdSource.CustomIcons)
{
if(GetCustomIconIndex(pwci.Uuid) >= 0) continue;
m_vCustomIcons.Add(pwci); // PwCustomIcon is immutable private void MergeInCustomIcons(PwDatabase pdSource,
m_bUINeedsIconUpdate = true; Dictionary<PwUuid, PwDeletedObject> dDel)
{
bool bIconsMod = false;
Dictionary<PwUuid, int> d = new Dictionary<PwUuid, int>();
for (int i = m_vCustomIcons.Count - 1; i >= 0; --i)
d[m_vCustomIcons[i].Uuid] = i;
Debug.Assert(d.Count == m_vCustomIcons.Count); // UUIDs unique
foreach (PwCustomIcon ciS in pdSource.m_vCustomIcons)
{
int iT;
if (d.TryGetValue(ciS.Uuid, out iT))
{
PwCustomIcon ciT = m_vCustomIcons[iT];
DateTime? odtT = ciT.LastModificationTime;
DateTime? odtS = ciS.LastModificationTime;
if (odtT.HasValue && odtS.HasValue)
{
if (odtT.Value >= odtS.Value) continue;
}
else if (odtT.HasValue) continue;
else if (!odtS.HasValue) continue; // Both no time
m_vCustomIcons[iT] = ciS.Clone();
}
else
{
d[ciS.Uuid] = m_vCustomIcons.Count;
m_vCustomIcons.Add(ciS.Clone());
}
bIconsMod = true;
} }
List<PwDeletedObject> lObsoleteDel = new List<PwDeletedObject>();
foreach (KeyValuePair<PwUuid, PwDeletedObject> kvpDel in dDel)
{
int iT;
if (d.TryGetValue(kvpDel.Key, out iT))
{
PwCustomIcon ci = m_vCustomIcons[iT];
if (ci == null) { Debug.Assert(false); continue; } // Dup. del. obj.?
DateTime? odt = ci.LastModificationTime;
if (odt.HasValue && (odt.Value > kvpDel.Value.DeletionTime))
lObsoleteDel.Add(kvpDel.Value);
else
{
m_vCustomIcons[iT] = null; // Preserve indices, removed below
bIconsMod = true;
}
}
}
Predicate<PwCustomIcon> f = delegate (PwCustomIcon ci) { return (ci == null); };
m_vCustomIcons.RemoveAll(f);
foreach (PwDeletedObject pdo in lObsoleteDel)
{
// Prevent future deletion attempts
if (!m_vDeletedObjects.Remove(pdo)) { Debug.Assert(false); }
if (!dDel.Remove(pdo.Uuid)) { Debug.Assert(false); }
}
if (bIconsMod) m_bUINeedsIconUpdate = true;
FixCustomIconRefs();
} }
private Dictionary<PwUuid, PwDeletedObject> CreateDeletedObjectsPool() private Dictionary<PwUuid, PwDeletedObject> CreateDeletedObjectsPool()
@@ -1212,7 +1282,9 @@ namespace KeePassLib
PwObjectBlock<T> b = new PwObjectBlock<T>(); PwObjectBlock<T> b = new PwObjectBlock<T>();
DateTime dtLoc; DateTime dtLoc;
PwObjectPoolEx pPool = GetBestPool(t, ppOrg, ppSrc, out dtLoc); PwUuid puPrevParent;
PwObjectPoolEx pPool = GetBestPool(t, ppOrg, ppSrc, out dtLoc,
out puPrevParent);
b.Add(t, dtLoc, pPool); b.Add(t, dtLoc, pPool);
lBlocks.Add(b); lBlocks.Add(b);
@@ -1247,7 +1319,7 @@ namespace KeePassLib
} }
if(idSrcNext == 0) break; if(idSrcNext == 0) break;
pPool = GetBestPool(tNext, ppOrg, ppSrc, out dtLoc); pPool = GetBestPool(tNext, ppOrg, ppSrc, out dtLoc, out puPrevParent);
b.Add(tNext, dtLoc, pPool); b.Add(tNext, dtLoc, pPool);
++u; ++u;
@@ -1260,28 +1332,31 @@ namespace KeePassLib
} }
private static PwObjectPoolEx GetBestPool<T>(T t, PwObjectPoolEx ppOrg, private static PwObjectPoolEx GetBestPool<T>(T t, PwObjectPoolEx ppOrg,
PwObjectPoolEx ppSrc, out DateTime dtLoc) PwObjectPoolEx ppSrc, out DateTime dtLoc, out PwUuid puPrevParent)
where T : class, ITimeLogger, IStructureItem, IDeepCloneable<T> where T : class, ITimeLogger, IStructureItem, IDeepCloneable<T>
{ {
PwObjectPoolEx p = null; PwObjectPoolEx p = null;
dtLoc = TimeUtil.SafeMinValueUtc; dtLoc = TimeUtil.SafeMinValueUtc;
puPrevParent = PwUuid.Zero;
IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid); IStructureItem ptOrg = ppOrg.GetItemByUuid(t.Uuid);
if(ptOrg != null) if (ptOrg != null)
{ {
dtLoc = ptOrg.LocationChanged; dtLoc = ptOrg.LocationChanged;
p = ppOrg; puPrevParent = ptOrg.PreviousParentGroup;
} p = ppOrg;
}
IStructureItem ptSrc = ppSrc.GetItemByUuid(t.Uuid); IStructureItem ptSrc = ppSrc.GetItemByUuid(t.Uuid);
if((ptSrc != null) && (ptSrc.LocationChanged > dtLoc)) if ((ptSrc != null) && (ptSrc.LocationChanged > dtLoc))
{ {
dtLoc = ptSrc.LocationChanged; dtLoc = ptSrc.LocationChanged;
p = ppSrc; puPrevParent = ptSrc.PreviousParentGroup;
} p = ppSrc;
}
Debug.Assert(p != null); Debug.Assert(p != null);
return p; return p;
} }
private static int FindLocationChangedPivot<T>(List<PwObjectBlock<T>> lBlocks, private static int FindLocationChangedPivot<T>(List<PwObjectBlock<T>> lBlocks,
@@ -1307,30 +1382,40 @@ namespace KeePassLib
return iPosMax; return iPosMax;
} }
private static void MergeInLocationChanged(PwGroup pg, private static void MergeInLocationChanged(PwGroup pg,
PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc) PwObjectPoolEx ppOrg, PwObjectPoolEx ppSrc)
{ {
GroupHandler gh = delegate(PwGroup pgSub) GroupHandler gh = delegate (PwGroup pgSub)
{ {
DateTime dt; DateTime dt;
if(GetBestPool<PwGroup>(pgSub, ppOrg, ppSrc, out dt) != null) PwUuid puPrevParent;
pgSub.LocationChanged = dt; if (GetBestPool<PwGroup>(pgSub, ppOrg, ppSrc, out dt,
else { Debug.Assert(false); } out puPrevParent) != null)
return true; {
}; pgSub.LocationChanged = dt;
pgSub.PreviousParentGroup = puPrevParent;
}
else { Debug.Assert(false); }
return true;
};
EntryHandler eh = delegate(PwEntry pe) EntryHandler eh = delegate (PwEntry pe)
{ {
DateTime dt; DateTime dt;
if(GetBestPool<PwEntry>(pe, ppOrg, ppSrc, out dt) != null) PwUuid puPrevParent;
pe.LocationChanged = dt; if (GetBestPool<PwEntry>(pe, ppOrg, ppSrc, out dt,
else { Debug.Assert(false); } out puPrevParent) != null)
return true; {
}; pe.LocationChanged = dt;
pe.PreviousParentGroup = puPrevParent;
}
else { Debug.Assert(false); }
return true;
};
gh(pg); gh(pg);
pg.TraverseTree(TraversalMethod.PreOrder, gh, eh); pg.TraverseTree(TraversalMethod.PreOrder, gh, eh);
} }
private static void InsertObjectAtBestPos<T>(PwObjectList<T> lItems, private static void InsertObjectAtBestPos<T>(PwObjectList<T> lItems,
T tNew, PwObjectPoolEx ppSrc) T tNew, PwObjectPoolEx ppSrc)
@@ -1445,12 +1530,18 @@ namespace KeePassLib
foreach(KeyValuePair<string, string> kvp in pdSource.m_dCustomData) foreach(KeyValuePair<string, string> kvp in pdSource.m_dCustomData)
{ {
if(bSourceNewer || !m_dCustomData.Exists(kvp.Key)) if(bSourceNewer || !m_dCustomData.Exists(kvp.Key))
m_dCustomData.Set(kvp.Key, kvp.Value); m_dCustomData.Set(kvp.Key, kvp.Value, null);
} }
VariantDictionary vdLocal = m_dPublicCustomData; // Backup // 'Clone' duplicates deep values (e.g. byte arrays)
m_dPublicCustomData = (VariantDictionary)pdSource.m_dPublicCustomData.Clone(); VariantDictionary vdS = (VariantDictionary)pdSource.m_dPublicCustomData.Clone();
if(!bSourceNewer) vdLocal.CopyTo(m_dPublicCustomData); // Merge if (bForce || bSourceNewer)
vdS.CopyTo(m_dPublicCustomData);
else
{
m_dPublicCustomData.CopyTo(vdS);
m_dPublicCustomData = vdS;
}
} }
private void MergeEntryHistory(PwEntry pe, PwEntry peSource, private void MergeEntryHistory(PwEntry pe, PwEntry peSource,
@@ -1543,12 +1634,12 @@ namespace KeePassLib
/// <returns>Index of the icon.</returns> /// <returns>Index of the icon.</returns>
public int GetCustomIconIndex(PwUuid pwIconId) public int GetCustomIconIndex(PwUuid pwIconId)
{ {
for(int i = 0; i < m_vCustomIcons.Count; ++i) for (int i = 0; i < m_vCustomIcons.Count; ++i)
{ {
PwCustomIcon pwci = m_vCustomIcons[i]; PwCustomIcon pwci = m_vCustomIcons[i];
if(pwci.Uuid.Equals(pwIconId)) if (pwci.Uuid.Equals(pwIconId))
return i; return i;
} }
// Debug.Assert(false); // Do not assert // Debug.Assert(false); // Do not assert
return -1; return -1;
@@ -1558,15 +1649,15 @@ namespace KeePassLib
{ {
if(pbPngData == null) { Debug.Assert(false); return -1; } if(pbPngData == null) { Debug.Assert(false); return -1; }
for(int i = 0; i < m_vCustomIcons.Count; ++i) for (int i = 0; i < m_vCustomIcons.Count; ++i)
{ {
PwCustomIcon pwci = m_vCustomIcons[i]; PwCustomIcon pwci = m_vCustomIcons[i];
byte[] pbEx = pwci.ImageDataPng; byte[] pbEx = pwci.ImageDataPng;
if(pbEx == null) { Debug.Assert(false); continue; } if (pbEx == null) { Debug.Assert(false); continue; }
if(MemUtil.ArraysEqual(pbEx, pbPngData)) if (MemUtil.ArraysEqual(pbEx, pbPngData))
return i; return i;
} }
return -1; return -1;
} }
@@ -1585,68 +1676,64 @@ namespace KeePassLib
else { Debug.Assert(false); return null; } else { Debug.Assert(false); return null; }
} }
public bool DeleteCustomIcons(List<PwUuid> vUuidsToDelete) public bool DeleteCustomIcons(List<PwUuid> lUuids)
{ {
Debug.Assert(vUuidsToDelete != null); if (lUuids == null) { Debug.Assert(false); throw new ArgumentNullException("lUuids"); }
if(vUuidsToDelete == null) throw new ArgumentNullException("vUuidsToDelete"); if (lUuids.Count == 0) return false;
if(vUuidsToDelete.Count <= 0) return true;
GroupHandler gh = delegate(PwGroup pg) Dictionary<PwUuid, bool> dToDel = new Dictionary<PwUuid, bool>();
{ foreach (PwUuid pu in lUuids) { dToDel[pu] = true; }
PwUuid uuidThis = pg.CustomIconUuid;
if(uuidThis.Equals(PwUuid.Zero)) return true;
foreach(PwUuid uuidDelete in vUuidsToDelete) DateTime dt = DateTime.UtcNow;
{ for (int i = m_vCustomIcons.Count - 1; i >= 0; --i)
if(uuidThis.Equals(uuidDelete)) {
{ PwUuid pu = m_vCustomIcons[i].Uuid;
pg.CustomIconUuid = PwUuid.Zero; if (dToDel.ContainsKey(pu))
break; {
} m_vCustomIcons[i] = null; // Removed below
} m_vDeletedObjects.Add(new PwDeletedObject(pu, dt));
}
}
return true; Predicate<PwCustomIcon> f = delegate (PwCustomIcon ci) { return (ci == null); };
}; m_vCustomIcons.RemoveAll(f);
EntryHandler eh = delegate(PwEntry pe) FixCustomIconRefs();
{ return true;
RemoveCustomIconUuid(pe, vUuidsToDelete); }
return true;
};
gh(m_pgRootGroup); private void FixCustomIconRefs()
if(!m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh)) {
{ Dictionary<PwUuid, bool> d = new Dictionary<PwUuid, bool>();
Debug.Assert(false); foreach (PwCustomIcon ci in m_vCustomIcons) { d[ci.Uuid] = true; }
return false;
}
foreach(PwUuid pwUuid in vUuidsToDelete) GroupHandler gh = delegate (PwGroup pg)
{ {
int nIndex = GetCustomIconIndex(pwUuid); PwUuid pu = pg.CustomIconUuid;
if(nIndex >= 0) m_vCustomIcons.RemoveAt(nIndex); if (pu.Equals(PwUuid.Zero)) return true;
} if (!d.ContainsKey(pu)) pg.CustomIconUuid = PwUuid.Zero;
return true;
};
return true; EntryHandler eh = delegate (PwEntry pe)
} {
FixCustomIconRefs(pe, d);
return true;
};
private static void RemoveCustomIconUuid(PwEntry pe, List<PwUuid> vToDelete) gh(m_pgRootGroup);
{ m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh);
PwUuid uuidThis = pe.CustomIconUuid; }
if(uuidThis.Equals(PwUuid.Zero)) return;
foreach(PwUuid uuidDelete in vToDelete) private void FixCustomIconRefs(PwEntry pe, Dictionary<PwUuid, bool> d)
{ {
if(uuidThis.Equals(uuidDelete)) PwUuid pu = pe.CustomIconUuid;
{ if (pu.Equals(PwUuid.Zero)) return;
pe.CustomIconUuid = PwUuid.Zero; if (!d.ContainsKey(pu)) pe.CustomIconUuid = PwUuid.Zero;
break;
} foreach (PwEntry peH in pe.History) FixCustomIconRefs(peH, d);
} }
foreach(PwEntry peHistory in pe.History)
RemoveCustomIconUuid(peHistory, vToDelete);
}
private int GetTotalObjectUuidCount() private int GetTotalObjectUuidCount()
{ {
@@ -1935,61 +2022,119 @@ namespace KeePassLib
return uDeleted; return uDeleted;
} }
public uint DeleteUnusedCustomIcons() public uint DeleteUnusedCustomIcons()
{ {
List<PwUuid> lToDelete = new List<PwUuid>(); Dictionary<PwUuid, bool> dToDel = new Dictionary<PwUuid, bool>();
foreach(PwCustomIcon pwci in m_vCustomIcons) foreach (PwCustomIcon ci in m_vCustomIcons) { dToDel[ci.Uuid] = true; }
lToDelete.Add(pwci.Uuid);
GroupHandler gh = delegate(PwGroup pg) GroupHandler gh = delegate (PwGroup pg)
{ {
PwUuid pwUuid = pg.CustomIconUuid; PwUuid pu = pg.CustomIconUuid;
if((pwUuid == null) || pwUuid.Equals(PwUuid.Zero)) return true; if (!pu.Equals(PwUuid.Zero)) dToDel.Remove(pu);
for(int i = 0; i < lToDelete.Count; ++i)
{
if(lToDelete[i].Equals(pwUuid))
{
lToDelete.RemoveAt(i);
break;
}
}
return true; return true;
}; };
EntryHandler eh = delegate(PwEntry pe) EntryHandler eh = delegate (PwEntry pe)
{ {
PwUuid pwUuid = pe.CustomIconUuid; RemoveCustomIconsFromDict(dToDel, pe);
if((pwUuid == null) || pwUuid.Equals(PwUuid.Zero)) return true;
for(int i = 0; i < lToDelete.Count; ++i)
{
if(lToDelete[i].Equals(pwUuid))
{
lToDelete.RemoveAt(i);
break;
}
}
return true; return true;
}; };
gh(m_pgRootGroup); gh(m_pgRootGroup);
m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); m_pgRootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh);
uint uDeleted = 0; uint cDel = (uint)dToDel.Count;
foreach(PwUuid pwDel in lToDelete) if (cDel != 0)
{ {
int nIndex = GetCustomIconIndex(pwDel); DeleteCustomIcons(new List<PwUuid>(dToDel.Keys));
if(nIndex < 0) { Debug.Assert(false); continue; } m_bUINeedsIconUpdate = true;
m_vCustomIcons.RemoveAt(nIndex);
++uDeleted;
} }
if(uDeleted > 0) m_bUINeedsIconUpdate = true; return cDel;
return uDeleted; }
private static void RemoveCustomIconsFromDict(Dictionary<PwUuid, bool> d,
PwEntry pe)
{
PwUuid pu = pe.CustomIconUuid;
if (!pu.Equals(PwUuid.Zero)) d.Remove(pu);
foreach (PwEntry peH in pe.History) RemoveCustomIconsFromDict(d, peH);
}
internal static void CopyCustomIcons(PwDatabase pdFrom, PwDatabase pdTo,
PwGroup pgSelect, bool bResetIfUnknown)
{
if (pgSelect == null) { Debug.Assert(false); return; }
Dictionary<PwUuid, PwCustomIcon> dFrom = new Dictionary<PwUuid, PwCustomIcon>();
if (pdFrom != null)
{
foreach (PwCustomIcon ci in pdFrom.m_vCustomIcons)
dFrom[ci.Uuid] = ci;
}
Dictionary<PwUuid, int> dTo = new Dictionary<PwUuid, int>();
if (pdTo != null)
{
for (int i = pdTo.m_vCustomIcons.Count - 1; i >= 0; --i)
dTo[pdTo.m_vCustomIcons[i].Uuid] = i;
}
Func<PwUuid, bool> fEnsureIcon = delegate (PwUuid puIcon)
{
if (puIcon.Equals(PwUuid.Zero)) return true;
if (pdTo == null) { Debug.Assert(false); return false; }
PwCustomIcon ciFrom;
if (!dFrom.TryGetValue(puIcon, out ciFrom)) { Debug.Assert(false); return false; }
int iTo;
if (dTo.TryGetValue(puIcon, out iTo))
{
PwCustomIcon ciTo = pdTo.m_vCustomIcons[iTo];
DateTime? odtFrom = ciFrom.LastModificationTime;
DateTime? odtTo = ciTo.LastModificationTime;
if (odtFrom.HasValue && odtTo.HasValue)
{
if (odtFrom.Value <= odtTo.Value) return true;
}
else if (odtTo.HasValue) return true;
else if (!odtFrom.HasValue) return true; // Both no time
pdTo.m_vCustomIcons[iTo] = ciFrom.Clone();
}
else
{
dTo[puIcon] = pdTo.m_vCustomIcons.Count;
pdTo.m_vCustomIcons.Add(ciFrom.Clone());
}
pdTo.Modified = true;
pdTo.UINeedsIconUpdate = true;
return true;
};
GroupHandler gh = delegate (PwGroup pgCur)
{
bool bTo = fEnsureIcon(pgCur.CustomIconUuid);
if (!bTo && bResetIfUnknown) pgCur.CustomIconUuid = PwUuid.Zero;
return true;
};
EntryHandler eh = delegate (PwEntry peCur)
{
bool bTo = fEnsureIcon(peCur.CustomIconUuid);
if (!bTo && bResetIfUnknown) peCur.CustomIconUuid = PwUuid.Zero;
return true;
};
gh(pgSelect);
pgSelect.TraverseTree(TraversalMethod.PreOrder, gh, eh);
} }
} }
} }

View File

@@ -228,6 +228,18 @@ namespace KeePassLib
/// </summary> /// </summary>
public sealed class SearchParameters public sealed class SearchParameters
{ {
private string m_strName = string.Empty;
[DefaultValue("")]
public string Name
{
get { return m_strName; }
set
{
if (value == null) throw new ArgumentNullException("value");
m_strName = value;
}
}
private string m_strText = string.Empty; private string m_strText = string.Empty;
[DefaultValue("")] [DefaultValue("")]
public string SearchString public string SearchString
@@ -235,17 +247,25 @@ namespace KeePassLib
get { return m_strText; } get { return m_strText; }
set set
{ {
if(value == null) throw new ArgumentNullException("value"); if (value == null) throw new ArgumentNullException("value");
m_strText = value; m_strText = value;
} }
} }
private bool m_bRegex = false; private PwSearchMode m_sm = PwSearchMode.Simple;
public PwSearchMode SearchMode
{
get { return m_sm; }
set { m_sm = value; }
}
[DefaultValue(false)] [DefaultValue(false)]
[Obsolete]
[XmlIgnore]
public bool RegularExpression public bool RegularExpression
{ {
get { return m_bRegex; } get { return (m_sm == PwSearchMode.Regular); }
set { m_bRegex = value; } set { m_sm = (value ? PwSearchMode.Regular : PwSearchMode.Simple); }
} }
private bool m_bSearchInTitles = true; private bool m_bSearchInTitles = true;
@@ -296,6 +316,22 @@ namespace KeePassLib
set { m_bSearchInOther = value; } set { m_bSearchInOther = value; }
} }
private bool m_bSearchInStringNames = false;
[DefaultValue(false)]
public bool SearchInStringNames
{
get { return m_bSearchInStringNames; }
set { m_bSearchInStringNames = value; }
}
private bool m_bSearchInTags = true;
[DefaultValue(true)]
public bool SearchInTags
{
get { return m_bSearchInTags; }
set { m_bSearchInTags = value; }
}
private bool m_bSearchInUuids = false; private bool m_bSearchInUuids = false;
[DefaultValue(false)] [DefaultValue(false)]
public bool SearchInUuids public bool SearchInUuids
@@ -304,6 +340,14 @@ namespace KeePassLib
set { m_bSearchInUuids = value; } set { m_bSearchInUuids = value; }
} }
private bool m_bSearchInGroupPaths = false;
[DefaultValue(false)]
public bool SearchInGroupPaths
{
get { return m_bSearchInGroupPaths; }
set { m_bSearchInGroupPaths = value; }
}
private bool m_bSearchInGroupNames = false; private bool m_bSearchInGroupNames = false;
[DefaultValue(false)] [DefaultValue(false)]
public bool SearchInGroupNames public bool SearchInGroupNames
@@ -312,12 +356,12 @@ namespace KeePassLib
set { m_bSearchInGroupNames = value; } set { m_bSearchInGroupNames = value; }
} }
private bool m_bSearchInTags = true; private bool m_bSearchInHistory = false;
[DefaultValue(true)] [DefaultValue(false)]
public bool SearchInTags public bool SearchInHistory
{ {
get { return m_bSearchInTags; } get { return m_bSearchInHistory; }
set { m_bSearchInTags = value; } set { m_bSearchInHistory = value; }
} }
#if KeePassUAP #if KeePassUAP
@@ -369,7 +413,7 @@ namespace KeePassLib
get { return m_strDataTrf; } get { return m_strDataTrf; }
set set
{ {
if(value == null) throw new ArgumentNullException("value"); if (value == null) throw new ArgumentNullException("value");
m_strDataTrf = value; m_strDataTrf = value;
} }
} }
@@ -381,20 +425,24 @@ namespace KeePassLib
{ {
SearchParameters sp = new SearchParameters(); SearchParameters sp = new SearchParameters();
// sp.m_strText = string.Empty; Debug.Assert(sp.m_strName.Length == 0);
// sp.m_bRegex = false; Debug.Assert(sp.m_strText.Length == 0);
Debug.Assert(sp.m_sm == PwSearchMode.Simple);
sp.m_bSearchInTitles = false; sp.m_bSearchInTitles = false;
sp.m_bSearchInUserNames = false; sp.m_bSearchInUserNames = false;
// sp.m_bSearchInPasswords = false; Debug.Assert(!sp.m_bSearchInPasswords);
sp.m_bSearchInUrls = false; sp.m_bSearchInUrls = false;
sp.m_bSearchInNotes = false; sp.m_bSearchInNotes = false;
sp.m_bSearchInOther = false; sp.m_bSearchInOther = false;
// sp.m_bSearchInUuids = false; Debug.Assert(!sp.m_bSearchInStringNames);
// sp.SearchInGroupNames = false;
sp.m_bSearchInTags = false; sp.m_bSearchInTags = false;
// sp.m_scType = StringComparison.InvariantCultureIgnoreCase; Debug.Assert(!sp.m_bSearchInUuids);
// sp.m_bExcludeExpired = false; Debug.Assert(!sp.m_bSearchInGroupPaths);
// m_bRespectEntrySearchingDisabled = true; Debug.Assert(!sp.m_bSearchInGroupNames);
Debug.Assert(!sp.m_bSearchInHistory);
// Debug.Assert(sp.m_scType == StringComparison.InvariantCultureIgnoreCase);
Debug.Assert(!sp.m_bExcludeExpired);
Debug.Assert(sp.m_bRespectEntrySearchingDisabled);
return sp; return sp;
} }

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -42,14 +42,15 @@ namespace KeePassLib
private PwUuid m_uuid = PwUuid.Zero; private PwUuid m_uuid = PwUuid.Zero;
private PwGroup m_pParentGroup = null; private PwGroup m_pParentGroup = null;
private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow; private DateTime m_tParentGroupLastMod = PwDefs.DtDefaultNow;
private PwUuid m_puPrevParentGroup = PwUuid.Zero;
private ProtectedStringDictionary m_listStrings = new ProtectedStringDictionary(); private ProtectedStringDictionary m_dStrings = new ProtectedStringDictionary();
private ProtectedBinaryDictionary m_listBinaries = new ProtectedBinaryDictionary(); private ProtectedBinaryDictionary m_dBinaries = new ProtectedBinaryDictionary();
private AutoTypeConfig m_listAutoType = new AutoTypeConfig(); private AutoTypeConfig m_cfgAutoType = new AutoTypeConfig();
private PwObjectList<PwEntry> m_listHistory = new PwObjectList<PwEntry>(); private PwObjectList<PwEntry> m_lHistory = new PwObjectList<PwEntry>();
private PwIcon m_pwIcon = PwIcon.Key; private PwIcon m_pwIcon = PwIcon.Key;
private PwUuid m_pwCustomIconID = PwUuid.Zero; private PwUuid m_puCustomIcon = PwUuid.Zero;
private Color m_clrForeground = Color.Empty; private Color m_clrForeground = Color.Empty;
private Color m_clrBackground = Color.Empty; private Color m_clrBackground = Color.Empty;
@@ -62,20 +63,21 @@ namespace KeePassLib
private ulong m_uUsageCount = 0; private ulong m_uUsageCount = 0;
private string m_strOverrideUrl = string.Empty; private string m_strOverrideUrl = string.Empty;
private bool m_bQualityCheck = true;
private List<string> m_vTags = new List<string>(); private List<string> m_lTags = new List<string>();
private StringDictionaryEx m_dCustomData = new StringDictionaryEx(); private StringDictionaryEx m_dCustomData = new StringDictionaryEx();
/// <summary> /// <summary>
/// UUID of this entry. /// UUID of this entry.
/// </summary> /// </summary>
/// </summary>
public PwUuid Uuid public PwUuid Uuid
{ {
get { return m_uuid; } get { return m_uuid; }
set set
{ {
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_uuid = value; m_uuid = value;
} }
} }
@@ -87,7 +89,7 @@ namespace KeePassLib
{ {
get { return m_pParentGroup; } get { return m_pParentGroup; }
/// Plugins: use <c>PwGroup.AddEntry</c> instead. // Plugins: use <c>PwGroup.AddEntry</c> instead.
internal set { m_pParentGroup = value; } internal set { m_pParentGroup = value; }
} }
@@ -100,17 +102,26 @@ namespace KeePassLib
set { m_tParentGroupLastMod = value; } set { m_tParentGroupLastMod = value; }
} }
public PwUuid PreviousParentGroup
{
get { return m_puPrevParentGroup; }
set
{
if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_puPrevParentGroup = value;
}
}
/// <summary> /// <summary>
/// Get or set all entry strings. /// Get or set all entry strings.
/// </summary> /// </summary>
public ProtectedStringDictionary Strings public ProtectedStringDictionary Strings
{ {
get { return m_listStrings; } get { return m_dStrings; }
set set
{ {
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_listStrings = value; m_dStrings = value;
} }
} }
@@ -119,11 +130,11 @@ namespace KeePassLib
/// </summary> /// </summary>
public ProtectedBinaryDictionary Binaries public ProtectedBinaryDictionary Binaries
{ {
get { return m_listBinaries; } get { return m_dBinaries; }
set set
{ {
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_listBinaries = value; m_dBinaries = value;
} }
} }
@@ -132,11 +143,11 @@ namespace KeePassLib
/// </summary> /// </summary>
public AutoTypeConfig AutoType public AutoTypeConfig AutoType
{ {
get { return m_listAutoType; } get { return m_cfgAutoType; }
set set
{ {
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_listAutoType = value; m_cfgAutoType = value;
} }
} }
@@ -145,11 +156,11 @@ namespace KeePassLib
/// </summary> /// </summary>
public PwObjectList<PwEntry> History public PwObjectList<PwEntry> History
{ {
get { return m_listHistory; } get { return m_lHistory; }
set set
{ {
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_listHistory = value; m_lHistory = value;
} }
} }
@@ -169,11 +180,11 @@ namespace KeePassLib
/// </summary> /// </summary>
public PwUuid CustomIconUuid public PwUuid CustomIconUuid
{ {
get { return m_pwCustomIconID; } get { return m_puCustomIcon; }
set set
{ {
Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value"); if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_pwCustomIconID = value; m_puCustomIcon = value;
} }
} }
@@ -252,28 +263,34 @@ namespace KeePassLib
} }
/// <summary> /// <summary>
/// Entry-specific override URL. If this string is non-empty, /// Entry-specific override URL.
/// </summary> /// </summary>
public string OverrideUrl public string OverrideUrl
{ {
get { return m_strOverrideUrl; } get { return m_strOverrideUrl; }
set set
{ {
if (value == null) throw new ArgumentNullException("value"); if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_strOverrideUrl = value; m_strOverrideUrl = value;
} }
} }
public bool QualityCheck
{
get { return m_bQualityCheck; }
set { m_bQualityCheck = value; }
}
/// <summary> /// <summary>
/// List of tags associated with this entry. /// List of tags associated with this entry.
/// </summary> /// </summary>
public List<string> Tags public List<string> Tags
{ {
get { return m_vTags; } get { StrUtil.NormalizeTags(m_lTags); return m_lTags; }
set set
{ {
if (value == null) throw new ArgumentNullException("value"); if (value == null) { Debug.Assert(false); throw new ArgumentNullException("value"); }
m_vTags = value; m_lTags = value;
} }
} }
@@ -349,19 +366,19 @@ namespace KeePassLib
} }
#if DEBUG #if DEBUG
/// <summary> // For display in debugger
public override string ToString() public override string ToString()
{ {
return (@"PwEntry '" + m_listStrings.ReadSafe(PwDefs.TitleField) + @"'"); return ("PwEntry '" + m_dStrings.ReadSafe(PwDefs.TitleField) + "'");
} }
#endif #endif
/// <summary>
/// Clone the current entry. The returned entry is an exact value copy /// Clone the current entry. The returned entry is an exact value copy
/// of the current entry (including UUID and parent group reference). /// of the current entry (including UUID and parent group reference).
/// All mutable members are cloned. /// All mutable members are cloned.
/// </summary> /// </summary>
/// <returns>Exact value clone. All references to mutable values changed.</returns> /// <returns>Exact value clone. All references to mutable values changed.</returns>
/// </summary>
/// <returns>Exact value clone. All references to mutable values changed.</returns>
public PwEntry CloneDeep() public PwEntry CloneDeep()
{ {
PwEntry peNew = new PwEntry(false, false); PwEntry peNew = new PwEntry(false, false);
@@ -369,14 +386,15 @@ namespace KeePassLib
peNew.m_uuid = m_uuid; // PwUuid is immutable peNew.m_uuid = m_uuid; // PwUuid is immutable
peNew.m_pParentGroup = m_pParentGroup; peNew.m_pParentGroup = m_pParentGroup;
peNew.m_tParentGroupLastMod = m_tParentGroupLastMod; peNew.m_tParentGroupLastMod = m_tParentGroupLastMod;
peNew.m_puPrevParentGroup = m_puPrevParentGroup;
peNew.m_listStrings = m_listStrings.CloneDeep(); peNew.m_dStrings = m_dStrings.CloneDeep();
peNew.m_listBinaries = m_listBinaries.CloneDeep(); peNew.m_dBinaries = m_dBinaries.CloneDeep();
peNew.m_listAutoType = m_listAutoType.CloneDeep(); peNew.m_cfgAutoType = m_cfgAutoType.CloneDeep();
peNew.m_listHistory = m_listHistory.CloneDeep(); peNew.m_lHistory = m_lHistory.CloneDeep();
peNew.m_pwIcon = m_pwIcon; peNew.m_pwIcon = m_pwIcon;
peNew.m_pwCustomIconID = m_pwCustomIconID; peNew.m_puCustomIcon = m_puCustomIcon;
peNew.m_clrForeground = m_clrForeground; peNew.m_clrForeground = m_clrForeground;
peNew.m_clrBackground = m_clrBackground; peNew.m_clrBackground = m_clrBackground;
@@ -389,8 +407,9 @@ namespace KeePassLib
peNew.m_uUsageCount = m_uUsageCount; peNew.m_uUsageCount = m_uUsageCount;
peNew.m_strOverrideUrl = m_strOverrideUrl; peNew.m_strOverrideUrl = m_strOverrideUrl;
peNew.m_bQualityCheck = m_bQualityCheck;
peNew.m_vTags = new List<string>(m_vTags); peNew.m_lTags.AddRange(m_lTags);
peNew.m_dCustomData = m_dCustomData.CloneDeep(); peNew.m_dCustomData = m_dCustomData.CloneDeep();
@@ -457,27 +476,29 @@ namespace KeePassLib
if (m_pParentGroup != pe.m_pParentGroup) return false; if (m_pParentGroup != pe.m_pParentGroup) return false;
if (!bIgnoreLastMod && (m_tParentGroupLastMod != pe.m_tParentGroupLastMod)) if (!bIgnoreLastMod && (m_tParentGroupLastMod != pe.m_tParentGroupLastMod))
return false; return false;
if (!m_puPrevParentGroup.Equals(pe.m_puPrevParentGroup))
return false;
} }
if (!m_listStrings.EqualsDictionary(pe.m_listStrings, pwOpt, mpCmpStr)) if (!m_dStrings.EqualsDictionary(pe.m_dStrings, pwOpt, mpCmpStr))
return false; return false;
if (!m_listBinaries.EqualsDictionary(pe.m_listBinaries)) return false; if (!m_dBinaries.EqualsDictionary(pe.m_dBinaries)) return false;
if (!m_listAutoType.Equals(pe.m_listAutoType)) return false; if (!m_cfgAutoType.Equals(pe.m_cfgAutoType)) return false;
if ((pwOpt & PwCompareOptions.IgnoreHistory) == PwCompareOptions.None) if ((pwOpt & PwCompareOptions.IgnoreHistory) == PwCompareOptions.None)
{ {
bool bIgnoreLastBackup = ((pwOpt & PwCompareOptions.IgnoreLastBackup) != bool bIgnoreLastBackup = ((pwOpt & PwCompareOptions.IgnoreLastBackup) !=
PwCompareOptions.None); PwCompareOptions.None);
if (!bIgnoreLastBackup && (m_listHistory.UCount != pe.m_listHistory.UCount)) if (!bIgnoreLastBackup && (m_lHistory.UCount != pe.m_lHistory.UCount))
return false; return false;
if (bIgnoreLastBackup && (m_listHistory.UCount == 0)) if (bIgnoreLastBackup && (m_lHistory.UCount == 0))
{ {
Debug.Assert(false); Debug.Assert(false);
return false; return false;
} }
if (bIgnoreLastBackup && ((m_listHistory.UCount - 1) != pe.m_listHistory.UCount)) if (bIgnoreLastBackup && ((m_lHistory.UCount - 1) != pe.m_lHistory.UCount))
return false; return false;
PwCompareOptions cmpSub = PwCompareOptions.IgnoreParentGroup; PwCompareOptions cmpSub = PwCompareOptions.IgnoreParentGroup;
@@ -485,16 +506,16 @@ namespace KeePassLib
if (bIgnoreLastMod) cmpSub |= PwCompareOptions.IgnoreLastMod; if (bIgnoreLastMod) cmpSub |= PwCompareOptions.IgnoreLastMod;
if (bIgnoreLastAccess) cmpSub |= PwCompareOptions.IgnoreLastAccess; if (bIgnoreLastAccess) cmpSub |= PwCompareOptions.IgnoreLastAccess;
for (uint uHist = 0; uHist < pe.m_listHistory.UCount; ++uHist) for (uint uHist = 0; uHist < pe.m_lHistory.UCount; ++uHist)
{ {
if (!m_listHistory.GetAt(uHist).EqualsEntry(pe.m_listHistory.GetAt( if (!m_lHistory.GetAt(uHist).EqualsEntry(pe.m_lHistory.GetAt(
uHist), cmpSub, MemProtCmpMode.None)) uHist), cmpSub, MemProtCmpMode.None))
return false; return false;
} }
} }
if (m_pwIcon != pe.m_pwIcon) return false; if (m_pwIcon != pe.m_pwIcon) return false;
if (!m_pwCustomIconID.Equals(pe.m_pwCustomIconID)) return false; if (!m_puCustomIcon.Equals(pe.m_puCustomIcon)) return false;
if (m_clrForeground != pe.m_clrForeground) return false; if (m_clrForeground != pe.m_clrForeground) return false;
if (m_clrBackground != pe.m_clrBackground) return false; if (m_clrBackground != pe.m_clrBackground) return false;
@@ -507,12 +528,10 @@ namespace KeePassLib
if (!bIgnoreLastAccess && (m_uUsageCount != pe.m_uUsageCount)) return false; if (!bIgnoreLastAccess && (m_uUsageCount != pe.m_uUsageCount)) return false;
if (m_strOverrideUrl != pe.m_strOverrideUrl) return false; if (m_strOverrideUrl != pe.m_strOverrideUrl) return false;
if (m_bQualityCheck != pe.m_bQualityCheck) return false;
if (m_vTags.Count != pe.m_vTags.Count) return false; // The Tags property normalizes
for (int iTag = 0; iTag < m_vTags.Count; ++iTag) if (!MemUtil.ListsEqual<string>(this.Tags, pe.Tags)) return false;
{
if (m_vTags[iTag] != pe.m_vTags[iTag]) return false;
}
if (!m_dCustomData.Equals(pe.m_dCustomData)) return false; if (!m_dCustomData.Equals(pe.m_dCustomData)) return false;
@@ -543,16 +562,19 @@ namespace KeePassLib
m_uuid = peTemplate.m_uuid; m_uuid = peTemplate.m_uuid;
if (bAssignLocationChanged) if (bAssignLocationChanged)
{
m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod; m_tParentGroupLastMod = peTemplate.m_tParentGroupLastMod;
m_puPrevParentGroup = peTemplate.m_puPrevParentGroup;
}
m_listStrings = peTemplate.m_listStrings.CloneDeep(); m_dStrings = peTemplate.m_dStrings.CloneDeep();
m_listBinaries = peTemplate.m_listBinaries.CloneDeep(); m_dBinaries = peTemplate.m_dBinaries.CloneDeep();
m_listAutoType = peTemplate.m_listAutoType.CloneDeep(); m_cfgAutoType = peTemplate.m_cfgAutoType.CloneDeep();
if (bIncludeHistory) if (bIncludeHistory)
m_listHistory = peTemplate.m_listHistory.CloneDeep(); m_lHistory = peTemplate.m_lHistory.CloneDeep();
m_pwIcon = peTemplate.m_pwIcon; m_pwIcon = peTemplate.m_pwIcon;
m_pwCustomIconID = peTemplate.m_pwCustomIconID; // Immutable m_puCustomIcon = peTemplate.m_puCustomIcon; // Immutable
m_clrForeground = peTemplate.m_clrForeground; m_clrForeground = peTemplate.m_clrForeground;
m_clrBackground = peTemplate.m_clrBackground; m_clrBackground = peTemplate.m_clrBackground;
@@ -565,8 +587,9 @@ namespace KeePassLib
m_uUsageCount = peTemplate.m_uUsageCount; m_uUsageCount = peTemplate.m_uUsageCount;
m_strOverrideUrl = peTemplate.m_strOverrideUrl; m_strOverrideUrl = peTemplate.m_strOverrideUrl;
m_bQualityCheck = peTemplate.m_bQualityCheck;
m_vTags = new List<string>(peTemplate.m_vTags); m_lTags = new List<string>(peTemplate.m_lTags);
m_dCustomData = peTemplate.m_dCustomData.CloneDeep(); m_dCustomData = peTemplate.m_dCustomData.CloneDeep();
} }
@@ -629,9 +652,9 @@ namespace KeePassLib
public void CreateBackup(PwDatabase pwHistMntcSettings) public void CreateBackup(PwDatabase pwHistMntcSettings)
{ {
PwEntry peCopy = CloneDeep(); PwEntry peCopy = CloneDeep();
peCopy.History = new PwObjectList<PwEntry>(); // Remove history peCopy.m_lHistory.Clear();
m_listHistory.Add(peCopy); // Must be added at end, see EqualsEntry m_lHistory.Add(peCopy); // Must be added at end, see EqualsEntry
if (pwHistMntcSettings != null) MaintainBackups(pwHistMntcSettings); if (pwHistMntcSettings != null) MaintainBackups(pwHistMntcSettings);
} }
@@ -658,12 +681,14 @@ namespace KeePassLib
/// This parameter may be <c>null</c> (no maintenance then).</param> /// This parameter may be <c>null</c> (no maintenance then).</param>
public void RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings) public void RestoreFromBackup(uint uBackupIndex, PwDatabase pwHistMntcSettings)
{ {
Debug.Assert(uBackupIndex < m_listHistory.UCount); if (uBackupIndex >= m_lHistory.UCount)
if (uBackupIndex >= m_listHistory.UCount) {
Debug.Assert(false);
throw new ArgumentOutOfRangeException("uBackupIndex"); throw new ArgumentOutOfRangeException("uBackupIndex");
}
PwEntry pe = m_listHistory.GetAt(uBackupIndex); PwEntry pe = m_lHistory.GetAt(uBackupIndex);
Debug.Assert(pe != null); if (pe == null) throw new InvalidOperationException(); if (pe == null) { Debug.Assert(false); throw new InvalidOperationException(); }
CreateBackup(pwHistMntcSettings); // Backup current data before restoring CreateBackup(pwHistMntcSettings); // Backup current data before restoring
AssignProperties(pe, false, false, false); AssignProperties(pe, false, false, false);
@@ -679,7 +704,7 @@ namespace KeePassLib
if (bIgnoreLastMod) cmpOpt |= PwCompareOptions.IgnoreLastMod; if (bIgnoreLastMod) cmpOpt |= PwCompareOptions.IgnoreLastMod;
if (bIgnoreLastAccess) cmpOpt |= PwCompareOptions.IgnoreLastAccess; if (bIgnoreLastAccess) cmpOpt |= PwCompareOptions.IgnoreLastAccess;
foreach (PwEntry pe in m_listHistory) foreach (PwEntry pe in m_lHistory)
{ {
if (pe.EqualsEntry(peData, cmpOpt, MemProtCmpMode.None)) return true; if (pe.EqualsEntry(peData, cmpOpt, MemProtCmpMode.None)) return true;
} }
@@ -688,21 +713,28 @@ namespace KeePassLib
} }
/// <summary> /// <summary>
/// Delete old history items if there are too many or the history /// Delete old history entries if there are too many or the
/// size is too large. /// history size is too large.
/// <returns>If one or more history items have been deleted, <c>true</c> /// <returns>If one or more history entries have been deleted,
/// is returned. Otherwise <c>false</c>.</returns> /// <c>true</c> is returned. Otherwise <c>false</c>.</returns>
/// </summary> /// </summary>
public bool MaintainBackups(PwDatabase pwSettings) public bool MaintainBackups(PwDatabase pwSettings)
{ {
if (pwSettings == null) { Debug.Assert(false); return false; } if (pwSettings == null) { Debug.Assert(false); return false; }
// Fix UUIDs of history entries; should not be necessary
PwUuid pu = m_uuid;
foreach (PwEntry pe in m_lHistory)
{
if (!pe.Uuid.Equals(pu)) { Debug.Assert(false); pe.Uuid = pu; }
}
bool bDeleted = false; bool bDeleted = false;
int nMaxItems = pwSettings.HistoryMaxItems; int nMaxItems = pwSettings.HistoryMaxItems;
if (nMaxItems >= 0) if (nMaxItems >= 0)
{ {
while (m_listHistory.UCount > (uint)nMaxItems) while (m_lHistory.UCount > (uint)nMaxItems)
{ {
RemoveOldestBackup(); RemoveOldestBackup();
bDeleted = true; bDeleted = true;
@@ -715,7 +747,7 @@ namespace KeePassLib
while (true) while (true)
{ {
ulong uHistSize = 0; ulong uHistSize = 0;
foreach (PwEntry pe in m_listHistory) { uHistSize += pe.GetSize(); } foreach (PwEntry pe in m_lHistory) { uHistSize += pe.GetSize(); }
if (uHistSize > (ulong)lMaxSize) if (uHistSize > (ulong)lMaxSize)
{ {
@@ -734,9 +766,9 @@ namespace KeePassLib
DateTime dtMin = TimeUtil.SafeMaxValueUtc; DateTime dtMin = TimeUtil.SafeMaxValueUtc;
uint idxRemove = uint.MaxValue; uint idxRemove = uint.MaxValue;
for (uint u = 0; u < m_listHistory.UCount; ++u) for (uint u = 0; u < m_lHistory.UCount; ++u)
{ {
PwEntry pe = m_listHistory.GetAt(u); PwEntry pe = m_lHistory.GetAt(u);
if (TimeUtil.Compare(pe.LastModificationTime, dtMin, true) < 0) if (TimeUtil.Compare(pe.LastModificationTime, dtMin, true) < 0)
{ {
idxRemove = u; idxRemove = u;
@@ -744,12 +776,12 @@ namespace KeePassLib
} }
} }
if (idxRemove != uint.MaxValue) m_listHistory.RemoveAt(idxRemove); if (idxRemove != uint.MaxValue) m_lHistory.RemoveAt(idxRemove);
} }
public bool GetAutoTypeEnabled() public bool GetAutoTypeEnabled()
{ {
if (!m_listAutoType.Enabled) return false; if (!m_cfgAutoType.Enabled) return false;
if (m_pParentGroup != null) if (m_pParentGroup != null)
return m_pParentGroup.GetAutoTypeEnabledInherited(); return m_pParentGroup.GetAutoTypeEnabledInherited();
@@ -759,7 +791,7 @@ namespace KeePassLib
public string GetAutoTypeSequence() public string GetAutoTypeSequence()
{ {
string strSeq = m_listAutoType.DefaultSequence; string strSeq = m_cfgAutoType.DefaultSequence;
PwGroup pg = m_pParentGroup; PwGroup pg = m_pParentGroup;
while (pg != null) while (pg != null)
@@ -785,69 +817,67 @@ namespace KeePassLib
} }
/// <summary> /// <summary>
/// Approximate the total size of this entry in bytes (including /// Approximate the total size (in process memory) of this entry
/// strings, binaries and history entries). /// in bytes (including strings, binaries and history entries).
/// </summary> /// </summary>
/// <returns>Size in bytes.</returns> /// <returns>Size in bytes.</returns>
public ulong GetSize() public ulong GetSize()
{ {
ulong uSize = 128; // Approx fixed length data // This method assumes 64-bit pointers/references and Unicode
// strings (i.e. 2 bytes per character)
foreach (KeyValuePair<string, ProtectedString> kvpStr in m_listStrings) ulong cb = 276; // Number of bytes; approx. fixed length data
ulong cc = 0; // Number of characters
cb += (ulong)m_dStrings.UCount * 40;
foreach (KeyValuePair<string, ProtectedString> kvpStr in m_dStrings)
cc += (ulong)kvpStr.Key.Length + (ulong)kvpStr.Value.Length;
cb += (ulong)m_dBinaries.UCount * 65;
foreach (KeyValuePair<string, ProtectedBinary> kvpBin in m_dBinaries)
{ {
uSize += (ulong)kvpStr.Key.Length; cc += (ulong)kvpBin.Key.Length;
uSize += (ulong)kvpStr.Value.Length; cb += (ulong)kvpBin.Value.Length;
} }
foreach (KeyValuePair<string, ProtectedBinary> kvpBin in m_listBinaries) cc += (ulong)m_cfgAutoType.DefaultSequence.Length;
{ cb += (ulong)m_cfgAutoType.AssociationsCount * 24;
uSize += (ulong)kvpBin.Key.Length; foreach (AutoTypeAssociation a in m_cfgAutoType.Associations)
uSize += kvpBin.Value.Length; cc += (ulong)a.WindowName.Length + (ulong)a.Sequence.Length;
}
uSize += (ulong)m_listAutoType.DefaultSequence.Length; cb += (ulong)m_lHistory.UCount * 8;
foreach (AutoTypeAssociation a in m_listAutoType.Associations) foreach (PwEntry peHistory in m_lHistory)
{ cb += peHistory.GetSize();
uSize += (ulong)a.WindowName.Length;
uSize += (ulong)a.Sequence.Length;
}
foreach (PwEntry peHistory in m_listHistory) cc += (ulong)m_strOverrideUrl.Length;
uSize += peHistory.GetSize();
uSize += (ulong)m_strOverrideUrl.Length; cb += (ulong)m_lTags.Count * 8;
foreach (string strTag in m_lTags)
foreach (string strTag in m_vTags) cc += (ulong)strTag.Length;
uSize += (ulong)strTag.Length;
cb += (ulong)m_dCustomData.Count * 16;
foreach (KeyValuePair<string, string> kvp in m_dCustomData) foreach (KeyValuePair<string, string> kvp in m_dCustomData)
uSize += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length; cc += (ulong)kvp.Key.Length + (ulong)kvp.Value.Length;
return uSize; return (cb + (cc << 1));
} }
public bool HasTag(string strTag) public bool HasTag(string strTag)
{ {
if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
for (int i = 0; i < m_vTags.Count; ++i) // this.Tags normalizes
{ return this.Tags.Contains(StrUtil.NormalizeTag(strTag));
if (m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return true;
}
return false;
} }
public bool AddTag(string strTag) public bool AddTag(string strTag)
{ {
if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
for (int i = 0; i < m_vTags.Count; ++i) strTag = StrUtil.NormalizeTag(strTag);
{ if (this.Tags.Contains(strTag)) return false; // this.Tags normalizes
if (m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) return false;
}
m_vTags.Add(strTag); m_lTags.Add(strTag);
return true; return true;
} }
@@ -855,16 +885,17 @@ namespace KeePassLib
{ {
if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; } if (string.IsNullOrEmpty(strTag)) { Debug.Assert(false); return false; }
for (int i = 0; i < m_vTags.Count; ++i) // this.Tags normalizes
{ return this.Tags.Remove(StrUtil.NormalizeTag(strTag));
if (m_vTags[i].Equals(strTag, StrUtil.CaseIgnoreCmp)) }
{
m_vTags.RemoveAt(i);
return true;
}
}
return false; internal List<string> GetTagsInherited()
{
List<string> l = ((m_pParentGroup != null) ?
m_pParentGroup.GetTagsInherited(false) : new List<string>());
l.AddRange(this.Tags);
StrUtil.NormalizeTags(l);
return l;
} }
public bool IsContainedIn(PwGroup pgContainer) public bool IsContainedIn(PwGroup pgContainer)
@@ -886,10 +917,8 @@ namespace KeePassLib
if (bAlsoChangeHistoryUuids) if (bAlsoChangeHistoryUuids)
{ {
foreach (PwEntry peHist in m_listHistory) foreach (PwEntry peHist in m_lHistory)
{
peHist.Uuid = pwNewUuid; peHist.Uuid = pwNewUuid;
}
} }
} }

View File

@@ -316,4 +316,13 @@ namespace KeePassLib
Cinnamon, Cinnamon,
Pantheon Pantheon
} }
public enum PwSearchMode
{
None = 0,
Simple,
Regular,
XPath
}
} }

View File

@@ -0,0 +1,372 @@
/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.Xml.XPath;
using KeePassLib.Collections;
using KeePassLib.Delegates;
using KeePassLib.Interfaces;
using KeePassLib.Security;
using KeePassLib.Serialization;
using KeePassLib.Utility;
namespace KeePassLib
{
public sealed partial class PwGroup
{
private const int SearchContextStringMaxLength = 50; // Note, doesn't include elipsis, if added
public const string SearchContextUuid = "Uuid";
public const string SearchContextParentGroup = "Parent Group";
public const string SearchContextTags = "Tags";
/// <summary>
/// Search this group and all subgroups for entries.
/// </summary>
/// <param name="sp">Specifies the search method.</param>
/// <param name="listStorage">Entry list in which the search results will
/// be stored.</param>
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage)
{
SearchEntries(sp, listStorage, null);
}
/// <summary>
/// Search this group and all subgroups for entries.
/// </summary>
/// <param name="sp">Specifies the search method.</param>
/// <param name="listStorage">Entry list in which the search results will
/// be stored.</param>
/// <param name="slStatus">Optional status reporting object.</param>
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage,
IStatusLogger slStatus)
{
SearchEntries(sp, listStorage, null, slStatus);
}
/// <summary>
/// Search this group and all subgroups for entries.
/// </summary>
/// <param name="sp">Specifies the search method.</param>
/// <param name="listStorage">Entry list in which the search results will
/// be stored.</param>
/// <param name="resultContexts">Dictionary that will be populated with text fragments indicating the context of why each entry (keyed by Uuid) was returned</param>
public void SearchEntries(SearchParameters sp, PwObjectList<PwEntry> listStorage,
IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts,
IStatusLogger slStatus)
{
if (sp == null)
{
Debug.Assert(false);
return;
}
if (listStorage == null)
{
Debug.Assert(false);
return;
}
ulong uCurEntries = 0, uTotalEntries = 0;
List<string> lTerms = StrUtil.SplitSearchTerms(sp.SearchString);
if ((lTerms.Count <= 1) || sp.RegularExpression)
{
if (slStatus != null) uTotalEntries = GetEntriesCount(true);
SearchEntriesSingle(sp, listStorage, resultContexts, slStatus, ref uCurEntries,
uTotalEntries);
return;
}
// Search longer strings first (for improved performance)
lTerms.Sort(StrUtil.CompareLengthGt);
string strFullSearch = sp.SearchString; // Backup
PwGroup pg = this;
for (int iTerm = 0; iTerm < lTerms.Count; ++iTerm)
{
// Update counters for a better state guess
if (slStatus != null)
{
ulong uRemRounds = (ulong) (lTerms.Count - iTerm);
uTotalEntries = uCurEntries + (uRemRounds *
pg.GetEntriesCount(true));
}
PwGroup pgNew = new PwGroup();
sp.SearchString = lTerms[iTerm];
bool bNegate = false;
if (sp.SearchString.StartsWith("-"))
{
sp.SearchString = sp.SearchString.Substring(1);
bNegate = (sp.SearchString.Length > 0);
}
if (!pg.SearchEntriesSingle(sp, pgNew.Entries, resultContexts, slStatus,
ref uCurEntries, uTotalEntries))
{
pg = null;
break;
}
if (bNegate)
{
PwObjectList<PwEntry> lCand = pg.GetEntries(true);
pg = new PwGroup();
foreach (PwEntry peCand in lCand)
{
if (pgNew.Entries.IndexOf(peCand) < 0) pg.Entries.Add(peCand);
}
}
else pg = pgNew;
}
if (pg != null) listStorage.Add(pg.Entries);
sp.SearchString = strFullSearch; // Restore
}
private bool SearchEntriesSingle(SearchParameters spIn,
PwObjectList<PwEntry> listStorage, IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts,
IStatusLogger slStatus,
ref ulong uCurEntries, ulong uTotalEntries)
{
SearchParameters sp = spIn.Clone();
if (sp.SearchString == null)
{
Debug.Assert(false);
return true;
}
sp.SearchString = sp.SearchString.Trim();
bool bTitle = sp.SearchInTitles;
bool bUserName = sp.SearchInUserNames;
bool bPassword = sp.SearchInPasswords;
bool bUrl = sp.SearchInUrls;
bool bNotes = sp.SearchInNotes;
bool bOther = sp.SearchInOther;
bool bUuids = sp.SearchInUuids;
bool bGroupName = sp.SearchInGroupNames;
bool bTags = sp.SearchInTags;
bool bExcludeExpired = sp.ExcludeExpired;
bool bRespectEntrySearchingDisabled = sp.RespectEntrySearchingDisabled;
DateTime dtNow = DateTime.Now;
Regex rx = null;
if (sp.RegularExpression)
{
RegexOptions ro = RegexOptions.None; // RegexOptions.Compiled
if ((sp.ComparisonMode == StringComparison.CurrentCultureIgnoreCase) ||
#if !KeePassUAP
(sp.ComparisonMode == StringComparison.InvariantCultureIgnoreCase) ||
#endif
(sp.ComparisonMode == StringComparison.OrdinalIgnoreCase))
{
ro |= RegexOptions.IgnoreCase;
}
rx = new Regex(sp.SearchString, ro);
}
ulong uLocalCurEntries = uCurEntries;
EntryHandler eh = null;
if (sp.SearchString.Length <= 0) // Report all
{
eh = delegate(PwEntry pe)
{
if (slStatus != null)
{
if (!slStatus.SetProgress((uint) ((uLocalCurEntries *
100UL) / uTotalEntries))) return false;
++uLocalCurEntries;
}
if (bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled())
return true; // Skip
if (bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime))
return true; // Skip
listStorage.Add(pe);
return true;
};
}
else
{
eh = delegate(PwEntry pe)
{
if (slStatus != null)
{
if (!slStatus.SetProgress((uint) ((uLocalCurEntries *
100UL) / uTotalEntries))) return false;
++uLocalCurEntries;
}
if (bRespectEntrySearchingDisabled && !pe.GetSearchingEnabled())
return true; // Skip
if (bExcludeExpired && pe.Expires && (dtNow > pe.ExpiryTime))
return true; // Skip
uint uInitialResults = listStorage.UCount;
foreach (KeyValuePair<string, ProtectedString> kvp in pe.Strings)
{
string strKey = kvp.Key;
if (strKey == PwDefs.TitleField)
{
if (bTitle)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
}
else if (strKey == PwDefs.UserNameField)
{
if (bUserName)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
}
else if (strKey == PwDefs.PasswordField)
{
if (bPassword)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
}
else if (strKey == PwDefs.UrlField)
{
if (bUrl)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
}
else if (strKey == PwDefs.NotesField)
{
if (bNotes)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
}
else if (bOther)
SearchEvalAdd(sp, kvp.Value.ReadString(),
rx, pe, listStorage, resultContexts, strKey);
// An entry can match only once => break if we have added it
if (listStorage.UCount > uInitialResults) break;
}
if (bUuids && (listStorage.UCount == uInitialResults))
SearchEvalAdd(sp, pe.Uuid.ToHexString(), rx, pe, listStorage, resultContexts,
SearchContextTags);
if (bGroupName && (listStorage.UCount == uInitialResults) &&
(pe.ParentGroup != null))
SearchEvalAdd(sp, pe.ParentGroup.Name, rx, pe, listStorage, resultContexts,
SearchContextParentGroup);
if (bTags)
{
foreach (string strTag in pe.Tags)
{
if (listStorage.UCount != uInitialResults) break; // Match
SearchEvalAdd(sp, strTag, rx, pe, listStorage, resultContexts, SearchContextTags);
}
}
return true;
};
}
if (!PreOrderTraverseTree(null, eh)) return false;
uCurEntries = uLocalCurEntries;
return true;
}
private static void SearchEvalAdd(SearchParameters sp, string strDataField,
Regex rx, PwEntry pe, PwObjectList<PwEntry> lResults,
IDictionary<PwUuid, KeyValuePair<string, string>> resultContexts, string contextFieldName)
{
bool bMatch = false;
int matchPos;
if (rx == null)
{
matchPos = strDataField.IndexOf(sp.SearchString, sp.ComparisonMode);
bMatch = matchPos >= 0;
}
else
{
var match = rx.Match(strDataField);
bMatch = match.Success;
matchPos = match.Index;
}
if (!bMatch && (sp.DataTransformationFn != null))
{
string strCmp = sp.DataTransformationFn(strDataField, pe);
if (!object.ReferenceEquals(strCmp, strDataField))
{
if (rx == null)
{
matchPos = strCmp.IndexOf(sp.SearchString, sp.ComparisonMode);
bMatch = matchPos >= 0;
}
else
{
var match = rx.Match(strCmp);
bMatch = match.Success;
matchPos = match.Index;
}
}
}
if (bMatch)
{
lResults.Add(pe);
if (resultContexts != null)
{
// Trim the value if necessary
var contextString = strDataField;
if (contextString.Length > SearchContextStringMaxLength)
{
// Start 10% before actual data, and don't run over
var startPos = Math.Max(0,
Math.Min(matchPos - (SearchContextStringMaxLength / 10),
contextString.Length - SearchContextStringMaxLength));
contextString = "… " + contextString.Substring(startPos, SearchContextStringMaxLength) +
((startPos + SearchContextStringMaxLength < contextString.Length)
? " …"
: null);
}
resultContexts[pe.Uuid] = new KeyValuePair<string, string>(contextFieldName, contextString);
}
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -16,7 +16,7 @@ namespace KeePassLib.Resources
{ {
string strTemp; string strTemp;
if(dictNew.TryGetValue(strName, out strTemp)) if (dictNew.TryGetValue(strName, out strTemp))
return strTemp; return strTemp;
return strDefault; return strDefault;
@@ -24,8 +24,11 @@ namespace KeePassLib.Resources
public static void SetTranslatedStrings(Dictionary<string, string> dictNew) public static void SetTranslatedStrings(Dictionary<string, string> dictNew)
{ {
if(dictNew == null) throw new ArgumentNullException("dictNew"); if (dictNew == null) throw new ArgumentNullException("dictNew");
m_strAlgorithmUnknown = TryGetEx(dictNew, "AlgorithmUnknown", m_strAlgorithmUnknown);
m_strCharSetInvalid = TryGetEx(dictNew, "CharSetInvalid", m_strCharSetInvalid);
m_strCharSetTooFewChars = TryGetEx(dictNew, "CharSetTooFewChars", m_strCharSetTooFewChars);
m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed); m_strCryptoStreamFailed = TryGetEx(dictNew, "CryptoStreamFailed", m_strCryptoStreamFailed);
m_strEncDataTooLarge = TryGetEx(dictNew, "EncDataTooLarge", m_strEncDataTooLarge); m_strEncDataTooLarge = TryGetEx(dictNew, "EncDataTooLarge", m_strEncDataTooLarge);
m_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard); m_strErrorInClipboard = TryGetEx(dictNew, "ErrorInClipboard", m_strErrorInClipboard);
@@ -41,7 +44,7 @@ namespace KeePassLib.Resources
m_strFileNewVerOrPlgReq = TryGetEx(dictNew, "FileNewVerOrPlgReq", m_strFileNewVerOrPlgReq); m_strFileNewVerOrPlgReq = TryGetEx(dictNew, "FileNewVerOrPlgReq", m_strFileNewVerOrPlgReq);
m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq); m_strFileNewVerReq = TryGetEx(dictNew, "FileNewVerReq", m_strFileNewVerReq);
m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning); m_strFileSaveCorruptionWarning = TryGetEx(dictNew, "FileSaveCorruptionWarning", m_strFileSaveCorruptionWarning);
m_strFileSaveFailed = TryGetEx(dictNew, "FileSaveFailed", m_strFileSaveFailed); m_strFileSaveFailed2 = TryGetEx(dictNew, "FileSaveFailed2", m_strFileSaveFailed2);
m_strFileSigInvalid = TryGetEx(dictNew, "FileSigInvalid", m_strFileSigInvalid); m_strFileSigInvalid = TryGetEx(dictNew, "FileSigInvalid", m_strFileSigInvalid);
m_strFileUnknownCipher = TryGetEx(dictNew, "FileUnknownCipher", m_strFileUnknownCipher); m_strFileUnknownCipher = TryGetEx(dictNew, "FileUnknownCipher", m_strFileUnknownCipher);
m_strFileUnknownCompression = TryGetEx(dictNew, "FileUnknownCompression", m_strFileUnknownCompression); m_strFileUnknownCompression = TryGetEx(dictNew, "FileUnknownCompression", m_strFileUnknownCompression);
@@ -55,12 +58,18 @@ namespace KeePassLib.Resources
m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint); m_strKeePass1xHint = TryGetEx(dictNew, "KeePass1xHint", m_strKeePass1xHint);
m_strKeyBits = TryGetEx(dictNew, "KeyBits", m_strKeyBits); m_strKeyBits = TryGetEx(dictNew, "KeyBits", m_strKeyBits);
m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel); m_strKeyFileDbSel = TryGetEx(dictNew, "KeyFileDbSel", m_strKeyFileDbSel);
m_strKeyHashMismatch = TryGetEx(dictNew, "KeyHashMismatch", m_strKeyHashMismatch);
m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid); m_strMasterSeedLengthInvalid = TryGetEx(dictNew, "MasterSeedLengthInvalid", m_strMasterSeedLengthInvalid);
m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat); m_strOldFormat = TryGetEx(dictNew, "OldFormat", m_strOldFormat);
m_strPassive = TryGetEx(dictNew, "Passive", m_strPassive); m_strPassive = TryGetEx(dictNew, "Passive", m_strPassive);
m_strPathBackslash = TryGetEx(dictNew, "PathBackslash", m_strPathBackslash);
m_strPatternInvalid = TryGetEx(dictNew, "PatternInvalid", m_strPatternInvalid);
m_strPreAuth = TryGetEx(dictNew, "PreAuth", m_strPreAuth); m_strPreAuth = TryGetEx(dictNew, "PreAuth", m_strPreAuth);
m_strPwGenFailed = TryGetEx(dictNew, "PwGenFailed", m_strPwGenFailed);
m_strStructsTooDeep = TryGetEx(dictNew, "StructsTooDeep", m_strStructsTooDeep);
m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout); m_strTimeout = TryGetEx(dictNew, "Timeout", m_strTimeout);
m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs); m_strTryAgainSecs = TryGetEx(dictNew, "TryAgainSecs", m_strTryAgainSecs);
m_strUnknownError = TryGetEx(dictNew, "UnknownError", m_strUnknownError);
m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId); m_strUnknownHeaderId = TryGetEx(dictNew, "UnknownHeaderId", m_strUnknownHeaderId);
m_strUnknownKdf = TryGetEx(dictNew, "UnknownKdf", m_strUnknownKdf); m_strUnknownKdf = TryGetEx(dictNew, "UnknownKdf", m_strUnknownKdf);
m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError); m_strUserAccountKeyError = TryGetEx(dictNew, "UserAccountKeyError", m_strUserAccountKeyError);
@@ -68,6 +77,9 @@ namespace KeePassLib.Resources
} }
private static readonly string[] m_vKeyNames = { private static readonly string[] m_vKeyNames = {
"AlgorithmUnknown",
"CharSetInvalid",
"CharSetTooFewChars",
"CryptoStreamFailed", "CryptoStreamFailed",
"EncDataTooLarge", "EncDataTooLarge",
"ErrorInClipboard", "ErrorInClipboard",
@@ -83,7 +95,7 @@ namespace KeePassLib.Resources
"FileNewVerOrPlgReq", "FileNewVerOrPlgReq",
"FileNewVerReq", "FileNewVerReq",
"FileSaveCorruptionWarning", "FileSaveCorruptionWarning",
"FileSaveFailed", "FileSaveFailed2",
"FileSigInvalid", "FileSigInvalid",
"FileUnknownCipher", "FileUnknownCipher",
"FileUnknownCompression", "FileUnknownCompression",
@@ -97,12 +109,18 @@ namespace KeePassLib.Resources
"KeePass1xHint", "KeePass1xHint",
"KeyBits", "KeyBits",
"KeyFileDbSel", "KeyFileDbSel",
"KeyHashMismatch",
"MasterSeedLengthInvalid", "MasterSeedLengthInvalid",
"OldFormat", "OldFormat",
"Passive", "Passive",
"PathBackslash",
"PatternInvalid",
"PreAuth", "PreAuth",
"PwGenFailed",
"StructsTooDeep",
"Timeout", "Timeout",
"TryAgainSecs", "TryAgainSecs",
"UnknownError",
"UnknownHeaderId", "UnknownHeaderId",
"UnknownKdf", "UnknownKdf",
"UserAccountKeyError", "UserAccountKeyError",
@@ -114,6 +132,39 @@ namespace KeePassLib.Resources
return m_vKeyNames; return m_vKeyNames;
} }
private static string m_strAlgorithmUnknown =
@"The algorithm is unknown.";
/// <summary>
/// Look up a localized string similar to
/// 'The algorithm is unknown.'.
/// </summary>
public static string AlgorithmUnknown
{
get { return m_strAlgorithmUnknown; }
}
private static string m_strCharSetInvalid =
@"The character set is invalid.";
/// <summary>
/// Look up a localized string similar to
/// 'The character set is invalid.'.
/// </summary>
public static string CharSetInvalid
{
get { return m_strCharSetInvalid; }
}
private static string m_strCharSetTooFewChars =
@"There are too few characters in the character set.";
/// <summary>
/// Look up a localized string similar to
/// 'There are too few characters in the character set.'.
/// </summary>
public static string CharSetTooFewChars
{
get { return m_strCharSetTooFewChars; }
}
private static string m_strCryptoStreamFailed = private static string m_strCryptoStreamFailed =
@"Failed to initialize encryption/decryption stream!"; @"Failed to initialize encryption/decryption stream!";
/// <summary> /// <summary>
@@ -279,15 +330,15 @@ namespace KeePassLib.Resources
get { return m_strFileSaveCorruptionWarning; } get { return m_strFileSaveCorruptionWarning; }
} }
private static string m_strFileSaveFailed = private static string m_strFileSaveFailed2 =
@"Failed to save the current database to the specified location!"; @"Failed to save to the specified file!";
/// <summary> /// <summary>
/// Look up a localized string similar to /// Look up a localized string similar to
/// 'Failed to save the current database to the specified location!'. /// 'Failed to save to the specified file!'.
/// </summary> /// </summary>
public static string FileSaveFailed public static string FileSaveFailed2
{ {
get { return m_strFileSaveFailed; } get { return m_strFileSaveFailed2; }
} }
private static string m_strFileSigInvalid = private static string m_strFileSigInvalid =
@@ -346,10 +397,10 @@ namespace KeePassLib.Resources
} }
private static string m_strFrameworkNotImplExcp = private static string m_strFrameworkNotImplExcp =
@"The .NET framework/runtime under which KeePass is currently running does not support this operation."; @"The .NET Framework/runtime under which KeePass is currently running does not support this operation.";
/// <summary> /// <summary>
/// Look up a localized string similar to /// Look up a localized string similar to
/// 'The .NET framework/runtime under which KeePass is currently running does not support this operation.'. /// 'The .NET Framework/runtime under which KeePass is currently running does not support this operation.'.
/// </summary> /// </summary>
public static string FrameworkNotImplExcp public static string FrameworkNotImplExcp
{ {
@@ -368,10 +419,10 @@ namespace KeePassLib.Resources
} }
private static string m_strInvalidCompositeKey = private static string m_strInvalidCompositeKey =
@"The composite key is invalid!"; @"The master key is invalid!";
/// <summary> /// <summary>
/// Look up a localized string similar to /// Look up a localized string similar to
/// 'The composite key is invalid!'. /// 'The master key is invalid!'.
/// </summary> /// </summary>
public static string InvalidCompositeKey public static string InvalidCompositeKey
{ {
@@ -379,10 +430,10 @@ namespace KeePassLib.Resources
} }
private static string m_strInvalidCompositeKeyHint = private static string m_strInvalidCompositeKeyHint =
@"Make sure the composite key is correct and try again."; @"Make sure that the master key is correct and try it again.";
/// <summary> /// <summary>
/// Look up a localized string similar to /// Look up a localized string similar to
/// 'Make sure the composite key is correct and try again.'. /// 'Make sure that the master key is correct and try it again.'.
/// </summary> /// </summary>
public static string InvalidCompositeKeyHint public static string InvalidCompositeKeyHint
{ {
@@ -433,6 +484,17 @@ namespace KeePassLib.Resources
get { return m_strKeyFileDbSel; } get { return m_strKeyFileDbSel; }
} }
private static string m_strKeyHashMismatch =
@"The key and the hash do not match, i.e. the key or the hash is invalid.";
/// <summary>
/// Look up a localized string similar to
/// 'The key and the hash do not match, i.e. the key or the hash is invalid.'.
/// </summary>
public static string KeyHashMismatch
{
get { return m_strKeyHashMismatch; }
}
private static string m_strMasterSeedLengthInvalid = private static string m_strMasterSeedLengthInvalid =
@"The length of the master key seed is invalid!"; @"The length of the master key seed is invalid!";
/// <summary> /// <summary>
@@ -466,6 +528,28 @@ namespace KeePassLib.Resources
get { return m_strPassive; } get { return m_strPassive; }
} }
private static string m_strPathBackslash =
@"The path contains a backslash. Such paths are not supported (for security reasons).";
/// <summary>
/// Look up a localized string similar to
/// 'The path contains a backslash. Such paths are not supported (for security reasons).'.
/// </summary>
public static string PathBackslash
{
get { return m_strPathBackslash; }
}
private static string m_strPatternInvalid =
@"The pattern is invalid.";
/// <summary>
/// Look up a localized string similar to
/// 'The pattern is invalid.'.
/// </summary>
public static string PatternInvalid
{
get { return m_strPatternInvalid; }
}
private static string m_strPreAuth = private static string m_strPreAuth =
@"Pre-authenticate"; @"Pre-authenticate";
/// <summary> /// <summary>
@@ -477,6 +561,28 @@ namespace KeePassLib.Resources
get { return m_strPreAuth; } get { return m_strPreAuth; }
} }
private static string m_strPwGenFailed =
@"Failed to generate a password.";
/// <summary>
/// Look up a localized string similar to
/// 'Failed to generate a password.'.
/// </summary>
public static string PwGenFailed
{
get { return m_strPwGenFailed; }
}
private static string m_strStructsTooDeep =
@"Structures are nested too deeply.";
/// <summary>
/// Look up a localized string similar to
/// 'Structures are nested too deeply.'.
/// </summary>
public static string StructsTooDeep
{
get { return m_strStructsTooDeep; }
}
private static string m_strTimeout = private static string m_strTimeout =
@"Timeout"; @"Timeout";
/// <summary> /// <summary>
@@ -499,6 +605,17 @@ namespace KeePassLib.Resources
get { return m_strTryAgainSecs; } get { return m_strTryAgainSecs; }
} }
private static string m_strUnknownError =
@"An unknown error occurred.";
/// <summary>
/// Look up a localized string similar to
/// 'An unknown error occurred.'.
/// </summary>
public static string UnknownError
{
get { return m_strUnknownError; }
}
private static string m_strUnknownHeaderId = private static string m_strUnknownHeaderId =
@"Unknown header ID!"; @"Unknown header ID!";
/// <summary> /// <summary>

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -33,11 +33,11 @@ using KeePassLibSD;
namespace KeePassLib.Security namespace KeePassLib.Security
{ {
/// <summary> /// <summary>
/// Represents an in-memory encrypted string. /// A string that is protected in process memory.
/// <c>ProtectedString</c> objects are immutable and thread-safe. /// <c>ProtectedString</c> objects are immutable and thread-safe.
/// </summary> /// </summary>
#if (DEBUG && !KeePassLibSD) #if (DEBUG && !KeePassLibSD)
[DebuggerDisplay(@"{ReadString()}")] [DebuggerDisplay("{ReadString()}")]
#endif #endif
public sealed class ProtectedString public sealed class ProtectedString
{ {
@@ -48,11 +48,24 @@ namespace KeePassLib.Security
private bool m_bIsProtected; private bool m_bIsProtected;
private static readonly ProtectedString m_psEmpty = new ProtectedString(); private static readonly ProtectedString m_psEmpty = new ProtectedString();
/// <summary>
/// Get an empty <c>ProtectedString</c> object, without protection.
/// </summary>
public static ProtectedString Empty public static ProtectedString Empty
{ {
get { return m_psEmpty; } get { return m_psEmpty; }
} }
private static readonly ProtectedString m_psEmptyEx = new ProtectedString(
true, new byte[0]);
/// <summary>
/// Get an empty <c>ProtectedString</c> object, with protection turned on.
/// </summary>
public static ProtectedString EmptyEx
{
get { return m_psEmptyEx; }
}
/// <summary> /// <summary>
/// A flag specifying whether the <c>ProtectedString</c> object /// A flag specifying whether the <c>ProtectedString</c> object
/// has turned on memory protection or not. /// has turned on memory protection or not.
@@ -66,8 +79,8 @@ namespace KeePassLib.Security
{ {
get get
{ {
ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
if(pBin != null) return (pBin.Length == 0); if (p != null) return (p.Length == 0);
Debug.Assert(m_strPlainText != null); Debug.Assert(m_strPlainText != null);
return (m_strPlainText.Length == 0); return (m_strPlainText.Length == 0);
@@ -75,18 +88,21 @@ namespace KeePassLib.Security
} }
private int m_nCachedLength = -1; private int m_nCachedLength = -1;
/// <summary>
/// Length of the protected string, in characters.
/// </summary>
public int Length public int Length
{ {
get get
{ {
if(m_nCachedLength >= 0) return m_nCachedLength; if (m_nCachedLength >= 0) return m_nCachedLength;
ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
if(pBin != null) if (p != null)
{ {
byte[] pbPlain = pBin.ReadData(); byte[] pbPlain = p.ReadData();
m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); try { m_nCachedLength = StrUtil.Utf8.GetCharCount(pbPlain); }
MemUtil.ZeroByteArray(pbPlain); finally { MemUtil.ZeroByteArray(pbPlain); }
} }
else else
{ {
@@ -140,53 +156,51 @@ namespace KeePassLib.Security
/// to the value passed in the <c>XorredBuffer</c> object. /// to the value passed in the <c>XorredBuffer</c> object.
/// </summary> /// </summary>
/// <param name="bEnableProtection">Enable protection or not.</param> /// <param name="bEnableProtection">Enable protection or not.</param>
/// <param name="xbProtected"><c>XorredBuffer</c> object containing the /// <param name="xb"><c>XorredBuffer</c> object containing the
/// string in UTF-8 representation. The UTF-8 string must not /// string in UTF-8 representation. The UTF-8 string must not
/// be <c>null</c>-terminated.</param> /// be <c>null</c>-terminated.</param>
public ProtectedString(bool bEnableProtection, XorredBuffer xbProtected) public ProtectedString(bool bEnableProtection, XorredBuffer xb)
{ {
Debug.Assert(xbProtected != null); if (xb == null) { Debug.Assert(false); throw new ArgumentNullException("xb"); }
if(xbProtected == null) throw new ArgumentNullException("xbProtected");
byte[] pb = xbProtected.ReadPlainText(); byte[] pb = xb.ReadPlainText();
Init(bEnableProtection, pb); try { Init(bEnableProtection, pb); }
finally { if (bEnableProtection) MemUtil.ZeroByteArray(pb); }
if(bEnableProtection) MemUtil.ZeroByteArray(pb);
} }
private void Init(bool bEnableProtection, string str) private void Init(bool bEnableProtection, string str)
{ {
if(str == null) throw new ArgumentNullException("str"); if (str == null) throw new ArgumentNullException("str");
m_bIsProtected = bEnableProtection; m_bIsProtected = bEnableProtection;
// The string already is in memory and immutable, // As the string already is in memory and immutable,
// protection would be useless // protection would be useless
m_strPlainText = str; m_strPlainText = str;
} }
private void Init(bool bEnableProtection, byte[] pbUtf8) private void Init(bool bEnableProtection, byte[] pbUtf8)
{ {
if(pbUtf8 == null) throw new ArgumentNullException("pbUtf8"); if (pbUtf8 == null) throw new ArgumentNullException("pbUtf8");
m_bIsProtected = bEnableProtection; m_bIsProtected = bEnableProtection;
if(bEnableProtection) if (bEnableProtection)
m_pbUtf8 = new ProtectedBinary(true, pbUtf8); m_pbUtf8 = new ProtectedBinary(true, pbUtf8);
else else
m_strPlainText = StrUtil.Utf8.GetString(pbUtf8, 0, pbUtf8.Length); m_strPlainText = StrUtil.Utf8.GetString(pbUtf8, 0, pbUtf8.Length);
} }
/// <summary> /// <summary>
/// Convert the protected string to a normal string object. /// Convert the protected string to a standard string object.
/// Be careful with this function, the returned string object /// Be careful with this function, as the returned string object
/// isn't protected anymore and stored in plain-text in the /// isn't protected anymore and stored in plain-text in the
/// process memory. /// process memory.
/// </summary> /// </summary>
/// <returns>Plain-text string. Is never <c>null</c>.</returns> /// <returns>Plain-text string. Is never <c>null</c>.</returns>
public string ReadString() public string ReadString()
{ {
if(m_strPlainText != null) return m_strPlainText; if (m_strPlainText != null) return m_strPlainText;
byte[] pb = ReadUtf8(); byte[] pb = ReadUtf8();
string str = ((pb.Length == 0) ? string.Empty : string str = ((pb.Length == 0) ? string.Empty :
@@ -194,82 +208,120 @@ namespace KeePassLib.Security
// No need to clear pb // No need to clear pb
// As the text is now visible in process memory anyway, // As the text is now visible in process memory anyway,
// there's no need to protect it anymore // there's no need to protect it anymore (strings are
// immutable and thus cannot be overwritten)
m_strPlainText = str; m_strPlainText = str;
m_pbUtf8 = null; // Thread-safe order m_pbUtf8 = null; // Thread-safe order
return str; return str;
} }
/// <summary>
/// Read out the string and return it as a char array.
/// The returned array is not protected and should be cleared by
/// the caller.
/// </summary>
/// <returns>Plain-text char array.</returns>
public char[] ReadChars()
{
if (m_strPlainText != null) return m_strPlainText.ToCharArray();
byte[] pb = ReadUtf8();
char[] v;
try { v = StrUtil.Utf8.GetChars(pb); }
finally { MemUtil.ZeroByteArray(pb); }
return v;
}
/// <summary> /// <summary>
/// Read out the string and return a byte array that contains the /// Read out the string and return a byte array that contains the
/// string encoded using UTF-8. The returned string is not protected /// string encoded using UTF-8.
/// anymore! /// The returned array is not protected and should be cleared by
/// the caller.
/// </summary> /// </summary>
/// <returns>Plain-text UTF-8 byte array.</returns> /// <returns>Plain-text UTF-8 byte array.</returns>
public byte[] ReadUtf8() public byte[] ReadUtf8()
{ {
ProtectedBinary pBin = m_pbUtf8; // Local ref for thread-safety ProtectedBinary p = m_pbUtf8; // Local ref for thread-safety
if(pBin != null) return pBin.ReadData(); if (p != null) return p.ReadData();
return StrUtil.Utf8.GetBytes(m_strPlainText); return StrUtil.Utf8.GetBytes(m_strPlainText);
} }
/// <summary> /// <summary>
/// Read the protected string and return it protected with a sequence /// Get the string as an UTF-8 sequence xorred with bytes
/// of bytes generated by a random stream. /// from a <c>CryptoRandomStream</c>.
/// </summary> /// </summary>
/// <param name="crsRandomSource">Random number source.</param>
/// <returns>Protected string.</returns>
public byte[] ReadXorredString(CryptoRandomStream crsRandomSource) public byte[] ReadXorredString(CryptoRandomStream crsRandomSource)
{ {
Debug.Assert(crsRandomSource != null); if(crsRandomSource == null) throw new ArgumentNullException("crsRandomSource"); if (crsRandomSource == null) { Debug.Assert(false); throw new ArgumentNullException("crsRandomSource"); }
byte[] pbData = ReadUtf8(); byte[] pbData = ReadUtf8();
uint uLen = (uint)pbData.Length; int cb = pbData.Length;
byte[] randomPad = crsRandomSource.GetRandomBytes(uLen); byte[] pbPad = crsRandomSource.GetRandomBytes((uint)cb);
Debug.Assert(randomPad.Length == pbData.Length); Debug.Assert(pbPad.Length == cb);
for(uint i = 0; i < uLen; ++i) for (int i = 0; i < cb; ++i)
pbData[i] ^= randomPad[i]; pbData[i] ^= pbPad[i];
MemUtil.ZeroByteArray(pbPad);
return pbData; return pbData;
} }
public ProtectedString WithProtection(bool bProtect) public ProtectedString WithProtection(bool bProtect)
{ {
if(bProtect == m_bIsProtected) return this; if (bProtect == m_bIsProtected) return this;
byte[] pb = ReadUtf8(); byte[] pb = ReadUtf8();
ProtectedString ps = new ProtectedString(bProtect, pb);
if(bProtect) MemUtil.ZeroByteArray(pb); // No need to clear pb; either the current or the new object is unprotected
return ps; return new ProtectedString(bProtect, pb);
}
public bool Equals(ProtectedString ps, bool bCheckProtEqual)
{
if (ps == null) throw new ArgumentNullException("ps");
if (object.ReferenceEquals(this, ps)) return true; // Perf. opt.
bool bPA = m_bIsProtected, bPB = ps.m_bIsProtected;
if (bCheckProtEqual && (bPA != bPB)) return false;
if (!bPA && !bPB) return (ReadString() == ps.ReadString());
byte[] pbA = ReadUtf8(), pbB = null;
bool bEq;
try
{
pbB = ps.ReadUtf8();
bEq = MemUtil.ArraysEqual(pbA, pbB);
}
finally
{
if (bPA) MemUtil.ZeroByteArray(pbA);
if (bPB && (pbB != null)) MemUtil.ZeroByteArray(pbB);
}
return bEq;
} }
public ProtectedString Insert(int iStart, string strInsert) public ProtectedString Insert(int iStart, string strInsert)
{ {
if(iStart < 0) throw new ArgumentOutOfRangeException("iStart"); if (iStart < 0) throw new ArgumentOutOfRangeException("iStart");
if(strInsert == null) throw new ArgumentNullException("strInsert"); if (strInsert == null) throw new ArgumentNullException("strInsert");
if(strInsert.Length == 0) return this; if (strInsert.Length == 0) return this;
// Only operate directly with strings when m_bIsProtected is if (!m_bIsProtected)
// false, not in the case of non-null m_strPlainText, because
// the operation creates a new sequence in memory
if(!m_bIsProtected)
return new ProtectedString(false, ReadString().Insert( return new ProtectedString(false, ReadString().Insert(
iStart, strInsert)); iStart, strInsert));
UTF8Encoding utf8 = StrUtil.Utf8; UTF8Encoding utf8 = StrUtil.Utf8;
char[] v = ReadChars(), vNew = null;
byte[] pb = ReadUtf8(); byte[] pbNew = null;
char[] v = utf8.GetChars(pb); ProtectedString ps;
char[] vNew;
try try
{ {
if(iStart > v.Length) if (iStart > v.Length)
throw new ArgumentOutOfRangeException("iStart"); throw new ArgumentOutOfRangeException("iStart");
char[] vIns = strInsert.ToCharArray(); char[] vIns = strInsert.ToCharArray();
@@ -279,68 +331,104 @@ namespace KeePassLib.Security
Array.Copy(vIns, 0, vNew, iStart, vIns.Length); Array.Copy(vIns, 0, vNew, iStart, vIns.Length);
Array.Copy(v, iStart, vNew, iStart + vIns.Length, Array.Copy(v, iStart, vNew, iStart + vIns.Length,
v.Length - iStart); v.Length - iStart);
pbNew = utf8.GetBytes(vNew);
ps = new ProtectedString(true, pbNew);
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
ReadString().Insert(iStart, strInsert));
} }
finally finally
{ {
MemUtil.ZeroArray<char>(v); MemUtil.ZeroArray<char>(v);
MemUtil.ZeroByteArray(pb); if (vNew != null) MemUtil.ZeroArray<char>(vNew);
if (pbNew != null) MemUtil.ZeroByteArray(pbNew);
} }
byte[] pbNew = utf8.GetBytes(vNew);
ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew);
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
ReadString().Insert(iStart, strInsert));
MemUtil.ZeroArray<char>(vNew);
MemUtil.ZeroByteArray(pbNew);
return ps; return ps;
} }
public ProtectedString Remove(int iStart, int nCount) public ProtectedString Remove(int iStart, int nCount)
{ {
if(iStart < 0) throw new ArgumentOutOfRangeException("iStart"); if (iStart < 0) throw new ArgumentOutOfRangeException("iStart");
if(nCount < 0) throw new ArgumentOutOfRangeException("nCount"); if (nCount < 0) throw new ArgumentOutOfRangeException("nCount");
if(nCount == 0) return this; if (nCount == 0) return this;
// Only operate directly with strings when m_bIsProtected is if (!m_bIsProtected)
// false, not in the case of non-null m_strPlainText, because
// the operation creates a new sequence in memory
if(!m_bIsProtected)
return new ProtectedString(false, ReadString().Remove( return new ProtectedString(false, ReadString().Remove(
iStart, nCount)); iStart, nCount));
UTF8Encoding utf8 = StrUtil.Utf8; UTF8Encoding utf8 = StrUtil.Utf8;
char[] v = ReadChars(), vNew = null;
byte[] pb = ReadUtf8(); byte[] pbNew = null;
char[] v = utf8.GetChars(pb); ProtectedString ps;
char[] vNew;
try try
{ {
if((iStart + nCount) > v.Length) if ((iStart + nCount) > v.Length)
throw new ArgumentException("iStart + nCount"); throw new ArgumentException("(iStart + nCount) > v.Length");
vNew = new char[v.Length - nCount]; vNew = new char[v.Length - nCount];
Array.Copy(v, 0, vNew, 0, iStart); Array.Copy(v, 0, vNew, 0, iStart);
Array.Copy(v, iStart + nCount, vNew, iStart, v.Length - Array.Copy(v, iStart + nCount, vNew, iStart, v.Length -
(iStart + nCount)); (iStart + nCount));
pbNew = utf8.GetBytes(vNew);
ps = new ProtectedString(true, pbNew);
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
ReadString().Remove(iStart, nCount));
} }
finally finally
{ {
MemUtil.ZeroArray<char>(v); MemUtil.ZeroArray<char>(v);
MemUtil.ZeroByteArray(pb); if (vNew != null) MemUtil.ZeroArray<char>(vNew);
if (pbNew != null) MemUtil.ZeroByteArray(pbNew);
} }
byte[] pbNew = utf8.GetBytes(vNew);
ProtectedString ps = new ProtectedString(m_bIsProtected, pbNew);
Debug.Assert(utf8.GetString(pbNew, 0, pbNew.Length) ==
ReadString().Remove(iStart, nCount));
MemUtil.ZeroArray<char>(vNew);
MemUtil.ZeroByteArray(pbNew);
return ps; return ps;
} }
public static ProtectedString operator +(ProtectedString a, ProtectedString b)
{
if (a == null) throw new ArgumentNullException("a");
if (b == null) throw new ArgumentNullException("b");
if (b.IsEmpty) return a.WithProtection(a.IsProtected || b.IsProtected);
if (a.IsEmpty) return b.WithProtection(a.IsProtected || b.IsProtected);
if (!a.IsProtected && !b.IsProtected)
return new ProtectedString(false, a.ReadString() + b.ReadString());
char[] vA = a.ReadChars(), vB = null, vNew = null;
byte[] pbNew = null;
ProtectedString ps;
try
{
vB = b.ReadChars();
vNew = new char[vA.Length + vB.Length];
Array.Copy(vA, vNew, vA.Length);
Array.Copy(vB, 0, vNew, vA.Length, vB.Length);
pbNew = StrUtil.Utf8.GetBytes(vNew);
ps = new ProtectedString(true, pbNew);
}
finally
{
MemUtil.ZeroArray<char>(vA);
if (vB != null) MemUtil.ZeroArray<char>(vB);
if (vNew != null) MemUtil.ZeroArray<char>(vNew);
if (pbNew != null) MemUtil.ZeroByteArray(pbNew);
}
return ps;
}
public static ProtectedString operator +(ProtectedString a, string b)
{
ProtectedString psB = new ProtectedString(false, b);
return (a + psB);
}
} }
} }

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -20,97 +20,90 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using KeePassLib.Utility;
namespace KeePassLib.Security namespace KeePassLib.Security
{ {
/// <summary> /// <summary>
/// Represents an object that is encrypted using a XOR pad until /// A <c>XorredBuffer</c> object stores data that is encrypted
/// it is read. <c>XorredBuffer</c> objects are immutable and /// using a XOR pad.
/// thread-safe.
/// </summary> /// </summary>
public sealed class XorredBuffer public sealed class XorredBuffer : IDisposable
{ {
private byte[] m_pbData; // Never null private byte[] m_pbCT;
private byte[] m_pbXorPad; // Always valid for m_pbData private byte[] m_pbXorPad;
/// <summary>
/// Length of the protected data in bytes.
/// </summary>
public uint Length public uint Length
{ {
get { return (uint)m_pbData.Length; } get
{
if (m_pbCT == null) { Debug.Assert(false); throw new ObjectDisposedException(null); }
return (uint)m_pbCT.Length;
}
} }
/// <summary> /// <summary>
/// Construct a new XOR-protected object using a protected byte array /// Construct a new <c>XorredBuffer</c> object.
/// and a XOR pad that decrypts the protected data. The /// The <paramref name="pbCT" /> byte array must have the same
/// <paramref name="pbProtectedData" /> byte array must have the same size /// length as the <paramref name="pbXorPad" /> byte array.
/// as the <paramref name="pbXorPad" /> byte array.
/// The <c>XorredBuffer</c> object takes ownership of the two byte /// The <c>XorredBuffer</c> object takes ownership of the two byte
/// arrays, i.e. the caller must not use or modify them afterwards. /// arrays, i.e. the caller must not use them afterwards.
/// </summary> /// </summary>
/// <param name="pbProtectedData">Protected data (XOR pad applied).</param> /// <param name="pbCT">Data with XOR pad applied.</param>
/// <param name="pbXorPad">XOR pad that can be used to decrypt the /// <param name="pbXorPad">XOR pad that can be used to decrypt the
/// <paramref name="pbProtectedData" /> parameter.</param> /// <paramref name="pbCT" /> byte array.</param>
/// <exception cref="System.ArgumentNullException">Thrown if one of the input public XorredBuffer(byte[] pbCT, byte[] pbXorPad)
/// parameters is <c>null</c>.</exception>
/// <exception cref="System.ArgumentException">Thrown if the byte arrays are
/// of different size.</exception>
public XorredBuffer(byte[] pbProtectedData, byte[] pbXorPad)
{ {
if(pbProtectedData == null) { Debug.Assert(false); throw new ArgumentNullException("pbProtectedData"); } if (pbCT == null) { Debug.Assert(false); throw new ArgumentNullException("pbCT"); }
if(pbXorPad == null) { Debug.Assert(false); throw new ArgumentNullException("pbXorPad"); } if (pbXorPad == null) { Debug.Assert(false); throw new ArgumentNullException("pbXorPad"); }
if (pbCT.Length != pbXorPad.Length)
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("pbXorPad");
}
Debug.Assert(pbProtectedData.Length == pbXorPad.Length); m_pbCT = pbCT;
if(pbProtectedData.Length != pbXorPad.Length) throw new ArgumentException();
m_pbData = pbProtectedData;
m_pbXorPad = pbXorPad; m_pbXorPad = pbXorPad;
} }
#if DEBUG
~XorredBuffer()
{
Debug.Assert((m_pbCT == null) && (m_pbXorPad == null));
}
#endif
public void Dispose()
{
if (m_pbCT == null) return;
MemUtil.ZeroByteArray(m_pbCT);
m_pbCT = null;
MemUtil.ZeroByteArray(m_pbXorPad);
m_pbXorPad = null;
}
/// <summary> /// <summary>
/// Get a copy of the plain-text. The caller is responsible /// Get a copy of the plain-text. The caller is responsible
/// for clearing the byte array safely after using it. /// for clearing the byte array safely after using it.
/// </summary> /// </summary>
/// <returns>Unprotected plain-text byte array.</returns> /// <returns>Plain-text byte array.</returns>
public byte[] ReadPlainText() public byte[] ReadPlainText()
{ {
byte[] pbPlain = new byte[m_pbData.Length]; byte[] pbCT = m_pbCT, pbX = m_pbXorPad;
if ((pbCT == null) || (pbX == null) || (pbCT.Length != pbX.Length))
for(int i = 0; i < pbPlain.Length; ++i)
pbPlain[i] = (byte)(m_pbData[i] ^ m_pbXorPad[i]);
return pbPlain;
}
/* public bool EqualsValue(XorredBuffer xb)
{
if(xb == null) { Debug.Assert(false); throw new ArgumentNullException("xb"); }
if(xb.m_pbData.Length != m_pbData.Length) return false;
for(int i = 0; i < m_pbData.Length; ++i)
{ {
byte bt1 = (byte)(m_pbData[i] ^ m_pbXorPad[i]); Debug.Assert(false);
byte bt2 = (byte)(xb.m_pbData[i] ^ xb.m_pbXorPad[i]); throw new ObjectDisposedException(null);
if(bt1 != bt2) return false;
} }
return true; byte[] pbPT = new byte[pbCT.Length];
for (int i = 0; i < pbPT.Length; ++i)
pbPT[i] = (byte)(pbCT[i] ^ pbX[i]);
return pbPT;
} }
public bool EqualsValue(byte[] pb)
{
if(pb == null) { Debug.Assert(false); throw new ArgumentNullException("pb"); }
if(pb.Length != m_pbData.Length) return false;
for(int i = 0; i < m_pbData.Length; ++i)
{
if((byte)(m_pbData[i] ^ m_pbXorPad[i]) != pb[i]) return false;
}
return true;
} */
} }
} }

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -78,9 +78,9 @@ namespace KeePassLib.Serialization
public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger) public void Load(Stream sSource, KdbxFormat fmt, IStatusLogger slLogger)
{ {
Debug.Assert(sSource != null); Debug.Assert(sSource != null);
if(sSource == null) throw new ArgumentNullException("sSource"); if (sSource == null) throw new ArgumentNullException("sSource");
if(m_bUsedOnce) if (m_bUsedOnce)
throw new InvalidOperationException("Do not reuse KdbxFile objects!"); throw new InvalidOperationException("Do not reuse KdbxFile objects!");
m_bUsedOnce = true; m_bUsedOnce = true;
@@ -91,7 +91,8 @@ namespace KeePassLib.Serialization
m_format = fmt; m_format = fmt;
m_slLogger = slLogger; m_slLogger = slLogger;
m_pbsBinaries.Clear(); // Other applications might not perform a deduplication
m_pbsBinaries = new ProtectedBinarySet(false);
UTF8Encoding encNoBom = StrUtil.Utf8; UTF8Encoding encNoBom = StrUtil.Utf8;
byte[] pbCipherKey = null; byte[] pbCipherKey = null;
@@ -103,153 +104,156 @@ namespace KeePassLib.Serialization
HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null); HashingStreamEx sHashing = new HashingStreamEx(sSource, false, null);
lStreams.Add(sHashing); lStreams.Add(sHashing);
try try
{ {
Stream sXml; Stream sXml;
if (fmt == KdbxFormat.Default || fmt == KdbxFormat.ProtocolBuffers) if (fmt == KdbxFormat.Default || fmt == KdbxFormat.ProtocolBuffers)
{ {
BinaryReaderEx br = new BinaryReaderEx(sHashing, BinaryReaderEx br = new BinaryReaderEx(sHashing,
encNoBom, KLRes.FileCorrupted); encNoBom, KLRes.FileCorrupted);
byte[] pbHeader = LoadHeader(br); byte[] pbHeader = LoadHeader(br);
m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader); m_pbHashOfHeader = CryptoUtil.HashSha256(pbHeader);
int cbEncKey, cbEncIV; int cbEncKey, cbEncIV;
ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV); ICipherEngine iCipher = GetCipher(out cbEncKey, out cbEncIV);
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_TransformingKey", LogStatusType.AdditionalInfo);
ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
string strIncomplete = KLRes.FileHeaderCorrupted + " " + if (m_slLogger != null)
KLRes.FileIncomplete; m_slLogger.SetText("KP2AKEY_TransformingKey", LogStatusType.AdditionalInfo);
Stream sPlain; ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
if(m_uFileVersion < FileVersion32_4)
{
Stream sDecrypted = EncryptStream(sHashing, iCipher,
pbCipherKey, cbEncIV, false);
if((sDecrypted == null) || (sDecrypted == sHashing))
throw new SecurityException(KLRes.CryptoStreamFailed);
if (m_slLogger != null) string strIncomplete = KLRes.FileHeaderCorrupted + " " +
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo); KLRes.FileIncomplete;
lStreams.Add(sDecrypted); Stream sPlain;
if (m_uFileVersion < FileVersion32_4)
{
Stream sDecrypted = EncryptStream(sHashing, iCipher,
pbCipherKey, cbEncIV, false);
if ((sDecrypted == null) || (sDecrypted == sHashing))
throw new SecurityException(KLRes.CryptoStreamFailed);
BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted, if (m_slLogger != null)
encNoBom, strIncomplete); m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32);
if((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32)) lStreams.Add(sDecrypted);
throw new EndOfStreamException(strIncomplete);
if(!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes))
throw new InvalidCompositeKeyException();
if (m_slLogger != null) BinaryReaderEx brDecrypted = new BinaryReaderEx(sDecrypted,
m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo); encNoBom, strIncomplete);
byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32);
if ((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.Length != 32))
throw new EndOfStreamException(strIncomplete);
if (!MemUtil.ArraysEqual(pbStoredStartBytes, m_pbStreamStartBytes))
throw new InvalidCompositeKeyException();
sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode); if (m_slLogger != null)
} m_slLogger.SetText("KP2AKEY_DecodingDatabase", LogStatusType.AdditionalInfo);
else // KDBX >= 4
{
byte[] pbStoredHash = MemUtil.Read(sHashing, 32);
if((pbStoredHash == null) || (pbStoredHash.Length != 32))
throw new EndOfStreamException(strIncomplete);
if(!MemUtil.ArraysEqual(m_pbHashOfHeader, pbStoredHash))
throw new InvalidDataException(KLRes.FileHeaderCorrupted);
byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64); sPlain = new HashedBlockStream(sDecrypted, false, 0, !m_bRepairMode);
byte[] pbStoredHmac = MemUtil.Read(sHashing, 32); }
if((pbStoredHmac == null) || (pbStoredHmac.Length != 32)) else // KDBX >= 4
throw new EndOfStreamException(strIncomplete); {
if(!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac)) byte[] pbStoredHash = MemUtil.Read(sHashing, 32);
throw new InvalidCompositeKeyException(); if ((pbStoredHash == null) || (pbStoredHash.Length != 32))
throw new EndOfStreamException(strIncomplete);
if (!MemUtil.ArraysEqual(m_pbHashOfHeader, pbStoredHash))
throw new InvalidDataException(KLRes.FileHeaderCorrupted);
HmacBlockStream sBlocks = new HmacBlockStream(sHashing, byte[] pbHeaderHmac = ComputeHeaderHmac(pbHeader, pbHmacKey64);
false, !m_bRepairMode, pbHmacKey64); byte[] pbStoredHmac = MemUtil.Read(sHashing, 32);
lStreams.Add(sBlocks); if ((pbStoredHmac == null) || (pbStoredHmac.Length != 32))
throw new EndOfStreamException(strIncomplete);
if (!MemUtil.ArraysEqual(pbHeaderHmac, pbStoredHmac))
throw new InvalidCompositeKeyException();
sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, HmacBlockStream sBlocks = new HmacBlockStream(sHashing,
cbEncIV, false); false, !m_bRepairMode, pbHmacKey64);
if((sPlain == null) || (sPlain == sBlocks)) lStreams.Add(sBlocks);
throw new SecurityException(KLRes.CryptoStreamFailed);
}
lStreams.Add(sPlain);
if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
{ cbEncIV, false);
sXml = new GZipStream(sPlain, CompressionMode.Decompress); if ((sPlain == null) || (sPlain == sBlocks))
lStreams.Add(sXml); throw new SecurityException(KLRes.CryptoStreamFailed);
} }
else sXml = sPlain;
if(m_uFileVersion >= FileVersion32_4) lStreams.Add(sPlain);
LoadInnerHeader(sXml); // Binary header before XML
}
else if(fmt == KdbxFormat.PlainXml)
sXml = sHashing;
else { Debug.Assert(false); throw new ArgumentOutOfRangeException("fmt"); }
if(fmt == KdbxFormat.Default) if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
{ {
if(m_pbInnerRandomStreamKey == null) sXml = new GZipStream(sPlain, CompressionMode.Decompress);
{ lStreams.Add(sXml);
Debug.Assert(false); }
throw new SecurityException("Invalid inner random stream key!"); else sXml = sPlain;
}
if (m_uFileVersion >= FileVersion32_4)
LoadInnerHeader(sXml); // Binary header before XML
}
else if (fmt == KdbxFormat.PlainXml)
sXml = sHashing;
else
{
Debug.Assert(false);
throw new ArgumentOutOfRangeException("fmt");
}
if (fmt == KdbxFormat.Default)
{
if (m_pbInnerRandomStreamKey == null)
{
Debug.Assert(false);
throw new SecurityException("Invalid inner random stream key!");
}
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
m_pbInnerRandomStreamKey);
}
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_ParsingDatabase", LogStatusType.AdditionalInfo);
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
m_pbInnerRandomStreamKey);
}
if (m_slLogger != null)
m_slLogger.SetText("KP2AKEY_ParsingDatabase", LogStatusType.AdditionalInfo);
#if KeePassDebug_WriteXml #if KeePassDebug_WriteXml
// FileStream fsOut = new FileStream("Raw.xml", FileMode.Create, #warning XML output is enabled!
// FileAccess.Write, FileShare.None); /* using(FileStream fsOut = new FileStream("Raw.xml", FileMode.Create,
// try FileAccess.Write, FileShare.None))
// { {
// while(true) while(true)
// { {
// int b = sXml.ReadByte(); int b = sXml.ReadByte();
// if(b == -1) break; if(b == -1) throw new EndOfStreamException();
// fsOut.WriteByte((byte)b); fsOut.WriteByte((byte)b);
// } }
// } } */
// catch(Exception) { }
// fsOut.Close();
#endif #endif
var stopWatch = Stopwatch.StartNew(); var stopWatch = Stopwatch.StartNew();
if (fmt == KdbxFormat.ProtocolBuffers)
{
KdbpFile.ReadDocument(m_pwDatabase, sXml, m_pbInnerRandomStreamKey, m_pbHashOfHeader);
Kp2aLog.Log(String.Format("KdbpFile.ReadDocument: {0}ms", stopWatch.ElapsedMilliseconds)); if (fmt == KdbxFormat.ProtocolBuffers)
{
KdbpFile.ReadDocument(m_pwDatabase, sXml, m_pbInnerRandomStreamKey, m_pbHashOfHeader);
} Kp2aLog.Log(String.Format("KdbpFile.ReadDocument: {0}ms", stopWatch.ElapsedMilliseconds));
else
{
ReadXmlStreamed(sXml, sHashing); }
else
{
Kp2aLog.Log(String.Format("ReadXmlStreamed: {0}ms", stopWatch.ElapsedMilliseconds)); ReadXmlStreamed(sXml, sHashing);
}
// ReadXmlDom(sXml);
}
catch(CryptographicException) // Thrown on invalid padding
{
throw new CryptographicException(KLRes.FileCorrupted);
}
finally
{
if(pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey);
if(pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64);
CommonCleanUpRead(lStreams, sHashing); Kp2aLog.Log(String.Format("ReadXmlStreamed: {0}ms", stopWatch.ElapsedMilliseconds));
} }
}
// ReadXmlDom(sXml);
catch (CryptographicException) // Thrown on invalid padding
{
throw new CryptographicException(KLRes.FileCorrupted);
}
finally
{
if (pbCipherKey != null) MemUtil.ZeroByteArray(pbCipherKey);
if (pbHmacKey64 != null) MemUtil.ZeroByteArray(pbHmacKey64);
CommonCleanUpRead(lStreams, sHashing);
}
#if KDBX_BENCHMARK #if KDBX_BENCHMARK
swTime.Stop(); swTime.Stop();
@@ -267,9 +271,9 @@ namespace KeePassLib.Serialization
Debug.Assert(m_pbHashOfFileOnDisk != null); Debug.Assert(m_pbHashOfFileOnDisk != null);
CleanUpInnerRandomStream(); CleanUpInnerRandomStream();
// Reset memory protection settings (to always use reasonable // Reset memory protection settings (to always use reasonable
// defaults) // defaults)
// defaults)
m_pwDatabase.MemoryProtection = new MemoryProtectionConfig(); m_pwDatabase.MemoryProtection = new MemoryProtectionConfig();
// Remove old backups (this call is required here in order to apply // Remove old backups (this call is required here in order to apply
@@ -282,7 +286,7 @@ namespace KeePassLib.Serialization
// Expand the root group, such that in case the user accidently // Expand the root group, such that in case the user accidently
// collapses the root group he can simply reopen the database // collapses the root group he can simply reopen the database
PwGroup pgRoot = m_pwDatabase.RootGroup; PwGroup pgRoot = m_pwDatabase.RootGroup;
if(pgRoot != null) pgRoot.IsExpanded = true; if (pgRoot != null) pgRoot.IsExpanded = true;
else { Debug.Assert(false); } else { Debug.Assert(false); }
m_pbHashOfHeader = null; m_pbHashOfHeader = null;
@@ -303,25 +307,25 @@ namespace KeePassLib.Serialization
byte[] pbSig2 = br.ReadBytes(4); byte[] pbSig2 = br.ReadBytes(4);
uint uSig2 = MemUtil.BytesToUInt32(pbSig2); uint uSig2 = MemUtil.BytesToUInt32(pbSig2);
if((uSig1 == FileSignatureOld1) && (uSig2 == FileSignatureOld2)) if ((uSig1 == FileSignatureOld1) && (uSig2 == FileSignatureOld2))
throw new OldFormatException(PwDefs.ShortProductName + @" 1.x", throw new OldFormatException(PwDefs.ShortProductName + @" 1.x",
OldFormatException.OldFormatType.KeePass1x); OldFormatException.OldFormatType.KeePass1x);
if((uSig1 == FileSignature1) && (uSig2 == FileSignature2)) { } if ((uSig1 == FileSignature1) && (uSig2 == FileSignature2)) { }
else if((uSig1 == FileSignaturePreRelease1) && (uSig2 == else if ((uSig1 == FileSignaturePreRelease1) && (uSig2 ==
FileSignaturePreRelease2)) { } FileSignaturePreRelease2)) { }
else throw new FormatException(KLRes.FileSigInvalid); else throw new FormatException(KLRes.FileSigInvalid);
byte[] pb = br.ReadBytes(4); byte[] pb = br.ReadBytes(4);
uint uVersion = MemUtil.BytesToUInt32(pb); uint uVersion = MemUtil.BytesToUInt32(pb);
if((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask)) if ((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask))
throw new FormatException(KLRes.FileVersionUnsupported + throw new FormatException(KLRes.FileVersionUnsupported +
MessageService.NewParagraph + KLRes.FileNewVerReq); MessageService.NewParagraph + KLRes.FileNewVerReq);
m_uFileVersion = uVersion; m_uFileVersion = uVersion;
while(true) while (true)
{ {
if(!ReadHeaderField(br)) break; if (!ReadHeaderField(br)) break;
} }
br.CopyDataTo = null; br.CopyDataTo = null;
@@ -335,23 +339,23 @@ namespace KeePassLib.Serialization
private bool ReadHeaderField(BinaryReaderEx brSource) private bool ReadHeaderField(BinaryReaderEx brSource)
{ {
Debug.Assert(brSource != null); Debug.Assert(brSource != null);
if(brSource == null) throw new ArgumentNullException("brSource"); if (brSource == null) throw new ArgumentNullException("brSource");
byte btFieldID = brSource.ReadByte(); byte btFieldID = brSource.ReadByte();
int cbSize; int cbSize;
Debug.Assert(m_uFileVersion > 0); Debug.Assert(m_uFileVersion > 0);
if(m_uFileVersion < FileVersion32_4) if (m_uFileVersion < FileVersion32_4)
cbSize = (int)MemUtil.BytesToUInt16(brSource.ReadBytes(2)); cbSize = (int)MemUtil.BytesToUInt16(brSource.ReadBytes(2));
else cbSize = MemUtil.BytesToInt32(brSource.ReadBytes(4)); else cbSize = MemUtil.BytesToInt32(brSource.ReadBytes(4));
if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted); if (cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
byte[] pbData = MemUtil.EmptyByteArray; byte[] pbData = MemUtil.EmptyByteArray;
if(cbSize > 0) pbData = brSource.ReadBytes(cbSize); if (cbSize > 0) pbData = brSource.ReadBytes(cbSize);
bool bResult = true; bool bResult = true;
KdbxHeaderFieldID kdbID = (KdbxHeaderFieldID)btFieldID; KdbxHeaderFieldID kdbID = (KdbxHeaderFieldID)btFieldID;
switch(kdbID) switch (kdbID)
{ {
case KdbxHeaderFieldID.EndOfHeader: case KdbxHeaderFieldID.EndOfHeader:
bResult = false; // Returning false indicates end of header bResult = false; // Returning false indicates end of header
@@ -375,7 +379,7 @@ namespace KeePassLib.Serialization
Debug.Assert(m_uFileVersion < FileVersion32_4); Debug.Assert(m_uFileVersion < FileVersion32_4);
AesKdf kdfS = new AesKdf(); AesKdf kdfS = new AesKdf();
if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid)) if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfS.Uuid))
m_pwDatabase.KdfParameters = kdfS.GetDefaultParameters(); m_pwDatabase.KdfParameters = kdfS.GetDefaultParameters();
// m_pbTransformSeed = pbData; // m_pbTransformSeed = pbData;
@@ -389,7 +393,7 @@ namespace KeePassLib.Serialization
Debug.Assert(m_uFileVersion < FileVersion32_4); Debug.Assert(m_uFileVersion < FileVersion32_4);
AesKdf kdfR = new AesKdf(); AesKdf kdfR = new AesKdf();
if(!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid)) if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfR.Uuid))
m_pwDatabase.KdfParameters = kdfR.GetDefaultParameters(); m_pwDatabase.KdfParameters = kdfR.GetDefaultParameters();
// m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData); // m_pwDatabase.KeyEncryptionRounds = MemUtil.BytesToUInt64(pbData);
@@ -429,8 +433,8 @@ namespace KeePassLib.Serialization
default: default:
Debug.Assert(false); Debug.Assert(false);
if(m_slLogger != null) if (m_slLogger != null)
m_slLogger.SetText(KLRes.UnknownHeaderId + @": " + m_slLogger.SetText(KLRes.UnknownHeaderId + ": " +
kdbID.ToString() + "!", LogStatusType.Warning); kdbID.ToString() + "!", LogStatusType.Warning);
break; break;
} }
@@ -443,28 +447,28 @@ namespace KeePassLib.Serialization
BinaryReaderEx br = new BinaryReaderEx(s, StrUtil.Utf8, BinaryReaderEx br = new BinaryReaderEx(s, StrUtil.Utf8,
KLRes.FileCorrupted + " " + KLRes.FileIncompleteExpc); KLRes.FileCorrupted + " " + KLRes.FileIncompleteExpc);
while(true) while (true)
{ {
if(!ReadInnerHeaderField(br)) break; if (!ReadInnerHeaderField(br)) break;
} }
} }
private bool ReadInnerHeaderField(BinaryReaderEx br) private bool ReadInnerHeaderField(BinaryReaderEx br)
{ {
Debug.Assert(br != null); Debug.Assert(br != null);
if(br == null) throw new ArgumentNullException("br"); if (br == null) throw new ArgumentNullException("br");
byte btFieldID = br.ReadByte(); byte btFieldID = br.ReadByte();
int cbSize = MemUtil.BytesToInt32(br.ReadBytes(4)); int cbSize = MemUtil.BytesToInt32(br.ReadBytes(4));
if(cbSize < 0) throw new FormatException(KLRes.FileCorrupted); if (cbSize < 0) throw new FormatException(KLRes.FileCorrupted);
byte[] pbData = MemUtil.EmptyByteArray; byte[] pbData = MemUtil.EmptyByteArray;
if(cbSize > 0) pbData = br.ReadBytes(cbSize); if (cbSize > 0) pbData = br.ReadBytes(cbSize);
bool bResult = true; bool bResult = true;
KdbxInnerHeaderFieldID kdbID = (KdbxInnerHeaderFieldID)btFieldID; KdbxInnerHeaderFieldID kdbID = (KdbxInnerHeaderFieldID)btFieldID;
switch(kdbID) switch (kdbID)
{ {
case KdbxInnerHeaderFieldID.EndOfHeader: case KdbxInnerHeaderFieldID.EndOfHeader:
bResult = false; // Returning false indicates end of header bResult = false; // Returning false indicates end of header
@@ -481,15 +485,16 @@ namespace KeePassLib.Serialization
break; break;
case KdbxInnerHeaderFieldID.Binary: case KdbxInnerHeaderFieldID.Binary:
if(pbData.Length < 1) throw new FormatException(); if (pbData.Length < 1) throw new FormatException();
KdbxBinaryFlags f = (KdbxBinaryFlags)pbData[0]; KdbxBinaryFlags f = (KdbxBinaryFlags)pbData[0];
bool bProt = ((f & KdbxBinaryFlags.Protected) != KdbxBinaryFlags.None); bool bProt = ((f & KdbxBinaryFlags.Protected) != KdbxBinaryFlags.None);
ProtectedBinary pb = new ProtectedBinary(bProt, pbData, ProtectedBinary pb = new ProtectedBinary(bProt, pbData,
1, pbData.Length - 1); 1, pbData.Length - 1);
Debug.Assert(m_pbsBinaries.Find(pb) < 0); // No deduplication?
m_pbsBinaries.Add(pb); m_pbsBinaries.Add(pb);
if(bProt) MemUtil.ZeroByteArray(pbData); if (bProt) MemUtil.ZeroByteArray(pbData);
break; break;
default: default:
@@ -502,7 +507,7 @@ namespace KeePassLib.Serialization
private void SetCipher(byte[] pbID) private void SetCipher(byte[] pbID)
{ {
if((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize)) if ((pbID == null) || (pbID.Length != (int)PwUuid.UuidSize))
throw new FormatException(KLRes.FileUnknownCipher); throw new FormatException(KLRes.FileUnknownCipher);
m_pwDatabase.DataCipherUuid = new PwUuid(pbID); m_pwDatabase.DataCipherUuid = new PwUuid(pbID);
@@ -511,7 +516,7 @@ namespace KeePassLib.Serialization
private void SetCompressionFlags(byte[] pbFlags) private void SetCompressionFlags(byte[] pbFlags)
{ {
int nID = (int)MemUtil.BytesToUInt32(pbFlags); int nID = (int)MemUtil.BytesToUInt32(pbFlags);
if((nID < 0) || (nID >= (int)PwCompressionAlgorithm.Count)) if ((nID < 0) || (nID >= (int)PwCompressionAlgorithm.Count))
throw new FormatException(KLRes.FileUnknownCompression); throw new FormatException(KLRes.FileUnknownCompression);
m_pwDatabase.Compression = (PwCompressionAlgorithm)nID; m_pwDatabase.Compression = (PwCompressionAlgorithm)nID;
@@ -520,12 +525,35 @@ namespace KeePassLib.Serialization
private void SetInnerRandomStreamID(byte[] pbID) private void SetInnerRandomStreamID(byte[] pbID)
{ {
uint uID = MemUtil.BytesToUInt32(pbID); uint uID = MemUtil.BytesToUInt32(pbID);
if(uID >= (uint)CrsAlgorithm.Count) if (uID >= (uint)CrsAlgorithm.Count)
throw new FormatException(KLRes.FileUnknownCipher); throw new FormatException(KLRes.FileUnknownCipher);
m_craInnerRandomStream = (CrsAlgorithm)uID; m_craInnerRandomStream = (CrsAlgorithm)uID;
} }
internal static PwGroup ReadGroup(Stream msData, PwDatabase pdContext,
bool bCopyIcons, bool bNewUuids, bool bSetCreatedNow)
{
PwDatabase pd = new PwDatabase();
pd.New(new IOConnectionInfo(), new CompositeKey(), "");
KdbxFile f = new KdbxFile(pd);
f.Load(msData, KdbxFormat.PlainXml, null);
if (bCopyIcons)
PwDatabase.CopyCustomIcons(pd, pdContext, pd.RootGroup, true);
if (bNewUuids)
{
pd.RootGroup.Uuid = new PwUuid(true);
pd.RootGroup.CreateNewItemUuids(true, true, true);
}
if (bSetCreatedNow) pd.RootGroup.SetCreatedNow(true);
return pd.RootGroup;
}
[Obsolete] [Obsolete]
public static List<PwEntry> ReadEntries(Stream msData) public static List<PwEntry> ReadEntries(Stream msData)
{ {
@@ -537,81 +565,14 @@ namespace KeePassLib.Serialization
{ {
return ReadEntries(msData, pdContext, true); return ReadEntries(msData, pdContext, true);
} }
/// <summary>
/// Read entries from a stream.
/// </summary>
/// <param name="msData">Input stream to read the entries from.</param>
/// <returns>Extracted entries.</returns>
/// <param name="pdContext">Context database (e.g. for storing icons).</param>
/// <param name="bCopyIcons">If <c>true</c>, custom icons required by
/// the loaded entries are copied to the context database.</param>
/// <returns>Loaded entries.</returns>
public static List<PwEntry> ReadEntries(Stream msData, PwDatabase pdContext, public static List<PwEntry> ReadEntries(Stream msData, PwDatabase pdContext,
bool bCopyIcons) bool bCopyIcons)
{ {
List<PwEntry> lEntries = new List<PwEntry>(); if (msData == null) { Debug.Assert(false); return new List<PwEntry>(); }
/* KdbxFile f = new KdbxFile(pwDatabase);
if(msData == null) { Debug.Assert(false); return lEntries; }
f.m_format = KdbxFormat.PlainXml;
XmlDocument doc = new XmlDocument(); PwGroup pg = ReadGroup(msData, pdContext, bCopyIcons, true, true);
doc.Load(msData); return pg.GetEntries(true).CloneShallowToList();
XmlElement el = doc.DocumentElement;
if(el.Name != ElemRoot) throw new FormatException();
List<PwEntry> vEntries = new List<PwEntry>();
foreach(XmlNode xmlChild in el.ChildNodes)
{
if(xmlChild.Name == ElemEntry)
{
PwEntry pe = f.ReadEntry(xmlChild);
pe.Uuid = new PwUuid(true);
foreach(PwEntry peHistory in pe.History)
peHistory.Uuid = pe.Uuid;
vEntries.Add(pe);
}
else { Debug.Assert(false); }
}
return vEntries; */
PwDatabase pd = new PwDatabase();
pd.New(new IOConnectionInfo(), new CompositeKey(), "");
KdbxFile f = new KdbxFile(pd);
f.Load(msData, KdbxFormat.PlainXml, null);
foreach(PwEntry pe in pd.RootGroup.Entries)
{
pe.SetUuid(new PwUuid(true), true);
lEntries.Add(pe);
if(bCopyIcons && (pdContext != null))
{
PwUuid pu = pe.CustomIconUuid;
if(!pu.Equals(PwUuid.Zero))
{
int iSrc = pd.GetCustomIconIndex(pu);
int iDst = pdContext.GetCustomIconIndex(pu);
if(iSrc < 0) { Debug.Assert(false); }
else if(iDst < 0)
{
pdContext.CustomIcons.Add(pd.CustomIcons[iSrc]);
pdContext.Modified = true;
pdContext.UINeedsIconUpdate = true;
}
}
}
}
return lEntries;
} }
} }
} }

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2016 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -25,7 +25,7 @@ using System.IO;
using System.Security; using System.Security;
using System.Text; using System.Text;
using System.Xml; using System.Xml;
using keepass2android;
#if !KeePassUAP #if !KeePassUAP
using System.Drawing; using System.Drawing;
using System.Security.Cryptography; using System.Security.Cryptography;
@@ -48,8 +48,6 @@ using KeePassLib.Resources;
using KeePassLib.Security; using KeePassLib.Security;
using KeePassLib.Utility; using KeePassLib.Utility;
using keepass2android;
namespace KeePassLib.Serialization namespace KeePassLib.Serialization
{ {
/// <summary> /// <summary>
@@ -81,14 +79,15 @@ namespace KeePassLib.Serialization
IStatusLogger slLogger) IStatusLogger slLogger)
{ {
Debug.Assert(sSaveTo != null); Debug.Assert(sSaveTo != null);
if(sSaveTo == null) throw new ArgumentNullException("sSaveTo"); if (sSaveTo == null) throw new ArgumentNullException("sSaveTo");
if(m_bUsedOnce) if (m_bUsedOnce)
throw new InvalidOperationException("Do not reuse KdbxFile objects!"); throw new InvalidOperationException("Do not reuse KdbxFile objects!");
m_bUsedOnce = true; m_bUsedOnce = true;
m_format = fmt; m_format = fmt;
m_slLogger = slLogger; m_slLogger = slLogger;
m_xmlWriter = null;
PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup); PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup);
UTF8Encoding encNoBom = StrUtil.Utf8; UTF8Encoding encNoBom = StrUtil.Utf8;
@@ -96,7 +95,7 @@ namespace KeePassLib.Serialization
byte[] pbCipherKey = null; byte[] pbCipherKey = null;
byte[] pbHmacKey64 = null; byte[] pbHmacKey64 = null;
m_pbsBinaries.Clear(); m_pbsBinaries = new ProtectedBinarySet(true);
m_pbsBinaries.AddFrom(pgRoot); m_pbsBinaries.AddFrom(pgRoot);
List<Stream> lStreams = new List<Stream>(); List<Stream> lStreams = new List<Stream>();
@@ -107,6 +106,10 @@ namespace KeePassLib.Serialization
try try
{ {
// Fix history entries (should not be necessary; just for safety,
// as e.g. XPath searches depend on correct history entry UUIDs)
if (m_pwDatabase.MaintainBackups()) { Debug.Assert(false); }
m_uFileVersion = GetMinKdbxVersion(); m_uFileVersion = GetMinKdbxVersion();
int cbEncKey, cbEncIV; int cbEncKey, cbEncIV;
@@ -118,30 +121,30 @@ namespace KeePassLib.Serialization
// m_pbTransformSeed = cr.GetRandomBytes(32); // m_pbTransformSeed = cr.GetRandomBytes(32);
PwUuid puKdf = m_pwDatabase.KdfParameters.KdfUuid; PwUuid puKdf = m_pwDatabase.KdfParameters.KdfUuid;
KdfEngine kdf = KdfPool.Get(puKdf); KdfEngine kdf = KdfPool.Get(puKdf);
if(kdf == null) if (kdf == null)
throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph + throw new Exception(KLRes.UnknownKdf + MessageService.NewParagraph +
// KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + // KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph +
"UUID: " + puKdf.ToHexString() + "."); "UUID: " + puKdf.ToHexString() + ".");
kdf.Randomize(m_pwDatabase.KdfParameters); kdf.Randomize(m_pwDatabase.KdfParameters);
if(m_format == KdbxFormat.Default) if (m_format == KdbxFormat.Default)
{ {
if(m_uFileVersion < FileVersion32_4) if (m_uFileVersion < FileVersion32_4)
{ {
m_craInnerRandomStream = CrsAlgorithm.Salsa20; m_craInnerRandomStream = CrsAlgorithm.Salsa20;
m_pbInnerRandomStreamKey = cr.GetRandomBytes(32); m_pbInnerRandomStreamKey = cr.GetRandomBytes(32);
} }
else // KDBX >= 4 else // KDBX >= 4
{ {
m_craInnerRandomStream = CrsAlgorithm.ChaCha20; m_craInnerRandomStream = CrsAlgorithm.ChaCha20;
m_pbInnerRandomStreamKey = cr.GetRandomBytes(64); m_pbInnerRandomStreamKey = cr.GetRandomBytes(64);
} }
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
m_pbInnerRandomStreamKey); m_pbInnerRandomStreamKey);
} }
if(m_uFileVersion < FileVersion32_4) if (m_uFileVersion < FileVersion32_4)
m_pbStreamStartBytes = cr.GetRandomBytes(32); m_pbStreamStartBytes = cr.GetRandomBytes(32);
Stream sXml; Stream sXml;
@@ -156,11 +159,11 @@ namespace KeePassLib.Serialization
ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64); ComputeKeys(out pbCipherKey, cbEncKey, out pbHmacKey64);
Stream sPlain; Stream sPlain;
if(m_uFileVersion < FileVersion32_4) if (m_uFileVersion < FileVersion32_4)
{ {
Stream sEncrypted = EncryptStream(sHashing, iCipher, Stream sEncrypted = EncryptStream(sHashing, iCipher,
pbCipherKey, cbEncIV, true); pbCipherKey, cbEncIV, true);
if((sEncrypted == null) || (sEncrypted == sHashing)) if ((sEncrypted == null) || (sEncrypted == sHashing))
throw new SecurityException(KLRes.CryptoStreamFailed); throw new SecurityException(KLRes.CryptoStreamFailed);
lStreams.Add(sEncrypted); lStreams.Add(sEncrypted);
@@ -182,22 +185,22 @@ namespace KeePassLib.Serialization
sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey, sPlain = EncryptStream(sBlocks, iCipher, pbCipherKey,
cbEncIV, true); cbEncIV, true);
if((sPlain == null) || (sPlain == sBlocks)) if ((sPlain == null) || (sPlain == sBlocks))
throw new SecurityException(KLRes.CryptoStreamFailed); throw new SecurityException(KLRes.CryptoStreamFailed);
} }
lStreams.Add(sPlain); lStreams.Add(sPlain);
if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip) if (m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
{ {
sXml = new GZipStream(sPlain, CompressionMode.Compress); sXml = new GZipStream(sPlain, CompressionMode.Compress);
lStreams.Add(sXml); lStreams.Add(sXml);
} }
else sXml = sPlain; else sXml = sPlain;
if(m_uFileVersion >= FileVersion32_4) if (m_uFileVersion >= FileVersion32_4)
WriteInnerHeader(sXml); // Binary header before XML WriteInnerHeader(sXml); // Binary header before XML
} }
else if(m_format == KdbxFormat.PlainXml) else if (m_format == KdbxFormat.PlainXml)
sXml = sHashing; sXml = sHashing;
else else
{ {
@@ -251,6 +254,8 @@ namespace KeePassLib.Serialization
private void CommonCleanUpWrite(List<Stream> lStreams, HashingStreamEx sHashing) private void CommonCleanUpWrite(List<Stream> lStreams, HashingStreamEx sHashing)
{ {
if (m_xmlWriter != null) { m_xmlWriter.Close(); m_xmlWriter = null; }
CloseStreams(lStreams); CloseStreams(lStreams);
Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed Debug.Assert(lStreams.Contains(sHashing)); // sHashing must be closed
@@ -259,14 +264,13 @@ namespace KeePassLib.Serialization
CleanUpInnerRandomStream(); CleanUpInnerRandomStream();
m_xmlWriter = null;
m_pbHashOfHeader = null; m_pbHashOfHeader = null;
} }
private byte[] GenerateHeader() private byte[] GenerateHeader()
{ {
byte[] pbHeader; byte[] pbHeader;
using(MemoryStream ms = new MemoryStream()) using (MemoryStream ms = new MemoryStream())
{ {
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1)); MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1));
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2)); MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2));
@@ -281,7 +285,7 @@ namespace KeePassLib.Serialization
WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed);
if(m_uFileVersion < FileVersion32_4) if (m_uFileVersion < FileVersion32_4)
{ {
Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals( Debug.Assert(m_pwDatabase.KdfParameters.KdfUuid.Equals(
(new AesKdf()).Uuid)); (new AesKdf()).Uuid));
@@ -295,10 +299,10 @@ namespace KeePassLib.Serialization
WriteHeaderField(ms, KdbxHeaderFieldID.KdfParameters, WriteHeaderField(ms, KdbxHeaderFieldID.KdfParameters,
KdfParameters.SerializeExt(m_pwDatabase.KdfParameters)); KdfParameters.SerializeExt(m_pwDatabase.KdfParameters));
if(m_pbEncryptionIV.Length > 0) if (m_pbEncryptionIV.Length > 0)
WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV);
if(m_uFileVersion < FileVersion32_4) if (m_uFileVersion < FileVersion32_4)
{ {
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamKey, WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamKey,
m_pbInnerRandomStreamKey); m_pbInnerRandomStreamKey);
@@ -306,14 +310,14 @@ namespace KeePassLib.Serialization
WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes,
m_pbStreamStartBytes); m_pbStreamStartBytes);
int nIrsID = (int)m_craInnerRandomStream; int nIrsID = (int)m_craInnerRandomStream;
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID,
MemUtil.Int32ToBytes(nIrsID)); MemUtil.Int32ToBytes(nIrsID));
} }
// Write public custom data only when there is at least one item, // Write public custom data only when there is at least one item,
// because KDBX 3.1 didn't support this field yet // because KDBX 3.1 didn't support this field yet
if(m_pwDatabase.PublicCustomData.Count > 0) if (m_pwDatabase.PublicCustomData.Count > 0)
WriteHeaderField(ms, KdbxHeaderFieldID.PublicCustomData, WriteHeaderField(ms, KdbxHeaderFieldID.PublicCustomData,
VariantDictionary.Serialize(m_pwDatabase.PublicCustomData)); VariantDictionary.Serialize(m_pwDatabase.PublicCustomData));
@@ -333,12 +337,12 @@ namespace KeePassLib.Serialization
byte[] pb = (pbData ?? MemUtil.EmptyByteArray); byte[] pb = (pbData ?? MemUtil.EmptyByteArray);
int cb = pb.Length; int cb = pb.Length;
if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); } if (cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
Debug.Assert(m_uFileVersion > 0); Debug.Assert(m_uFileVersion > 0);
if(m_uFileVersion < FileVersion32_4) if (m_uFileVersion < FileVersion32_4)
{ {
if(cb > (int)ushort.MaxValue) if (cb > (int)ushort.MaxValue)
{ {
Debug.Assert(false); Debug.Assert(false);
throw new ArgumentOutOfRangeException("pbData"); throw new ArgumentOutOfRangeException("pbData");
@@ -361,13 +365,13 @@ namespace KeePassLib.Serialization
m_pbInnerRandomStreamKey, null); m_pbInnerRandomStreamKey, null);
ProtectedBinary[] vBin = m_pbsBinaries.ToArray(); ProtectedBinary[] vBin = m_pbsBinaries.ToArray();
for(int i = 0; i < vBin.Length; ++i) for (int i = 0; i < vBin.Length; ++i)
{ {
ProtectedBinary pb = vBin[i]; ProtectedBinary pb = vBin[i];
if(pb == null) throw new InvalidOperationException(); if (pb == null) throw new InvalidOperationException();
KdbxBinaryFlags f = KdbxBinaryFlags.None; KdbxBinaryFlags f = KdbxBinaryFlags.None;
if(pb.IsProtected) f |= KdbxBinaryFlags.Protected; if (pb.IsProtected) f |= KdbxBinaryFlags.Protected;
byte[] pbFlags = new byte[1] { (byte)f }; byte[] pbFlags = new byte[1] { (byte)f };
byte[] pbData = pb.ReadData(); byte[] pbData = pb.ReadData();
@@ -375,7 +379,7 @@ namespace KeePassLib.Serialization
WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.Binary, WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.Binary,
pbFlags, pbData); pbFlags, pbData);
if(pb.IsProtected) MemUtil.ZeroByteArray(pbData); if (pb.IsProtected) MemUtil.ZeroByteArray(pbData);
} }
WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.EndOfHeader, WriteInnerHeaderField(s, KdbxInnerHeaderFieldID.EndOfHeader,
@@ -391,7 +395,7 @@ namespace KeePassLib.Serialization
byte[] pb2 = (pbData2 ?? MemUtil.EmptyByteArray); byte[] pb2 = (pbData2 ?? MemUtil.EmptyByteArray);
int cb = pb1.Length + pb2.Length; int cb = pb1.Length + pb2.Length;
if(cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); } if (cb < 0) { Debug.Assert(false); throw new OutOfMemoryException(); }
MemUtil.Write(s, MemUtil.Int32ToBytes(cb)); MemUtil.Write(s, MemUtil.Int32ToBytes(cb));
MemUtil.Write(s, pb1); MemUtil.Write(s, pb1);
@@ -401,7 +405,7 @@ namespace KeePassLib.Serialization
private void WriteDocument(PwGroup pgRoot) private void WriteDocument(PwGroup pgRoot)
{ {
Debug.Assert(m_xmlWriter != null); Debug.Assert(m_xmlWriter != null);
if(m_xmlWriter == null) throw new InvalidOperationException(); if (m_xmlWriter == null) throw new InvalidOperationException();
uint uNumGroups, uNumEntries, uCurEntry = 0; uint uNumGroups, uNumEntries, uCurEntry = 0;
pgRoot.GetCounts(true, out uNumGroups, out uNumEntries); pgRoot.GetCounts(true, out uNumGroups, out uNumEntries);
@@ -417,14 +421,14 @@ namespace KeePassLib.Serialization
Stack<PwGroup> groupStack = new Stack<PwGroup>(); Stack<PwGroup> groupStack = new Stack<PwGroup>();
groupStack.Push(pgRoot); groupStack.Push(pgRoot);
GroupHandler gh = delegate(PwGroup pg) GroupHandler gh = delegate (PwGroup pg)
{ {
Debug.Assert(pg != null); Debug.Assert(pg != null);
if(pg == null) throw new ArgumentNullException("pg"); if (pg == null) throw new ArgumentNullException("pg");
while(true) while (true)
{ {
if(pg.ParentGroup == groupStack.Peek()) if (pg.ParentGroup == groupStack.Peek())
{ {
groupStack.Push(pg); groupStack.Push(pg);
StartGroup(pg); StartGroup(pg);
@@ -433,7 +437,7 @@ namespace KeePassLib.Serialization
else else
{ {
groupStack.Pop(); groupStack.Pop();
if(groupStack.Count <= 0) return false; if (groupStack.Count <= 0) return false;
EndGroup(); EndGroup();
} }
@@ -442,23 +446,25 @@ namespace KeePassLib.Serialization
return true; return true;
}; };
EntryHandler eh = delegate(PwEntry pe) EntryHandler eh = delegate (PwEntry pe)
{ {
Debug.Assert(pe != null); Debug.Assert(pe != null);
WriteEntry(pe, false); WriteEntry(pe, false);
++uCurEntry; ++uCurEntry;
if(m_slLogger != null) if (m_slLogger != null)
if(!m_slLogger.SetProgress((100 * uCurEntry) / uNumEntries)) {
if (!m_slLogger.SetProgress((100 * uCurEntry) / uNumEntries))
return false; return false;
}
return true; return true;
}; };
if(!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh)) if (!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh))
throw new InvalidOperationException(); throw new OperationCanceledException();
while(groupStack.Count > 1) while (groupStack.Count > 1)
{ {
m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteEndElement();
groupStack.Pop(); groupStack.Pop();
@@ -479,11 +485,11 @@ namespace KeePassLib.Serialization
WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false);
if((m_pbHashOfHeader != null) && (m_uFileVersion < FileVersion32_4)) if ((m_pbHashOfHeader != null) && (m_uFileVersion < FileVersion32_4))
WriteObject(ElemHeaderHash, Convert.ToBase64String( WriteObject(ElemHeaderHash, Convert.ToBase64String(
m_pbHashOfHeader), false); m_pbHashOfHeader), false);
if(m_uFileVersion >= FileVersion32_4) if (m_uFileVersion >= FileVersion32_4)
WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged); WriteObject(ElemSettingsChanged, m_pwDatabase.SettingsChanged);
WriteObject(ElemDbName, m_pwDatabase.Name, true); WriteObject(ElemDbName, m_pwDatabase.Name, true);
@@ -497,7 +503,7 @@ namespace KeePassLib.Serialization
WriteObject(ElemDbKeyChanged, m_pwDatabase.MasterKeyChanged); WriteObject(ElemDbKeyChanged, m_pwDatabase.MasterKeyChanged);
WriteObject(ElemDbKeyChangeRec, m_pwDatabase.MasterKeyChangeRec); WriteObject(ElemDbKeyChangeRec, m_pwDatabase.MasterKeyChangeRec);
WriteObject(ElemDbKeyChangeForce, m_pwDatabase.MasterKeyChangeForce); WriteObject(ElemDbKeyChangeForce, m_pwDatabase.MasterKeyChangeForce);
if(m_pwDatabase.MasterKeyChangeForceOnce) if (m_pwDatabase.MasterKeyChangeForceOnce)
WriteObject(ElemDbKeyChangeForceOnce, true); WriteObject(ElemDbKeyChangeForceOnce, true);
WriteList(ElemMemoryProt, m_pwDatabase.MemoryProtection); WriteList(ElemMemoryProt, m_pwDatabase.MemoryProtection);
@@ -515,8 +521,8 @@ namespace KeePassLib.Serialization
WriteObject(ElemLastSelectedGroup, m_pwDatabase.LastSelectedGroup); WriteObject(ElemLastSelectedGroup, m_pwDatabase.LastSelectedGroup);
WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.LastTopVisibleGroup); WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.LastTopVisibleGroup);
if((m_format != KdbxFormat.Default) || (m_uFileVersion < FileVersion32_4)) if ((m_format != KdbxFormat.Default) || (m_uFileVersion < FileVersion32_4))
WriteBinPool(); WriteBinPool();
WriteList(ElemCustomData, m_pwDatabase.CustomData); WriteList(ElemCustomData, m_pwDatabase.CustomData);
@@ -530,10 +536,10 @@ namespace KeePassLib.Serialization
WriteObject(ElemName, pg.Name, true); WriteObject(ElemName, pg.Name, true);
WriteObject(ElemNotes, pg.Notes, true); WriteObject(ElemNotes, pg.Notes, true);
WriteObject(ElemIcon, (int)pg.IconId); WriteObject(ElemIcon, (int)pg.IconId);
if(!pg.CustomIconUuid.Equals(PwUuid.Zero)) if (!pg.CustomIconUuid.Equals(PwUuid.Zero))
WriteObject(ElemCustomIconID, pg.CustomIconUuid); WriteObject(ElemCustomIconID, pg.CustomIconUuid);
WriteList(ElemTimes, pg); WriteList(ElemTimes, pg);
WriteObject(ElemIsExpanded, pg.IsExpanded); WriteObject(ElemIsExpanded, pg.IsExpanded);
WriteObject(ElemGroupDefaultAutoTypeSeq, pg.DefaultAutoTypeSequence, true); WriteObject(ElemGroupDefaultAutoTypeSeq, pg.DefaultAutoTypeSequence, true);
@@ -541,7 +547,17 @@ namespace KeePassLib.Serialization
WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false); WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false);
WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry); WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry);
if(pg.CustomData.Count > 0) if (m_uFileVersion >= FileVersion32_4_1)
{
if (!pg.PreviousParentGroup.Equals(PwUuid.Zero))
WriteObject(ElemPreviousParentGroup, pg.PreviousParentGroup);
List<string> lTags = pg.Tags;
if (lTags.Count != 0)
WriteObject(ElemTags, StrUtil.TagsToString(lTags, false), true);
}
if (pg.CustomData.Count > 0)
WriteList(ElemCustomData, pg.CustomData); WriteList(ElemCustomData, pg.CustomData);
} }
@@ -552,31 +568,39 @@ namespace KeePassLib.Serialization
private void WriteEntry(PwEntry pe, bool bIsHistory) private void WriteEntry(PwEntry pe, bool bIsHistory)
{ {
Debug.Assert(pe != null); if(pe == null) throw new ArgumentNullException("pe"); Debug.Assert(pe != null); if (pe == null) throw new ArgumentNullException("pe");
m_xmlWriter.WriteStartElement(ElemEntry); m_xmlWriter.WriteStartElement(ElemEntry);
WriteObject(ElemUuid, pe.Uuid); WriteObject(ElemUuid, pe.Uuid);
WriteObject(ElemIcon, (int)pe.IconId); WriteObject(ElemIcon, (int)pe.IconId);
if(!pe.CustomIconUuid.Equals(PwUuid.Zero)) if (!pe.CustomIconUuid.Equals(PwUuid.Zero))
WriteObject(ElemCustomIconID, pe.CustomIconUuid); WriteObject(ElemCustomIconID, pe.CustomIconUuid);
WriteObject(ElemFgColor, StrUtil.ColorToUnnamedHtml(pe.ForegroundColor, true), false); WriteObject(ElemFgColor, StrUtil.ColorToUnnamedHtml(pe.ForegroundColor, true), false);
WriteObject(ElemBgColor, StrUtil.ColorToUnnamedHtml(pe.BackgroundColor, true), false); WriteObject(ElemBgColor, StrUtil.ColorToUnnamedHtml(pe.BackgroundColor, true), false);
WriteObject(ElemOverrideUrl, pe.OverrideUrl, true); WriteObject(ElemOverrideUrl, pe.OverrideUrl, true);
if ((m_uFileVersion >= FileVersion32_4_1) && !pe.QualityCheck)
WriteObject(ElemQualityCheck, false);
WriteObject(ElemTags, StrUtil.TagsToString(pe.Tags, false), true); WriteObject(ElemTags, StrUtil.TagsToString(pe.Tags, false), true);
if ((m_uFileVersion >= FileVersion32_4_1) &&
!pe.PreviousParentGroup.Equals(PwUuid.Zero))
WriteObject(ElemPreviousParentGroup, pe.PreviousParentGroup);
WriteList(ElemTimes, pe); WriteList(ElemTimes, pe);
WriteList(pe.Strings, true); WriteList(pe.Strings, true);
WriteList(pe.Binaries); WriteList(pe.Binaries);
WriteList(ElemAutoType, pe.AutoType); WriteList(ElemAutoType, pe.AutoType);
if(pe.CustomData.Count > 0) if (pe.CustomData.Count > 0)
WriteList(ElemCustomData, pe.CustomData); WriteList(ElemCustomData, pe.CustomData);
if(!bIsHistory) WriteList(ElemHistory, pe.History, true); if (!bIsHistory) WriteList(ElemHistory, pe.History, true);
else { Debug.Assert(pe.History.UCount == 0); } else { Debug.Assert(pe.History.UCount == 0); }
m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteEndElement();
@@ -585,18 +609,18 @@ namespace KeePassLib.Serialization
private void WriteList(ProtectedStringDictionary dictStrings, bool bEntryStrings) private void WriteList(ProtectedStringDictionary dictStrings, bool bEntryStrings)
{ {
Debug.Assert(dictStrings != null); Debug.Assert(dictStrings != null);
if(dictStrings == null) throw new ArgumentNullException("dictStrings"); if (dictStrings == null) throw new ArgumentNullException("dictStrings");
foreach(KeyValuePair<string, ProtectedString> kvp in dictStrings) foreach (KeyValuePair<string, ProtectedString> kvp in dictStrings)
WriteObject(kvp.Key, kvp.Value, bEntryStrings); WriteObject(kvp.Key, kvp.Value, bEntryStrings);
} }
private void WriteList(ProtectedBinaryDictionary dictBinaries) private void WriteList(ProtectedBinaryDictionary dictBinaries)
{ {
Debug.Assert(dictBinaries != null); Debug.Assert(dictBinaries != null);
if(dictBinaries == null) throw new ArgumentNullException("dictBinaries"); if (dictBinaries == null) throw new ArgumentNullException("dictBinaries");
foreach(KeyValuePair<string, ProtectedBinary> kvp in dictBinaries) foreach (KeyValuePair<string, ProtectedBinary> kvp in dictBinaries)
WriteObject(kvp.Key, kvp.Value, true); WriteObject(kvp.Key, kvp.Value, true);
} }
@@ -604,19 +628,19 @@ namespace KeePassLib.Serialization
{ {
Debug.Assert(name != null); Debug.Assert(name != null);
Debug.Assert(cfgAutoType != null); Debug.Assert(cfgAutoType != null);
if(cfgAutoType == null) throw new ArgumentNullException("cfgAutoType"); if (cfgAutoType == null) throw new ArgumentNullException("cfgAutoType");
m_xmlWriter.WriteStartElement(name); m_xmlWriter.WriteStartElement(name);
WriteObject(ElemAutoTypeEnabled, cfgAutoType.Enabled); WriteObject(ElemAutoTypeEnabled, cfgAutoType.Enabled);
WriteObject(ElemAutoTypeObfuscation, (int)cfgAutoType.ObfuscationOptions); WriteObject(ElemAutoTypeObfuscation, (int)cfgAutoType.ObfuscationOptions);
if(cfgAutoType.DefaultSequence.Length > 0) if (cfgAutoType.DefaultSequence.Length > 0)
WriteObject(ElemAutoTypeDefaultSeq, cfgAutoType.DefaultSequence, true); WriteObject(ElemAutoTypeDefaultSeq, cfgAutoType.DefaultSequence, true);
foreach(AutoTypeAssociation a in cfgAutoType.Associations) foreach (AutoTypeAssociation a in cfgAutoType.Associations)
WriteObject(ElemAutoTypeItem, ElemWindow, ElemKeystrokeSequence, WriteObject(ElemAutoTypeItem, ElemWindow, ElemKeystrokeSequence,
new KeyValuePair<string, string>(a.WindowName, a.Sequence)); new KeyValuePair<string, string>(a.WindowName, a.Sequence), null);
m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteEndElement();
} }
@@ -624,7 +648,7 @@ namespace KeePassLib.Serialization
private void WriteList(string name, ITimeLogger times) private void WriteList(string name, ITimeLogger times)
{ {
Debug.Assert(name != null); Debug.Assert(name != null);
Debug.Assert(times != null); if(times == null) throw new ArgumentNullException("times"); Debug.Assert(times != null); if (times == null) throw new ArgumentNullException("times");
m_xmlWriter.WriteStartElement(name); m_xmlWriter.WriteStartElement(name);
@@ -636,17 +660,17 @@ namespace KeePassLib.Serialization
WriteObject(ElemUsageCount, times.UsageCount); WriteObject(ElemUsageCount, times.UsageCount);
WriteObject(ElemLocationChanged, times.LocationChanged); WriteObject(ElemLocationChanged, times.LocationChanged);
m_xmlWriter.WriteEndElement(); // Name m_xmlWriter.WriteEndElement();
} }
private void WriteList(string name, PwObjectList<PwEntry> value, bool bIsHistory) private void WriteList(string name, PwObjectList<PwEntry> value, bool bIsHistory)
{ {
Debug.Assert(name != null); Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name); m_xmlWriter.WriteStartElement(name);
foreach(PwEntry pe in value) foreach (PwEntry pe in value)
WriteEntry(pe, bIsHistory); WriteEntry(pe, bIsHistory);
m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteEndElement();
@@ -655,11 +679,11 @@ namespace KeePassLib.Serialization
private void WriteList(string name, PwObjectList<PwDeletedObject> value) private void WriteList(string name, PwObjectList<PwDeletedObject> value)
{ {
Debug.Assert(name != null); Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name); m_xmlWriter.WriteStartElement(name);
foreach(PwDeletedObject pdo in value) foreach (PwDeletedObject pdo in value)
WriteObject(ElemDeletedObject, pdo); WriteObject(ElemDeletedObject, pdo);
m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteEndElement();
@@ -685,31 +709,45 @@ namespace KeePassLib.Serialization
private void WriteList(string name, StringDictionaryEx value) private void WriteList(string name, StringDictionaryEx value)
{ {
Debug.Assert(name != null); Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name); m_xmlWriter.WriteStartElement(name);
foreach(KeyValuePair<string, string> kvp in value) foreach (KeyValuePair<string, string> kvp in value)
WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp); {
DateTime? odtLastMod = null;
if (m_uFileVersion >= FileVersion32_4_1)
odtLastMod = value.GetLastModificationTime(kvp.Key);
WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp, odtLastMod);
}
m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteEndElement();
} }
private void WriteCustomIconList() private void WriteCustomIconList()
{ {
if(m_pwDatabase.CustomIcons.Count == 0) return; if (m_pwDatabase.CustomIcons.Count == 0) return;
m_xmlWriter.WriteStartElement(ElemCustomIcons); m_xmlWriter.WriteStartElement(ElemCustomIcons);
foreach(PwCustomIcon pwci in m_pwDatabase.CustomIcons) foreach (PwCustomIcon ci in m_pwDatabase.CustomIcons)
{ {
m_xmlWriter.WriteStartElement(ElemCustomIconItem); m_xmlWriter.WriteStartElement(ElemCustomIconItem);
WriteObject(ElemCustomIconItemID, pwci.Uuid); WriteObject(ElemCustomIconItemID, ci.Uuid);
string strData = Convert.ToBase64String(pwci.ImageDataPng); string strData = Convert.ToBase64String(ci.ImageDataPng);
WriteObject(ElemCustomIconItemData, strData, false); WriteObject(ElemCustomIconItemData, strData, false);
if (m_uFileVersion >= FileVersion32_4_1)
{
if (ci.Name.Length != 0)
WriteObject(ElemName, ci.Name, true);
if (ci.LastModificationTime.HasValue)
WriteObject(ElemLastModTime, ci.LastModificationTime.Value);
}
m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteEndElement();
} }
@@ -724,7 +762,7 @@ namespace KeePassLib.Serialization
m_xmlWriter.WriteStartElement(name); m_xmlWriter.WriteStartElement(name);
if(bFilterValueXmlChars) if (bFilterValueXmlChars)
m_xmlWriter.WriteString(StrUtil.SafeXmlString(value)); m_xmlWriter.WriteString(StrUtil.SafeXmlString(value));
else m_xmlWriter.WriteString(value); else m_xmlWriter.WriteString(value);
@@ -741,7 +779,7 @@ namespace KeePassLib.Serialization
private void WriteObject(string name, PwUuid value) private void WriteObject(string name, PwUuid value)
{ {
Debug.Assert(name != null); Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
WriteObject(name, Convert.ToBase64String(value.UuidBytes), false); WriteObject(name, Convert.ToBase64String(value.UuidBytes), false);
} }
@@ -788,7 +826,7 @@ namespace KeePassLib.Serialization
Debug.Assert(value.Kind == DateTimeKind.Utc); Debug.Assert(value.Kind == DateTimeKind.Utc);
// Cf. ReadTime // Cf. ReadTime
if((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4)) if ((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4))
{ {
DateTime dt = TimeUtil.ToUtc(value, false); DateTime dt = TimeUtil.ToUtc(value, false);
@@ -805,29 +843,33 @@ namespace KeePassLib.Serialization
byte[] pb = MemUtil.Int64ToBytes(lSec); byte[] pb = MemUtil.Int64ToBytes(lSec);
WriteObject(name, Convert.ToBase64String(pb), false); WriteObject(name, Convert.ToBase64String(pb), false);
} }
else WriteObject(name, TimeUtil.SerializeUtc(value), false); else WriteObject(name, TimeUtil.SerializeUtc(value), false);
} }
private void WriteObject(string name, string strKeyName, private void WriteObject(string name, string strKeyName, string strValueName,
string strValueName, KeyValuePair<string, string> kvp) KeyValuePair<string, string> kvp, DateTime? odtLastMod)
{ {
m_xmlWriter.WriteStartElement(name); m_xmlWriter.WriteStartElement(name);
m_xmlWriter.WriteStartElement(strKeyName); m_xmlWriter.WriteStartElement(strKeyName);
m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Key)); m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Key));
m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteEndElement();
m_xmlWriter.WriteStartElement(strValueName); m_xmlWriter.WriteStartElement(strValueName);
m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Value)); m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Value));
m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteEndElement();
if (odtLastMod.HasValue)
WriteObject(ElemLastModTime, odtLastMod.Value);
m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteEndElement();
} }
private void WriteObject(string name, ProtectedString value, bool bIsEntryString) private void WriteObject(string name, ProtectedString value, bool bIsEntryString)
{ {
Debug.Assert(name != null); Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(ElemString); m_xmlWriter.WriteStartElement(ElemString);
m_xmlWriter.WriteStartElement(ElemKey); m_xmlWriter.WriteStartElement(ElemKey);
@@ -836,30 +878,30 @@ namespace KeePassLib.Serialization
m_xmlWriter.WriteStartElement(ElemValue); m_xmlWriter.WriteStartElement(ElemValue);
bool bProtected = value.IsProtected; bool bProtected = value.IsProtected;
if(bIsEntryString) if (bIsEntryString)
{ {
// Adjust memory protection setting (which might be different // Adjust memory protection setting (which might be different
// from the database default, e.g. due to an import which // from the database default, e.g. due to an import which
// didn't specify the correct setting) // didn't specify the correct setting)
if(name == PwDefs.TitleField) if (name == PwDefs.TitleField)
bProtected = m_pwDatabase.MemoryProtection.ProtectTitle; bProtected = m_pwDatabase.MemoryProtection.ProtectTitle;
else if(name == PwDefs.UserNameField) else if (name == PwDefs.UserNameField)
bProtected = m_pwDatabase.MemoryProtection.ProtectUserName; bProtected = m_pwDatabase.MemoryProtection.ProtectUserName;
else if(name == PwDefs.PasswordField) else if (name == PwDefs.PasswordField)
bProtected = m_pwDatabase.MemoryProtection.ProtectPassword; bProtected = m_pwDatabase.MemoryProtection.ProtectPassword;
else if(name == PwDefs.UrlField) else if (name == PwDefs.UrlField)
bProtected = m_pwDatabase.MemoryProtection.ProtectUrl; bProtected = m_pwDatabase.MemoryProtection.ProtectUrl;
else if(name == PwDefs.NotesField) else if (name == PwDefs.NotesField)
bProtected = m_pwDatabase.MemoryProtection.ProtectNotes; bProtected = m_pwDatabase.MemoryProtection.ProtectNotes;
} }
if(bProtected && (m_format == KdbxFormat.Default)) if (bProtected && (m_format == KdbxFormat.Default))
{ {
m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue); m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue);
byte[] pbEncoded = value.ReadXorredString(m_randomStream); byte[] pbEnc = value.ReadXorredString(m_randomStream);
if(pbEncoded.Length > 0) if (pbEnc.Length > 0)
m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length); m_xmlWriter.WriteBase64(pbEnc, 0, pbEnc.Length);
} }
else else
{ {
@@ -869,25 +911,24 @@ namespace KeePassLib.Serialization
// string transformation here. By default, language-dependent conversions // string transformation here. By default, language-dependent conversions
// should be applied, otherwise characters could be rendered incorrectly // should be applied, otherwise characters could be rendered incorrectly
// (code page problems). // (code page problems).
if(m_bLocalizedNames) if (g_bLocalizedNames)
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
foreach(char ch in strValue) foreach (char ch in strValue)
{ {
char chMapped = ch; char chMapped = ch;
// Symbols and surrogates must be moved into the correct code // Symbols and surrogates must be moved into the correct code
// page area // page area
if(char.IsSymbol(ch) || char.IsSurrogate(ch)) if (char.IsSymbol(ch) || char.IsSurrogate(ch))
{ {
System.Globalization.UnicodeCategory cat = UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory(ch);
CharUnicodeInfo.GetUnicodeCategory(ch);
// Map character to correct position in code page // Map character to correct position in code page
chMapped = (char)((int)cat * 32 + ch); chMapped = (char)((int)cat * 32 + ch);
} }
else if(char.IsControl(ch)) else if (char.IsControl(ch))
{ {
if(ch >= 256) // Control character in high ANSI code page if (ch >= 256) // Control character in high ANSI code page
{ {
// Some of the control characters map to corresponding ones // Some of the control characters map to corresponding ones
// in the low ANSI range (up to 255) when calling // in the low ANSI range (up to 255) when calling
@@ -907,7 +948,7 @@ namespace KeePassLib.Serialization
strValue = sb.ToString(); // Correct string for current code page strValue = sb.ToString(); // Correct string for current code page
} }
if((m_format == KdbxFormat.PlainXml) && bProtected) if ((m_format == KdbxFormat.PlainXml) && bProtected)
m_xmlWriter.WriteAttributeString(AttrProtectedInMemPlainXml, ValTrue); m_xmlWriter.WriteAttributeString(AttrProtectedInMemPlainXml, ValTrue);
m_xmlWriter.WriteString(StrUtil.SafeXmlString(strValue)); m_xmlWriter.WriteString(StrUtil.SafeXmlString(strValue));
@@ -920,7 +961,7 @@ namespace KeePassLib.Serialization
private void WriteObject(string name, ProtectedBinary value, bool bAllowRef) private void WriteObject(string name, ProtectedBinary value, bool bAllowRef)
{ {
Debug.Assert(name != null); Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(ElemBinary); m_xmlWriter.WriteStartElement(ElemBinary);
m_xmlWriter.WriteStartElement(ElemKey); m_xmlWriter.WriteStartElement(ElemKey);
@@ -929,13 +970,13 @@ namespace KeePassLib.Serialization
m_xmlWriter.WriteStartElement(ElemValue); m_xmlWriter.WriteStartElement(ElemValue);
string strRef = null; string strRef = null;
if(bAllowRef) if (bAllowRef)
{ {
int iRef = m_pbsBinaries.Find(value); int iRef = m_pbsBinaries.Find(value);
if(iRef >= 0) strRef = iRef.ToString(NumberFormatInfo.InvariantInfo); if (iRef >= 0) strRef = iRef.ToString(NumberFormatInfo.InvariantInfo);
else { Debug.Assert(false); } else { Debug.Assert(false); }
} }
if(strRef != null) if (strRef != null)
m_xmlWriter.WriteAttributeString(AttrRef, strRef); m_xmlWriter.WriteAttributeString(AttrRef, strRef);
else SubWriteValue(value); else SubWriteValue(value);
@@ -945,17 +986,17 @@ namespace KeePassLib.Serialization
private void SubWriteValue(ProtectedBinary value) private void SubWriteValue(ProtectedBinary value)
{ {
if(value.IsProtected && (m_format == KdbxFormat.Default)) if (value.IsProtected && (m_format == KdbxFormat.Default))
{ {
m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue); m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue);
byte[] pbEncoded = value.ReadXorredData(m_randomStream); byte[] pbEnc = value.ReadXorredData(m_randomStream);
if(pbEncoded.Length > 0) if (pbEnc.Length > 0)
m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length); m_xmlWriter.WriteBase64(pbEnc, 0, pbEnc.Length);
} }
else else
{ {
if(m_pwDatabase.Compression != PwCompressionAlgorithm.None) if (m_pwDatabase.Compression != PwCompressionAlgorithm.None)
{ {
m_xmlWriter.WriteAttributeString(AttrCompressed, ValTrue); m_xmlWriter.WriteAttributeString(AttrCompressed, ValTrue);
@@ -963,18 +1004,18 @@ namespace KeePassLib.Serialization
byte[] pbCmp = MemUtil.Compress(pbRaw); byte[] pbCmp = MemUtil.Compress(pbRaw);
m_xmlWriter.WriteBase64(pbCmp, 0, pbCmp.Length); m_xmlWriter.WriteBase64(pbCmp, 0, pbCmp.Length);
if(value.IsProtected) if (value.IsProtected)
{ {
MemUtil.ZeroByteArray(pbRaw); MemUtil.ZeroByteArray(pbRaw);
MemUtil.ZeroByteArray(pbCmp); MemUtil.ZeroByteArray(pbCmp);
} }
} }
else else
{ {
byte[] pbRaw = value.ReadData(); byte[] pbRaw = value.ReadData();
m_xmlWriter.WriteBase64(pbRaw, 0, pbRaw.Length); m_xmlWriter.WriteBase64(pbRaw, 0, pbRaw.Length);
if(value.IsProtected) MemUtil.ZeroByteArray(pbRaw); if (value.IsProtected) MemUtil.ZeroByteArray(pbRaw);
} }
} }
} }
@@ -982,7 +1023,7 @@ namespace KeePassLib.Serialization
private void WriteObject(string name, PwDeletedObject value) private void WriteObject(string name, PwDeletedObject value)
{ {
Debug.Assert(name != null); Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value"); Debug.Assert(value != null); if (value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name); m_xmlWriter.WriteStartElement(name);
WriteObject(ElemUuid, value.Uuid); WriteObject(ElemUuid, value.Uuid);
@@ -995,7 +1036,7 @@ namespace KeePassLib.Serialization
m_xmlWriter.WriteStartElement(ElemBinaries); m_xmlWriter.WriteStartElement(ElemBinaries);
ProtectedBinary[] v = m_pbsBinaries.ToArray(); ProtectedBinary[] v = m_pbsBinaries.ToArray();
for(int i = 0; i < v.Length; ++i) for (int i = 0; i < v.Length; ++i)
{ {
m_xmlWriter.WriteStartElement(ElemBinary); m_xmlWriter.WriteStartElement(ElemBinary);
m_xmlWriter.WriteAttributeString(AttrId, m_xmlWriter.WriteAttributeString(AttrId,
@@ -1007,6 +1048,25 @@ namespace KeePassLib.Serialization
m_xmlWriter.WriteEndElement(); m_xmlWriter.WriteEndElement();
} }
internal static void WriteGroup(Stream msOutput, PwDatabase pdContext,
PwGroup pg)
{
if (msOutput == null) throw new ArgumentNullException("msOutput");
// pdContext may be null
if (pg == null) throw new ArgumentNullException("pg");
PwDatabase pd = new PwDatabase();
pd.New(new IOConnectionInfo(), new CompositeKey(), pg.Name);
pd.RootGroup = pg.CloneDeep();
pd.RootGroup.ParentGroup = null;
PwDatabase.CopyCustomIcons(pdContext, pd, pd.RootGroup, true);
KdbxFile f = new KdbxFile(pd);
f.Save(msOutput, null, KdbxFormat.PlainXml, null);
}
[Obsolete] [Obsolete]
public static bool WriteEntries(Stream msOutput, PwEntry[] vEntries) public static bool WriteEntries(Stream msOutput, PwEntry[] vEntries)
{ {
@@ -1014,66 +1074,21 @@ namespace KeePassLib.Serialization
} }
public static bool WriteEntries(Stream msOutput, PwDatabase pdContext, public static bool WriteEntries(Stream msOutput, PwDatabase pdContext,
PwEntry[] vEntries) PwEntry[] vEntries)
{ {
if (msOutput == null) { Debug.Assert(false); return false; } if (msOutput == null) { Debug.Assert(false); return false; }
// pdContext may be null // pdContext may be null
if (vEntries == null) { Debug.Assert(false); return false; } if (vEntries == null) { Debug.Assert(false); return false; }
/* KdbxFile f = new KdbxFile(pwDatabase); PwGroup pg = new PwGroup(true, true);
f.m_format = KdbxFormat.PlainXml;
XmlTextWriter xtw = null;
try { xtw = new XmlTextWriter(msOutput, StrUtil.Utf8); }
catch(Exception) { Debug.Assert(false); return false; }
if(xtw == null) { Debug.Assert(false); return false; }
f.m_xmlWriter = xtw;
xtw.Formatting = Formatting.Indented;
xtw.IndentChar = '\t';
xtw.Indentation = 1;
xtw.WriteStartDocument(true);
xtw.WriteStartElement(ElemRoot);
foreach(PwEntry pe in vEntries)
f.WriteEntry(pe, false);
xtw.WriteEndElement();
xtw.WriteEndDocument();
xtw.Flush();
xtw.Close();
return true; */
PwDatabase pd = new PwDatabase();
pd.New(new IOConnectionInfo(), new CompositeKey(), "");
PwGroup pg = pd.RootGroup;
if (pg == null) { Debug.Assert(false); return false; }
foreach (PwEntry pe in vEntries) foreach (PwEntry pe in vEntries)
{ {
PwUuid pu = pe.CustomIconUuid;
if (!pu.Equals(PwUuid.Zero) && (pd.GetCustomIconIndex(pu) < 0))
{
int i = -1;
if (pdContext != null) i = pdContext.GetCustomIconIndex(pu);
if (i >= 0)
{
PwCustomIcon ci = pdContext.CustomIcons[i];
pd.CustomIcons.Add(ci);
}
else { Debug.Assert(pdContext == null); }
}
PwEntry peCopy = pe.CloneDeep(); PwEntry peCopy = pe.CloneDeep();
pg.AddEntry(peCopy, true); pg.AddEntry(peCopy, true);
} }
KdbxFile f = new KdbxFile(pd); WriteGroup(msOutput, pdContext, pg);
f.Save(msOutput, null, KdbxFormat.PlainXml, null);
return true; return true;
} }
} }

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -67,47 +67,6 @@ namespace KeePassLib.Serialization
/// </summary> /// </summary>
public sealed partial class KdbxFile public sealed partial class KdbxFile
{ {
private class ColorTranslator
{
public static Color FromHtml(String colorString)
{
Color color;
if (colorString.StartsWith("#"))
{
colorString = colorString.Substring(1);
}
if (colorString.EndsWith(";"))
{
colorString = colorString.Substring(0, colorString.Length - 1);
}
int red, green, blue;
switch (colorString.Length)
{
case 6:
red = int.Parse(colorString.Substring(0, 2), System.Globalization.NumberStyles.HexNumber);
green = int.Parse(colorString.Substring(2, 2), System.Globalization.NumberStyles.HexNumber);
blue = int.Parse(colorString.Substring(4, 2), System.Globalization.NumberStyles.HexNumber);
color = Color.FromArgb(red, green, blue);
break;
case 3:
red = int.Parse(colorString.Substring(0, 1), System.Globalization.NumberStyles.HexNumber);
green = int.Parse(colorString.Substring(1, 1), System.Globalization.NumberStyles.HexNumber);
blue = int.Parse(colorString.Substring(2, 1), System.Globalization.NumberStyles.HexNumber);
color = Color.FromArgb(red, green, blue);
break;
case 1:
red = green = blue = int.Parse(colorString.Substring(0, 1), System.Globalization.NumberStyles.HexNumber);
color = Color.FromArgb(red, green, blue);
break;
default:
throw new ArgumentException("Invalid color: " + colorString);
}
return color;
}
}
/// <summary> /// <summary>
/// File identifier, first 32-bit value. /// File identifier, first 32-bit value.
/// </summary> /// </summary>
@@ -119,16 +78,17 @@ namespace KeePassLib.Serialization
internal const uint FileSignature2 = 0xB54BFB67; internal const uint FileSignature2 = 0xB54BFB67;
/// <summary> /// <summary>
/// File version of files saved by the current <c>KdbxFile</c> class. /// Maximum supported version of database files.
/// KeePass 2.07 has version 1.01, 2.08 has 1.02, 2.09 has 2.00, /// KeePass 2.07 has version 1.01, 2.08 has 1.02, 2.09 has 2.00,
/// 2.10 has 2.02, 2.11 has 2.04, 2.15 has 3.00, 2.20 has 3.01. /// 2.10 has 2.02, 2.11 has 2.04, 2.15 has 3.00, 2.20 has 3.01.
/// The first 2 bytes are critical (i.e. loading will fail, if the /// The first 2 bytes are critical (i.e. loading will fail, if the
/// file version is too high), the last 2 bytes are informational. /// file version is too high), the last 2 bytes are informational.
/// </summary> /// </summary>
private const uint FileVersion32 = 0x00040000; private const uint FileVersion32 = 0x00040001;
public const uint FileVersion32_4 = 0x00040000; // First of 4.x series public const uint FileVersion32_4_1 = 0x00040001; // 4.1
public const uint FileVersion32_3 = 0x00030001; // Old format 3.1 public const uint FileVersion32_4 = 0x00040000; // 4.0
public const uint FileVersion32_3_1 = 0x00030001; // 3.1
private const uint FileVersionCriticalMask = 0xFFFF0000; private const uint FileVersionCriticalMask = 0xFFFF0000;
@@ -143,7 +103,7 @@ namespace KeePassLib.Serialization
private const string ElemMeta = "Meta"; private const string ElemMeta = "Meta";
private const string ElemRoot = "Root"; private const string ElemRoot = "Root";
private const string ElemGroup = "Group"; private const string ElemGroup = "Group";
private const string ElemEntry = "Entry"; internal const string ElemEntry = "Entry";
private const string ElemGenerator = "Generator"; private const string ElemGenerator = "Generator";
private const string ElemHeaderHash = "HeaderHash"; private const string ElemHeaderHash = "HeaderHash";
@@ -188,12 +148,13 @@ namespace KeePassLib.Serialization
private const string ElemName = "Name"; private const string ElemName = "Name";
private const string ElemNotes = "Notes"; private const string ElemNotes = "Notes";
private const string ElemUuid = "UUID"; internal const string ElemUuid = "UUID";
private const string ElemIcon = "IconID"; private const string ElemIcon = "IconID";
private const string ElemCustomIconID = "CustomIconUUID"; private const string ElemCustomIconID = "CustomIconUUID";
private const string ElemFgColor = "ForegroundColor"; private const string ElemFgColor = "ForegroundColor";
private const string ElemBgColor = "BackgroundColor"; private const string ElemBgColor = "BackgroundColor";
private const string ElemOverrideUrl = "OverrideURL"; private const string ElemOverrideUrl = "OverrideURL";
private const string ElemQualityCheck = "QualityCheck";
private const string ElemTimes = "Times"; private const string ElemTimes = "Times";
private const string ElemTags = "Tags"; private const string ElemTags = "Tags";
@@ -205,6 +166,8 @@ namespace KeePassLib.Serialization
private const string ElemUsageCount = "UsageCount"; private const string ElemUsageCount = "UsageCount";
private const string ElemLocationChanged = "LocationChanged"; private const string ElemLocationChanged = "LocationChanged";
private const string ElemPreviousParentGroup = "PreviousParentGroup";
private const string ElemGroupDefaultAutoTypeSeq = "DefaultAutoTypeSequence"; private const string ElemGroupDefaultAutoTypeSeq = "DefaultAutoTypeSequence";
private const string ElemEnableAutoType = "EnableAutoType"; private const string ElemEnableAutoType = "EnableAutoType";
private const string ElemEnableSearching = "EnableSearching"; private const string ElemEnableSearching = "EnableSearching";
@@ -261,7 +224,7 @@ namespace KeePassLib.Serialization
private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant; private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant;
private byte[] m_pbInnerRandomStreamKey = null; private byte[] m_pbInnerRandomStreamKey = null;
private ProtectedBinarySet m_pbsBinaries = new ProtectedBinarySet(); private ProtectedBinarySet m_pbsBinaries = null;
private byte[] m_pbHashOfHeader = null; private byte[] m_pbHashOfHeader = null;
private byte[] m_pbHashOfFileOnDisk = null; private byte[] m_pbHashOfFileOnDisk = null;
@@ -271,7 +234,7 @@ namespace KeePassLib.Serialization
private const uint NeutralLanguageOffset = 0x100000; // 2^20, see 32-bit Unicode specs private const uint NeutralLanguageOffset = 0x100000; // 2^20, see 32-bit Unicode specs
private const uint NeutralLanguageIDSec = 0x7DC5C; // See 32-bit Unicode specs private const uint NeutralLanguageIDSec = 0x7DC5C; // See 32-bit Unicode specs
private const uint NeutralLanguageID = NeutralLanguageOffset + NeutralLanguageIDSec; private const uint NeutralLanguageID = NeutralLanguageOffset + NeutralLanguageIDSec;
private static bool m_bLocalizedNames = false; private static bool g_bLocalizedNames = false;
private enum KdbxHeaderFieldID : byte private enum KdbxHeaderFieldID : byte
{ {
@@ -345,7 +308,7 @@ namespace KeePassLib.Serialization
public KdbxFile(PwDatabase pwDataStore) public KdbxFile(PwDatabase pwDataStore)
{ {
Debug.Assert(pwDataStore != null); Debug.Assert(pwDataStore != null);
if(pwDataStore == null) throw new ArgumentNullException("pwDataStore"); if (pwDataStore == null) throw new ArgumentNullException("pwDataStore");
m_pwDatabase = pwDataStore; m_pwDatabase = pwDataStore;
} }
@@ -356,57 +319,92 @@ namespace KeePassLib.Serialization
public static void DetermineLanguageId() public static void DetermineLanguageId()
{ {
// Test if localized names should be used. If localized names are used, // Test if localized names should be used. If localized names are used,
// the m_bLocalizedNames value must be set to true. By default, localized // the g_bLocalizedNames value must be set to true. By default, localized
// names should be used! (Otherwise characters could be corrupted // names should be used (otherwise characters could be corrupted
// because of different code pages). // because of different code pages).
unchecked unchecked
{ {
uint uTest = 0; uint uTest = 0;
foreach(char ch in PwDatabase.LocalizedAppName) foreach (char ch in PwDatabase.LocalizedAppName)
uTest = uTest * 5 + ch; uTest = uTest * 5 + ch;
m_bLocalizedNames = (uTest != NeutralLanguageID); g_bLocalizedNames = (uTest != NeutralLanguageID);
} }
} }
private uint GetMinKdbxVersion() private uint GetMinKdbxVersion()
{
if (m_uForceVersion != 0) return m_uForceVersion;
uint minVersionForKeys = m_pwDatabase.MasterKey.UserKeys.Select(key => key.GetMinKdbxVersion()).Max();
uint minRequiredVersion = Math.Max(minVersionForKeys, m_uFileVersion); //don't save a version lower than what we read
return Math.Max(minRequiredVersion, GetMinKdbxVersionOrig());
}
private uint GetMinKdbxVersionOrig()
{ {
if(m_uForceVersion != 0) return m_uForceVersion; if (m_uForceVersion != 0) return m_uForceVersion;
// See also KeePassKdb2x3.Export (KDBX 3.1 export module) // See also KeePassKdb2x3.Export (KDBX 3.1 export module)
uint minVersionForKeys = m_pwDatabase.MasterKey.UserKeys.Select(key => key.GetMinKdbxVersion()).Max();
uint uMin = 0;
uint minRequiredVersion = Math.Max(minVersionForKeys, m_uFileVersion); //don't save a version lower than what we read GroupHandler gh = delegate (PwGroup pg)
AesKdf kdfAes = new AesKdf();
if(!kdfAes.Uuid.Equals(m_pwDatabase.KdfParameters.KdfUuid))
return Math.Max(FileVersion32, minRequiredVersion);
if(m_pwDatabase.PublicCustomData.Count > 0)
return Math.Max(FileVersion32, minRequiredVersion);
bool bCustomData = false;
GroupHandler gh = delegate(PwGroup pg)
{ {
if(pg == null) { Debug.Assert(false); return true; } if (pg == null) { Debug.Assert(false); return true; }
if(pg.CustomData.Count > 0) { bCustomData = true; return false; }
if (pg.Tags.Count != 0)
uMin = Math.Max(uMin, FileVersion32_4_1);
if (pg.CustomData.Count != 0)
uMin = Math.Max(uMin, FileVersion32_4);
return true; return true;
}; };
EntryHandler eh = delegate(PwEntry pe)
EntryHandler eh = delegate (PwEntry pe)
{ {
if(pe == null) { Debug.Assert(false); return true; } if (pe == null) { Debug.Assert(false); return true; }
if(pe.CustomData.Count > 0) { bCustomData = true; return false; }
if (!pe.QualityCheck)
uMin = Math.Max(uMin, FileVersion32_4_1);
if (pe.CustomData.Count != 0)
uMin = Math.Max(uMin, FileVersion32_4);
return true; return true;
}; };
gh(m_pwDatabase.RootGroup); gh(m_pwDatabase.RootGroup);
m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh); m_pwDatabase.RootGroup.TraverseTree(TraversalMethod.PreOrder, gh, eh);
if(bCustomData)
return Math.Max(FileVersion32, minRequiredVersion);
return Math.Max(FileVersion32_3, minRequiredVersion); ; // KDBX 3.1 is sufficient if (uMin >= FileVersion32_4_1) return uMin; // All below is <= 4.1
foreach (PwCustomIcon ci in m_pwDatabase.CustomIcons)
{
if ((ci.Name.Length != 0) || ci.LastModificationTime.HasValue)
return FileVersion32_4_1;
}
foreach (KeyValuePair<string, string> kvp in m_pwDatabase.CustomData)
{
DateTime? odt = m_pwDatabase.CustomData.GetLastModificationTime(kvp.Key);
if (odt.HasValue) return FileVersion32_4_1;
}
if (uMin >= FileVersion32_4) return uMin; // All below is <= 4
if (m_pwDatabase.DataCipherUuid.Equals(ChaCha20Engine.ChaCha20Uuid))
return FileVersion32_4;
AesKdf kdfAes = new AesKdf();
if (!m_pwDatabase.KdfParameters.KdfUuid.Equals(kdfAes.Uuid))
return FileVersion32_4;
if (m_pwDatabase.PublicCustomData.Count != 0)
return FileVersion32_4;
return FileVersion32_3_1; // KDBX 3.1 is sufficient
} }
private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey, private void ComputeKeys(out byte[] pbCipherKey, int cbCipherKey,
@@ -416,12 +414,12 @@ namespace KeePassLib.Serialization
try try
{ {
Debug.Assert(m_pbMasterSeed != null); Debug.Assert(m_pbMasterSeed != null);
if(m_pbMasterSeed == null) if (m_pbMasterSeed == null)
throw new ArgumentNullException("m_pbMasterSeed"); throw new ArgumentNullException("m_pbMasterSeed");
Debug.Assert(m_pbMasterSeed.Length == 32); Debug.Assert(m_pbMasterSeed.Length == 32);
if(m_pbMasterSeed.Length != 32) if (m_pbMasterSeed.Length != 32)
throw new FormatException(KLRes.MasterSeedLengthInvalid); throw new FormatException(KLRes.MasterSeedLengthInvalid);
Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32);
Debug.Assert(m_pwDatabase != null); Debug.Assert(m_pwDatabase != null);
Debug.Assert(m_pwDatabase.MasterKey != null); Debug.Assert(m_pwDatabase.MasterKey != null);
@@ -431,10 +429,10 @@ namespace KeePassLib.Serialization
Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32); Array.Copy(m_pbMasterSeed, 0, pbCmp, 0, 32);
Debug.Assert(pbinUser != null); Debug.Assert(pbinUser != null);
if(pbinUser == null) if (pbinUser == null)
throw new SecurityException(KLRes.InvalidCompositeKey); throw new SecurityException(KLRes.InvalidCompositeKey);
byte[] pUserKey32 = pbinUser.ReadData(); byte[] pUserKey32 = pbinUser.ReadData();
if((pUserKey32 == null) || (pUserKey32.Length != 32)) if ((pUserKey32 == null) || (pUserKey32.Length != 32))
throw new SecurityException(KLRes.InvalidCompositeKey); throw new SecurityException(KLRes.InvalidCompositeKey);
Array.Copy(pUserKey32, 0, pbCmp, 32, 32); Array.Copy(pUserKey32, 0, pbCmp, 32, 32);
MemUtil.ZeroByteArray(pUserKey32); MemUtil.ZeroByteArray(pUserKey32);
@@ -442,7 +440,7 @@ namespace KeePassLib.Serialization
pbCipherKey = CryptoUtil.ResizeKey(pbCmp, 0, 64, cbCipherKey); pbCipherKey = CryptoUtil.ResizeKey(pbCmp, 0, 64, cbCipherKey);
pbCmp[64] = 1; pbCmp[64] = 1;
using(SHA512Managed h = new SHA512Managed()) using (SHA512Managed h = new SHA512Managed())
{ {
pbHmacKey64 = h.ComputeHash(pbCmp); pbHmacKey64 = h.ComputeHash(pbCmp);
} }
@@ -454,19 +452,19 @@ namespace KeePassLib.Serialization
{ {
PwUuid pu = m_pwDatabase.DataCipherUuid; PwUuid pu = m_pwDatabase.DataCipherUuid;
ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu); ICipherEngine iCipher = CipherPool.GlobalPool.GetCipher(pu);
if(iCipher == null) // CryptographicExceptions are translated to "file corrupted" if (iCipher == null) // CryptographicExceptions are translated to "file corrupted"
throw new Exception(KLRes.FileUnknownCipher + throw new Exception(KLRes.FileUnknownCipher +
MessageService.NewParagraph + KLRes.FileNewVerOrPlgReq + MessageService.NewParagraph + KLRes.FileNewVerOrPlgReq +
MessageService.NewParagraph + "UUID: " + pu.ToHexString() + "."); MessageService.NewParagraph + "UUID: " + pu.ToHexString() + ".");
ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2); ICipherEngine2 iCipher2 = (iCipher as ICipherEngine2);
if(iCipher2 != null) if (iCipher2 != null)
{ {
cbEncKey = iCipher2.KeyLength; cbEncKey = iCipher2.KeyLength;
if(cbEncKey < 0) throw new InvalidOperationException("EncKey.Length"); if (cbEncKey < 0) throw new InvalidOperationException("EncKey.Length");
cbEncIV = iCipher2.IVLength; cbEncIV = iCipher2.IVLength;
if(cbEncIV < 0) throw new InvalidOperationException("EncIV.Length"); if (cbEncIV < 0) throw new InvalidOperationException("EncIV.Length");
} }
else else
{ {
@@ -481,13 +479,13 @@ namespace KeePassLib.Serialization
byte[] pbKey, int cbIV, bool bEncrypt) byte[] pbKey, int cbIV, bool bEncrypt)
{ {
byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray); byte[] pbIV = (m_pbEncryptionIV ?? MemUtil.EmptyByteArray);
if(pbIV.Length != cbIV) if (pbIV.Length != cbIV)
{ {
Debug.Assert(false); Debug.Assert(false);
throw new Exception(KLRes.FileCorrupted); throw new Exception(KLRes.FileCorrupted);
} }
if(bEncrypt) if (bEncrypt)
return iCipher.EncryptStream(s, pbKey, pbIV); return iCipher.EncryptStream(s, pbKey, pbIV);
return iCipher.DecryptStream(s, pbKey, pbIV); return iCipher.DecryptStream(s, pbKey, pbIV);
} }
@@ -497,7 +495,7 @@ namespace KeePassLib.Serialization
byte[] pbHeaderHmac; byte[] pbHeaderHmac;
byte[] pbBlockKey = HmacBlockStream.GetHmacKey64( byte[] pbBlockKey = HmacBlockStream.GetHmacKey64(
pbKey, ulong.MaxValue); pbKey, ulong.MaxValue);
using(HMACSHA256 h = new HMACSHA256(pbBlockKey)) using (HMACSHA256 h = new HMACSHA256(pbBlockKey))
{ {
pbHeaderHmac = h.ComputeHash(pbHeader); pbHeaderHmac = h.ComputeHash(pbHeader);
} }
@@ -508,7 +506,7 @@ namespace KeePassLib.Serialization
private void CloseStreams(List<Stream> lStreams) private void CloseStreams(List<Stream> lStreams)
{ {
if(lStreams == null) { Debug.Assert(false); return; } if (lStreams == null) { Debug.Assert(false); return; }
// Typically, closing a stream also closes its base // Typically, closing a stream also closes its base
// stream; however, there may be streams that do not // stream; however, there may be streams that do not
@@ -516,14 +514,14 @@ namespace KeePassLib.Serialization
// we close all streams manually, from the innermost // we close all streams manually, from the innermost
// to the outermost // to the outermost
for(int i = lStreams.Count - 1; i >= 0; --i) for (int i = lStreams.Count - 1; i >= 0; --i)
{ {
// Check for duplicates // Check for duplicates
Debug.Assert((lStreams.IndexOf(lStreams[i]) == i) && Debug.Assert((lStreams.IndexOf(lStreams[i]) == i) &&
(lStreams.LastIndexOf(lStreams[i]) == i)); (lStreams.LastIndexOf(lStreams[i]) == i));
try { lStreams[i].Close(); } try { lStreams[i].Close(); }
catch(Exception) { Debug.Assert(false); } catch (Exception) { Debug.Assert(false); }
} }
// Do not clear the list // Do not clear the list
@@ -531,18 +529,18 @@ namespace KeePassLib.Serialization
private void CleanUpInnerRandomStream() private void CleanUpInnerRandomStream()
{ {
if(m_randomStream != null) m_randomStream.Dispose(); if (m_randomStream != null) m_randomStream.Dispose();
if(m_pbInnerRandomStreamKey != null) if (m_pbInnerRandomStreamKey != null)
MemUtil.ZeroByteArray(m_pbInnerRandomStreamKey); MemUtil.ZeroByteArray(m_pbInnerRandomStreamKey);
} }
private static void SaveBinary(string strName, ProtectedBinary pb, private static void SaveBinary(string strName, ProtectedBinary pb,
string strSaveDir) string strSaveDir)
{ {
if(pb == null) { Debug.Assert(false); return; } if (pb == null) { Debug.Assert(false); return; }
if(string.IsNullOrEmpty(strName)) strName = "File.bin"; strName = UrlUtil.GetSafeFileName(strName);
string strPath; string strPath;
int iTry = 1; int iTry = 1;
@@ -550,31 +548,23 @@ namespace KeePassLib.Serialization
{ {
strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false); strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false);
string strExt = UrlUtil.GetExtension(strName);
string strDesc = UrlUtil.StripExtension(strName); string strDesc = UrlUtil.StripExtension(strName);
string strExt = UrlUtil.GetExtension(strName);
strPath += strDesc; strPath += strDesc;
if(iTry > 1) if (iTry > 1)
strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) + strPath += " (" + iTry.ToString(NumberFormatInfo.InvariantInfo) +
")"; ")";
if(!string.IsNullOrEmpty(strExt)) strPath += "." + strExt; if (!string.IsNullOrEmpty(strExt)) strPath += "." + strExt;
++iTry; ++iTry;
} }
while(File.Exists(strPath)); while (File.Exists(strPath));
#if !KeePassLibSD
byte[] pbData = pb.ReadData(); byte[] pbData = pb.ReadData();
File.WriteAllBytes(strPath, pbData); try { File.WriteAllBytes(strPath, pbData); }
MemUtil.ZeroByteArray(pbData); finally { if (pb.IsProtected) MemUtil.ZeroByteArray(pbData); }
#else
FileStream fs = new FileStream(strPath, FileMode.Create,
FileAccess.Write, FileShare.None);
byte[] pbData = pb.ReadData();
fs.Write(pbData, 0, pbData.Length);
fs.Close();
#endif
} }
} }
} }

View File

@@ -786,5 +786,28 @@ namespace KeePassLib.Utility
yield break; yield break;
} }
internal static bool ListsEqual<T>(List<T> a, List<T> b)
where T : class, IEquatable<T>
{
if (object.ReferenceEquals(a, b)) return true;
if ((a == null) || (b == null)) return false;
int n = a.Count;
if (n != b.Count) return false;
for (int i = 0; i < n; ++i)
{
T tA = a[i], tB = b[i];
if (tA == null)
{
if (tB != null) return false;
}
else if (tB == null) return false;
else if (!tA.Equals(tB)) return false;
}
return true;
}
} }
} }

View File

@@ -429,7 +429,7 @@ Clipboard.SetText(ObjectsToMessage(vLines, true));*/
if ((strFilePath != null) && (strFilePath.Length > 0)) if ((strFilePath != null) && (strFilePath.Length > 0))
str += strFilePath + MessageService.NewParagraph; str += strFilePath + MessageService.NewParagraph;
str += KLRes.FileSaveFailed; str += KLRes.FileSaveFailed2;
if ((ex != null) && (ex.Message != null) && (ex.Message.Length > 0)) if ((ex != null) && (ex.Message != null) && (ex.Message.Length > 0))
str += MessageService.NewParagraph + ex.Message; str += MessageService.NewParagraph + ex.Message;

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
/* /*
KeePass Password Safe - The Open-Source Password Manager KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2017 Dominik Reichl <dominik.reichl@t-online.de> Copyright (C) 2003-2021 Dominik Reichl <dominik.reichl@t-online.de>
This program is free software; you can redistribute it and/or modify This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by it under the terms of the GNU General Public License as published by
@@ -20,8 +20,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
using System.IO; using System.IO;
using System.Text; using System.Text;
using System.Text.RegularExpressions;
using KeePassLib.Native; using KeePassLib.Native;
@@ -33,9 +35,7 @@ namespace KeePassLib.Utility
/// </summary> /// </summary>
public static class UrlUtil public static class UrlUtil
{ {
private static readonly char[] m_vDirSeps = new char[] { private static readonly char[] g_vPathTrimCharsWs = new char[] {
'\\', '/', UrlUtil.LocalDirSepChar };
private static readonly char[] m_vPathTrimCharsWs = new char[] {
'\"', ' ', '\t', '\r', '\n' }; '\"', ' ', '\t', '\r', '\n' };
public static char LocalDirSepChar public static char LocalDirSepChar
@@ -43,6 +43,32 @@ namespace KeePassLib.Utility
get { return Path.DirectorySeparatorChar; } get { return Path.DirectorySeparatorChar; }
} }
private static char[] g_vDirSepChars = null;
private static char[] DirSepChars
{
get
{
if (g_vDirSepChars == null)
{
List<char> l = new List<char>();
l.Add('/'); // For URLs, also on Windows
// On Unix-like systems, '\\' is not a separator
if (!NativeLib.IsUnix()) l.Add('\\');
if (!l.Contains(UrlUtil.LocalDirSepChar))
{
Debug.Assert(false);
l.Add(UrlUtil.LocalDirSepChar);
}
g_vDirSepChars = l.ToArray();
}
return g_vDirSepChars;
}
}
/// <summary> /// <summary>
/// Get the directory (path) of a file name. The returned string may be /// Get the directory (path) of a file name. The returned string may be
/// terminated by a directory separator character. Example: /// terminated by a directory separator character. Example:
@@ -63,16 +89,16 @@ namespace KeePassLib.Utility
bool bEnsureValidDirSpec) bool bEnsureValidDirSpec)
{ {
Debug.Assert(strFile != null); Debug.Assert(strFile != null);
if(strFile == null) throw new ArgumentNullException("strFile"); if (strFile == null) throw new ArgumentNullException("strFile");
int nLastSep = strFile.LastIndexOfAny(m_vDirSeps); int nLastSep = strFile.LastIndexOfAny(UrlUtil.DirSepChars);
if(nLastSep < 0) return string.Empty; // No directory if (nLastSep < 0) return string.Empty; // No directory
if(bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') && if (bEnsureValidDirSpec && (nLastSep == 2) && (strFile[1] == ':') &&
(strFile[2] == '\\')) // Length >= 3 and Windows root directory (strFile[2] == '\\')) // Length >= 3 and Windows root directory
bAppendTerminatingChar = true; bAppendTerminatingChar = true;
if(!bAppendTerminatingChar) return strFile.Substring(0, nLastSep); if (!bAppendTerminatingChar) return strFile.Substring(0, nLastSep);
return EnsureTerminatingSeparator(strFile.Substring(0, nLastSep), return EnsureTerminatingSeparator(strFile.Substring(0, nLastSep),
(strFile[nLastSep] == '/')); (strFile[nLastSep] == '/'));
} }
@@ -87,12 +113,12 @@ namespace KeePassLib.Utility
/// an empty string (<c>""</c>) if the input parameter is <c>null</c>.</returns> /// an empty string (<c>""</c>) if the input parameter is <c>null</c>.</returns>
public static string GetFileName(string strPath) public static string GetFileName(string strPath)
{ {
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
int nLastSep = strPath.LastIndexOfAny(m_vDirSeps); int nLastSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
if(nLastSep < 0) return strPath; if (nLastSep < 0) return strPath;
if(nLastSep >= (strPath.Length - 1)) return string.Empty; if (nLastSep >= (strPath.Length - 1)) return string.Empty;
return strPath.Substring(nLastSep + 1); return strPath.Substring(nLastSep + 1);
} }
@@ -104,12 +130,12 @@ namespace KeePassLib.Utility
/// <returns>File name without extension.</returns> /// <returns>File name without extension.</returns>
public static string StripExtension(string strPath) public static string StripExtension(string strPath)
{ {
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps); int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
int nLastExtDot = strPath.LastIndexOf('.'); int nLastExtDot = strPath.LastIndexOf('.');
if(nLastExtDot <= nLastDirSep) return strPath; if (nLastExtDot <= nLastDirSep) return strPath;
return strPath.Substring(0, nLastExtDot); return strPath.Substring(0, nLastExtDot);
} }
@@ -121,13 +147,13 @@ namespace KeePassLib.Utility
/// <returns>Extension without prepending dot.</returns> /// <returns>Extension without prepending dot.</returns>
public static string GetExtension(string strPath) public static string GetExtension(string strPath)
{ {
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
int nLastDirSep = strPath.LastIndexOfAny(m_vDirSeps); int nLastDirSep = strPath.LastIndexOfAny(UrlUtil.DirSepChars);
int nLastExtDot = strPath.LastIndexOf('.'); int nLastExtDot = strPath.LastIndexOf('.');
if(nLastExtDot <= nLastDirSep) return string.Empty; if (nLastExtDot <= nLastDirSep) return string.Empty;
if(nLastExtDot == (strPath.Length - 1)) return string.Empty; if (nLastExtDot == (strPath.Length - 1)) return string.Empty;
return strPath.Substring(nLastExtDot + 1); return strPath.Substring(nLastExtDot + 1);
} }
@@ -142,19 +168,16 @@ namespace KeePassLib.Utility
/// <returns>Path having a directory separator as last character.</returns> /// <returns>Path having a directory separator as last character.</returns>
public static string EnsureTerminatingSeparator(string strPath, bool bUrl) public static string EnsureTerminatingSeparator(string strPath, bool bUrl)
{ {
Debug.Assert(strPath != null); if(strPath == null) throw new ArgumentNullException("strPath"); Debug.Assert(strPath != null); if (strPath == null) throw new ArgumentNullException("strPath");
int nLength = strPath.Length; int nLength = strPath.Length;
if(nLength <= 0) return string.Empty; if (nLength <= 0) return string.Empty;
char chLast = strPath[nLength - 1]; char chLast = strPath[nLength - 1];
if (Array.IndexOf<char>(UrlUtil.DirSepChars, chLast) >= 0)
return strPath;
for(int i = 0; i < m_vDirSeps.Length; ++i) if (bUrl) return (strPath + '/');
{
if(chLast == m_vDirSeps[i]) return strPath;
}
if(bUrl) return (strPath + '/');
return (strPath + UrlUtil.LocalDirSepChar); return (strPath + UrlUtil.LocalDirSepChar);
} }
@@ -216,55 +239,78 @@ namespace KeePassLib.Utility
return false; return false;
} */ } */
internal static int IndexOfSecondEnclQuote(string str)
{
if (str == null) { Debug.Assert(false); return -1; }
if (str.Length <= 1) return -1;
if (str[0] != '\"') { Debug.Assert(false); return -1; }
if (NativeLib.IsUnix())
{
// Find non-escaped quote
string strFlt = str.Replace("\\\\", new string(
StrUtil.GetUnusedChar(str + "\\\""), 2)); // Same length
Match m = Regex.Match(strFlt, "[^\\\\]\\u0022");
int i = (((m != null) && m.Success) ? m.Index : -1);
return ((i >= 0) ? (i + 1) : -1); // Index of quote
}
// Windows does not allow quotes in folder/file names
return str.IndexOf('\"', 1);
}
public static string GetQuotedAppPath(string strPath) public static string GetQuotedAppPath(string strPath)
{ {
if(strPath == null) { Debug.Assert(false); return string.Empty; } if (strPath == null) { Debug.Assert(false); return string.Empty; }
// int nFirst = strPath.IndexOf('\"');
// int nSecond = strPath.IndexOf('\"', nFirst + 1);
// if((nFirst >= 0) && (nSecond >= 0))
// return strPath.Substring(nFirst + 1, nSecond - nFirst - 1);
// return strPath;
string str = strPath.Trim(); string str = strPath.Trim();
if(str.Length <= 1) return str; if (str.Length <= 1) return str;
if(str[0] != '\"') return str; if (str[0] != '\"') return str;
int iSecond = str.IndexOf('\"', 1); int iSecond = IndexOfSecondEnclQuote(str);
if(iSecond <= 0) return str; if (iSecond <= 0) return str;
return str.Substring(1, iSecond - 1); return str.Substring(1, iSecond - 1);
} }
public static string FileUrlToPath(string strUrl) public static string FileUrlToPath(string strUrl)
{ {
Debug.Assert(strUrl != null); if (strUrl == null) { Debug.Assert(false); throw new ArgumentNullException("strUrl"); }
if(strUrl == null) throw new ArgumentNullException("strUrl"); if (strUrl.Length == 0) { Debug.Assert(false); return string.Empty; }
string str = strUrl; if (!strUrl.StartsWith(Uri.UriSchemeFile + ":", StrUtil.CaseIgnoreCmp))
if(str.StartsWith(@"file:///", StrUtil.CaseIgnoreCmp)) {
str = str.Substring(8, str.Length - 8); Debug.Assert(false);
return strUrl;
}
str = str.Replace('/', UrlUtil.LocalDirSepChar); try
{
Uri uri = new Uri(strUrl);
string str = uri.LocalPath;
if (!string.IsNullOrEmpty(str)) return str;
}
catch (Exception) { Debug.Assert(false); }
return str; Debug.Assert(false);
return strUrl;
} }
public static bool UnhideFile(string strFile) public static bool UnhideFile(string strFile)
{ {
#if (KeePassLibSD || KeePassRT) #if KeePassLibSD
return false; return false;
#else #else
if(strFile == null) throw new ArgumentNullException("strFile"); if (strFile == null) throw new ArgumentNullException("strFile");
try try
{ {
FileAttributes fa = File.GetAttributes(strFile); FileAttributes fa = File.GetAttributes(strFile);
if((long)(fa & FileAttributes.Hidden) == 0) return false; if ((long)(fa & FileAttributes.Hidden) == 0) return false;
return HideFile(strFile, false); return HideFile(strFile, false);
} }
catch(Exception) { } catch (Exception) { }
return false; return false;
#endif #endif
@@ -272,26 +318,26 @@ namespace KeePassLib.Utility
public static bool HideFile(string strFile, bool bHide) public static bool HideFile(string strFile, bool bHide)
{ {
#if (KeePassLibSD || KeePassRT) #if KeePassLibSD
return false; return false;
#else #else
if(strFile == null) throw new ArgumentNullException("strFile"); if (strFile == null) throw new ArgumentNullException("strFile");
try try
{ {
FileAttributes fa = File.GetAttributes(strFile); FileAttributes fa = File.GetAttributes(strFile);
if(bHide) fa = ((fa & ~FileAttributes.Normal) | FileAttributes.Hidden); if (bHide) fa = ((fa & ~FileAttributes.Normal) | FileAttributes.Hidden);
else // Unhide else // Unhide
{ {
fa &= ~FileAttributes.Hidden; fa &= ~FileAttributes.Hidden;
if((long)fa == 0) fa = FileAttributes.Normal; if ((long)fa == 0) fa = FileAttributes.Normal;
} }
File.SetAttributes(strFile, fa); File.SetAttributes(strFile, fa);
return true; return true;
} }
catch(Exception) { } catch (Exception) { }
return false; return false;
#endif #endif
@@ -299,48 +345,47 @@ namespace KeePassLib.Utility
public static string MakeRelativePath(string strBaseFile, string strTargetFile) public static string MakeRelativePath(string strBaseFile, string strTargetFile)
{ {
if(strBaseFile == null) throw new ArgumentNullException("strBasePath"); if (strBaseFile == null) throw new ArgumentNullException("strBasePath");
if(strTargetFile == null) throw new ArgumentNullException("strTargetPath"); if (strTargetFile == null) throw new ArgumentNullException("strTargetPath");
if(strBaseFile.Length == 0) return strTargetFile; if (strBaseFile.Length == 0) return strTargetFile;
if(strTargetFile.Length == 0) return string.Empty; if (strTargetFile.Length == 0) return string.Empty;
// Test whether on different Windows drives // Test whether on different Windows drives
if((strBaseFile.Length >= 3) && (strTargetFile.Length >= 3)) if ((strBaseFile.Length >= 3) && (strTargetFile.Length >= 3))
{ {
if((strBaseFile[1] == ':') && (strTargetFile[1] == ':') && if ((strBaseFile[1] == ':') && (strTargetFile[1] == ':') &&
(strBaseFile[2] == '\\') && (strTargetFile[2] == '\\') && (strBaseFile[2] == '\\') && (strTargetFile[2] == '\\') &&
(strBaseFile[0] != strTargetFile[0])) (strBaseFile[0] != strTargetFile[0]))
return strTargetFile; return strTargetFile;
} }
#if (!KeePassLibSD && !KeePassUAP) #if (!KeePassLibSD && !KeePassUAP)
if(NativeLib.IsUnix()) if (NativeLib.IsUnix())
#endif
{ {
#endif
bool bBaseUnc = IsUncPath(strBaseFile); bool bBaseUnc = IsUncPath(strBaseFile);
bool bTargetUnc = IsUncPath(strTargetFile); bool bTargetUnc = IsUncPath(strTargetFile);
if((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc)) if ((!bBaseUnc && bTargetUnc) || (bBaseUnc && !bTargetUnc))
return strTargetFile; return strTargetFile;
string strBase = GetShortestAbsolutePath(strBaseFile); string strBase = GetShortestAbsolutePath(strBaseFile);
string strTarget = GetShortestAbsolutePath(strTargetFile); string strTarget = GetShortestAbsolutePath(strTargetFile);
string[] vBase = strBase.Split(m_vDirSeps); string[] vBase = strBase.Split(UrlUtil.DirSepChars);
string[] vTarget = strTarget.Split(m_vDirSeps); string[] vTarget = strTarget.Split(UrlUtil.DirSepChars);
int i = 0; int i = 0;
while((i < (vBase.Length - 1)) && (i < (vTarget.Length - 1)) && while ((i < (vBase.Length - 1)) && (i < (vTarget.Length - 1)) &&
(vBase[i] == vTarget[i])) { ++i; } (vBase[i] == vTarget[i])) { ++i; }
StringBuilder sbRel = new StringBuilder(); StringBuilder sbRel = new StringBuilder();
for(int j = i; j < (vBase.Length - 1); ++j) for (int j = i; j < (vBase.Length - 1); ++j)
{ {
if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar); if (sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar);
sbRel.Append(".."); sbRel.Append("..");
} }
for(int k = i; k < vTarget.Length; ++k) for (int k = i; k < vTarget.Length; ++k)
{ {
if(sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar); if (sbRel.Length > 0) sbRel.Append(UrlUtil.LocalDirSepChar);
sbRel.Append(vTarget[k]); sbRel.Append(vTarget[k]);
} }
@@ -352,28 +397,28 @@ namespace KeePassLib.Utility
{ {
const int nMaxPath = NativeMethods.MAX_PATH * 2; const int nMaxPath = NativeMethods.MAX_PATH * 2;
StringBuilder sb = new StringBuilder(nMaxPath + 2); StringBuilder sb = new StringBuilder(nMaxPath + 2);
if(NativeMethods.PathRelativePathTo(sb, strBaseFile, 0, if (!NativeMethods.PathRelativePathTo(sb, strBaseFile, 0,
strTargetFile, 0) == false) strTargetFile, 0))
return strTargetFile; return strTargetFile;
string str = sb.ToString(); string str = sb.ToString();
while(str.StartsWith(".\\")) str = str.Substring(2, str.Length - 2); while (str.StartsWith(".\\")) str = str.Substring(2, str.Length - 2);
return str; return str;
} }
catch(Exception) { Debug.Assert(false); } catch (Exception) { Debug.Assert(false); }
return strTargetFile; return strTargetFile;
#endif #endif
} }
public static string MakeAbsolutePath(string strBaseFile, string strTargetFile) public static string MakeAbsolutePath(string strBaseFile, string strTargetFile)
{ {
if(strBaseFile == null) throw new ArgumentNullException("strBasePath"); if (strBaseFile == null) throw new ArgumentNullException("strBasePath");
if(strTargetFile == null) throw new ArgumentNullException("strTargetPath"); if (strTargetFile == null) throw new ArgumentNullException("strTargetPath");
if(strBaseFile.Length == 0) return strTargetFile; if (strBaseFile.Length == 0) return strTargetFile;
if(strTargetFile.Length == 0) return string.Empty; if (strTargetFile.Length == 0) return string.Empty;
if(IsAbsolutePath(strTargetFile)) return strTargetFile; if (IsAbsolutePath(strTargetFile)) return strTargetFile;
string strBaseDir = GetFileDirectory(strBaseFile, true, false); string strBaseDir = GetFileDirectory(strBaseFile, true, false);
return GetShortestAbsolutePath(strBaseDir + strTargetFile); return GetShortestAbsolutePath(strBaseDir + strTargetFile);
@@ -381,55 +426,56 @@ namespace KeePassLib.Utility
public static bool IsAbsolutePath(string strPath) public static bool IsAbsolutePath(string strPath)
{ {
if(strPath == null) throw new ArgumentNullException("strPath"); if (strPath == null) throw new ArgumentNullException("strPath");
if(strPath.Length == 0) return false; if (strPath.Length == 0) return false;
if(IsUncPath(strPath)) return true; if (IsUncPath(strPath)) return true;
try { return Path.IsPathRooted(strPath); } try { return Path.IsPathRooted(strPath); }
catch(Exception) { Debug.Assert(false); } catch (Exception) { Debug.Assert(false); }
return true; return true;
} }
public static string GetShortestAbsolutePath(string strPath) public static string GetShortestAbsolutePath(string strPath)
{ {
if(strPath == null) throw new ArgumentNullException("strPath"); if (strPath == null) throw new ArgumentNullException("strPath");
if(strPath.Length == 0) return string.Empty; if (strPath.Length == 0) return string.Empty;
// Path.GetFullPath is incompatible with UNC paths traversing over // Path.GetFullPath is incompatible with UNC paths traversing over
// different server shares (which are created by PathRelativePathTo); // different server shares (which are created by PathRelativePathTo);
// we need to build the absolute path on our own... // we need to build the absolute path on our own...
if(IsUncPath(strPath)) if (IsUncPath(strPath))
{ {
char chSep = strPath[0]; char chSep = strPath[0];
Debug.Assert(Array.IndexOf<char>(m_vDirSeps, chSep) >= 0); char[] vSep = ((chSep == '/') ? (new char[] { '/' }) :
(new char[] { '\\', '/' }));
List<string> l = new List<string>(); List<string> l = new List<string>();
#if !KeePassLibSD #if !KeePassLibSD
string[] v = strPath.Split(m_vDirSeps, StringSplitOptions.None); string[] v = strPath.Split(vSep, StringSplitOptions.None);
#else #else
string[] v = strPath.Split(m_vDirSeps); string[] v = strPath.Split(vSep);
#endif #endif
Debug.Assert((v.Length >= 3) && (v[0].Length == 0) && Debug.Assert((v.Length >= 3) && (v[0].Length == 0) &&
(v[1].Length == 0)); (v[1].Length == 0));
foreach(string strPart in v) foreach (string strPart in v)
{ {
if(strPart.Equals(".")) continue; if (strPart.Equals(".")) continue;
else if(strPart.Equals("..")) else if (strPart.Equals(".."))
{ {
if(l.Count > 0) l.RemoveAt(l.Count - 1); if (l.Count > 0) l.RemoveAt(l.Count - 1);
else { Debug.Assert(false); } else { Debug.Assert(false); }
} }
else l.Add(strPart); // Do not ignore zero length parts else l.Add(strPart); // Do not ignore zero length parts
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for(int i = 0; i < l.Count; ++i) for (int i = 0; i < l.Count; ++i)
{ {
// Don't test length of sb, might be 0 due to initial UNC seps // Don't test length of sb, might be 0 due to initial UNC seps
if(i > 0) sb.Append(chSep); if (i > 0) sb.Append(chSep);
sb.Append(l[i]); sb.Append(l[i]);
} }
@@ -438,20 +484,11 @@ namespace KeePassLib.Utility
} }
string str; string str;
try try { str = Path.GetFullPath(strPath); }
{ catch (Exception) { Debug.Assert(false); return strPath; }
#if KeePassRT
var dirT = Windows.Storage.StorageFolder.GetFolderFromPathAsync(
strPath).AwaitEx();
str = dirT.Path;
#else
str = Path.GetFullPath(strPath);
#endif
}
catch(Exception) { Debug.Assert(false); return strPath; }
Debug.Assert(str.IndexOf("\\..\\") < 0); Debug.Assert((str.IndexOf("\\..\\") < 0) || NativeLib.IsUnix());
foreach(char ch in m_vDirSeps) foreach (char ch in UrlUtil.DirSepChars)
{ {
string strSep = new string(ch, 1); string strSep = new string(ch, 1);
str = str.Replace(strSep + "." + strSep, strSep); str = str.Replace(strSep + "." + strSep, strSep);
@@ -462,17 +499,17 @@ namespace KeePassLib.Utility
public static int GetUrlLength(string strText, int nOffset) public static int GetUrlLength(string strText, int nOffset)
{ {
if(strText == null) throw new ArgumentNullException("strText"); if (strText == null) throw new ArgumentNullException("strText");
if(nOffset > strText.Length) throw new ArgumentException(); // Not >= (0 len) if (nOffset > strText.Length) throw new ArgumentException(); // Not >= (0 len)
int iPosition = nOffset, nLength = 0, nStrLen = strText.Length; int iPosition = nOffset, nLength = 0, nStrLen = strText.Length;
while(iPosition < nStrLen) while (iPosition < nStrLen)
{ {
char ch = strText[iPosition]; char ch = strText[iPosition];
++iPosition; ++iPosition;
if((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n')) if ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'))
break; break;
++nLength; ++nLength;
@@ -481,24 +518,30 @@ namespace KeePassLib.Utility
return nLength; return nLength;
} }
internal static string GetScheme(string strUrl)
{
if (string.IsNullOrEmpty(strUrl)) return string.Empty;
int i = strUrl.IndexOf(':');
if (i > 0) return strUrl.Substring(0, i);
return string.Empty;
}
public static string RemoveScheme(string strUrl) public static string RemoveScheme(string strUrl)
{ {
if(string.IsNullOrEmpty(strUrl)) return string.Empty; if (string.IsNullOrEmpty(strUrl)) return string.Empty;
int nNetScheme = strUrl.IndexOf(@"://", StrUtil.CaseIgnoreCmp); int i = strUrl.IndexOf(':');
int nShScheme = strUrl.IndexOf(@":/", StrUtil.CaseIgnoreCmp); if (i < 0) return strUrl; // No scheme to remove
int nSmpScheme = strUrl.IndexOf(@":", StrUtil.CaseIgnoreCmp); ++i;
if((nNetScheme < 0) && (nShScheme < 0) && (nSmpScheme < 0)) // A single '/' indicates a path (absolute) and should not be removed
return strUrl; // No scheme if (((i + 1) < strUrl.Length) && (strUrl[i] == '/') &&
(strUrl[i + 1] == '/'))
i += 2; // Skip authority prefix
int nMin = Math.Min(Math.Min((nNetScheme >= 0) ? nNetScheme : int.MaxValue, return strUrl.Substring(i);
(nShScheme >= 0) ? nShScheme : int.MaxValue),
(nSmpScheme >= 0) ? nSmpScheme : int.MaxValue);
if(nMin == nNetScheme) return strUrl.Substring(nMin + 3);
if(nMin == nShScheme) return strUrl.Substring(nMin + 2);
return strUrl.Substring(nMin + 1);
} }
public static string ConvertSeparators(string strPath) public static string ConvertSeparators(string strPath)
@@ -508,7 +551,7 @@ namespace KeePassLib.Utility
public static string ConvertSeparators(string strPath, char chSeparator) public static string ConvertSeparators(string strPath, char chSeparator)
{ {
if(string.IsNullOrEmpty(strPath)) return string.Empty; if (string.IsNullOrEmpty(strPath)) return string.Empty;
strPath = strPath.Replace('/', chSeparator); strPath = strPath.Replace('/', chSeparator);
strPath = strPath.Replace('\\', chSeparator); strPath = strPath.Replace('\\', chSeparator);
@@ -518,33 +561,61 @@ namespace KeePassLib.Utility
public static bool IsUncPath(string strPath) public static bool IsUncPath(string strPath)
{ {
if(strPath == null) throw new ArgumentNullException("strPath"); if (strPath == null) throw new ArgumentNullException("strPath");
return (strPath.StartsWith("\\\\") || strPath.StartsWith("//")); return (strPath.StartsWith("\\\\") || strPath.StartsWith("//"));
} }
public static string FilterFileName(string strName) public static string FilterFileName(string strName)
{ {
if(strName == null) { Debug.Assert(false); return string.Empty; } if (string.IsNullOrEmpty(strName)) { Debug.Assert(false); return string.Empty; }
string str = strName; // https://docs.microsoft.com/en-us/windows/desktop/fileio/naming-a-file
str = str.Replace('/', '-'); StringBuilder sb = new StringBuilder(strName.Length);
str = str.Replace('\\', '-'); foreach (char ch in strName)
str = str.Replace(":", string.Empty); {
str = str.Replace("*", string.Empty); if (ch < '\u0020') continue;
str = str.Replace("?", string.Empty);
str = str.Replace("\"", string.Empty);
str = str.Replace(@"'", string.Empty);
str = str.Replace('<', '(');
str = str.Replace('>', ')');
str = str.Replace('|', '-');
return str; switch (ch)
{
case '\"':
case '*':
case ':':
case '?':
break;
case '/':
case '\\':
case '|':
sb.Append('-');
break;
case '<':
sb.Append('(');
break;
case '>':
sb.Append(')');
break;
default: sb.Append(ch); break;
}
}
// Trim trailing spaces and periods
for (int i = sb.Length - 1; i >= 0; --i)
{
char ch = sb[i];
if ((ch == ' ') || (ch == '.')) sb.Remove(i, 1);
else break;
}
return sb.ToString();
} }
/// <summary> /// <summary>
/// Get the host component of an URL. /// Get the host component of a URL.
/// This method is faster and more fault-tolerant than creating /// This method is faster and more fault-tolerant than creating
/// an <code>Uri</code> object and querying its <code>Host</code> /// an <code>Uri</code> object and querying its <code>Host</code>
/// property. /// property.
@@ -555,60 +626,60 @@ namespace KeePassLib.Utility
/// </example> /// </example>
public static string GetHost(string strUrl) public static string GetHost(string strUrl)
{ {
if(strUrl == null) { Debug.Assert(false); return string.Empty; } if (strUrl == null) { Debug.Assert(false); return string.Empty; }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
bool bInExtHost = false; bool bInExtHost = false;
for(int i = 0; i < strUrl.Length; ++i) for (int i = 0; i < strUrl.Length; ++i)
{ {
char ch = strUrl[i]; char ch = strUrl[i];
if(bInExtHost) if (bInExtHost)
{ {
if(ch == '/') if (ch == '/')
{ {
if(sb.Length == 0) { } // Ignore leading '/'s if (sb.Length == 0) { } // Ignore leading '/'s
else break; else break;
} }
else sb.Append(ch); else sb.Append(ch);
} }
else // !bInExtHost else // !bInExtHost
{ {
if(ch == ':') bInExtHost = true; if (ch == ':') bInExtHost = true;
} }
} }
string str = sb.ToString(); string str = sb.ToString();
if(str.Length == 0) str = strUrl; if (str.Length == 0) str = strUrl;
// Remove the login part // Remove the login part
int nLoginLen = str.IndexOf('@'); int nLoginLen = str.IndexOf('@');
if(nLoginLen >= 0) str = str.Substring(nLoginLen + 1); if (nLoginLen >= 0) str = str.Substring(nLoginLen + 1);
// Remove the port // Remove the port
int iPort = str.LastIndexOf(':'); int iPort = str.LastIndexOf(':');
if(iPort >= 0) str = str.Substring(0, iPort); if (iPort >= 0) str = str.Substring(0, iPort);
return str; return str;
} }
public static bool AssemblyEquals(string strExt, string strShort) public static bool AssemblyEquals(string strExt, string strShort)
{ {
if((strExt == null) || (strShort == null)) { Debug.Assert(false); return false; } if ((strExt == null) || (strShort == null)) { Debug.Assert(false); return false; }
if(strExt.Equals(strShort, StrUtil.CaseIgnoreCmp) || if (strExt.Equals(strShort, StrUtil.CaseIgnoreCmp) ||
strExt.StartsWith(strShort + ",", StrUtil.CaseIgnoreCmp)) strExt.StartsWith(strShort + ",", StrUtil.CaseIgnoreCmp))
return true; return true;
if(!strShort.EndsWith(".dll", StrUtil.CaseIgnoreCmp)) if (!strShort.EndsWith(".dll", StrUtil.CaseIgnoreCmp))
{ {
if(strExt.Equals(strShort + ".dll", StrUtil.CaseIgnoreCmp) || if (strExt.Equals(strShort + ".dll", StrUtil.CaseIgnoreCmp) ||
strExt.StartsWith(strShort + ".dll,", StrUtil.CaseIgnoreCmp)) strExt.StartsWith(strShort + ".dll,", StrUtil.CaseIgnoreCmp))
return true; return true;
} }
if(!strShort.EndsWith(".exe", StrUtil.CaseIgnoreCmp)) if (!strShort.EndsWith(".exe", StrUtil.CaseIgnoreCmp))
{ {
if(strExt.Equals(strShort + ".exe", StrUtil.CaseIgnoreCmp) || if (strExt.Equals(strShort + ".exe", StrUtil.CaseIgnoreCmp) ||
strExt.StartsWith(strShort + ".exe,", StrUtil.CaseIgnoreCmp)) strExt.StartsWith(strShort + ".exe,", StrUtil.CaseIgnoreCmp))
return true; return true;
} }
@@ -619,7 +690,7 @@ namespace KeePassLib.Utility
public static string GetTempPath() public static string GetTempPath()
{ {
string strDir; string strDir;
if(NativeLib.IsUnix()) if (NativeLib.IsUnix())
strDir = NativeMethods.GetUserRuntimeDir(); strDir = NativeMethods.GetUserRuntimeDir();
#if KeePassUAP #if KeePassUAP
else strDir = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path; else strDir = Windows.Storage.ApplicationData.Current.TemporaryFolder.Path;
@@ -629,9 +700,9 @@ namespace KeePassLib.Utility
try try
{ {
if(!Directory.Exists(strDir)) Directory.CreateDirectory(strDir); if (!Directory.Exists(strDir)) Directory.CreateDirectory(strDir);
} }
catch(Exception) { Debug.Assert(false); } catch (Exception) { Debug.Assert(false); }
return strDir; return strDir;
} }
@@ -642,31 +713,29 @@ namespace KeePassLib.Utility
SearchOption opt) SearchOption opt)
{ {
List<string> l = new List<string>(); List<string> l = new List<string>();
if(strDir == null) { Debug.Assert(false); return l; } if (strDir == null) { Debug.Assert(false); return l; }
if(strPattern == null) { Debug.Assert(false); return l; } if (strPattern == null) { Debug.Assert(false); return l; }
string[] v = Directory.GetFiles(strDir, strPattern, opt); string[] v = Directory.GetFiles(strDir, strPattern, opt);
if(v == null) { Debug.Assert(false); return l; } if (v == null) { Debug.Assert(false); return l; }
// Only accept files with the correct extension; GetFiles may // Only accept files with the correct extension; GetFiles may
// return additional files, see GetFiles documentation // return additional files, see GetFiles documentation
string strExt = GetExtension(strPattern); string strExt = GetExtension(strPattern);
if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) && if (!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) &&
(strExt.IndexOf('?') < 0)) (strExt.IndexOf('?') < 0))
{ {
strExt = "." + strExt; strExt = "." + strExt;
foreach(string strPathRaw in v) foreach (string strPathRaw in v)
{ {
if(strPathRaw == null) { Debug.Assert(false); continue; } if (strPathRaw == null) { Debug.Assert(false); continue; }
string strPath = strPathRaw.Trim(m_vPathTrimCharsWs); string strPath = strPathRaw.Trim(g_vPathTrimCharsWs);
if(strPath.Length == 0) { Debug.Assert(false); continue; } if (strPath.Length == 0) { Debug.Assert(false); continue; }
Debug.Assert(strPath == strPathRaw); Debug.Assert(strPath == strPathRaw);
if(!strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) if (strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
continue; l.Add(strPathRaw);
l.Add(strPathRaw);
} }
} }
else l.AddRange(v); else l.AddRange(v);
@@ -679,33 +748,31 @@ namespace KeePassLib.Utility
SearchOption opt) SearchOption opt)
{ {
List<FileInfo> l = new List<FileInfo>(); List<FileInfo> l = new List<FileInfo>();
if(di == null) { Debug.Assert(false); return l; } if (di == null) { Debug.Assert(false); return l; }
if(strPattern == null) { Debug.Assert(false); return l; } if (strPattern == null) { Debug.Assert(false); return l; }
FileInfo[] v = di.GetFiles(strPattern, opt); FileInfo[] v = di.GetFiles(strPattern, opt);
if(v == null) { Debug.Assert(false); return l; } if (v == null) { Debug.Assert(false); return l; }
// Only accept files with the correct extension; GetFiles may // Only accept files with the correct extension; GetFiles may
// return additional files, see GetFiles documentation // return additional files, see GetFiles documentation
string strExt = GetExtension(strPattern); string strExt = GetExtension(strPattern);
if(!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) && if (!string.IsNullOrEmpty(strExt) && (strExt.IndexOf('*') < 0) &&
(strExt.IndexOf('?') < 0)) (strExt.IndexOf('?') < 0))
{ {
strExt = "." + strExt; strExt = "." + strExt;
foreach(FileInfo fi in v) foreach (FileInfo fi in v)
{ {
if(fi == null) { Debug.Assert(false); continue; } if (fi == null) { Debug.Assert(false); continue; }
string strPathRaw = fi.FullName; string strPathRaw = fi.FullName;
if(strPathRaw == null) { Debug.Assert(false); continue; } if (strPathRaw == null) { Debug.Assert(false); continue; }
string strPath = strPathRaw.Trim(m_vPathTrimCharsWs); string strPath = strPathRaw.Trim(g_vPathTrimCharsWs);
if(strPath.Length == 0) { Debug.Assert(false); continue; } if (strPath.Length == 0) { Debug.Assert(false); continue; }
Debug.Assert(strPath == strPathRaw); Debug.Assert(strPath == strPathRaw);
if(!strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp)) if (strPath.EndsWith(strExt, StrUtil.CaseIgnoreCmp))
continue; l.Add(fi);
l.Add(fi);
} }
} }
else l.AddRange(v); else l.AddRange(v);
@@ -713,5 +780,82 @@ namespace KeePassLib.Utility
return l; return l;
} }
#endif #endif
public static char GetDriveLetter(string strPath)
{
if (strPath == null) throw new ArgumentNullException("strPath");
Debug.Assert(default(char) == '\0');
if (strPath.Length < 3) return '\0';
if ((strPath[1] != ':') || (strPath[2] != '\\')) return '\0';
char ch = char.ToUpperInvariant(strPath[0]);
return (((ch >= 'A') && (ch <= 'Z')) ? ch : '\0');
}
internal static string GetSafeFileName(string strName)
{
Debug.Assert(!string.IsNullOrEmpty(strName));
string str = FilterFileName(GetFileName(strName ?? string.Empty));
if (string.IsNullOrEmpty(str))
{
Debug.Assert(false);
return "File.dat";
}
return str;
}
internal static string GetCanonicalUri(string strUri)
{
if (string.IsNullOrEmpty(strUri)) { Debug.Assert(false); return strUri; }
try
{
Uri uri = new Uri(strUri);
if (uri.IsAbsoluteUri) return uri.AbsoluteUri;
else { Debug.Assert(false); }
}
catch (Exception) { Debug.Assert(false); }
return strUri;
}
/* internal static Dictionary<string, string> ParseQuery(string strQuery)
{
Dictionary<string, string> d = new Dictionary<string, string>();
if(string.IsNullOrEmpty(strQuery)) return d;
string[] vKvp = strQuery.Split(new char[] { '?', '&' });
if(vKvp == null) { Debug.Assert(false); return d; }
foreach(string strKvp in vKvp)
{
if(string.IsNullOrEmpty(strKvp)) continue;
string strKey, strValue;
int iSep = strKvp.IndexOf('=');
if(iSep < 0)
{
strKey = strKvp;
strValue = string.Empty;
}
else
{
strKey = strKvp.Substring(0, iSep);
strValue = strKvp.Substring(iSep + 1);
}
strKey = Uri.UnescapeDataString(strKey);
strValue = Uri.UnescapeDataString(strValue);
d[strKey] = strValue;
}
return d;
} */
} }
} }

View File

@@ -353,9 +353,6 @@ namespace keepass2android.search
case PwDefs.NotesField: case PwDefs.NotesField:
intlResourceId = Resource.String.entry_comment; intlResourceId = Resource.String.entry_comment;
break; break;
case PwGroup.SearchContextTags:
intlResourceId = Resource.String.entry_tags;
break;
default: default:
//don't disclose protected strings: //don't disclose protected strings:
if (CurrentEntry.Strings.Get(context.Key).IsProtected) if (CurrentEntry.Strings.Get(context.Key).IsProtected)