mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Refactoring Binary cache
This commit is contained in:
@@ -125,7 +125,7 @@ class EntryActivity : LockingActivity() {
|
||||
historyView = findViewById(R.id.history_container)
|
||||
entryContentsView = findViewById(R.id.entry_contents)
|
||||
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
|
||||
entryContentsView?.setAttachmentCipherKey(mDatabase?.loadedCipherKey)
|
||||
entryContentsView?.setAttachmentCipherKey(mDatabase)
|
||||
entryProgress = findViewById(R.id.entry_progress)
|
||||
lockView = findViewById(R.id.lock_button)
|
||||
|
||||
|
||||
@@ -202,7 +202,7 @@ class EntryEditActivity : LockingActivity(),
|
||||
// Build fragment to manage entry modification
|
||||
entryEditFragment = supportFragmentManager.findFragmentByTag(ENTRY_EDIT_FRAGMENT_TAG) as? EntryEditFragment?
|
||||
if (entryEditFragment == null) {
|
||||
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo, mDatabase?.loadedCipherKey)
|
||||
entryEditFragment = EntryEditFragment.getInstance(tempEntryInfo)
|
||||
}
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.entry_edit_contents, entryEditFragment!!, ENTRY_EDIT_FRAGMENT_TAG)
|
||||
|
||||
@@ -232,8 +232,13 @@ class IconPickerActivity : LockingActivity() {
|
||||
mDatabase?.buildNewCustomIcon(UriUtil.getBinaryDir(this@IconPickerActivity)) { customIcon, binary ->
|
||||
if (customIcon != null) {
|
||||
iconCustomState.iconCustom = customIcon
|
||||
BinaryDatabaseManager.resizeBitmapAndStoreDataInBinaryFile(contentResolver,
|
||||
iconToUploadUri, binary)
|
||||
mDatabase?.let { database ->
|
||||
BinaryDatabaseManager.resizeBitmapAndStoreDataInBinaryFile(
|
||||
contentResolver,
|
||||
database,
|
||||
iconToUploadUri,
|
||||
binary)
|
||||
}
|
||||
when {
|
||||
binary == null -> {
|
||||
}
|
||||
|
||||
@@ -39,6 +39,8 @@ import kotlin.math.max
|
||||
|
||||
class ImageViewerActivity : LockingActivity() {
|
||||
|
||||
private var mDatabase: Database? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -59,6 +61,8 @@ class ImageViewerActivity : LockingActivity() {
|
||||
resources.displayMetrics.heightPixels * 2
|
||||
)
|
||||
|
||||
mDatabase = Database.getInstance()
|
||||
|
||||
try {
|
||||
progressView.visibility = View.VISIBLE
|
||||
intent.getParcelableExtra<Attachment>(IMAGE_ATTACHMENT_TAG)?.let { attachment ->
|
||||
@@ -66,16 +70,18 @@ class ImageViewerActivity : LockingActivity() {
|
||||
supportActionBar?.title = attachment.name
|
||||
supportActionBar?.subtitle = Formatter.formatFileSize(this, attachment.binaryData.getSize())
|
||||
|
||||
BinaryDatabaseManager.loadBitmap(
|
||||
attachment.binaryData,
|
||||
Database.getInstance().loadedCipherKey,
|
||||
mImagePreviewMaxWidth
|
||||
) { bitmapLoaded ->
|
||||
if (bitmapLoaded == null) {
|
||||
finish()
|
||||
} else {
|
||||
progressView.visibility = View.GONE
|
||||
imageView.setImageBitmap(bitmapLoaded)
|
||||
mDatabase?.let { database ->
|
||||
BinaryDatabaseManager.loadBitmap(
|
||||
database,
|
||||
attachment.binaryData,
|
||||
mImagePreviewMaxWidth
|
||||
) { bitmapLoaded ->
|
||||
if (bitmapLoaded == null) {
|
||||
finish()
|
||||
} else {
|
||||
progressView.visibility = View.GONE
|
||||
imageView.setImageBitmap(bitmapLoaded)
|
||||
}
|
||||
}
|
||||
}
|
||||
} ?: finish()
|
||||
|
||||
@@ -84,7 +84,6 @@ class EntryEditFragment: StylishFragment() {
|
||||
|
||||
// Elements to modify the current entry
|
||||
private var mEntryInfo = EntryInfo()
|
||||
private var mBinaryCipherKey: Database.LoadedKey? = null
|
||||
private var mLastFocusedEditField: FocusedEditField? = null
|
||||
private var mExtraViewToRequestFocus: EditText? = null
|
||||
|
||||
@@ -122,7 +121,9 @@ class EntryEditFragment: StylishFragment() {
|
||||
attachmentsContainerView = rootView.findViewById(R.id.entry_attachments_container)
|
||||
attachmentsListView = rootView.findViewById(R.id.entry_attachments_list)
|
||||
attachmentsAdapter = EntryAttachmentsItemsAdapter(requireContext())
|
||||
attachmentsAdapter.binaryCipherKey = arguments?.getSerializable(KEY_BINARY_CIPHER_KEY) as? Database.LoadedKey?
|
||||
// TODO retrieve current database with its unique key
|
||||
attachmentsAdapter.database = Database.getInstance()
|
||||
//attachmentsAdapter.database = arguments?.getInt(KEY_DATABASE)
|
||||
attachmentsAdapter.onListSizeChangedListener = { previousSize, newSize ->
|
||||
if (previousSize > 0 && newSize == 0) {
|
||||
attachmentsContainerView.collapse(true)
|
||||
@@ -502,7 +503,6 @@ class EntryEditFragment: StylishFragment() {
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
populateEntryWithViews()
|
||||
outState.putParcelable(KEY_TEMP_ENTRY_INFO, mEntryInfo)
|
||||
outState.putSerializable(KEY_BINARY_CIPHER_KEY, mBinaryCipherKey)
|
||||
outState.putParcelable(KEY_LAST_FOCUSED_FIELD, mLastFocusedEditField)
|
||||
|
||||
super.onSaveInstanceState(outState)
|
||||
@@ -510,15 +510,16 @@ class EntryEditFragment: StylishFragment() {
|
||||
|
||||
companion object {
|
||||
const val KEY_TEMP_ENTRY_INFO = "KEY_TEMP_ENTRY_INFO"
|
||||
const val KEY_BINARY_CIPHER_KEY = "KEY_BINARY_CIPHER_KEY"
|
||||
const val KEY_DATABASE = "KEY_DATABASE"
|
||||
const val KEY_LAST_FOCUSED_FIELD = "KEY_LAST_FOCUSED_FIELD"
|
||||
|
||||
fun getInstance(entryInfo: EntryInfo?,
|
||||
loadedKey: Database.LoadedKey?): EntryEditFragment {
|
||||
fun getInstance(entryInfo: EntryInfo?): EntryEditFragment {
|
||||
//database: Database?): EntryEditFragment {
|
||||
return EntryEditFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable(KEY_TEMP_ENTRY_INFO, entryInfo)
|
||||
putSerializable(KEY_BINARY_CIPHER_KEY, loadedKey)
|
||||
// TODO Unique database key database.key
|
||||
putInt(KEY_DATABASE, 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ import kotlin.math.max
|
||||
class EntryAttachmentsItemsAdapter(context: Context)
|
||||
: AnimatedItemsAdapter<EntryAttachmentState, EntryAttachmentsItemsAdapter.EntryBinariesViewHolder>(context) {
|
||||
|
||||
var binaryCipherKey: Database.LoadedKey? = null
|
||||
var database: Database? = null
|
||||
var onItemClickListener: ((item: EntryAttachmentState)->Unit)? = null
|
||||
var onBinaryPreviewLoaded: ((item: EntryAttachmentState) -> Unit)? = null
|
||||
|
||||
@@ -82,21 +82,23 @@ class EntryAttachmentsItemsAdapter(context: Context)
|
||||
if (entryAttachmentState.previewState == AttachmentState.NULL) {
|
||||
entryAttachmentState.previewState = AttachmentState.IN_PROGRESS
|
||||
// Load the bitmap image
|
||||
BinaryDatabaseManager.loadBitmap(
|
||||
entryAttachmentState.attachment.binaryData,
|
||||
binaryCipherKey,
|
||||
mImagePreviewMaxWidth
|
||||
) { imageLoaded ->
|
||||
if (imageLoaded == null) {
|
||||
entryAttachmentState.previewState = AttachmentState.ERROR
|
||||
visibility = View.GONE
|
||||
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
|
||||
} else {
|
||||
entryAttachmentState.previewState = AttachmentState.COMPLETE
|
||||
setImageBitmap(imageLoaded)
|
||||
if (visibility != View.VISIBLE) {
|
||||
expand(true, resources.getDimensionPixelSize(R.dimen.item_file_info_height)) {
|
||||
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
|
||||
database?.let { database ->
|
||||
BinaryDatabaseManager.loadBitmap(
|
||||
database,
|
||||
entryAttachmentState.attachment.binaryData,
|
||||
mImagePreviewMaxWidth
|
||||
) { imageLoaded ->
|
||||
if (imageLoaded == null) {
|
||||
entryAttachmentState.previewState = AttachmentState.ERROR
|
||||
visibility = View.GONE
|
||||
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
|
||||
} else {
|
||||
entryAttachmentState.previewState = AttachmentState.COMPLETE
|
||||
setImageBitmap(imageLoaded)
|
||||
if (visibility != View.VISIBLE) {
|
||||
expand(true, resources.getDimensionPixelSize(R.dimen.item_file_info_height)) {
|
||||
onBinaryPreviewLoaded?.invoke(entryAttachmentState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class ReloadDatabaseRunnable(private val context: Context,
|
||||
private var tempCipherKey: Database.LoadedKey? = null
|
||||
|
||||
override fun onStartRun() {
|
||||
tempCipherKey = mDatabase.loadedCipherKey
|
||||
tempCipherKey = mDatabase.binaryCache.loadedCipherKey
|
||||
// Clear before we load
|
||||
mDatabase.clear(UriUtil.getBinaryDir(context))
|
||||
mDatabase.wasReloaded = true
|
||||
|
||||
@@ -70,7 +70,7 @@ class Database {
|
||||
var isReadOnly = false
|
||||
|
||||
val iconDrawableFactory = IconDrawableFactory(
|
||||
{ loadedCipherKey },
|
||||
{ binaryCache },
|
||||
{ iconId -> iconsManager.getBinaryForCustomIcon(iconId) }
|
||||
)
|
||||
|
||||
@@ -92,13 +92,13 @@ class Database {
|
||||
* Cipher key regenerated when the database is loaded and closed
|
||||
* Can be used to temporarily store database elements
|
||||
*/
|
||||
var loadedCipherKey: LoadedKey?
|
||||
var binaryCache: BinaryCache
|
||||
private set(value) {
|
||||
mDatabaseKDB?.loadedCipherKey = value
|
||||
mDatabaseKDBX?.loadedCipherKey = value
|
||||
mDatabaseKDB?.binaryCache = value
|
||||
mDatabaseKDBX?.binaryCache = value
|
||||
}
|
||||
get() {
|
||||
return mDatabaseKDB?.loadedCipherKey ?: mDatabaseKDBX?.loadedCipherKey
|
||||
return mDatabaseKDB?.binaryCache ?: mDatabaseKDBX?.binaryCache ?: BinaryCache()
|
||||
}
|
||||
|
||||
private val iconsManager: IconsManager
|
||||
@@ -136,7 +136,7 @@ class Database {
|
||||
|
||||
fun removeCustomIcon(customIcon: IconImageCustom) {
|
||||
iconDrawableFactory.clearFromCache(customIcon)
|
||||
iconsManager.removeCustomIcon(customIcon.uuid)
|
||||
iconsManager.removeCustomIcon(binaryCache, customIcon.uuid)
|
||||
}
|
||||
|
||||
val allowName: Boolean
|
||||
@@ -381,7 +381,7 @@ class Database {
|
||||
|
||||
fun createData(databaseUri: Uri, databaseName: String, rootName: String) {
|
||||
val newDatabase = DatabaseKDBX(databaseName, rootName)
|
||||
newDatabase.loadedCipherKey = LoadedKey.generateNewCipherKey()
|
||||
newDatabase.binaryCache.loadedCipherKey = LoadedKey.generateNewCipherKey()
|
||||
setDatabaseKDBX(newDatabase)
|
||||
this.fileUri = databaseUri
|
||||
// Set Database state
|
||||
@@ -669,6 +669,7 @@ class Database {
|
||||
}
|
||||
|
||||
fun clear(filesDirectory: File? = null) {
|
||||
binaryCache.clear()
|
||||
iconsManager.clearCache()
|
||||
iconDrawableFactory.clearCache()
|
||||
// Delete the cache of the database if present
|
||||
|
||||
@@ -24,21 +24,16 @@ 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 java.io.*
|
||||
import java.util.zip.GZIPOutputStream
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherInputStream
|
||||
import javax.crypto.CipherOutputStream
|
||||
import javax.crypto.spec.IvParameterSpec
|
||||
|
||||
class BinaryByte : BinaryData {
|
||||
|
||||
private var mDataByteId: Int? = null
|
||||
|
||||
private fun getByteArray(): ByteArray {
|
||||
val keyData = KeyByteArray.getByteArray(mDataByteId)
|
||||
private fun getByteArray(binaryCache: BinaryCache): ByteArray {
|
||||
val keyData = binaryCache.getByteArray(mDataByteId)
|
||||
mDataByteId = keyData.key
|
||||
return keyData.data
|
||||
}
|
||||
@@ -46,14 +41,10 @@ class BinaryByte : BinaryData {
|
||||
/**
|
||||
* Empty protected binary
|
||||
*/
|
||||
constructor() : super() {
|
||||
getByteArray()
|
||||
}
|
||||
constructor() : super()
|
||||
|
||||
constructor(compressed: Boolean = false,
|
||||
protected: Boolean = false) : super(compressed, protected) {
|
||||
getByteArray()
|
||||
}
|
||||
protected: Boolean = false) : super(compressed, protected)
|
||||
|
||||
constructor(mDataByteId: Int,
|
||||
compressed: Boolean = false,
|
||||
@@ -73,27 +64,25 @@ class BinaryByte : BinaryData {
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream {
|
||||
override fun getInputDataStream(binaryCache: BinaryCache): InputStream {
|
||||
return when {
|
||||
getSize() > 0 -> {
|
||||
cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
|
||||
Base64InputStream(CipherInputStream(ByteArrayInputStream(getByteArray()), cipherDecryption), Base64.NO_WRAP)
|
||||
Base64InputStream(ByteArrayInputStream(getByteArray(binaryCache)), Base64.NO_WRAP)
|
||||
}
|
||||
else -> ByteArrayInputStream(ByteArray(0))
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
|
||||
cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
|
||||
return BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(ByteOutputStream(), cipherEncryption), Base64.NO_WRAP))
|
||||
override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream {
|
||||
return BinaryCountingOutputStream(Base64OutputStream(ByteOutputStream(binaryCache), Base64.NO_WRAP))
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun compress(cipherKey: Database.LoadedKey) {
|
||||
override fun compress(binaryCache: BinaryCache) {
|
||||
if (!isCompressed) {
|
||||
GZIPOutputStream(getOutputDataStream(cipherKey)).use { outputStream ->
|
||||
getInputDataStream(cipherKey).use { inputStream ->
|
||||
GZIPOutputStream(getOutputDataStream(binaryCache)).use { outputStream ->
|
||||
getInputDataStream(binaryCache).use { inputStream ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
@@ -104,10 +93,10 @@ class BinaryByte : BinaryData {
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun decompress(cipherKey: Database.LoadedKey) {
|
||||
override fun decompress(binaryCache: BinaryCache) {
|
||||
if (isCompressed) {
|
||||
getUnGzipInputDataStream(cipherKey).use { inputStream ->
|
||||
getOutputDataStream(cipherKey).use { outputStream ->
|
||||
getUnGzipInputDataStream(binaryCache).use { inputStream ->
|
||||
getOutputDataStream(binaryCache).use { outputStream ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
@@ -118,12 +107,14 @@ class BinaryByte : BinaryData {
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun clear() {
|
||||
KeyByteArray.removeByteArray(mDataByteId)
|
||||
override fun delete() {
|
||||
mDataByteId = null
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return getByteArray().toString()
|
||||
@Throws(IOException::class)
|
||||
override fun clear(binaryCache: BinaryCache) {
|
||||
binaryCache.removeByteArray(mDataByteId)
|
||||
mDataByteId = null
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
@@ -145,9 +136,9 @@ class BinaryByte : BinaryData {
|
||||
/**
|
||||
* Custom OutputStream to calculate the size and hash of binary file
|
||||
*/
|
||||
private inner class ByteOutputStream : ByteArrayOutputStream() {
|
||||
private inner class ByteOutputStream(private val binaryCache: BinaryCache) : ByteArrayOutputStream() {
|
||||
override fun close() {
|
||||
KeyByteArray.setByteArray(mDataByteId, this.toByteArray())
|
||||
binaryCache.setByteArray(mDataByteId, this.toByteArray())
|
||||
super.close()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.kunzisoft.keepass.database.element.database
|
||||
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import java.util.*
|
||||
|
||||
class BinaryCache {
|
||||
|
||||
/**
|
||||
* Cipher key generated when the database is loaded, and destroyed when the database is closed
|
||||
* Can be used to temporarily store database elements
|
||||
*/
|
||||
var loadedCipherKey: Database.LoadedKey? = null
|
||||
|
||||
// Similar to file storage but much faster
|
||||
private val byteArrayList = HashMap<Int, ByteArray>()
|
||||
|
||||
fun getByteArray(key: Int?): KeyByteArray {
|
||||
if (key == null) {
|
||||
val newItem = KeyByteArray(byteArrayList.size, ByteArray(0))
|
||||
byteArrayList[newItem.key] = newItem.data
|
||||
return newItem
|
||||
}
|
||||
if (!byteArrayList.containsKey(key)) {
|
||||
val newItem = KeyByteArray(key, ByteArray(0))
|
||||
byteArrayList[newItem.key] = newItem.data
|
||||
return newItem
|
||||
}
|
||||
return KeyByteArray(key, byteArrayList[key]!!)
|
||||
}
|
||||
|
||||
fun setByteArray(key: Int?, data: ByteArray): KeyByteArray {
|
||||
return if (key == null) {
|
||||
val keyByteArray = KeyByteArray(byteArrayList.size, data)
|
||||
byteArrayList[keyByteArray.key] = keyByteArray.data
|
||||
keyByteArray
|
||||
} else {
|
||||
byteArrayList[key] = data
|
||||
KeyByteArray(key, data)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeByteArray(key: Int?) {
|
||||
key?.let {
|
||||
byteArrayList.remove(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
byteArrayList.clear()
|
||||
}
|
||||
|
||||
data class KeyByteArray(val key: Int, val data: ByteArray) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is KeyByteArray) return false
|
||||
|
||||
if (key != other.key) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return key
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,37 +71,40 @@ abstract class BinaryData : Parcelable {
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
abstract fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream
|
||||
abstract fun getInputDataStream(binaryCache: BinaryCache): InputStream
|
||||
|
||||
@Throws(IOException::class)
|
||||
abstract fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream
|
||||
abstract fun getOutputDataStream(binaryCache: BinaryCache): OutputStream
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getUnGzipInputDataStream(cipherKey: Database.LoadedKey): InputStream {
|
||||
fun getUnGzipInputDataStream(binaryCache: BinaryCache): InputStream {
|
||||
return if (isCompressed) {
|
||||
GZIPInputStream(getInputDataStream(cipherKey))
|
||||
GZIPInputStream(getInputDataStream(binaryCache))
|
||||
} else {
|
||||
getInputDataStream(cipherKey)
|
||||
getInputDataStream(binaryCache)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
fun getGzipOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
|
||||
fun getGzipOutputDataStream(binaryCache: BinaryCache): OutputStream {
|
||||
return if (isCompressed) {
|
||||
GZIPOutputStream(getOutputDataStream(cipherKey))
|
||||
GZIPOutputStream(getOutputDataStream(binaryCache))
|
||||
} else {
|
||||
getOutputDataStream(cipherKey)
|
||||
getOutputDataStream(binaryCache)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
abstract fun compress(cipherKey: Database.LoadedKey)
|
||||
abstract fun compress(binaryCache: BinaryCache)
|
||||
|
||||
@Throws(IOException::class)
|
||||
abstract fun decompress(cipherKey: Database.LoadedKey)
|
||||
abstract fun decompress(binaryCache: BinaryCache)
|
||||
|
||||
@Throws(IOException::class)
|
||||
abstract fun clear()
|
||||
abstract fun delete()
|
||||
|
||||
@Throws(IOException::class)
|
||||
abstract fun clear(binaryCache: BinaryCache)
|
||||
|
||||
open fun dataExists(): Boolean {
|
||||
return mLength > 0
|
||||
|
||||
@@ -24,12 +24,8 @@ 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.GZIPOutputStream
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherInputStream
|
||||
@@ -55,19 +51,20 @@ class BinaryFile : BinaryData {
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getInputDataStream(cipherKey: Database.LoadedKey): InputStream {
|
||||
return buildInputStream(mDataFile, cipherKey)
|
||||
override fun getInputDataStream(binaryCache: BinaryCache): InputStream {
|
||||
return buildInputStream(mDataFile, binaryCache)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun getOutputDataStream(cipherKey: Database.LoadedKey): OutputStream {
|
||||
return buildOutputStream(mDataFile, cipherKey)
|
||||
override fun getOutputDataStream(binaryCache: BinaryCache): OutputStream {
|
||||
return buildOutputStream(mDataFile, binaryCache)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun buildInputStream(file: File?, cipherKey: Database.LoadedKey): InputStream {
|
||||
private fun buildInputStream(file: File?, binaryCache: BinaryCache): InputStream {
|
||||
val cipherKey = binaryCache.loadedCipherKey
|
||||
return when {
|
||||
file != null && file.length() > 0 -> {
|
||||
cipherKey != null && file != null && file.length() > 0 -> {
|
||||
cipherDecryption.init(Cipher.DECRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
|
||||
Base64InputStream(CipherInputStream(FileInputStream(file), cipherDecryption), Base64.NO_WRAP)
|
||||
}
|
||||
@@ -76,9 +73,10 @@ class BinaryFile : BinaryData {
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun buildOutputStream(file: File?, cipherKey: Database.LoadedKey): OutputStream {
|
||||
private fun buildOutputStream(file: File?, binaryCache: BinaryCache): OutputStream {
|
||||
val cipherKey = binaryCache.loadedCipherKey
|
||||
return when {
|
||||
file != null -> {
|
||||
cipherKey != null && file != null -> {
|
||||
cipherEncryption.init(Cipher.ENCRYPT_MODE, cipherKey.key, IvParameterSpec(cipherKey.iv))
|
||||
BinaryCountingOutputStream(Base64OutputStream(CipherOutputStream(FileOutputStream(file), cipherEncryption), Base64.NO_WRAP))
|
||||
}
|
||||
@@ -87,14 +85,14 @@ class BinaryFile : BinaryData {
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun compress(cipherKey: Database.LoadedKey) {
|
||||
override fun compress(binaryCache: BinaryCache) {
|
||||
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 ->
|
||||
getInputDataStream(binaryCache).use { inputStream ->
|
||||
GZIPOutputStream(buildOutputStream(fileBinaryCompress, binaryCache)).use { outputStream ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
@@ -112,13 +110,13 @@ class BinaryFile : BinaryData {
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun decompress(cipherKey: Database.LoadedKey) {
|
||||
override fun decompress(binaryCache: BinaryCache) {
|
||||
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 ->
|
||||
getUnGzipInputDataStream(binaryCache).use { inputStream ->
|
||||
buildOutputStream(fileBinaryDecompress, binaryCache).use { outputStream ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
@@ -135,12 +133,16 @@ class BinaryFile : BinaryData {
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun clear() {
|
||||
override fun delete() {
|
||||
if (mDataFile != null && !mDataFile!!.delete())
|
||||
throw IOException("Unable to delete temp file " + mDataFile!!.absolutePath)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun clear(binaryCache: BinaryCache) {
|
||||
delete()
|
||||
}
|
||||
|
||||
override fun dataExists(): Boolean {
|
||||
return mDataFile != null && super.dataExists()
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
package com.kunzisoft.keepass.database.element.database
|
||||
|
||||
import android.util.Log
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import kotlin.math.abs
|
||||
|
||||
@@ -227,7 +226,7 @@ abstract class BinaryPool<T> {
|
||||
@Throws(IOException::class)
|
||||
fun clear() {
|
||||
doForEachBinary { _, binary ->
|
||||
binary.clear()
|
||||
binary.delete()
|
||||
}
|
||||
pool.clear()
|
||||
}
|
||||
|
||||
@@ -212,10 +212,8 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
private fun compressAllBinaries() {
|
||||
binaryPool.doForEachBinary { _, binary ->
|
||||
try {
|
||||
val cipherKey = loadedCipherKey
|
||||
?: throw IOException("Unable to retrieve cipher key to compress binaries")
|
||||
// To compress, create a new binary with file
|
||||
binary.compress(cipherKey)
|
||||
binary.compress(binaryCache)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to compress $binary", e)
|
||||
}
|
||||
@@ -225,9 +223,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
private fun decompressAllBinaries() {
|
||||
binaryPool.doForEachBinary { _, binary ->
|
||||
try {
|
||||
val cipherKey = loadedCipherKey
|
||||
?: throw IOException("Unable to retrieve cipher key to decompress binaries")
|
||||
binary.decompress(cipherKey)
|
||||
binary.decompress(binaryCache)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to decompress $binary", e)
|
||||
}
|
||||
@@ -690,7 +686,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
try {
|
||||
binaryPool.remove(it)
|
||||
if (clear)
|
||||
it.clear()
|
||||
it.clear(binaryCache)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to clean binaries", e)
|
||||
}
|
||||
|
||||
@@ -57,10 +57,11 @@ abstract class DatabaseVersioned<
|
||||
protected set
|
||||
|
||||
/**
|
||||
* To manage binaries in faster way
|
||||
* Cipher key generated when the database is loaded, and destroyed when the database is closed
|
||||
* Can be used to temporarily store database elements
|
||||
*/
|
||||
var loadedCipherKey: Database.LoadedKey? = null
|
||||
var binaryCache = BinaryCache()
|
||||
|
||||
val iconsManager = IconsManager()
|
||||
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package com.kunzisoft.keepass.database.element.database
|
||||
|
||||
import java.util.HashMap
|
||||
|
||||
class KeyByteArray {
|
||||
|
||||
data class KeyByteArray(val key: Int, val data: ByteArray) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is KeyByteArray) return false
|
||||
|
||||
if (key != other.key) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return key
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
// Similar to file storage but much faster
|
||||
private val byteArrayList = HashMap<Int, ByteArray>()
|
||||
|
||||
fun getByteArray(key: Int?): KeyByteArray {
|
||||
if (key == null) {
|
||||
val newItem = KeyByteArray(byteArrayList.size, ByteArray(0))
|
||||
byteArrayList[newItem.key] = newItem.data
|
||||
return newItem
|
||||
}
|
||||
if (!byteArrayList.containsKey(key)) {
|
||||
val newItem = KeyByteArray(key, ByteArray(0))
|
||||
byteArrayList[newItem.key] = newItem.data
|
||||
return newItem
|
||||
}
|
||||
return KeyByteArray(key, byteArrayList[key]!!)
|
||||
}
|
||||
|
||||
fun setByteArray(key: Int?, data: ByteArray): KeyByteArray {
|
||||
return if (key == null) {
|
||||
val keyByteArray = KeyByteArray(byteArrayList.size, data)
|
||||
byteArrayList[keyByteArray.key] = keyByteArray.data
|
||||
keyByteArray
|
||||
} else {
|
||||
byteArrayList[key] = data
|
||||
KeyByteArray(key, data)
|
||||
}
|
||||
}
|
||||
|
||||
fun removeByteArray(key: Int?) {
|
||||
key?.let {
|
||||
byteArrayList.remove(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,7 @@
|
||||
package com.kunzisoft.keepass.database.element.icon
|
||||
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryByte
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryData
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryFile
|
||||
import com.kunzisoft.keepass.database.element.database.CustomIconPool
|
||||
import com.kunzisoft.keepass.database.element.database.*
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.KEY_ID
|
||||
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
|
||||
import java.io.File
|
||||
@@ -82,11 +79,11 @@ class IconsManager {
|
||||
return customCache.isBinaryDuplicate(binaryData)
|
||||
}
|
||||
|
||||
fun removeCustomIcon(iconUuid: UUID) {
|
||||
fun removeCustomIcon(binaryCache: BinaryCache, iconUuid: UUID) {
|
||||
val binary = customCache[iconUuid]
|
||||
customCache.remove(iconUuid)
|
||||
try {
|
||||
binary?.clear()
|
||||
binary?.clear(binaryCache)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to remove custom icon binary", e)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class DatabaseInputKDB(cacheDirectory: File,
|
||||
progressTaskUpdater: ProgressTaskUpdater?,
|
||||
fixDuplicateUUID: Boolean): DatabaseKDB {
|
||||
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
|
||||
mDatabase.loadedCipherKey = loadedCipherKey
|
||||
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
|
||||
mDatabase.retrieveMasterKey(password, keyfileInputStream)
|
||||
}
|
||||
}
|
||||
@@ -72,7 +72,7 @@ class DatabaseInputKDB(cacheDirectory: File,
|
||||
progressTaskUpdater: ProgressTaskUpdater?,
|
||||
fixDuplicateUUID: Boolean): DatabaseKDB {
|
||||
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
|
||||
mDatabase.loadedCipherKey = loadedCipherKey
|
||||
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
|
||||
mDatabase.masterKey = masterKey
|
||||
}
|
||||
}
|
||||
@@ -309,9 +309,7 @@ class DatabaseInputKDB(cacheDirectory: File,
|
||||
if (fieldSize > 0) {
|
||||
val binaryData = mDatabase.buildNewAttachment(cacheDirectory)
|
||||
entry.putBinary(binaryData, mDatabase.binaryPool)
|
||||
val cipherKey = mDatabase.loadedCipherKey
|
||||
?: throw IOException("Unable to retrieve cipher key to load binaries")
|
||||
BufferedOutputStream(binaryData.getOutputDataStream(cipherKey)).use { outputStream ->
|
||||
BufferedOutputStream(binaryData.getOutputDataStream(mDatabase.binaryCache)).use { outputStream ->
|
||||
cipherInputStream.readBytes(fieldSize) { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
|
||||
@@ -102,7 +102,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
progressTaskUpdater: ProgressTaskUpdater?,
|
||||
fixDuplicateUUID: Boolean): DatabaseKDBX {
|
||||
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
|
||||
mDatabase.loadedCipherKey = loadedCipherKey
|
||||
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
|
||||
mDatabase.retrieveMasterKey(password, keyfileInputStream)
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
progressTaskUpdater: ProgressTaskUpdater?,
|
||||
fixDuplicateUUID: Boolean): DatabaseKDBX {
|
||||
return openDatabase(databaseInputStream, progressTaskUpdater, fixDuplicateUUID) {
|
||||
mDatabase.loadedCipherKey = loadedCipherKey
|
||||
mDatabase.binaryCache.loadedCipherKey = loadedCipherKey
|
||||
mDatabase.masterKey = masterKey
|
||||
}
|
||||
}
|
||||
@@ -279,9 +279,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
// No compression at this level
|
||||
val protectedBinary = mDatabase.buildNewAttachment(cacheDirectory,
|
||||
isRAMSufficient.invoke(byteLength.toLong()), false, protectedFlag)
|
||||
val cipherKey = mDatabase.loadedCipherKey
|
||||
?: throw IOException("Unable to retrieve cipher key to load binaries")
|
||||
protectedBinary.getOutputDataStream(cipherKey).use { outputStream ->
|
||||
protectedBinary.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
|
||||
dataInputStream.readBytes(byteLength) { buffer ->
|
||||
outputStream.write(buffer)
|
||||
}
|
||||
@@ -708,10 +706,8 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
val iconData = customIconData
|
||||
if (customIconID != DatabaseVersioned.UUID_ZERO && iconData != null) {
|
||||
mDatabase.addCustomIcon(cacheDirectory, customIconID, isRAMSufficient.invoke(iconData.size.toLong())) { _, binary ->
|
||||
mDatabase.loadedCipherKey?.let { cipherKey ->
|
||||
binary?.getOutputDataStream(cipherKey)?.use { outputStream ->
|
||||
outputStream.write(iconData)
|
||||
}
|
||||
binary?.getOutputDataStream(mDatabase.binaryCache)?.use { outputStream ->
|
||||
outputStream.write(iconData)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1023,16 +1019,14 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
// Build the new binary and compress
|
||||
val binaryAttachment = mDatabase.buildNewAttachment(cacheDirectory,
|
||||
isRAMSufficient.invoke(base64.length.toLong()), compressed, protected, binaryId)
|
||||
val binaryCipherKey = mDatabase.loadedCipherKey
|
||||
?: throw IOException("Unable to retrieve cipher key to load binaries")
|
||||
try {
|
||||
binaryAttachment.getOutputDataStream(binaryCipherKey).use { outputStream ->
|
||||
binaryAttachment.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
|
||||
outputStream.write(Base64.decode(base64, BASE_64_FLAG))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to read base 64 attachment", e)
|
||||
binaryAttachment.isCorrupted = true
|
||||
binaryAttachment.getOutputDataStream(binaryCipherKey).use { outputStream ->
|
||||
binaryAttachment.getOutputDataStream(mDatabase.binaryCache).use { outputStream ->
|
||||
outputStream.write(base64.toByteArray())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +217,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
|
||||
}
|
||||
// Entries
|
||||
mDatabaseKDB.doForEachEntryInIndex { entry ->
|
||||
EntryOutputKDB(entry, outputStream, mDatabaseKDB.loadedCipherKey).output(mDatabaseKDB)
|
||||
EntryOutputKDB(mDatabaseKDB, entry, outputStream).output()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -136,28 +136,26 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
dataOutputStream.writeInt(streamKeySize)
|
||||
dataOutputStream.write(header.innerRandomStreamKey)
|
||||
|
||||
database.loadedCipherKey?.let { binaryCipherKey ->
|
||||
database.binaryPool.doForEachOrderedBinaryWithoutDuplication { _, binary ->
|
||||
// Force decompression to add binary in header
|
||||
binary.decompress(binaryCipherKey)
|
||||
// Write type binary
|
||||
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
|
||||
// Write size
|
||||
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1))
|
||||
// Write protected flag
|
||||
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
|
||||
if (binary.isProtected) {
|
||||
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
|
||||
}
|
||||
dataOutputStream.writeByte(flag)
|
||||
database.binaryPool.doForEachOrderedBinaryWithoutDuplication { _, binary ->
|
||||
// Force decompression to add binary in header
|
||||
binary.decompress(database.binaryCache)
|
||||
// Write type binary
|
||||
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary)
|
||||
// Write size
|
||||
dataOutputStream.writeUInt(UnsignedInt.fromKotlinLong(binary.getSize() + 1))
|
||||
// Write protected flag
|
||||
var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None
|
||||
if (binary.isProtected) {
|
||||
flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected
|
||||
}
|
||||
dataOutputStream.writeByte(flag)
|
||||
|
||||
binary.getInputDataStream(binaryCipherKey).use { inputStream ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
dataOutputStream.write(buffer)
|
||||
}
|
||||
binary.getInputDataStream(database.binaryCache).use { inputStream ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
dataOutputStream.write(buffer)
|
||||
}
|
||||
}
|
||||
} ?: Log.e(TAG, "Unable to retrieve cipher key to write head binaries")
|
||||
}
|
||||
|
||||
dataOutputStream.writeByte(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader)
|
||||
dataOutputStream.writeInt(0)
|
||||
@@ -494,31 +492,29 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
// With kdbx4, don't use this method because binaries are in header file
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeMetaBinaries() {
|
||||
mDatabaseKDBX.loadedCipherKey?.let { binaryCipherKey ->
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
|
||||
// Use indexes because necessarily (binary header ref is the order)
|
||||
mDatabaseKDBX.binaryPool.doForEachOrderedBinaryWithoutDuplication { index, binary ->
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
|
||||
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
|
||||
if (binary.getSize() > 0) {
|
||||
if (binary.isCompressed) {
|
||||
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
|
||||
}
|
||||
try {
|
||||
// Write the XML
|
||||
binary.getInputDataStream(binaryCipherKey).use { inputStream ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to write binary", e)
|
||||
}
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
|
||||
// Use indexes because necessarily (binary header ref is the order)
|
||||
mDatabaseKDBX.binaryPool.doForEachOrderedBinaryWithoutDuplication { index, binary ->
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
|
||||
xml.attribute(null, DatabaseKDBXXML.AttrId, index.toString())
|
||||
if (binary.getSize() > 0) {
|
||||
if (binary.isCompressed) {
|
||||
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
|
||||
}
|
||||
try {
|
||||
// Write the XML
|
||||
binary.getInputDataStream(mDatabaseKDBX.binaryCache).use { inputStream ->
|
||||
inputStream.readAllBytes { buffer ->
|
||||
xml.text(String(Base64.encode(buffer, BASE_64_FLAG)))
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to write binary", e)
|
||||
}
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
|
||||
}
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemBinaries)
|
||||
} ?: Log.e(TAG, "Unable to retrieve cipher key to write binaries")
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemBinary)
|
||||
}
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemBinaries)
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
@@ -699,39 +695,37 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeCustomIconList() {
|
||||
mDatabaseKDBX.loadedCipherKey?.let { cipherKey ->
|
||||
var firstElement = true
|
||||
mDatabaseKDBX.iconsManager.doForEachCustomIcon { iconCustom, binary ->
|
||||
if (binary.dataExists()) {
|
||||
// Write the parent tag
|
||||
if (firstElement) {
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons)
|
||||
firstElement = false
|
||||
}
|
||||
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem)
|
||||
|
||||
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, iconCustom.uuid)
|
||||
var customImageData = ByteArray(0)
|
||||
try {
|
||||
binary.getInputDataStream(cipherKey).use { inputStream ->
|
||||
customImageData = inputStream.readBytes()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to write custom icon", e)
|
||||
} finally {
|
||||
writeObject(DatabaseKDBXXML.ElemCustomIconItemData,
|
||||
String(Base64.encode(customImageData, BASE_64_FLAG)))
|
||||
}
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
|
||||
var firstElement = true
|
||||
mDatabaseKDBX.iconsManager.doForEachCustomIcon { iconCustom, binary ->
|
||||
if (binary.dataExists()) {
|
||||
// Write the parent tag
|
||||
if (firstElement) {
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons)
|
||||
firstElement = false
|
||||
}
|
||||
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem)
|
||||
|
||||
writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, iconCustom.uuid)
|
||||
var customImageData = ByteArray(0)
|
||||
try {
|
||||
binary.getInputDataStream(mDatabaseKDBX.binaryCache).use { inputStream ->
|
||||
customImageData = inputStream.readBytes()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to write custom icon", e)
|
||||
} finally {
|
||||
writeObject(DatabaseKDBXXML.ElemCustomIconItemData,
|
||||
String(Base64.encode(customImageData, BASE_64_FLAG)))
|
||||
}
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
|
||||
}
|
||||
// Close the parent tag
|
||||
if (!firstElement) {
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemCustomIcons)
|
||||
}
|
||||
} ?: Log.e(TAG, "Unable to retrieve cipher key to write custom icons")
|
||||
}
|
||||
// Close the parent tag
|
||||
if (!firstElement) {
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemCustomIcons)
|
||||
}
|
||||
}
|
||||
|
||||
private fun safeXmlString(text: String): String {
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.file.output
|
||||
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||
@@ -34,13 +32,13 @@ import java.nio.charset.Charset
|
||||
/**
|
||||
* Output the GroupKDB to the stream
|
||||
*/
|
||||
class EntryOutputKDB(private val mEntry: EntryKDB,
|
||||
private val mOutputStream: OutputStream,
|
||||
private val mCipherKey: Database.LoadedKey?) {
|
||||
class EntryOutputKDB(private val mDatabase: DatabaseKDB,
|
||||
private val mEntry: EntryKDB,
|
||||
private val mOutputStream: OutputStream) {
|
||||
|
||||
//NOTE: Need be to careful about using ints. The actual type written to file is a unsigned int
|
||||
@Throws(DatabaseOutputException::class)
|
||||
fun output(database: DatabaseKDB) {
|
||||
fun output() {
|
||||
try {
|
||||
// UUID
|
||||
mOutputStream.write(UUID_FIELD_TYPE)
|
||||
@@ -95,22 +93,20 @@ class EntryOutputKDB(private val mEntry: EntryKDB,
|
||||
StringDatabaseKDBUtils.writeStringToStream(mOutputStream, mEntry.binaryDescription)
|
||||
|
||||
// Binary
|
||||
mCipherKey?.let { cipherKey ->
|
||||
mOutputStream.write(BINARY_DATA_FIELD_TYPE)
|
||||
val binaryData = mEntry.getBinary(database.binaryPool)
|
||||
val binaryDataLength = binaryData?.getSize() ?: 0L
|
||||
// Write data length
|
||||
mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromKotlinLong(binaryDataLength)))
|
||||
// Write data
|
||||
if (binaryDataLength > 0) {
|
||||
binaryData?.getInputDataStream(cipherKey).use { inputStream ->
|
||||
inputStream?.readAllBytes { buffer ->
|
||||
mOutputStream.write(buffer)
|
||||
}
|
||||
inputStream?.close()
|
||||
mOutputStream.write(BINARY_DATA_FIELD_TYPE)
|
||||
val binaryData = mEntry.getBinary(mDatabase.binaryPool)
|
||||
val binaryDataLength = binaryData?.getSize() ?: 0L
|
||||
// Write data length
|
||||
mOutputStream.write(uIntTo4Bytes(UnsignedInt.fromKotlinLong(binaryDataLength)))
|
||||
// Write data
|
||||
if (binaryDataLength > 0) {
|
||||
binaryData?.getInputDataStream(mDatabase.binaryCache).use { inputStream ->
|
||||
inputStream?.readAllBytes { buffer ->
|
||||
mOutputStream.write(buffer)
|
||||
}
|
||||
inputStream?.close()
|
||||
}
|
||||
} ?: Log.e(TAG, "Unable to retrieve cipher key to write entry binary")
|
||||
}
|
||||
|
||||
// End
|
||||
mOutputStream.write(END_FIELD_TYPE)
|
||||
|
||||
@@ -33,8 +33,8 @@ import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.core.widget.ImageViewCompat
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryData
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryCache
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageDraw
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -48,7 +48,7 @@ import kotlin.collections.HashMap
|
||||
/**
|
||||
* Factory class who build database icons dynamically, can assign an icon of IconPack, or a custom icon to an ImageView with a tint
|
||||
*/
|
||||
class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedKey?,
|
||||
class IconDrawableFactory(private val retrieveBinaryCache : () -> BinaryCache?,
|
||||
private val retrieveCustomIconBinary : (iconId: UUID) -> BinaryData?) {
|
||||
|
||||
/** customIconMap
|
||||
@@ -87,13 +87,13 @@ class IconDrawableFactory(private val retrieveCipherKey : () -> Database.LoadedK
|
||||
*/
|
||||
private fun getIconDrawable(resources: Resources, icon: IconImageCustom, iconCustomBinary: BinaryData?): Drawable? {
|
||||
val patternIcon = PatternIcon(resources)
|
||||
val cipherKey = retrieveCipherKey()
|
||||
if (cipherKey != null) {
|
||||
val binaryManager = retrieveBinaryCache()
|
||||
if (binaryManager != null) {
|
||||
val draw: Drawable? = customIconMap[icon.uuid]?.get()
|
||||
if (draw == null) {
|
||||
iconCustomBinary?.let { binaryFile ->
|
||||
try {
|
||||
var bitmap: Bitmap? = BitmapFactory.decodeStream(binaryFile.getInputDataStream(cipherKey))
|
||||
var bitmap: Bitmap? = BitmapFactory.decodeStream(binaryFile.getInputDataStream(binaryManager))
|
||||
bitmap?.let { bitmapIcon ->
|
||||
bitmap = resize(bitmapIcon, patternIcon)
|
||||
val createdDraw = BitmapDrawable(resources, bitmap)
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.util.Log
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.model.AttachmentState
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.model.StreamDirection
|
||||
@@ -48,6 +49,8 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
|
||||
private val mainScope = CoroutineScope(Dispatchers.Main)
|
||||
|
||||
private var mDatabase: Database? = Database.getInstance()
|
||||
|
||||
override fun retrieveChannelId(): String {
|
||||
return CHANNEL_ATTACHMENT_ID
|
||||
}
|
||||
@@ -285,11 +288,14 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
// Add action to the list on start
|
||||
attachmentNotificationList.add(attachmentNotification)
|
||||
|
||||
mainScope.launch {
|
||||
AttachmentFileAction(attachmentNotification,
|
||||
contentResolver).apply {
|
||||
listener = attachmentFileActionListener
|
||||
}.executeAction()
|
||||
mDatabase?.let { database ->
|
||||
mainScope.launch {
|
||||
AttachmentFileAction(attachmentNotification,
|
||||
database,
|
||||
contentResolver).apply {
|
||||
listener = attachmentFileActionListener
|
||||
}.executeAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
@@ -313,6 +319,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
|
||||
private class AttachmentFileAction(
|
||||
private val attachmentNotification: AttachmentNotification,
|
||||
private val database: Database,
|
||||
private val contentResolver: ContentResolver) {
|
||||
|
||||
private val updateMinFrequency = 1000
|
||||
@@ -345,6 +352,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
when (streamDirection) {
|
||||
StreamDirection.UPLOAD -> {
|
||||
BinaryDatabaseManager.uploadToDatabase(
|
||||
database,
|
||||
attachmentNotification.uri,
|
||||
attachment.binaryData,
|
||||
contentResolver,
|
||||
@@ -358,6 +366,7 @@ class AttachmentFileNotificationService: LockNotificationService() {
|
||||
}
|
||||
StreamDirection.DOWNLOAD -> {
|
||||
BinaryDatabaseManager.downloadFromDatabase(
|
||||
database,
|
||||
attachmentNotification.uri,
|
||||
attachment.binaryData,
|
||||
contentResolver,
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryData
|
||||
import com.kunzisoft.keepass.database.element.database.BinaryCache
|
||||
import com.kunzisoft.keepass.stream.readAllBytes
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import kotlinx.coroutines.*
|
||||
@@ -21,41 +22,42 @@ import kotlin.math.pow
|
||||
|
||||
object BinaryDatabaseManager {
|
||||
|
||||
fun downloadFromDatabase(attachmentToUploadUri: Uri,
|
||||
fun downloadFromDatabase(database: Database,
|
||||
attachmentToUploadUri: Uri,
|
||||
binaryData: BinaryData,
|
||||
contentResolver: ContentResolver,
|
||||
update: ((percent: Int)->Unit)? = null,
|
||||
canceled: ()-> Boolean = { false },
|
||||
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
|
||||
UriUtil.getUriOutputStream(contentResolver, attachmentToUploadUri)?.use { outputStream ->
|
||||
downloadFromDatabase(outputStream, binaryData, update, canceled, bufferSize)
|
||||
downloadFromDatabase(database.binaryCache, outputStream, binaryData, update, canceled, bufferSize)
|
||||
}
|
||||
}
|
||||
|
||||
private fun downloadFromDatabase(outputStream: OutputStream,
|
||||
private fun downloadFromDatabase(binaryCache: BinaryCache,
|
||||
outputStream: OutputStream,
|
||||
binaryData: BinaryData,
|
||||
update: ((percent: Int)->Unit)? = null,
|
||||
canceled: ()-> Boolean = { false },
|
||||
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
|
||||
val fileSize = binaryData.getSize()
|
||||
var dataDownloaded = 0L
|
||||
Database.getInstance().loadedCipherKey?.let { binaryCipherKey ->
|
||||
binaryData.getUnGzipInputDataStream(binaryCipherKey).use { inputStream ->
|
||||
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
|
||||
outputStream.write(buffer)
|
||||
dataDownloaded += buffer.size
|
||||
try {
|
||||
val percentDownload = (100 * dataDownloaded / fileSize).toInt()
|
||||
update?.invoke(percentDownload)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to call update callback during download", e)
|
||||
}
|
||||
binaryData.getUnGzipInputDataStream(binaryCache).use { inputStream ->
|
||||
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
|
||||
outputStream.write(buffer)
|
||||
dataDownloaded += buffer.size
|
||||
try {
|
||||
val percentDownload = (100 * dataDownloaded / fileSize).toInt()
|
||||
update?.invoke(percentDownload)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to call update callback during download", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun uploadToDatabase(attachmentFromDownloadUri: Uri,
|
||||
fun uploadToDatabase(database: Database,
|
||||
attachmentFromDownloadUri: Uri,
|
||||
binaryData: BinaryData,
|
||||
contentResolver: ContentResolver,
|
||||
update: ((percent: Int)->Unit)? = null,
|
||||
@@ -63,34 +65,34 @@ object BinaryDatabaseManager {
|
||||
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
|
||||
val fileSize = contentResolver.openFileDescriptor(attachmentFromDownloadUri, "r")?.statSize ?: 0
|
||||
UriUtil.getUriInputStream(contentResolver, attachmentFromDownloadUri)?.use { inputStream ->
|
||||
uploadToDatabase(inputStream, fileSize, binaryData, update, canceled, bufferSize)
|
||||
uploadToDatabase(database.binaryCache, inputStream, fileSize, binaryData, update, canceled, bufferSize)
|
||||
}
|
||||
}
|
||||
|
||||
private fun uploadToDatabase(inputStream: InputStream,
|
||||
private fun uploadToDatabase(binaryCache: BinaryCache,
|
||||
inputStream: InputStream,
|
||||
fileSize: Long,
|
||||
binaryData: BinaryData,
|
||||
update: ((percent: Int)->Unit)? = null,
|
||||
canceled: ()-> Boolean = { false },
|
||||
bufferSize: Int = DEFAULT_BUFFER_SIZE) {
|
||||
var dataUploaded = 0L
|
||||
Database.getInstance().loadedCipherKey?.let { binaryCipherKey ->
|
||||
binaryData.getGzipOutputDataStream(binaryCipherKey).use { outputStream ->
|
||||
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
|
||||
outputStream.write(buffer)
|
||||
dataUploaded += buffer.size
|
||||
try {
|
||||
val percentDownload = (100 * dataUploaded / fileSize).toInt()
|
||||
update?.invoke(percentDownload)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to call update callback during upload", e)
|
||||
}
|
||||
binaryData.getGzipOutputDataStream(binaryCache).use { outputStream ->
|
||||
inputStream.readAllBytes(bufferSize, canceled) { buffer ->
|
||||
outputStream.write(buffer)
|
||||
dataUploaded += buffer.size
|
||||
try {
|
||||
val percentDownload = (100 * dataUploaded / fileSize).toInt()
|
||||
update?.invoke(percentDownload)
|
||||
} catch (e: Exception) {
|
||||
Log.w(TAG, "Unable to call update callback during upload", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resizeBitmapAndStoreDataInBinaryFile(contentResolver: ContentResolver,
|
||||
database: Database,
|
||||
bitmapUri: Uri?,
|
||||
binaryData: BinaryData?) {
|
||||
try {
|
||||
@@ -103,6 +105,7 @@ object BinaryDatabaseManager {
|
||||
val bitmapData: ByteArray = byteArrayOutputStream.toByteArray()
|
||||
val byteArrayInputStream = ByteArrayInputStream(bitmapData)
|
||||
uploadToDatabase(
|
||||
database.binaryCache,
|
||||
byteArrayInputStream,
|
||||
bitmapData.size.toLong(),
|
||||
binaryData
|
||||
@@ -118,7 +121,6 @@ object BinaryDatabaseManager {
|
||||
|
||||
/**
|
||||
* reduces the size of the image
|
||||
* @param image
|
||||
* @param maxSize
|
||||
* @return
|
||||
*/
|
||||
@@ -136,20 +138,18 @@ object BinaryDatabaseManager {
|
||||
return Bitmap.createScaledBitmap(this, width, height, true)
|
||||
}
|
||||
|
||||
fun loadBitmap(binaryData: BinaryData,
|
||||
binaryCipherKey: Database.LoadedKey?,
|
||||
fun loadBitmap(database: Database,
|
||||
binaryData: BinaryData,
|
||||
maxWidth: Int,
|
||||
actionOnFinish: (Bitmap?) -> Unit) {
|
||||
CoroutineScope(Dispatchers.Main).launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
val asyncResult: Deferred<Bitmap?> = async {
|
||||
runCatching {
|
||||
binaryCipherKey?.let { binaryKey ->
|
||||
val bitmap: Bitmap? = decodeSampledBitmap(binaryData,
|
||||
binaryKey,
|
||||
maxWidth)
|
||||
bitmap
|
||||
}
|
||||
val bitmap: Bitmap? = decodeSampledBitmap(binaryData,
|
||||
database.binaryCache,
|
||||
maxWidth)
|
||||
bitmap
|
||||
}.getOrNull()
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
@@ -160,13 +160,13 @@ object BinaryDatabaseManager {
|
||||
}
|
||||
|
||||
private fun decodeSampledBitmap(binaryData: BinaryData,
|
||||
binaryCipherKey: Database.LoadedKey,
|
||||
binaryCache: BinaryCache,
|
||||
maxWidth: Int): Bitmap? {
|
||||
// First decode with inJustDecodeBounds=true to check dimensions
|
||||
return BitmapFactory.Options().run {
|
||||
try {
|
||||
inJustDecodeBounds = true
|
||||
binaryData.getUnGzipInputDataStream(binaryCipherKey).use {
|
||||
binaryData.getUnGzipInputDataStream(binaryCache).use {
|
||||
BitmapFactory.decodeStream(it, null, this)
|
||||
}
|
||||
// Calculate inSampleSize
|
||||
@@ -178,7 +178,7 @@ object BinaryDatabaseManager {
|
||||
|
||||
// Decode bitmap with inSampleSize set
|
||||
inJustDecodeBounds = false
|
||||
binaryData.getUnGzipInputDataStream(binaryCipherKey).use {
|
||||
binaryData.getUnGzipInputDataStream(binaryCache).use {
|
||||
BitmapFactory.decodeStream(it, null, this)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -322,8 +322,8 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
|
||||
* -------------
|
||||
*/
|
||||
|
||||
fun setAttachmentCipherKey(cipherKey: Database.LoadedKey?) {
|
||||
attachmentsAdapter.binaryCipherKey = cipherKey
|
||||
fun setAttachmentCipherKey(database: Database?) {
|
||||
attachmentsAdapter.database = database
|
||||
}
|
||||
|
||||
private fun showAttachments(show: Boolean) {
|
||||
|
||||
Reference in New Issue
Block a user