Merge branch 'release/2.5.0.0beta26'

This commit is contained in:
J-Jamet
2020-02-07 21:45:40 +01:00
434 changed files with 7657 additions and 4346 deletions

View File

@@ -1,3 +1,16 @@
KeepassDX (2.5.0.0beta26)
* Download attachments
* Change file size string format
* Prevent screenshot for all screen
* Auto performed "Go" key in Magikeyboard
* Restore and delete entry history
* Setting to hide expired entries
* New Black theme
* Fix crash when clearing clipboard
* Fix attachments compressions
* Fix dates
* Fix UUID message for Database v1
KeepassDX (2.5.0.0beta25) KeepassDX (2.5.0.0beta25)
* Setting for Recycle Bin * Setting for Recycle Bin
* Fix Recycle bin issues * Fix Recycle bin issues

View File

@@ -11,7 +11,7 @@
* **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC...) * **Compatible** with the majority of alternative programs (KeePass, KeePassX, KeePassXC...)
* Allows **fast copy** of fields and opening of URI / URL * Allows **fast copy** of fields and opening of URI / URL
* **Biometric recognition** for fast unlocking *(Fingerprint / Face unlock / ...)* * **Biometric recognition** for fast unlocking *(Fingerprint / Face unlock / ...)*
* **One-Time Password** management *(HOTP / TOTP)* * **One-Time Password** management *(HOTP / TOTP)* for Two-factor authentication (2FA)
* Material design with **themes** * Material design with **themes**
* **AutoFill** and Integration * **AutoFill** and Integration
* Field filling **keyboard** * Field filling **keyboard**
@@ -65,7 +65,7 @@ Other questions? You can read the [F.A.Q.](https://github.com/Kunzisoft/KeePassD
## License ## License
Copyright (c) 2019 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com). Copyright (c) 2020 Jeremy Jamet / [Kunzisoft](https://www.kunzisoft.com).
This file is part of KeePassDX. This file is part of KeePassDX.

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 14 minSdkVersion 14
targetSdkVersion 28 targetSdkVersion 28
versionCode = 25 versionCode = 26
versionName = "2.5.0.0beta25" versionName = "2.5.0.0beta26"
multiDexEnabled true multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests" testApplicationId = "com.kunzisoft.keepass.tests"

View File

@@ -1,38 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.tests
import junit.framework.TestCase
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import org.junit.Assert
class PwDateTest : TestCase() {
fun testDate() {
val jDate = DateInstant(System.currentTimeMillis())
val intermediate = DateInstant(jDate)
val cDate = DatabaseInputOutputUtils.readCDate(DatabaseInputOutputUtils.writeCDate(intermediate.date)!!, 0)
Assert.assertTrue("jDate and intermediate not equal", jDate == intermediate)
Assert.assertTrue("jDate $jDate and cDate $cDate not equal", cDate == jDate)
}
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
@@ -19,19 +19,15 @@
*/ */
package com.kunzisoft.keepass.tests package com.kunzisoft.keepass.tests
import org.junit.Assert.assertArrayEquals import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
import java.io.ByteArrayOutputStream import com.kunzisoft.keepass.stream.*
import java.util.Calendar
import java.util.Random
import junit.framework.TestCase import junit.framework.TestCase
import org.junit.Assert.assertArrayEquals
import java.io.ByteArrayOutputStream
import java.util.*
import com.kunzisoft.keepass.stream.LEDataInputStream class StringDatabaseKDBUtilsTest : TestCase() {
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
class DatabaseInputOutputUtilsTest : TestCase() {
fun testReadWriteLongZero() { fun testReadWriteLongZero() {
testReadWriteLong(0.toByte()) testReadWriteLong(0.toByte())
@@ -55,15 +51,9 @@ class DatabaseInputOutputUtilsTest : TestCase() {
private fun testReadWriteLong(value: Byte) { private fun testReadWriteLong(value: Byte) {
val orig = ByteArray(8) val orig = ByteArray(8)
val dest = ByteArray(8) setArray(orig, value, 8)
setArray(orig, value, 0, 8)
val one = LEDataInputStream.readLong(orig, 0)
LEDataOutputStream.writeLong(one, dest, 0)
assertArrayEquals(orig, dest)
assertArrayEquals(orig, longTo8Bytes(bytes64ToLong(orig)))
} }
fun testReadWriteIntZero() { fun testReadWriteIntZero() {
@@ -80,24 +70,22 @@ class DatabaseInputOutputUtilsTest : TestCase() {
private fun testReadWriteInt(value: Byte) { private fun testReadWriteInt(value: Byte) {
val orig = ByteArray(4) val orig = ByteArray(4)
val dest = ByteArray(4)
for (i in 0..3) { for (i in 0..3) {
orig[i] = 0 orig[i] = 0
} }
setArray(orig, value, 0, 4) setArray(orig, value, 4)
val one = LEDataInputStream.readInt(orig, 0) val one = bytes4ToInt(orig)
val dest = intTo4Bytes(one)
LEDataOutputStream.writeInt(one, dest, 0)
assertArrayEquals(orig, dest) assertArrayEquals(orig, dest)
} }
private fun setArray(buf: ByteArray, value: Byte, offset: Int, size: Int) { private fun setArray(buf: ByteArray, value: Byte, size: Int) {
for (i in offset until offset + size) { for (i in 0 until size) {
buf[i] = value buf[i] = value
} }
} }
@@ -108,11 +96,10 @@ class DatabaseInputOutputUtilsTest : TestCase() {
orig[0] = 0 orig[0] = 0
orig[1] = 1 orig[1] = 1
val one = LEDataInputStream.readUShort(orig, 0) val one = bytes2ToUShort(orig)
val dest = LEDataOutputStream.writeUShortBuf(one) val dest = uShortTo2Bytes(one)
assertArrayEquals(orig, dest) assertArrayEquals(orig, dest)
} }
fun testReadWriteShortMin() { fun testReadWriteShortMin() {
@@ -125,15 +112,12 @@ class DatabaseInputOutputUtilsTest : TestCase() {
private fun testReadWriteShort(value: Byte) { private fun testReadWriteShort(value: Byte) {
val orig = ByteArray(2) val orig = ByteArray(2)
val dest = ByteArray(2) setArray(orig, value, 2)
setArray(orig, value, 0, 2) val one = bytes2ToUShort(orig)
val dest = uShortTo2Bytes(one)
val one = LEDataInputStream.readUShort(orig, 0)
LEDataOutputStream.writeUShort(one, dest, 0)
assertArrayEquals(orig, dest) assertArrayEquals(orig, dest)
} }
fun testReadWriteByteZero() { fun testReadWriteByteZero() {
@@ -149,16 +133,8 @@ class DatabaseInputOutputUtilsTest : TestCase() {
} }
private fun testReadWriteByte(value: Byte) { private fun testReadWriteByte(value: Byte) {
val orig = ByteArray(1) val dest: Byte = uIntToByte(byteToUInt(value))
val dest = ByteArray(1) assert(value == dest)
setArray(orig, value, 0, 1)
val one = DatabaseInputOutputUtils.readUByte(orig, 0)
DatabaseInputOutputUtils.writeUByte(one, dest, 0)
assertArrayEquals(orig, dest)
} }
fun testDate() { fun testDate() {
@@ -168,27 +144,33 @@ class DatabaseInputOutputUtilsTest : TestCase() {
expected.set(2008, 1, 2, 3, 4, 5) expected.set(2008, 1, 2, 3, 4, 5)
val actual = Calendar.getInstance() val actual = Calendar.getInstance()
DatabaseInputOutputUtils.writeCDate(expected.time, cal)?.let { buf -> dateTo5Bytes(expected.time, cal)?.let { buf ->
actual.time = DatabaseInputOutputUtils.readCDate(buf, 0, cal).date actual.time = bytes5ToDate(buf, cal).date
} }
val jDate = DateInstant(System.currentTimeMillis())
val intermediate = DateInstant(jDate)
val cDate = bytes5ToDate(dateTo5Bytes(intermediate.date)!!)
assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR)) assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR))
assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH)) assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH))
assertEquals("Day mismatch: ", 2, actual.get(Calendar.DAY_OF_MONTH)) assertEquals("Day mismatch: ", 2, actual.get(Calendar.DAY_OF_MONTH))
assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY)) assertEquals("Hour mismatch: ", 3, actual.get(Calendar.HOUR_OF_DAY))
assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE)) assertEquals("Minute mismatch: ", 4, actual.get(Calendar.MINUTE))
assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND)) assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND))
assertTrue("jDate and intermediate not equal", jDate == intermediate)
assertTrue("jDate $jDate and cDate $cDate not equal", cDate == jDate)
} }
fun testUUID() { fun testUUID() {
val bUUID = ByteArray(16) val bUUID = ByteArray(16)
Random().nextBytes(bUUID) Random().nextBytes(bUUID)
val uuid = DatabaseInputOutputUtils.bytesToUuid(bUUID) val uuid = bytes16ToUuid(bUUID)
val eUUID = DatabaseInputOutputUtils.uuidToBytes(uuid) val eUUID = uuidTo16Bytes(uuid)
val lUUID = LEDataInputStream.readUuid(bUUID, 0) val lUUID = bytes16ToUuid(bUUID)
val leUUID = DatabaseInputOutputUtils.uuidToBytes(lUUID) val leUUID = uuidTo16Bytes(lUUID)
assertArrayEquals("UUID match failed", bUUID, eUUID) assertArrayEquals("UUID match failed", bUUID, eUUID)
assertArrayEquals("UUID match failed", bUUID, leUUID) assertArrayEquals("UUID match failed", bUUID, leUUID)
@@ -202,8 +184,8 @@ class DatabaseInputOutputUtilsTest : TestCase() {
} }
val bos = ByteArrayOutputStream() val bos = ByteArrayOutputStream()
val leos = LEDataOutputStream(bos) val leos = LittleEndianDataOutputStream(bos)
leos.writeLong(DatabaseInputOutputUtils.ULONG_MAX_VALUE) leos.writeLong(ULONG_MAX_VALUE)
leos.close() leos.close()
val uLongMax = bos.toByteArray() val uLongMax = bos.toByteArray()

View File

@@ -39,9 +39,8 @@ import junit.framework.TestCase
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.engine.AesEngine import com.kunzisoft.keepass.crypto.engine.AesEngine
import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.stream.BetterCipherInputStream import com.kunzisoft.keepass.stream.BetterCipherInputStream
import com.kunzisoft.keepass.stream.LEDataInputStream import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
class CipherTest : TestCase() { class CipherTest : TestCase() {
private val rand = Random() private val rand = Random()
@@ -93,7 +92,7 @@ class CipherTest : TestCase() {
val bis = ByteArrayInputStream(secrettext) val bis = ByteArrayInputStream(secrettext)
val cis = BetterCipherInputStream(bis, decrypt) val cis = BetterCipherInputStream(bis, decrypt)
val lis = LEDataInputStream(cis) val lis = LittleEndianDataInputStream(cis)
val decrypttext = lis.readBytes(MESSAGE_LENGTH) val decrypttext = lis.readBytes(MESSAGE_LENGTH)

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.kunzisoft.keepass" package="com.kunzisoft.keepass"
android:installLocation="auto"> android:installLocation="auto">
<supports-screens <supports-screens
@@ -9,8 +10,8 @@
android:anyDensity="true" /> android:anyDensity="true" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.VIBRATE"/> <uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<application <application
android:label="@string/app_name" android:label="@string/app_name"
@@ -21,7 +22,9 @@
android:fullBackupContent="@xml/backup" android:fullBackupContent="@xml/backup"
android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent" android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent"
android:largeHeap="true" android:largeHeap="true"
android:theme="@style/KeepassDXStyle.Night"> android:resizeableActivity="true"
android:theme="@style/KeepassDXStyle.Night"
tools:targetApi="n">
<!-- TODO backup API Key --> <!-- TODO backup API Key -->
<meta-data <meta-data
android:name="com.google.android.backup.api_key" android:name="com.google.android.backup.api_key"
@@ -150,6 +153,10 @@
android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService" android:name="com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService"
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />
<service
android:name=".notifications.AttachmentFileNotificationService"
android:enabled="true"
android:exported="false" />
<service <service
android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService" android:name="com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService"
android:enabled="true" android:enabled="true"

View File

@@ -22,6 +22,7 @@ import android.app.Activity
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.Log import android.util.Log
@@ -36,24 +37,33 @@ import androidx.appcompat.widget.Toolbar
import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.CollapsingToolbarLayout
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingHideActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.education.EntryActivityEducation import com.kunzisoft.keepass.education.EntryActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment
import com.kunzisoft.keepass.notifications.AttachmentFileNotificationService
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsAutofillActivity import com.kunzisoft.keepass.settings.SettingsAutofillActivity
import com.kunzisoft.keepass.tasks.AttachmentFileBinderManager
import com.kunzisoft.keepass.timeout.ClipboardHelper import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.utils.createDocument
import com.kunzisoft.keepass.utils.onCreateDocumentResult
import com.kunzisoft.keepass.view.EntryContentsView import com.kunzisoft.keepass.view.EntryContentsView
import java.util.* import java.util.*
import kotlin.collections.HashMap
class EntryActivity : LockingHideActivity() { class EntryActivity : LockingActivity() {
private var collapsingToolbarLayout: CollapsingToolbarLayout? = null private var collapsingToolbarLayout: CollapsingToolbarLayout? = null
private var titleIconView: ImageView? = null private var titleIconView: ImageView? = null
@@ -65,10 +75,16 @@ class EntryActivity : LockingHideActivity() {
private var mDatabase: Database? = null private var mDatabase: Database? = null
private var mEntry: Entry? = null private var mEntry: Entry? = null
private var mIsHistory: Boolean = false private var mIsHistory: Boolean = false
private var mEntryLastVersion: Entry? = null
private var mEntryHistoryPosition: Int = -1
private var mShowPassword: Boolean = false private var mShowPassword: Boolean = false
private var mAttachmentFileBinderManager: AttachmentFileBinderManager? = null
private var mAttachmentsToDownload: HashMap<Int, EntryAttachment> = HashMap()
private var clipboardHelper: ClipboardHelper? = null private var clipboardHelper: ClipboardHelper? = null
private var firstLaunchOfActivity: Boolean = false private var firstLaunchOfActivity: Boolean = false
@@ -108,6 +124,21 @@ class EntryActivity : LockingHideActivity() {
// Init the clipboard helper // Init the clipboard helper
clipboardHelper = ClipboardHelper(this) clipboardHelper = ClipboardHelper(this)
firstLaunchOfActivity = true firstLaunchOfActivity = true
// Init attachment service binder manager
mAttachmentFileBinderManager = AttachmentFileBinderManager(this)
mProgressDialogThread?.onActionFinish = { actionTask, result ->
when (actionTask) {
ACTION_DATABASE_RESTORE_ENTRY_HISTORY,
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> {
// Close the current activity after an history action
if (result.isSuccess)
finish()
}
}
// TODO Visual error for entry history
}
} }
override fun onResume() { override fun onResume() {
@@ -117,11 +148,13 @@ class EntryActivity : LockingHideActivity() {
try { try {
val keyEntry: NodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY) val keyEntry: NodeId<UUID> = intent.getParcelableExtra(KEY_ENTRY)
mEntry = mDatabase?.getEntryById(keyEntry) mEntry = mDatabase?.getEntryById(keyEntry)
mEntryLastVersion = mEntry
} catch (e: ClassCastException) { } catch (e: ClassCastException) {
Log.e(TAG, "Unable to retrieve the entry key") Log.e(TAG, "Unable to retrieve the entry key")
} }
val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, -1) val historyPosition = intent.getIntExtra(KEY_ENTRY_HISTORY_POSITION, mEntryHistoryPosition)
mEntryHistoryPosition = historyPosition
if (historyPosition >= 0) { if (historyPosition >= 0) {
mIsHistory = true mIsHistory = true
mEntry = mEntry?.getHistory()?.get(historyPosition) mEntry = mEntry?.getHistory()?.get(historyPosition)
@@ -155,9 +188,24 @@ class EntryActivity : LockingHideActivity() {
} }
} }
mAttachmentFileBinderManager?.apply {
registerProgressTask()
onActionTaskListener = object : AttachmentFileNotificationService.ActionTaskListener {
override fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment) {
entryContentsView?.updateAttachmentDownloadProgress(attachment)
}
}
}
firstLaunchOfActivity = false firstLaunchOfActivity = false
} }
override fun onPause() {
mAttachmentFileBinderManager?.unregisterProgressTask()
super.onPause()
}
private fun fillEntryDataInContentsView(entry: Entry) { private fun fillEntryDataInContentsView(entry: Entry) {
val database = Database.getInstance() val database = Database.getInstance()
@@ -265,6 +313,27 @@ class EntryActivity : LockingHideActivity() {
} }
entryContentsView?.setHiddenPasswordStyle(!mShowPassword) entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
// Manage attachments
val attachments = entry.getAttachments()
val showAttachmentsView = attachments.isNotEmpty()
entryContentsView?.showAttachments(showAttachmentsView)
if (showAttachmentsView) {
entryContentsView?.assignAttachments(attachments)
entryContentsView?.onAttachmentClick { attachmentItem, _ ->
when (attachmentItem.downloadState) {
AttachmentState.NULL, AttachmentState.ERROR, AttachmentState.COMPLETE -> {
createDocument(this, attachmentItem.name)?.let { requestCode ->
mAttachmentsToDownload[requestCode] = attachmentItem
}
}
else -> {
// TODO Stop download
}
}
}
}
entryContentsView?.refreshAttachments()
// Assign dates // Assign dates
entryContentsView?.assignCreationDate(entry.creationTime) entryContentsView?.assignCreationDate(entry.creationTime)
entryContentsView?.assignModificationDate(entry.lastModificationTime) entryContentsView?.assignModificationDate(entry.lastModificationTime)
@@ -276,9 +345,6 @@ class EntryActivity : LockingHideActivity() {
entryContentsView?.assignExpiresDate(getString(R.string.never)) entryContentsView?.assignExpiresDate(getString(R.string.never))
} }
// Assign special data
entryContentsView?.assignUUID(entry.nodeId.id)
// Manage history // Manage history
historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE historyView?.visibility = if (mIsHistory) View.VISIBLE else View.GONE
if (mIsHistory) { if (mIsHistory) {
@@ -287,23 +353,26 @@ class EntryActivity : LockingHideActivity() {
taColorAccent.recycle() taColorAccent.recycle()
} }
val entryHistory = entry.getHistory() val entryHistory = entry.getHistory()
// isMainEntry = not an history // TODO isMainEntry = not an history
val showHistoryView = entryHistory.isNotEmpty() val showHistoryView = entryHistory.isNotEmpty()
entryContentsView?.showHistory(showHistoryView) entryContentsView?.showHistory(showHistoryView)
if (showHistoryView) { if (showHistoryView) {
entryContentsView?.assignHistory(entryHistory) entryContentsView?.assignHistory(entryHistory)
entryContentsView?.onHistoryClick { historyItem, position -> entryContentsView?.onHistoryClick { historyItem, position ->
launch(this, historyItem, true, position) launch(this, historyItem, mReadOnly, position)
} }
} }
entryContentsView?.refreshHistory()
// Assign special data
entryContentsView?.assignUUID(entry.nodeId.id)
database.stopManageEntry(entry) database.stopManageEntry(entry)
entryContentsView?.refreshHistory()
} }
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
when (requestCode) { when (requestCode) {
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE ->
// Not directly get the entry from intent data but from database // Not directly get the entry from intent data but from database
@@ -311,6 +380,15 @@ class EntryActivity : LockingHideActivity() {
fillEntryDataInContentsView(it) fillEntryDataInContentsView(it)
} }
} }
onCreateDocumentResult(requestCode, resultCode, data) { createdFileUri ->
if (createdFileUri != null) {
mAttachmentsToDownload[requestCode]?.let { attachmentToDownload ->
mAttachmentFileBinderManager
?.startDownloadAttachment(createdFileUri, attachmentToDownload)
}
}
}
} }
private fun changeShowPasswordIcon(togglePassword: MenuItem?) { private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
@@ -330,7 +408,10 @@ class EntryActivity : LockingHideActivity() {
MenuUtil.contributionMenuInflater(inflater, menu) MenuUtil.contributionMenuInflater(inflater, menu)
inflater.inflate(R.menu.entry, menu) inflater.inflate(R.menu.entry, menu)
inflater.inflate(R.menu.database, menu) inflater.inflate(R.menu.database, menu)
if (mReadOnly) { if (mIsHistory && !mReadOnly) {
inflater.inflate(R.menu.entry_history, menu)
}
if (mIsHistory || mReadOnly) {
menu.findItem(R.id.menu_save_database)?.isVisible = false menu.findItem(R.id.menu_save_database)?.isVisible = false
menu.findItem(R.id.menu_edit)?.isVisible = false menu.findItem(R.id.menu_edit)?.isVisible = false
} }
@@ -423,6 +504,22 @@ class EntryActivity : LockingHideActivity() {
UriUtil.gotoUrl(this, url) UriUtil.gotoUrl(this, url)
return true return true
} }
R.id.menu_restore_entry_history -> {
mEntryLastVersion?.let { mainEntry ->
mProgressDialogThread?.startDatabaseRestoreEntryHistory(
mainEntry,
mEntryHistoryPosition,
!mReadOnly && mAutoSaveEnable)
}
}
R.id.menu_delete_entry_history -> {
mEntryLastVersion?.let { mainEntry ->
mProgressDialogThread?.startDatabaseDeleteEntryHistory(
mainEntry,
mEntryHistoryPosition,
!mReadOnly && mAutoSaveEnable)
}
}
R.id.menu_lock -> { R.id.menu_lock -> {
lockAndExit() lockAndExit()
return true return true

View File

@@ -28,11 +28,13 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.ScrollView import android.widget.ScrollView
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout
import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment
import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.lock.LockingHideActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
@@ -47,9 +49,10 @@ import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.view.EntryEditContentsView import com.kunzisoft.keepass.view.EntryEditContentsView
import com.kunzisoft.keepass.view.asError
import java.util.* import java.util.*
class EntryEditActivity : LockingHideActivity(), class EntryEditActivity : LockingActivity(),
IconPickerDialogFragment.IconPickerListener, IconPickerDialogFragment.IconPickerListener,
GeneratePasswordDialogFragment.GeneratePasswordListener, GeneratePasswordDialogFragment.GeneratePasswordListener,
SetOTPDialogFragment.CreateOtpListener { SetOTPDialogFragment.CreateOtpListener {
@@ -64,6 +67,7 @@ class EntryEditActivity : LockingHideActivity(),
private var mIsNew: Boolean = false private var mIsNew: Boolean = false
// Views // Views
private var coordinatorLayout: CoordinatorLayout? = null
private var scrollView: ScrollView? = null private var scrollView: ScrollView? = null
private var entryEditContentsView: EntryEditContentsView? = null private var entryEditContentsView: EntryEditContentsView? = null
private var saveView: View? = null private var saveView: View? = null
@@ -81,6 +85,8 @@ class EntryEditActivity : LockingHideActivity(),
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true) supportActionBar?.setDisplayShowHomeEnabled(true)
coordinatorLayout = findViewById(R.id.entry_edit_coordinator_layout)
scrollView = findViewById(R.id.entry_edit_scroll) scrollView = findViewById(R.id.entry_edit_scroll)
scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET
@@ -182,6 +188,15 @@ class EntryEditActivity : LockingHideActivity(),
finish() finish()
} }
} }
// Show error
if (!result.isSuccess) {
result.message?.let { resultMessage ->
Snackbar.make(coordinatorLayout!!,
resultMessage,
Snackbar.LENGTH_LONG).asError().show()
}
}
} }
} }

