Java tutorial
package com.hanhuy.keepassj; /* KeePass Password Safe - The Open-Source Password Manager Copyright (C) 2003-2014 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 */ import com.google.common.base.Objects; import com.google.common.base.Strings; import com.google.common.collect.Maps; import com.google.common.io.BaseEncoding; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory; import org.xmlpull.v1.XmlSerializer; import java.io.*; import java.nio.charset.Charset; import java.security.MessageDigest; import java.util.*; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; /// <summary> /// Serialization to KeePass KDBX files. /// </summary> public class KdbxFile { /// <summary> /// File identifier, first 32-bit value. /// </summary> static final int FileSignature1 = 0x9AA2D903; /// <summary> /// File identifier, second 32-bit value. /// </summary> static final int FileSignature2 = 0xB54BFB67; /// <summary> /// File version of files saved by the current <c>KdbxFile</c> class. /// 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. /// The first 2 bytes are critical (i.e. loading will fail, if the /// file version is too high), the last 2 bytes are informational. /// </summary> private final static int FileVersion32 = 0x00030001; private final static int FileVersionCriticalMask = 0xFFFF0000; // KeePass 1.x signature final static int FileSignatureOld1 = 0x9AA2D903; final static int FileSignatureOld2 = 0xB54BFB65; // KeePass 2.x pre-release (alpha and beta) signature final static int FileSignaturePreRelease1 = 0x9AA2D903; final static int FileSignaturePreRelease2 = 0xB54BFB66; private final static String ElemDocNode = "KeePassFile"; private final static String ElemMeta = "Meta"; private final static String ElemRoot = "Root"; private final static String ElemGroup = "Group"; private final static String ElemEntry = "Entry"; private final static String ElemGenerator = "Generator"; private final static String ElemHeaderHash = "HeaderHash"; private final static String ElemDbName = "DatabaseName"; private final static String ElemDbNameChanged = "DatabaseNameChanged"; private final static String ElemDbDesc = "DatabaseDescription"; private final static String ElemDbDescChanged = "DatabaseDescriptionChanged"; private final static String ElemDbDefaultUser = "DefaultUserName"; private final static String ElemDbDefaultUserChanged = "DefaultUserNameChanged"; private final static String ElemDbMntncHistoryDays = "MaintenanceHistoryDays"; private final static String ElemDbColor = "Color"; private final static String ElemDbKeyChanged = "MasterKeyChanged"; private final static String ElemDbKeyChangeRec = "MasterKeyChangeRec"; private final static String ElemDbKeyChangeForce = "MasterKeyChangeForce"; private final static String ElemRecycleBinEnabled = "RecycleBinEnabled"; private final static String ElemRecycleBinUuid = "RecycleBinUUID"; private final static String ElemRecycleBinChanged = "RecycleBinChanged"; private final static String ElemEntryTemplatesGroup = "EntryTemplatesGroup"; private final static String ElemEntryTemplatesGroupChanged = "EntryTemplatesGroupChanged"; private final static String ElemHistoryMaxItems = "HistoryMaxItems"; private final static String ElemHistoryMaxSize = "HistoryMaxSize"; private final static String ElemLastSelectedGroup = "LastSelectedGroup"; private final static String ElemLastTopVisibleGroup = "LastTopVisibleGroup"; private final static String ElemMemoryProt = "MemoryProtection"; private final static String ElemProtTitle = "ProtectTitle"; private final static String ElemProtUserName = "ProtectUserName"; private final static String ElemProtPassword = "ProtectPassword"; private final static String ElemProtUrl = "ProtectURL"; private final static String ElemProtNotes = "ProtectNotes"; // private final static String ElemProtAutoHide = "AutoEnableVisualHiding"; private final static String ElemCustomIcons = "CustomIcons"; private final static String ElemCustomIconItem = "Icon"; private final static String ElemCustomIconItemID = "UUID"; private final static String ElemCustomIconItemData = "Data"; private final static String ElemAutoType = "AutoType"; private final static String ElemHistory = "History"; private final static String ElemName = "Name"; private final static String ElemNotes = "Notes"; private final static String ElemUuid = "UUID"; private final static String ElemIcon = "IconID"; private final static String ElemCustomIconID = "CustomIconUUID"; private final static String ElemFgColor = "ForegroundColor"; private final static String ElemBgColor = "BackgroundColor"; private final static String ElemOverrideUrl = "OverrideURL"; private final static String ElemTimes = "Times"; private final static String ElemTags = "Tags"; private final static String ElemCreationTime = "CreationTime"; private final static String ElemLastModTime = "LastModificationTime"; private final static String ElemLastAccessTime = "LastAccessTime"; private final static String ElemExpiryTime = "ExpiryTime"; private final static String ElemExpires = "Expires"; private final static String ElemUsageCount = "UsageCount"; private final static String ElemLocationChanged = "LocationChanged"; private final static String ElemGroupDefaultAutoTypeSeq = "DefaultAutoTypeSequence"; private final static String ElemEnableAutoType = "EnableAutoType"; private final static String ElemEnableSearching = "EnableSearching"; private final static String ElemString = "String"; private final static String ElemBinary = "Binary"; private final static String ElemKey = "Key"; private final static String ElemValue = "Value"; private final static String ElemAutoTypeEnabled = "Enabled"; private final static String ElemAutoTypeObfuscation = "DataTransferObfuscation"; private final static String ElemAutoTypeDefaultSeq = "DefaultSequence"; private final static String ElemAutoTypeItem = "Association"; private final static String ElemWindow = "Window"; private final static String ElemKeystrokeSequence = "KeystrokeSequence"; private final static String ElemBinaries = "Binaries"; private final static String AttrId = "ID"; private final static String AttrRef = "Ref"; private final static String AttrProtected = "Protected"; private final static String AttrProtectedInMemPlainXml = "ProtectInMemory"; private final static String AttrCompressed = "Compressed"; private final static String ElemIsExpanded = "IsExpanded"; private final static String ElemLastTopVisibleEntry = "LastTopVisibleEntry"; private final static String ElemDeletedObjects = "DeletedObjects"; private final static String ElemDeletedObject = "DeletedObject"; private final static String ElemDeletionTime = "DeletionTime"; private final static String ValFalse = "False"; private final static String ValTrue = "True"; private final static String ElemCustomData = "CustomData"; private final static String ElemStringDictExItem = "Item"; private PwDatabase m_pwDatabase; // Not null, see constructor private XmlSerializer m_xmlWriter = null; private CryptoRandomStream m_randomStream = null; private KdbxFormat m_format = KdbxFormat.Default; private IStatusLogger m_slLogger = null; private byte[] m_pbMasterSeed = null; private byte[] m_pbTransformSeed = null; private byte[] m_pbEncryptionIV = null; private byte[] m_pbProtectedStreamKey = null; private byte[] m_pbStreamStartBytes = null; // ArcFourVariant only for compatibility; KeePass will default to a // different (more secure) algorithm when *writing* databases private CrsAlgorithm m_craInnerRandomStream = CrsAlgorithm.ArcFourVariant; private Map<String, ProtectedBinary> m_dictBinPool = new HashMap<String, ProtectedBinary>(); private byte[] m_pbHashOfHeader = null; private byte[] m_pbHashOfFileOnDisk = null; private final Date m_dtNow = new Date(); // Cache current time private final static int NeutralLanguageOffset = 0x100000; // 2^20, see 32-bit Unicode specs private final static int NeutralLanguageIDSec = 0x7DC5C; // See 32-bit Unicode specs private final static int NeutralLanguageID = NeutralLanguageOffset + NeutralLanguageIDSec; private static boolean m_bLocalizedNames = false; private enum KdbxHeaderFieldID { EndOfHeader, Comment, CipherID, CompressionFlags, MasterSeed, TransformSeed, TransformRounds, EncryptionIV, ProtectedStreamKey, StreamStartBytes, InnerRandomStreamID } public byte[] getHashOfFileOnDisk() { return m_pbHashOfFileOnDisk; } private boolean m_bRepairMode = false; public boolean getRepairMode() { return m_bRepairMode; } public void setRepairMode(boolean value) { m_bRepairMode = value; } private String m_strDetachBins = null; /// <summary> /// Detach binaries when opening a file. If this isn't <c>null</c>, /// all binaries are saved to the specified path and are removed /// from the database. /// </summary> public String getDetachBinaries() { return m_strDetachBins; } public void setDetachBinaries(String value) { m_strDetachBins = value; } /// <summary> /// Default constructor. /// </summary> /// <param name="pwDataStore">The <c>PwDatabase</c> instance that the /// class will load file data into or use to create a KDBX file.</param> public KdbxFile(PwDatabase pwDataStore) { assert pwDataStore != null; if (pwDataStore == null) throw new IllegalArgumentException("pwDataStore"); m_pwDatabase = pwDataStore; } /// <summary> /// Call this once to determine the current localization settings. /// </summary> public static void DetermineLanguageId() { // Test if localized names should be used. If localized names are used, // the m_bLocalizedNames value must be set to true. By default, localized // names should be used! (Otherwise characters could be corrupted // because of different code pages). int uTest = 0; for (char ch : PwDatabase.getLocalizedAppName().toCharArray()) uTest = uTest * 5 + ch; m_bLocalizedNames = (uTest != NeutralLanguageID); } private void BinPoolBuild(PwGroup pgDataSource) { m_dictBinPool = new HashMap<String, ProtectedBinary>(); if (pgDataSource == null) { assert false; return; } EntryHandler eh = new EntryHandler() { public boolean delegate(PwEntry pe) { for (PwEntry peHistory : pe.getHistory()) { BinPoolAdd(peHistory.getBinaries()); } BinPoolAdd(pe.getBinaries()); return true; } }; pgDataSource.TraverseTree(TraversalMethod.PreOrder, null, eh); } private void BinPoolAdd(ProtectedBinaryDictionary dict) { for (Map.Entry<String, ProtectedBinary> kvp : dict) { BinPoolAdd(kvp.getValue()); } } private void BinPoolAdd(ProtectedBinary pb) { if (pb == null) { assert false; return; } if (BinPoolFind(pb) != null) return; // Exists already m_dictBinPool.put(String.valueOf(m_dictBinPool.size()), pb); } private String BinPoolFind(ProtectedBinary pb) { if (pb == null) { assert false; return null; } for (Map.Entry<String, ProtectedBinary> kvp : m_dictBinPool.entrySet()) { if (pb.Equals(kvp.getValue())) return kvp.getKey(); } return null; } private ProtectedBinary BinPoolGet(String strKey) { if (strKey == null) { assert false; return null; } return m_dictBinPool.get(strKey); } private static void SaveBinary(String strName, ProtectedBinary pb, String strSaveDir) throws IOException { if (pb == null) { assert false; return; } if (Strings.isNullOrEmpty(strName)) strName = "File.bin"; String strPath; int iTry = 1; do { strPath = UrlUtil.EnsureTerminatingSeparator(strSaveDir, false); String strExt = UrlUtil.GetExtension(strName); String strDesc = UrlUtil.StripExtension(strName); strPath += strDesc; if (iTry > 1) strPath += " (" + String.valueOf(iTry) + ")"; if (!Strings.isNullOrEmpty(strExt)) strPath += "." + strExt; ++iTry; } while (new File(strPath).exists()); FileOutputStream fs = new FileOutputStream(strPath, false); try { byte[] pbData = pb.ReadData(); fs.write(pbData, 0, pbData.length); } finally { fs.close(); } } // KdbxFile.Read.cs /// <summary> /// Serialization to KeePass KDBX files. /// </summary> /// <summary> /// Load a KDB file from a file. /// </summary> /// <param name="strFilePath">File to load.</param> /// <param name="kdbFormat">Format specifier.</param> /// <param name="slLogger">Status logger (optional).</param> public void Load(String strFilePath, KdbxFormat kdbFormat, IStatusLogger slLogger) throws IOException { IOConnectionInfo ioc = IOConnectionInfo.FromPath(strFilePath); Load(IOConnection.OpenRead(ioc), kdbFormat, slLogger); } /// <summary> /// Load a KDB file from a stream. /// </summary> /// <param name="sSource">Stream to read the data from. Must contain /// a KDBX stream.</param> /// <param name="kdbFormat">Format specifier.</param> /// <param name="slLogger">Status logger (optional).</param> public void Load(InputStream sSource, KdbxFormat kdbFormat, IStatusLogger slLogger) throws IOException { assert sSource != null; if (sSource == null) throw new IllegalArgumentException("sSource"); m_format = kdbFormat; m_slLogger = slLogger; HashingInputStreamEx hashedStream = new HashingInputStreamEx(sSource); Charset encNoBom = StrUtil.Utf8; try { BinaryReaderEx br = null; BinaryReaderEx brDecrypted = null; InputStream readerStream = null; if (kdbFormat == KdbxFormat.Default) { br = new BinaryReaderEx(hashedStream, encNoBom, "file corrupted"); ReadHeader(br); InputStream sDecrypted = AttachStreamDecryptor(hashedStream); if ((sDecrypted == null) || (sDecrypted == hashedStream)) throw new SecurityException("stream decryption failure"); brDecrypted = new BinaryReaderEx(sDecrypted, encNoBom, "file corrupted"); byte[] pbStoredStartBytes = brDecrypted.ReadBytes(32); if ((m_pbStreamStartBytes == null) || (m_pbStreamStartBytes.length != 32)) throw new KdbxFileFormatException("stream start bytes"); for (int iStart = 0; iStart < 32; ++iStart) { if (pbStoredStartBytes[iStart] != m_pbStreamStartBytes[iStart]) throw new InvalidCompositeKeyException(BaseEncoding.base16().encode(pbStoredStartBytes) + " != " + BaseEncoding.base16().encode(m_pbStreamStartBytes)); } InputStream sHashed = new HashedBlockStream.Input(sDecrypted, !m_bRepairMode); if (m_pwDatabase.getCompression() == PwCompressionAlgorithm.GZip) readerStream = new GZIPInputStream(sHashed); else readerStream = sHashed; } else if (kdbFormat == KdbxFormat.PlainXml) readerStream = hashedStream; else { assert false; throw new KdbxFileFormatException("KdbFormat"); } if (kdbFormat != KdbxFormat.PlainXml) // Is an encrypted format { if (m_pbProtectedStreamKey == null) { assert false; throw new SecurityException("Invalid protected stream key!"); } m_randomStream = new CryptoRandomStream(m_craInnerRandomStream, m_pbProtectedStreamKey); } else m_randomStream = null; // No random stream for plain-text files ReadXmlStreamed(readerStream, hashedStream); // ReadXmlDom(readerStream); readerStream.close(); // GC.KeepAlive(br); // GC.KeepAlive(brDecrypted); } catch (Exception e) // Thrown on invalid padding { throw new KdbxFileFormatException(e); } finally { try { CommonCleanUpRead(sSource, hashedStream); } catch (AssertionError e) { } } } private void CommonCleanUpRead(InputStream sSource, HashingInputStreamEx hashedStream) throws IOException { hashedStream.close(); m_pbHashOfFileOnDisk = hashedStream.getHash(); sSource.close(); // Reset memory protection settings (to always use reasonable // defaults) m_pwDatabase.setMemoryProtection(new MemoryProtectionConfig()); // Remove old backups (this call is required here in order to apply // the default history maintenance settings for people upgrading from // KeePass <= 2.14 to >= 2.15; also it ensures history integrity in // case a different application has created the KDBX file and ignored // the history maintenance settings) m_pwDatabase.MaintainBackups(); // Don't mark database as modified m_pbHashOfHeader = null; } private void ReadHeader(BinaryReaderEx br) throws IOException { ByteArrayOutputStream msHeader = new ByteArrayOutputStream(); assert br.getCopyDataTo() == null; br.setCopyDataTo(msHeader); byte[] pbSig1 = br.ReadBytes(4); int uSig1 = MemUtil.BytesToUInt32(pbSig1); byte[] pbSig2 = br.ReadBytes(4); int uSig2 = MemUtil.BytesToUInt32(pbSig2); if ((uSig1 == FileSignatureOld1) && (uSig2 == FileSignatureOld2)) throw new OldFormatException(PwDefs.ShortProductName + " 1.x", OldFormatException.OldFormatType.KeePass1x); if ((uSig1 == FileSignature1) && (uSig2 == FileSignature2)) { } else if ((uSig1 == FileSignaturePreRelease1) && (uSig2 == FileSignaturePreRelease2)) { } else throw new KdbxFileFormatException("invalid signature"); byte[] pb = br.ReadBytes(4); int uVersion = MemUtil.BytesToUInt32(pb); if ((uVersion & FileVersionCriticalMask) > (FileVersion32 & FileVersionCriticalMask)) throw new KdbxFileFormatException("Unsupported file version" + "\n" + "new version required"); while (true) { if (!ReadHeaderField(br)) break; } br.setCopyDataTo(null); byte[] pbHeader = msHeader.toByteArray(); msHeader.close(); m_pbHashOfHeader = Digests.sha256(pbHeader); } private boolean ReadHeaderField(BinaryReaderEx brSource) throws IOException { assert brSource != null; if (brSource == null) throw new IllegalArgumentException("brSource"); byte btFieldID = brSource.ReadByte(); short uSize = MemUtil.BytesToUInt16(brSource.ReadBytes(2)); byte[] pbData = null; if (uSize > 0) { String strPrevExcpText = brSource.getReadExceptionText(); brSource.setReadExceptionText("file header end early"); pbData = brSource.ReadBytes(uSize); brSource.setReadExceptionText(strPrevExcpText); } boolean bResult = true; KdbxHeaderFieldID kdbID = KdbxHeaderFieldID.values()[btFieldID]; switch (kdbID) { case EndOfHeader: bResult = false; // Returning false indicates end of header break; case CipherID: SetCipher(pbData); break; case CompressionFlags: SetCompressionFlags(pbData); break; case MasterSeed: m_pbMasterSeed = pbData; CryptoRandom.getInstance().AddEntropy(pbData); break; case TransformSeed: m_pbTransformSeed = pbData; CryptoRandom.getInstance().AddEntropy(pbData); break; case TransformRounds: m_pwDatabase.setKeyEncryptionRounds(MemUtil.BytesToUInt64(pbData)); break; case EncryptionIV: m_pbEncryptionIV = pbData; break; case ProtectedStreamKey: m_pbProtectedStreamKey = pbData; CryptoRandom.getInstance().AddEntropy(pbData); break; case StreamStartBytes: m_pbStreamStartBytes = pbData; break; case InnerRandomStreamID: SetInnerRandomStreamID(pbData); break; default: assert false; if (m_slLogger != null) m_slLogger.SetText("unknown header id" + ": " + kdbID + "!", LogStatusType.Warning); break; } return bResult; } private void SetCipher(byte[] pbID) { if ((pbID == null) || (pbID.length != 16)) throw new KdbxFileFormatException("unknown cipher"); m_pwDatabase.setDataCipherUuid(new PwUuid(pbID)); } private void SetCompressionFlags(byte[] pbFlags) { int nID = (int) MemUtil.BytesToUInt32(pbFlags); if ((nID < 0) || (nID >= PwCompressionAlgorithm.Count.ordinal())) throw new KdbxFileFormatException("unknown file compression type"); m_pwDatabase.setCompression(PwCompressionAlgorithm.values()[nID]); } private void SetInnerRandomStreamID(byte[] pbID) { int uID = MemUtil.BytesToUInt32(pbID); if (uID >= CrsAlgorithm.Count.ordinal()) throw new KdbxFileFormatException("unknown cipher"); m_craInnerRandomStream = CrsAlgorithm.values()[uID]; } private InputStream AttachStreamDecryptor(InputStream s) { ByteArrayOutputStream ms = new ByteArrayOutputStream(); assert m_pbMasterSeed.length == 32; if (m_pbMasterSeed.length != 32) throw new KdbxFileFormatException("master seed length invalid"); ms.write(m_pbMasterSeed, 0, 32); byte[] pKey32 = m_pwDatabase.getMasterKey() .GenerateKey32(m_pbTransformSeed, m_pwDatabase.getKeyEncryptionRounds()).ReadData(); if ((pKey32 == null) || (pKey32.length != 32)) throw new SecurityException("invalid composite key"); ms.write(pKey32, 0, 32); byte[] aesKey = Digests.sha256(ms.toByteArray()); Arrays.fill(pKey32, 0, 32, (byte) 0); if ((aesKey == null) || (aesKey.length != 32)) throw new SecurityException("final key creation failed"); ICipherEngine iEngine = CipherPool.getGlobalPool().GetCipher(m_pwDatabase.getDataCipherUuid()); if (iEngine == null) throw new SecurityException("Unknown file cipher"); return iEngine.DecryptStream(s, aesKey, m_pbEncryptionIV); } @Deprecated public static List<PwEntry> ReadEntries(PwDatabase pwDatabase, InputStream msData) throws IOException { return ReadEntries(msData); } /// <summary> /// Read entries from a stream. /// </summary> /// <param name="msData">Input stream to read the entries from.</param> /// <returns>Extracted entries.</returns> public static List<PwEntry> ReadEntries(InputStream msData) throws IOException { /* KdbxFile f = new KdbxFile(pwDatabase); f.m_format = KdbxFormat.PlainXml; XmlDocument doc = new XmlDocument(); doc.Load(msData); XmlElement el = doc.DocumentElement; if(el.Name != ElemRoot) throw new FormatException(); List<PwEntry> vEntries = new List<PwEntry>(); for(XmlNode xmlChild : el.ChildNodes) { if(xmlChild.Name == ElemEntry) { PwEntry pe = f.ReadEntry(xmlChild); pe.Uuid = new PwUuid(true); for(PwEntry peHistory : pe.History) peHistory.Uuid = pe.Uuid; vEntries.Add(pe); } else { assert false; } } return vEntries; */ PwDatabase pd = new PwDatabase(); KdbxFile f = new KdbxFile(pd); f.Load(msData, KdbxFormat.PlainXml, null); List<PwEntry> vEntries = new ArrayList<PwEntry>(); for (PwEntry pe : pd.getRootGroup().getEntries()) { pe.SetUuid(new PwUuid(true), true); vEntries.add(pe); } return vEntries; } // KdbxFile.Read.Streamed.cs private enum KdbContext { Null, KeePassFile, Meta, Root, MemoryProtection, CustomIcons, CustomIcon, Binaries, CustomData, CustomDataItem, RootDeletedObjects, DeletedObject, Group, GroupTimes, Entry, EntryTimes, EntryString, EntryBinary, EntryAutoType, EntryAutoTypeItem, EntryHistory } private boolean m_bReadNextNode = true; private Stack<PwGroup> m_ctxGroups = new Stack<PwGroup>(); 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 boolean 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 void ReadXmlStreamed(InputStream readerStream, InputStream sParentStream) throws IOException, XmlPullParserException { ReadDocumentStreamed(CreateXmlReader(readerStream), sParentStream); } /* static XmlReaderSettings CreateStdXmlReaderSettings() { { XmlReaderSettings xrs = new XmlReaderSettings(); xrs.CloseInput = true; xrs.IgnoreComments = true; xrs.IgnoreProcessingInstructions = true; xrs.IgnoreWhitespace = true; xrs.ProhibitDtd = true; xrs.ValidationType = ValidationType.None; return xrs; } */ private static XmlPullParser CreateXmlReader(InputStream readerStream) { try { XmlPullParserFactory fact = XmlPullParserFactory.newInstance(); fact.setFeature(XmlPullParser.FEATURE_VALIDATION, false); // XmlReaderSettings xrs = CreateStdXmlReaderSettings(); XmlPullParser xr = fact.newPullParser(); xr.setInput(readerStream, "utf-8"); return xr; } catch (Exception e) { throw new IllegalStateException(e); } } private void ReadDocumentStreamed(XmlPullParser xr, InputStream sParentStream) throws IOException, XmlPullParserException { assert xr != null; if (xr == null) throw new IllegalArgumentException("xr"); m_ctxGroups.clear(); m_dictBinPool = new HashMap<String, ProtectedBinary>(); KdbContext ctx = KdbContext.Null; int uTagCounter = 0; boolean bSupportsStatus = (m_slLogger != null); long lStreamLength = 1; /* try { sParentStream.Position.ToString(); // Test Position support lStreamLength = sParentStream.Length; } catch(Exception e) { bSupportsStatus = false; } */ if (lStreamLength <= 0) { assert false; lStreamLength = 1; } m_bReadNextNode = true; while (true) { if (m_bReadNextNode) { if (xr.next() == XmlPullParser.END_DOCUMENT) break; } else m_bReadNextNode = true; switch (xr.getEventType()) { case XmlPullParser.START_TAG: ctx = ReadXmlElement(ctx, xr); if (xr.getEventType() == XmlPullParser.START_TAG && xr.isEmptyElementTag()) xr.next(); // skip empty END_TAG break; case XmlPullParser.END_TAG: ctx = EndXmlElement(ctx, xr); break; case XmlPullParser.DOCDECL: break; // Ignore default: // assert false; break; } ++uTagCounter; /* if(((uTagCounter % 256) == 0) && bSupportsStatus) { assert lStreamLength == sParentStream.Length; int uPct = (int)((sParentStream.Position * 100) / lStreamLength); // Clip percent value in case the stream reports incorrect // position/length values (M120413) if(uPct > 100) { assert false; uPct = 100; } m_slLogger.SetProgress(uPct); } */ } assert ctx == KdbContext.Null; if (ctx != KdbContext.Null) throw new KdbxFileFormatException("non null context"); assert m_ctxGroups.size() == 0; if (m_ctxGroups.size() != 0) throw new KdbxFileFormatException("nonzero groups"); } private KdbContext ReadXmlElement(KdbContext ctx, XmlPullParser xr) throws XmlPullParserException, IOException { switch (ctx) { case Null: if (Objects.equal(xr.getName(), ElemDocNode)) return SwitchContext(ctx, KdbContext.KeePassFile, xr); else ReadUnknown(xr); break; case KeePassFile: if (xr.getName().equals(ElemMeta)) return SwitchContext(ctx, KdbContext.Meta, xr); else if (xr.getName().equals(ElemRoot)) return SwitchContext(ctx, KdbContext.Root, xr); else ReadUnknown(xr); break; case Meta: if (xr.getName().equals(ElemGenerator)) ReadString(xr); // Ignore else if (xr.getName().equals(ElemHeaderHash)) { String strHash = ReadString(xr); if (!Strings.isNullOrEmpty(strHash) && (m_pbHashOfHeader != null) && !m_bRepairMode) { byte[] pbHash = BaseEncoding.base64().decode(strHash); if (!MemUtil.ArraysEqual(pbHash, m_pbHashOfHeader)) throw new KdbxFileFormatException("file corrupted"); } } else if (xr.getName().equals(ElemDbName)) m_pwDatabase.setName(ReadString(xr)); else if (xr.getName().equals(ElemDbNameChanged)) m_pwDatabase.setNameChanged(ReadTime(xr)); else if (xr.getName().equals(ElemDbDesc)) m_pwDatabase.setDescription(ReadString(xr)); else if (xr.getName().equals(ElemDbDescChanged)) m_pwDatabase.setDescriptionChanged(ReadTime(xr)); else if (xr.getName().equals(ElemDbDefaultUser)) m_pwDatabase.setDefaultUserName(ReadString(xr)); else if (xr.getName().equals(ElemDbDefaultUserChanged)) m_pwDatabase.setDefaultUserNameChanged(ReadTime(xr)); else if (xr.getName().equals(ElemDbMntncHistoryDays)) m_pwDatabase.setMaintenanceHistoryDays(ReadUInt(xr, 365)); else if (xr.getName().equals(ElemDbColor)) { String strColor = ReadString(xr); if (!Strings.isNullOrEmpty(strColor)) m_pwDatabase.setColor(ColorTranslator.FromHtml(strColor)); } else if (xr.getName().equals(ElemDbKeyChanged)) m_pwDatabase.setMasterKeyChanged(ReadTime(xr)); else if (xr.getName().equals(ElemDbKeyChangeRec)) m_pwDatabase.setMasterKeyChangeRec(ReadLong(xr, -1)); else if (xr.getName().equals(ElemDbKeyChangeForce)) m_pwDatabase.setMasterKeyChangeForce(ReadLong(xr, -1)); else if (xr.getName().equals(ElemMemoryProt)) return SwitchContext(ctx, KdbContext.MemoryProtection, xr); else if (xr.getName().equals(ElemCustomIcons)) return SwitchContext(ctx, KdbContext.CustomIcons, xr); else if (xr.getName().equals(ElemRecycleBinEnabled)) m_pwDatabase.setRecycleBinEnabled(ReadBool(xr, true)); else if (xr.getName().equals(ElemRecycleBinUuid)) m_pwDatabase.setRecycleBinUuid(ReadUuid(xr)); else if (xr.getName().equals(ElemRecycleBinChanged)) m_pwDatabase.setRecycleBinChanged(ReadTime(xr)); else if (xr.getName().equals(ElemEntryTemplatesGroup)) m_pwDatabase.setEntryTemplatesGroup(ReadUuid(xr)); else if (xr.getName().equals(ElemEntryTemplatesGroupChanged)) m_pwDatabase.setEntryTemplatesGroupChanged(ReadTime(xr)); else if (xr.getName().equals(ElemHistoryMaxItems)) m_pwDatabase.setHistoryMaxItems(ReadInt(xr, -1)); else if (xr.getName().equals(ElemHistoryMaxSize)) m_pwDatabase.setHistoryMaxSize(ReadLong(xr, -1)); else if (xr.getName().equals(ElemLastSelectedGroup)) { m_pwDatabase.setLastSelectedGroup(ReadUuid(xr)); } else if (xr.getName().equals(ElemLastTopVisibleGroup)) m_pwDatabase.setLastTopVisibleGroup(ReadUuid(xr)); else if (xr.getName().equals(ElemBinaries)) return SwitchContext(ctx, KdbContext.Binaries, xr); else if (xr.getName().equals(ElemCustomData)) return SwitchContext(ctx, KdbContext.CustomData, xr); else ReadUnknown(xr); break; case MemoryProtection: if (xr.getName().equals(ElemProtTitle)) m_pwDatabase.getMemoryProtection().ProtectTitle = ReadBool(xr, false); else if (xr.getName().equals(ElemProtUserName)) m_pwDatabase.getMemoryProtection().ProtectUserName = ReadBool(xr, false); else if (xr.getName().equals(ElemProtPassword)) m_pwDatabase.getMemoryProtection().ProtectPassword = ReadBool(xr, true); else if (xr.getName().equals(ElemProtUrl)) m_pwDatabase.getMemoryProtection().ProtectUrl = ReadBool(xr, false); else if (xr.getName().equals(ElemProtNotes)) m_pwDatabase.getMemoryProtection().ProtectNotes = ReadBool(xr, false); // else if(xr.Name == ElemProtAutoHide) // m_pwDatabase.MemoryProtection.AutoEnableVisualHiding = ReadBool(xr, true); else ReadUnknown(xr); break; case CustomIcons: if (xr.getName().equals(ElemCustomIconItem)) return SwitchContext(ctx, KdbContext.CustomIcon, xr); else ReadUnknown(xr); break; case CustomIcon: if (xr.getName().equals(ElemCustomIconItemID)) m_uuidCustomIconID = ReadUuid(xr); else if (xr.getName().equals(ElemCustomIconItemData)) { String strData = ReadString(xr); if (!Strings.isNullOrEmpty(strData)) m_pbCustomIconData = BaseEncoding.base64().decode(strData); else { assert false; } } else ReadUnknown(xr); break; case Binaries: if (xr.getName().equals(ElemBinary)) { String strKey = xr.getAttributeValue(null, AttrId); if (strKey != null) { ProtectedBinary pbData = ReadProtectedBinary(xr); m_dictBinPool.put(strKey == null ? "" : strKey, pbData); } else ReadUnknown(xr); } else ReadUnknown(xr); break; case CustomData: if (xr.getName().equals(ElemStringDictExItem)) return SwitchContext(ctx, KdbContext.CustomDataItem, xr); else ReadUnknown(xr); break; case CustomDataItem: if (xr.getName().equals(ElemKey)) m_strCustomDataKey = ReadString(xr); else if (xr.getName().equals(ElemValue)) m_strCustomDataValue = ReadString(xr); else ReadUnknown(xr); break; case Root: if (xr.getName().equals(ElemGroup)) { assert m_ctxGroups.size() == 0; if (m_ctxGroups.size() != 0) throw new KdbxFileFormatException("groups size not 0"); m_pwDatabase.setRootGroup(new PwGroup(false, false)); m_ctxGroups.push(m_pwDatabase.getRootGroup()); m_ctxGroup = m_ctxGroups.peek(); return SwitchContext(ctx, KdbContext.Group, xr); } else if (xr.getName().equals(ElemDeletedObjects)) return SwitchContext(ctx, KdbContext.RootDeletedObjects, xr); else ReadUnknown(xr); break; case Group: if (xr.getName().equals(ElemUuid)) m_ctxGroup.setUuid(ReadUuid(xr)); else if (xr.getName().equals(ElemName)) m_ctxGroup.setName(ReadString(xr)); else if (xr.getName().equals(ElemNotes)) m_ctxGroup.setNotes(ReadString(xr)); else if (xr.getName().equals(ElemIcon)) m_ctxGroup.setIconId(PwIcon.values()[ReadInt(xr, PwIcon.Folder.ordinal())]); else if (xr.getName().equals(ElemCustomIconID)) m_ctxGroup.setCustomIconUuid(ReadUuid(xr)); else if (xr.getName().equals(ElemTimes)) return SwitchContext(ctx, KdbContext.GroupTimes, xr); else if (xr.getName().equals(ElemIsExpanded)) m_ctxGroup.setExpanded(ReadBool(xr, true)); else if (xr.getName().equals(ElemGroupDefaultAutoTypeSeq)) m_ctxGroup.setDefaultAutoTypeSequence(ReadString(xr)); else if (xr.getName().equals(ElemEnableAutoType)) m_ctxGroup.setEnableAutoType(StrUtil.StringToBoolEx(ReadString(xr))); else if (xr.getName().equals(ElemEnableSearching)) m_ctxGroup.setEnableSearching(StrUtil.StringToBoolEx(ReadString(xr))); else if (xr.getName().equals(ElemLastTopVisibleEntry)) m_ctxGroup.setLastTopVisibleEntry(ReadUuid(xr)); else if (xr.getName().equals(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.getName().equals(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 Entry: if (xr.getName().equals(ElemUuid)) { m_ctxEntry.setUuid(ReadUuid(xr)); } else if (xr.getName().equals(ElemIcon)) m_ctxEntry.setIconId(PwIcon.values()[ReadInt(xr, PwIcon.Key.ordinal())]); else if (xr.getName().equals(ElemCustomIconID)) m_ctxEntry.setCustomIconUuid(ReadUuid(xr)); else if (xr.getName().equals(ElemFgColor)) { String strColor = ReadString(xr); if (!Strings.isNullOrEmpty(strColor)) m_ctxEntry.setForegroundColor(ColorTranslator.FromHtml(strColor)); } else if (xr.getName().equals(ElemBgColor)) { String strColor = ReadString(xr); if (!Strings.isNullOrEmpty(strColor)) m_ctxEntry.setBackgroundColor(ColorTranslator.FromHtml(strColor)); } else if (xr.getName().equals(ElemOverrideUrl)) m_ctxEntry.setOverrideUrl(ReadString(xr)); else if (xr.getName().equals(ElemTags)) m_ctxEntry.setTags(StrUtil.StringToTags(ReadString(xr))); else if (xr.getName().equals(ElemTimes)) return SwitchContext(ctx, KdbContext.EntryTimes, xr); else if (xr.getName().equals(ElemString)) return SwitchContext(ctx, KdbContext.EntryString, xr); else if (xr.getName().equals(ElemBinary)) return SwitchContext(ctx, KdbContext.EntryBinary, xr); else if (xr.getName().equals(ElemAutoType)) return SwitchContext(ctx, KdbContext.EntryAutoType, xr); else if (xr.getName().equals(ElemHistory)) { assert !m_bEntryInHistory; if (!m_bEntryInHistory) { m_ctxHistoryBase = m_ctxEntry; return SwitchContext(ctx, KdbContext.EntryHistory, xr); } else ReadUnknown(xr); } else ReadUnknown(xr); break; case GroupTimes: case EntryTimes: ITimeLogger tl = ((ctx == KdbContext.GroupTimes) ? (ITimeLogger) m_ctxGroup : (ITimeLogger) m_ctxEntry); assert tl != null; if (xr.getName().equals(ElemCreationTime)) tl.setCreationTime(ReadTime(xr)); else if (xr.getName().equals(ElemLastModTime)) tl.setLastModificationTime(ReadTime(xr)); else if (xr.getName().equals(ElemLastAccessTime)) tl.setLastAccessTime(ReadTime(xr)); else if (xr.getName().equals(ElemExpiryTime)) tl.setExpiryTime(ReadTime(xr)); else if (xr.getName().equals(ElemExpires)) tl.setExpires(ReadBool(xr, false)); else if (xr.getName().equals(ElemUsageCount)) tl.setUsageCount(ReadULong(xr, 0)); else if (xr.getName().equals(ElemLocationChanged)) tl.setLocationChanged(ReadTime(xr)); else ReadUnknown(xr); break; case EntryString: if (xr.getName().equals(ElemKey)) m_ctxStringName = ReadString(xr); else if (xr.getName().equals(ElemValue)) m_ctxStringValue = ReadProtectedString(xr); else ReadUnknown(xr); break; case EntryBinary: if (xr.getName().equals(ElemKey)) m_ctxBinaryName = ReadString(xr); else if (xr.getName().equals(ElemValue)) m_ctxBinaryValue = ReadProtectedBinary(xr); else ReadUnknown(xr); break; case EntryAutoType: if (xr.getName().equals(ElemAutoTypeEnabled)) m_ctxEntry.getAutoType().setEnabled(ReadBool(xr, true)); else if (xr.getName().equals(ElemAutoTypeObfuscation)) m_ctxEntry.getAutoType().setObfuscationOptions(AutoTypeObfuscationOptions.values()[ReadInt(xr, 0)]); else if (xr.getName().equals(ElemAutoTypeDefaultSeq)) m_ctxEntry.getAutoType().setDefaultSequence(ReadString(xr)); else if (xr.getName().equals(ElemAutoTypeItem)) return SwitchContext(ctx, KdbContext.EntryAutoTypeItem, xr); else ReadUnknown(xr); break; case EntryAutoTypeItem: if (xr.getName().equals(ElemWindow)) m_ctxATName = ReadString(xr); else if (xr.getName().equals(ElemKeystrokeSequence)) m_ctxATSeq = ReadString(xr); else ReadUnknown(xr); break; case EntryHistory: if (xr.getName().equals(ElemEntry)) { m_ctxEntry = new PwEntry(false, false); m_ctxHistoryBase.getHistory().Add(m_ctxEntry); m_bEntryInHistory = true; return SwitchContext(ctx, KdbContext.Entry, xr); } else ReadUnknown(xr); break; case RootDeletedObjects: if (xr.getName().equals(ElemDeletedObject)) { m_ctxDeletedObject = new PwDeletedObject(); m_pwDatabase.getDeletedObjects().Add(m_ctxDeletedObject); return SwitchContext(ctx, KdbContext.DeletedObject, xr); } else ReadUnknown(xr); break; case DeletedObject: if (xr.getName().equals(ElemUuid)) m_ctxDeletedObject.setUuid(ReadUuid(xr)); else if (xr.getName().equals(ElemDeletionTime)) m_ctxDeletedObject.setDeletionTime(ReadTime(xr)); else ReadUnknown(xr); break; default: ReadUnknown(xr); break; } return ctx; } private KdbContext EndXmlElement(KdbContext ctx, XmlPullParser xr) throws XmlPullParserException, IOException { assert xr.getEventType() == XmlPullParser.END_TAG; if ((ctx == KdbContext.KeePassFile) && (xr.getName().equals(ElemDocNode))) return KdbContext.Null; else if ((ctx == KdbContext.Meta) && (xr.getName().equals(ElemMeta))) return KdbContext.KeePassFile; else if ((ctx == KdbContext.Root) && (xr.getName().equals(ElemRoot))) return KdbContext.KeePassFile; else if ((ctx == KdbContext.MemoryProtection) && (xr.getName().equals(ElemMemoryProt))) return KdbContext.Meta; else if ((ctx == KdbContext.CustomIcons) && (xr.getName().equals(ElemCustomIcons))) return KdbContext.Meta; else if ((ctx == KdbContext.CustomIcon) && (xr.getName().equals(ElemCustomIconItem))) { if (!m_uuidCustomIconID.Equals(PwUuid.Zero) && (m_pbCustomIconData != null)) m_pwDatabase.getCustomIcons().add(new PwCustomIcon(m_uuidCustomIconID, m_pbCustomIconData)); else { assert false; } m_uuidCustomIconID = PwUuid.Zero; m_pbCustomIconData = null; return KdbContext.CustomIcons; } else if ((ctx == KdbContext.Binaries) && (xr.getName().equals(ElemBinaries))) return KdbContext.Meta; else if ((ctx == KdbContext.CustomData) && (xr.getName().equals(ElemCustomData))) return KdbContext.Meta; else if ((ctx == KdbContext.CustomDataItem) && (xr.getName().equals(ElemStringDictExItem))) { if ((m_strCustomDataKey != null) && (m_strCustomDataValue != null)) m_pwDatabase.getCustomData().Set(m_strCustomDataKey, m_strCustomDataValue); else { assert false; } m_strCustomDataKey = null; m_strCustomDataValue = null; return KdbContext.CustomData; } else if ((ctx == KdbContext.Group) && (xr.getName().equals(ElemGroup))) { if (PwUuid.Zero.Equals(m_ctxGroup.getUuid())) m_ctxGroup.setUuid(new PwUuid(true)); // No assert (import) m_ctxGroups.pop(); if (m_ctxGroups.size() == 0) { m_ctxGroup = null; return KdbContext.Root; } else { m_ctxGroup = m_ctxGroups.peek(); return KdbContext.Group; } } else if ((ctx == KdbContext.GroupTimes) && (xr.getName().equals(ElemTimes))) return KdbContext.Group; else if ((ctx == KdbContext.Entry) && (xr.getName().equals(ElemEntry))) { // Create new UUID if absent if (PwUuid.Zero.Equals(m_ctxEntry.getUuid())) m_ctxEntry.setUuid(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.getName().equals(ElemTimes))) return KdbContext.Entry; else if ((ctx == KdbContext.EntryString) && (xr.getName().equals(ElemString))) { m_ctxEntry.getStrings().Set(m_ctxStringName, m_ctxStringValue); m_ctxStringName = null; m_ctxStringValue = null; return KdbContext.Entry; } else if ((ctx == KdbContext.EntryBinary) && (xr.getName().equals(ElemBinary))) { if (Strings.isNullOrEmpty(m_strDetachBins)) m_ctxEntry.getBinaries().Set(m_ctxBinaryName, m_ctxBinaryValue); else { SaveBinary(m_ctxBinaryName, m_ctxBinaryValue, m_strDetachBins); m_ctxBinaryValue = null; } m_ctxBinaryName = null; m_ctxBinaryValue = null; return KdbContext.Entry; } else if ((ctx == KdbContext.EntryAutoType) && (xr.getName().equals(ElemAutoType))) return KdbContext.Entry; else if ((ctx == KdbContext.EntryAutoTypeItem) && (xr.getName().equals(ElemAutoTypeItem))) { AutoTypeAssociation atAssoc = new AutoTypeAssociation(m_ctxATName, m_ctxATSeq); m_ctxEntry.getAutoType().Add(atAssoc); m_ctxATName = null; m_ctxATSeq = null; return KdbContext.EntryAutoType; } else if ((ctx == KdbContext.EntryHistory) && (xr.getName().equals(ElemHistory))) { m_bEntryInHistory = false; return KdbContext.Entry; } else if ((ctx == KdbContext.RootDeletedObjects) && (xr.getName().equals(ElemDeletedObjects))) return KdbContext.Root; else if ((ctx == KdbContext.DeletedObject) && (xr.getName().equals(ElemDeletedObject))) { m_ctxDeletedObject = null; return KdbContext.RootDeletedObjects; } else { throw new KdbxFileFormatException("xml end tag failure: " + ctx + " => " + xr.getName()); } } private String ReadString(XmlPullParser xr) throws XmlPullParserException, IOException { XorredBuffer xb = ProcessNode(xr); if (xb != null) { byte[] pb = xb.ReadPlainText(); if (pb.length == 0) return ""; return new String(pb, 0, pb.length, StrUtil.Utf8); } //m_bReadNextNode = false; // ReadElementString skips end tag return xr.nextText(); } private String ReadStringRaw(XmlPullParser xr) throws XmlPullParserException, IOException { //m_bReadNextNode = false; // ReadElementString skips end tag return xr.nextText(); } private boolean ReadBool(XmlPullParser xr, boolean bDefault) throws XmlPullParserException, IOException { String str = ReadString(xr); if (str.equals(ValTrue)) return true; else if (str.equals(ValFalse)) return false; assert false; return bDefault; } private PwUuid ReadUuid(XmlPullParser xr) throws XmlPullParserException, IOException { String str = ReadString(xr); if (Strings.isNullOrEmpty(str)) return PwUuid.Zero; return new PwUuid(BaseEncoding.base64().decode(str)); } private int ReadInt(XmlPullParser xr, int nDefault) throws XmlPullParserException, IOException { String str = ReadString(xr); int[] n = new int[1]; if (StrUtil.TryParseIntInvariant(str, n)) return n[0]; // Backward compatibility if (StrUtil.TryParseInt(str, n)) return n[0]; assert false; return nDefault; } private int ReadUInt(XmlPullParser xr, int uDefault) throws IOException, XmlPullParserException { String str = ReadString(xr); int[] u = new int[1]; if (StrUtil.TryParseUIntInvariant(str, u)) return u[0]; // Backward compatibility if (StrUtil.TryParseUInt(str, u)) return u[0]; assert false; return uDefault; } private long ReadLong(XmlPullParser xr, long lDefault) throws IOException, XmlPullParserException { String str = ReadString(xr); long[] l = new long[1]; if (StrUtil.TryParseLongInvariant(str, l)) return l[0]; // Backward compatibility if (StrUtil.TryParseLong(str, l)) return l[0]; assert false; return lDefault; } private long ReadULong(XmlPullParser xr, long uDefault) throws IOException, XmlPullParserException { return ReadLong(xr, uDefault); } private Date ReadTime(XmlPullParser xr) throws IOException, XmlPullParserException { String str = ReadString(xr); Date[] dt = new Date[1]; if (TimeUtil.TryDeserializeUtc(str, dt)) return dt[0]; assert false; return m_dtNow; } private ProtectedString ReadProtectedString(XmlPullParser xr) throws IOException, XmlPullParserException { XorredBuffer xb = ProcessNode(xr); if (xb != null) return new ProtectedString(true, xb); boolean bProtect = false; if (m_format == KdbxFormat.PlainXml) { String strProtect = xr.getAttributeValue(null, AttrProtectedInMemPlainXml); if (strProtect != null) { bProtect = ((strProtect != null) && (strProtect.equals(ValTrue))); } } ProtectedString ps = new ProtectedString(bProtect, ReadString(xr)); return ps; } private ProtectedBinary ReadProtectedBinary(XmlPullParser xr) throws IOException, XmlPullParserException { String strRef = xr.getAttributeValue(null, AttrRef); if (strRef != null) { ProtectedBinary pb = BinPoolGet(strRef); if (pb != null) return pb; else { assert false; } } boolean bCompressed = false; bCompressed = ValTrue.equals(xr.getAttributeValue(null, AttrCompressed)); XorredBuffer xb = ProcessNode(xr); if (xb != null) { assert !bCompressed; // See SubWriteValue(ProtectedBinary value) return new ProtectedBinary(true, xb); } String strValue = ReadString(xr); if (strValue.length() == 0) return new ProtectedBinary(); byte[] pbData = BaseEncoding.base64().decode(strValue); if (bCompressed) pbData = MemUtil.Decompress(pbData); return new ProtectedBinary(false, pbData); } private void ReadUnknown(XmlPullParser xr) throws XmlPullParserException, IOException { // assert false; // Unknown node! if (xr.isEmptyElementTag()) return; String strUnknownName = xr.getName(); ProcessNode(xr); int xrNodeType; while ((xrNodeType = xr.next()) != XmlPullParser.END_DOCUMENT) { if (xrNodeType == XmlPullParser.END_TAG) break; if (xrNodeType != XmlPullParser.START_TAG) continue; ReadUnknown(xr); } assert xr.getName().equals(strUnknownName); } private XorredBuffer ProcessNode(XmlPullParser xr) throws IOException, XmlPullParserException { // assert xr.NodeType == XmlNodeType.Element; XorredBuffer xb = null; if (xr.getAttributeCount() > 0) { if (ValTrue.equals(xr.getAttributeValue(null, AttrProtected))) { String strEncrypted = ReadStringRaw(xr); byte[] pbEncrypted; if (strEncrypted.length() > 0) pbEncrypted = BaseEncoding.base64().decode(strEncrypted); else pbEncrypted = new byte[0]; byte[] pbPad = m_randomStream.GetRandomBytes((int) pbEncrypted.length); xb = new XorredBuffer(pbEncrypted, pbPad); } } return xb; } private static KdbContext SwitchContext(KdbContext ctxCurrent, KdbContext ctxNew, XmlPullParser xr) throws XmlPullParserException { if (xr.isEmptyElementTag()) return ctxCurrent; return ctxNew; } // KdbxFile.Write.cs /// <summary> /// Serialization to KeePass KDBX files. /// </summary> // public void Save(String strFile, PwGroup pgDataSource, KdbxFormat format, // IStatusLogger slLogger) // { // boolean 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(OutputStream sSaveTo, PwGroup pgDataSource, KdbxFormat format, IStatusLogger slLogger) throws IOException { assert sSaveTo != null; if (sSaveTo == null) throw new IllegalArgumentException("sSaveTo"); m_format = format; m_slLogger = slLogger; HashingOutputStreamEx hashedStream = new HashingOutputStreamEx(sSaveTo); Charset encNoBom = StrUtil.Utf8; CryptoRandom cr = CryptoRandom.getInstance(); 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); OutputStream writerStream; if (m_format == KdbxFormat.Default) { WriteHeader(hashedStream); // Also flushes the stream OutputStream sEncrypted = AttachStreamEncryptor(hashedStream); if ((sEncrypted == null) || (sEncrypted == hashedStream)) throw new SecurityException("failed to create crypto stream"); sEncrypted.write(m_pbStreamStartBytes, 0, m_pbStreamStartBytes.length); OutputStream sHashed = new HashedBlockStream.Output(sEncrypted, 0); if (m_pwDatabase.getCompression() == PwCompressionAlgorithm.GZip) writerStream = new GZIPOutputStream(sHashed); else writerStream = sHashed; } else if (m_format == KdbxFormat.PlainXml) writerStream = hashedStream; else { assert false; throw new KdbxFileFormatException("KdbFormat"); } XmlPullParserFactory fact = XmlPullParserFactory.newInstance(); m_xmlWriter = fact.newSerializer(); m_xmlWriter.setOutput(writerStream, "utf-8"); // new XmlTextWriter(writerStream, encNoBom); WriteDocument(pgDataSource); m_xmlWriter.flush(); writerStream.close(); } catch (XmlPullParserException e) { throw new RuntimeException(e); } finally { CommonCleanUpWrite(sSaveTo, hashedStream); } } private void CommonCleanUpWrite(OutputStream sSaveTo, HashingOutputStreamEx hashedStream) throws IOException { hashedStream.close(); m_pbHashOfFileOnDisk = hashedStream.getHash(); sSaveTo.close(); m_xmlWriter = null; m_pbHashOfHeader = null; } private void WriteHeader(OutputStream s) throws IOException { ByteArrayOutputStream ms = new ByteArrayOutputStream(); MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature1)); MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileSignature2)); MemUtil.Write(ms, MemUtil.UInt32ToBytes(FileVersion32)); WriteHeaderField(ms, KdbxHeaderFieldID.CipherID, m_pwDatabase.getDataCipherUuid().getUuidBytes()); int nCprID = m_pwDatabase.getCompression().ordinal(); WriteHeaderField(ms, KdbxHeaderFieldID.CompressionFlags, MemUtil.UInt32ToBytes((int) nCprID)); WriteHeaderField(ms, KdbxHeaderFieldID.MasterSeed, m_pbMasterSeed); WriteHeaderField(ms, KdbxHeaderFieldID.TransformSeed, m_pbTransformSeed); WriteHeaderField(ms, KdbxHeaderFieldID.TransformRounds, MemUtil.UInt64ToBytes(m_pwDatabase.getKeyEncryptionRounds())); WriteHeaderField(ms, KdbxHeaderFieldID.EncryptionIV, m_pbEncryptionIV); WriteHeaderField(ms, KdbxHeaderFieldID.ProtectedStreamKey, m_pbProtectedStreamKey); WriteHeaderField(ms, KdbxHeaderFieldID.StreamStartBytes, m_pbStreamStartBytes); int nIrsID = m_craInnerRandomStream.ordinal(); WriteHeaderField(ms, KdbxHeaderFieldID.InnerRandomStreamID, MemUtil.UInt32ToBytes((int) nIrsID)); WriteHeaderField(ms, KdbxHeaderFieldID.EndOfHeader, new byte[] { (byte) '\r', (byte) '\n', (byte) '\r', (byte) '\n' }); byte[] pbHeader = ms.toByteArray(); ms.close(); m_pbHashOfHeader = Digests.sha256(pbHeader); s.write(pbHeader, 0, pbHeader.length); s.flush(); } private static void WriteHeaderField(OutputStream s, KdbxHeaderFieldID kdbID, byte[] pbData) throws IOException { s.write(kdbID.ordinal()); if (pbData != null) { short uLength = (short) pbData.length; MemUtil.Write(s, MemUtil.UInt16ToBytes(uLength)); if (uLength > 0) s.write(pbData, 0, pbData.length); } else MemUtil.Write(s, MemUtil.UInt16ToBytes((short) 0)); } private OutputStream AttachStreamEncryptor(OutputStream s) throws IOException { ByteArrayOutputStream ms = new ByteArrayOutputStream(); assert m_pbMasterSeed != null; assert m_pbMasterSeed.length == 32; ms.write(m_pbMasterSeed, 0, 32); assert m_pwDatabase != null; assert m_pwDatabase.getMasterKey() != null; ProtectedBinary pbinKey = m_pwDatabase.getMasterKey().GenerateKey32(m_pbTransformSeed, m_pwDatabase.getKeyEncryptionRounds()); assert pbinKey != null; if (pbinKey == null) throw new SecurityException("Invalid composite key"); byte[] pKey32 = pbinKey.ReadData(); if ((pKey32 == null) || (pKey32.length != 32)) throw new SecurityException("Invalid composite key"); ms.write(pKey32, 0, 32); byte[] aesKey = Digests.sha256(ms.toByteArray()); ms.close(); Arrays.fill(pKey32, 0, 32, (byte) 0); assert CipherPool.getGlobalPool() != null; ICipherEngine iEngine = CipherPool.getGlobalPool().GetCipher(m_pwDatabase.getDataCipherUuid()); if (iEngine == null) throw new SecurityException("Unknown cipher"); return iEngine.EncryptStream(s, aesKey, m_pbEncryptionIV); } private void WriteDocument(PwGroup pgDataSource) throws IOException { assert m_xmlWriter != null; if (m_xmlWriter == null) throw new UnsupportedOperationException(); PwGroup pgRoot = (pgDataSource != null ? pgDataSource : m_pwDatabase.getRootGroup()); final int[] uNumGroups = new int[1], uNumEntries = new int[1], uCurEntry = { 0 }; pgRoot.GetCounts(true, uNumGroups, uNumEntries); BinPoolBuild(pgRoot); // m_xmlWriter.Formatting = Formatting.Indented; // m_xmlWriter.IndentChar = '\t'; // m_xmlWriter.Indentation = 1; // unsupported on android // m_xmlWriter.setProperty("http://xmlpull.org/v1/doc/properties.html#serializer-indentation", "\t"); m_xmlWriter.startDocument("utf-8", true); m_xmlWriter.startTag(null, ElemDocNode); WriteMeta(); m_xmlWriter.startTag(null, ElemRoot); StartGroup(pgRoot); final Stack<PwGroup> groupStack = new Stack<PwGroup>(); groupStack.push(pgRoot); GroupHandler gh = new GroupHandler() { public boolean delegate(PwGroup pg) { assert pg != null; if (pg == null) throw new IllegalArgumentException("pg"); while (true) { if (pg.getParentGroup().equals(groupStack.peek())) { groupStack.push(pg); try { StartGroup(pg); } catch (IOException e) { throw new RuntimeException(e); } break; } else { groupStack.pop(); if (groupStack.size() <= 0) return false; try { EndGroup(); } catch (IOException e) { throw new RuntimeException(e); } } } return true; } }; EntryHandler eh = new EntryHandler() { public boolean delegate(PwEntry pe) { assert pe != null; try { WriteEntry(pe, false); } catch (IOException e) { throw new RuntimeException(e); } ++uCurEntry[0]; if (m_slLogger != null) if (!m_slLogger.SetProgress((100 * uCurEntry[0]) / uNumEntries[0])) return false; return true; } }; if (!pgRoot.TraverseTree(TraversalMethod.PreOrder, gh, eh)) throw new UnsupportedOperationException(); while (groupStack.size() > 1) { // m_xmlWriter.WriteEndElement(); EndGroup(); // FIXME TODO is this correct? groupStack.pop(); } EndGroup(); WriteList(ElemDeletedObjects, m_pwDatabase.getDeletedObjects()); m_xmlWriter.endTag(null, ElemRoot); // Root m_xmlWriter.endTag(null, ElemDocNode); // ElemDocNode m_xmlWriter.endDocument(); } private void WriteMeta() throws IOException { m_xmlWriter.startTag(null, ElemMeta); WriteObject(ElemGenerator, PwDatabase.getLocalizedAppName(), false); // Generator name if (m_pbHashOfHeader != null) WriteObject(ElemHeaderHash, BaseEncoding.base64().encode(m_pbHashOfHeader), false); WriteObject(ElemDbName, m_pwDatabase.getName(), true); WriteObject(ElemDbNameChanged, m_pwDatabase.getNameChanged()); WriteObject(ElemDbDesc, m_pwDatabase.getDescription(), true); WriteObject(ElemDbDescChanged, m_pwDatabase.getDescriptionChanged()); WriteObject(ElemDbDefaultUser, m_pwDatabase.getDefaultUserName(), true); WriteObject(ElemDbDefaultUserChanged, m_pwDatabase.getDefaultUserNameChanged()); WriteObject(ElemDbMntncHistoryDays, m_pwDatabase.getMaintenanceHistoryDays()); WriteObject(ElemDbColor, StrUtil.ColorToUnnamedHtml(m_pwDatabase.getColor(), true), false); WriteObject(ElemDbKeyChanged, m_pwDatabase.getMasterKeyChanged()); WriteObject(ElemDbKeyChangeRec, m_pwDatabase.getMasterKeyChangeRec()); WriteObject(ElemDbKeyChangeForce, m_pwDatabase.getMasterKeyChangeForce()); WriteList(ElemMemoryProt, m_pwDatabase.getMemoryProtection()); WriteCustomIconList(); WriteObject(ElemRecycleBinEnabled, m_pwDatabase.isRecycleBinEnabled()); WriteObject(ElemRecycleBinUuid, m_pwDatabase.getRecycleBinUuid()); WriteObject(ElemRecycleBinChanged, m_pwDatabase.getRecycleBinChanged()); WriteObject(ElemEntryTemplatesGroup, m_pwDatabase.getEntryTemplatesGroup()); WriteObject(ElemEntryTemplatesGroupChanged, m_pwDatabase.getEntryTemplatesGroupChanged()); WriteObject(ElemHistoryMaxItems, m_pwDatabase.getHistoryMaxItems()); WriteObject(ElemHistoryMaxSize, m_pwDatabase.getHistoryMaxSize()); WriteObject(ElemLastSelectedGroup, m_pwDatabase.getLastSelectedGroup()); WriteObject(ElemLastTopVisibleGroup, m_pwDatabase.getLastTopVisibleGroup()); WriteBinPool(); WriteList(ElemCustomData, m_pwDatabase.getCustomData()); m_xmlWriter.endTag(null, ElemMeta); } private void StartGroup(PwGroup pg) throws IOException { m_xmlWriter.startTag(null, ElemGroup); WriteObject(ElemUuid, pg.getUuid()); WriteObject(ElemName, pg.getName(), true); WriteObject(ElemNotes, pg.getNotes(), true); WriteObject(ElemIcon, pg.getIconId().ordinal()); if (!pg.getCustomIconUuid().Equals(PwUuid.Zero)) WriteObject(ElemCustomIconID, pg.getCustomIconUuid()); WriteList(ElemTimes, pg); WriteObject(ElemIsExpanded, pg.isExpanded()); WriteObject(ElemGroupDefaultAutoTypeSeq, pg.getDefaultAutoTypeSequence(), true); WriteObject(ElemEnableAutoType, StrUtil.BoolToStringEx(pg.getEnableAutoType()), false); WriteObject(ElemEnableSearching, StrUtil.BoolToStringEx(pg.getEnableSearching()), false); WriteObject(ElemLastTopVisibleEntry, pg.getLastTopVisibleEntry()); } private void EndGroup() throws IOException { m_xmlWriter.endTag(null, ElemGroup); // Close group element } private void WriteEntry(PwEntry pe, boolean bIsHistory) throws IOException { assert pe != null; if (pe == null) throw new IllegalArgumentException("pe"); m_xmlWriter.startTag(null, ElemEntry); WriteObject(ElemUuid, pe.getUuid()); WriteObject(ElemIcon, pe.getIconId().ordinal()); if (!pe.getCustomIconUuid().Equals(PwUuid.Zero)) WriteObject(ElemCustomIconID, pe.getCustomIconUuid()); WriteObject(ElemFgColor, StrUtil.ColorToUnnamedHtml(pe.getForegroundColor(), true), false); WriteObject(ElemBgColor, StrUtil.ColorToUnnamedHtml(pe.getBackgroundColor(), true), false); WriteObject(ElemOverrideUrl, pe.getOverrideUrl(), true); WriteObject(ElemTags, StrUtil.TagsToString(pe.getTags(), false), true); WriteList(ElemTimes, pe); WriteList(pe.getStrings(), true); WriteList(pe.getBinaries()); WriteList(ElemAutoType, pe.getAutoType()); if (!bIsHistory) WriteList(ElemHistory, pe.getHistory(), true); else { assert pe.getHistory().getUCount() == 0; } m_xmlWriter.endTag(null, ElemEntry); } private void WriteList(ProtectedStringDictionary dictStrings, boolean bEntryStrings) throws IOException { assert dictStrings != null; if (dictStrings == null) throw new IllegalArgumentException("dictStrings"); for (Map.Entry<String, ProtectedString> kvp : dictStrings) WriteObject(kvp.getKey(), kvp.getValue(), bEntryStrings); } private void WriteList(ProtectedBinaryDictionary dictBinaries) throws IOException { assert dictBinaries != null; if (dictBinaries == null) throw new IllegalArgumentException("dictBinaries"); for (Map.Entry<String, ProtectedBinary> kvp : dictBinaries) WriteObject(kvp.getKey(), kvp.getValue(), true); } private void WriteList(String name, AutoTypeConfig cfgAutoType) throws IOException { assert name != null; assert cfgAutoType != null; if (cfgAutoType == null) throw new IllegalArgumentException("cfgAutoType"); m_xmlWriter.startTag(null, name); WriteObject(ElemAutoTypeEnabled, cfgAutoType.isEnabled()); WriteObject(ElemAutoTypeObfuscation, cfgAutoType.getObfuscationOptions().ordinal()); if (cfgAutoType.getDefaultSequence().length() > 0) WriteObject(ElemAutoTypeDefaultSeq, cfgAutoType.getDefaultSequence(), true); for (AutoTypeAssociation a : cfgAutoType.getAssociations()) WriteObject(ElemAutoTypeItem, ElemWindow, ElemKeystrokeSequence, Maps.immutableEntry(a.getWindowName(), a.getSequence())); m_xmlWriter.endTag(null, name); } private void WriteList(String name, ITimeLogger times) throws IOException { assert name != null; assert times != null; if (times == null) throw new IllegalArgumentException("times"); m_xmlWriter.startTag(null, name); WriteObject(ElemCreationTime, times.getCreationTime()); WriteObject(ElemLastModTime, times.getLastModificationTime()); WriteObject(ElemLastAccessTime, times.getLastAccessTime()); WriteObject(ElemExpiryTime, times.getExpiryTime()); WriteObject(ElemExpires, times.getExpires()); WriteObject(ElemUsageCount, times.getUsageCount()); WriteObject(ElemLocationChanged, times.getLocationChanged()); m_xmlWriter.endTag(null, name); // Name } private void WriteList(String name, PwObjectList<PwEntry> value, boolean bIsHistory) throws IOException { assert name != null; assert value != null; if (value == null) throw new IllegalArgumentException("value"); m_xmlWriter.startTag(null, name); for (PwEntry pe : value) WriteEntry(pe, bIsHistory); m_xmlWriter.endTag(null, name); } private void WriteList(String name, PwObjectList<PwDeletedObject> value) throws IOException { assert name != null; assert value != null; if (value == null) throw new IllegalArgumentException("value"); m_xmlWriter.startTag(null, name); for (PwDeletedObject pdo : value) WriteObject(ElemDeletedObject, pdo); m_xmlWriter.endTag(null, name); } private void WriteList(String name, MemoryProtectionConfig value) throws IOException { assert name != null; assert value != null; m_xmlWriter.startTag(null, 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.endTag(null, name); } private void WriteList(String name, StringDictionaryEx value) throws IOException { assert name != null; assert value != null; if (value == null) throw new IllegalArgumentException("value"); m_xmlWriter.startTag(null, name); for (Map.Entry<String, String> kvp : value) WriteObject(ElemStringDictExItem, ElemKey, ElemValue, kvp); m_xmlWriter.endTag(null, name); } private void WriteCustomIconList() throws IOException { if (m_pwDatabase.getCustomIcons().size() == 0) return; m_xmlWriter.startTag(null, ElemCustomIcons); for (PwCustomIcon pwci : m_pwDatabase.getCustomIcons()) { m_xmlWriter.startTag(null, ElemCustomIconItem); WriteObject(ElemCustomIconItemID, pwci.getUuid()); String strData = BaseEncoding.base64().encode(pwci.getImageDataPng()); WriteObject(ElemCustomIconItemData, strData, false); m_xmlWriter.endTag(null, ElemCustomIconItem); } m_xmlWriter.endTag(null, ElemCustomIcons); } private void WriteObject(String name, String value, boolean bFilterValueXmlChars) throws IOException { assert name != null; assert value != null; m_xmlWriter.startTag(null, name); if (bFilterValueXmlChars) m_xmlWriter.text(StrUtil.SafeXmlString(value)); else m_xmlWriter.text(value); m_xmlWriter.endTag(null, name); } private void WriteObject(String name, boolean value) throws IOException { assert name != null; WriteObject(name, value ? ValTrue : ValFalse, false); } private void WriteObject(String name, PwUuid value) throws IOException { assert name != null; assert value != null; if (value == null) throw new IllegalArgumentException("value"); WriteObject(name, BaseEncoding.base64().encode(value.getUuidBytes()), false); } private void WriteObject(String name, int value) throws IOException { assert name != null; m_xmlWriter.startTag(null, name); m_xmlWriter.text(String.valueOf(value)); m_xmlWriter.endTag(null, name); } private void WriteObject(String name, long value) throws IOException { assert name != null; m_xmlWriter.startTag(null, name); m_xmlWriter.text(String.valueOf(value)); m_xmlWriter.endTag(null, name); } private void WriteObject(String name, Date value) throws IOException { assert name != null; WriteObject(name, TimeUtil.SerializeUtc(value), false); } private void WriteObject(String name, String strKeyName, String strValueName, Map.Entry<String, String> kvp) throws IOException { m_xmlWriter.startTag(null, name); m_xmlWriter.startTag(null, strKeyName); m_xmlWriter.text(StrUtil.SafeXmlString(kvp.getKey())); m_xmlWriter.endTag(null, strKeyName); m_xmlWriter.startTag(null, strValueName); m_xmlWriter.text(StrUtil.SafeXmlString(kvp.getValue())); m_xmlWriter.endTag(null, strValueName); m_xmlWriter.endTag(null, name); } private void WriteObject(String name, ProtectedString value, boolean bIsEntryString) throws IOException { assert name != null; assert value != null; if (value == null) throw new IllegalArgumentException("value"); m_xmlWriter.startTag(null, ElemString); m_xmlWriter.startTag(null, ElemKey); m_xmlWriter.text(StrUtil.SafeXmlString(name)); m_xmlWriter.endTag(null, ElemKey); m_xmlWriter.startTag(null, ElemValue); boolean 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 (PwDefs.TitleField.equals(name)) bProtected = m_pwDatabase.getMemoryProtection().ProtectTitle; else if (PwDefs.UserNameField.equals(name)) bProtected = m_pwDatabase.getMemoryProtection().ProtectUserName; else if (PwDefs.PasswordField.equals(name)) bProtected = m_pwDatabase.getMemoryProtection().ProtectPassword; else if (PwDefs.UrlField.equals(name)) bProtected = m_pwDatabase.getMemoryProtection().ProtectUrl; else if (PwDefs.NotesField.equals(name)) bProtected = m_pwDatabase.getMemoryProtection().ProtectNotes; } if (bProtected && (m_format != KdbxFormat.PlainXml)) { m_xmlWriter.attribute(null, AttrProtected, ValTrue); byte[] pbEncoded = value.ReadXorredString(m_randomStream); if (pbEncoded.length > 0) m_xmlWriter.text(BaseEncoding.base64().encode(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(); for (char ch : strValue.toCharArray()) { char chMapped = ch; // Symbols and surrogates must be moved into the correct code // page area /* TODO FIXME is this necessary in java??? if(Character.getType(ch) == Character.OTHER_SYMBOL || Character.isSurrogate(ch)) { System.Globalization.UnicodeCategory cat = CharUnicodeInfo.GetUnicodeCategory(ch); // Map character to correct position in code page chMapped = (char)((int)cat * 32 + ch); } else if(Character.isISOControl(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 = Character.toLowerCase(ch); } } */ sb.append(chMapped); } strValue = sb.toString(); // Correct String for current code page } if ((m_format == KdbxFormat.PlainXml) && bProtected) m_xmlWriter.attribute(null, AttrProtectedInMemPlainXml, ValTrue); m_xmlWriter.text(StrUtil.SafeXmlString(strValue)); } m_xmlWriter.endTag(null, ElemValue); // ElemValue m_xmlWriter.endTag(null, ElemString); // ElemString } private void WriteObject(String name, ProtectedBinary value, boolean bAllowRef) throws IOException { assert name != null; assert value != null; if (value == null) throw new IllegalArgumentException("value"); m_xmlWriter.startTag(null, ElemBinary); m_xmlWriter.startTag(null, ElemKey); m_xmlWriter.text(StrUtil.SafeXmlString(name)); m_xmlWriter.endTag(null, ElemKey); m_xmlWriter.startTag(null, ElemValue); String strRef = (bAllowRef ? BinPoolFind(value) : null); if (strRef != null) { m_xmlWriter.attribute(null, AttrRef, strRef); } else SubWriteValue(value); m_xmlWriter.endTag(null, ElemValue); // ElemValue m_xmlWriter.endTag(null, ElemBinary); // ElemBinary } private void SubWriteValue(ProtectedBinary value) throws IOException { if (value.isProtected() && (m_format != KdbxFormat.PlainXml)) { m_xmlWriter.attribute(null, AttrProtected, ValTrue); byte[] pbEncoded = value.ReadXorredData(m_randomStream); if (pbEncoded.length > 0) m_xmlWriter.text(BaseEncoding.base64().encode(pbEncoded, 0, pbEncoded.length)); } else { if (m_pwDatabase.getCompression() == PwCompressionAlgorithm.GZip) { m_xmlWriter.attribute(null, AttrCompressed, ValTrue); byte[] pbRaw = value.ReadData(); byte[] pbCmp = MemUtil.Compress(pbRaw); m_xmlWriter.text(BaseEncoding.base64().encode(pbCmp, 0, pbCmp.length)); } else { byte[] pbRaw = value.ReadData(); m_xmlWriter.text(BaseEncoding.base64().encode(pbRaw, 0, pbRaw.length)); } } } private void WriteObject(String name, PwDeletedObject value) throws IOException { assert name != null; assert value != null; if (value == null) throw new IllegalArgumentException("value"); m_xmlWriter.startTag(null, name); WriteObject(ElemUuid, value.getUuid()); WriteObject(ElemDeletionTime, value.getDeletionTime()); m_xmlWriter.endTag(null, name); } private void WriteBinPool() throws IOException { m_xmlWriter.startTag(null, ElemBinaries); for (Map.Entry<String, ProtectedBinary> kvp : m_dictBinPool.entrySet()) { m_xmlWriter.startTag(null, ElemBinary); m_xmlWriter.attribute(null, AttrId, kvp.getKey()); SubWriteValue(kvp.getValue()); m_xmlWriter.endTag(null, ElemBinary); } m_xmlWriter.endTag(null, ElemBinaries); } @Deprecated public static boolean WriteEntries(OutputStream msOutput, PwDatabase pwDatabase, PwEntry[] vEntries) throws IOException { 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 boolean WriteEntries(OutputStream msOutput, PwEntry[] vEntries) throws IOException { /* KdbxFile f = new KdbxFile(pwDatabase); f.m_format = KdbxFormat.PlainXml; XmlTextWriter xtw = null; try { xtw = new XmlTextWriter(msOutput, StrUtil.Utf8); } catch(Exception) { assert false; return false; } if(xtw == null) { assert false; return false; } f.m_xmlWriter = xtw; xtw.Formatting = Formatting.Indented; xtw.IndentChar = '\t'; xtw.Indentation = 1; xtw.WriteStartDocument(true); xtw.WriteStartElement(ElemRoot); for(PwEntry pe : 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()); for (PwEntry peCopy : vEntries) pd.getRootGroup().AddEntry(peCopy.CloneDeep(), true); KdbxFile f = new KdbxFile(pd); f.Save(msOutput, null, KdbxFormat.PlainXml, null); return true; } }