diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0613db3eb..79fa6bea8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ android:allowBackup="true" android:fullBackupContent="@xml/backup" android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent" + android:largeHeap="true" android:theme="@style/KeepassDXStyle.Night"> { private var numKeyEncRounds: Long = 0 var publicCustomData = VariantDictionary() + var kdbxVersion: Long = 0 var name = "" var nameChanged = PwDate() // TODO change setting date @@ -116,7 +119,14 @@ class PwDatabaseV4 : PwDatabase { } override val version: String - get() = "KeePass 2" + get() { + val kdbxStringVersion = when(kdbxVersion) { + FILE_VERSION_32_3 -> "3.1" + FILE_VERSION_32_4 -> "4.0" + else -> "UNKNOWN" + } + return "KeePass 2 - KDBX$kdbxStringVersion" + } override val kdfEngine: KdfEngine? get() = try { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV4.kt index f4c39930e..cfb5510e5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV4.kt @@ -49,7 +49,7 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { var iconCustom = PwIconCustom.UNKNOWN_ICON private var customData = HashMap() var fields = HashMap() - val binaries = HashMap() + var binaries = HashMap() var foregroundColor = "" var backgroundColor = "" var overrideURL = "" @@ -98,7 +98,7 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { locationChanged = parcel.readParcelable(PwDate::class.java.classLoader) ?: locationChanged customData = ParcelableUtil.readStringParcelableMap(parcel) fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java) - // TODO binaries = ParcelableUtil.readStringParcelableMap(parcel, ProtectedBinary.class); + binaries = ParcelableUtil.readStringParcelableMap(parcel, ProtectedBinary::class.java) foregroundColor = parcel.readString() ?: foregroundColor backgroundColor = parcel.readString() ?: backgroundColor overrideURL = parcel.readString() ?: overrideURL @@ -116,7 +116,7 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { dest.writeParcelable(locationChanged, flags) ParcelableUtil.writeStringParcelableMap(dest, customData) ParcelableUtil.writeStringParcelableMap(dest, flags, fields) - // TODO ParcelableUtil.writeStringParcelableMap(dest, flags, binaries); + ParcelableUtil.writeStringParcelableMap(dest, flags, binaries) dest.writeString(foregroundColor) dest.writeString(backgroundColor) dest.writeString(overrideURL) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt index 02ba53f6d..584a68400 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt @@ -22,18 +22,14 @@ package com.kunzisoft.keepass.database.element.security import android.os.Parcel import android.os.Parcelable import android.util.Log +import java.io.* -import java.io.ByteArrayInputStream -import java.io.File -import java.io.FileInputStream -import java.io.IOException -import java.io.InputStream import java.util.Arrays class ProtectedBinary : Parcelable { + var isCompressed: Boolean? = null // Only for KDBX3.1- var isProtected: Boolean = false - private set private var data: ByteArray? = null private var dataFile: File? = null @@ -49,30 +45,36 @@ class ProtectedBinary : Parcelable { * Empty protected binary */ constructor() { + this.isCompressed = null this.isProtected = false this.data = null this.dataFile = null } constructor(protectedBinary: ProtectedBinary) { + this.isCompressed = protectedBinary.isCompressed this.isProtected = protectedBinary.isProtected this.data = protectedBinary.data this.dataFile = protectedBinary.dataFile } - constructor(enableProtection: Boolean, data: ByteArray?) { + constructor(data: ByteArray?, enableProtection: Boolean = false, compressed: Boolean? = null) { + this.isCompressed = compressed this.isProtected = enableProtection this.data = data this.dataFile = null } - constructor(enableProtection: Boolean, dataFile: File) { + constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean? = null) { + this.isCompressed = compressed this.isProtected = enableProtection this.data = null this.dataFile = dataFile } private constructor(parcel: Parcel) { + val compressedByte = parcel.readByte().toInt() + isCompressed = if (compressedByte == 2) null else compressedByte != 0 isProtected = parcel.readByte().toInt() != 0 data = ByteArray(parcel.readInt()) parcel.readByteArray(data) @@ -80,11 +82,11 @@ class ProtectedBinary : Parcelable { } @Throws(IOException::class) - fun getData(): InputStream? { + fun getInputDataStream(): InputStream { return when { data != null -> ByteArrayInputStream(data) dataFile != null -> FileInputStream(dataFile!!) - else -> null + else -> throw IOException("Unable to get binary data") } } @@ -111,12 +113,15 @@ class ProtectedBinary : Parcelable { && dataFile == null && other.dataFile == null) sameData = true - return isProtected == other.isProtected && sameData + return isCompressed == other.isCompressed + && isProtected == other.isProtected + && sameData } override fun hashCode(): Int { var result = 0 + result = 31 * result + if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0 result = 31 * result + if (isProtected) 1 else 0 result = 31 * result + dataFile!!.hashCode() result = 31 * result + Arrays.hashCode(data) @@ -128,6 +133,7 @@ class ProtectedBinary : Parcelable { } override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeByte((if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0).toByte()) dest.writeByte((if (isProtected) 1 else 0).toByte()) dest.writeInt(data?.size ?: 0) dest.writeByteArray(data) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt index dd9f8838e..25a406e0c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt @@ -32,13 +32,9 @@ import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.file.KDBX4DateUtil import com.kunzisoft.keepass.database.file.PwDbHeaderV4 -import com.kunzisoft.keepass.stream.BetterCipherInputStream -import com.kunzisoft.keepass.stream.HashedBlockInputStream -import com.kunzisoft.keepass.stream.HmacBlockInputStream -import com.kunzisoft.keepass.stream.LEDataInputStream +import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils -import org.apache.commons.io.IOUtils import org.spongycastle.crypto.StreamCipher import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException @@ -58,7 +54,6 @@ class ImporterV4(private val streamDir: File, private lateinit var mDatabase: PwDatabaseV4 private var hashOfHeader: ByteArray? = null - private var version: Long = 0 private val unusedCacheFileName: String get() = mDatabase.binPool.findUnusedKey().toString() @@ -102,7 +97,7 @@ class ImporterV4(private val streamDir: File, val header = PwDbHeaderV4(mDatabase) val headerAndHash = header.loadFromFile(databaseInputStream) - version = header.version + mDatabase.kdbxVersion = header.version hashOfHeader = headerAndHash.hash val pbHeader = headerAndHash.header @@ -124,7 +119,7 @@ class ImporterV4(private val streamDir: File, } val isPlain: InputStream - if (version < PwDbHeaderV4.FILE_VERSION_32_4) { + if (mDatabase.kdbxVersion < PwDbHeaderV4.FILE_VERSION_32_4) { val decrypted = attachCipherStream(databaseInputStream, cipher) val dataDecrypted = LEDataInputStream(decrypted) @@ -172,7 +167,7 @@ class ImporterV4(private val streamDir: File, else -> isPlain } - if (version >= PwDbHeaderV4.FILE_VERSION_32_4) { + if (mDatabase.kdbxVersion >= PwDbHeaderV4.FILE_VERSION_32_4) { loadInnerHeader(inputStreamXml, header) } @@ -241,9 +236,13 @@ class ImporterV4(private val streamDir: File, // Read in a file val file = File(streamDir, unusedCacheFileName) FileOutputStream(file).use { outputStream -> - dataInputStream.readBytes(byteLength) { outputStream.write(it) } + dataInputStream.readBytes(byteLength, object : ReadBytes { + override fun read(buffer: ByteArray) { + outputStream.write(buffer) + } + }) } - val protectedBinary = ProtectedBinary(protectedFlag, file) + val protectedBinary = ProtectedBinary(file, protectedFlag) mDatabase.binPool.add(protectedBinary) } else -> { @@ -432,7 +431,7 @@ class ImporterV4(private val streamDir: File, KdbContext.Binaries -> if (name.equals(PwDatabaseV4XML.ElemBinary, ignoreCase = true)) { val key = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrId) if (key != null) { - val pbData = readProtectedBinary(xpp) + val pbData = readBinary(xpp) val id = Integer.parseInt(key) mDatabase.binPool.put(id, pbData!!) } else { @@ -606,7 +605,7 @@ class ImporterV4(private val streamDir: File, KdbContext.EntryBinary -> if (name.equals(PwDatabaseV4XML.ElemKey, ignoreCase = true)) { ctxBinaryName = readString(xpp) } else if (name.equals(PwDatabaseV4XML.ElemValue, ignoreCase = true)) { - ctxBinaryValue = readProtectedBinary(xpp) + ctxBinaryValue = readBinary(xpp) } KdbContext.EntryAutoType -> if (name.equals(PwDatabaseV4XML.ElemAutoTypeEnabled, ignoreCase = true)) { @@ -806,7 +805,7 @@ class ImporterV4(private val streamDir: File, val sDate = readString(xpp) var utcDate: Date? = null - if (version >= PwDbHeaderV4.FILE_VERSION_32_4) { + if (mDatabase.kdbxVersion >= PwDbHeaderV4.FILE_VERSION_32_4) { var buf = Base64.decode(sDate, BASE_64_FLAG) if (buf.size != 8) { val buf8 = ByteArray(8) @@ -939,7 +938,9 @@ class ImporterV4(private val streamDir: File, } @Throws(XmlPullParserException::class, IOException::class) - private fun readProtectedBinary(xpp: XmlPullParser): ProtectedBinary? { + private fun readBinary(xpp: XmlPullParser): ProtectedBinary? { + + // Reference Id to a binary already present in binary pool val ref = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrRef) if (ref != null) { xpp.next() // Consume end tag @@ -948,41 +949,38 @@ class ImporterV4(private val streamDir: File, return mDatabase.binPool[id] } - var compressed = false - var protected = false + // New binary to retrieve + else { + var compressed: Boolean? = null + var protected = false - if (xpp.attributeCount > 0) { - val compress = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrCompressed) - if (compress != null) { - compressed = compress.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true) - } - - val protect = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrProtected) - if (protect != null) { - protected = protect.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true) - } - } - - val base64 = readString(xpp) - if (base64.isEmpty()) - return ProtectedBinary() - val data = Base64.decode(base64, BASE_64_FLAG) - - return if (!compressed && data.size <= BUFFER_SIZE_BYTES) { - // Small data, don't need a file - ProtectedBinary(protected, data) - } else { - val file = File(streamDir, unusedCacheFileName) - if (compressed) { - FileOutputStream(file).use { outputStream -> - IOUtils.copy(GZIPInputStream(ByteArrayInputStream(data)), outputStream) + if (xpp.attributeCount > 0) { + val compress = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrCompressed) + if (compress != null) { + compressed = compress.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true) } + + val protect = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrProtected) + if (protect != null) { + protected = protect.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true) + } + } + + val base64 = readString(xpp) + if (base64.isEmpty()) + return ProtectedBinary() + val data = Base64.decode(base64, BASE_64_FLAG) + + return if (data.size <= BUFFER_SIZE_BYTES) { + // Small data, don't need a file + ProtectedBinary(data, protected, compressed) } else { + val file = File(streamDir, unusedCacheFileName) FileOutputStream(file).use { outputStream -> outputStream.write(data) } + ProtectedBinary(file, protected, compressed) } - ProtectedBinary(protected, file) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbInnerHeaderOutputV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbInnerHeaderOutputV4.kt index 70868ba5e..9705a2736 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbInnerHeaderOutputV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbInnerHeaderOutputV4.kt @@ -22,10 +22,10 @@ package com.kunzisoft.keepass.database.file.save import com.kunzisoft.keepass.database.element.PwDatabaseV4 import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES import com.kunzisoft.keepass.database.file.PwDbHeaderV4 -import com.kunzisoft.keepass.stream.ActionReadBytes +import com.kunzisoft.keepass.stream.ReadBytes import com.kunzisoft.keepass.stream.LEDataOutputStream +import com.kunzisoft.keepass.stream.readFromStream import java.io.IOException -import java.io.InputStream import java.io.OutputStream import kotlin.experimental.or @@ -58,32 +58,16 @@ class PwDbInnerHeaderOutputV4(private val database: PwDatabaseV4, dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify dataOutputStream.write(flag.toInt()) - protectedBinary.getData()?.let { - readBytes(it, ActionReadBytes { buffer -> - dataOutputStream.write(buffer) - }) - } ?: throw IOException("Can't write protected binary") + readFromStream(protectedBinary.getInputDataStream(), BUFFER_SIZE_BYTES, + object : ReadBytes { + override fun read(buffer: ByteArray) { + dataOutputStream.write(buffer) + } + } + ) } dataOutputStream.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader.toInt()) dataOutputStream.writeInt(0) } - - @Throws(IOException::class) - fun readBytes(inputStream: InputStream, actionReadBytes: ActionReadBytes) { - val buffer = ByteArray(BUFFER_SIZE_BYTES) - var read = 0 - while (read != -1) { - read = inputStream.read(buffer, 0, buffer.size) - if (read != -1) { - val optimizedBuffer: ByteArray = if (buffer.size == read) { - buffer - } else { - buffer.copyOf(read) - } - actionReadBytes.doAction(optimizedBuffer) - } - } - } - } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV4Output.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV4Output.kt index 49496d2f4..f039c2a45 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV4Output.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV4Output.kt @@ -37,9 +37,7 @@ import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.UnknownKDF import com.kunzisoft.keepass.database.file.KDBX4DateUtil import com.kunzisoft.keepass.database.file.PwDbHeaderV4 -import com.kunzisoft.keepass.stream.HashedBlockOutputStream -import com.kunzisoft.keepass.stream.HmacBlockOutputStream -import com.kunzisoft.keepass.stream.LEDataOutputStream +import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import org.joda.time.DateTime import org.spongycastle.crypto.StreamCipher @@ -53,7 +51,8 @@ import javax.crypto.Cipher import javax.crypto.CipherOutputStream -class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputStream) : PwDbOutput(outputStream) { +class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, + outputStream: OutputStream) : PwDbOutput(outputStream) { private var randomStream: StreamCipher? = null private lateinit var xml: XmlSerializer @@ -75,17 +74,16 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt header = outputHeader(mOS) val osPlain: OutputStream - if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { + osPlain = if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { val cos = attachStreamEncryptor(header!!, mOS) cos.write(header!!.streamStartBytes) - osPlain = HashedBlockOutputStream(cos) + HashedBlockOutputStream(cos) } else { mOS.write(hashOfHeader!!) mOS.write(headerHmac!!) - val hbos = HmacBlockOutputStream(mOS, mDatabaseV4.hmacKey) - osPlain = attachStreamEncryptor(header!!, hbos) + attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseV4.hmacKey)) } val osXml: OutputStream @@ -114,11 +112,11 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt } @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun outputDatabase(os: OutputStream) { + private fun outputDatabase(outputStream: OutputStream) { xml = Xml.newSerializer() - xml.setOutput(os, "UTF-8") + xml.setOutput(outputStream, "UTF-8") xml.startDocument("UTF-8", true) xml.startTag(null, PwDatabaseV4XML.ElemDocNode) @@ -210,17 +208,17 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt writeCustomIconList() writeObject(PwDatabaseV4XML.ElemRecycleBinEnabled, mDatabaseV4.isRecycleBinEnabled) - writeObject(PwDatabaseV4XML.ElemRecycleBinUuid, mDatabaseV4.recycleBinUUID) + writeUuid(PwDatabaseV4XML.ElemRecycleBinUuid, mDatabaseV4.recycleBinUUID) writeObject(PwDatabaseV4XML.ElemRecycleBinChanged, mDatabaseV4.recycleBinChanged) - writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroup, mDatabaseV4.entryTemplatesGroup) + writeUuid(PwDatabaseV4XML.ElemEntryTemplatesGroup, mDatabaseV4.entryTemplatesGroup) writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroupChanged, mDatabaseV4.entryTemplatesGroupChanged.date) writeObject(PwDatabaseV4XML.ElemHistoryMaxItems, mDatabaseV4.historyMaxItems.toLong()) writeObject(PwDatabaseV4XML.ElemHistoryMaxSize, mDatabaseV4.historyMaxSize) - writeObject(PwDatabaseV4XML.ElemLastSelectedGroup, mDatabaseV4.lastSelectedGroupUUID) - writeObject(PwDatabaseV4XML.ElemLastTopVisibleGroup, mDatabaseV4.lastTopVisibleGroupUUID) + writeUuid(PwDatabaseV4XML.ElemLastSelectedGroup, mDatabaseV4.lastSelectedGroupUUID) + writeUuid(PwDatabaseV4XML.ElemLastTopVisibleGroup, mDatabaseV4.lastTopVisibleGroupUUID) if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { - writeBinPool() + writeBinariesKDBX31() } writeCustomData(mDatabaseV4.customData) @@ -306,13 +304,13 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) private fun startGroup(group: PwGroupV4) { xml.startTag(null, PwDatabaseV4XML.ElemGroup) - writeObject(PwDatabaseV4XML.ElemUuid, group.id) + writeUuid(PwDatabaseV4XML.ElemUuid, group.id) writeObject(PwDatabaseV4XML.ElemName, group.title) writeObject(PwDatabaseV4XML.ElemNotes, group.notes) writeObject(PwDatabaseV4XML.ElemIcon, group.icon.iconId.toLong()) if (group.iconCustom != PwIconCustom.UNKNOWN_ICON) { - writeObject(PwDatabaseV4XML.ElemCustomIconID, group.iconCustom.uuid) + writeUuid(PwDatabaseV4XML.ElemCustomIconID, group.iconCustom.uuid) } writeTimes(group) @@ -320,7 +318,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt writeObject(PwDatabaseV4XML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence) writeObject(PwDatabaseV4XML.ElemEnableAutoType, group.enableAutoType) writeObject(PwDatabaseV4XML.ElemEnableSearching, group.enableSearching) - writeObject(PwDatabaseV4XML.ElemLastTopVisibleEntry, group.lastTopVisibleEntry) + writeUuid(PwDatabaseV4XML.ElemLastTopVisibleEntry, group.lastTopVisibleEntry) } @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @@ -333,11 +331,11 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt xml.startTag(null, PwDatabaseV4XML.ElemEntry) - writeObject(PwDatabaseV4XML.ElemUuid, entry.id) + writeUuid(PwDatabaseV4XML.ElemUuid, entry.id) writeObject(PwDatabaseV4XML.ElemIcon, entry.icon.iconId.toLong()) if (entry.iconCustom != PwIconCustom.UNKNOWN_ICON) { - writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.iconCustom.uuid) + writeUuid(PwDatabaseV4XML.ElemCustomIconID, entry.iconCustom.uuid) } writeObject(PwDatabaseV4XML.ElemFgColor, entry.foregroundColor) @@ -348,7 +346,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt writeTimes(entry) writeFields(entry.fields) - writeList(entry.binaries) + writeEntryBinaries(entry.binaries) writeAutoType(entry.autoType) if (!isHistory) { @@ -358,81 +356,6 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt xml.endTag(null, PwDatabaseV4XML.ElemEntry) } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeObject(key: String, binary: ProtectedBinary) { - - xml.startTag(null, PwDatabaseV4XML.ElemBinary) - xml.startTag(null, PwDatabaseV4XML.ElemKey) - xml.text(safeXmlString(key)) - xml.endTag(null, PwDatabaseV4XML.ElemKey) - - xml.startTag(null, PwDatabaseV4XML.ElemValue) - val ref = mDatabaseV4.binPool.findKey(binary) - if (ref != null) { - xml.attribute(null, PwDatabaseV4XML.AttrRef, ref.toString()) - } else { - writeBinary(binary) - } - xml.endTag(null, PwDatabaseV4XML.ElemValue) - - xml.endTag(null, PwDatabaseV4XML.ElemBinary) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeBinary(value: ProtectedBinary) { - - val valLength = value.length().toInt() - if (valLength > 0) { - val buffer = ByteArray(valLength) - if (valLength == value.getData()!!.read(buffer, 0, valLength)) { - - if (value.isProtected) { - xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue) - - val encoded = ByteArray(valLength) - randomStream!!.processBytes(buffer, 0, valLength, encoded, 0) - xml.text(String(Base64.encode(encoded, BASE_64_FLAG))) - - } else { - if (mDatabaseV4.compressionAlgorithm === PwCompressionAlgorithm.GZip) { - xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue) - - val byteArrayOutputStream = ByteArrayOutputStream() - val gzipOutputStream = GZIPOutputStream(byteArrayOutputStream) - copyStream(ByteArrayInputStream(buffer), gzipOutputStream) - // IOUtils.copy(ByteArrayInputStream(ByteArrayInputStream(buffer)), gzipOutputStream) - gzipOutputStream.close() - - xml.text(String(Base64.encode(byteArrayOutputStream.toByteArray(), BASE_64_FLAG))) - - } else { - xml.text(String(Base64.encode(buffer, BASE_64_FLAG))) - } - } - } else { - Log.e(TAG, "Unable to read the stream of the protected binary") - } - } - } - - @Throws(IOException::class) - fun copyStream(inputStream: InputStream, out: OutputStream) { - val buffer = ByteArray(BUFFER_SIZE_BYTES) - try { - var read = inputStream.read(buffer) - while (read != -1) { - out.write(buffer, 0, read) - read = inputStream.read(buffer) - if (Thread.interrupted()) { - throw InterruptedException() - } - } - } catch (error: OutOfMemoryError) { - throw IOException(error) - } - } - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) private fun writeObject(name: String, value: String, filterXmlChars: Boolean = false) { var xmlString = value @@ -477,11 +400,57 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt } @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeObject(name: String, uuid: UUID) { + private fun writeUuid(name: String, uuid: UUID) { val data = DatabaseInputOutputUtils.uuidToBytes(uuid) writeObject(name, String(Base64.encode(data, BASE_64_FLAG))) } + // Only for KDBX3.1- + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeBinariesKDBX31() { + xml.startTag(null, PwDatabaseV4XML.ElemBinaries) + + mDatabaseV4.binPool.doForEachBinary { key, binary -> + xml.startTag(null, PwDatabaseV4XML.ElemBinary) + xml.attribute(null, PwDatabaseV4XML.AttrId, key.toString()) + xml.attribute(null, PwDatabaseV4XML.AttrCompressed, + if (mDatabaseV4.compressionAlgorithm === PwCompressionAlgorithm.GZip) { + PwDatabaseV4XML.ValTrue + } else { + PwDatabaseV4XML.ValFalse + } + ) + /* + // TODO compression needed here ? + // Binary need to be compressed before storage + val byteArrayOutputStream = ByteArrayOutputStream() + val gzipOutputStream = GZIPOutputStream(byteArrayOutputStream) + IOUtils.copy(binary.getInputDataStream(), gzipOutputStream) + gzipOutputStream.close() + readFromStream(ByteArrayInputStream(byteArrayOutputStream.toByteArray()), BUFFER_SIZE_BYTES, + object : ReadBytes { + override fun read(buffer: ByteArray) { + xml.text(String(Base64.encode(buffer, BASE_64_FLAG))) + } + } + ) + }*/ + + readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES, + object : ReadBytes { + override fun read(buffer: ByteArray) { + val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray() + xml.text(charArray, 0, charArray.size) + } + } + ) + + xml.endTag(null, PwDatabaseV4XML.ElemBinary) + } + + xml.endTag(null, PwDatabaseV4XML.ElemBinaries) + } + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) private fun writeObject(name: String, keyName: String, keyValue: String, valueName: String, valueValue: String) { xml.startTag(null, name) @@ -565,20 +534,58 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt private fun writeDeletedObject(value: PwDeletedObject) { xml.startTag(null, PwDatabaseV4XML.ElemDeletedObject) - writeObject(PwDatabaseV4XML.ElemUuid, value.uuid) + writeUuid(PwDatabaseV4XML.ElemUuid, value.uuid) writeObject(PwDatabaseV4XML.ElemDeletionTime, value.deletionTime) xml.endTag(null, PwDatabaseV4XML.ElemDeletedObject) } @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeList(binaries: Map) { - for ((key, value) in binaries) { - writeObject(key, value) + private fun writeEntryBinaries(binaries: Map) { + for ((key, binary) in binaries) { + xml.startTag(null, PwDatabaseV4XML.ElemBinary) + xml.startTag(null, PwDatabaseV4XML.ElemKey) + xml.text(safeXmlString(key)) + xml.endTag(null, PwDatabaseV4XML.ElemKey) + + xml.startTag(null, PwDatabaseV4XML.ElemValue) + val ref = mDatabaseV4.binPool.findKey(binary) + if (ref != null) { + xml.attribute(null, PwDatabaseV4XML.AttrRef, ref.toString()) + } else { + val binaryLength = binary.length() + if (binaryLength > 0) { + + if (binary.isProtected) { + xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue) + + readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES, + object : ReadBytes { + override fun read(buffer: ByteArray) { + val encoded = ByteArray(buffer.size) + randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0) + val charArray = String(Base64.encode(encoded, BASE_64_FLAG)).toCharArray() + xml.text(charArray, 0, charArray.size) + } + }) + } else { + readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES, + object : ReadBytes { + override fun read(buffer: ByteArray) { + val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray() + xml.text(charArray, 0, charArray.size) + } + } + ) + } + } + } + xml.endTag(null, PwDatabaseV4XML.ElemValue) + + xml.endTag(null, PwDatabaseV4XML.ElemBinary) } } - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) private fun writeDeletedObjects(value: List) { xml.startTag(null, PwDatabaseV4XML.ElemDeletedObjects) @@ -658,7 +665,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt for (icon in customIcons) { xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem) - writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid) + writeUuid(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid) writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String(Base64.encode(icon.imageData, BASE_64_FLAG))) xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem) @@ -667,22 +674,6 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputSt xml.endTag(null, PwDatabaseV4XML.ElemCustomIcons) } - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeBinPool() { - xml.startTag(null, PwDatabaseV4XML.ElemBinaries) - - mDatabaseV4.binPool.doForEachBinary { key, binary -> - xml.startTag(null, PwDatabaseV4XML.ElemBinary) - xml.attribute(null, PwDatabaseV4XML.AttrId, key.toString()) - - writeBinary(binary) - - xml.endTag(null, PwDatabaseV4XML.ElemBinary) - } - - xml.endTag(null, PwDatabaseV4XML.ElemBinaries) - } - private fun safeXmlString(text: String): String { if (text.isEmpty()) { return text diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/ActionReadBytes.java b/app/src/main/java/com/kunzisoft/keepass/stream/ActionReadBytes.java deleted file mode 100644 index d72ecb2a4..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/stream/ActionReadBytes.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.kunzisoft.keepass.stream; - -import java.io.IOException; - -public interface ActionReadBytes { - /** - * Called after each buffer fill - * @param buffer filled - */ - void doAction(byte[] buffer) throws IOException; -} diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/LEDataInputStream.java b/app/src/main/java/com/kunzisoft/keepass/stream/LEDataInputStream.java index 7af2f021e..ead0413de 100644 --- a/app/src/main/java/com/kunzisoft/keepass/stream/LEDataInputStream.java +++ b/app/src/main/java/com/kunzisoft/keepass/stream/LEDataInputStream.java @@ -124,7 +124,7 @@ public class LEDataInputStream extends InputStream { return buf; } - public void readBytes(int length, ActionReadBytes actionReadBytes) throws IOException { + public void readBytes(int length, ReadBytes readBytes) throws IOException { int bufferSize = 256 * 3; // TODO Buffer size byte[] buffer = new byte[bufferSize]; @@ -146,7 +146,7 @@ public class LEDataInputStream extends InputStream { } else { optimizedBuffer = buffer; } - actionReadBytes.doAction(optimizedBuffer); + readBytes.read(optimizedBuffer); offset += read; } } diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/ReadBytes.kt b/app/src/main/java/com/kunzisoft/keepass/stream/ReadBytes.kt new file mode 100644 index 000000000..3c03dabd4 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/stream/ReadBytes.kt @@ -0,0 +1,30 @@ +package com.kunzisoft.keepass.stream + +import java.io.IOException +import java.io.InputStream + +interface ReadBytes { + /** + * Called after each buffer fill + * @param buffer filled + */ + @Throws(IOException::class) + fun read(buffer: ByteArray) +} + +@Throws(IOException::class) +fun readFromStream(inputStream: InputStream, bufferSize: Int, readBytes: ReadBytes) { + val buffer = ByteArray(bufferSize) + var read = 0 + while (read != -1) { + read = inputStream.read(buffer, 0, buffer.size) + if (read != -1) { + val optimizedBuffer: ByteArray = if (buffer.size == read) { + buffer + } else { + buffer.copyOf(read) + } + readBytes.read(optimizedBuffer) + } + } +}