View File

@@ -42,7 +42,6 @@ import androidx.recyclerview.widget.SimpleItemAnimator
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment
import com.kunzisoft.keepass.activities.dialogs.BrowserDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.OpenFileHelper import com.kunzisoft.keepass.activities.helpers.OpenFileHelper
import com.kunzisoft.keepass.activities.stylish.StylishActivity import com.kunzisoft.keepass.activities.stylish.StylishActivity
@@ -53,8 +52,7 @@ import com.kunzisoft.keepass.database.action.ProgressDialogThread
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation import com.kunzisoft.keepass.education.FileDatabaseSelectActivityEducation
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
import com.kunzisoft.keepass.utils.MenuUtil import com.kunzisoft.keepass.utils.*
import com.kunzisoft.keepass.utils.UriUtil
import com.kunzisoft.keepass.view.asError import com.kunzisoft.keepass.view.asError
import kotlinx.android.synthetic.main.activity_file_selection.* import kotlinx.android.synthetic.main.activity_file_selection.*
import java.io.FileNotFoundException import java.io.FileNotFoundException
@@ -92,17 +90,14 @@ class FileDatabaseSelectActivity : StylishActivity(),
// Create button // Create button
createButtonView = findViewById(R.id.create_database_button) createButtonView = findViewById(R.id.create_database_button)
if (Intent(Intent.ACTION_CREATE_DOCUMENT).apply { if (allowCreateDocumentByStorageAccessFramework(packageManager)) {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
}.resolveActivity(packageManager) == null) {
// No Activity found that can handle this intent.
createButtonView?.visibility = View.GONE
}
else{
// There is an activity which can handle this intent. // There is an activity which can handle this intent.
createButtonView?.visibility = View.VISIBLE createButtonView?.visibility = View.VISIBLE
} }
else{
// No Activity found that can handle this intent.
createButtonView?.visibility = View.GONE
}
createButtonView?.setOnClickListener { createNewFile() } createButtonView?.setOnClickListener { createNewFile() }
@@ -182,18 +177,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
*/ */
@SuppressLint("InlinedApi") @SuppressLint("InlinedApi")
private fun createNewFile() { private fun createNewFile() {
try { createDocument(this, getString(R.string.database_file_name_default) +
startActivityForResult(Intent( getString(R.string.database_file_extension_default), "application/x-keepass")
Intent.ACTION_CREATE_DOCUMENT).apply {
addCategory(Intent.CATEGORY_OPENABLE)
type = "application/x-keepass"
putExtra(Intent.EXTRA_TITLE, getString(R.string.database_file_name_default) +
getString(R.string.database_file_extension_default))
},
CREATE_FILE_REQUEST_CODE)
} catch (e: Exception) {
BrowserDialogFragment().show(supportFragmentManager, "browserDialog")
}
} }
private fun fileNoFoundAction(e: FileNotFoundException) { private fun fileNoFoundAction(e: FileNotFoundException) {
@@ -367,8 +352,8 @@ class FileDatabaseSelectActivity : StylishActivity(),
} }
// Retrieve the created URI from the file manager // Retrieve the created URI from the file manager
if (requestCode == CREATE_FILE_REQUEST_CODE && resultCode == Activity.RESULT_OK) { onCreateDocumentResult(requestCode, resultCode, data) { databaseFileCreatedUri ->
mDatabaseFileUri = data?.data mDatabaseFileUri = databaseFileCreatedUri
if (mDatabaseFileUri != null) { if (mDatabaseFileUri != null) {
AssignMasterKeyDialogFragment.getInstance(true) AssignMasterKeyDialogFragment.getInstance(true)
.show(supportFragmentManager, "passwordDialog") .show(supportFragmentManager, "passwordDialog")
@@ -427,8 +412,6 @@ class FileDatabaseSelectActivity : StylishActivity(),
private const val EXTRA_STAY = "EXTRA_STAY" private const val EXTRA_STAY = "EXTRA_STAY"
private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI" private const val EXTRA_DATABASE_URI = "EXTRA_DATABASE_URI"
private const val CREATE_FILE_REQUEST_CODE = 3853
/* /*
* ------------------------- * -------------------------
* No Standard Launch, pass by PasswordActivity * No Standard Launch, pass by PasswordActivity

View File

@@ -42,14 +42,19 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentManager
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.* import com.kunzisoft.keepass.activities.dialogs.DeleteNodesDialogFragment
import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment
import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment
import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper
import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.activities.lock.LockingActivity
import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter
import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.autofill.AutofillHelper
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
@@ -430,12 +435,8 @@ class GroupActivity : LockingActivity(),
val addGroupEnabled = !mReadOnly && !mCurrentGroupIsASearch val addGroupEnabled = !mReadOnly && !mCurrentGroupIsASearch
var addEntryEnabled = !mReadOnly && !mCurrentGroupIsASearch var addEntryEnabled = !mReadOnly && !mCurrentGroupIsASearch
mCurrentGroup?.let { mCurrentGroup?.let {
val isRoot = it == mRootGroup
if (!it.allowAddEntryIfIsRoot()) if (!it.allowAddEntryIfIsRoot())
addEntryEnabled = !isRoot && addEntryEnabled addEntryEnabled = it != mRootGroup && addEntryEnabled
if (isRoot) {
showWarnings()
}
} }
enableAddGroup(addGroupEnabled) enableAddGroup(addGroupEnabled)
enableAddEntry(addEntryEnabled) enableAddEntry(addEntryEnabled)
@@ -448,7 +449,7 @@ class GroupActivity : LockingActivity(),
private fun refreshNumberOfChildren() { private fun refreshNumberOfChildren() {
numberChildrenView?.apply { numberChildrenView?.apply {
if (PreferencesUtil.showNumberEntries(context)) { if (PreferencesUtil.showNumberEntries(context)) {
text = mCurrentGroup?.getChildEntries(true)?.size?.toString() ?: "" text = mCurrentGroup?.getChildEntries(*Group.ChildFilter.getDefaults(context))?.size?.toString() ?: ""
visibility = View.VISIBLE visibility = View.VISIBLE
} else { } else {
visibility = View.GONE visibility = View.GONE
@@ -658,7 +659,8 @@ class GroupActivity : LockingActivity(),
} }
// Menu for recycle bin // Menu for recycle bin
if (mDatabase?.isRecycleBinEnabled == true if (!mReadOnly
&& mDatabase?.isRecycleBinEnabled == true
&& mDatabase?.recycleBin == mCurrentGroup) { && mDatabase?.recycleBin == mCurrentGroup) {
inflater.inflate(R.menu.recycle_bin, menu) inflater.inflate(R.menu.recycle_bin, menu)
} }
@@ -853,14 +855,6 @@ class GroupActivity : LockingActivity(),
.iconPicked(bundle) .iconPicked(bundle)
} }
private fun showWarnings() {
if (Database.getInstance().isReadOnly) {
if (PreferencesUtil.showReadOnlyWarning(this)) {
ReadOnlyDialog().show(supportFragmentManager, "readOnlyDialog")
}
}
}
override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) { override fun onSortSelected(sortNodeEnum: SortNodeEnum, ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) {
mListNodesFragment?.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom) mListNodesFragment?.onSortSelected(sortNodeEnum, ascending, groupsBefore, recycleBinBottom)
} }

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities package com.kunzisoft.keepass.activities
import android.content.Context import android.content.Context

View File

@@ -24,6 +24,7 @@ import android.app.assist.AssistStructure
import android.app.backup.BackupManager import android.app.backup.BackupManager
import android.content.Intent import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.content.pm.PackageManager
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@@ -37,13 +38,11 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.inputmethod.EditorInfo.IME_ACTION_DONE import android.view.inputmethod.EditorInfo.IME_ACTION_DONE
import android.widget.Button import android.widget.*
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.TextView
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.core.app.ActivityCompat
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
@@ -99,6 +98,7 @@ class PasswordActivity : StylishActivity() {
private var mRememberKeyFile: Boolean = false private var mRememberKeyFile: Boolean = false
private var mOpenFileHelper: OpenFileHelper? = null private var mOpenFileHelper: OpenFileHelper? = null
private var mPermissionAsked = false
private var readOnly: Boolean = false private var readOnly: Boolean = false
private var mProgressDialogThread: ProgressDialogThread? = null private var mProgressDialogThread: ProgressDialogThread? = null
@@ -130,6 +130,7 @@ class PasswordActivity : StylishActivity() {
checkboxDefaultDatabaseView = findViewById(R.id.default_database) checkboxDefaultDatabaseView = findViewById(R.id.default_database)
advancedUnlockInfoView = findViewById(R.id.biometric_info) advancedUnlockInfoView = findViewById(R.id.biometric_info)
mPermissionAsked = savedInstanceState?.getBoolean(KEY_PERMISSION_ASKED) ?: mPermissionAsked
readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState) readOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrPreference(this, savedInstanceState)
val browseView = findViewById<View>(R.id.open_database_button) val browseView = findViewById<View>(R.id.open_database_button)
@@ -280,6 +281,7 @@ class PasswordActivity : StylishActivity() {
} }
override fun onSaveInstanceState(outState: Bundle) { override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_PERMISSION_ASKED, mPermissionAsked)
ReadOnlyHelper.onSaveInstanceState(outState, readOnly) ReadOnlyHelper.onSaveInstanceState(outState, readOnly)
super.onSaveInstanceState(outState) super.onSaveInstanceState(outState)
} }
@@ -545,9 +547,6 @@ class PasswordActivity : StylishActivity() {
}.show(supportFragmentManager, "duplicateUUIDDialog") }.show(supportFragmentManager, "duplicateUUIDDialog")
} }
// To fix multiple view education
private var performedEductionInProgress = false
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater val inflater = menuInflater
// Read menu // Read menu
@@ -563,51 +562,96 @@ class PasswordActivity : StylishActivity() {
super.onCreateOptionsMenu(menu) super.onCreateOptionsMenu(menu)
if (!performedEductionInProgress) { launchEducation(menu) {
performedEductionInProgress = true launchCheckPermission()
// Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu) }
} }
return true return true
} }
// Check permission
private fun launchCheckPermission() {
val writePermission = android.Manifest.permission.WRITE_EXTERNAL_STORAGE
val permissions = arrayOf(writePermission)
if (Build.VERSION.SDK_INT >= 23
&& !readOnly
&& !mPermissionAsked) {
mPermissionAsked = true
// Check self permission to show or not the dialog
if (toolbar != null
&& ActivityCompat.checkSelfPermission(this, writePermission) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, permissions, WRITE_EXTERNAL_STORAGE_REQUEST)
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
when (requestCode) {
WRITE_EXTERNAL_STORAGE_REQUEST -> {
if (grantResults.isEmpty() || grantResults[0] != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, android.Manifest.permission.WRITE_EXTERNAL_STORAGE))
Toast.makeText(this, R.string.read_only_warning, Toast.LENGTH_LONG).show()
}
}
}
}
// To fix multiple view education
private var performedEductionInProgress = false
private fun launchEducation(menu: Menu, onEducationFinished: ()-> Unit) {
if (!performedEductionInProgress) {
performedEductionInProgress = true
// Show education views
Handler().post { performedNextEducation(PasswordActivityEducation(this), menu, onEducationFinished) }
}
}
private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation, private fun performedNextEducation(passwordActivityEducation: PasswordActivityEducation,
menu: Menu) { menu: Menu,
val educationContainerView = containerView onEducationFinished: ()-> Unit) {
val unlockEducationPerformed = educationContainerView != null val educationToolbar = toolbar
val unlockEducationPerformed = educationToolbar != null
&& passwordActivityEducation.checkAndPerformedUnlockEducation( && passwordActivityEducation.checkAndPerformedUnlockEducation(
educationContainerView, educationToolbar,
{ {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
}, },
{ {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
}) })
if (!unlockEducationPerformed) { if (!unlockEducationPerformed) {
val educationToolbar = toolbar
val readOnlyEducationPerformed = val readOnlyEducationPerformed =
educationToolbar?.findViewById<View>(R.id.menu_open_file_read_mode_key) != null educationToolbar?.findViewById<View>(R.id.menu_open_file_read_mode_key) != null
&& passwordActivityEducation.checkAndPerformedReadOnlyEducation( && passwordActivityEducation.checkAndPerformedReadOnlyEducation(
educationToolbar.findViewById(R.id.menu_open_file_read_mode_key), educationToolbar.findViewById(R.id.menu_open_file_read_mode_key),
{ {
onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key)) onOptionsItemSelected(menu.findItem(R.id.menu_open_file_read_mode_key))
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
}, },
{ {
performedNextEducation(passwordActivityEducation, menu) performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
}) })
if (!readOnlyEducationPerformed) { if (!readOnlyEducationPerformed) {
val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate() val biometricCanAuthenticate = BiometricManager.from(this).canAuthenticate()
// EducationPerformed val biometricEducationPerformed =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& PreferencesUtil.isBiometricUnlockEnable(applicationContext) && PreferencesUtil.isBiometricUnlockEnable(applicationContext)
&& (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS) && (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
&& advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null && advancedUnlockInfoView != null && advancedUnlockInfoView?.unlockIconImageView != null
&& passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!) && passwordActivityEducation.checkAndPerformedBiometricEducation(advancedUnlockInfoView?.unlockIconImageView!!,
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
},
{
performedNextEducation(passwordActivityEducation, menu, onEducationFinished)
})
if (!biometricEducationPerformed) {
onEducationFinished.invoke()
}
} }
} }
} }
@@ -683,6 +727,10 @@ class PasswordActivity : StylishActivity() {
private const val KEY_PASSWORD = "password" private const val KEY_PASSWORD = "password"
private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately" private const val KEY_LAUNCH_IMMEDIATELY = "launchImmediately"
private const val KEY_PERMISSION_ASKED = "KEY_PERMISSION_ASKED"
private const val WRITE_EXTERNAL_STORAGE_REQUEST = 647
private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?, private fun buildAndLaunchIntent(activity: Activity, databaseFile: Uri, keyFile: Uri?,
intentBuildLauncher: (Intent) -> Unit) { intentBuildLauncher: (Intent) -> Unit) {
val intent = Intent(activity, PasswordActivity::class.java) val intent = Intent(activity, PasswordActivity::class.java)

View File

@@ -1,56 +0,0 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs
import android.app.Dialog
import android.os.Build
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.fragment.app.DialogFragment
import com.kunzisoft.keepass.R
class ReadOnlyDialog : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
activity?.let { activity ->
// Use the Builder class for convenient dialog construction
val builder = androidx.appcompat.app.AlertDialog.Builder(activity)
var warning = getString(R.string.read_only_warning)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
warning = warning + "\n\n" + getString(R.string.read_only_kitkat_warning)
}
builder.setMessage(warning)
builder.setPositiveButton(getString(android.R.string.ok)) { _, _ -> dismiss() }
builder.setNegativeButton(getString(R.string.beta_dontask)) { _, _ ->
val prefs = PreferenceManager.getDefaultSharedPreferences(context)
val edit = prefs.edit()
edit.putBoolean(getString(R.string.show_read_only_warning), false)
edit.apply()
dismiss()
}
// Create the AlertDialog object and return it
return builder.create()
}
return super.onCreateDialog(savedInstanceState)
}
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.dialogs package com.kunzisoft.keepass.activities.dialogs
import android.annotation.SuppressLint import android.annotation.SuppressLint

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.helpers package com.kunzisoft.keepass.activities.helpers
import android.app.assist.AssistStructure import android.app.assist.AssistStructure

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.helpers package com.kunzisoft.keepass.activities.helpers
import android.content.Context import android.content.Context

View File

@@ -1,55 +0,0 @@
/*
* Copyright 2017 Brian Pellin, 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities.lock
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.view.WindowManager
/**
* Locking Hide Activity that sets FLAG_SECURE to prevent screenshots, and from
* appearing in the recent app preview
*/
abstract class LockingHideActivity : LockingActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
}
/* (non-Javadoc) Workaround for HTC Linkify issues
* @see android.app.Activity#startActivity(android.content.Intent)
*/
override fun startActivity(intent: Intent) {
try {
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
intent.component = null
}
super.startActivity(intent)
} catch (e: ActivityNotFoundException) {
/* Catch the bad HTC implementation case */
super.startActivity(Intent.createChooser(intent, null))
}
}
}

View File

@@ -61,6 +61,7 @@ object Stylish {
return when (themeString) { return when (themeString) {
context.getString(R.string.list_style_name_night) -> R.style.KeepassDXStyle_Night context.getString(R.string.list_style_name_night) -> R.style.KeepassDXStyle_Night
context.getString(R.string.list_style_name_black) -> R.style.KeepassDXStyle_Black
context.getString(R.string.list_style_name_dark) -> R.style.KeepassDXStyle_Dark context.getString(R.string.list_style_name_dark) -> R.style.KeepassDXStyle_Dark
context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue context.getString(R.string.list_style_name_blue) -> R.style.KeepassDXStyle_Blue
context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red context.getString(R.string.list_style_name_red) -> R.style.KeepassDXStyle_Red

View File

@@ -19,20 +19,45 @@
*/ */
package com.kunzisoft.keepass.activities.stylish package com.kunzisoft.keepass.activities.stylish
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.util.Log import android.util.Log
import android.view.WindowManager
/**
* Stylish Hide Activity that apply a dynamic style and sets FLAG_SECURE to prevent screenshots / from
* appearing in the recent app preview
*/
abstract class StylishActivity : AppCompatActivity() { abstract class StylishActivity : AppCompatActivity() {
@StyleRes @StyleRes
private var themeId: Int = 0 private var themeId: Int = 0
/* (non-Javadoc) Workaround for HTC Linkify issues
* @see android.app.Activity#startActivity(android.content.Intent)
*/
override fun startActivity(intent: Intent) {
try {
if (intent.component != null && intent.component!!.shortClassName == ".HtcLinkifyDispatcherActivity") {
intent.component = null
}
super.startActivity(intent)
} catch (e: ActivityNotFoundException) {
/* Catch the bad HTC implementation case */
super.startActivity(Intent.createChooser(intent, null))
}
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
this.themeId = Stylish.getThemeId(this) this.themeId = Stylish.getThemeId(this)
setTheme(themeId) setTheme(themeId)
// Several gingerbread devices have problems with FLAG_SECURE
window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
} }
override fun onResume() { override fun onResume() {

View File

@@ -0,0 +1,96 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters
import android.content.Context
import android.text.format.Formatter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment
class EntryAttachmentsAdapter(val context: Context) : RecyclerView.Adapter<EntryAttachmentsAdapter.EntryBinariesViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context)
var entryAttachmentsList: MutableList<EntryAttachment> = ArrayList()
var onItemClickListener: ((item: EntryAttachment, position: Int)->Unit)? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryBinariesViewHolder {
return EntryBinariesViewHolder(inflater.inflate(R.layout.item_attachment, parent, false))
}
override fun onBindViewHolder(holder: EntryBinariesViewHolder, position: Int) {
val entryAttachment = entryAttachmentsList[position]
holder.binaryFileTitle.text = entryAttachment.name
holder.binaryFileSize.text = Formatter.formatFileSize(context,
entryAttachment.binaryAttachment.length())
holder.binaryFileCompression.apply {
if (entryAttachment.binaryAttachment.isCompressed == true) {
text = CompressionAlgorithm.GZip.getName(context.resources)
visibility = View.VISIBLE
} else {
text = ""
visibility = View.GONE
}
}
holder.binaryFileProgress.apply {
visibility = when (entryAttachment.downloadState) {
AttachmentState.NULL, AttachmentState.COMPLETE, AttachmentState.ERROR -> View.GONE
AttachmentState.START, AttachmentState.IN_PROGRESS -> View.VISIBLE
}
progress = entryAttachment.downloadProgression
}
holder.itemView.setOnClickListener {
onItemClickListener?.invoke(entryAttachment, position)
}
}
override fun getItemCount(): Int {
return entryAttachmentsList.size
}
fun updateProgress(entryAttachment: EntryAttachment) {
val indexEntryAttachment = entryAttachmentsList.indexOfLast { current -> current.name == entryAttachment.name }
if (indexEntryAttachment != -1) {
entryAttachmentsList[indexEntryAttachment] = entryAttachment
notifyItemChanged(indexEntryAttachment)
}
}
fun clear() {
entryAttachmentsList.clear()
}
inner class EntryBinariesViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var binaryFileTitle: TextView = itemView.findViewById(R.id.item_attachment_title)
var binaryFileSize: TextView = itemView.findViewById(R.id.item_attachment_size)
var binaryFileCompression: TextView = itemView.findViewById(R.id.item_attachment_compression)
var binaryFileProgress: ProgressBar = itemView.findViewById(R.id.item_attachment_progress)
}
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters package com.kunzisoft.keepass.adapters
import android.content.Context import android.content.Context

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.adapters package com.kunzisoft.keepass.adapters
import android.content.Context import android.content.Context
@@ -15,7 +34,7 @@ import java.util.ArrayList
class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() { class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.FieldViewHolder>() {
private val inflater: LayoutInflater = LayoutInflater.from(context) private val inflater: LayoutInflater = LayoutInflater.from(context)
var fields: MutableList<Field> = ArrayList() private var fields: MutableList<Field> = ArrayList()
var onItemClickListener: OnItemClickListener? = null var onItemClickListener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
@@ -33,6 +52,11 @@ class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.Field
return fields.size return fields.size
} }
fun setFields(fieldsToAdd: List<Field>) {
fields.clear()
fields.addAll(fieldsToAdd)
}
fun clear() { fun clear() {
fields.clear() fields.clear()
} }

View File

@@ -34,12 +34,15 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.SortedList
import androidx.recyclerview.widget.SortedListAdapterCallback import androidx.recyclerview.widget.SortedListAdapterCallback
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.Group
import com.kunzisoft.keepass.database.element.SortNodeEnum import com.kunzisoft.keepass.database.element.SortNodeEnum
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.strikeOut
import java.util.* import java.util.*
class NodeAdapter class NodeAdapter
@@ -66,6 +69,7 @@ class NodeAdapter
private var recycleBinBottomSort: Boolean = true private var recycleBinBottomSort: Boolean = true
private var showUserNames: Boolean = true private var showUserNames: Boolean = true
private var showNumberEntries: Boolean = true private var showNumberEntries: Boolean = true
private var entryFilters = arrayOf<Group.ChildFilter>()
private var actionNodesList = LinkedList<Node>() private var actionNodesList = LinkedList<Node>()
private var nodeClickCallback: NodeClickCallback? = null private var nodeClickCallback: NodeClickCallback? = null
@@ -128,6 +132,8 @@ class NodeAdapter
this.showUserNames = PreferencesUtil.showUsernamesListEntries(context) this.showUserNames = PreferencesUtil.showUsernamesListEntries(context)
this.showNumberEntries = PreferencesUtil.showNumberEntries(context) this.showNumberEntries = PreferencesUtil.showNumberEntries(context)
this.entryFilters = Group.ChildFilter.getDefaults(context)
// Reinit textSize for all view type // Reinit textSize for all view type
calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true } calculateViewTypeTextSize.forEachIndexed { index, _ -> calculateViewTypeTextSize[index] = true }
} }
@@ -139,7 +145,7 @@ class NodeAdapter
this.nodeSortedList.clear() this.nodeSortedList.clear()
assignPreferences() assignPreferences()
try { try {
this.nodeSortedList.addAll(group.getChildren()) this.nodeSortedList.addAll(group.getChildren(*entryFilters))
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Can't add node elements to the list", e) Log.e(TAG, "Can't add node elements to the list", e)
Toast.makeText(context, "Can't add node elements to the list : " + e.message, Toast.LENGTH_LONG).show() Toast.makeText(context, "Can't add node elements to the list : " + e.message, Toast.LENGTH_LONG).show()
@@ -289,15 +295,53 @@ class NodeAdapter
width = iconSize.toInt() width = iconSize.toInt()
} }
} }
// Assign text // Assign text
holder.text.apply { holder.text.apply {
text = subNode.title text = subNode.title
setTextSize(textSizeUnit, infoTextSize) setTextSize(textSizeUnit, infoTextSize)
paintFlags = if (subNode.isCurrentlyExpires) strikeOut(subNode.isCurrentlyExpires)
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
} }
// Add subText with username
holder.subText.apply {
text = ""
strikeOut(subNode.isCurrentlyExpires)
visibility = View.GONE
}
// Specific elements for entry
if (subNode.type == Type.ENTRY) {
val entry = subNode as Entry
mDatabase.startManageEntry(entry)
holder.text.text = entry.getVisualTitle()
holder.subText.apply {
val username = entry.username
if (showUserNames && username.isNotEmpty()) {
visibility = View.VISIBLE
text = username
setTextSize(textSizeUnit, subtextSize)
}
}
mDatabase.stopManageEntry(entry)
}
// Add number of entries in groups
if (subNode.type == Type.GROUP) {
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as Group)
.getChildEntries(*entryFilters)
.size.toString()
setTextSize(textSizeUnit, numberChildrenTextSize)
visibility = View.VISIBLE
}
} else {
holder.numberChildren?.visibility = View.GONE
}
}
// Assign click // Assign click
holder.container.setOnClickListener { holder.container.setOnClickListener {
nodeClickCallback?.onNodeClick(subNode) nodeClickCallback?.onNodeClick(subNode)
@@ -307,46 +351,8 @@ class NodeAdapter
} }
holder.container.isSelected = actionNodesList.contains(subNode) holder.container.isSelected = actionNodesList.contains(subNode)
// Add subText with username
holder.subText.apply {
text = ""
paintFlags = if (subNode.isCurrentlyExpires)
paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
else
paintFlags and Paint.STRIKE_THRU_TEXT_FLAG
visibility = View.GONE
if (subNode.type == Type.ENTRY) {
val entry = subNode as Entry
mDatabase.startManageEntry(entry)
holder.text.text = entry.getVisualTitle()
val username = entry.username
if (showUserNames && username.isNotEmpty()) {
visibility = View.VISIBLE
text = username
setTextSize(textSizeUnit, subtextSize)
} }
mDatabase.stopManageEntry(entry)
}
}
// Add number of entries in groups
if (subNode.type == Type.GROUP) {
if (showNumberEntries) {
holder.numberChildren?.apply {
text = (subNode as Group).getChildEntries(true).size.toString()
setTextSize(textSizeUnit, numberChildrenTextSize)
visibility = View.VISIBLE
}
} else {
holder.numberChildren?.visibility = View.GONE
}
}
}
override fun getItemCount(): Int { override fun getItemCount(): Int {
return nodeSortedList.size() return nodeSortedList.size()

View File

@@ -28,15 +28,14 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.cursor.EntryCursor
import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.* import com.kunzisoft.keepass.view.strikeOut
class SearchEntryCursorAdapter(context: Context, private val database: Database) class SearchEntryCursorAdapter(private val context: Context,
private val database: Database)
: androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) { : androidx.cursoradapter.widget.CursorAdapter(context, null, FLAG_REGISTER_CONTENT_OBSERVER) {
private val cursorInflater: LayoutInflater = context.getSystemService( private val cursorInflater: LayoutInflater = context.getSystemService(
@@ -71,34 +70,31 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
override fun bindView(view: View, context: Context, cursor: Cursor) { override fun bindView(view: View, context: Context, cursor: Cursor) {
// Retrieve elements from cursor database.getEntryFrom(cursor)?.let { currentEntry ->
val uuid = UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS)))
val iconFactory = database.iconFactory
var icon: IconImage = iconFactory.getIcon(
UUID(cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_MOST_SIGNIFICANT_BITS)),
cursor.getLong(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_CUSTOM_UUID_LEAST_SIGNIFICANT_BITS))))
if (icon.isUnknown) {
icon = iconFactory.getIcon(cursor.getInt(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_ICON_STANDARD)))
if (icon.isUnknown)
icon = iconFactory.keyIcon
}
val title = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_TITLE))
val username = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_USERNAME))
val url = cursor.getString(cursor.getColumnIndex(EntryCursor.COLUMN_INDEX_URL))
val viewHolder = view.tag as ViewHolder val viewHolder = view.tag as ViewHolder
// Assign image // Assign image
viewHolder.imageViewIcon?.assignDatabaseIcon(database.drawFactory, icon, iconColor) viewHolder.imageViewIcon?.assignDatabaseIcon(
database.drawFactory,
currentEntry.icon,
iconColor)
// Assign title // Assign title
val showTitle = Entry.getVisualTitle(false, title, username, url, uuid.toString()) viewHolder.textViewTitle?.apply {
viewHolder.textViewTitle?.text = showTitle text = currentEntry.getVisualTitle()
if (displayUsername && username.isNotEmpty()) { strikeOut(currentEntry.isCurrentlyExpires)
viewHolder.textViewSubTitle?.text = String.format("(%s)", username) }
// Assign subtitle
viewHolder.textViewSubTitle?.apply {
val entryUsername = currentEntry.username
text = if (displayUsername && entryUsername.isNotEmpty()) {
String.format("(%s)", entryUsername)
} else { } else {
viewHolder.textViewSubTitle?.text = "" ""
}
strikeOut(currentEntry.isCurrentlyExpires)
}
} }
} }
@@ -109,7 +105,7 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database)
} }
override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? { override fun runQueryOnBackgroundThread(constraint: CharSequence): Cursor? {
return database.searchEntries(constraint.toString()) return database.searchEntries(context, constraint.toString())
} }
fun getEntryFromPosition(position: Int): Entry? { fun getEntryFromPosition(position: Int): Entry? {

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database package com.kunzisoft.keepass.app.database
import android.os.AsyncTask import android.os.AsyncTask

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database package com.kunzisoft.keepass.app.database
import androidx.room.Database import androidx.room.Database

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database package com.kunzisoft.keepass.app.database
import android.content.Context import android.content.Context

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database package com.kunzisoft.keepass.app.database
import androidx.room.* import androidx.room.*

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database package com.kunzisoft.keepass.app.database
import android.os.Parcel import android.os.Parcel

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database package com.kunzisoft.keepass.app.database
import androidx.room.* import androidx.room.*

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.app.database package com.kunzisoft.keepass.app.database
import androidx.room.ColumnInfo import androidx.room.ColumnInfo

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.biometric package com.kunzisoft.keepass.biometric
import android.content.Intent import android.content.Intent

View File

@@ -19,15 +19,13 @@
*/ */
package com.kunzisoft.keepass.crypto package com.kunzisoft.keepass.crypto
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.stream.NullOutputStream import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.stream.longTo8Bytes
import java.io.IOException import java.io.IOException
import java.security.DigestOutputStream import java.security.DigestOutputStream
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.Arrays import java.util.*
import javax.crypto.Mac import javax.crypto.Mac
import kotlin.math.min import kotlin.math.min
@@ -60,7 +58,7 @@ object CryptoUtil {
throw RuntimeException(e) throw RuntimeException(e)
} }
val pbR = LEDataOutputStream.writeLongBuf(r) val pbR = longTo8Bytes(r)
val part = hmac.doFinal(pbR) val part = hmac.doFinal(pbR)
val copy = min(cbOut - pos, part.size) val copy = min(cbOut - pos, part.size)

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
@@ -47,7 +47,22 @@ class AesEngine : CipherEngine() {
companion object { companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid( val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0x31.toByte(), 0xC1.toByte(), 0xF2.toByte(), 0xE6.toByte(), 0xBF.toByte(), 0x71.toByte(), 0x43.toByte(), 0x50.toByte(), 0xBE.toByte(), 0x58.toByte(), 0x05.toByte(), 0x21.toByte(), 0x6A.toByte(), 0xFC.toByte(), 0x5A.toByte(), 0xFF.toByte())) byteArrayOf(0x31.toByte(),
0xC1.toByte(),
0xF2.toByte(),
0xE6.toByte(),
0xBF.toByte(),
0x71.toByte(),
0x43.toByte(),
0x50.toByte(),
0xBE.toByte(),
0x58.toByte(),
0x05.toByte(),
0x21.toByte(),
0x6A.toByte(),
0xFC.toByte(),
0x5A.toByte(),
0xFF.toByte()))
} }
} }

