/* KeePass Password Safe - The Open-Source Password Manager Copyright (C) 2003-2020 Dominik Reichl 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.IO; using System.Text; using System.Xml; #if !ModernKeePassLib && !KeePassUAP using System.Drawing; #endif using ModernKeePassLib; using ModernKeePassLib.Collections; using ModernKeePassLib.Interfaces; using ModernKeePassLib.Resources; using ModernKeePassLib.Security; using ModernKeePassLib.Utility; using ModernKeePassLib.Cryptography; using ModernKeePassLib.Cryptography.Cipher; namespace ModernKeePassLib.Serialization { /// /// Serialization to KeePass KDBX files. /// public sealed partial class KdbxFile { private enum KdbContext { Null, KeePassFile, Meta, Root, MemoryProtection, CustomIcons, CustomIcon, Binaries, CustomData, CustomDataItem, RootDeletedObjects, DeletedObject, Group, GroupTimes, GroupCustomData, GroupCustomDataItem, Entry, EntryTimes, EntryString, EntryBinary, EntryAutoType, EntryAutoTypeItem, EntryHistory, EntryCustomData, EntryCustomDataItem } private bool m_bReadNextNode = true; private Stack m_ctxGroups = new Stack(); private PwGroup m_ctxGroup = null; private PwEntry m_ctxEntry = null; private string m_ctxStringName = null; private ProtectedString m_ctxStringValue = null; private string m_ctxBinaryName = null; private ProtectedBinary m_ctxBinaryValue = null; private string m_ctxATName = null; private string m_ctxATSeq = null; private bool m_bEntryInHistory = false; private PwEntry m_ctxHistoryBase = null; private PwDeletedObject m_ctxDeletedObject = null; private PwUuid m_uuidCustomIconID = PwUuid.Zero; private byte[] m_pbCustomIconData = null; private string m_strCustomDataKey = null; private string m_strCustomDataValue = null; private string m_strGroupCustomDataKey = null; private string m_strGroupCustomDataValue = null; private string m_strEntryCustomDataKey = null; private string m_strEntryCustomDataValue = null; private void ReadXmlStreamed(Stream sXml, Stream sParent) { using(XmlReader xr = XmlUtilEx.CreateXmlReader(sXml)) { ReadDocumentStreamed(xr, sParent); } } private void ReadDocumentStreamed(XmlReader xr, Stream sParentStream) { Debug.Assert(xr != null); if(xr == null) throw new ArgumentNullException("xr"); m_ctxGroups.Clear(); KdbContext ctx = KdbContext.Null; uint uTagCounter = 0; bool bSupportsStatus = (m_slLogger != null); long lStreamLength = 1; try { sParentStream.Position.ToString(); // Test Position support lStreamLength = sParentStream.Length; } catch(Exception) { bSupportsStatus = false; } if(lStreamLength <= 0) { Debug.Assert(false); lStreamLength = 1; } m_bReadNextNode = true; while(true) { if(m_bReadNextNode) { if(!xr.Read()) break; } else m_bReadNextNode = true; switch(xr.NodeType) { case XmlNodeType.Element: ctx = ReadXmlElement(ctx, xr); break; case XmlNodeType.EndElement: ctx = EndXmlElement(ctx, xr); break; case XmlNodeType.XmlDeclaration: break; // Ignore default: Debug.Assert(false); break; } ++uTagCounter; if(((uTagCounter & 0xFFU) == 0) && bSupportsStatus) { Debug.Assert(lStreamLength == sParentStream.Length); uint uPct = (uint)((sParentStream.Position * 100) / lStreamLength); // Clip percent value in case the stream reports incorrect // position/length values (M120413) if(uPct > 100) { Debug.Assert(false); uPct = 100; } if(!m_slLogger.SetProgress(uPct)) throw new OperationCanceledException(); } } Debug.Assert(ctx == KdbContext.Null); if(ctx != KdbContext.Null) throw new FormatException(); Debug.Assert(m_ctxGroups.Count == 0); if(m_ctxGroups.Count != 0) throw new FormatException(); } private KdbContext ReadXmlElement(KdbContext ctx, XmlReader xr) { Debug.Assert(xr.NodeType == XmlNodeType.Element); switch(ctx) { case KdbContext.Null: if(xr.Name == ElemDocNode) return SwitchContext(ctx, KdbContext.KeePassFile, xr); else ReadUnknown(xr); break; case KdbContext.KeePassFile: if(xr.Name == ElemMeta) return SwitchContext(ctx, KdbContext.Meta, xr); else if(xr.Name == ElemRoot) return SwitchContext(ctx, KdbContext.Root, xr); else ReadUnknown(xr); break; case KdbContext.Meta: if(xr.Name == ElemGenerator) ReadString(xr); // Ignore else if(xr.Name == ElemHeaderHash) { // The header hash is typically only stored in // KDBX <= 3.1 files, not in KDBX >= 4 files // (here, the header is verified via a HMAC), // but we also support it for KDBX >= 4 files // (i.e. if it's present, we check it) string strHash = ReadString(xr); if(!string.IsNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && !m_bRepairMode) { Debug.Assert(m_uFileVersion < FileVersion32_4); byte[] pbHash = Convert.FromBase64String(strHash); if(!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader)) throw new InvalidDataException(KLRes.FileCorrupted); } } else if(xr.Name == ElemSettingsChanged) m_pwDatabase.SettingsChanged = ReadTime(xr); else if(xr.Name == ElemDbName) m_pwDatabase.Name = ReadString(xr); else if(xr.Name == ElemDbNameChanged) m_pwDatabase.NameChanged = ReadTime(xr); else if(xr.Name == ElemDbDesc) m_pwDatabase.Description = ReadString(xr); else if(xr.Name == ElemDbDescChanged) m_pwDatabase.DescriptionChanged = ReadTime(xr); else if(xr.Name == ElemDbDefaultUser) m_pwDatabase.DefaultUserName = ReadString(xr); else if(xr.Name == ElemDbDefaultUserChanged) m_pwDatabase.DefaultUserNameChanged = ReadTime(xr); else if(xr.Name == ElemDbMntncHistoryDays) m_pwDatabase.MaintenanceHistoryDays = ReadUInt(xr, 365); else if(xr.Name == ElemDbColor) { string strColor = ReadString(xr); if(!string.IsNullOrEmpty(strColor)) m_pwDatabase.Color = ColorTranslator.FromHtml(strColor); } else if(xr.Name == ElemDbKeyChanged) m_pwDatabase.MasterKeyChanged = ReadTime(xr); else if(xr.Name == ElemDbKeyChangeRec) m_pwDatabase.MasterKeyChangeRec = ReadLong(xr, -1); else if(xr.Name == ElemDbKeyChangeForce) m_pwDatabase.MasterKeyChangeForce = ReadLong(xr, -1); else if(xr.Name == ElemDbKeyChangeForceOnce) m_pwDatabase.MasterKeyChangeForceOnce = ReadBool(xr, false); else if(xr.Name == ElemMemoryProt) return SwitchContext(ctx, KdbContext.MemoryProtection, xr); else if(xr.Name == ElemCustomIcons) return SwitchContext(ctx, KdbContext.CustomIcons, xr); else if(xr.Name == ElemRecycleBinEnabled) m_pwDatabase.RecycleBinEnabled = ReadBool(xr, true); else if(xr.Name == ElemRecycleBinUuid) m_pwDatabase.RecycleBinUuid = ReadUuid(xr); else if(xr.Name == ElemRecycleBinChanged) m_pwDatabase.RecycleBinChanged = ReadTime(xr); else if(xr.Name == ElemEntryTemplatesGroup) m_pwDatabase.EntryTemplatesGroup = ReadUuid(xr); else if(xr.Name == ElemEntryTemplatesGroupChanged) m_pwDatabase.EntryTemplatesGroupChanged = ReadTime(xr); else if(xr.Name == ElemHistoryMaxItems) m_pwDatabase.HistoryMaxItems = ReadInt(xr, -1); else if(xr.Name == ElemHistoryMaxSize) m_pwDatabase.HistoryMaxSize = ReadLong(xr, -1); else if(xr.Name == ElemLastSelectedGroup) m_pwDatabase.LastSelectedGroup = ReadUuid(xr); else if(xr.Name == ElemLastTopVisibleGroup) m_pwDatabase.LastTopVisibleGroup = ReadUuid(xr); else if(xr.Name == ElemBinaries) return SwitchContext(ctx, KdbContext.Binaries, xr); else if(xr.Name == ElemCustomData) return SwitchContext(ctx, KdbContext.CustomData, xr); else ReadUnknown(xr); break; case KdbContext.MemoryProtection: if(xr.Name == ElemProtTitle) m_pwDatabase.MemoryProtection.ProtectTitle = ReadBool(xr, false); else if(xr.Name == ElemProtUserName) m_pwDatabase.MemoryProtection.ProtectUserName = ReadBool(xr, false); else if(xr.Name == ElemProtPassword) m_pwDatabase.MemoryProtection.ProtectPassword = ReadBool(xr, true); else if(xr.Name == ElemProtUrl) m_pwDatabase.MemoryProtection.ProtectUrl = ReadBool(xr, false); else if(xr.Name == ElemProtNotes) m_pwDatabase.MemoryProtection.ProtectNotes = ReadBool(xr, false); // else if(xr.Name == ElemProtAutoHide) // m_pwDatabase.MemoryProtection.AutoEnableVisualHiding = ReadBool(xr, true); else ReadUnknown(xr); break; case KdbContext.CustomIcons: if(xr.Name == ElemCustomIconItem) return SwitchContext(ctx, KdbContext.CustomIcon, xr); else ReadUnknown(xr); break; case KdbContext.CustomIcon: if(xr.Name == ElemCustomIconItemID) m_uuidCustomIconID = ReadUuid(xr); else if(xr.Name == ElemCustomIconItemData) { string strData = ReadString(xr); if(!string.IsNullOrEmpty(strData)) m_pbCustomIconData = Convert.FromBase64String(strData); else { Debug.Assert(false); } } else ReadUnknown(xr); break; case KdbContext.Binaries: if(xr.Name == ElemBinary) { if(xr.MoveToAttribute(AttrId)) { string strKey = xr.Value; ProtectedBinary pbData = ReadProtectedBinary(xr); int iKey; if(!StrUtil.TryParseIntInvariant(strKey, out iKey)) throw new FormatException(); if(iKey < 0) throw new FormatException(); Debug.Assert(m_pbsBinaries.Get(iKey) == null); Debug.Assert(m_pbsBinaries.Find(pbData) < 0); m_pbsBinaries.Set(iKey, pbData); } else ReadUnknown(xr); } else ReadUnknown(xr); break; case KdbContext.CustomData: if(xr.Name == ElemStringDictExItem) return SwitchContext(ctx, KdbContext.CustomDataItem, xr); else ReadUnknown(xr); break; case KdbContext.CustomDataItem: if(xr.Name == ElemKey) m_strCustomDataKey = ReadString(xr); else if(xr.Name == ElemValue) m_strCustomDataValue = ReadString(xr); else ReadUnknown(xr); break; case KdbContext.Root: if(xr.Name == ElemGroup) { Debug.Assert(m_ctxGroups.Count == 0); if(m_ctxGroups.Count != 0) throw new FormatException(); m_pwDatabase.RootGroup = new PwGroup(false, false); m_ctxGroups.Push(m_pwDatabase.RootGroup); m_ctxGroup = m_ctxGroups.Peek(); return SwitchContext(ctx, KdbContext.Group, xr); } else if(xr.Name == ElemDeletedObjects) return SwitchContext(ctx, KdbContext.RootDeletedObjects, xr); else ReadUnknown(xr); break; case KdbContext.Group: if(xr.Name == ElemUuid) m_ctxGroup.Uuid = ReadUuid(xr); else if(xr.Name == ElemName) m_ctxGroup.Name = ReadString(xr); else if(xr.Name == ElemNotes) m_ctxGroup.Notes = ReadString(xr); else if(xr.Name == ElemIcon) m_ctxGroup.IconId = ReadIconId(xr, PwIcon.Folder); else if(xr.Name == ElemCustomIconID) m_ctxGroup.CustomIconUuid = ReadUuid(xr); else if(xr.Name == ElemTimes) return SwitchContext(ctx, KdbContext.GroupTimes, xr); else if(xr.Name == ElemIsExpanded) m_ctxGroup.IsExpanded = ReadBool(xr, true); else if(xr.Name == ElemGroupDefaultAutoTypeSeq) m_ctxGroup.DefaultAutoTypeSequence = ReadString(xr); else if(xr.Name == ElemEnableAutoType) m_ctxGroup.EnableAutoType = StrUtil.StringToBoolEx(ReadString(xr)); else if(xr.Name == ElemEnableSearching) m_ctxGroup.EnableSearching = StrUtil.StringToBoolEx(ReadString(xr)); else if(xr.Name == ElemLastTopVisibleEntry) m_ctxGroup.LastTopVisibleEntry = ReadUuid(xr); else if(xr.Name == ElemCustomData) return SwitchContext(ctx, KdbContext.GroupCustomData, xr); else if(xr.Name == ElemGroup) { m_ctxGroup = new PwGroup(false, false); m_ctxGroups.Peek().AddGroup(m_ctxGroup, true); m_ctxGroups.Push(m_ctxGroup); return SwitchContext(ctx, KdbContext.Group, xr); } else if(xr.Name == ElemEntry) { m_ctxEntry = new PwEntry(false, false); m_ctxGroup.AddEntry(m_ctxEntry, true); m_bEntryInHistory = false; return SwitchContext(ctx, KdbContext.Entry, xr); } else ReadUnknown(xr); break; case KdbContext.GroupCustomData: if(xr.Name == ElemStringDictExItem) return SwitchContext(ctx, KdbContext.GroupCustomDataItem, xr); else ReadUnknown(xr); break; case KdbContext.GroupCustomDataItem: if(xr.Name == ElemKey) m_strGroupCustomDataKey = ReadString(xr); else if(xr.Name == ElemValue) m_strGroupCustomDataValue = ReadString(xr); else ReadUnknown(xr); break; case KdbContext.Entry: if(xr.Name == ElemUuid) m_ctxEntry.Uuid = ReadUuid(xr); else if(xr.Name == ElemIcon) m_ctxEntry.IconId = ReadIconId(xr, PwIcon.Key); else if(xr.Name == ElemCustomIconID) m_ctxEntry.CustomIconUuid = ReadUuid(xr); else if(xr.Name == ElemFgColor) { string strColor = ReadString(xr); if(!string.IsNullOrEmpty(strColor)) m_ctxEntry.ForegroundColor = ColorTranslator.FromHtml(strColor); } else if(xr.Name == ElemBgColor) { string strColor = ReadString(xr); if(!string.IsNullOrEmpty(strColor)) m_ctxEntry.BackgroundColor = ColorTranslator.FromHtml(strColor); } else if(xr.Name == ElemOverrideUrl) m_ctxEntry.OverrideUrl = ReadString(xr); else if(xr.Name == ElemTags) m_ctxEntry.Tags = StrUtil.StringToTags(ReadString(xr)); else if(xr.Name == ElemTimes) return SwitchContext(ctx, KdbContext.EntryTimes, xr); else if(xr.Name == ElemString) return SwitchContext(ctx, KdbContext.EntryString, xr); else if(xr.Name == ElemBinary) return SwitchContext(ctx, KdbContext.EntryBinary, xr); else if(xr.Name == ElemAutoType) return SwitchContext(ctx, KdbContext.EntryAutoType, xr); else if(xr.Name == ElemCustomData) return SwitchContext(ctx, KdbContext.EntryCustomData, xr); else if(xr.Name == ElemHistory) { Debug.Assert(m_bEntryInHistory == false); if(m_bEntryInHistory == false) { m_ctxHistoryBase = m_ctxEntry; return SwitchContext(ctx, KdbContext.EntryHistory, xr); } else ReadUnknown(xr); } else ReadUnknown(xr); break; case KdbContext.GroupTimes: case KdbContext.EntryTimes: ITimeLogger tl = ((ctx == KdbContext.GroupTimes) ? (ITimeLogger)m_ctxGroup : (ITimeLogger)m_ctxEntry); Debug.Assert(tl != null); if(xr.Name == ElemCreationTime) tl.CreationTime = ReadTime(xr); else if(xr.Name == ElemLastModTime) tl.LastModificationTime = ReadTime(xr); else if(xr.Name == ElemLastAccessTime) tl.LastAccessTime = ReadTime(xr); else if(xr.Name == ElemExpiryTime) tl.ExpiryTime = ReadTime(xr); else if(xr.Name == ElemExpires) tl.Expires = ReadBool(xr, false); else if(xr.Name == ElemUsageCount) tl.UsageCount = ReadULong(xr, 0); else if(xr.Name == ElemLocationChanged) tl.LocationChanged = ReadTime(xr); else ReadUnknown(xr); break; case KdbContext.EntryString: if(xr.Name == ElemKey) m_ctxStringName = ReadString(xr); else if(xr.Name == ElemValue) m_ctxStringValue = ReadProtectedString(xr); else ReadUnknown(xr); break; case KdbContext.EntryBinary: if(xr.Name == ElemKey) m_ctxBinaryName = ReadString(xr); else if(xr.Name == ElemValue) m_ctxBinaryValue = ReadProtectedBinary(xr); else ReadUnknown(xr); break; case KdbContext.EntryAutoType: if(xr.Name == ElemAutoTypeEnabled) m_ctxEntry.AutoType.Enabled = ReadBool(xr, true); else if(xr.Name == ElemAutoTypeObfuscation) m_ctxEntry.AutoType.ObfuscationOptions = (AutoTypeObfuscationOptions)ReadInt(xr, 0); else if(xr.Name == ElemAutoTypeDefaultSeq) m_ctxEntry.AutoType.DefaultSequence = ReadString(xr); else if(xr.Name == ElemAutoTypeItem) return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xr); else ReadUnknown(xr); break; case KdbContext.EntryAutoTypeItem: if(xr.Name == ElemWindow) m_ctxATName = ReadString(xr); else if(xr.Name == ElemKeystrokeSequence) m_ctxATSeq = ReadString(xr); else ReadUnknown(xr); break; case KdbContext.EntryCustomData: if(xr.Name == ElemStringDictExItem) return SwitchContext(ctx, KdbContext.EntryCustomDataItem, xr); else ReadUnknown(xr); break; case KdbContext.EntryCustomDataItem: if(xr.Name == ElemKey) m_strEntryCustomDataKey = ReadString(xr); else if(xr.Name == ElemValue) m_strEntryCustomDataValue = ReadString(xr); else ReadUnknown(xr); break; case KdbContext.EntryHistory: if(xr.Name == ElemEntry) { m_ctxEntry = new PwEntry(false, false); m_ctxHistoryBase.History.Add(m_ctxEntry); m_bEntryInHistory = true; return SwitchContext(ctx, KdbContext.Entry, xr); } else ReadUnknown(xr); break; case KdbContext.RootDeletedObjects: if(xr.Name == ElemDeletedObject) { m_ctxDeletedObject = new PwDeletedObject(); m_pwDatabase.DeletedObjects.Add(m_ctxDeletedObject); return SwitchContext(ctx, KdbContext.DeletedObject, xr); } else ReadUnknown(xr); break; case KdbContext.DeletedObject: if(xr.Name == ElemUuid) m_ctxDeletedObject.Uuid = ReadUuid(xr); else if(xr.Name == ElemDeletionTime) m_ctxDeletedObject.DeletionTime = ReadTime(xr); else ReadUnknown(xr); break; default: ReadUnknown(xr); break; } return ctx; } private KdbContext EndXmlElement(KdbContext ctx, XmlReader xr) { Debug.Assert(xr.NodeType == XmlNodeType.EndElement); if((ctx == KdbContext.KeePassFile) && (xr.Name == ElemDocNode)) return KdbContext.Null; else if((ctx == KdbContext.Meta) && (xr.Name == ElemMeta)) return KdbContext.KeePassFile; else if((ctx == KdbContext.Root) && (xr.Name == ElemRoot)) return KdbContext.KeePassFile; else if((ctx == KdbContext.MemoryProtection) && (xr.Name == ElemMemoryProt)) return KdbContext.Meta; else if((ctx == KdbContext.CustomIcons) && (xr.Name == ElemCustomIcons)) return KdbContext.Meta; else if((ctx == KdbContext.CustomIcon) && (xr.Name == ElemCustomIconItem)) { if(!m_uuidCustomIconID.Equals(PwUuid.Zero) && (m_pbCustomIconData != null)) m_pwDatabase.CustomIcons.Add(new PwCustomIcon( m_uuidCustomIconID, m_pbCustomIconData)); else { Debug.Assert(false); } m_uuidCustomIconID = PwUuid.Zero; m_pbCustomIconData = null; return KdbContext.CustomIcons; } else if((ctx == KdbContext.Binaries) && (xr.Name == ElemBinaries)) return KdbContext.Meta; else if((ctx == KdbContext.CustomData) && (xr.Name == ElemCustomData)) return KdbContext.Meta; else if((ctx == KdbContext.CustomDataItem) && (xr.Name == ElemStringDictExItem)) { if((m_strCustomDataKey != null) && (m_strCustomDataValue != null)) m_pwDatabase.CustomData.Set(m_strCustomDataKey, m_strCustomDataValue); else { Debug.Assert(false); } m_strCustomDataKey = null; m_strCustomDataValue = null; return KdbContext.CustomData; } else if((ctx == KdbContext.Group) && (xr.Name == ElemGroup)) { if(PwUuid.Zero.Equals(m_ctxGroup.Uuid)) m_ctxGroup.Uuid = new PwUuid(true); // No assert (import) m_ctxGroups.Pop(); if(m_ctxGroups.Count == 0) { m_ctxGroup = null; return KdbContext.Root; } else { m_ctxGroup = m_ctxGroups.Peek(); return KdbContext.Group; } } else if((ctx == KdbContext.GroupTimes) && (xr.Name == ElemTimes)) return KdbContext.Group; else if((ctx == KdbContext.GroupCustomData) && (xr.Name == ElemCustomData)) return KdbContext.Group; else if((ctx == KdbContext.GroupCustomDataItem) && (xr.Name == ElemStringDictExItem)) { if((m_strGroupCustomDataKey != null) && (m_strGroupCustomDataValue != null)) m_ctxGroup.CustomData.Set(m_strGroupCustomDataKey, m_strGroupCustomDataValue); else { Debug.Assert(false); } m_strGroupCustomDataKey = null; m_strGroupCustomDataValue = null; return KdbContext.GroupCustomData; } else if((ctx == KdbContext.Entry) && (xr.Name == ElemEntry)) { // Create new UUID if absent if(PwUuid.Zero.Equals(m_ctxEntry.Uuid)) m_ctxEntry.Uuid = new PwUuid(true); // No assert (import) if(m_bEntryInHistory) { m_ctxEntry = m_ctxHistoryBase; return KdbContext.EntryHistory; } return KdbContext.Group; } else if((ctx == KdbContext.EntryTimes) && (xr.Name == ElemTimes)) return KdbContext.Entry; else if((ctx == KdbContext.EntryString) && (xr.Name == ElemString)) { m_ctxEntry.Strings.Set(m_ctxStringName, m_ctxStringValue); m_ctxStringName = null; m_ctxStringValue = null; return KdbContext.Entry; } else if((ctx == KdbContext.EntryBinary) && (xr.Name == ElemBinary)) { if(string.IsNullOrEmpty(m_strDetachBins)) m_ctxEntry.Binaries.Set(m_ctxBinaryName, m_ctxBinaryValue); else { SaveBinary(m_ctxBinaryName, m_ctxBinaryValue, m_strDetachBins); m_ctxBinaryValue = null; GC.Collect(); } m_ctxBinaryName = null; m_ctxBinaryValue = null; return KdbContext.Entry; } else if((ctx == KdbContext.EntryAutoType) && (xr.Name == ElemAutoType)) return KdbContext.Entry; else if((ctx == KdbContext.EntryAutoTypeItem) && (xr.Name == ElemAutoTypeItem)) { AutoTypeAssociation atAssoc = new AutoTypeAssociation(m_ctxATName, m_ctxATSeq); m_ctxEntry.AutoType.Add(atAssoc); m_ctxATName = null; m_ctxATSeq = null; return KdbContext.EntryAutoType; } else if((ctx == KdbContext.EntryCustomData) && (xr.Name == ElemCustomData)) return KdbContext.Entry; else if((ctx == KdbContext.EntryCustomDataItem) && (xr.Name == ElemStringDictExItem)) { if((m_strEntryCustomDataKey != null) && (m_strEntryCustomDataValue != null)) m_ctxEntry.CustomData.Set(m_strEntryCustomDataKey, m_strEntryCustomDataValue); else { Debug.Assert(false); } m_strEntryCustomDataKey = null; m_strEntryCustomDataValue = null; return KdbContext.EntryCustomData; } else if((ctx == KdbContext.EntryHistory) && (xr.Name == ElemHistory)) { m_bEntryInHistory = false; return KdbContext.Entry; } else if((ctx == KdbContext.RootDeletedObjects) && (xr.Name == ElemDeletedObjects)) return KdbContext.Root; else if((ctx == KdbContext.DeletedObject) && (xr.Name == ElemDeletedObject)) { m_ctxDeletedObject = null; return KdbContext.RootDeletedObjects; } else { Debug.Assert(false); throw new FormatException(); } } private string ReadString(XmlReader xr) { XorredBuffer xb = ProcessNode(xr); if(xb != null) { Debug.Assert(false); // Protected data is unexpected here try { byte[] pb = xb.ReadPlainText(); if(pb.Length == 0) return string.Empty; try { return StrUtil.Utf8.GetString(pb, 0, pb.Length); } finally { MemUtil.ZeroByteArray(pb); } } finally { xb.Dispose(); } } m_bReadNextNode = false; // ReadElementString skips end tag return xr.ReadElementString(); } private string ReadStringRaw(XmlReader xr) { m_bReadNextNode = false; // ReadElementString skips end tag return xr.ReadElementString(); } private byte[] ReadBase64(XmlReader xr, bool bRaw) { // if(bRaw) return ReadBase64RawInChunks(xr); string str = (bRaw ? ReadStringRaw(xr) : ReadString(xr)); if(string.IsNullOrEmpty(str)) return MemUtil.EmptyByteArray; return Convert.FromBase64String(str); } /* private byte[] m_pbBase64ReadBuf = new byte[1024 * 1024 * 3]; private byte[] ReadBase64RawInChunks(XmlReader xr) { xr.MoveToContent(); List lParts = new List(); byte[] pbBuf = m_pbBase64ReadBuf; while(true) { int cb = xr.ReadElementContentAsBase64(pbBuf, 0, pbBuf.Length); if(cb == 0) break; byte[] pb = new byte[cb]; Array.Copy(pbBuf, 0, pb, 0, cb); lParts.Add(pb); // No break when cb < pbBuf.Length, because ReadElementContentAsBase64 // moves to the next XML node only when returning 0 } m_bReadNextNode = false; if(lParts.Count == 0) return MemUtil.EmptyByteArray; if(lParts.Count == 1) return lParts[0]; long cbRes = 0; for(int i = 0; i < lParts.Count; ++i) cbRes += lParts[i].Length; byte[] pbRes = new byte[cbRes]; int cbCur = 0; for(int i = 0; i < lParts.Count; ++i) { Array.Copy(lParts[i], 0, pbRes, cbCur, lParts[i].Length); cbCur += lParts[i].Length; } return pbRes; } */ private bool ReadBool(XmlReader xr, bool bDefault) { string str = ReadString(xr); if(str == ValTrue) return true; else if(str == ValFalse) return false; Debug.Assert(false); return bDefault; } private PwUuid ReadUuid(XmlReader xr) { byte[] pb = ReadBase64(xr, false); if(pb.Length == 0) return PwUuid.Zero; return new PwUuid(pb); } private int ReadInt(XmlReader xr, int nDefault) { string str = ReadString(xr); int n; if(StrUtil.TryParseIntInvariant(str, out n)) return n; // Backward compatibility if(StrUtil.TryParseInt(str, out n)) return n; Debug.Assert(false); return nDefault; } private uint ReadUInt(XmlReader xr, uint uDefault) { string str = ReadString(xr); uint u; if(StrUtil.TryParseUIntInvariant(str, out u)) return u; // Backward compatibility if(StrUtil.TryParseUInt(str, out u)) return u; Debug.Assert(false); return uDefault; } private long ReadLong(XmlReader xr, long lDefault) { string str = ReadString(xr); long l; if(StrUtil.TryParseLongInvariant(str, out l)) return l; // Backward compatibility if(StrUtil.TryParseLong(str, out l)) return l; Debug.Assert(false); return lDefault; } private ulong ReadULong(XmlReader xr, ulong uDefault) { string str = ReadString(xr); ulong u; if(StrUtil.TryParseULongInvariant(str, out u)) return u; // Backward compatibility if(StrUtil.TryParseULong(str, out u)) return u; Debug.Assert(false); return uDefault; } private DateTime ReadTime(XmlReader xr) { // Cf. WriteObject(string, DateTime) if((m_format == KdbxFormat.Default) && (m_uFileVersion >= FileVersion32_4)) { // long l = ReadLong(xr, -1); // if(l != -1) return DateTime.FromBinary(l); byte[] pb = ReadBase64(xr, false); if(pb.Length != 8) { Debug.Assert(false); byte[] pb8 = new byte[8]; Array.Copy(pb, pb8, Math.Min(pb.Length, 8)); // Little-endian pb = pb8; } long lSec = MemUtil.BytesToInt64(pb); return new DateTime(lSec * TimeSpan.TicksPerSecond, DateTimeKind.Utc); } else { string str = ReadString(xr); DateTime dt; if(TimeUtil.TryDeserializeUtc(str, out dt)) return dt; } Debug.Assert(false); return m_dtNow; } private PwIcon ReadIconId(XmlReader xr, PwIcon icDefault) { int i = ReadInt(xr, (int)icDefault); if((i >= 0) && (i < (int)PwIcon.Count)) return (PwIcon)i; Debug.Assert(false); return icDefault; } private ProtectedString ReadProtectedString(XmlReader xr) { XorredBuffer xb = ProcessNode(xr); if(xb != null) { try { return new ProtectedString(true, xb); } finally { xb.Dispose(); } } bool bProtect = false; if(m_format == KdbxFormat.PlainXml) { if(xr.MoveToAttribute(AttrProtectedInMemPlainXml)) { string strProtect = xr.Value; bProtect = ((strProtect != null) && (strProtect == ValTrue)); } } return new ProtectedString(bProtect, ReadString(xr)); } private ProtectedBinary ReadProtectedBinary(XmlReader xr) { if(xr.MoveToAttribute(AttrRef)) { string strRef = xr.Value; if(!string.IsNullOrEmpty(strRef)) { int iRef; if(StrUtil.TryParseIntInvariant(strRef, out iRef)) { ProtectedBinary pb = m_pbsBinaries.Get(iRef); if(pb != null) { // https://sourceforge.net/p/keepass/feature-requests/2023/ xr.MoveToElement(); #if DEBUG string strInner = ReadStringRaw(xr); Debug.Assert(string.IsNullOrEmpty(strInner)); #else ReadStringRaw(xr); #endif return pb; } else { Debug.Assert(false); } } else { Debug.Assert(false); } } else { Debug.Assert(false); } } bool bCompressed = false; if(xr.MoveToAttribute(AttrCompressed)) bCompressed = (xr.Value == ValTrue); XorredBuffer xb = ProcessNode(xr); if(xb != null) { Debug.Assert(!bCompressed); // See SubWriteValue(ProtectedBinary value) try { return new ProtectedBinary(true, xb); } finally { xb.Dispose(); } } byte[] pbData = ReadBase64(xr, true); if(pbData.Length == 0) return new ProtectedBinary(); if(bCompressed) pbData = MemUtil.Decompress(pbData); return new ProtectedBinary(false, pbData); } private void ReadUnknown(XmlReader xr) { Debug.Assert(false); // Unknown node! Debug.Assert(xr.NodeType == XmlNodeType.Element); bool bRead = false; int cOpen = 0; do { if(bRead) xr.Read(); bRead = true; if(xr.NodeType == XmlNodeType.EndElement) --cOpen; else if(xr.NodeType == XmlNodeType.Element) { if(!xr.IsEmptyElement) { XorredBuffer xb = ProcessNode(xr); if(xb != null) { xb.Dispose(); bRead = m_bReadNextNode; continue; } ++cOpen; } } } while(cOpen > 0); m_bReadNextNode = bRead; } private XorredBuffer ProcessNode(XmlReader xr) { // Debug.Assert(xr.NodeType == XmlNodeType.Element); if(xr.HasAttributes) { if(xr.MoveToAttribute(AttrProtected)) { if(xr.Value == ValTrue) { xr.MoveToElement(); byte[] pbCT = ReadBase64(xr, true); byte[] pbPad = m_randomStream.GetRandomBytes((uint)pbCT.Length); return new XorredBuffer(pbCT, pbPad); } } } return null; } private static KdbContext SwitchContext(KdbContext ctxCurrent, KdbContext ctxNew, XmlReader xr) { if(xr.IsEmptyElement) return ctxCurrent; return ctxNew; } } }