diff --git a/app/src/androidTest/java/com/kunzisoft/keepass/tests/stream/BinaryDataTest.kt b/app/src/androidTest/java/com/kunzisoft/keepass/tests/stream/BinaryDataTest.kt index d47b16928..e44df8a54 100644 --- a/app/src/androidTest/java/com/kunzisoft/keepass/tests/stream/BinaryDataTest.kt +++ b/app/src/androidTest/java/com/kunzisoft/keepass/tests/stream/BinaryDataTest.kt @@ -3,7 +3,7 @@ package com.kunzisoft.keepass.tests.stream import android.content.Context import androidx.test.platform.app.InstrumentationRegistry import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.element.database.BinaryData +import com.kunzisoft.keepass.database.element.database.BinaryFile import com.kunzisoft.keepass.stream.readAllBytes import com.kunzisoft.keepass.utils.UriUtil import junit.framework.TestCase.assertEquals @@ -25,7 +25,7 @@ class BinaryDataTest { private val loadedKey = Database.LoadedKey.generateNewCipherKey() - private fun saveBinary(asset: String, binaryData: BinaryData) { + private fun saveBinary(asset: String, binaryData: BinaryFile) { context.assets.open(asset).use { assetInputStream -> binaryData.getOutputDataStream(loadedKey).use { binaryOutputStream -> assetInputStream.readAllBytes(DEFAULT_BUFFER_SIZE) { buffer -> @@ -37,8 +37,8 @@ class BinaryDataTest { @Test fun testSaveTextInCache() { - val binaryA = BinaryData(fileA) - val binaryB = BinaryData(fileB) + val binaryA = BinaryFile(fileA) + val binaryB = BinaryFile(fileB) saveBinary(TEST_TEXT_ASSET, binaryA) saveBinary(TEST_TEXT_ASSET, binaryB) assertEquals("Save text binary length failed.", binaryA.getSize(), binaryB.getSize()) @@ -47,8 +47,8 @@ class BinaryDataTest { @Test fun testSaveImageInCache() { - val binaryA = BinaryData(fileA) - val binaryB = BinaryData(fileB) + val binaryA = BinaryFile(fileA) + val binaryB = BinaryFile(fileB) saveBinary(TEST_IMAGE_ASSET, binaryA) saveBinary(TEST_IMAGE_ASSET, binaryB) assertEquals("Save image binary length failed.", binaryA.getSize(), binaryB.getSize()) @@ -57,9 +57,9 @@ class BinaryDataTest { @Test fun testCompressText() { - val binaryA = BinaryData(fileA) - val binaryB = BinaryData(fileB) - val binaryC = BinaryData(fileC) + val binaryA = BinaryFile(fileA) + val binaryB = BinaryFile(fileB) + val binaryC = BinaryFile(fileC) saveBinary(TEST_TEXT_ASSET, binaryA) saveBinary(TEST_TEXT_ASSET, binaryB) saveBinary(TEST_TEXT_ASSET, binaryC) @@ -74,9 +74,9 @@ class BinaryDataTest { @Test fun testCompressImage() { - val binaryA = BinaryData(fileA) - var binaryB = BinaryData(fileB) - val binaryC = BinaryData(fileC) + val binaryA = BinaryFile(fileA) + var binaryB = BinaryFile(fileB) + val binaryC = BinaryFile(fileC) saveBinary(TEST_IMAGE_ASSET, binaryA) saveBinary(TEST_IMAGE_ASSET, binaryB) saveBinary(TEST_IMAGE_ASSET, binaryC) @@ -84,7 +84,7 @@ class BinaryDataTest { binaryB.compress(loadedKey) assertEquals("Compress image length failed.", binaryA.getSize(), binaryA.getSize()) assertEquals("Compress image failed.", binaryA.binaryHash(), binaryA.binaryHash()) - binaryB = BinaryData(fileB, true) + binaryB = BinaryFile(fileB, true) binaryB.decompress(loadedKey) assertEquals("Decompress image length failed.", binaryB.getSize(), binaryC.getSize()) assertEquals("Decompress image failed.", binaryB.binaryHash(), binaryC.binaryHash()) @@ -92,7 +92,7 @@ class BinaryDataTest { @Test fun testReadText() { - val binaryA = BinaryData(fileA) + val binaryA = BinaryFile(fileA) saveBinary(TEST_TEXT_ASSET, binaryA) assert(streamAreEquals(context.assets.open(TEST_TEXT_ASSET), binaryA.getInputDataStream(loadedKey))) @@ -100,7 +100,7 @@ class BinaryDataTest { @Test fun testReadImage() { - val binaryA = BinaryData(fileA) + val binaryA = BinaryFile(fileA) saveBinary(TEST_IMAGE_ASSET, binaryA) assert(streamAreEquals(context.assets.open(TEST_IMAGE_ASSET), binaryA.getInputDataStream(loadedKey))) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Attachment.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Attachment.kt index bd5c988de..cfc4690b5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Attachment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Attachment.kt @@ -21,6 +21,7 @@ package com.kunzisoft.keepass.database.element import android.os.Parcel import android.os.Parcelable +import com.kunzisoft.keepass.database.element.database.BinaryByte import com.kunzisoft.keepass.database.element.database.BinaryData @@ -29,7 +30,7 @@ data class Attachment(var name: String, constructor(parcel: Parcel) : this( parcel.readString() ?: "", - parcel.readParcelable(BinaryData::class.java.classLoader) ?: BinaryData() + parcel.readParcelable(BinaryData::class.java.classLoader) ?: BinaryByte() ) override fun writeToParcel(parcel: Parcel, flags: Int) { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryByte.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryByte.kt new file mode 100644 index 000000000..e567f33a7 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryByte.kt @@ -0,0 +1,153 @@ +/* + * Copyright 2018 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePassDX. + * + * KeePassDX 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. + * + * KeePassDX 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 KeePassDX. If not, see . + * + */ +package com.kunzisoft.keepass.database.element.database + +import android.os.Parcel +import android.os.Parcelable +import com.kunzisoft.keepass.database.element.Database +import java.io.* + +class BinaryByte : BinaryData { + + private var mDataByte: ByteArray = ByteArray(0) + + /** + * Empty protected binary + */ + constructor() : super() + + constructor(byteArray: ByteArray, + compressed: Boolean = false, + protected: Boolean = false) : super(compressed, protected) { + this.mDataByte = byteArray + } + + constructor(parcel: Parcel) : super(parcel) { + val byteArray = ByteArray(parcel.readInt()) + parcel.readByteArray(byteArray) + mDataByte = byteArray + } + + @Throws(IOException::class) + override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream { + return ByteArrayInputStream(mDataByte) + } + + @Throws(IOException::class) + override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream { + return ByteOutputStream() + } + + @Throws(IOException::class) + override fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream { + return getInputDataStream(cipherKey) + } + + @Throws(IOException::class) + override fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream { + return getOutputDataStream(cipherKey) + } + + @Throws(IOException::class) + override fun compress(cipherKey: Database.LoadedKey) { + // TODO compress + } + + @Throws(IOException::class) + override fun decompress(cipherKey: Database.LoadedKey) { + // TODO decompress + } + + @Throws(IOException::class) + override fun clear() { + mDataByte = ByteArray(0) + } + + override fun dataExists(): Boolean { + return mDataByte.isNotEmpty() + } + + override fun getSize(): Long { + return mDataByte.size.toLong() + } + + /** + * Hash of the raw encrypted file in temp folder, only to compare binary data + */ + override fun binaryHash(): Int { + return if (dataExists()) + mDataByte.contentHashCode() + else + 0 + } + + override fun toString(): String { + return mDataByte.toString() + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + super.writeToParcel(dest, flags) + dest.writeInt(mDataByte.size) + dest.writeByteArray(mDataByte) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is BinaryByte) return false + if (!super.equals(other)) return false + + if (!mDataByte.contentEquals(other.mDataByte)) return false + + return true + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + mDataByte.contentHashCode() + return result + } + + /** + * Custom OutputStream to calculate the size and hash of binary file + */ + private inner class ByteOutputStream : ByteArrayOutputStream() { + override fun close() { + mDataByte = this.toByteArray() + super.close() + } + } + + companion object { + + private val TAG = BinaryByte::class.java.name + + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BinaryByte { + return BinaryByte(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryData.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryData.kt index 0f727ef0f..64b440932 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryData.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryData.kt @@ -21,278 +21,92 @@ package com.kunzisoft.keepass.database.element.database import android.os.Parcel import android.os.Parcelable -import android.util.Base64 -import android.util.Base64InputStream -import android.util.Base64OutputStream import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.stream.readAllBytes -import org.apache.commons.io.output.CountingOutputStream -import java.io.* -import java.nio.ByteBuffer -import java.security.MessageDigest -import java.util.zip.GZIPInputStream -import java.util.zip.GZIPOutputStream -import javax.crypto.Cipher -import javax.crypto.CipherInputStream -import javax.crypto.CipherOutputStream -import javax.crypto.spec.IvParameterSpec +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream -class BinaryData : Parcelable { +abstract class BinaryData : Parcelable { - private var mDataFile: File? = null - private var mLength: Long = 0 - private var mBinaryHash = 0 var isCompressed: Boolean = false - private set + protected set var isProtected: Boolean = false - private set + protected set var isCorrupted: Boolean = false - // Cipher to encrypt temp file - private var cipherEncryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER) - private var cipherDecryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER) /** * Empty protected binary */ - constructor() + protected constructor() - constructor(dataFile: File, compressed: Boolean = false, protected: Boolean = false) { - this.mDataFile = dataFile - this.mLength = 0 + protected constructor(compressed: Boolean = false, protected: Boolean = false) { this.isCompressed = compressed this.isProtected = protected } - private constructor(parcel: Parcel) { - parcel.readString()?.let { - mDataFile = File(it) - } - mLength = parcel.readLong() + protected constructor(parcel: Parcel) { isCompressed = parcel.readByte().toInt() != 0 isProtected = parcel.readByte().toInt() != 0 isCorrupted = parcel.readByte().toInt() != 0 } @Throws(IOException::class) - fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream { - return buildInputStream(mDataFile, cipherKey) - } + abstract fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream @Throws(IOException::class) - fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream { - return buildOutputStream(mDataFile, cipherKey) - } + abstract fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream @Throws(IOException::class) - fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream { - return if (isCompressed) { - GZIPInputStream(getInputDataStream(cipherKey)) - } else { - getInputDataStream(cipherKey) - } - } + abstract fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream @Throws(IOException::class) - fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream { - return if (isCompressed) { - GZIPOutputStream(getOutputDataStream(cipherKey)) - } else { - getOutputDataStream(cipherKey) - } - } + abstract fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream @Throws(IOException::class) - private fun buildInputStream(file: File?, cipherKey: Database.LoadedKey): InputStream { - return when { - file != null && file.length() > 0 -> { - cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv)) - Base64InputStream(CipherInputStream(FileInputStream(file), cipherDecryption), Base64.NO_WRAP) - } - else -> ByteArrayInputStream(ByteArray(0)) - } - } + abstract fun compress(cipherKey: Database.LoadedKey) @Throws(IOException::class) - private fun buildOutputStream(file: File?, cipherKey: Database.LoadedKey): OutputStream { - return when { - file != null -> { - cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv)) - BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(FileOutputStream(file), cipherEncryption), Base64.NO_WRAP)) - } - else -> throw IOException("Unable to write in an unknown file") - } - } + abstract fun decompress(cipherKey: Database.LoadedKey) @Throws(IOException::class) - fun compress(cipherKey: Database.LoadedKey) { - mDataFile?.let { concreteDataFile -> - // To compress, create a new binary with file - if (!isCompressed) { - // Encrypt the new gzipped temp file - val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp") - getInputDataStream(cipherKey).use { inputStream -> - GZIPOutputStream(buildOutputStream(fileBinaryCompress, cipherKey)).use { outputStream -> - inputStream.readAllBytes { buffer -> - outputStream.write(buffer) - } - } - } - // Remove ungzip file - if (concreteDataFile.delete()) { - if (fileBinaryCompress.renameTo(concreteDataFile)) { - // Harmonize with database compression - isCompressed = true - } - } - } - } - } + abstract fun clear() - @Throws(IOException::class) - fun decompress(cipherKey: Database.LoadedKey) { - mDataFile?.let { concreteDataFile -> - if (isCompressed) { - // Encrypt the new ungzipped temp file - val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp") - getUnGzipInputDataStream(cipherKey).use { inputStream -> - buildOutputStream(fileBinaryDecompress, cipherKey).use { outputStream -> - inputStream.readAllBytes { buffer -> - outputStream.write(buffer) - } - } - } - // Remove gzip file - if (concreteDataFile.delete()) { - if (fileBinaryDecompress.renameTo(concreteDataFile)) { - // Harmonize with database compression - isCompressed = false - } - } - } - } - } + abstract fun dataExists(): Boolean - @Throws(IOException::class) - fun clear() { - if (mDataFile != null && !mDataFile!!.delete()) - throw IOException("Unable to delete temp file " + mDataFile!!.absolutePath) - } + abstract fun getSize(): Long - fun dataExists(): Boolean { - return mDataFile != null && mLength > 0 - } - - fun getSize(): Long { - return mLength - } - - /** - * Hash of the raw encrypted file in temp folder, only to compare binary data - */ - @Throws(FileNotFoundException::class) - fun binaryHash(): Int { - return mBinaryHash - } - - override fun equals(other: Any?): Boolean { - if (this === other) - return true - if (other == null || javaClass != other.javaClass) - return false - if (other !is BinaryData) - return false - - var sameData = false - if (mDataFile != null && mDataFile == other.mDataFile) - sameData = true - - return isCompressed == other.isCompressed - && isProtected == other.isProtected - && isCorrupted == other.isCorrupted - && sameData - } - - override fun hashCode(): Int { - - var result = 0 - result = 31 * result + if (isCompressed) 1 else 0 - result = 31 * result + if (isProtected) 1 else 0 - result = 31 * result + if (isCorrupted) 1 else 0 - result = 31 * result + mDataFile!!.hashCode() - result = 31 * result + mLength.hashCode() - return result - } - - override fun toString(): String { - return mDataFile.toString() - } + abstract fun binaryHash(): Int override fun describeContents(): Int { return 0 } override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeString(mDataFile?.absolutePath) - dest.writeLong(mLength) dest.writeByte((if (isCompressed) 1 else 0).toByte()) dest.writeByte((if (isProtected) 1 else 0).toByte()) dest.writeByte((if (isCorrupted) 1 else 0).toByte()) } - /** - * Custom OutputStream to calculate the size and hash of binary file - */ - private inner class BinaryCountingOutputStream(out: OutputStream): CountingOutputStream(out) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is BinaryData) return false - private val mMessageDigest: MessageDigest - init { - mLength = 0 - mMessageDigest = MessageDigest.getInstance("MD5") - mBinaryHash = 0 - } + if (isCompressed != other.isCompressed) return false + if (isProtected != other.isProtected) return false + if (isCorrupted != other.isCorrupted) return false - override fun beforeWrite(n: Int) { - super.beforeWrite(n) - mLength = byteCount - } + return true + } - override fun write(idx: Int) { - super.write(idx) - mMessageDigest.update(idx.toByte()) - } - - override fun write(bts: ByteArray) { - super.write(bts) - mMessageDigest.update(bts) - } - - override fun write(bts: ByteArray, st: Int, end: Int) { - super.write(bts, st, end) - mMessageDigest.update(bts, st, end) - } - - override fun close() { - super.close() - mLength = byteCount - val bytes = mMessageDigest.digest() - mBinaryHash = ByteBuffer.wrap(bytes).int - } + override fun hashCode(): Int { + var result = isCompressed.hashCode() + result = 31 * result + isProtected.hashCode() + result = 31 * result + isCorrupted.hashCode() + return result } companion object { - private val TAG = BinaryData::class.java.name - - @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): BinaryData { - return BinaryData(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryFile.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryFile.kt new file mode 100644 index 000000000..07c09a204 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryFile.kt @@ -0,0 +1,273 @@ +/* + * Copyright 2018 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePassDX. + * + * KeePassDX 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. + * + * KeePassDX 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 KeePassDX. If not, see . + * + */ +package com.kunzisoft.keepass.database.element.database + +import android.os.Parcel +import android.os.Parcelable +import android.util.Base64 +import android.util.Base64InputStream +import android.util.Base64OutputStream +import com.kunzisoft.keepass.database.element.Database +import com.kunzisoft.keepass.stream.readAllBytes +import org.apache.commons.io.output.CountingOutputStream +import java.io.* +import java.nio.ByteBuffer +import java.security.MessageDigest +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream +import javax.crypto.Cipher +import javax.crypto.CipherInputStream +import javax.crypto.CipherOutputStream +import javax.crypto.spec.IvParameterSpec + +class BinaryFile : BinaryData { + + private var mDataFile: File? = null + private var mLength: Long = 0 + private var mBinaryHash = 0 + // Cipher to encrypt temp file + @Transient + private var cipherEncryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER) + @Transient + private var cipherDecryption: Cipher = Cipher.getInstance(Database.LoadedKey.BINARY_CIPHER) + + constructor() : super() + + constructor(dataFile: File, + compressed: Boolean = false, + protected: Boolean = false) : super(compressed, protected) { + this.mDataFile = dataFile + this.mLength = 0 + this.mBinaryHash = 0 + } + + constructor(parcel: Parcel) : super(parcel) { + parcel.readString()?.let { + mDataFile = File(it) + } + mLength = parcel.readLong() + mBinaryHash = parcel.readInt() + } + + @Throws(IOException::class) + override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream { + return buildInputStream(mDataFile, cipherKey) + } + + @Throws(IOException::class) + override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream { + return buildOutputStream(mDataFile, cipherKey) + } + + @Throws(IOException::class) + override fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream { + return if (isCompressed) { + GZIPInputStream(getInputDataStream(cipherKey)) + } else { + getInputDataStream(cipherKey) + } + } + + @Throws(IOException::class) + override fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream { + return if (isCompressed) { + GZIPOutputStream(getOutputDataStream(cipherKey)) + } else { + getOutputDataStream(cipherKey) + } + } + + @Throws(IOException::class) + private fun buildInputStream(file: File?, cipherKey: Database.LoadedKey): InputStream { + return when { + file != null && file.length() > 0 -> { + cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv)) + Base64InputStream(CipherInputStream(FileInputStream(file), cipherDecryption), Base64.NO_WRAP) + } + else -> ByteArrayInputStream(ByteArray(0)) + } + } + + @Throws(IOException::class) + private fun buildOutputStream(file: File?, cipherKey: Database.LoadedKey): OutputStream { + return when { + file != null -> { + cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv)) + BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(FileOutputStream(file), cipherEncryption), Base64.NO_WRAP)) + } + else -> throw IOException("Unable to write in an unknown file") + } + } + + @Throws(IOException::class) + override fun compress(cipherKey: Database.LoadedKey) { + mDataFile?.let { concreteDataFile -> + // To compress, create a new binary with file + if (!isCompressed) { + // Encrypt the new gzipped temp file + val fileBinaryCompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp") + getInputDataStream(cipherKey).use { inputStream -> + GZIPOutputStream(buildOutputStream(fileBinaryCompress, cipherKey)).use { outputStream -> + inputStream.readAllBytes { buffer -> + outputStream.write(buffer) + } + } + } + // Remove ungzip file + if (concreteDataFile.delete()) { + if (fileBinaryCompress.renameTo(concreteDataFile)) { + // Harmonize with database compression + isCompressed = true + } + } + } + } + } + + @Throws(IOException::class) + override fun decompress(cipherKey: Database.LoadedKey) { + mDataFile?.let { concreteDataFile -> + if (isCompressed) { + // Encrypt the new ungzipped temp file + val fileBinaryDecompress = File(concreteDataFile.parent, concreteDataFile.name + "_temp") + getUnGzipInputDataStream(cipherKey).use { inputStream -> + buildOutputStream(fileBinaryDecompress, cipherKey).use { outputStream -> + inputStream.readAllBytes { buffer -> + outputStream.write(buffer) + } + } + } + // Remove gzip file + if (concreteDataFile.delete()) { + if (fileBinaryDecompress.renameTo(concreteDataFile)) { + // Harmonize with database compression + isCompressed = false + } + } + } + } + } + + @Throws(IOException::class) + override fun clear() { + if (mDataFile != null && !mDataFile!!.delete()) + throw IOException("Unable to delete temp file " + mDataFile!!.absolutePath) + } + + override fun dataExists(): Boolean { + return mDataFile != null && mLength > 0 + } + + override fun getSize(): Long { + return mLength + } + + /** + * Hash of the raw encrypted file in temp folder, only to compare binary data + */ + @Throws(FileNotFoundException::class) + override fun binaryHash(): Int { + return mBinaryHash + } + + override fun toString(): String { + return mDataFile.toString() + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + super.writeToParcel(dest, flags) + dest.writeString(mDataFile?.absolutePath) + dest.writeLong(mLength) + dest.writeInt(mBinaryHash) + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is BinaryFile) return false + if (!super.equals(other)) return false + + return mDataFile != null && mDataFile == other.mDataFile + } + + override fun hashCode(): Int { + var result = super.hashCode() + result = 31 * result + (mDataFile?.hashCode() ?: 0) + result = 31 * result + mLength.hashCode() + result = 31 * result + mBinaryHash + return result + } + + /** + * Custom OutputStream to calculate the size and hash of binary file + */ + private inner class BinaryCountingOutputStream(out: OutputStream): CountingOutputStream(out) { + + private val mMessageDigest: MessageDigest + init { + mLength = 0 + mMessageDigest = MessageDigest.getInstance("MD5") + mBinaryHash = 0 + } + + override fun beforeWrite(n: Int) { + super.beforeWrite(n) + mLength = byteCount + } + + override fun write(idx: Int) { + super.write(idx) + mMessageDigest.update(idx.toByte()) + } + + override fun write(bts: ByteArray) { + super.write(bts) + mMessageDigest.update(bts) + } + + override fun write(bts: ByteArray, st: Int, end: Int) { + super.write(bts, st, end) + mMessageDigest.update(bts, st, end) + } + + override fun close() { + super.close() + mLength = byteCount + val bytes = mMessageDigest.digest() + mBinaryHash = ByteBuffer.wrap(bytes).int + } + } + + companion object { + + private val TAG = BinaryFile::class.java.name + + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BinaryFile { + return BinaryFile(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryPool.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryPool.kt index 827bd260e..5210fe778 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryPool.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryPool.kt @@ -49,7 +49,7 @@ abstract class BinaryPool { protection: Boolean = false): KeyBinary { val fileInCache = File(cacheDirectory, "$poolId$creationId$binaryFileIncrement") binaryFileIncrement++ - val newBinaryFile = BinaryData(fileInCache, compression, protection) + val newBinaryFile = BinaryFile(fileInCache, compression, protection) val newKey = put(key, newBinaryFile) return KeyBinary(newBinaryFile, newKey) } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt index c804904fc..312b99b5f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt @@ -278,7 +278,7 @@ class DatabaseKDB : DatabaseVersioned() { // Generate an unique new file val fileInCache = File(cacheDirectory, binaryIncrement.toString()) binaryIncrement++ - return BinaryData(fileInCache) + return BinaryFile(fileInCache) } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/model/EntryAttachmentState.kt b/app/src/main/java/com/kunzisoft/keepass/model/EntryAttachmentState.kt index 2f0863fae..5fcb9fb67 100644 --- a/app/src/main/java/com/kunzisoft/keepass/model/EntryAttachmentState.kt +++ b/app/src/main/java/com/kunzisoft/keepass/model/EntryAttachmentState.kt @@ -22,7 +22,7 @@ package com.kunzisoft.keepass.model import android.os.Parcel import android.os.Parcelable import com.kunzisoft.keepass.database.element.Attachment -import com.kunzisoft.keepass.database.element.database.BinaryData +import com.kunzisoft.keepass.database.element.database.BinaryByte import com.kunzisoft.keepass.utils.readEnum import com.kunzisoft.keepass.utils.writeEnum @@ -33,7 +33,7 @@ data class EntryAttachmentState(var attachment: Attachment, var previewState: AttachmentState = AttachmentState.NULL) : Parcelable { constructor(parcel: Parcel) : this( - parcel.readParcelable(Attachment::class.java.classLoader) ?: Attachment("", BinaryData()), + parcel.readParcelable(Attachment::class.java.classLoader) ?: Attachment("", BinaryByte()), parcel.readEnum() ?: StreamDirection.DOWNLOAD, parcel.readEnum() ?: AttachmentState.NULL, parcel.readInt(),