View File

@@ -20,7 +20,7 @@
package com.kunzisoft.keepass.crypto.engine package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import com.kunzisoft.keepass.stream.bytes16ToUuid
import org.spongycastle.jce.provider.BouncyCastleProvider import org.spongycastle.jce.provider.BouncyCastleProvider
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException import java.security.InvalidKeyException
@@ -50,7 +50,22 @@ class ChaCha20Engine : CipherEngine() {
companion object { companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid( val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xD6.toByte(), 0x03.toByte(), 0x8A.toByte(), 0x2B.toByte(), 0x8B.toByte(), 0x6F.toByte(), 0x4C.toByte(), 0xB5.toByte(), 0xA5.toByte(), 0x24.toByte(), 0x33.toByte(), 0x9A.toByte(), 0x31.toByte(), 0xDB.toByte(), 0xB5.toByte(), 0x9A.toByte())) byteArrayOf(0xD6.toByte(),
0x03.toByte(),
0x8A.toByte(),
0x2B.toByte(),
0x8B.toByte(),
0x6F.toByte(),
0x4C.toByte(),
0xB5.toByte(),
0xA5.toByte(),
0x24.toByte(),
0x33.toByte(),
0x9A.toByte(),
0x31.toByte(),
0xDB.toByte(),
0xB5.toByte(),
0x9A.toByte()))
} }
} }

View File

@@ -21,13 +21,11 @@ package com.kunzisoft.keepass.crypto.engine
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.security.InvalidAlgorithmParameterException import java.security.InvalidAlgorithmParameterException
import java.security.InvalidKeyException import java.security.InvalidKeyException
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import java.util.UUID import java.util.*
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
@@ -53,7 +51,22 @@ class TwofishEngine : CipherEngine() {
companion object { companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid( val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xAD.toByte(), 0x68.toByte(), 0xF2.toByte(), 0x9F.toByte(), 0x57.toByte(), 0x6F.toByte(), 0x4B.toByte(), 0xB9.toByte(), 0xA3.toByte(), 0x6A.toByte(), 0xD4.toByte(), 0x7A.toByte(), 0xF9.toByte(), 0x65.toByte(), 0x34.toByte(), 0x6C.toByte())) byteArrayOf(0xAD.toByte(),
0x68.toByte(),
0xF2.toByte(),
0x9F.toByte(),
0x57.toByte(),
0x6F.toByte(),
0x4B.toByte(),
0xB9.toByte(),
0xA3.toByte(),
0x6A.toByte(),
0xD4.toByte(),
0x7A.toByte(),
0xF9.toByte(),
0x65.toByte(),
0x34.toByte(),
0x6C.toByte()))
} }
} }

View File

