diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt index 092744fb8..57d5133c6 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt @@ -21,9 +21,7 @@ package com.kunzisoft.keepass.database.action import android.content.Context import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.tasks.ActionRunnable -import java.io.IOException open class SaveDatabaseRunnable(protected var context: Context, protected var database: Database, @@ -38,9 +36,7 @@ open class SaveDatabaseRunnable(protected var context: Context, if (saveDatabase && result.isSuccess) { try { database.saveData(context.contentResolver) - } catch (e: IOException) { - setError(e.message) - } catch (e: DatabaseOutputException) { + } catch (e: Exception) { setError(e.message) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/UpdateCompressionBinariesDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/UpdateCompressionBinariesDatabaseRunnable.kt new file mode 100644 index 000000000..3ce9be00a --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/UpdateCompressionBinariesDatabaseRunnable.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX 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 3 of the License, or + * (at your option) any later version. + * + * KeePass DX 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 KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.action + +import android.content.Context +import com.kunzisoft.keepass.database.element.Database +import com.kunzisoft.keepass.database.element.PwCompressionAlgorithm + +class UpdateCompressionBinariesDatabaseRunnable ( + context: Context, + database: Database, + private val oldCompressionAlgorithm: PwCompressionAlgorithm, + private val newCompressionAlgorithm: PwCompressionAlgorithm, + saveDatabase: Boolean) + : SaveDatabaseRunnable(context, database, saveDatabase) { + + override fun onStartRun() { + // Set new compression + if (database.allowDataCompression) { + try { + database.apply { + updateDataBinaryCompression(oldCompressionAlgorithm, newCompressionAlgorithm) + compressionAlgorithm = newCompressionAlgorithm + } + } catch (e: Exception) { + setError(e.message) + } + } + + super.onStartRun() + } + + override fun onFinishRun() { + super.onFinishRun() + + if (database.allowDataCompression) { + if (!result.isSuccess) { + try { + database.apply { + compressionAlgorithm = oldCompressionAlgorithm + updateDataBinaryCompression(newCompressionAlgorithm, oldCompressionAlgorithm) + } + } catch (e: Exception) { + setError(e.message) + } + } + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/BinaryPool.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/BinaryPool.kt index 66fb7620f..8622bcbc0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/BinaryPool.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/BinaryPool.kt @@ -20,25 +20,27 @@ package com.kunzisoft.keepass.database.element import android.util.SparseArray -import com.kunzisoft.keepass.database.element.security.ProtectedBinary +import com.kunzisoft.keepass.database.element.security.BinaryAttachment +import java.io.IOException class BinaryPool { - private val pool = SparseArray() + private val pool = SparseArray() - operator fun get(key: Int): ProtectedBinary? { + operator fun get(key: Int): BinaryAttachment? { return pool[key] } - fun put(key: Int, value: ProtectedBinary) { + fun put(key: Int, value: BinaryAttachment) { pool.put(key, value) } - fun doForEachBinary(action: (key: Int, binary: ProtectedBinary) -> Unit) { + fun doForEachBinary(action: (key: Int, binary: BinaryAttachment) -> Unit) { for (i in 0 until pool.size()) { action.invoke(i, pool.get(pool.keyAt(i))) } } + @Throws(IOException::class) fun clear() { doForEachBinary { _, binary -> binary.clear() @@ -46,9 +48,9 @@ class BinaryPool { pool.clear() } - fun add(protectedBinary: ProtectedBinary) { - if (findKey(protectedBinary) == null) { - pool.put(findUnusedKey(), protectedBinary) + fun add(fileBinary: BinaryAttachment) { + if (findKey(fileBinary) == null) { + pool.put(findUnusedKey(), fileBinary) } } @@ -59,7 +61,7 @@ class BinaryPool { return unusedKey } - fun findKey(pb: ProtectedBinary): Int? { + fun findKey(pb: BinaryAttachment): Int? { for (i in 0 until pool.size()) { if (pool.get(pool.keyAt(i)) == pb) return i } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt index 1f7131925..19a65bff2 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt @@ -139,6 +139,11 @@ class Database { } } + fun updateDataBinaryCompression(oldCompression: PwCompressionAlgorithm, + newCompression: PwCompressionAlgorithm) { + pwDatabaseV4?.changeBinaryCompression(oldCompression, newCompression) + } + val allowNoMasterKey: Boolean get() = pwDatabaseV4 != null diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwCompressionAlgorithm.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/PwCompressionAlgorithm.kt index c4bd02208..735bfb018 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwCompressionAlgorithm.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/PwCompressionAlgorithm.kt @@ -20,17 +20,30 @@ package com.kunzisoft.keepass.database.element import android.content.res.Resources +import android.os.Parcel +import android.os.Parcelable import com.kunzisoft.keepass.R import com.kunzisoft.keepass.utils.ObjectNameResource +import com.kunzisoft.keepass.utils.readEnum +import com.kunzisoft.keepass.utils.writeEnum + // Note: We can get away with using int's to store unsigned 32-bit ints // since we won't do arithmetic on these values (also unlikely to // reach negative ids). -enum class PwCompressionAlgorithm : ObjectNameResource { +enum class PwCompressionAlgorithm : ObjectNameResource, Parcelable { None, GZip; + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeEnum(this) + } + + override fun describeContents(): Int { + return 0 + } + override fun getName(resources: Resources): String { return when (this) { None -> resources.getString(R.string.compression_none) @@ -38,4 +51,14 @@ enum class PwCompressionAlgorithm : ObjectNameResource { } } + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): PwCompressionAlgorithm { + return parcel.readEnum() ?: None + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.kt index 8fad32a6c..99f48f596 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.kt @@ -94,7 +94,7 @@ class PwDatabaseV4 : PwDatabase { val customIcons = ArrayList() val customData = HashMap() - var binPool = BinaryPool() + var binaryPool = BinaryPool() var localizedAppName = "KeePassDX" @@ -161,6 +161,39 @@ class PwDatabaseV4 : PwDatabase { return list } + fun changeBinaryCompression(oldCompression: PwCompressionAlgorithm, + newCompression: PwCompressionAlgorithm) { + binaryPool.doForEachBinary { key, binary -> + + try { + when (oldCompression) { + PwCompressionAlgorithm.None -> { + when (newCompression) { + PwCompressionAlgorithm.None -> { + } + PwCompressionAlgorithm.GZip -> { + // To compress, create a new binary with file + binary.compress() + } + } + } + PwCompressionAlgorithm.GZip -> { + when (newCompression) { + PwCompressionAlgorithm.None -> { + // To decompress, create a new binary with file + binary.decompress() + } + PwCompressionAlgorithm.GZip -> { + } + } + } + } + } catch (e: Exception) { + Log.e(TAG, "Unable to change compression for $key") + } + } + } + override val availableEncryptionAlgorithms: List get() { val list = ArrayList() @@ -501,8 +534,12 @@ class PwDatabaseV4 : PwDatabase { } override fun clearCache() { - super.clearCache() - binPool.clear() + try { + super.clearCache() + binaryPool.clear() + } catch (e: Exception) { + Log.e(TAG, "Unable to clear cache", e) + } } companion object { 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 cfb5510e5..5702e1d0b 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 @@ -21,7 +21,7 @@ package com.kunzisoft.keepass.database.element import android.os.Parcel import android.os.Parcelable -import com.kunzisoft.keepass.database.element.security.ProtectedBinary +import com.kunzisoft.keepass.database.element.security.BinaryAttachment import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.utils.ParcelableUtil import java.util.* @@ -49,7 +49,7 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { var iconCustom = PwIconCustom.UNKNOWN_ICON private var customData = HashMap() var fields = HashMap() - var 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) - binaries = ParcelableUtil.readStringParcelableMap(parcel, ProtectedBinary::class.java) + binaries = ParcelableUtil.readStringParcelableMap(parcel, BinaryAttachment::class.java) foregroundColor = parcel.readString() ?: foregroundColor backgroundColor = parcel.readString() ?: backgroundColor overrideURL = parcel.readString() ?: overrideURL @@ -274,7 +274,7 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { fields[label] = value } - fun putProtectedBinary(key: String, value: ProtectedBinary) { + fun putProtectedBinary(key: String, value: BinaryAttachment) { binaries[key] = value } 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/BinaryAttachment.kt similarity index 50% rename from app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/security/BinaryAttachment.kt index 584a68400..29b552c3c 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/BinaryAttachment.kt @@ -21,21 +21,22 @@ package com.kunzisoft.keepass.database.element.security import android.os.Parcel import android.os.Parcelable -import android.util.Log +import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES +import com.kunzisoft.keepass.stream.ReadBytes +import com.kunzisoft.keepass.stream.readFromStream import java.io.* +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream -import java.util.Arrays +class BinaryAttachment : Parcelable { -class ProtectedBinary : Parcelable { - - var isCompressed: Boolean? = null // Only for KDBX3.1- + var isCompressed: Boolean? = null + private set var isProtected: Boolean = false - private var data: ByteArray? = null + private set private var dataFile: File? = null fun length(): Long { - if (data != null) - return data!!.size.toLong() if (dataFile != null) return dataFile!!.length() return 0 @@ -47,28 +48,12 @@ class ProtectedBinary : Parcelable { 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(data: ByteArray?, enableProtection: Boolean = false, compressed: Boolean? = null) { - this.isCompressed = compressed - this.isProtected = enableProtection - this.data = data this.dataFile = null } constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean? = null) { this.isCompressed = compressed this.isProtected = enableProtection - this.data = null this.dataFile = dataFile } @@ -76,24 +61,74 @@ class ProtectedBinary : Parcelable { 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) dataFile = File(parcel.readString()) } @Throws(IOException::class) fun getInputDataStream(): InputStream { return when { - data != null -> ByteArrayInputStream(data) dataFile != null -> FileInputStream(dataFile!!) - else -> throw IOException("Unable to get binary data") + // TODO + // else -> throw IOException("Unable to get binary data") + else -> ByteArrayInputStream(ByteArray(0)) } } + @Throws(IOException::class) + fun compress() { + if (dataFile != null) { + // To compress, create a new binary with file + if (isCompressed != true) { + val fileBinaryCompress = File(dataFile!!.parent, dataFile!!.name + "_temp") + val outputStream = GZIPOutputStream(FileOutputStream(fileBinaryCompress)) + readFromStream(getInputDataStream(), BUFFER_SIZE_BYTES, + object : ReadBytes { + override fun read(buffer: ByteArray) { + outputStream.write(buffer) + } + }) + outputStream.close() + + // Remove unGzip file + if (dataFile!!.delete()) { + if (fileBinaryCompress.renameTo(dataFile)) { + // Harmonize with database compression + isCompressed = true + } + } + } + } + } + + @Throws(IOException::class) + fun decompress() { + if (dataFile != null) { + if (isCompressed != false) { + val fileBinaryDecompress = File(dataFile!!.parent, dataFile!!.name + "_temp") + val outputStream = FileOutputStream(fileBinaryDecompress) + readFromStream(GZIPInputStream(getInputDataStream()), BUFFER_SIZE_BYTES, + object : ReadBytes { + override fun read(buffer: ByteArray) { + outputStream.write(buffer) + } + }) + outputStream.close() + + // Remove gzip file + if (dataFile!!.delete()) { + if (fileBinaryDecompress.renameTo(dataFile)) { + // Harmonize with database compression + isCompressed = false + } + } + } + } + } + + @Throws(IOException::class) fun clear() { - data = null if (dataFile != null && !dataFile!!.delete()) - Log.e(TAG, "Unable to delete temp file " + dataFile!!.absolutePath) + throw IOException("Unable to delete temp file " + dataFile!!.absolutePath) } override fun equals(other: Any?): Boolean { @@ -101,16 +136,11 @@ class ProtectedBinary : Parcelable { return true if (other == null || javaClass != other.javaClass) return false - if (other !is ProtectedBinary) + if (other !is BinaryAttachment) return false var sameData = false - if (data != null && Arrays.equals(data, other.data)) - sameData = true - else if (dataFile != null && dataFile == other.dataFile) - sameData = true - else if (data == null && other.data == null - && dataFile == null && other.dataFile == null) + if (dataFile != null && dataFile == other.dataFile) sameData = true return isCompressed == other.isCompressed @@ -124,7 +154,6 @@ class ProtectedBinary : Parcelable { 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) return result } @@ -135,22 +164,20 @@ 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) dest.writeString(dataFile?.absolutePath) } companion object { - private val TAG = ProtectedBinary::class.java.name + private val TAG = BinaryAttachment::class.java.name @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): ProtectedBinary { - return ProtectedBinary(parcel) + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BinaryAttachment { + return BinaryAttachment(parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } 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 25a406e0c..a85026438 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 @@ -26,8 +26,7 @@ import com.kunzisoft.keepass.crypto.StreamCipherFactory import com.kunzisoft.keepass.crypto.engine.CipherEngine import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BASE_64_FLAG -import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES -import com.kunzisoft.keepass.database.element.security.ProtectedBinary +import com.kunzisoft.keepass.database.element.security.BinaryAttachment import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.file.KDBX4DateUtil @@ -44,6 +43,7 @@ import java.nio.charset.Charset import java.text.ParseException import java.util.* import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream import javax.crypto.Cipher import kotlin.math.min @@ -56,7 +56,7 @@ class ImporterV4(private val streamDir: File, private var hashOfHeader: ByteArray? = null private val unusedCacheFileName: String - get() = mDatabase.binPool.findUnusedKey().toString() + get() = mDatabase.binaryPool.findUnusedKey().toString() private var readNextNode = true private val ctxGroups = Stack() @@ -65,7 +65,7 @@ class ImporterV4(private val streamDir: File, private var ctxStringName: String? = null private var ctxStringValue: ProtectedString? = null private var ctxBinaryName: String? = null - private var ctxBinaryValue: ProtectedBinary? = null + private var ctxBinaryValue: BinaryAttachment? = null private var ctxATName: String? = null private var ctxATSeq: String? = null private var entryInHistory = false @@ -242,8 +242,8 @@ class ImporterV4(private val streamDir: File, } }) } - val protectedBinary = ProtectedBinary(file, protectedFlag) - mDatabase.binPool.add(protectedBinary) + val protectedBinary = BinaryAttachment(file, protectedFlag) + mDatabase.binaryPool.add(protectedBinary) } else -> { return false @@ -433,7 +433,7 @@ class ImporterV4(private val streamDir: File, if (key != null) { val pbData = readBinary(xpp) val id = Integer.parseInt(key) - mDatabase.binPool.put(id, pbData!!) + mDatabase.binaryPool.put(id, pbData!!) } else { readUnknown(xpp) } @@ -938,7 +938,7 @@ class ImporterV4(private val streamDir: File, } @Throws(XmlPullParserException::class, IOException::class) - private fun readBinary(xpp: XmlPullParser): ProtectedBinary? { + private fun readBinary(xpp: XmlPullParser): BinaryAttachment? { // Reference Id to a binary already present in binary pool val ref = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrRef) @@ -946,7 +946,7 @@ class ImporterV4(private val streamDir: File, xpp.next() // Consume end tag val id = Integer.parseInt(ref) - return mDatabase.binPool[id] + return mDatabase.binaryPool[id] } // New binary to retrieve @@ -968,18 +968,20 @@ class ImporterV4(private val streamDir: File, val base64 = readString(xpp) if (base64.isEmpty()) - return ProtectedBinary() + return BinaryAttachment() 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 -> + val file = File(streamDir, unusedCacheFileName) + return FileOutputStream(file).use { outputStream -> + // Force compression in this specific case + if (mDatabase.compressionAlgorithm == PwCompressionAlgorithm.GZip + && compressed == false) { + GZIPOutputStream(outputStream).write(data) + BinaryAttachment(file, protected, true) + } else { outputStream.write(data) + BinaryAttachment(file, protected) } - ProtectedBinary(file, protected, compressed) } } } 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 9705a2736..4c3d59e85 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 @@ -48,7 +48,7 @@ class PwDbInnerHeaderOutputV4(private val database: PwDatabaseV4, dataOutputStream.writeInt(streamKeySize) dataOutputStream.write(header.innerRandomStreamKey) - database.binPool.doForEachBinary { _, protectedBinary -> + database.binaryPool.doForEachBinary { _, protectedBinary -> var flag = PwDbHeaderV4.KdbxBinaryFlags.None if (protectedBinary.isProtected) { flag = flag or PwDbHeaderV4.KdbxBinaryFlags.Protected 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 f039c2a45..c452eaba6 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 @@ -31,7 +31,7 @@ import com.kunzisoft.keepass.database.NodeHandler import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BASE_64_FLAG import com.kunzisoft.keepass.database.element.PwDatabaseV4.Companion.BUFFER_SIZE_BYTES -import com.kunzisoft.keepass.database.element.security.ProtectedBinary +import com.kunzisoft.keepass.database.element.security.BinaryAttachment import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.UnknownKDF @@ -46,6 +46,7 @@ import java.io.* import java.security.NoSuchAlgorithmException import java.security.SecureRandom import java.util.* +import java.util.zip.GZIPInputStream import java.util.zip.GZIPOutputStream import javax.crypto.Cipher import javax.crypto.CipherOutputStream @@ -217,9 +218,10 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, writeUuid(PwDatabaseV4XML.ElemLastSelectedGroup, mDatabaseV4.lastSelectedGroupUUID) writeUuid(PwDatabaseV4XML.ElemLastTopVisibleGroup, mDatabaseV4.lastTopVisibleGroupUUID) - if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { - writeBinariesKDBX31() - } + // Seem to work properly if always in meta + // if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) + writeMetaBinaries() + writeCustomData(mDatabaseV4.customData) xml.endTag(null, PwDatabaseV4XML.ElemMeta) @@ -405,14 +407,15 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, writeObject(name, String(Base64.encode(data, BASE_64_FLAG))) } - // Only for KDBX3.1- @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeBinariesKDBX31() { + private fun writeMetaBinaries() { xml.startTag(null, PwDatabaseV4XML.ElemBinaries) - mDatabaseV4.binPool.doForEachBinary { key, binary -> + mDatabaseV4.binaryPool.doForEachBinary { key, binary -> xml.startTag(null, PwDatabaseV4XML.ElemBinary) xml.attribute(null, PwDatabaseV4XML.AttrId, key.toString()) + + // Force binary compression from database (compression was harmonized during import) xml.attribute(null, PwDatabaseV4XML.AttrCompressed, if (mDatabaseV4.compressionAlgorithm === PwCompressionAlgorithm.GZip) { PwDatabaseV4XML.ValTrue @@ -420,23 +423,17 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, 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, + // Force decompression in this specific case + val binaryInputStream = if (mDatabaseV4.compressionAlgorithm == PwCompressionAlgorithm.None + && binary.isCompressed == true) { + GZIPInputStream(binary.getInputDataStream()) + } else { + binary.getInputDataStream() + } + + // Write the XML + readFromStream(binaryInputStream, BUFFER_SIZE_BYTES, object : ReadBytes { override fun read(buffer: ByteArray) { val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray() @@ -541,7 +538,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, } @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeEntryBinaries(binaries: Map) { + private fun writeEntryBinaries(binaries: Map) { for ((key, binary) in binaries) { xml.startTag(null, PwDatabaseV4XML.ElemBinary) xml.startTag(null, PwDatabaseV4XML.ElemKey) @@ -549,7 +546,7 @@ class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, xml.endTag(null, PwDatabaseV4XML.ElemKey) xml.startTag(null, PwDatabaseV4XML.ElemValue) - val ref = mDatabaseV4.binPool.findKey(binary) + val ref = mDatabaseV4.binaryPool.findKey(binary) if (ref != null) { xml.attribute(null, PwDatabaseV4XML.AttrRef, ref.toString()) } else { diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt index 56fedf46b..0edb42bde 100644 --- a/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt @@ -8,10 +8,7 @@ import android.os.Bundle import android.os.IBinder import com.kunzisoft.keepass.R import com.kunzisoft.keepass.app.database.CipherDatabaseEntity -import com.kunzisoft.keepass.database.action.AssignPasswordInDatabaseRunnable -import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable -import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable -import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable +import com.kunzisoft.keepass.database.action.* import com.kunzisoft.keepass.database.action.node.* import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.settings.PreferencesUtil @@ -107,11 +104,11 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat ACTION_DATABASE_COPY_NODES_TASK -> buildDatabaseCopyNodesActionTask(intent) ACTION_DATABASE_MOVE_NODES_TASK -> buildDatabaseMoveNodesActionTask(intent) ACTION_DATABASE_DELETE_NODES_TASK -> buildDatabaseDeleteNodesActionTask(intent) + ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent) ACTION_DATABASE_UPDATE_NAME_TASK, ACTION_DATABASE_UPDATE_DESCRIPTION_TASK, ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK, ACTION_DATABASE_UPDATE_COLOR_TASK, - ACTION_DATABASE_UPDATE_COMPRESSION_TASK, ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK, ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK, ACTION_DATABASE_UPDATE_ENCRYPTION_TASK, @@ -409,6 +406,25 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat } } + private fun buildDatabaseUpdateCompressionActionTask(intent: Intent): ActionRunnable? { + return if (intent.hasExtra(OLD_ELEMENT_KEY) + && intent.hasExtra(NEW_ELEMENT_KEY) + && intent.hasExtra(SAVE_DATABASE_KEY)) { + return UpdateCompressionBinariesDatabaseRunnable(this, + Database.getInstance(), + intent.getParcelableExtra(OLD_ELEMENT_KEY), + intent.getParcelableExtra(NEW_ELEMENT_KEY), + intent.getBooleanExtra(SAVE_DATABASE_KEY, false) + ).apply { + mAfterSaveDatabase = { result -> + result.data = intent.extras + } + } + } else { + null + } + } + private fun buildDatabaseUpdateElementActionTask(intent: Intent): ActionRunnable? { return if (intent.hasExtra(SAVE_DATABASE_KEY)) { return SaveDatabaseRunnable(this, diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/ParcelableUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/ParcelableUtil.kt index c37c5cdc1..ada214fc3 100644 --- a/app/src/main/java/com/kunzisoft/keepass/utils/ParcelableUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/utils/ParcelableUtil.kt @@ -96,3 +96,9 @@ object ParcelableUtil { return map } } + +inline fun > Parcel.readEnum() = + readString()?.let { enumValueOf(it) } + +inline fun > Parcel.writeEnum(value: T?) = + writeString(value?.name)