keepass2android/src/KeePassLib2Android/Serialization/KdbxFile.Write.cs

853 lines
26 KiB
C#

/*
KeePass Password Safe - The Open-Source Password Manager
Copyright (C) 2003-2012 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
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.Text;
using System.IO;
using System.Xml;
using System.Diagnostics;
using System.Security;
using System.Security.Cryptography;
using System.Drawing;
using System.Globalization;
#if !KeePassLibSD
using System.IO.Compression;
#else
using KeePassLibSD;
#endif
using KeePassLib.Collections;
using KeePassLib.Cryptography;
using KeePassLib.Cryptography.Cipher;
using KeePassLib.Delegates;
using KeePassLib.Interfaces;
using KeePassLib.Keys;
using KeePassLib.Resources;
using KeePassLib.Security;
using KeePassLib.Utility;
namespace KeePassLib.Serialization
{
/// <summary>
/// Serialization to KeePass KDBX files.
/// </summary>
public sealed partial class KdbxFile
{
// public void Save(string strFile, PwGroup pgDataSource, KdbxFormat format,
// IStatusLogger slLogger)
// {
// bool bMadeUnhidden = UrlUtil.UnhideFile(strFile);
//
// IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFile);
// this.Save(IOConnection.OpenWrite(ioc), pgDataSource, format, slLogger);
//
// if(bMadeUnhidden) UrlUtil.HideFile(strFile, true); // Hide again
// }
/// <summary>
/// Save the contents of the current <c>PwDatabase</c> to a KDBX file.
/// </summary>
/// <param name="sSaveTo">Stream to write the KDBX file into.</param>
/// <param name="pgDataSource">Group containing all groups and
/// entries to write. If <c>null</c>, the complete database will
/// be written.</param>
/// <param name="format">Format of the file to create.</param>
/// <param name="slLogger">Logger that recieves status information.</param>
public void Save(Stream sSaveTo, PwGroup pgDataSource, KdbxFormat format,
IStatusLogger slLogger)
{
Debug.Assert(sSaveTo != null);
if(sSaveTo == null) throw new ArgumentNullException("sSaveTo");
m_format = format;
m_slLogger = slLogger;
HashingStreamEx hashedStream = new HashingStreamEx(sSaveTo, true, null);
UTF8Encoding encNoBom = StrUtil.Utf8;
CryptoRandom cr = CryptoRandom.Instance;
try
{
m_pbMasterSeed = cr.GetRandomBytes(32);
m_pbTransformSeed = cr.GetRandomBytes(32);
m_pbEncryptionIV = cr.GetRandomBytes(16);
m_pbProtectedStreamKey = cr.GetRandomBytes(32);
m_craInnerRandomStream = CrsAlgorithm.Salsa20;
m_randomStream = new CryptoRandomStream(m_craInnerRandomStream,
m_pbProtectedStreamKey);
m_pbStreamStartBytes = cr.GetRandomBytes(32);
Stream writerStream;
if(m_format == KdbxFormat.Default)
{
WriteHeader(hashedStream); // Also flushes the stream
Stream sEncrypted = AttachStreamEncryptor(hashedStream);
if((sEncrypted == null) || (sEncrypted == hashedStream))
throw new SecurityException(KLRes.CryptoStreamFailed);
sEncrypted.Write(m_pbStreamStartBytes, 0, m_pbStreamStartBytes.Length);
Stream sHashed = new HashedBlockStream(sEncrypted, true);
if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
writerStream = new GZipStream(sHashed, CompressionMode.Compress);
else
writerStream = sHashed;
}
else if(m_format == KdbxFormat.PlainXml)
writerStream = hashedStream;
else { Debug.Assert(false); throw new FormatException("KdbFormat"); }
m_xmlWriter = new XmlTextWriter(writerStream, encNoBom);
WriteDocument(pgDataSource);
m_xmlWriter.Flush();
m_xmlWriter.Close();
writerStream.Close();
}
finally { CommonCleanUpWrite(sSaveTo, hashedStream); }
}
private void CommonCleanUpWrite(Stream sSaveTo, HashingStreamEx hashedStream)
{
hashedStream.Close();
m_pbHashOfFileOnDisk = hashedStream.Hash;
sSaveTo.Close();
m_xmlWriter = null;
m_pbHashOfHeader = null;
}
private void WriteHeader(Stream s)
{
MemoryStream ms = new MemoryStream();
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1));
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2));
MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileVersion32));
WriteHeaderField(ms, KdbxHeaderFieldID.CipherID,
m_pwDatabase.DataCipherUuid.UuidBytes);
int nCprID = (int)m_pwDatabase.Compression;
WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags,
MemUtil.UInt32ToBytes((uint)nCprID));
WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed);
WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, m_pbTransformSeed);
WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds,
MemUtil.UInt64ToBytes(m_pwDatabase.KeyEncryptionRounds));
WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV);
WriteHeaderField(ms, KdbxHeaderFieldID.ProtectedStreamKey, m_pbProtectedStreamKey);
WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, m_pbStreamStartBytes);
int nIrsID = (int)m_craInnerRandomStream;
WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID,
MemUtil.UInt32ToBytes((uint)nIrsID));
WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[]{
(byte)'\r', (byte)'\n', (byte)'\r', (byte)'\n' });
byte[] pbHeader = ms.ToArray();
ms.Close();
SHA256Managed sha256 = new SHA256Managed();
m_pbHashOfHeader = sha256.ComputeHash(pbHeader);
s.Write(pbHeader, 0, pbHeader.Length);
s.Flush();
}
private static void WriteHeaderField(Stream s, KdbxHeaderFieldID kdbID,
byte[] pbData)
{
s.WriteByte((byte)kdbID);
if(pbData != null)
{
ushort uLength = (ushort)pbData.Length;
MemUtil.Write(s, MemUtil.UInt16ToBytes(uLength));
if(uLength > 0) s.Write(pbData, 0, pbData.Length);
}
else MemUtil.Write(s, MemUtil.UInt16ToBytes((ushort)0));
}
private Stream AttachStreamEncryptor(Stream s)
{
MemoryStream ms = new MemoryStream();
Debug.Assert(m_pbMasterSeed != null);
Debug.Assert(m_pbMasterSeed.Length == 32);
ms.Write(m_pbMasterSeed, 0, 32);
Debug.Assert(m_pwDatabase != null);
Debug.Assert(m_pwDatabase.MasterKey != null);
ProtectedBinary pbinKey = m_pwDatabase.MasterKey.GenerateKey32(
m_pbTransformSeed, m_pwDatabase.KeyEncryptionRounds);
Debug.Assert(pbinKey != null);
if(pbinKey == null)
throw new SecurityException(KLRes.InvalidCompositeKey);
byte[] pKey32 = pbinKey.ReadData();
if((pKey32 == null) || (pKey32.Length != 32))
throw new SecurityException(KLRes.InvalidCompositeKey);
ms.Write(pKey32, 0, 32);
SHA256Managed sha256 = new SHA256Managed();
byte[] aesKey = sha256.ComputeHash(ms.ToArray());
ms.Close();
Array.Clear(pKey32, 0, 32);
Debug.Assert(CipherPool.GlobalPool != null);
ICipherEngine iEngine = CipherPool.GlobalPool.GetCipher(m_pwDatabase.DataCipherUuid);
if(iEngine == null) throw new SecurityException(KLRes.FileUnknownCipher);
return iEngine.EncryptStream(s, aesKey, m_pbEncryptionIV);
}
private void WriteDocument(PwGroup pgDataSource)
{
Debug.Assert(m_xmlWriter != null);
if(m_xmlWriter == null) throw new InvalidOperationException();
PwGroup pgRoot = (pgDataSource ?? m_pwDatabase.RootGroup);
uint uNumGroups, uNumEntries, uCurEntry = 0;
pgRoot.GetCounts(true, out uNumGroups, out uNumEntries);
BinPoolBuild(pgRoot);
m_xmlWriter.Formatting = Formatting.Indented;
m_xmlWriter.IndentChar = '\t';
m_xmlWriter.Indentation = 1;
m_xmlWriter.WriteStartDocument(true);
m_xmlWriter.WriteStartElement(ElemDocNode);
WriteMeta();
m_xmlWriter.WriteStartElement(ElemRoot);
StartGroup(pgRoot);
Stack<PwGroup> groupStack = new Stack<PwGroup>();
groupStack.Push(pgRoot);
GroupHandler gh = delegate(PwGroup pg)
{
Debug.Assert(pg != null);
if(pg == null) throw new ArgumentNullException("pg");
while(true)
{
if(pg.ParentGroup == groupStack.Peek())
{
groupStack.Push(pg);
StartGroup(pg);
break;
}
else
{
groupStack.Pop();
if(groupStack.Count <= 0) return false;
EndGroup();
}
}
return true;
};
EntryHandler eh = delegate(PwEntry pe)
{
Debug.Assert(pe != null);
WriteEntry(pe, false);
++uCurEntry;
if(m_slLogger != null)
if(!m_slLogger.SetProgress((100 * uCurEntry) / uNumEntries))
return false;
return true;
};
if(!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh))
throw new InvalidOperationException();
while(groupStack.Count > 1)
{
m_xmlWriter.WriteEndElement();
groupStack.Pop();
}
EndGroup();
WriteList(ElemDeletedObjects, m_pwDatabase.DeletedObjects);
m_xmlWriter.WriteEndElement(); // Root
m_xmlWriter.WriteEndElement(); // ElemDocNode
m_xmlWriter.WriteEndDocument();
}
private void WriteMeta()
{
m_xmlWriter.WriteStartElement(ElemMeta);
WriteObject(ElemGenerator, PwDatabase.LocalizedAppName, false); // Generator name
if(m_pbHashOfHeader != null)
WriteObject(ElemHeaderHash, Convert.ToBase64String(
m_pbHashOfHeader), false);
WriteObject(ElemDbName, m_pwDatabase.Name, true);
WriteObject(ElemDbNameChanged, m_pwDatabase.NameChanged);
WriteObject(ElemDbDesc, m_pwDatabase.Description, true);
WriteObject(ElemDbDescChanged, m_pwDatabase.DescriptionChanged);
WriteObject(ElemDbDefaultUser, m_pwDatabase.DefaultUserName, true);
WriteObject(ElemDbDefaultUserChanged, m_pwDatabase.DefaultUserNameChanged);
WriteObject(ElemDbMntncHistoryDays, m_pwDatabase.MaintenanceHistoryDays);
WriteObject(ElemDbColor, StrUtil.ColorToUnnamedHtml(m_pwDatabase.Color, true), false);
WriteObject(ElemDbKeyChanged, m_pwDatabase.MasterKeyChanged);
WriteObject(ElemDbKeyChangeRec, m_pwDatabase.MasterKeyChangeRec);
WriteObject(ElemDbKeyChangeForce, m_pwDatabase.MasterKeyChangeForce);
WriteList(ElemMemoryProt, m_pwDatabase.MemoryProtection);
WriteCustomIconList();
WriteObject(ElemRecycleBinEnabled, m_pwDatabase.RecycleBinEnabled);
WriteObject(ElemRecycleBinUuid, m_pwDatabase.RecycleBinUuid);
WriteObject(ElemRecycleBinChanged, m_pwDatabase.RecycleBinChanged);
WriteObject(ElemEntryTemplatesGroup, m_pwDatabase.EntryTemplatesGroup);
WriteObject(ElemEntryTemplatesGroupChanged, m_pwDatabase.EntryTemplatesGroupChanged);
WriteObject(ElemHistoryMaxItems, m_pwDatabase.HistoryMaxItems);
WriteObject(ElemHistoryMaxSize, m_pwDatabase.HistoryMaxSize);
WriteObject(ElemLastSelectedGroup, m_pwDatabase.LastSelectedGroup);
WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.LastTopVisibleGroup);
WriteBinPool();
WriteList(ElemCustomData, m_pwDatabase.CustomData);
m_xmlWriter.WriteEndElement();
}
private void StartGroup(PwGroup pg)
{
m_xmlWriter.WriteStartElement(ElemGroup);
WriteObject(ElemUuid, pg.Uuid);
WriteObject(ElemName, pg.Name, true);
WriteObject(ElemNotes, pg.Notes, true);
WriteObject(ElemIcon, (int)pg.IconId);
if(pg.CustomIconUuid != PwUuid.Zero)
WriteObject(ElemCustomIconID, pg.CustomIconUuid);
WriteList(ElemTimes, pg);
WriteObject(ElemIsExpanded, pg.IsExpanded);
WriteObject(ElemGroupDefaultAutoTypeSeq, pg.DefaultAutoTypeSequence, true);
WriteObject(ElemEnableAutoType, StrUtil.BoolToStringEx(pg.EnableAutoType), false);
WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.EnableSearching), false);
WriteObject(ElemLastTopVisibleEntry, pg.LastTopVisibleEntry);
}
private void EndGroup()
{
m_xmlWriter.WriteEndElement(); // Close group element
}
private void WriteEntry(PwEntry pe, bool bIsHistory)
{
Debug.Assert(pe != null); if(pe == null) throw new ArgumentNullException("pe");
m_xmlWriter.WriteStartElement(ElemEntry);
WriteObject(ElemUuid, pe.Uuid);
WriteObject(ElemIcon, (int)pe.IconId);
if(pe.CustomIconUuid != PwUuid.Zero)
WriteObject(ElemCustomIconID, pe.CustomIconUuid);
WriteObject(ElemFgColor, StrUtil.ColorToUnnamedHtml(pe.ForegroundColor, true), false);
WriteObject(ElemBgColor, StrUtil.ColorToUnnamedHtml(pe.BackgroundColor, true), false);
WriteObject(ElemOverrideUrl, pe.OverrideUrl, true);
WriteObject(ElemTags, StrUtil.TagsToString(pe.Tags, false), true);
WriteList(ElemTimes, pe);
WriteList(pe.Strings, true);
WriteList(pe.Binaries);
WriteList(ElemAutoType, pe.AutoType);
if(!bIsHistory) WriteList(ElemHistory, pe.History, true);
else { Debug.Assert(pe.History.UCount == 0); }
m_xmlWriter.WriteEndElement();
}
private void WriteList(ProtectedStringDictionary dictStrings, bool bEntryStrings)
{
Debug.Assert(dictStrings != null);
if(dictStrings == null) throw new ArgumentNullException("dictStrings");
foreach(KeyValuePair<string, ProtectedString> kvp in dictStrings)
WriteObject(kvp.Key, kvp.Value, bEntryStrings);
}
private void WriteList(ProtectedBinaryDictionary dictBinaries)
{
Debug.Assert(dictBinaries != null);
if(dictBinaries == null) throw new ArgumentNullException("dictBinaries");
foreach(KeyValuePair<string, ProtectedBinary> kvp in dictBinaries)
WriteObject(kvp.Key, kvp.Value, true);
}
private void WriteList(string name, AutoTypeConfig cfgAutoType)
{
Debug.Assert(name != null);
Debug.Assert(cfgAutoType != null);
if(cfgAutoType == null) throw new ArgumentNullException("cfgAutoType");
m_xmlWriter.WriteStartElement(name);
WriteObject(ElemAutoTypeEnabled, cfgAutoType.Enabled);
WriteObject(ElemAutoTypeObfuscation, (int)cfgAutoType.ObfuscationOptions);
if(cfgAutoType.DefaultSequence.Length > 0)
WriteObject(ElemAutoTypeDefaultSeq, cfgAutoType.DefaultSequence, true);
foreach(AutoTypeAssociation a in cfgAutoType.Associations)
WriteObject(ElemAutoTypeItem, ElemWindow, ElemKeystrokeSequence,
new KeyValuePair<string, string>(a.WindowName, a.Sequence));
m_xmlWriter.WriteEndElement();
}
private void WriteList(string name, ITimeLogger times)
{
Debug.Assert(name != null);
Debug.Assert(times != null); if(times == null) throw new ArgumentNullException("times");
m_xmlWriter.WriteStartElement(name);
WriteObject(ElemLastModTime, times.LastModificationTime);
WriteObject(ElemCreationTime, times.CreationTime);
WriteObject(ElemLastAccessTime, times.LastAccessTime);
WriteObject(ElemExpiryTime, times.ExpiryTime);
WriteObject(ElemExpires, times.Expires);
WriteObject(ElemUsageCount, times.UsageCount);
WriteObject(ElemLocationChanged, times.LocationChanged);
m_xmlWriter.WriteEndElement(); // Name
}
private void WriteList(string name, PwObjectList<PwEntry> value, bool bIsHistory)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name);
foreach(PwEntry pe in value)
WriteEntry(pe, bIsHistory);
m_xmlWriter.WriteEndElement();
}
private void WriteList(string name, PwObjectList<PwDeletedObject> value)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name);
foreach(PwDeletedObject pdo in value)
WriteObject(ElemDeletedObject, pdo);
m_xmlWriter.WriteEndElement();
}
private void WriteList(string name, MemoryProtectionConfig value)
{
Debug.Assert(name != null);
Debug.Assert(value != null);
m_xmlWriter.WriteStartElement(name);
WriteObject(ElemProtTitle, value.ProtectTitle);
WriteObject(ElemProtUserName, value.ProtectUserName);
WriteObject(ElemProtPassword, value.ProtectPassword);
WriteObject(ElemProtUrl, value.ProtectUrl);
WriteObject(ElemProtNotes, value.ProtectNotes);
// WriteObject(ElemProtAutoHide, value.AutoEnableVisualHiding);
m_xmlWriter.WriteEndElement();
}
private void WriteList(string name, StringDictionaryEx value)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name);
foreach(KeyValuePair<string, string> kvp in value)
WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp);
m_xmlWriter.WriteEndElement();
}
private void WriteCustomIconList()
{
if(m_pwDatabase.CustomIcons.Count == 0) return;
m_xmlWriter.WriteStartElement(ElemCustomIcons);
foreach(PwCustomIcon pwci in m_pwDatabase.CustomIcons)
{
m_xmlWriter.WriteStartElement(ElemCustomIconItem);
WriteObject(ElemCustomIconItemID, pwci.Uuid);
string strData = Convert.ToBase64String(pwci.ImageDataPng);
WriteObject(ElemCustomIconItemData, strData, false);
m_xmlWriter.WriteEndElement();
}
m_xmlWriter.WriteEndElement();
}
private void WriteObject(string name, string value,
bool bFilterValueXmlChars)
{
Debug.Assert(name != null);
Debug.Assert(value != null);
m_xmlWriter.WriteStartElement(name);
if(bFilterValueXmlChars)
m_xmlWriter.WriteString(StrUtil.SafeXmlString(value));
else m_xmlWriter.WriteString(value);
m_xmlWriter.WriteEndElement();
}
private void WriteObject(string name, bool value)
{
Debug.Assert(name != null);
WriteObject(name, value ? ValTrue : ValFalse, false);
}
private void WriteObject(string name, PwUuid value)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
WriteObject(name, Convert.ToBase64String(value.UuidBytes), false);
}
private void WriteObject(string name, int value)
{
Debug.Assert(name != null);
m_xmlWriter.WriteStartElement(name);
m_xmlWriter.WriteString(value.ToString());
m_xmlWriter.WriteEndElement();
}
private void WriteObject(string name, uint value)
{
Debug.Assert(name != null);
m_xmlWriter.WriteStartElement(name);
m_xmlWriter.WriteString(value.ToString());
m_xmlWriter.WriteEndElement();
}
private void WriteObject(string name, long value)
{
Debug.Assert(name != null);
m_xmlWriter.WriteStartElement(name);
m_xmlWriter.WriteString(value.ToString());
m_xmlWriter.WriteEndElement();
}
private void WriteObject(string name, ulong value)
{
Debug.Assert(name != null);
m_xmlWriter.WriteStartElement(name);
m_xmlWriter.WriteString(value.ToString());
m_xmlWriter.WriteEndElement();
}
private void WriteObject(string name, DateTime value)
{
Debug.Assert(name != null);
WriteObject(name, TimeUtil.SerializeUtc(value), false);
}
private void WriteObject(string name, string strKeyName,
string strValueName, KeyValuePair<string, string> kvp)
{
m_xmlWriter.WriteStartElement(name);
m_xmlWriter.WriteStartElement(strKeyName);
m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Key));
m_xmlWriter.WriteEndElement();
m_xmlWriter.WriteStartElement(strValueName);
m_xmlWriter.WriteString(StrUtil.SafeXmlString(kvp.Value));
m_xmlWriter.WriteEndElement();
m_xmlWriter.WriteEndElement();
}
private void WriteObject(string name, ProtectedString value, bool bIsEntryString)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(ElemString);
m_xmlWriter.WriteStartElement(ElemKey);
m_xmlWriter.WriteString(StrUtil.SafeXmlString(name));
m_xmlWriter.WriteEndElement();
m_xmlWriter.WriteStartElement(ElemValue);
bool bProtected = value.IsProtected;
if(bIsEntryString)
{
// Adjust memory protection setting (which might be different
// from the database default, e.g. due to an import which
// didn't specify the correct setting)
if(name == PwDefs.TitleField)
bProtected = m_pwDatabase.MemoryProtection.ProtectTitle;
else if(name == PwDefs.UserNameField)
bProtected = m_pwDatabase.MemoryProtection.ProtectUserName;
else if(name == PwDefs.PasswordField)
bProtected = m_pwDatabase.MemoryProtection.ProtectPassword;
else if(name == PwDefs.UrlField)
bProtected = m_pwDatabase.MemoryProtection.ProtectUrl;
else if(name == PwDefs.NotesField)
bProtected = m_pwDatabase.MemoryProtection.ProtectNotes;
}
if(bProtected && (m_format != KdbxFormat.PlainXml))
{
m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue);
byte[] pbEncoded = value.ReadXorredString(m_randomStream);
if(pbEncoded.Length > 0)
m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length);
}
else
{
string strValue = value.ReadString();
// If names should be localized, we need to apply the language-dependent
// string transformation here. By default, language-dependent conversions
// should be applied, otherwise characters could be rendered incorrectly
// (code page problems).
if(m_bLocalizedNames)
{
StringBuilder sb = new StringBuilder();
foreach(char ch in strValue)
{
char chMapped = ch;
// Symbols and surrogates must be moved into the correct code
// page area
if(char.IsSymbol(ch) || char.IsSurrogate(ch))
{
System.Globalization.UnicodeCategory cat = char.GetUnicodeCategory(ch);
// Map character to correct position in code page
chMapped = (char)((int)cat * 32 + ch);
}
else if(char.IsControl(ch))
{
if(ch >= 256) // Control character in high ANSI code page
{
// Some of the control characters map to corresponding ones
// in the low ANSI range (up to 255) when calling
// ToLower on them with invariant culture (see
// http://lists.ximian.com/pipermail/mono-patches/2002-February/086106.html )
chMapped = char.ToLower(ch, CultureInfo.InvariantCulture);
}
}
sb.Append(chMapped);
}
strValue = sb.ToString(); // Correct string for current code page
}
if((m_format == KdbxFormat.PlainXml) && bProtected)
m_xmlWriter.WriteAttributeString(AttrProtectedInMemPlainXml, ValTrue);
m_xmlWriter.WriteString(StrUtil.SafeXmlString(strValue));
}
m_xmlWriter.WriteEndElement(); // ElemValue
m_xmlWriter.WriteEndElement(); // ElemString
}
private void WriteObject(string name, ProtectedBinary value, bool bAllowRef)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(ElemBinary);
m_xmlWriter.WriteStartElement(ElemKey);
m_xmlWriter.WriteString(StrUtil.SafeXmlString(name));
m_xmlWriter.WriteEndElement();
m_xmlWriter.WriteStartElement(ElemValue);
string strRef = (bAllowRef ? BinPoolFind(value) : null);
if(strRef != null)
{
m_xmlWriter.WriteAttributeString(AttrRef, strRef);
}
else SubWriteValue(value);
m_xmlWriter.WriteEndElement(); // ElemValue
m_xmlWriter.WriteEndElement(); // ElemBinary
}
private void SubWriteValue(ProtectedBinary value)
{
if(value.IsProtected && (m_format != KdbxFormat.PlainXml))
{
m_xmlWriter.WriteAttributeString(AttrProtected, ValTrue);
byte[] pbEncoded = value.ReadXorredData(m_randomStream);
if(pbEncoded.Length > 0)
m_xmlWriter.WriteBase64(pbEncoded, 0, pbEncoded.Length);
}
else
{
if(m_pwDatabase.Compression == PwCompressionAlgorithm.GZip)
{
m_xmlWriter.WriteAttributeString(AttrCompressed, ValTrue);
byte[] pbRaw = value.ReadData();
byte[] pbCmp = MemUtil.Compress(pbRaw);
m_xmlWriter.WriteBase64(pbCmp, 0, pbCmp.Length);
}
else
{
byte[] pbRaw = value.ReadData();
m_xmlWriter.WriteBase64(pbRaw, 0, pbRaw.Length);
}
}
}
private void WriteObject(string name, PwDeletedObject value)
{
Debug.Assert(name != null);
Debug.Assert(value != null); if(value == null) throw new ArgumentNullException("value");
m_xmlWriter.WriteStartElement(name);
WriteObject(ElemUuid, value.Uuid);
WriteObject(ElemDeletionTime, value.DeletionTime);
m_xmlWriter.WriteEndElement();
}
private void WriteBinPool()
{
m_xmlWriter.WriteStartElement(ElemBinaries);
foreach(KeyValuePair<string, ProtectedBinary> kvp in m_dictBinPool)
{
m_xmlWriter.WriteStartElement(ElemBinary);
m_xmlWriter.WriteAttributeString(AttrId, kvp.Key);
SubWriteValue(kvp.Value);
m_xmlWriter.WriteEndElement();
}
m_xmlWriter.WriteEndElement();
}
[Obsolete]
public static bool WriteEntries(Stream msOutput, PwDatabase pwDatabase,
PwEntry[] vEntries)
{
return WriteEntries(msOutput, vEntries);
}
/// <summary>
/// Write entries to a stream.
/// </summary>
/// <param name="msOutput">Output stream to which the entries will be written.</param>
/// <param name="vEntries">Entries to serialize.</param>
/// <returns>Returns <c>true</c>, if the entries were written successfully
/// to the stream.</returns>
public static bool WriteEntries(Stream msOutput, PwEntry[] vEntries)
{
/* KdbxFile f = new KdbxFile(pwDatabase);
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());
foreach(PwEntry peCopy in vEntries)
pd.RootGroup.AddEntry(peCopy.CloneDeep(), true);
KdbxFile f = new KdbxFile(pd);
f.Save(msOutput, null, KdbxFormat.PlainXml, null);
return true;
}
}
}