@@ -23,7 +23,7 @@ import android.content.res.Resources
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CryptoUtil import com.kunzisoft.keepass.crypto.CryptoUtil
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.io.IOException import java.io.IOException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
@@ -88,7 +88,7 @@ class AesKdf internal constructor() : KdfEngine() {
private const val DEFAULT_ROUNDS = 6000 private const val DEFAULT_ROUNDS = 6000
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid( val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xC9.toByte(), byteArrayOf(0xC9.toByte(),
0xD9.toByte(), 0xD9.toByte(),
0xF3.toByte(), 0xF3.toByte(),

View File

@@ -21,7 +21,7 @@ package com.kunzisoft.keepass.crypto.keyDerivation
import android.content.res.Resources import android.content.res.Resources
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import com.kunzisoft.keepass.stream.bytes16ToUuid
import java.io.IOException import java.io.IOException
import java.security.SecureRandom import java.security.SecureRandom
import java.util.* import java.util.*
@@ -126,7 +126,7 @@ class Argon2Kdf internal constructor() : KdfEngine() {
companion object { companion object {
val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid( val CIPHER_UUID: UUID = bytes16ToUuid(
byteArrayOf(0xEF.toByte(), byteArrayOf(0xEF.toByte(),
0x63.toByte(), 0x63.toByte(),
0x6D.toByte(), 0x6D.toByte(),

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
@@ -19,20 +19,20 @@
*/ */
package com.kunzisoft.keepass.crypto.keyDerivation package com.kunzisoft.keepass.crypto.keyDerivation
import com.kunzisoft.keepass.stream.LittleEndianDataInputStream
import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.bytes16ToUuid
import com.kunzisoft.keepass.stream.uuidTo16Bytes
import com.kunzisoft.keepass.utils.VariantDictionary import com.kunzisoft.keepass.utils.VariantDictionary
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.stream.LEDataOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.util.UUID import java.util.*
class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() { class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
fun setParamUUID() { fun setParamUUID() {
setByteArray(PARAM_UUID, DatabaseInputOutputUtils.uuidToBytes(uuid)) setByteArray(PARAM_UUID, uuidTo16Bytes(uuid))
} }
companion object { companion object {
@@ -42,11 +42,11 @@ class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
@Throws(IOException::class) @Throws(IOException::class)
fun deserialize(data: ByteArray): KdfParameters? { fun deserialize(data: ByteArray): KdfParameters? {
val bis = ByteArrayInputStream(data) val bis = ByteArrayInputStream(data)
val lis = LEDataInputStream(bis) val lis = LittleEndianDataInputStream(bis)
val d = deserialize(lis) ?: return null val d = deserialize(lis) ?: return null
val uuid = DatabaseInputOutputUtils.bytesToUuid(d.getByteArray(PARAM_UUID)) val uuid = bytes16ToUuid(d.getByteArray(PARAM_UUID))
val kdfP = KdfParameters(uuid) val kdfP = KdfParameters(uuid)
kdfP.copyTo(d) kdfP.copyTo(d)
@@ -56,7 +56,7 @@ class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() {
@Throws(IOException::class) @Throws(IOException::class)
fun serialize(kdf: KdfParameters): ByteArray { fun serialize(kdf: KdfParameters): ByteArray {
val bos = ByteArrayOutputStream() val bos = ByteArrayOutputStream()
val los = LEDataOutputStream(bos) val los = LittleEndianDataOutputStream(bos)
serialize(kdf, los) serialize(kdf, los)

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action package com.kunzisoft.keepass.database.action
import android.content.* import android.content.*
@@ -22,9 +41,11 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_GROUP_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_DELETE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_MOVE_NODES_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_RESTORE_ENTRY_HISTORY
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COLOR_TASK
import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_COMPRESSION_TASK
@@ -178,7 +199,11 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
unBindService() unBindService()
try {
activity.unregisterReceiver(databaseTaskBroadcastReceiver) activity.unregisterReceiver(databaseTaskBroadcastReceiver)
} catch (e: IllegalArgumentException) {
// If receiver not register, do nothing
}
} }
@Synchronized @Synchronized
@@ -348,6 +373,34 @@ class ProgressDialogThread(private val activity: FragmentActivity) {
startDatabaseActionListNodes(ACTION_DATABASE_DELETE_NODES_TASK, nodesToDelete, null, save) startDatabaseActionListNodes(ACTION_DATABASE_DELETE_NODES_TASK, nodesToDelete, null, save)
} }
/*
-----------------
Entry History Settings
-----------------
*/
fun startDatabaseRestoreEntryHistory(mainEntry: Entry,
entryHistoryPosition: Int,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_RESTORE_ENTRY_HISTORY)
}
fun startDatabaseDeleteEntryHistory(mainEntry: Entry,
entryHistoryPosition: Int,
save: Boolean) {
start(Bundle().apply {
putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, mainEntry.nodeId)
putInt(DatabaseTaskNotificationService.ENTRY_HISTORY_POSITION_KEY, entryHistoryPosition)
putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save)
}
, ACTION_DATABASE_DELETE_ENTRY_HISTORY)
}
/* /*
----------------- -----------------
Main Settings Main Settings

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2020 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.history
import android.content.Context
import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
class DeleteEntryHistoryDatabaseRunnable (
context: Context,
database: Database,
private val mainEntry: Entry,
private val entryHistoryPosition: Int,
saveDatabase: Boolean)
: SaveDatabaseRunnable(context, database, saveDatabase) {
override fun onStartRun() {
try {
mainEntry.removeEntryFromHistory(entryHistoryPosition)
} catch (e: Exception) {
setError(e)
}
super.onStartRun()
}
}

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2020 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.history
import android.content.Context
import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable
import com.kunzisoft.keepass.database.element.Database
import com.kunzisoft.keepass.database.element.Entry
import com.kunzisoft.keepass.tasks.ActionRunnable
class RestoreEntryHistoryDatabaseRunnable (
private val context: Context,
private val database: Database,
private val mainEntry: Entry,
private val entryHistoryPosition: Int,
private val saveDatabase: Boolean)
: ActionRunnable() {
private var updateEntryRunnable: UpdateEntryRunnable? = null
override fun onStartRun() {
try {
val historyToRestore = Entry(mainEntry.getHistory()[entryHistoryPosition])
// Copy history of main entry in the restore entry
mainEntry.getHistory().forEach {
historyToRestore.addEntryToHistory(it)
}
// Update the entry with the fresh formatted entry to restore
updateEntryRunnable = UpdateEntryRunnable(context,
database,
mainEntry,
historyToRestore,
saveDatabase,
null)
updateEntryRunnable?.onStartRun()
} catch (e: Exception) {
setError(e)
}
}
override fun onActionRun() {
updateEntryRunnable?.onActionRun()
}
override fun onFinishRun() {
updateEntryRunnable?.onFinishRun()
}
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.action.node package com.kunzisoft.keepass.database.action.node
import android.content.Context import android.content.Context

View File

@@ -1,10 +1,31 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor package com.kunzisoft.keepass.database.cursor
import android.database.MatrixCursor import android.database.MatrixCursor
import android.provider.BaseColumns import android.provider.BaseColumns
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.entry.EntryVersioned import com.kunzisoft.keepass.database.element.entry.EntryVersioned
import com.kunzisoft.keepass.database.element.icon.IconImageFactory import com.kunzisoft.keepass.database.element.icon.IconImageFactory
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import java.util.*
abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>> : MatrixCursor(arrayOf( abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>> : MatrixCursor(arrayOf(
_ID, _ID,
@@ -17,7 +38,9 @@ abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>>
COLUMN_INDEX_USERNAME, COLUMN_INDEX_USERNAME,
COLUMN_INDEX_PASSWORD, COLUMN_INDEX_PASSWORD,
COLUMN_INDEX_URL, COLUMN_INDEX_URL,
COLUMN_INDEX_NOTES COLUMN_INDEX_NOTES,
COLUMN_INDEX_EXPIRY_TIME,
COLUMN_INDEX_EXPIRES
)) { )) {
protected var entryId: Long = 0 protected var entryId: Long = 0
@@ -37,6 +60,9 @@ abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>>
pwEntry.password = getString(getColumnIndex(COLUMN_INDEX_PASSWORD)) pwEntry.password = getString(getColumnIndex(COLUMN_INDEX_PASSWORD))
pwEntry.url = getString(getColumnIndex(COLUMN_INDEX_URL)) pwEntry.url = getString(getColumnIndex(COLUMN_INDEX_URL))
pwEntry.notes = getString(getColumnIndex(COLUMN_INDEX_NOTES)) pwEntry.notes = getString(getColumnIndex(COLUMN_INDEX_NOTES))
pwEntry.expiryTime = DateInstant(getString(getColumnIndex(COLUMN_INDEX_EXPIRY_TIME)))
pwEntry.expires = getString(getColumnIndex(COLUMN_INDEX_EXPIRES))
.toLowerCase(Locale.ENGLISH) != "false"
} }
companion object { companion object {
@@ -51,5 +77,7 @@ abstract class EntryCursor<EntryId, PwEntryV : EntryVersioned<*, EntryId, *, *>>
const val COLUMN_INDEX_PASSWORD = "password" const val COLUMN_INDEX_PASSWORD = "password"
const val COLUMN_INDEX_URL = "URL" const val COLUMN_INDEX_URL = "URL"
const val COLUMN_INDEX_NOTES = "notes" const val COLUMN_INDEX_NOTES = "notes"
const val COLUMN_INDEX_EXPIRY_TIME = "expiry_time"
const val COLUMN_INDEX_EXPIRES = "expires"
} }
} }

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
@@ -17,7 +36,9 @@ class EntryCursorKDB : EntryCursorUUID<EntryKDB>() {
entry.username, entry.username,
entry.password, entry.password,
entry.url, entry.url,
entry.notes entry.notes,
entry.expiryTime,
entry.expires
)) ))
entryId++ entryId++
} }

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
@@ -21,7 +40,9 @@ class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
entry.username, entry.username,
entry.password, entry.password,
entry.url, entry.url,
entry.notes entry.notes,
entry.expiryTime,
entry.expires
)) ))
for (element in entry.customFields.entries) { for (element in entry.customFields.entries) {

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor package com.kunzisoft.keepass.database.cursor
import com.kunzisoft.keepass.database.element.entry.EntryVersioned import com.kunzisoft.keepass.database.element.entry.EntryVersioned

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.cursor package com.kunzisoft.keepass.database.cursor
import android.database.MatrixCursor import android.database.MatrixCursor

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.database.element package com.kunzisoft.keepass.database.element
import android.content.ContentResolver import android.content.ContentResolver
import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import android.database.Cursor import android.database.Cursor
import android.net.Uri import android.net.Uri
@@ -36,7 +37,10 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.exception.FileNotFoundDatabaseException
import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.database.exception.SignatureDatabaseException
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
@@ -45,7 +49,7 @@ import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX
import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.database.search.SearchHelper
import com.kunzisoft.keepass.icons.IconDrawableFactory import com.kunzisoft.keepass.icons.IconDrawableFactory
import com.kunzisoft.keepass.stream.LEDataInputStream import com.kunzisoft.keepass.stream.readBytes4ToInt
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.SingletonHolder import com.kunzisoft.keepass.utils.SingletonHolder
import com.kunzisoft.keepass.utils.UriUtil import com.kunzisoft.keepass.utils.UriUtil
@@ -324,47 +328,37 @@ class Database {
isReadOnly = !file.canWrite() isReadOnly = !file.canWrite()
} }
// Pass Uris as InputStreams
val inputStream: InputStream?
try {
inputStream = UriUtil.getUriInputStream(contentResolver, uri)
} catch (e: Exception) {
Log.e("KPD", "Database::loadData", e)
throw FileNotFoundDatabaseException()
}
// Pass KeyFile Uri as InputStreams // Pass KeyFile Uri as InputStreams
var databaseInputStream: InputStream? = null
var keyFileInputStream: InputStream? = null var keyFileInputStream: InputStream? = null
keyfile?.let {
try { try {
// Get keyFile inputStream
keyfile?.let {
keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile) keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile)
} catch (e: Exception) {
Log.e("KPD", "Database::loadData", e)
throw FileNotFoundDatabaseException()
}
} }
// Load Data // Load Data, pass Uris as InputStreams
databaseInputStream = BufferedInputStream(UriUtil.getUriInputStream(contentResolver, uri))
val bufferedInputStream = BufferedInputStream(inputStream) if (!databaseInputStream.markSupported()) {
if (!bufferedInputStream.markSupported()) {
throw IOException("Input stream does not support mark.") throw IOException("Input stream does not support mark.")
} }
// We'll end up reading 8 bytes to identify the header. Might as well use two extra. // We'll end up reading 8 bytes to identify the header. Might as well use two extra.
bufferedInputStream.mark(10) databaseInputStream.mark(10)
// Get the file directory to save the attachments // Get the file directory to save the attachments
val sig1 = LEDataInputStream.readInt(bufferedInputStream) val sig1 = databaseInputStream.readBytes4ToInt()
val sig2 = LEDataInputStream.readInt(bufferedInputStream) val sig2 = databaseInputStream.readBytes4ToInt()
// Return to the start // Return to the start
bufferedInputStream.reset() databaseInputStream.reset()
when { when {
// Header of database KDB // Header of database KDB
DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(DatabaseInputKDB() DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(DatabaseInputKDB(
.openDatabase(bufferedInputStream, cacheDirectory,
fixDuplicateUUID)
.openDatabase(databaseInputStream,
password, password,
keyFileInputStream, keyFileInputStream,
progressTaskUpdater)) progressTaskUpdater))
@@ -373,7 +367,7 @@ class Database {
DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(DatabaseInputKDBX( DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(DatabaseInputKDBX(
cacheDirectory, cacheDirectory,
fixDuplicateUUID) fixDuplicateUUID)
.openDatabase(bufferedInputStream, .openDatabase(databaseInputStream,
password, password,
keyFileInputStream, keyFileInputStream,
progressTaskUpdater)) progressTaskUpdater))
@@ -384,6 +378,17 @@ class Database {
this.mSearchHelper = SearchHelper(omitBackup) this.mSearchHelper = SearchHelper(omitBackup)
loaded = true loaded = true
} catch (e: LoadDatabaseException) {
Log.e("KPD", "Database::loadData", e)
throw e
} catch (e: Exception) {
Log.e("KPD", "Database::loadData", e)
throw FileNotFoundDatabaseException()
} finally {
keyFileInputStream?.close()
databaseInputStream?.close()
}
} }
fun isGroupSearchable(group: Group, isOmitBackup: Boolean): Boolean { fun isGroupSearchable(group: Group, isOmitBackup: Boolean): Boolean {
@@ -397,7 +402,7 @@ class Database {
return mSearchHelper?.search(this, str, max) return mSearchHelper?.search(this, str, max)
} }
fun searchEntries(query: String): Cursor? { fun searchEntries(context: Context, query: String): Cursor? {
var cursorKDB: EntryCursorKDB? = null var cursorKDB: EntryCursorKDB? = null
var cursorKDBX: EntryCursorKDBX? = null var cursorKDBX: EntryCursorKDBX? = null
@@ -409,7 +414,8 @@ class Database {
val searchResult = search(query, SearchHelper.MAX_SEARCH_ENTRY) val searchResult = search(query, SearchHelper.MAX_SEARCH_ENTRY)
if (searchResult != null) { if (searchResult != null) {
for (entry in searchResult.getChildEntries(true)) { // Search in hide entries but not meta-stream
for (entry in searchResult.getChildEntries(*Group.ChildFilter.getDefaults(context))) {
entry.entryKDB?.let { entry.entryKDB?.let {
cursorKDB?.addEntry(it) cursorKDB?.addEntry(it)
} }
@@ -424,25 +430,21 @@ class Database {
fun getEntryFrom(cursor: Cursor): Entry? { fun getEntryFrom(cursor: Cursor): Entry? {
val iconFactory = mDatabaseKDB?.iconFactory ?: mDatabaseKDBX?.iconFactory ?: IconImageFactory() val iconFactory = mDatabaseKDB?.iconFactory ?: mDatabaseKDBX?.iconFactory ?: IconImageFactory()
val entry = createEntry()
// TODO invert field reference manager return createEntry()?.apply {
entry?.let { entryVersioned -> startManageEntry(this)
startManageEntry(entryVersioned)
mDatabaseKDB?.let { mDatabaseKDB?.let {
entryVersioned.entryKDB?.let { entryKDB -> entryKDB?.let { entryKDB ->
(cursor as EntryCursorKDB).populateEntry(entryKDB, iconFactory) (cursor as EntryCursorKDB).populateEntry(entryKDB, iconFactory)
} }
} }
mDatabaseKDBX?.let { mDatabaseKDBX?.let {
entryVersioned.entryKDBX?.let { entryKDBX -> entryKDBX?.let { entryKDBX ->
(cursor as EntryCursorKDBX).populateEntry(entryKDBX, iconFactory) (cursor as EntryCursorKDBX).populateEntry(entryKDBX, iconFactory)
} }
} }
stopManageEntry(entryVersioned) stopManageEntry(this)
} }
return entry
} }
@Throws(DatabaseOutputException::class) @Throws(DatabaseOutputException::class)

View File

@@ -23,6 +23,7 @@ import android.content.res.Resources
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import androidx.core.os.ConfigurationCompat import androidx.core.os.ConfigurationCompat
import java.text.SimpleDateFormat
import java.util.* import java.util.*
class DateInstant : Parcelable { class DateInstant : Parcelable {
@@ -44,6 +45,10 @@ class DateInstant : Parcelable {
jDate = Date(millis) jDate = Date(millis)
} }
constructor(string: String) {
jDate = dateFormat.parse(string)
}
constructor() { constructor() {
jDate = Date() jDate = Date()
} }
@@ -84,12 +89,13 @@ class DateInstant : Parcelable {
} }
override fun toString(): String { override fun toString(): String {
return jDate.toString() return dateFormat.format(jDate)
} }
companion object { companion object {
val NEVER_EXPIRE = neverExpire val NEVER_EXPIRE = neverExpire
private val dateFormat = SimpleDateFormat.getDateTimeInstance()
private val neverExpire: DateInstant private val neverExpire: DateInstant
get() { get() {

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element package com.kunzisoft.keepass.database.element
import android.os.Parcel import android.os.Parcel
@@ -13,7 +32,9 @@ import com.kunzisoft.keepass.database.element.node.Node
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.EntryAttachment
import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpElement
@@ -209,12 +230,27 @@ class Entry : Node, EntryVersionedInterface<Group> {
return title == PMS_TAN_ENTRY && username.isNotEmpty() return title == PMS_TAN_ENTRY && username.isNotEmpty()
} }
/**
* {@inheritDoc}
* Get the display title from an entry, <br></br>
* [.startManageEntry] and [.stopManageEntry] must be called
* before and after [.getVisualTitle]
*/
fun getVisualTitle(): String { fun getVisualTitle(): String {
return getVisualTitle(isTan(), return if (isTan()) {
title, "$PMS_TAN_ENTRY $username"
username, } else {
url, if (title.isEmpty())
nodeId.toString()) if (username.isEmpty())
if (url.isEmpty())
nodeId.toString()
else
url
else
username
else
title
}
} }
/* /*
@@ -284,10 +320,29 @@ class Entry : Node, EntryVersionedInterface<Group> {
entryKDBX?.stopToManageFieldReferences() entryKDBX?.stopToManageFieldReferences()
} }
fun getAttachments(): ArrayList<EntryAttachment> {
val attachments = ArrayList<EntryAttachment>()
val binaryDescriptionKDB = entryKDB?.binaryDescription ?: ""
val binaryKDB = entryKDB?.binaryData
if (binaryKDB != null) {
attachments.add(EntryAttachment(binaryDescriptionKDB, binaryKDB))
}
val actionEach = object : (Map.Entry<String, BinaryAttachment>)->Unit {
override fun invoke(mapEntry: Map.Entry<String, BinaryAttachment>) {
attachments.add(EntryAttachment(mapEntry.key, mapEntry.value))
}
}
entryKDBX?.binaries?.forEach(actionEach)
return attachments
}
fun getHistory(): ArrayList<Entry> { fun getHistory(): ArrayList<Entry> {
val history = ArrayList<Entry>() val history = ArrayList<Entry>()
val entryV4History = entryKDBX?.history ?: ArrayList() val entryKDBXHistory = entryKDBX?.history ?: ArrayList()
for (entryHistory in entryV4History) { for (entryHistory in entryKDBXHistory) {
history.add(Entry(entryHistory)) history.add(Entry(entryHistory))
} }
return history return history
@@ -299,6 +354,10 @@ class Entry : Node, EntryVersionedInterface<Group> {
} }
} }
fun removeEntryFromHistory(position: Int) {
entryKDBX?.removeEntryFromHistory(position)
}
fun removeAllHistory() { fun removeAllHistory() {
entryKDBX?.removeAllHistory() entryKDBX?.removeAllHistory()
} }
@@ -379,28 +438,5 @@ class Entry : Node, EntryVersionedInterface<Group> {
} }
const val PMS_TAN_ENTRY = "<TAN>" const val PMS_TAN_ENTRY = "<TAN>"
/**
* {@inheritDoc}
* Get the display title from an entry, <br></br>
* [.startManageEntry] and [.stopManageEntry] must be called
* before and after [.getVisualTitle]
*/
fun getVisualTitle(isTan: Boolean, title: String, userName: String, url: String, id: String): String {
return if (isTan) {
"$PMS_TAN_ENTRY $userName"
} else {
if (title.isEmpty())
if (userName.isEmpty())
if (url.isEmpty())
id
else
url
else
userName
else
title
}
}
} }
} }

View File

@@ -1,5 +1,25 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element package com.kunzisoft.keepass.database.element
import android.content.Context
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
@@ -8,6 +28,7 @@ import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.icon.IconImage
import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.database.element.icon.IconImageStandard
import com.kunzisoft.keepass.database.element.node.* import com.kunzisoft.keepass.database.element.node.*
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@@ -57,6 +78,20 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
groupKDBX = parcel.readParcelable(GroupKDBX::class.java.classLoader) groupKDBX = parcel.readParcelable(GroupKDBX::class.java.classLoader)
} }
enum class ChildFilter {
META_STREAM, EXPIRED;
companion object {
fun getDefaults(context: Context): Array<ChildFilter> {
return if (PreferencesUtil.showExpiredEntries(context)) {
arrayOf(META_STREAM)
} else {
arrayOf(META_STREAM, EXPIRED)
}
}
}
}
companion object CREATOR : Parcelable.Creator<Group> { companion object CREATOR : Parcelable.Creator<Group> {
override fun createFromParcel(parcel: Parcel): Group { override fun createFromParcel(parcel: Parcel): Group {
return Group(parcel) return Group(parcel)
@@ -204,38 +239,45 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
} }
override fun getChildEntries(): MutableList<Entry> { override fun getChildEntries(): MutableList<Entry> {
return getChildEntries(false) // To cal function with vararg
return getChildEntries(*emptyArray<ChildFilter>())
} }
fun getChildEntries(withoutMetaStream: Boolean): MutableList<Entry> { fun getChildEntries(vararg filter: ChildFilter): MutableList<Entry> {
val children = ArrayList<Entry>() val children = ArrayList<Entry>()
val withoutMetaStream = filter.contains(ChildFilter.META_STREAM)
val showExpiredEntries = !filter.contains(ChildFilter.EXPIRED)
groupKDB?.getChildEntries()?.forEach { groupKDB?.getChildEntries()?.forEach {
val entryToAddAsChild = Entry(it) val entryToAddAsChild = Entry(it)
if (!withoutMetaStream || (withoutMetaStream && !entryToAddAsChild.isMetaStream)) if ((!withoutMetaStream || (withoutMetaStream && !entryToAddAsChild.isMetaStream))
&& (!entryToAddAsChild.isCurrentlyExpires or showExpiredEntries))
children.add(entryToAddAsChild) children.add(entryToAddAsChild)
} }
groupKDBX?.getChildEntries()?.forEach { groupKDBX?.getChildEntries()?.forEach {
children.add(Entry(it)) val entryToAddAsChild = Entry(it)
if (!entryToAddAsChild.isCurrentlyExpires or showExpiredEntries)
children.add(entryToAddAsChild)
} }
return children return children
} }
/** /**
* Filter MetaStream entries and return children * Filter entries and return children
* @return List of direct children (one level below) as NodeVersioned * @return List of direct children (one level below) as NodeVersioned
*/ */
fun getChildren(withoutMetaStream: Boolean = true): List<Node> { fun getChildren(vararg filter: ChildFilter): List<Node> {
val children = ArrayList<Node>() val children = ArrayList<Node>()
children.addAll(getChildGroups()) children.addAll(getChildGroups())
groupKDB?.let { groupKDB?.let {
children.addAll(getChildEntries(withoutMetaStream)) children.addAll(getChildEntries(*filter))
} }
groupKDBX?.let { groupKDBX?.let {
// No MetasStream in V4 // No MetasStream in V4
children.addAll(getChildEntries(withoutMetaStream)) children.addAll(getChildEntries(*filter))
} }
return children return children

View File

@@ -22,12 +22,12 @@ package com.kunzisoft.keepass.database.element.database
import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory
import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned import com.kunzisoft.keepass.database.element.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.stream.NullOutputStream import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@@ -267,6 +267,8 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
private const val DEFAULT_ENCRYPTION_ROUNDS = 300 private const val DEFAULT_ENCRYPTION_ROUNDS = 300
const val BUFFER_SIZE_BYTES = 3 * 128
/** /**
* Encrypt the master key a few times to make brute-force key-search harder * Encrypt the master key a few times to make brute-force key-search harder
* @throws IOException * @throws IOException

View File

@@ -181,7 +181,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} }
CompressionAlgorithm.GZip -> { CompressionAlgorithm.GZip -> {
// To compress, create a new binary with file // To compress, create a new binary with file
binary.compress() binary.compress(BUFFER_SIZE_BYTES)
} }
} }
} }
@@ -189,7 +189,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
when (newCompression) { when (newCompression) {
CompressionAlgorithm.None -> { CompressionAlgorithm.None -> {
// To decompress, create a new binary with file // To decompress, create a new binary with file
binary.decompress() binary.decompress(BUFFER_SIZE_BYTES)
} }
CompressionAlgorithm.GZip -> { CompressionAlgorithm.GZip -> {
} }
@@ -562,7 +562,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
private const val KeyElementName = "Key" private const val KeyElementName = "Key"
private const val KeyDataElementName = "Data" private const val KeyDataElementName = "Data"
const val BASE_64_FLAG = Base64.DEFAULT const val BASE_64_FLAG = Base64.NO_WRAP
const val BUFFER_SIZE_BYTES = 3 * 128 const val BUFFER_SIZE_BYTES = 3 * 128
} }

View File

@@ -26,6 +26,7 @@ import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeKDBInterface import com.kunzisoft.keepass.database.element.node.NodeKDBInterface
import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import java.util.* import java.util.*
/** /**
@@ -51,19 +52,15 @@ import java.util.*
*/ */
class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface { class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
/** A string describing what is in pBinaryData */ /** A string describing what is in binaryData */
var binaryDesc = "" var binaryDescription = ""
/** var binaryData: BinaryAttachment? = null
* @return the actual binaryData byte array.
*/
var binaryData: ByteArray = ByteArray(0)
// Determine if this is a MetaStream entry // Determine if this is a MetaStream entry
val isMetaStream: Boolean val isMetaStream: Boolean
get() { get() {
if (binaryData.contentEquals(ByteArray(0))) return false
if (notes.isEmpty()) return false if (notes.isEmpty()) return false
if (binaryDesc != PMS_ID_BINDESC) return false if (binaryDescription != PMS_ID_BINDESC) return false
if (title.isEmpty()) return false if (title.isEmpty()) return false
if (title != PMS_ID_TITLE) return false if (title != PMS_ID_TITLE) return false
if (username.isEmpty()) return false if (username.isEmpty()) return false
@@ -88,9 +85,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
password = parcel.readString() ?: password password = parcel.readString() ?: password
url = parcel.readString() ?: url url = parcel.readString() ?: url
notes = parcel.readString() ?: notes notes = parcel.readString() ?: notes
binaryDesc = parcel.readString() ?: binaryDesc binaryDescription = parcel.readString() ?: binaryDescription
binaryData = ByteArray(parcel.readInt()) binaryData = parcel.readParcelable(BinaryAttachment::class.java.classLoader)
parcel.readByteArray(binaryData)
} }
override fun readParentParcelable(parcel: Parcel): GroupKDB? { override fun readParentParcelable(parcel: Parcel): GroupKDB? {
@@ -108,9 +104,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
dest.writeString(password) dest.writeString(password)
dest.writeString(url) dest.writeString(url)
dest.writeString(notes) dest.writeString(notes)
dest.writeString(binaryDesc) dest.writeString(binaryDescription)
dest.writeInt(binaryData.size) dest.writeParcelable(binaryData, flags)
dest.writeByteArray(binaryData)
} }
fun updateWith(source: EntryKDB) { fun updateWith(source: EntryKDB) {
@@ -120,11 +115,8 @@ class EntryKDB : EntryVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
password = source.password password = source.password
url = source.url url = source.url
notes = source.notes notes = source.notes
binaryDesc = source.binaryDesc binaryDescription = source.binaryDescription
binaryData = source.binaryData
val descLen = source.binaryData.size
binaryData = ByteArray(descLen)
System.arraycopy(source.binaryData, 0, binaryData, 0, descLen)
} }
override var username = "" override var username = ""

View File

@@ -304,6 +304,10 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
history.add(entry) history.add(entry)
} }
fun removeEntryFromHistory(position: Int) {
history.removeAt(position)
}
fun removeAllHistory() { fun removeAllHistory() {
history.clear() history.clear()
} }

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.entry package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel import android.os.Parcel

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.entry package com.kunzisoft.keepass.database.element.entry
import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.group package com.kunzisoft.keepass.database.element.group
import android.os.Parcel import android.os.Parcel

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.group package com.kunzisoft.keepass.database.element.group
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.element.Group
@@ -8,7 +27,7 @@ interface Node: NodeVersionedInterface<Group> {
val nodePositionInParent: Int val nodePositionInParent: Int
get() { get() {
parent?.getChildren(true)?.let { children -> parent?.getChildren(Group.ChildFilter.META_STREAM)?.let { children ->
children.forEachIndexed { index, nodeVersioned -> children.forEachIndexed { index, nodeVersioned ->
if (nodeVersioned.nodeId == this.nodeId) if (nodeVersioned.nodeId == this.nodeId)
return index return index

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.DateInstant

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.element.node package com.kunzisoft.keepass.database.element.node
import android.os.Parcelable import android.os.Parcelable

View File

@@ -19,11 +19,11 @@
*/ */
package com.kunzisoft.keepass.database.element.security package com.kunzisoft.keepass.database.element.security
import android.content.ContentResolver
import android.net.Uri
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BUFFER_SIZE_BYTES import com.kunzisoft.keepass.stream.readBytes
import com.kunzisoft.keepass.stream.ReadBytes
import com.kunzisoft.keepass.stream.readFromStream
import java.io.* import java.io.*
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
@@ -73,19 +73,22 @@ class BinaryAttachment : Parcelable {
} }
@Throws(IOException::class) @Throws(IOException::class)
fun compress() { fun compress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
if (dataFile != null) { if (dataFile != null) {
// To compress, create a new binary with file // To compress, create a new binary with file
if (isCompressed != true) { if (isCompressed != true) {
val fileBinaryCompress = File(dataFile!!.parent, dataFile!!.name + "_temp") val fileBinaryCompress = File(dataFile!!.parent, dataFile!!.name + "_temp")
val outputStream = GZIPOutputStream(FileOutputStream(fileBinaryCompress)) var outputStream: GZIPOutputStream? = null
readFromStream(getInputDataStream(), BUFFER_SIZE_BYTES, var inputStream: InputStream? = null
object : ReadBytes { try {
override fun read(buffer: ByteArray) { outputStream = GZIPOutputStream(FileOutputStream(fileBinaryCompress))
inputStream = getInputDataStream()
inputStream.readBytes(bufferSize) { buffer ->
outputStream.write(buffer) outputStream.write(buffer)
} }
}) } finally {
outputStream.close() inputStream?.close()
outputStream?.close()
// Remove unGzip file // Remove unGzip file
if (dataFile!!.delete()) { if (dataFile!!.delete()) {
@@ -97,20 +100,24 @@ class BinaryAttachment : Parcelable {
} }
} }
} }
}
@Throws(IOException::class) @Throws(IOException::class)
fun decompress() { fun decompress(bufferSize: Int = DEFAULT_BUFFER_SIZE) {
if (dataFile != null) { if (dataFile != null) {
if (isCompressed != false) { if (isCompressed != false) {
val fileBinaryDecompress = File(dataFile!!.parent, dataFile!!.name + "_temp") val fileBinaryDecompress = File(dataFile!!.parent, dataFile!!.name + "_temp")
val outputStream = FileOutputStream(fileBinaryDecompress) var outputStream: FileOutputStream? = null
readFromStream(GZIPInputStream(getInputDataStream()), BUFFER_SIZE_BYTES, var inputStream: GZIPInputStream? = null
object : ReadBytes { try {
override fun read(buffer: ByteArray) { outputStream = FileOutputStream(fileBinaryDecompress)
inputStream = GZIPInputStream(getInputDataStream())
inputStream.readBytes(bufferSize) { buffer ->
outputStream.write(buffer) outputStream.write(buffer)
} }
}) } finally {
outputStream.close() inputStream?.close()
outputStream?.close()
// Remove gzip file // Remove gzip file
if (dataFile!!.delete()) { if (dataFile!!.delete()) {
@@ -122,6 +129,33 @@ class BinaryAttachment : Parcelable {
} }
} }
} }
}
fun download(createdFileUri: Uri,
contentResolver: ContentResolver,
bufferSize: Int = DEFAULT_BUFFER_SIZE,
update: ((percent: Int)->Unit)? = null) {
var dataDownloaded = 0
contentResolver.openOutputStream(createdFileUri).use { outputStream ->
outputStream?.let { fileOutputStream ->
if (isCompressed == true) {
GZIPInputStream(getInputDataStream())
} else {
getInputDataStream()
}.use { inputStream ->
inputStream.readBytes(bufferSize) { buffer ->
fileOutputStream.write(buffer)
dataDownloaded += buffer.size
try {
val percentDownload = (100 * dataDownloaded / length()).toInt()
update?.invoke(percentDownload)
} catch (e: Exception) {}
}
}
}
}
}
@Throws(IOException::class) @Throws(IOException::class)
fun clear() { fun clear() {

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception package com.kunzisoft.keepass.database.exception
import android.content.res.Resources import android.content.res.Resources

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.database.exception package com.kunzisoft.keepass.database.exception
import java.io.IOException import java.io.IOException

View File

@@ -21,9 +21,11 @@
package com.kunzisoft.keepass.database.file package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.stream.LEDataInputStream import com.kunzisoft.keepass.stream.bytes4ToInt
import com.kunzisoft.keepass.stream.readBytesLength
import com.kunzisoft.keepass.stream.readBytes4ToInt
import java.io.IOException import java.io.IOException
import java.io.InputStream
class DatabaseHeaderKDB : DatabaseHeader() { class DatabaseHeaderKDB : DatabaseHeader() {
@@ -47,30 +49,25 @@ class DatabaseHeaderKDB : DatabaseHeader() {
*/ */
var contentsHash = ByteArray(32) var contentsHash = ByteArray(32)
// As UInt
var numKeyEncRounds: Int = 0 var numKeyEncRounds: Int = 0
/** /**
* Parse given buf, as read from file. * Parse given buf, as read from file.
* @param buf
* @throws IOException
*/ */
@Throws(IOException::class) @Throws(IOException::class)
fun loadFromFile(buf: ByteArray, offset: Int) { fun loadFromFile(inputStream: InputStream) {
signature1 = LEDataInputStream.readInt(buf, offset) signature1 = inputStream.readBytes4ToInt() // 4 bytes
signature2 = LEDataInputStream.readInt(buf, offset + 4) signature2 = inputStream.readBytes4ToInt() // 4 bytes
flags = LEDataInputStream.readInt(buf, offset + 8) flags = inputStream.readBytes4ToInt() // 4 bytes
version = LEDataInputStream.readInt(buf, offset + 12) version = inputStream.readBytes4ToInt() // 4 bytes
masterSeed = inputStream.readBytesLength(16) // 16 bytes
System.arraycopy(buf, offset + 16, masterSeed, 0, 16) encryptionIV = inputStream.readBytesLength(16) // 16 bytes
System.arraycopy(buf, offset + 32, encryptionIV, 0, 16) numGroups = inputStream.readBytes4ToInt() // 4 bytes
numEntries = inputStream.readBytes4ToInt() // 4 bytes
numGroups = LEDataInputStream.readInt(buf, offset + 48) contentsHash = inputStream.readBytesLength(32) // 32 bytes
numEntries = LEDataInputStream.readInt(buf, offset + 52) transformSeed = inputStream.readBytesLength(32) // 32 bytes
numKeyEncRounds = inputStream.readBytes4ToInt()
System.arraycopy(buf, offset + 56, contentsHash, 0, 32)
System.arraycopy(buf, offset + 88, transformSeed, 0, 32)
numKeyEncRounds = LEDataInputStream.readInt(buf, offset + 120)
if (numKeyEncRounds < 0) { if (numKeyEncRounds < 0) {
// TODO: Really treat this like an unsigned integer // TODO: Really treat this like an unsigned integer
throw IOException("Does not support more than " + Integer.MAX_VALUE + " rounds.") throw IOException("Does not support more than " + Integer.MAX_VALUE + " rounds.")

View File

@@ -30,10 +30,7 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.exception.VersionDatabaseException import com.kunzisoft.keepass.database.exception.VersionDatabaseException
import com.kunzisoft.keepass.stream.CopyInputStream import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.stream.HmacBlockStream
import com.kunzisoft.keepass.stream.LEDataInputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@@ -148,7 +145,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
val headerBOS = ByteArrayOutputStream() val headerBOS = ByteArrayOutputStream()
val copyInputStream = CopyInputStream(inputStream, headerBOS) val copyInputStream = CopyInputStream(inputStream, headerBOS)
val digestInputStream = DigestInputStream(copyInputStream, messageDigest) val digestInputStream = DigestInputStream(copyInputStream, messageDigest)
val littleEndianDataInputStream = LEDataInputStream(digestInputStream) val littleEndianDataInputStream = LittleEndianDataInputStream(digestInputStream)
val sig1 = littleEndianDataInputStream.readInt() val sig1 = littleEndianDataInputStream.readInt()
val sig2 = littleEndianDataInputStream.readInt() val sig2 = littleEndianDataInputStream.readInt()
@@ -172,7 +169,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun readHeaderField(dis: LEDataInputStream): Boolean { private fun readHeaderField(dis: LittleEndianDataInputStream): Boolean {
val fieldID = dis.read().toByte() val fieldID = dis.read().toByte()
val fieldSize: Int = if (version < FILE_VERSION_32_4) { val fieldSize: Int = if (version < FILE_VERSION_32_4) {
@@ -243,12 +240,12 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid cipher ID.") throw IOException("Invalid cipher ID.")
} }
databaseV4.dataCipher = DatabaseInputOutputUtils.bytesToUuid(pbId) databaseV4.dataCipher = bytes16ToUuid(pbId)
} }
private fun setTransformRound(roundsByte: ByteArray?) { private fun setTransformRound(roundsByte: ByteArray) {
assignAesKdfEngineIfNotExists() assignAesKdfEngineIfNotExists()
val rounds = LEDataInputStream.readLong(roundsByte!!, 0) val rounds = bytes64ToLong(roundsByte)
databaseV4.kdfParameters?.setUInt64(AesKdf.PARAM_ROUNDS, rounds) databaseV4.kdfParameters?.setUInt64(AesKdf.PARAM_ROUNDS, rounds)
databaseV4.numberKeyEncryptionRounds = rounds databaseV4.numberKeyEncryptionRounds = rounds
} }
@@ -259,7 +256,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid compression flags.") throw IOException("Invalid compression flags.")
} }
val flag = LEDataInputStream.readInt(pbFlags, 0) val flag = bytes4ToInt(pbFlags)
if (flag < 0 || flag >= CompressionAlgorithm.values().size) { if (flag < 0 || flag >= CompressionAlgorithm.values().size) {
throw IOException("Unrecognized compression flag.") throw IOException("Unrecognized compression flag.")
} }
@@ -275,7 +272,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
throw IOException("Invalid stream id.") throw IOException("Invalid stream id.")
} }
val id = LEDataInputStream.readInt(streamID, 0) val id = bytes4ToInt(streamID)
if (id < 0 || id >= CrsAlgorithm.values().size) { if (id < 0 || id >= CrsAlgorithm.values().size) {
throw IOException("Invalid stream id.") throw IOException("Invalid stream id.")
} }
@@ -295,6 +292,9 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
} }
companion object { companion object {
var ULONG_MAX_VALUE: Long = -1
const val DBSIG_PRE2 = -0x4ab4049a const val DBSIG_PRE2 = -0x4ab4049a
const val DBSIG_2 = -0x4ab40499 const val DBSIG_2 = -0x4ab40499
@@ -323,7 +323,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
@Throws(IOException::class) @Throws(IOException::class)
fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray { fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray {
val blockKey = HmacBlockStream.GetHmacKey64(key, DatabaseInputOutputUtils.ULONG_MAX_VALUE) val blockKey = HmacBlockStream.getHmacKey64(key, ULONG_MAX_VALUE)
val hmac: Mac val hmac: Mac
try { try {

View File

@@ -22,9 +22,11 @@ package com.kunzisoft.keepass.database.file.input
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.database.exception.LoadDatabaseException
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import java.io.File
import java.io.InputStream import java.io.InputStream
abstract class DatabaseInput<PwDb : DatabaseVersioned<*, *, *, *>> { abstract class DatabaseInput<PwDb : DatabaseVersioned<*, *, *, *>>
(protected val cacheDirectory: File) {
/** /**
* Load a versioned database file, return contents in a new DatabaseVersioned. * Load a versioned database file, return contents in a new DatabaseVersioned.

View File

@@ -16,36 +16,10 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. * along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
* *
*
Derived from
KeePass for J2ME
Copyright 2007 Naomaru Itoi <nao@phoneid.org>
This file was derived from
Java clone of KeePass - A KeePass file viewer for Java
Copyright 2006 Bill Zwicky <billzwicky@users.sourceforge.net>
This program 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; version 2
This program 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 this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
package com.kunzisoft.keepass.database.file.input package com.kunzisoft.keepass.database.file.input
import android.util.Log
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.database.element.database.DatabaseKDB import com.kunzisoft.keepass.database.element.database.DatabaseKDB
@@ -53,28 +27,29 @@ import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.node.NodeIdInt import com.kunzisoft.keepass.database.element.node.NodeIdInt
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm
import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeader import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.LEDataInputStream import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.stream.NullOutputStream
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import org.joda.time.Instant
import java.io.*
import javax.crypto.* import java.security.*
import java.util.*
import javax.crypto.Cipher
import javax.crypto.NoSuchPaddingException
import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.IvParameterSpec
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
import java.io.IOException
import java.io.InputStream
import java.io.UnsupportedEncodingException
import java.security.*
import java.util.Arrays
/** /**
* Load a KDB database file. * Load a KDB database file.
*/ */
class DatabaseInputKDB : DatabaseInput<DatabaseKDB>() { class DatabaseInputKDB(cacheDirectory: File,
private val fixDuplicateUUID: Boolean = false)
: DatabaseInput<DatabaseKDB>(cacheDirectory) {
private lateinit var mDatabaseToOpen: DatabaseKDB private lateinit var mDatabaseToOpen: DatabaseKDB
@@ -87,52 +62,63 @@ class DatabaseInputKDB : DatabaseInput<DatabaseKDB>() {
try { try {
// Load entire file, most of it's encrypted. // Load entire file, most of it's encrypted.
val fileSize = databaseInputStream.available() val fileSize = databaseInputStream.available()
val filebuf = ByteArray(fileSize + 16) // Pad with a blocksize (Twofish uses 128 bits), since Android 4.3 tries to write more to the buffer
databaseInputStream.read(filebuf, 0, fileSize) // TODO remove
databaseInputStream.close()
// Parse header (unencrypted) // Parse header (unencrypted)
if (fileSize < DatabaseHeaderKDB.BUF_SIZE) if (fileSize < DatabaseHeaderKDB.BUF_SIZE)
throw IOException("File too short for header") throw IOException("File too short for header")
val hdr = DatabaseHeaderKDB() val header = DatabaseHeaderKDB()
hdr.loadFromFile(filebuf, 0) header.loadFromFile(databaseInputStream)
if (hdr.signature1 != DatabaseHeader.PWM_DBSIG_1 || hdr.signature2 != DatabaseHeaderKDB.DBSIG_2) { val contentSize = databaseInputStream.available()
if (fileSize != (contentSize + DatabaseHeaderKDB.BUF_SIZE))
throw IOException("Header corrupted")
if (header.signature1 != DatabaseHeader.PWM_DBSIG_1
|| header.signature2 != DatabaseHeaderKDB.DBSIG_2) {
throw SignatureDatabaseException() throw SignatureDatabaseException()
} }
if (!hdr.matchesVersion()) { if (!header.matchesVersion()) {
throw VersionDatabaseException() throw VersionDatabaseException()
} }
progressTaskUpdater?.updateMessage(R.string.retrieving_db_key) progressTaskUpdater?.updateMessage(R.string.retrieving_db_key)
mDatabaseToOpen = DatabaseKDB() mDatabaseToOpen = DatabaseKDB()
mDatabaseToOpen.changeDuplicateId = fixDuplicateUUID
mDatabaseToOpen.retrieveMasterKey(password, keyInputStream) mDatabaseToOpen.retrieveMasterKey(password, keyInputStream)
// Select algorithm // Select algorithm
when { when {
hdr.flags and DatabaseHeaderKDB.FLAG_RIJNDAEL != 0 -> mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.AESRijndael header.flags and DatabaseHeaderKDB.FLAG_RIJNDAEL != 0 -> {
hdr.flags and DatabaseHeaderKDB.FLAG_TWOFISH != 0 -> mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.Twofish mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.AESRijndael
}
header.flags and DatabaseHeaderKDB.FLAG_TWOFISH != 0 -> {
mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.Twofish
}
else -> throw InvalidAlgorithmDatabaseException() else -> throw InvalidAlgorithmDatabaseException()
} }
mDatabaseToOpen.numberKeyEncryptionRounds = hdr.numKeyEncRounds.toLong() mDatabaseToOpen.numberKeyEncryptionRounds = header.numKeyEncRounds.toLong()
// Generate transformedMasterKey from masterKey // Generate transformedMasterKey from masterKey
mDatabaseToOpen.makeFinalKey(hdr.masterSeed, hdr.transformSeed, mDatabaseToOpen.numberKeyEncryptionRounds) mDatabaseToOpen.makeFinalKey(
header.masterSeed,
header.transformSeed,
mDatabaseToOpen.numberKeyEncryptionRounds)
progressTaskUpdater?.updateMessage(R.string.decrypting_db) progressTaskUpdater?.updateMessage(R.string.decrypting_db)
// Initialize Rijndael algorithm // Initialize Rijndael algorithm
val cipher: Cipher val cipher: Cipher = try {
try { when {
if (mDatabaseToOpen.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael) { mDatabaseToOpen.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> {
cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding") CipherFactory.getInstance("AES/CBC/PKCS5Padding")
} else if (mDatabaseToOpen.encryptionAlgorithm === EncryptionAlgorithm.Twofish) { }
cipher = CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING") mDatabaseToOpen.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> {
} else { CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING")
throw IOException("Encryption algorithm is not supported") }
else -> throw IOException("Encryption algorithm is not supported")
} }
} catch (e1: NoSuchAlgorithmException) { } catch (e1: NoSuchAlgorithmException) {
throw IOException("No such algorithm") throw IOException("No such algorithm")
} catch (e1: NoSuchPaddingException) { } catch (e1: NoSuchPaddingException) {
@@ -140,98 +126,231 @@ class DatabaseInputKDB : DatabaseInput<DatabaseKDB>() {
} }
try { try {
cipher.init(Cipher.DECRYPT_MODE, SecretKeySpec(mDatabaseToOpen.finalKey, "AES"), IvParameterSpec(hdr.encryptionIV)) cipher.init(Cipher.DECRYPT_MODE,
SecretKeySpec(mDatabaseToOpen.finalKey, "AES"),
IvParameterSpec(header.encryptionIV))
} catch (e1: InvalidKeyException) { } catch (e1: InvalidKeyException) {
throw IOException("Invalid key") throw IOException("Invalid key")
} catch (e1: InvalidAlgorithmParameterException) { } catch (e1: InvalidAlgorithmParameterException) {
throw IOException("Invalid algorithm parameter.") throw IOException("Invalid algorithm parameter.")
} }
// Decrypt! The first bytes aren't encrypted (that's the header) val messageDigest: MessageDigest
val encryptedPartSize: Int
try { try {
encryptedPartSize = cipher.doFinal(filebuf, DatabaseHeaderKDB.BUF_SIZE, fileSize - DatabaseHeaderKDB.BUF_SIZE, filebuf, DatabaseHeaderKDB.BUF_SIZE) messageDigest = MessageDigest.getInstance("SHA-256")
} catch (e1: ShortBufferException) {
throw IOException("Buffer too short")
} catch (e1: IllegalBlockSizeException) {
throw IOException("Invalid block size")
} catch (e1: BadPaddingException) {
throw InvalidCredentialsDatabaseException()
}
val md: MessageDigest
try {
md = MessageDigest.getInstance("SHA-256")
} catch (e: NoSuchAlgorithmException) { } catch (e: NoSuchAlgorithmException) {
throw IOException("No SHA-256 algorithm") throw IOException("No SHA-256 algorithm")
} }
val nos = NullOutputStream() // Decrypt content
val dos = DigestOutputStream(nos, md) val cipherInputStream = BufferedInputStream(
dos.write(filebuf, DatabaseHeaderKDB.BUF_SIZE, encryptedPartSize) DigestInputStream(
dos.close() BetterCipherInputStream(databaseInputStream, cipher),
val hash = md.digest() messageDigest
)
)
if (!Arrays.equals(hash, hdr.contentsHash)) { /* TODO checksum
// Add a mark to the content start
if (!cipherInputStream.markSupported()) {
throw IOException("Input stream does not support mark.")
}
cipherInputStream.mark(cipherInputStream.available() +1)
// Consume all data to get the digest
var numberRead = 0
while (numberRead > -1) {
numberRead = cipherInputStream.read(ByteArray(1024))
}
Log.w(TAG, "Database file did not decrypt correctly. (checksum code is broken)") // Check sum
if (!Arrays.equals(messageDigest.digest(), header.contentsHash)) {
throw InvalidCredentialsDatabaseException() throw InvalidCredentialsDatabaseException()
} }
// Back to the content start
cipherInputStream.reset()
*/
// New manual root because KDB contains multiple root groups (here available with getRootGroups()) // New manual root because KDB contains multiple root groups (here available with getRootGroups())
val newRoot = mDatabaseToOpen.createGroup() val newRoot = mDatabaseToOpen.createGroup()
newRoot.level = -1 newRoot.level = -1
mDatabaseToOpen.rootGroup = newRoot mDatabaseToOpen.rootGroup = newRoot
// Import all groups // Import all nodes
var pos = DatabaseHeaderKDB.BUF_SIZE var newGroup: GroupKDB? = null
var newGrp = mDatabaseToOpen.createGroup() var newEntry: EntryKDB? = null
run { var currentGroupNumber = 0
var i = 0 var currentEntryNumber = 0
while (i < hdr.numGroups) { while (currentGroupNumber < header.numGroups
val fieldType = LEDataInputStream.readUShort(filebuf, pos) || currentEntryNumber < header.numEntries) {
pos += 2
val fieldSize = LEDataInputStream.readInt(filebuf, pos)
pos += 4
if (fieldType == 0xFFFF) { val fieldType = cipherInputStream.readBytes2ToUShort()
// End-Group record. Save group and count it. val fieldSize = cipherInputStream.readBytes4ToUInt().toInt()
mDatabaseToOpen.addGroupIndex(newGrp)
newGrp = mDatabaseToOpen.createGroup()
i++
} else {
readGroupField(mDatabaseToOpen, newGrp, fieldType, filebuf, pos)
}
pos += fieldSize
}
}
// Import all entries when (fieldType) {
var newEnt = mDatabaseToOpen.createEntry() 0x0000 -> {
var i = 0 cipherInputStream.readBytesLength(fieldSize)
while (i < hdr.numEntries) {
val fieldType = LEDataInputStream.readUShort(filebuf, pos)
val fieldSize = LEDataInputStream.readInt(filebuf, pos + 2)
if (fieldType == 0xFFFF) {
// End-Group record. Save group and count it.
mDatabaseToOpen.addEntryIndex(newEnt)
newEnt = mDatabaseToOpen.createEntry()
i++
} else {
readEntryField(mDatabaseToOpen, newEnt, filebuf, pos)
} }
pos += 2 + 4 + fieldSize 0x0001 -> {
// Create new node depending on byte number
when (fieldSize) {
4 -> {
newGroup = mDatabaseToOpen.createGroup().apply {
setGroupId(cipherInputStream.readBytes4ToInt())
}
}
16 -> {
newEntry = mDatabaseToOpen.createEntry().apply {
nodeId = NodeIdUUID(cipherInputStream.readBytes16ToUuid())
}
}
else -> {
throw UnsupportedEncodingException("Field type $fieldType")
}
}
}
0x0002 -> {
newGroup?.let { group ->
group.title = cipherInputStream.readBytesToString(fieldSize)
} ?:
newEntry?.let { entry ->
val groupKDB = mDatabaseToOpen.createGroup()
groupKDB.nodeId = NodeIdInt(cipherInputStream.readBytes4ToInt())
entry.parent = groupKDB
}
}
0x0003 -> {
newGroup?.let { group ->
group.creationTime = cipherInputStream.readBytes5ToDate()
} ?:
newEntry?.let { entry ->
var iconId = cipherInputStream.readBytes4ToInt()
// Clean up after bug that set icon ids to -1
if (iconId == -1) {
iconId = 0
}
entry.icon = mDatabaseToOpen.iconFactory.getIcon(iconId)
}
}
0x0004 -> {
newGroup?.let { group ->
group.lastModificationTime = cipherInputStream.readBytes5ToDate()
} ?:
newEntry?.let { entry ->
entry.title = cipherInputStream.readBytesToString(fieldSize)
}
}
0x0005 -> {
newGroup?.let { group ->
group.lastAccessTime = cipherInputStream.readBytes5ToDate()
} ?:
newEntry?.let { entry ->
entry.url = cipherInputStream.readBytesToString(fieldSize)
}
}
0x0006 -> {
newGroup?.let { group ->
group.expiryTime = cipherInputStream.readBytes5ToDate()
} ?:
newEntry?.let { entry ->
entry.username = cipherInputStream.readBytesToString(fieldSize)
}
}
0x0007 -> {
newGroup?.let { group ->
group.icon = mDatabaseToOpen.iconFactory.getIcon(cipherInputStream.readBytes4ToInt())
} ?:
newEntry?.let { entry ->
entry.password = cipherInputStream.readBytesToString(fieldSize,false)
}
}
0x0008 -> {
newGroup?.let { group ->
group.level = cipherInputStream.readBytes2ToUShort()
} ?:
newEntry?.let { entry ->
entry.notes = cipherInputStream.readBytesToString(fieldSize)
}
}
0x0009 -> {
newGroup?.let { group ->
group.flags = cipherInputStream.readBytes4ToInt()
} ?:
newEntry?.let { entry ->
entry.creationTime = cipherInputStream.readBytes5ToDate()
}
}
0x000A -> {
newEntry?.let { entry ->
entry.lastModificationTime = cipherInputStream.readBytes5ToDate()
}
}
0x000B -> {
newEntry?.let { entry ->
entry.lastAccessTime = cipherInputStream.readBytes5ToDate()
}
}
0x000C -> {
newEntry?.let { entry ->
entry.expiryTime = cipherInputStream.readBytes5ToDate()
}
}
0x000D -> {
newEntry?.let { entry ->
entry.binaryDescription = cipherInputStream.readBytesToString(fieldSize)
}
}
0x000E -> {
newEntry?.let { entry ->
if (fieldSize > 0) {
// Generate an unique new file with timestamp
val binaryFile = File(cacheDirectory,
Instant.now().millis.toString())
entry.binaryData = BinaryAttachment(binaryFile)
BufferedOutputStream(FileOutputStream(binaryFile)).use { outputStream ->
cipherInputStream.readBytes(fieldSize,
DatabaseKDB.BUFFER_SIZE_BYTES) { buffer ->
outputStream.write(buffer)
}
}
}
}
}
0xFFFF -> {
// End record. Save node and count it.
newGroup?.let { group ->
mDatabaseToOpen.addGroupIndex(group)
currentGroupNumber++
newGroup = null
}
newEntry?.let { entry ->
mDatabaseToOpen.addEntryIndex(entry)
currentEntryNumber++
newEntry = null
}
cipherInputStream.readBytesLength(fieldSize)
}
else -> {
throw UnsupportedEncodingException("Field type $fieldType")
}
}
}
// Check sum
if (!Arrays.equals(messageDigest.digest(), header.contentsHash)) {
throw InvalidCredentialsDatabaseException()
} }
constructTreeFromIndex() constructTreeFromIndex()
} catch (e: LoadDatabaseException) { } catch (e: LoadDatabaseException) {
mDatabaseToOpen.clearCache()
throw e throw e
} catch (e: IOException) { } catch (e: IOException) {
mDatabaseToOpen.clearCache()
throw IODatabaseException(e) throw IODatabaseException(e)
} catch (e: OutOfMemoryError) { } catch (e: OutOfMemoryError) {
mDatabaseToOpen.clearCache()
throw NoMemoryDatabaseException(e) throw NoMemoryDatabaseException(e)
} catch (e: Exception) { } catch (e: Exception) {
mDatabaseToOpen.clearCache()
throw LoadDatabaseException(e) throw LoadDatabaseException(e)
} }
@@ -279,71 +398,6 @@ class DatabaseInputKDB : DatabaseInput<DatabaseKDB>() {
} }
} }
/**
* Parse and save one record from binary file.
* @param buf
* @param offset
* @return If >0,
* @throws UnsupportedEncodingException
*/
@Throws(UnsupportedEncodingException::class)
private fun readGroupField(db: DatabaseKDB, grp: GroupKDB, fieldType: Int, buf: ByteArray, offset: Int) {
when (fieldType) {
0x0000 -> {
}
0x0001 -> grp.setGroupId(LEDataInputStream.readInt(buf, offset))
0x0002 -> grp.title = DatabaseInputOutputUtils.readCString(buf, offset)
0x0003 -> grp.creationTime = DatabaseInputOutputUtils.readCDate(buf, offset)
0x0004 -> grp.lastModificationTime = DatabaseInputOutputUtils.readCDate(buf, offset)
0x0005 -> grp.lastAccessTime = DatabaseInputOutputUtils.readCDate(buf, offset)
0x0006 -> grp.expiryTime = DatabaseInputOutputUtils.readCDate(buf, offset)
0x0007 -> grp.icon = db.iconFactory.getIcon(LEDataInputStream.readInt(buf, offset))
0x0008 -> grp.level = LEDataInputStream.readUShort(buf, offset)
0x0009 -> grp.flags = LEDataInputStream.readInt(buf, offset)
}// Ignore field
}
@Throws(UnsupportedEncodingException::class)
private fun readEntryField(db: DatabaseKDB, ent: EntryKDB, buf: ByteArray, offset: Int) {
var offsetMutable = offset
val fieldType = LEDataInputStream.readUShort(buf, offsetMutable)
offsetMutable += 2
val fieldSize = LEDataInputStream.readInt(buf, offsetMutable)
offsetMutable += 4
when (fieldType) {
0x0000 -> {
}
0x0001 -> ent.nodeId = NodeIdUUID(LEDataInputStream.readUuid(buf, offsetMutable))
0x0002 -> {
val groupKDB = mDatabaseToOpen.createGroup()
groupKDB.nodeId = NodeIdInt(LEDataInputStream.readInt(buf, offsetMutable))
ent.parent = groupKDB
}
0x0003 -> {
var iconId = LEDataInputStream.readInt(buf, offsetMutable)
// Clean up after bug that set icon ids to -1
if (iconId == -1) {
iconId = 0
}
ent.icon = db.iconFactory.getIcon(iconId)
}
0x0004 -> ent.title = DatabaseInputOutputUtils.readCString(buf, offsetMutable)
0x0005 -> ent.url = DatabaseInputOutputUtils.readCString(buf, offsetMutable)
0x0006 -> ent.username = DatabaseInputOutputUtils.readCString(buf, offsetMutable)
0x0007 -> ent.password = DatabaseInputOutputUtils.readPassword(buf, offsetMutable)
0x0008 -> ent.notes = DatabaseInputOutputUtils.readCString(buf, offsetMutable)
0x0009 -> ent.creationTime = DatabaseInputOutputUtils.readCDate(buf, offsetMutable)
0x000A -> ent.lastModificationTime = DatabaseInputOutputUtils.readCDate(buf, offsetMutable)
0x000B -> ent.lastAccessTime = DatabaseInputOutputUtils.readCDate(buf, offsetMutable)
0x000C -> ent.expiryTime = DatabaseInputOutputUtils.readCDate(buf, offsetMutable)
0x000D -> ent.binaryDesc = DatabaseInputOutputUtils.readCString(buf, offsetMutable)
0x000E -> ent.binaryData = DatabaseInputOutputUtils.readBytes(buf, offsetMutable, fieldSize)
}// Ignore field
}
companion object { companion object {
private val TAG = DatabaseInputKDB::class.java.name private val TAG = DatabaseInputKDB::class.java.name
} }

View File

@@ -24,7 +24,8 @@ import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.CipherFactory
import com.kunzisoft.keepass.crypto.StreamCipherFactory import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
@@ -37,12 +38,11 @@ import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.security.BinaryAttachment import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.* import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import org.spongycastle.crypto.StreamCipher import org.spongycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserException
@@ -54,10 +54,12 @@ import java.util.*
import java.util.zip.GZIPInputStream import java.util.zip.GZIPInputStream
import java.util.zip.GZIPOutputStream import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.CipherInputStream
import kotlin.math.min import kotlin.math.min
class DatabaseInputKDBX(private val streamDir: File, class DatabaseInputKDBX(cacheDirectory: File,
private val fixDuplicateUUID: Boolean = false) : DatabaseInput<DatabaseKDBX>() { private val fixDuplicateUUID: Boolean = false)
: DatabaseInput<DatabaseKDBX>(cacheDirectory) {
private var randomStream: StreamCipher? = null private var randomStream: StreamCipher? = null
private lateinit var mDatabase: DatabaseKDBX private lateinit var mDatabase: DatabaseKDBX
@@ -131,11 +133,11 @@ class DatabaseInputKDBX(private val streamDir: File,
if (mDatabase.kdbxVersion < DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (mDatabase.kdbxVersion < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
val decrypted = attachCipherStream(databaseInputStream, cipher) val decrypted = attachCipherStream(databaseInputStream, cipher)
val dataDecrypted = LEDataInputStream(decrypted) val dataDecrypted = LittleEndianDataInputStream(decrypted)
val storedStartBytes: ByteArray? val storedStartBytes: ByteArray?
try { try {
storedStartBytes = dataDecrypted.readBytes(32) storedStartBytes = dataDecrypted.readBytes(32)
if (storedStartBytes == null || storedStartBytes.size != 32) { if (storedStartBytes.size != 32) {
throw InvalidCredentialsDatabaseException() throw InvalidCredentialsDatabaseException()
} }
} catch (e: IOException) { } catch (e: IOException) {
@@ -148,7 +150,7 @@ class DatabaseInputKDBX(private val streamDir: File,
isPlain = HashedBlockInputStream(dataDecrypted) isPlain = HashedBlockInputStream(dataDecrypted)
} else { // KDBX 4 } else { // KDBX 4
val isData = LEDataInputStream(databaseInputStream) val isData = LittleEndianDataInputStream(databaseInputStream)
val storedHash = isData.readBytes(32) val storedHash = isData.readBytes(32)
if (!Arrays.equals(storedHash, hashOfHeader)) { if (!Arrays.equals(storedHash, hashOfHeader)) {
throw InvalidCredentialsDatabaseException() throw InvalidCredentialsDatabaseException()
@@ -157,7 +159,7 @@ class DatabaseInputKDBX(private val streamDir: File,
val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException() val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException()
val headerHmac = DatabaseHeaderKDBX.computeHeaderHmac(pbHeader, hmacKey) val headerHmac = DatabaseHeaderKDBX.computeHeaderHmac(pbHeader, hmacKey)
val storedHmac = isData.readBytes(32) val storedHmac = isData.readBytes(32)
if (storedHmac == null || storedHmac.size != 32) { if (storedHmac.size != 32) {
throw InvalidCredentialsDatabaseException() throw InvalidCredentialsDatabaseException()
} }
// Mac doesn't match // Mac doesn't match
@@ -207,12 +209,12 @@ class DatabaseInputKDBX(private val streamDir: File,
} }
private fun attachCipherStream(inputStream: InputStream, cipher: Cipher): InputStream { private fun attachCipherStream(inputStream: InputStream, cipher: Cipher): InputStream {
return BetterCipherInputStream(inputStream, cipher, 50 * 1024) return CipherInputStream(inputStream, cipher)
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun loadInnerHeader(inputStream: InputStream, header: DatabaseHeaderKDBX) { private fun loadInnerHeader(inputStream: InputStream, header: DatabaseHeaderKDBX) {
val lis = LEDataInputStream(inputStream) val lis = LittleEndianDataInputStream(inputStream)
while (true) { while (true) {
if (!readInnerHeader(lis, header)) break if (!readInnerHeader(lis, header)) break
@@ -220,7 +222,8 @@ class DatabaseInputKDBX(private val streamDir: File,
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun readInnerHeader(dataInputStream: LEDataInputStream, header: DatabaseHeaderKDBX): Boolean { private fun readInnerHeader(dataInputStream: LittleEndianDataInputStream,
header: DatabaseHeaderKDBX): Boolean {
val fieldId = dataInputStream.read().toByte() val fieldId = dataInputStream.read().toByte()
val size = dataInputStream.readInt() val size = dataInputStream.readInt()
@@ -243,13 +246,11 @@ class DatabaseInputKDBX(private val streamDir: File,
val protectedFlag = flag && DatabaseHeaderKDBX.KdbxBinaryFlags.Protected.toInt() != DatabaseHeaderKDBX.KdbxBinaryFlags.None.toInt() val protectedFlag = flag && DatabaseHeaderKDBX.KdbxBinaryFlags.Protected.toInt() != DatabaseHeaderKDBX.KdbxBinaryFlags.None.toInt()
val byteLength = size - 1 val byteLength = size - 1
// Read in a file // Read in a file
val file = File(streamDir, unusedCacheFileName) val file = File(cacheDirectory, unusedCacheFileName)
FileOutputStream(file).use { outputStream -> FileOutputStream(file).use { outputStream ->
dataInputStream.readBytes(byteLength, object : ReadBytes { dataInputStream.readBytes(byteLength, DatabaseKDBX.BUFFER_SIZE_BYTES) { buffer ->
override fun read(buffer: ByteArray) {
outputStream.write(buffer) outputStream.write(buffer)
} }
})
} }
val protectedBinary = BinaryAttachment(file, protectedFlag) val protectedBinary = BinaryAttachment(file, protectedFlag)
mDatabase.binaryPool.add(protectedBinary) mDatabase.binaryPool.add(protectedBinary)
@@ -822,7 +823,7 @@ class DatabaseInputKDBX(private val streamDir: File,
buf = buf8 buf = buf8
} }
val seconds = LEDataInputStream.readLong(buf, 0) val seconds = bytes64ToLong(buf)
utcDate = DateKDBXUtil.convertKDBX4Time(seconds) utcDate = DateKDBXUtil.convertKDBX4Time(seconds)
} else { } else {
@@ -882,7 +883,7 @@ class DatabaseInputKDBX(private val streamDir: File,
} }
val buf = Base64.decode(encoded, BASE_64_FLAG) val buf = Base64.decode(encoded, BASE_64_FLAG)
return DatabaseInputOutputUtils.bytesToUuid(buf) return bytes16ToUuid(buf)
} }
@Throws(IOException::class, XmlPullParserException::class) @Throws(IOException::class, XmlPullParserException::class)
@@ -960,7 +961,7 @@ class DatabaseInputKDBX(private val streamDir: File,
// New binary to retrieve // New binary to retrieve
else { else {
var compressed: Boolean? = null var compressed = false
var protected = false var protected = false
if (xpp.attributeCount > 0) { if (xpp.attributeCount > 0) {
@@ -980,16 +981,16 @@ class DatabaseInputKDBX(private val streamDir: File,
return BinaryAttachment() return BinaryAttachment()
val data = Base64.decode(base64, BASE_64_FLAG) val data = Base64.decode(base64, BASE_64_FLAG)
val file = File(streamDir, unusedCacheFileName) val file = File(cacheDirectory, unusedCacheFileName)
return FileOutputStream(file).use { outputStream -> return FileOutputStream(file).use { outputStream ->
// Force compression in this specific case // Force compression in this specific case
if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip
&& compressed == false) { && !compressed) {
GZIPOutputStream(outputStream).write(data) GZIPOutputStream(outputStream).write(data)
BinaryAttachment(file, protected, true) BinaryAttachment(file, protected, true)
} else { } else {
outputStream.write(data) outputStream.write(data)
BinaryAttachment(file, protected) BinaryAttachment(file, protected, compressed)
} }
} }
} }

View File

@@ -20,34 +20,34 @@
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.LEDataOutputStream import com.kunzisoft.keepass.stream.intTo4Bytes
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB, private val mOS: OutputStream) { class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB,
private val mOutputStream: OutputStream) {
@Throws(IOException::class) @Throws(IOException::class)
fun outputStart() { fun outputStart() {
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.signature1)) mOutputStream.write(intTo4Bytes(mHeader.signature1))
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.signature2)) mOutputStream.write(intTo4Bytes(mHeader.signature2))
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.flags)) mOutputStream.write(intTo4Bytes(mHeader.flags))
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.version)) mOutputStream.write(intTo4Bytes(mHeader.version))
mOS.write(mHeader.masterSeed) mOutputStream.write(mHeader.masterSeed)
mOS.write(mHeader.encryptionIV) mOutputStream.write(mHeader.encryptionIV)
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numGroups)) mOutputStream.write(intTo4Bytes(mHeader.numGroups))
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numEntries)) mOutputStream.write(intTo4Bytes(mHeader.numEntries))
} }
@Throws(IOException::class) @Throws(IOException::class)
fun outputContentHash() { fun outputContentHash() {
mOS.write(mHeader.contentsHash) mOutputStream.write(mHeader.contentsHash)
} }
@Throws(IOException::class) @Throws(IOException::class)
fun outputEnd() { fun outputEnd() {
mOS.write(mHeader.transformSeed) mOutputStream.write(mHeader.transformSeed)
mOS.write(LEDataOutputStream.writeIntBuf(mHeader.numKeyEncRounds)) mOutputStream.write(intTo4Bytes(mHeader.numKeyEncRounds))
} }
@Throws(IOException::class) @Throws(IOException::class)
@@ -59,6 +59,6 @@ class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB, private va
@Throws(IOException::class) @Throws(IOException::class)
fun close() { fun close() {
mOS.close() mOutputStream.close()
} }
} }

View File

@@ -19,17 +19,14 @@
*/ */
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.utils.VariantDictionary
import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.ULONG_MAX_VALUE
import com.kunzisoft.keepass.stream.HmacBlockStream import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.stream.LEDataOutputStream import com.kunzisoft.keepass.utils.VariantDictionary
import com.kunzisoft.keepass.stream.MacOutputStream
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
@@ -37,13 +34,15 @@ import java.security.DigestOutputStream
import java.security.InvalidKeyException import java.security.InvalidKeyException
import java.security.MessageDigest import java.security.MessageDigest
import java.security.NoSuchAlgorithmException import java.security.NoSuchAlgorithmException
import javax.crypto.Mac import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class) class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class)
constructor(private val db: DatabaseKDBX, private val header: DatabaseHeaderKDBX, os: OutputStream) : DatabaseHeaderOutput() { constructor(private val databaseKDBX: DatabaseKDBX,
private val los: LEDataOutputStream private val header: DatabaseHeaderKDBX,
outputStream: OutputStream) : DatabaseHeaderOutput() {
private val los: LittleEndianDataOutputStream
private val mos: MacOutputStream private val mos: MacOutputStream
private val dos: DigestOutputStream private val dos: DigestOutputStream
lateinit var headerHmac: ByteArray lateinit var headerHmac: ByteArray
@@ -58,15 +57,16 @@ constructor(private val db: DatabaseKDBX, private val header: DatabaseHeaderKDBX
} }
try { try {
db.makeFinalKey(header.masterSeed) databaseKDBX.makeFinalKey(header.masterSeed)
} catch (e: IOException) { } catch (e: IOException) {
throw DatabaseOutputException(e) throw DatabaseOutputException(e)
} }
val hmacKey = databaseKDBX.hmacKey ?: throw DatabaseOutputException("HmacKey is not defined")
val hmac: Mac val hmac: Mac
try { try {
hmac = Mac.getInstance("HmacSHA256") hmac = Mac.getInstance("HmacSHA256")
val signingKey = SecretKeySpec(HmacBlockStream.GetHmacKey64(db.hmacKey, DatabaseInputOutputUtils.ULONG_MAX_VALUE), "HmacSHA256") val signingKey = SecretKeySpec(HmacBlockStream.getHmacKey64(hmacKey, ULONG_MAX_VALUE), "HmacSHA256")
hmac.init(signingKey) hmac.init(signingKey)
} catch (e: NoSuchAlgorithmException) { } catch (e: NoSuchAlgorithmException) {
throw DatabaseOutputException(e) throw DatabaseOutputException(e)
@@ -74,9 +74,9 @@ constructor(private val db: DatabaseKDBX, private val header: DatabaseHeaderKDBX
throw DatabaseOutputException(e) throw DatabaseOutputException(e)
} }
dos = DigestOutputStream(os, md) dos = DigestOutputStream(outputStream, md)
mos = MacOutputStream(dos, hmac) mos = MacOutputStream(dos, hmac)
los = LEDataOutputStream(mos) los = LittleEndianDataOutputStream(mos)
} }
@Throws(IOException::class) @Throws(IOException::class)
@@ -86,15 +86,15 @@ constructor(private val db: DatabaseKDBX, private val header: DatabaseHeaderKDBX
los.writeUInt(DatabaseHeaderKDBX.DBSIG_2.toLong()) los.writeUInt(DatabaseHeaderKDBX.DBSIG_2.toLong())
los.writeUInt(header.version) los.writeUInt(header.version)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, DatabaseInputOutputUtils.uuidToBytes(db.dataCipher)) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, uuidTo16Bytes(databaseKDBX.dataCipher))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, LEDataOutputStream.writeIntBuf(DatabaseHeaderKDBX.getFlagFromCompression(db.compressionAlgorithm))) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, intTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed)
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, LEDataOutputStream.writeLongBuf(db.numberKeyEncryptionRounds)) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds))
} else { } else {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.KdfParameters, KdfParameters.serialize(db.kdfParameters!!)) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.KdfParameters, KdfParameters.serialize(databaseKDBX.kdfParameters!!))
} }
if (header.encryptionIV.isNotEmpty()) { if (header.encryptionIV.isNotEmpty()) {
@@ -104,13 +104,13 @@ constructor(private val db: DatabaseKDBX, private val header: DatabaseHeaderKDBX
if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, LEDataOutputStream.writeIntBuf(header.innerRandomStream!!.id)) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, intTo4Bytes(header.innerRandomStream!!.id))
} }
if (db.containsPublicCustomData()) { if (databaseKDBX.containsPublicCustomData()) {
val bos = ByteArrayOutputStream() val bos = ByteArrayOutputStream()
val los = LEDataOutputStream(bos) val los = LittleEndianDataOutputStream(bos)
VariantDictionary.serialize(db.publicCustomData, los) VariantDictionary.serialize(databaseKDBX.publicCustomData, los)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray()) writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray())
} }

View File

@@ -22,9 +22,8 @@ package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BUFFER_SIZE_BYTES import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BUFFER_SIZE_BYTES
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.stream.ReadBytes import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.LEDataOutputStream import com.kunzisoft.keepass.stream.readBytes
import com.kunzisoft.keepass.stream.readFromStream
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import kotlin.experimental.or import kotlin.experimental.or
@@ -33,7 +32,7 @@ class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
private val header: DatabaseHeaderKDBX, private val header: DatabaseHeaderKDBX,
outputStream: OutputStream) { outputStream: OutputStream) {
private val dataOutputStream: LEDataOutputStream = LEDataOutputStream(outputStream) private val dataOutputStream: LittleEndianDataOutputStream = LittleEndianDataOutputStream(outputStream)
@Throws(IOException::class) @Throws(IOException::class)
fun output() { fun output() {
@@ -58,14 +57,10 @@ class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX,
dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify
dataOutputStream.write(flag.toInt()) dataOutputStream.write(flag.toInt())
readFromStream(protectedBinary.getInputDataStream(), BUFFER_SIZE_BYTES, protectedBinary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
object : ReadBytes {
override fun read(buffer: ByteArray) {
dataOutputStream.write(buffer) dataOutputStream.write(buffer)
} }
} }
)
}
dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader.toInt()) dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader.toInt())
dataOutputStream.writeInt(0) dataOutputStream.writeInt(0)

View File

@@ -26,7 +26,7 @@ import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.stream.LEDataOutputStream import com.kunzisoft.keepass.stream.LittleEndianDataOutputStream
import com.kunzisoft.keepass.stream.NullOutputStream import com.kunzisoft.keepass.stream.NullOutputStream
import java.io.BufferedOutputStream import java.io.BufferedOutputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
@@ -128,7 +128,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
header.version = DatabaseHeaderKDB.DBVER_DW header.version = DatabaseHeaderKDB.DBVER_DW
header.numGroups = mDatabaseKDB.numberOfGroups() header.numGroups = mDatabaseKDB.numberOfGroups()
header.numEntries = mDatabaseKDB.numberOfEntries() header.numEntries = mDatabaseKDB.numberOfEntries()
header.numKeyEncRounds = mDatabaseKDB.numberKeyEncryptionRounds.toInt() header.numKeyEncRounds = mDatabaseKDB.numberKeyEncryptionRounds.toInt() // TODO Signed Long - Unsigned Int
setIVs(header) setIVs(header)
@@ -197,7 +197,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
@Suppress("CAST_NEVER_SUCCEEDS") @Suppress("CAST_NEVER_SUCCEEDS")
@Throws(DatabaseOutputException::class) @Throws(DatabaseOutputException::class)
fun outputPlanGroupAndEntries(os: OutputStream) { fun outputPlanGroupAndEntries(os: OutputStream) {
val los = LEDataOutputStream(os) val los = LittleEndianDataOutputStream(os)
// useHeaderHash // useHeaderHash
if (headerHashBlock != null) { if (headerHashBlock != null) {
@@ -261,7 +261,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
@Throws(IOException::class) @Throws(IOException::class)
private fun writeExtData(headerDigest: ByteArray, os: OutputStream) { private fun writeExtData(headerDigest: ByteArray, os: OutputStream) {
val los = LEDataOutputStream(os) val los = LittleEndianDataOutputStream(os)
writeExtDataField(los, 0x0001, headerDigest, headerDigest.size) writeExtDataField(los, 0x0001, headerDigest, headerDigest.size)
val headerRandom = ByteArray(32) val headerRandom = ByteArray(32)
@@ -273,7 +273,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun writeExtDataField(los: LEDataOutputStream, fieldType: Int, data: ByteArray?, fieldSize: Int) { private fun writeExtDataField(los: LittleEndianDataOutputStream, fieldType: Int, data: ByteArray?, fieldSize: Int) {
los.writeUShort(fieldType) los.writeUShort(fieldType)
los.writeInt(fieldSize) los.writeInt(fieldSize)
if (data != null) { if (data != null) {

View File

@@ -28,7 +28,7 @@ import com.kunzisoft.keepass.crypto.StreamCipherFactory
import com.kunzisoft.keepass.crypto.engine.CipherEngine import com.kunzisoft.keepass.crypto.engine.CipherEngine
import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory
import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
@@ -47,7 +47,6 @@ import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils
import org.joda.time.DateTime import org.joda.time.DateTime
import org.spongycastle.crypto.StreamCipher import org.spongycastle.crypto.StreamCipher
import org.xmlpull.v1.XmlSerializer import org.xmlpull.v1.XmlSerializer
@@ -95,7 +94,8 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
mOS.write(hashOfHeader!!) mOS.write(hashOfHeader!!)
mOS.write(headerHmac!!) mOS.write(headerHmac!!)
attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseKDBX.hmacKey))
attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseKDBX.hmacKey!!))
} }
val osXml: OutputStream val osXml: OutputStream
@@ -230,7 +230,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID) writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID)
// Seem to work properly if always in meta // Seem to work properly if always in meta
// if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4)
writeMetaBinaries() writeMetaBinaries()
writeCustomData(mDatabaseKDBX.customData) writeCustomData(mDatabaseKDBX.customData)
@@ -390,7 +390,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
} else { } else {
val dt = DateTime(value) val dt = DateTime(value)
val seconds = DateKDBXUtil.convertDateToKDBX4Time(dt) val seconds = DateKDBXUtil.convertDateToKDBX4Time(dt)
val buf = LEDataOutputStream.writeLongBuf(seconds) val buf = longTo8Bytes(seconds)
val b64 = String(Base64.encode(buf, BASE_64_FLAG)) val b64 = String(Base64.encode(buf, BASE_64_FLAG))
writeObject(name, b64) writeObject(name, b64)
} }
@@ -414,26 +414,29 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeUuid(name: String, uuid: UUID) { private fun writeUuid(name: String, uuid: UUID) {
val data = DatabaseInputOutputUtils.uuidToBytes(uuid) val data = uuidTo16Bytes(uuid)
writeObject(name, String(Base64.encode(data, BASE_64_FLAG))) writeObject(name, String(Base64.encode(data, BASE_64_FLAG)))
} }
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeMetaBinaries() { private fun writeBinary(binary : BinaryAttachment) {
xml.startTag(null, DatabaseKDBXXML.ElemBinaries) val binaryLength = binary.length()
if (binaryLength > 0) {
mDatabaseKDBX.binaryPool.doForEachBinary { key, binary -> if (binary.isProtected) {
xml.startTag(null, DatabaseKDBXXML.ElemBinary) xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
xml.attribute(null, DatabaseKDBXXML.AttrId, key.toString())
// Force binary compression from database (compression was harmonized during import) binary.getInputDataStream().readBytes(BUFFER_SIZE_BYTES) { buffer ->
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, val encoded = ByteArray(buffer.size)
if (mDatabaseKDBX.compressionAlgorithm === CompressionAlgorithm.GZip) { randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
DatabaseKDBXXML.ValTrue val charArray = String(Base64.encode(encoded, BASE_64_FLAG)).toCharArray()
} else { xml.text(charArray, 0, charArray.size)
DatabaseKDBXXML.ValFalse }
} else {
// Force binary compression from database (compression was harmonized during import)
if (mDatabaseKDBX.compressionAlgorithm === CompressionAlgorithm.GZip) {
xml.attribute(null, DatabaseKDBXXML.AttrCompressed, DatabaseKDBXXML.ValTrue)
} }
)
// Force decompression in this specific case // Force decompression in this specific case
val binaryInputStream = if (mDatabaseKDBX.compressionAlgorithm == CompressionAlgorithm.None val binaryInputStream = if (mDatabaseKDBX.compressionAlgorithm == CompressionAlgorithm.None
@@ -444,15 +447,22 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
} }
// Write the XML // Write the XML
readFromStream(binaryInputStream, BUFFER_SIZE_BYTES, binaryInputStream.readBytes(BUFFER_SIZE_BYTES) { buffer ->
object : ReadBytes {
override fun read(buffer: ByteArray) {
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray() val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size) xml.text(charArray, 0, charArray.size)
} }
} }
) }
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeMetaBinaries() {
xml.startTag(null, DatabaseKDBXXML.ElemBinaries)
mDatabaseKDBX.binaryPool.doForEachBinary { key, binary ->
xml.startTag(null, DatabaseKDBXXML.ElemBinary)
xml.attribute(null, DatabaseKDBXXML.AttrId, key.toString())
writeBinary(binary)
xml.endTag(null, DatabaseKDBXXML.ElemBinary) xml.endTag(null, DatabaseKDBXXML.ElemBinary)
} }
@@ -561,32 +571,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
if (ref != null) { if (ref != null) {
xml.attribute(null, DatabaseKDBXXML.AttrRef, ref.toString()) xml.attribute(null, DatabaseKDBXXML.AttrRef, ref.toString())
} else { } else {
val binaryLength = binary.length() writeBinary(binary)
if (binaryLength > 0) {
if (binary.isProtected) {
xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue)
readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
val encoded = ByteArray(buffer.size)
randomStream!!.processBytes(buffer, 0, encoded.size, encoded, 0)
val charArray = String(Base64.encode(encoded, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
})
} else {
readFromStream(binary.getInputDataStream(), BUFFER_SIZE_BYTES,
object : ReadBytes {
override fun read(buffer: ByteArray) {
val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray()
xml.text(charArray, 0, charArray.size)
}
}
)
}
}
} }
xml.endTag(null, DatabaseKDBXXML.ElemValue) xml.endTag(null, DatabaseKDBXXML.ElemValue)

View File

@@ -19,12 +19,14 @@
*/ */
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.stream.LEDataOutputStream import com.kunzisoft.keepass.database.file.output.GroupOutputKDB.Companion.GROUPID_FIELD_SIZE
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import com.kunzisoft.keepass.stream.*
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
import java.nio.charset.Charset
class EntryOutputKDB class EntryOutputKDB
/** /**
@@ -47,53 +49,76 @@ class EntryOutputKDB
// UUID // UUID
mOutputStream.write(UUID_FIELD_TYPE) mOutputStream.write(UUID_FIELD_TYPE)
mOutputStream.write(UUID_FIELD_SIZE) mOutputStream.write(UUID_FIELD_SIZE)
mOutputStream.write(DatabaseInputOutputUtils.uuidToBytes(mEntry.id)) mOutputStream.write(uuidTo16Bytes(mEntry.id))
// Group ID // Group ID
mOutputStream.write(GROUPID_FIELD_TYPE) mOutputStream.write(GROUPID_FIELD_TYPE)
mOutputStream.write(LONG_FOUR) mOutputStream.write(GROUPID_FIELD_SIZE)
mOutputStream.write(LEDataOutputStream.writeIntBuf(mEntry.parent!!.id)) mOutputStream.write(intTo4Bytes(mEntry.parent!!.id))
// Image ID // Image ID
mOutputStream.write(IMAGEID_FIELD_TYPE) mOutputStream.write(IMAGEID_FIELD_TYPE)
mOutputStream.write(LONG_FOUR) mOutputStream.write(IMAGEID_FIELD_SIZE)
mOutputStream.write(LEDataOutputStream.writeIntBuf(mEntry.icon.iconId)) mOutputStream.write(intTo4Bytes(mEntry.icon.iconId))
// Title // Title
//byte[] title = mEntry.title.getBytes("UTF-8"); //byte[] title = mEntry.title.getBytes("UTF-8");
mOutputStream.write(TITLE_FIELD_TYPE) mOutputStream.write(TITLE_FIELD_TYPE)
length += DatabaseInputOutputUtils.writeCString(mEntry.title, mOutputStream).toLong() length += StringDatabaseKDBUtils.writeStringToBytes(mEntry.title, mOutputStream).toLong()
// URL // URL
mOutputStream.write(URL_FIELD_TYPE) mOutputStream.write(URL_FIELD_TYPE)
length += DatabaseInputOutputUtils.writeCString(mEntry.url, mOutputStream).toLong() length += StringDatabaseKDBUtils.writeStringToBytes(mEntry.url, mOutputStream).toLong()
// Username // Username
mOutputStream.write(USERNAME_FIELD_TYPE) mOutputStream.write(USERNAME_FIELD_TYPE)
length += DatabaseInputOutputUtils.writeCString(mEntry.username, mOutputStream).toLong() length += StringDatabaseKDBUtils.writeStringToBytes(mEntry.username, mOutputStream).toLong()
// Password // Password
mOutputStream.write(PASSWORD_FIELD_TYPE) mOutputStream.write(PASSWORD_FIELD_TYPE)
length += DatabaseInputOutputUtils.writePassword(mEntry.password, mOutputStream).toLong() length += writePassword(mEntry.password, mOutputStream).toLong()
// Additional // Additional
mOutputStream.write(ADDITIONAL_FIELD_TYPE) mOutputStream.write(ADDITIONAL_FIELD_TYPE)
length += DatabaseInputOutputUtils.writeCString(mEntry.notes, mOutputStream).toLong() length += StringDatabaseKDBUtils.writeStringToBytes(mEntry.notes, mOutputStream).toLong()
// Create date // Create date
writeDate(CREATE_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.creationTime.date)) writeDate(CREATE_FIELD_TYPE, dateTo5Bytes(mEntry.creationTime.date))
// Modification date // Modification date
writeDate(MOD_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.lastModificationTime.date)) writeDate(MOD_FIELD_TYPE, dateTo5Bytes(mEntry.lastModificationTime.date))
// Access date // Access date
writeDate(ACCESS_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.lastAccessTime.date)) writeDate(ACCESS_FIELD_TYPE, dateTo5Bytes(mEntry.lastAccessTime.date))
// Expiration date // Expiration date
writeDate(EXPIRE_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.expiryTime.date)) writeDate(EXPIRE_FIELD_TYPE, dateTo5Bytes(mEntry.expiryTime.date))
// Binary description
mOutputStream.write(BINARY_DESC_FIELD_TYPE)
length += StringDatabaseKDBUtils.writeStringToBytes(mEntry.binaryDescription, mOutputStream).toLong()
// Binary // Binary
writeBinary(mEntry.binaryData) mOutputStream.write(BINARY_DATA_FIELD_TYPE)
val binaryData = mEntry.binaryData
val binaryDataLength = binaryData?.length() ?: 0
val binaryDataLengthRightSize = if (binaryDataLength <= Int.MAX_VALUE) {
binaryDataLength.toInt()
} else {
0 // TODO if length > UInt.maxvalue show exception
}
// Write data length
mOutputStream.write(intTo4Bytes(binaryDataLengthRightSize))
// Write data
if (binaryDataLength > 0) {
binaryData?.getInputDataStream().use { inputStream ->
inputStream?.readBytes(DatabaseKDB.BUFFER_SIZE_BYTES) { buffer ->
length += buffer.size
mOutputStream.write(buffer)
}
inputStream?.close()
}
}
// End // End
mOutputStream.write(END_FIELD_TYPE) mOutputStream.write(END_FIELD_TYPE)
@@ -112,39 +137,40 @@ class EntryOutputKDB
} }
@Throws(IOException::class) @Throws(IOException::class)
private fun writeBinary(data: ByteArray?) { private fun writePassword(str: String, os: OutputStream): Int {
mOutputStream.write(BINARY_DESC_FIELD_TYPE) val initial = str.toByteArray(Charset.forName("UTF-8"))
length += DatabaseInputOutputUtils.writeCString(mEntry.binaryDesc, mOutputStream).toLong() val length = initial.size + 1
os.write(intTo4Bytes(length))
val dataLen: Int = data?.size ?: 0 os.write(initial)
mOutputStream.write(BINARY_DATA_FIELD_TYPE) os.write(0x00)
length += DatabaseInputOutputUtils.writeBytes(data, dataLen, mOutputStream) return length
} }
companion object { companion object {
// Constants // Constants
val UUID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(1) val UUID_FIELD_TYPE:ByteArray = uShortTo2Bytes(1)
val GROUPID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(2) val GROUPID_FIELD_TYPE:ByteArray = uShortTo2Bytes(2)
val IMAGEID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(3) val IMAGEID_FIELD_TYPE:ByteArray = uShortTo2Bytes(3)
val TITLE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(4) val TITLE_FIELD_TYPE:ByteArray = uShortTo2Bytes(4)
val URL_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(5) val URL_FIELD_TYPE:ByteArray = uShortTo2Bytes(5)
val USERNAME_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(6) val USERNAME_FIELD_TYPE:ByteArray = uShortTo2Bytes(6)
val PASSWORD_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(7) val PASSWORD_FIELD_TYPE:ByteArray = uShortTo2Bytes(7)
val ADDITIONAL_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(8) val ADDITIONAL_FIELD_TYPE:ByteArray = uShortTo2Bytes(8)
val CREATE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(9) val CREATE_FIELD_TYPE:ByteArray = uShortTo2Bytes(9)
val MOD_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(10) val MOD_FIELD_TYPE:ByteArray = uShortTo2Bytes(10)
val ACCESS_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(11) val ACCESS_FIELD_TYPE:ByteArray = uShortTo2Bytes(11)
val EXPIRE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(12) val EXPIRE_FIELD_TYPE:ByteArray = uShortTo2Bytes(12)
val BINARY_DESC_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(13) val BINARY_DESC_FIELD_TYPE:ByteArray = uShortTo2Bytes(13)
val BINARY_DATA_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(14) val BINARY_DATA_FIELD_TYPE:ByteArray = uShortTo2Bytes(14)
val END_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(0xFFFF) val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
val LONG_FOUR:ByteArray = LEDataOutputStream.writeIntBuf(4)
val UUID_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(16) val LONG_FOUR:ByteArray = intTo4Bytes(4)
val DATE_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(5) val UUID_FIELD_SIZE:ByteArray = intTo4Bytes(16)
val IMAGEID_FIELD_SIZE:ByteArray = LONG_FOUR val DATE_FIELD_SIZE:ByteArray = intTo4Bytes(5)
val LEVEL_FIELD_SIZE:ByteArray = LONG_FOUR val IMAGEID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val FLAGS_FIELD_SIZE:ByteArray = LONG_FOUR val LEVEL_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val ZERO_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(0) val FLAGS_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val ZERO_FIELD_SIZE:ByteArray = intTo4Bytes(0)
val ZERO_FIVE:ByteArray = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00) val ZERO_FIVE:ByteArray = byteArrayOf(0x00, 0x00, 0x00, 0x00, 0x00)
} }
} }

View File

@@ -20,9 +20,10 @@
package com.kunzisoft.keepass.database.file.output package com.kunzisoft.keepass.database.file.output
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.stream.LEDataOutputStream import com.kunzisoft.keepass.stream.dateTo5Bytes
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import com.kunzisoft.keepass.stream.intTo4Bytes
import com.kunzisoft.keepass.stream.uShortTo2Bytes
import com.kunzisoft.keepass.utils.StringDatabaseKDBUtils
import java.io.IOException import java.io.IOException
import java.io.OutputStream import java.io.OutputStream
@@ -38,46 +39,46 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
// Group ID // Group ID
mOutputStream.write(GROUPID_FIELD_TYPE) mOutputStream.write(GROUPID_FIELD_TYPE)
mOutputStream.write(GROUPID_FIELD_SIZE) mOutputStream.write(GROUPID_FIELD_SIZE)
mOutputStream.write(LEDataOutputStream.writeIntBuf(mGroup.id)) mOutputStream.write(intTo4Bytes(mGroup.id))
// Name // Name
mOutputStream.write(NAME_FIELD_TYPE) mOutputStream.write(NAME_FIELD_TYPE)
DatabaseInputOutputUtils.writeCString(mGroup.title, mOutputStream) StringDatabaseKDBUtils.writeStringToBytes(mGroup.title, mOutputStream)
// Create date // Create date
mOutputStream.write(CREATE_FIELD_TYPE) mOutputStream.write(CREATE_FIELD_TYPE)
mOutputStream.write(DATE_FIELD_SIZE) mOutputStream.write(DATE_FIELD_SIZE)
mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.creationTime.date)) mOutputStream.write(dateTo5Bytes(mGroup.creationTime.date))
// Modification date // Modification date
mOutputStream.write(MOD_FIELD_TYPE) mOutputStream.write(MOD_FIELD_TYPE)
mOutputStream.write(DATE_FIELD_SIZE) mOutputStream.write(DATE_FIELD_SIZE)
mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.lastModificationTime.date)) mOutputStream.write(dateTo5Bytes(mGroup.lastModificationTime.date))
// Access date // Access date
mOutputStream.write(ACCESS_FIELD_TYPE) mOutputStream.write(ACCESS_FIELD_TYPE)
mOutputStream.write(DATE_FIELD_SIZE) mOutputStream.write(DATE_FIELD_SIZE)
mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.lastAccessTime.date)) mOutputStream.write(dateTo5Bytes(mGroup.lastAccessTime.date))
// Expiration date // Expiration date
mOutputStream.write(EXPIRE_FIELD_TYPE) mOutputStream.write(EXPIRE_FIELD_TYPE)
mOutputStream.write(DATE_FIELD_SIZE) mOutputStream.write(DATE_FIELD_SIZE)
mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.expiryTime.date)) mOutputStream.write(dateTo5Bytes(mGroup.expiryTime.date))
// Image ID // Image ID
mOutputStream.write(IMAGEID_FIELD_TYPE) mOutputStream.write(IMAGEID_FIELD_TYPE)
mOutputStream.write(IMAGEID_FIELD_SIZE) mOutputStream.write(IMAGEID_FIELD_SIZE)
mOutputStream.write(LEDataOutputStream.writeIntBuf(mGroup.icon.iconId)) mOutputStream.write(intTo4Bytes(mGroup.icon.iconId))
// Level // Level
mOutputStream.write(LEVEL_FIELD_TYPE) mOutputStream.write(LEVEL_FIELD_TYPE)
mOutputStream.write(LEVEL_FIELD_SIZE) mOutputStream.write(LEVEL_FIELD_SIZE)
mOutputStream.write(LEDataOutputStream.writeUShortBuf(mGroup.level)) mOutputStream.write(uShortTo2Bytes(mGroup.level))
// Flags // Flags
mOutputStream.write(FLAGS_FIELD_TYPE) mOutputStream.write(FLAGS_FIELD_TYPE)
mOutputStream.write(FLAGS_FIELD_SIZE) mOutputStream.write(FLAGS_FIELD_SIZE)
mOutputStream.write(LEDataOutputStream.writeIntBuf(mGroup.flags)) mOutputStream.write(intTo4Bytes(mGroup.flags))
// End // End
mOutputStream.write(END_FIELD_TYPE) mOutputStream.write(END_FIELD_TYPE)
@@ -86,23 +87,22 @@ class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: O
companion object { companion object {
// Constants // Constants
val GROUPID_FIELD_TYPE: ByteArray = LEDataOutputStream.writeUShortBuf(1) val GROUPID_FIELD_TYPE: ByteArray = uShortTo2Bytes(1)
val NAME_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(2) val NAME_FIELD_TYPE:ByteArray = uShortTo2Bytes(2)
val CREATE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(3) val CREATE_FIELD_TYPE:ByteArray = uShortTo2Bytes(3)
val MOD_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(4) val MOD_FIELD_TYPE:ByteArray = uShortTo2Bytes(4)
val ACCESS_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(5) val ACCESS_FIELD_TYPE:ByteArray = uShortTo2Bytes(5)
val EXPIRE_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(6) val EXPIRE_FIELD_TYPE:ByteArray = uShortTo2Bytes(6)
val IMAGEID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(7) val IMAGEID_FIELD_TYPE:ByteArray = uShortTo2Bytes(7)
val LEVEL_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(8) val LEVEL_FIELD_TYPE:ByteArray = uShortTo2Bytes(8)
val FLAGS_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(9) val FLAGS_FIELD_TYPE:ByteArray = uShortTo2Bytes(9)
val END_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(0xFFFF) val END_FIELD_TYPE:ByteArray = uShortTo2Bytes(0xFFFF)
val LONG_FOUR:ByteArray = LEDataOutputStream.writeIntBuf(4)
val GROUPID_FIELD_SIZE:ByteArray = LONG_FOUR
val DATE_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(5)
val IMAGEID_FIELD_SIZE:ByteArray = LONG_FOUR
val LEVEL_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(2)
val FLAGS_FIELD_SIZE:ByteArray = LONG_FOUR
val ZERO_FIELD_SIZE:ByteArray = LEDataOutputStream.writeIntBuf(0)
}
val GROUPID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val DATE_FIELD_SIZE:ByteArray = intTo4Bytes(5)
val IMAGEID_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val LEVEL_FIELD_SIZE:ByteArray = intTo4Bytes(2)
val FLAGS_FIELD_SIZE:ByteArray = intTo4Bytes(4)
val ZERO_FIELD_SIZE:ByteArray = intTo4Bytes(0)
}
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. * Copyright 2019 Jeremy Jamet / Kunzisoft.
* *
* This file is part of KeePassDX. * This file is part of KeePassDX.
* *
@@ -19,16 +19,16 @@
*/ */
package com.kunzisoft.keepass.database.search; package com.kunzisoft.keepass.database.search;
import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils;
import java.util.UUID; import java.util.UUID;
import static com.kunzisoft.keepass.stream.StreamBytesUtilsKt.uuidTo16Bytes;
public class UuidUtil { public class UuidUtil {
public static String toHexString(UUID uuid) { public static String toHexString(UUID uuid) {
if (uuid == null) { return null; } if (uuid == null) { return null; }
byte[] buf = DatabaseInputOutputUtils.INSTANCE.uuidToBytes(uuid); byte[] buf = uuidTo16Bytes(uuid);
int len = buf.length; int len = buf.length;
if (len == 0) { return ""; } if (len == 0) { return ""; }

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education package com.kunzisoft.keepass.education
import android.app.Activity import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education package com.kunzisoft.keepass.education
import android.app.Activity import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education package com.kunzisoft.keepass.education
import android.app.Activity import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education package com.kunzisoft.keepass.education
import android.app.Activity import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education package com.kunzisoft.keepass.education
import android.app.Activity import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.education package com.kunzisoft.keepass.education
import android.app.Activity import android.app.Activity

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.magikeyboard package com.kunzisoft.keepass.magikeyboard
import android.os.Bundle import android.os.Bundle

View File

@@ -90,17 +90,10 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
keyboard = Keyboard(this, R.xml.keyboard_password) keyboard = Keyboard(this, R.xml.keyboard_password)
keyboardEntry = Keyboard(this, R.xml.keyboard_password_entry) keyboardEntry = Keyboard(this, R.xml.keyboard_password_entry)
if (!Database.getInstance().loaded)
removeEntryInfo()
assignKeyboardView()
keyboardView?.setOnKeyboardActionListener(this)
keyboardView?.isPreviewEnabled = false
val context = baseContext val context = baseContext
val popupFieldsView = LayoutInflater.from(context) val popupFieldsView = LayoutInflater.from(context)
.inflate(R.layout.keyboard_popup_fields, FrameLayout(context)) .inflate(R.layout.keyboard_popup_fields, FrameLayout(context))
dismissCustomKeys()
popupCustomKeys = PopupWindow(context).apply { popupCustomKeys = PopupWindow(context).apply {
width = WindowManager.LayoutParams.WRAP_CONTENT width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT height = WindowManager.LayoutParams.WRAP_CONTENT
@@ -114,6 +107,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener { fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
override fun onItemClick(item: Field) { override fun onItemClick(item: Field) {
currentInputConnection.commitText(entryInfoKey?.getGeneratedFieldValue(item.name) , 1) currentInputConnection.commitText(entryInfoKey?.getGeneratedFieldValue(item.name) , 1)
goNextAutomatically()
} }
} }
recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, true) recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, true)
@@ -122,6 +116,12 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
val closeView = popupFieldsView.findViewById<View>(R.id.keyboard_popup_close) val closeView = popupFieldsView.findViewById<View>(R.id.keyboard_popup_close)
closeView.setOnClickListener { popupCustomKeys?.dismiss() } closeView.setOnClickListener { popupCustomKeys?.dismiss() }
if (!Database.getInstance().loaded)
removeEntryInfo()
assignKeyboardView()
keyboardView?.setOnKeyboardActionListener(this)
keyboardView?.isPreviewEnabled = false
return rootKeyboardView return rootKeyboardView
} }
@@ -235,31 +235,41 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
} }
KEY_USERNAME -> { KEY_USERNAME -> {
if (entryInfoKey != null) { if (entryInfoKey != null) {
inputConnection.commitText(entryInfoKey!!.username, 1) currentInputConnection.commitText(entryInfoKey!!.username, 1)
} }
goNextAutomatically()
} }
KEY_PASSWORD -> { KEY_PASSWORD -> {
if (entryInfoKey != null) { if (entryInfoKey != null) {
inputConnection.commitText(entryInfoKey!!.password, 1) currentInputConnection.commitText(entryInfoKey!!.password, 1)
} }
goNextAutomatically()
} }
KEY_URL -> { KEY_URL -> {
if (entryInfoKey != null) { if (entryInfoKey != null) {
inputConnection.commitText(entryInfoKey!!.url, 1) currentInputConnection.commitText(entryInfoKey!!.url, 1)
} }
goNextAutomatically()
} }
KEY_FIELDS -> { KEY_FIELDS -> {
if (entryInfoKey != null) { if (entryInfoKey != null) {
fieldsAdapter?.fields = entryInfoKey!!.customFields fieldsAdapter?.apply {
fieldsAdapter?.notifyDataSetChanged() setFields(entryInfoKey!!.customFields)
notifyDataSetChanged()
}
} }
popupCustomKeys?.showAtLocation(keyboardView, Gravity.END or Gravity.TOP, 0, 0) popupCustomKeys?.showAtLocation(keyboardView, Gravity.END or Gravity.TOP, 0, 0)
} }
Keyboard.KEYCODE_DELETE -> inputConnection.deleteSurroundingText(1, 0) Keyboard.KEYCODE_DELETE -> inputConnection.deleteSurroundingText(1, 0)
Keyboard.KEYCODE_DONE -> inputConnection.sendKeyEvent(KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_ENTER)) Keyboard.KEYCODE_DONE -> inputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
}// TODO Unlock key }// TODO Unlock key
} }
private fun goNextAutomatically() {
if (PreferencesUtil.isAutoGoActionEnable(this))
currentInputConnection.performEditorAction(EditorInfo.IME_ACTION_GO)
}
override fun onPress(primaryCode: Int) { override fun onPress(primaryCode: Int) {
val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager? val audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager?
if (audioManager != null) if (audioManager != null)

View File

@@ -0,0 +1,63 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.model
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.security.BinaryAttachment
import com.kunzisoft.keepass.utils.readEnum
import com.kunzisoft.keepass.utils.writeEnum
data class EntryAttachment(var name: String,
var binaryAttachment: BinaryAttachment,
var downloadState: AttachmentState = AttachmentState.NULL,
var downloadProgression: Int = 0) : Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readParcelable(BinaryAttachment::class.java.classLoader) ?: BinaryAttachment(),
parcel.readEnum<AttachmentState>() ?: AttachmentState.NULL,
parcel.readInt())
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeParcelable(binaryAttachment, flags)
parcel.writeEnum(downloadState)
parcel.writeInt(downloadProgression)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<EntryAttachment> {
override fun createFromParcel(parcel: Parcel): EntryAttachment {
return EntryAttachment(parcel)
}
override fun newArray(size: Int): Array<EntryAttachment?> {
return arrayOfNulls(size)
}
}
}
enum class AttachmentState {
NULL, START, IN_PROGRESS, COMPLETE, ERROR
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.model package com.kunzisoft.keepass.model
import android.os.Parcel import android.os.Parcel

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.model package com.kunzisoft.keepass.model
import android.os.Parcel import android.os.Parcel

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.model package com.kunzisoft.keepass.model
import android.os.Parcel import android.os.Parcel

View File

@@ -0,0 +1,233 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.notifications
import android.app.PendingIntent
import android.content.Intent
import android.net.Uri
import android.os.Binder
import android.os.IBinder
import android.util.Log
import androidx.documentfile.provider.DocumentFile
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.model.AttachmentState
import com.kunzisoft.keepass.model.EntryAttachment
import com.kunzisoft.keepass.tasks.AttachmentFileAsyncTask
import java.util.*
import kotlin.collections.HashMap
class AttachmentFileNotificationService: LockNotificationService() {
override val notificationId: Int = 10000
private var mActionTaskBinder = ActionTaskBinder()
private var mActionTaskListeners = LinkedList<ActionTaskListener>()
inner class ActionTaskBinder: Binder() {
fun getService(): AttachmentFileNotificationService = this@AttachmentFileNotificationService
fun addActionTaskListener(actionTaskListener: ActionTaskListener) {
mActionTaskListeners.add(actionTaskListener)
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
entry.value.attachmentTask?.onUpdate = { uri, attachment, notificationIdAttach ->
newNotification(uri, attachment, notificationIdAttach)
mActionTaskListeners.forEach { actionListener ->
actionListener.onAttachmentProgress(entry.key, attachment)
}
}
}
})
}
fun removeActionTaskListener(actionTaskListener: ActionTaskListener) {
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
entry.value.attachmentTask?.onUpdate = null
}
})
mActionTaskListeners.remove(actionTaskListener)
}
}
interface ActionTaskListener {
fun onAttachmentProgress(fileUri: Uri, attachment: EntryAttachment)
}
override fun onBind(intent: Intent): IBinder? {
return mActionTaskBinder
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val downloadFileUri: Uri? = if (intent?.hasExtra(DOWNLOAD_FILE_URI_KEY) == true) {
intent.getParcelableExtra(DOWNLOAD_FILE_URI_KEY)
} else null
when(intent?.action) {
ACTION_ATTACHMENT_FILE_START_DOWNLOAD -> {
if (downloadFileUri != null
&& intent.hasExtra(ATTACHMENT_KEY)) {
val nextNotificationId = (downloadFileUris.values.maxBy { it.notificationId }
?.notificationId ?: notificationId) + 1
val entryAttachment: EntryAttachment = intent.getParcelableExtra(ATTACHMENT_KEY)
val attachmentNotification = AttachmentNotification(nextNotificationId, entryAttachment)
downloadFileUris[downloadFileUri] = attachmentNotification
try {
AttachmentFileAsyncTask(downloadFileUri,
attachmentNotification,
contentResolver).apply {
onUpdate = { uri, attachment, notificationIdAttach ->
newNotification(uri, attachment, notificationIdAttach)
mActionTaskListeners.forEach { actionListener ->
actionListener.onAttachmentProgress(downloadFileUri, attachment)
}
}
}.execute()
} catch (e: Exception) {
Log.e(TAG, "Unable to download $downloadFileUri", e)
}
}
}
else -> {
if (downloadFileUri != null) {
downloadFileUris[downloadFileUri]?.notificationId?.let {
notificationManager?.cancel(it)
downloadFileUris.remove(downloadFileUri)
}
}
if (downloadFileUris.isEmpty()) {
stopSelf()
}
}
}
return START_REDELIVER_INTENT
}
fun checkCurrentAttachmentProgress() {
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
mActionTaskListeners.forEach { actionListener ->
actionListener.onAttachmentProgress(entry.key, entry.value.entryAttachment)
}
}
})
}
private fun newNotification(downloadFileUri: Uri,
entryAttachment: EntryAttachment,
notificationIdAttachment: Int) {
val pendingContentIntent = PendingIntent.getActivity(this,
0,
Intent().apply {
action = Intent.ACTION_VIEW
setDataAndType(downloadFileUri, contentResolver.getType(downloadFileUri))
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}, PendingIntent.FLAG_CANCEL_CURRENT)
val pendingDeleteIntent = PendingIntent.getService(this,
0,
Intent(this, AttachmentFileNotificationService::class.java).apply {
// No action to delete the service
putExtra(DOWNLOAD_FILE_URI_KEY, downloadFileUri)
}, PendingIntent.FLAG_CANCEL_CURRENT)
val fileName = DocumentFile.fromSingleUri(this, downloadFileUri)?.name ?: ""
val builder = buildNewNotification().apply {
setSmallIcon(R.drawable.ic_file_download_white_24dp)
setContentTitle(getString(R.string.download_attachment, fileName))
setAutoCancel(false)
when (entryAttachment.downloadState) {
AttachmentState.NULL, AttachmentState.START -> {
setContentText(getString(R.string.download_initialization))
setOngoing(true)
}
AttachmentState.IN_PROGRESS -> {
if (entryAttachment.downloadProgression > 100) {
setContentText(getString(R.string.download_finalization))
} else {
setProgress(100, entryAttachment.downloadProgression, false)
setContentText(getString(R.string.download_progression, entryAttachment.downloadProgression))
}
setOngoing(true)
}
AttachmentState.COMPLETE, AttachmentState.ERROR -> {
setContentText(getString(R.string.download_complete))
setContentIntent(pendingContentIntent)
setDeleteIntent(pendingDeleteIntent)
setOngoing(false)
}
}
}
notificationManager?.notify(notificationIdAttachment, builder.build())
}
override fun onDestroy() {
downloadFileUris.forEach(object : (Map.Entry<Uri, AttachmentNotification>) -> Unit {
override fun invoke(entry: Map.Entry<Uri, AttachmentNotification>) {
entry.value.attachmentTask?.onUpdate = null
notificationManager?.cancel(entry.value.notificationId)
}
})
super.onDestroy()
}
data class AttachmentNotification(var notificationId: Int,
var entryAttachment: EntryAttachment,
var attachmentTask: AttachmentFileAsyncTask? = null) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AttachmentNotification
if (notificationId != other.notificationId) return false
return true
}
override fun hashCode(): Int {
return notificationId
}
}
companion object {
private val TAG = AttachmentFileNotificationService::javaClass.name
const val ACTION_ATTACHMENT_FILE_START_DOWNLOAD = "ACTION_ATTACHMENT_FILE_START_DOWNLOAD"
const val DOWNLOAD_FILE_URI_KEY = "DOWNLOAD_FILE_URI_KEY"
const val ATTACHMENT_KEY = "ATTACHMENT_KEY"
private val downloadFileUris = HashMap<Uri, AttachmentNotification>()
}
}

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.notifications package com.kunzisoft.keepass.notifications
import android.app.PendingIntent import android.app.PendingIntent

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.notifications package com.kunzisoft.keepass.notifications
import android.content.Intent import android.content.Intent
@@ -9,6 +28,8 @@ import android.os.IBinder
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity
import com.kunzisoft.keepass.database.action.* import com.kunzisoft.keepass.database.action.*
import com.kunzisoft.keepass.database.action.history.DeleteEntryHistoryDatabaseRunnable
import com.kunzisoft.keepass.database.action.history.RestoreEntryHistoryDatabaseRunnable
import com.kunzisoft.keepass.database.action.node.* import com.kunzisoft.keepass.database.action.node.*
import com.kunzisoft.keepass.database.element.* import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.database.element.node.Node
@@ -107,6 +128,8 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
ACTION_DATABASE_COPY_NODES_TASK -> buildDatabaseCopyNodesActionTask(intent) ACTION_DATABASE_COPY_NODES_TASK -> buildDatabaseCopyNodesActionTask(intent)
ACTION_DATABASE_MOVE_NODES_TASK -> buildDatabaseMoveNodesActionTask(intent) ACTION_DATABASE_MOVE_NODES_TASK -> buildDatabaseMoveNodesActionTask(intent)
ACTION_DATABASE_DELETE_NODES_TASK -> buildDatabaseDeleteNodesActionTask(intent) ACTION_DATABASE_DELETE_NODES_TASK -> buildDatabaseDeleteNodesActionTask(intent)
ACTION_DATABASE_RESTORE_ENTRY_HISTORY -> buildDatabaseRestoreEntryHistoryActionTask(intent)
ACTION_DATABASE_DELETE_ENTRY_HISTORY -> buildDatabaseDeleteEntryHistoryActionTask(intent)
ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent) ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent)
ACTION_DATABASE_UPDATE_NAME_TASK, ACTION_DATABASE_UPDATE_NAME_TASK,
ACTION_DATABASE_UPDATE_DESCRIPTION_TASK, ACTION_DATABASE_UPDATE_DESCRIPTION_TASK,
@@ -409,6 +432,42 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
} }
} }
private fun buildDatabaseRestoreEntryHistoryActionTask(intent: Intent): ActionRunnable? {
return if (intent.hasExtra(ENTRY_ID_KEY)
&& intent.hasExtra(ENTRY_HISTORY_POSITION_KEY)
&& intent.hasExtra(SAVE_DATABASE_KEY)
) {
val database = Database.getInstance()
database.getEntryById(intent.getParcelableExtra(ENTRY_ID_KEY))?.let { mainEntry ->
RestoreEntryHistoryDatabaseRunnable(this,
database,
mainEntry,
intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1),
intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
}
} else {
null
}
}
private fun buildDatabaseDeleteEntryHistoryActionTask(intent: Intent): ActionRunnable? {
return if (intent.hasExtra(ENTRY_ID_KEY)
&& intent.hasExtra(ENTRY_HISTORY_POSITION_KEY)
&& intent.hasExtra(SAVE_DATABASE_KEY)
) {
val database = Database.getInstance()
database.getEntryById(intent.getParcelableExtra(ENTRY_ID_KEY))?.let { mainEntry ->
DeleteEntryHistoryDatabaseRunnable(this,
database,
mainEntry,
intent.getIntExtra(ENTRY_HISTORY_POSITION_KEY, -1),
intent.getBooleanExtra(SAVE_DATABASE_KEY, false))
}
} else {
null
}
}
private fun buildDatabaseUpdateCompressionActionTask(intent: Intent): ActionRunnable? { private fun buildDatabaseUpdateCompressionActionTask(intent: Intent): ActionRunnable? {
return if (intent.hasExtra(OLD_ELEMENT_KEY) return if (intent.hasExtra(OLD_ELEMENT_KEY)
&& intent.hasExtra(NEW_ELEMENT_KEY) && intent.hasExtra(NEW_ELEMENT_KEY)
@@ -503,6 +562,8 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
const val ACTION_DATABASE_COPY_NODES_TASK = "ACTION_DATABASE_COPY_NODES_TASK" const val ACTION_DATABASE_COPY_NODES_TASK = "ACTION_DATABASE_COPY_NODES_TASK"
const val ACTION_DATABASE_MOVE_NODES_TASK = "ACTION_DATABASE_MOVE_NODES_TASK" const val ACTION_DATABASE_MOVE_NODES_TASK = "ACTION_DATABASE_MOVE_NODES_TASK"
const val ACTION_DATABASE_DELETE_NODES_TASK = "ACTION_DATABASE_DELETE_NODES_TASK" const val ACTION_DATABASE_DELETE_NODES_TASK = "ACTION_DATABASE_DELETE_NODES_TASK"
const val ACTION_DATABASE_RESTORE_ENTRY_HISTORY = "ACTION_DATABASE_RESTORE_ENTRY_HISTORY"
const val ACTION_DATABASE_DELETE_ENTRY_HISTORY = "ACTION_DATABASE_DELETE_ENTRY_HISTORY"
const val ACTION_DATABASE_UPDATE_NAME_TASK = "ACTION_DATABASE_UPDATE_NAME_TASK" const val ACTION_DATABASE_UPDATE_NAME_TASK = "ACTION_DATABASE_UPDATE_NAME_TASK"
const val ACTION_DATABASE_UPDATE_DESCRIPTION_TASK = "ACTION_DATABASE_UPDATE_DESCRIPTION_TASK" const val ACTION_DATABASE_UPDATE_DESCRIPTION_TASK = "ACTION_DATABASE_UPDATE_DESCRIPTION_TASK"
const val ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK = "ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK" const val ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK = "ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK"
@@ -532,6 +593,7 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat
const val GROUPS_ID_KEY = "GROUPS_ID_KEY" const val GROUPS_ID_KEY = "GROUPS_ID_KEY"
const val ENTRIES_ID_KEY = "ENTRIES_ID_KEY" const val ENTRIES_ID_KEY = "ENTRIES_ID_KEY"
const val PARENT_ID_KEY = "PARENT_ID_KEY" const val PARENT_ID_KEY = "PARENT_ID_KEY"
const val ENTRY_HISTORY_POSITION_KEY = "ENTRY_HISTORY_POSITION_KEY"
const val SAVE_DATABASE_KEY = "SAVE_DATABASE_KEY" const val SAVE_DATABASE_KEY = "SAVE_DATABASE_KEY"
const val OLD_NODES_KEY = "OLD_NODES_KEY" const val OLD_NODES_KEY = "OLD_NODES_KEY"
const val NEW_NODES_KEY = "NEW_NODES_KEY" const val NEW_NODES_KEY = "NEW_NODES_KEY"

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.notifications package com.kunzisoft.keepass.notifications
import android.app.PendingIntent import android.app.PendingIntent

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.notifications package com.kunzisoft.keepass.notifications
import android.content.BroadcastReceiver import android.content.BroadcastReceiver

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.otp package com.kunzisoft.keepass.otp
import com.kunzisoft.keepass.model.OtpModel import com.kunzisoft.keepass.model.OtpModel

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings package com.kunzisoft.keepass.settings
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException

View File

@@ -1,3 +1,22 @@
/*
* Copyright 2019 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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.settings package com.kunzisoft.keepass.settings
import android.graphics.Color import android.graphics.Color

Some files were not shown because too many files have changed in this diff Show More