mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'release/2.5.0.0beta26'
This commit is contained in:
13
CHANGELOG
13
CHANGELOG
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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? {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.*
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.*
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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++
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = ""
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.")
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 ""; }
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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>()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user