mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Fix OOM by stream implementation and add KDBX version for DatabaseV2
This commit is contained in:
@@ -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">
|
||||
<!-- TODO backup API Key -->
|
||||
<meta-data
|
||||
|
||||
@@ -31,6 +31,8 @@ import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
|
||||
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
|
||||
import com.kunzisoft.keepass.database.element.PwDatabaseV3.Companion.BACKUP_FOLDER_TITLE
|
||||
import com.kunzisoft.keepass.database.exception.UnknownKDF
|
||||
import com.kunzisoft.keepass.database.file.PwDbHeaderV4.Companion.FILE_VERSION_32_3
|
||||
import com.kunzisoft.keepass.database.file.PwDbHeaderV4.Companion.FILE_VERSION_32_4
|
||||
import com.kunzisoft.keepass.utils.VariantDictionary
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.Text
|
||||
@@ -56,6 +58,7 @@ class PwDatabaseV4 : PwDatabase<UUID, UUID, PwGroupV4, PwEntryV4> {
|
||||
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<UUID, UUID, PwGroupV4, PwEntryV4> {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
@@ -49,7 +49,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, PwNodeV4Interface {
|
||||
var iconCustom = PwIconCustom.UNKNOWN_ICON
|
||||
private var customData = HashMap<String, String>()
|
||||
var fields = HashMap<String, ProtectedString>()
|
||||
val binaries = HashMap<String, ProtectedBinary>()
|
||||
var binaries = HashMap<String, ProtectedBinary>()
|
||||
var foregroundColor = ""
|
||||
var backgroundColor = ""
|
||||
var overrideURL = ""
|
||||
@@ -98,7 +98,7 @@ class PwEntryV4 : PwEntry<UUID, UUID, PwGroupV4, PwEntryV4>, 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<UUID, UUID, PwGroupV4, PwEntryV4>, 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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<PwDbHeaderV4>(outputStream) {
|
||||
class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4,
|
||||
outputStream: OutputStream) : PwDbOutput<PwDbHeaderV4>(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<String, ProtectedBinary>) {
|
||||
for ((key, value) in binaries) {
|
||||
writeObject(key, value)
|
||||
private fun writeEntryBinaries(binaries: Map<String, ProtectedBinary>) {
|
||||
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<PwDeletedObject>) {
|
||||
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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
30
app/src/main/java/com/kunzisoft/keepass/stream/ReadBytes.kt
Normal file
30
app/src/main/java/com/kunzisoft/keepass/stream/ReadBytes.kt
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user