diff --git a/CHANGELOG b/CHANGELOG index f796ec313..b1d2b6707 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,11 @@ +KeepassDX (2.5.0.0beta25) + * Setting for Recycle Bin + * Fix Recycle bin issues + * Fix TOTP + * Fix infinite save + * Fix update group + * Fix OOM + KeepassDX (2.5.0.0beta24) * Add OTP (HOTP / TOTP) * Add settings (Color, Security, Master Key) diff --git a/app/build.gradle b/app/build.gradle index e86c1b10d..d92761824 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,14 +6,13 @@ apply plugin: 'kotlin-kapt' android { compileSdkVersion 28 buildToolsVersion '28.0.3' - ndkVersion "20.1.5948944" defaultConfig { applicationId "com.kunzisoft.keepass" minSdkVersion 14 targetSdkVersion 28 - versionCode = 24 - versionName = "2.5.0.0beta24" + versionCode = 25 + versionName = "2.5.0.0beta25" multiDexEnabled true testApplicationId = "com.kunzisoft.keepass.tests" @@ -28,7 +27,6 @@ android { } } - buildTypes { release { minifyEnabled = false @@ -109,8 +107,6 @@ dependencies { implementation 'org.apache.commons:commons-io:1.3.2' // Apache Commons Codec implementation 'commons-codec:commons-codec:1.11' - // Base64 - implementation 'biz.source_code:base64coder:2010-12-19' // Icon pack implementation project(path: ':icon-pack-classic') implementation project(path: ':icon-pack-material') diff --git a/app/src/androidTest/java/com/kunzisoft/keepass/tests/TypesTest.kt b/app/src/androidTest/java/com/kunzisoft/keepass/tests/DatabaseInputOutputUtilsTest.kt similarity index 84% rename from app/src/androidTest/java/com/kunzisoft/keepass/tests/TypesTest.kt rename to app/src/androidTest/java/com/kunzisoft/keepass/tests/DatabaseInputOutputUtilsTest.kt index 5a353e858..61dcd2a5d 100644 --- a/app/src/androidTest/java/com/kunzisoft/keepass/tests/TypesTest.kt +++ b/app/src/androidTest/java/com/kunzisoft/keepass/tests/DatabaseInputOutputUtilsTest.kt @@ -27,12 +27,11 @@ import java.util.Random import junit.framework.TestCase -import com.kunzisoft.keepass.database.element.PwDate import com.kunzisoft.keepass.stream.LEDataInputStream import com.kunzisoft.keepass.stream.LEDataOutputStream -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils -class TypesTest : TestCase() { +class DatabaseInputOutputUtilsTest : TestCase() { fun testReadWriteLongZero() { testReadWriteLong(0.toByte()) @@ -155,8 +154,8 @@ class TypesTest : TestCase() { setArray(orig, value, 0, 1) - val one = Types.readUByte(orig, 0) - Types.writeUByte(one, dest, 0) + val one = DatabaseInputOutputUtils.readUByte(orig, 0) + DatabaseInputOutputUtils.writeUByte(one, dest, 0) assertArrayEquals(orig, dest) @@ -168,27 +167,31 @@ class TypesTest : TestCase() { val expected = Calendar.getInstance() expected.set(2008, 1, 2, 3, 4, 5) - val buf = PwDate.writeTime(expected.time, cal) val actual = Calendar.getInstance() - actual.time = PwDate.readTime(buf, 0, cal) + DatabaseInputOutputUtils.writeCDate(expected.time, cal)?.let { buf -> + actual.time = DatabaseInputOutputUtils.readCDate(buf, 0, cal).date + } assertEquals("Year mismatch: ", 2008, actual.get(Calendar.YEAR)) assertEquals("Month mismatch: ", 1, actual.get(Calendar.MONTH)) - assertEquals("Day mismatch: ", 1, 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("Minute mismatch: ", 4, actual.get(Calendar.MINUTE)) assertEquals("Second mismatch: ", 5, actual.get(Calendar.SECOND)) } fun testUUID() { - val rnd = Random() val bUUID = ByteArray(16) - rnd.nextBytes(bUUID) + Random().nextBytes(bUUID) + + val uuid = DatabaseInputOutputUtils.bytesToUuid(bUUID) + val eUUID = DatabaseInputOutputUtils.uuidToBytes(uuid) - val uuid = Types.bytestoUUID(bUUID) - val eUUID = Types.UUIDtoBytes(uuid) + val lUUID = LEDataInputStream.readUuid(bUUID, 0) + val leUUID = DatabaseInputOutputUtils.uuidToBytes(lUUID) assertArrayEquals("UUID match failed", bUUID, eUUID) + assertArrayEquals("UUID match failed", bUUID, leUUID) } @Throws(Exception::class) @@ -200,7 +203,7 @@ class TypesTest : TestCase() { val bos = ByteArrayOutputStream() val leos = LEDataOutputStream(bos) - leos.writeLong(Types.ULONG_MAX_VALUE) + leos.writeLong(DatabaseInputOutputUtils.ULONG_MAX_VALUE) leos.close() val uLongMax = bos.toByteArray() diff --git a/app/src/androidTest/java/com/kunzisoft/keepass/tests/PwDateTest.kt b/app/src/androidTest/java/com/kunzisoft/keepass/tests/PwDateTest.kt index 7cb932d86..eadfbc4eb 100644 --- a/app/src/androidTest/java/com/kunzisoft/keepass/tests/PwDateTest.kt +++ b/app/src/androidTest/java/com/kunzisoft/keepass/tests/PwDateTest.kt @@ -21,17 +21,18 @@ package com.kunzisoft.keepass.tests import junit.framework.TestCase -import com.kunzisoft.keepass.database.element.PwDate +import com.kunzisoft.keepass.database.element.DateInstant +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import org.junit.Assert class PwDateTest : TestCase() { fun testDate() { - val jDate = PwDate(System.currentTimeMillis()) - val intermediate = PwDate(jDate) - val cDate = PwDate(intermediate.byteArrayDate!!, 0) + 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 and cDate not equal", cDate == jDate) + Assert.assertTrue("jDate $jDate and cDate $cDate not equal", cDate == jDate) } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0613db3eb..79fa6bea8 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -20,6 +20,7 @@ android:allowBackup="true" android:fullBackupContent="@xml/backup" android:backupAgent="com.kunzisoft.keepass.backup.SettingsBackupAgent" + android:largeHeap="true" android:theme="@style/KeepassDXStyle.Night"> = intent.getParcelableExtra(KEY_ENTRY) + val keyEntry: NodeId = intent.getParcelableExtra(KEY_ENTRY) mEntry = mDatabase?.getEntryById(keyEntry) } catch (e: ClassCastException) { Log.e(TAG, "Unable to retrieve the entry key") @@ -158,7 +158,7 @@ class EntryActivity : LockingHideActivity() { firstLaunchOfActivity = false } - private fun fillEntryDataInContentsView(entry: EntryVersioned) { + private fun fillEntryDataInContentsView(entry: Entry) { val database = Database.getInstance() database.startManageEntry(entry) @@ -298,6 +298,8 @@ class EntryActivity : LockingHideActivity() { } database.stopManageEntry(entry) + + entryContentsView?.refreshHistory() } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -327,9 +329,9 @@ class EntryActivity : LockingHideActivity() { val inflater = menuInflater MenuUtil.contributionMenuInflater(inflater, menu) inflater.inflate(R.menu.entry, menu) - inflater.inflate(R.menu.database_lock, menu) - + inflater.inflate(R.menu.database, menu) if (mReadOnly) { + menu.findItem(R.id.menu_save_database)?.isVisible = false menu.findItem(R.id.menu_edit)?.isVisible = false } @@ -398,21 +400,18 @@ class EntryActivity : LockingHideActivity() { MenuUtil.onContributionItemSelected(this) return true } - R.id.menu_toggle_pass -> { mShowPassword = !mShowPassword changeShowPasswordIcon(item) entryContentsView?.setHiddenPasswordStyle(!mShowPassword) return true } - R.id.menu_edit -> { mEntry?.let { EntryEditActivity.launch(this@EntryActivity, it) } return true } - R.id.menu_goto_url -> { var url: String = mEntry?.url ?: "" @@ -422,18 +421,17 @@ class EntryActivity : LockingHideActivity() { } UriUtil.gotoUrl(this, url) - return true } - R.id.menu_lock -> { lockAndExit() return true } - + R.id.menu_save_database -> { + mProgressDialogThread?.startDatabaseSave(!mReadOnly) + } android.R.id.home -> finish() // close this activity and return to preview activity (if there is any) } - return super.onOptionsItemSelected(item) } @@ -455,7 +453,7 @@ class EntryActivity : LockingHideActivity() { const val KEY_ENTRY = "KEY_ENTRY" const val KEY_ENTRY_HISTORY_POSITION = "KEY_ENTRY_HISTORY_POSITION" - fun launch(activity: Activity, entry: EntryVersioned, readOnly: Boolean, historyPosition: Int? = null) { + fun launch(activity: Activity, entry: Entry, readOnly: Boolean, historyPosition: Int? = null) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { val intent = Intent(activity, EntryActivity::class.java) intent.putExtra(KEY_ENTRY, entry.nodeId) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt index 0ad978870..b6e04520d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -33,8 +33,9 @@ import com.kunzisoft.keepass.activities.dialogs.SetOTPDialogFragment import com.kunzisoft.keepass.activities.dialogs.GeneratePasswordDialogFragment import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment import com.kunzisoft.keepass.activities.lock.LockingHideActivity -import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.element.icon.IconImage +import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.education.EntryEditActivityEducation import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_CREATE_ENTRY_TASK @@ -56,10 +57,10 @@ class EntryEditActivity : LockingHideActivity(), private var mDatabase: Database? = null // Refs of an entry and group in database, are not modifiable - private var mEntry: EntryVersioned? = null - private var mParent: GroupVersioned? = null + private var mEntry: Entry? = null + private var mParent: Group? = null // New or copy of mEntry in the database to be modifiable - private var mNewEntry: EntryVersioned? = null + private var mNewEntry: Entry? = null private var mIsNew: Boolean = false // Views @@ -67,9 +68,6 @@ class EntryEditActivity : LockingHideActivity(), private var entryEditContentsView: EntryEditContentsView? = null private var saveView: View? = null - // Dialog thread - private var progressDialogThread: ProgressDialogThread? = null - // Education private var entryEditActivityEducation: EntryEditActivityEducation? = null @@ -98,7 +96,7 @@ class EntryEditActivity : LockingHideActivity(), mDatabase = Database.getInstance() // Entry is retrieve, it's an entry to update - intent.getParcelableExtra>(KEY_ENTRY)?.let { + intent.getParcelableExtra>(KEY_ENTRY)?.let { mIsNew = false // Create an Entry copy to modify from the database entry mEntry = mDatabase?.getEntryById(it) @@ -118,7 +116,7 @@ class EntryEditActivity : LockingHideActivity(), || !savedInstanceState.containsKey(KEY_NEW_ENTRY)) { mEntry?.let { entry -> // Create a copy to modify - mNewEntry = EntryVersioned(entry).also { newEntry -> + mNewEntry = Entry(entry).also { newEntry -> // WARNING Remove the parent to keep memory with parcelable newEntry.removeParent() } @@ -127,7 +125,7 @@ class EntryEditActivity : LockingHideActivity(), } // Parent is retrieve, it's a new entry to create - intent.getParcelableExtra>(KEY_PARENT)?.let { + intent.getParcelableExtra>(KEY_PARENT)?.let { mIsNew = true // Create an empty new entry if (savedInstanceState == null @@ -176,7 +174,7 @@ class EntryEditActivity : LockingHideActivity(), entryEditActivityEducation = EntryEditActivityEducation(this) // Create progress dialog - progressDialogThread = ProgressDialogThread(this) { actionTask, result -> + mProgressDialogThread?.onActionFinish = { actionTask, result -> when (actionTask) { ACTION_DATABASE_CREATE_ENTRY_TASK, ACTION_DATABASE_UPDATE_ENTRY_TASK -> { @@ -187,7 +185,7 @@ class EntryEditActivity : LockingHideActivity(), } } - private fun populateViewsWithEntry(newEntry: EntryVersioned) { + private fun populateViewsWithEntry(newEntry: Entry) { // Don't start the field reference manager, we want to see the raw ref mDatabase?.stopManageEntry(newEntry) @@ -209,7 +207,7 @@ class EntryEditActivity : LockingHideActivity(), } } - private fun populateEntryWithViews(newEntry: EntryVersioned) { + private fun populateEntryWithViews(newEntry: Entry) { mDatabase?.startManageEntry(newEntry) @@ -231,7 +229,7 @@ class EntryEditActivity : LockingHideActivity(), mDatabase?.stopManageEntry(newEntry) } - private fun temporarilySaveAndShowSelectedIcon(icon: PwIcon) { + private fun temporarilySaveAndShowSelectedIcon(icon: IconImage) { mNewEntry?.icon = icon mDatabase?.drawFactory?.let { iconDrawFactory -> entryEditContentsView?.setIcon(iconDrawFactory, icon) @@ -265,26 +263,26 @@ class EntryEditActivity : LockingHideActivity(), // WARNING Add the parent previously deleted newEntry.parent = mEntry?.parent // Build info - newEntry.lastAccessTime = PwDate() - newEntry.lastModificationTime = PwDate() + newEntry.lastAccessTime = DateInstant() + newEntry.lastModificationTime = DateInstant() populateEntryWithViews(newEntry) // Open a progress dialog and save entry if (mIsNew) { mParent?.let { parent -> - progressDialogThread?.startDatabaseCreateEntry( + mProgressDialogThread?.startDatabaseCreateEntry( newEntry, parent, - !mReadOnly + !mReadOnly && mAutoSaveEnable ) } } else { mEntry?.let { oldEntry -> - progressDialogThread?.startDatabaseUpdateEntry( + mProgressDialogThread?.startDatabaseUpdateEntry( oldEntry, newEntry, - !mReadOnly + !mReadOnly && mAutoSaveEnable ) } } @@ -292,25 +290,16 @@ class EntryEditActivity : LockingHideActivity(), } } - override fun onResume() { - super.onResume() - - progressDialogThread?.registerProgressTask() - } - - override fun onPause() { - progressDialogThread?.unregisterProgressTask() - - super.onPause() - } - override fun onCreateOptionsMenu(menu: Menu): Boolean { super.onCreateOptionsMenu(menu) val inflater = menuInflater - inflater.inflate(R.menu.database_lock, menu) + inflater.inflate(R.menu.database, menu) + // Save database not needed here + menu.findItem(R.id.menu_save_database)?.isVisible = false MenuUtil.contributionMenuInflater(inflater, menu) - inflater.inflate(R.menu.edit_entry, menu) + if (mDatabase?.allowOTP == true) + inflater.inflate(R.menu.entry_otp, menu) entryEditActivityEducation?.let { Handler().post { performedNextEducation(it) } @@ -351,12 +340,13 @@ class EntryEditActivity : LockingHideActivity(), lockAndExit() return true } - + R.id.menu_save_database -> { + mProgressDialogThread?.startDatabaseSave(!mReadOnly) + } R.id.menu_contribute -> { MenuUtil.onContributionItemSelected(this) return true } - R.id.menu_add_otp -> { // Retrieve the current otpElement if exists // and open the dialog to set up the OTP @@ -364,7 +354,6 @@ class EntryEditActivity : LockingHideActivity(), .show(supportFragmentManager, "addOTPDialog") return true } - android.R.id.home -> finish() } @@ -452,7 +441,7 @@ class EntryEditActivity : LockingHideActivity(), * @param activity from activity * @param pwEntry Entry to update */ - fun launch(activity: Activity, pwEntry: EntryVersioned) { + fun launch(activity: Activity, pwEntry: Entry) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { val intent = Intent(activity, EntryEditActivity::class.java) intent.putExtra(KEY_ENTRY, pwEntry.nodeId) @@ -466,7 +455,7 @@ class EntryEditActivity : LockingHideActivity(), * @param activity from activity * @param pwGroup Group who will contains new entry */ - fun launch(activity: Activity, pwGroup: GroupVersioned) { + fun launch(activity: Activity, pwGroup: Group) { if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { val intent = Intent(activity, EntryEditActivity::class.java) intent.putExtra(KEY_PARENT, pwGroup.nodeId) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt index 78ac5f64a..906825af5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/FileDatabaseSelectActivity.kt @@ -76,7 +76,7 @@ class FileDatabaseSelectActivity : StylishActivity(), private var mOpenFileHelper: OpenFileHelper? = null - private var progressDialogThread: ProgressDialogThread? = null + private var mProgressDialogThread: ProgressDialogThread? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -163,13 +163,15 @@ class FileDatabaseSelectActivity : StylishActivity(), } // Attach the dialog thread to this activity - progressDialogThread = ProgressDialogThread(this) { actionTask, _ -> - when (actionTask) { - ACTION_DATABASE_CREATE_TASK -> { - // TODO Check - // mAdapterDatabaseHistory?.notifyDataSetChanged() - // updateFileListVisibility() - GroupActivity.launch(this) + mProgressDialogThread = ProgressDialogThread(this).apply { + onActionFinish = { actionTask, _ -> + when (actionTask) { + ACTION_DATABASE_CREATE_TASK -> { + // TODO Check + // mAdapterDatabaseHistory?.notifyDataSetChanged() + // updateFileListVisibility() + GroupActivity.launch(this@FileDatabaseSelectActivity) + } } } } @@ -296,12 +298,12 @@ class FileDatabaseSelectActivity : StylishActivity(), } // Register progress task - progressDialogThread?.registerProgressTask() + mProgressDialogThread?.registerProgressTask() } override fun onPause() { // Unregister progress task - progressDialogThread?.unregisterProgressTask() + mProgressDialogThread?.unregisterProgressTask() super.onPause() } @@ -329,7 +331,7 @@ class FileDatabaseSelectActivity : StylishActivity(), mDatabaseFileUri?.let { databaseUri -> // Create the new database - progressDialogThread?.startDatabaseCreate( + mProgressDialogThread?.startDatabaseCreate( databaseUri, masterPasswordChecked, masterPassword, diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt index 1cf9d7771..c73aaf0a3 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.kt @@ -42,18 +42,18 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.fragment.app.FragmentManager import com.google.android.material.snackbar.Snackbar import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment -import com.kunzisoft.keepass.activities.dialogs.IconPickerDialogFragment -import com.kunzisoft.keepass.activities.dialogs.ReadOnlyDialog -import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment +import com.kunzisoft.keepass.activities.dialogs.* import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.lock.LockingActivity import com.kunzisoft.keepass.adapters.SearchEntryCursorAdapter import com.kunzisoft.keepass.autofill.AutofillHelper -import com.kunzisoft.keepass.database.SortNodeEnum -import com.kunzisoft.keepass.database.action.ProgressDialogThread +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.node.Node +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.education.GroupActivityEducation import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.magikeyboard.MagikIME @@ -77,6 +77,7 @@ class GroupActivity : LockingActivity(), IconPickerDialogFragment.IconPickerListener, ListNodesFragment.NodeClickListener, ListNodesFragment.NodesActionMenuListener, + DeleteNodesDialogFragment.DeleteNodeListener, ListNodesFragment.OnScrollListener, SortDialogFragment.SortSelectionListener { @@ -96,14 +97,10 @@ class GroupActivity : LockingActivity(), private var mListNodesFragment: ListNodesFragment? = null private var mCurrentGroupIsASearch: Boolean = false - private var progressDialogThread: ProgressDialogThread? = null - // Nodes - private var mRootGroup: GroupVersioned? = null - private var mCurrentGroup: GroupVersioned? = null - private var mOldGroupToUpdate: GroupVersioned? = null - // TODO private var mNodeToCopy: NodeVersioned? = null - // TODO private var mNodeToMove: NodeVersioned? = null + private var mRootGroup: Group? = null + private var mCurrentGroup: Group? = null + private var mOldGroupToUpdate: Group? = null private var mSearchSuggestionAdapter: SearchEntryCursorAdapter? = null @@ -134,14 +131,6 @@ class GroupActivity : LockingActivity(), toolbar?.title = "" setSupportActionBar(toolbar) - /* - toolbarAction?.setNavigationOnClickListener { - toolbarAction?.collapse() - mNodeToCopy = null - mNodeToMove = null - } - */ - // Focus view to reinitialize timeout resetAppTimeoutWhenViewFocusedOrChanged(addNodeButtonView) @@ -207,13 +196,13 @@ class GroupActivity : LockingActivity(), mSearchSuggestionAdapter = SearchEntryCursorAdapter(this, database) // Init dialog thread - progressDialogThread = ProgressDialogThread(this) { actionTask, result -> + mProgressDialogThread?.onActionFinish = { actionTask, result -> - var oldNodes: List = ArrayList() + var oldNodes: List = ArrayList() result.data?.getBundle(OLD_NODES_KEY)?.let { oldNodesBundle -> oldNodes = getListNodesFromBundle(database, oldNodesBundle) } - var newNodes: List = ArrayList() + var newNodes: List = ArrayList() result.data?.getBundle(NEW_NODES_KEY)?.let { newNodesBundle -> newNodes = getListNodesFromBundle(database, newNodesBundle) } @@ -234,24 +223,23 @@ class GroupActivity : LockingActivity(), ACTION_DATABASE_DELETE_NODES_TASK -> { if (result.isSuccess) { - // Rebuild all the list the avoid bug when delete node from db sort - if (PreferencesUtil.getListSort(this@GroupActivity) == SortNodeEnum.DB) { - mListNodesFragment?.rebuildList() - } else { - // Use the old Nodes / entries unchanged with the old parent - mListNodesFragment?.removeNodes(oldNodes) - } + // Rebuild all the list to avoid bug when delete node from sort + mListNodesFragment?.rebuildList() // Add trash in views list if it doesn't exists if (database.isRecycleBinEnabled) { val recycleBin = database.recycleBin - if (mCurrentGroup != null && recycleBin != null - && mCurrentGroup!!.parent == null - && mCurrentGroup != recycleBin) { - if (mListNodesFragment?.contains(recycleBin) == true) + val currentGroup = mCurrentGroup + if (currentGroup != null && recycleBin != null + && currentGroup != recycleBin) { + // Recycle bin already here, simply update it + if (mListNodesFragment?.contains(recycleBin) == true) { mListNodesFragment?.updateNode(recycleBin) - else + } + // Recycle bin not here, verify if parents are similar to add it + else if (currentGroup == recycleBin.parent) { mListNodesFragment?.addNode(recycleBin) + } } } } @@ -259,9 +247,11 @@ class GroupActivity : LockingActivity(), } if (!result.isSuccess) { - result.exception?.errorId?.let { errorId -> - coordinatorLayout?.let { coordinatorLayout -> + coordinatorLayout?.let { coordinatorLayout -> + result.exception?.errorId?.let { errorId -> Snackbar.make(coordinatorLayout, errorId, Snackbar.LENGTH_LONG).asError().show() + } ?: result.message?.let { message -> + Snackbar.make(coordinatorLayout, message, Snackbar.LENGTH_LONG).asError().show() } } } @@ -291,7 +281,7 @@ class GroupActivity : LockingActivity(), } } - private fun openSearchGroup(group: GroupVersioned?) { + private fun openSearchGroup(group: Group?) { // Delete the previous search fragment val searchFragment = supportFragmentManager.findFragmentByTag(SEARCH_FRAGMENT_TAG) if (searchFragment != null) { @@ -303,11 +293,11 @@ class GroupActivity : LockingActivity(), openGroup(group, true) } - private fun openChildGroup(group: GroupVersioned) { + private fun openChildGroup(group: Group) { openGroup(group, false) } - private fun openGroup(group: GroupVersioned?, isASearch: Boolean) { + private fun openGroup(group: Group?, isASearch: Boolean) { // Check TimeoutHelper TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) { // Open a group in a new fragment @@ -347,7 +337,7 @@ class GroupActivity : LockingActivity(), super.onSaveInstanceState(outState) } - private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): GroupVersioned? { + private fun retrieveCurrentGroup(intent: Intent, savedInstanceState: Bundle?): Group? { // Force read only if the database is like that mReadOnly = mDatabase?.isReadOnly == true || mReadOnly @@ -358,7 +348,7 @@ class GroupActivity : LockingActivity(), } // else a real group else { - var pwGroupId: PwNodeId<*>? = null + var pwGroupId: NodeId<*>? = null if (savedInstanceState != null && savedInstanceState.containsKey(GROUP_ID_KEY)) { pwGroupId = savedInstanceState.getParcelable(GROUP_ID_KEY) } else { @@ -367,7 +357,7 @@ class GroupActivity : LockingActivity(), } Log.w(TAG, "Creating tree view") - val currentGroup: GroupVersioned? + val currentGroup: Group? currentGroup = if (pwGroupId == null) { mRootGroup } else { @@ -470,16 +460,16 @@ class GroupActivity : LockingActivity(), addNodeButtonView?.hideButtonOnScrollListener(dy) } - override fun onNodeClick(node: NodeVersioned) { + override fun onNodeClick(node: Node) { when (node.type) { Type.GROUP -> try { - openChildGroup(node as GroupVersioned) + openChildGroup(node as Group) } catch (e: ClassCastException) { Log.e(TAG, "Node can't be cast in Group") } Type.ENTRY -> try { - val entryVersioned = node as EntryVersioned + val entryVersioned = node as Entry EntrySelectionHelper.doEntrySelectionAction(intent, { EntryActivity.launch(this@GroupActivity, entryVersioned, mReadOnly) @@ -517,7 +507,7 @@ class GroupActivity : LockingActivity(), actionNodeMode = null } - override fun onNodeSelected(nodes: List): Boolean { + override fun onNodeSelected(nodes: List): Boolean { if (nodes.isNotEmpty()) { if (actionNodeMode == null || toolbarAction?.getSupportActionModeCallback() == null) { mListNodesFragment?.actionNodesCallback(nodes, this)?.let { @@ -532,34 +522,34 @@ class GroupActivity : LockingActivity(), return true } - override fun onOpenMenuClick(node: NodeVersioned): Boolean { + override fun onOpenMenuClick(node: Node): Boolean { finishNodeAction() onNodeClick(node) return true } - override fun onEditMenuClick(node: NodeVersioned): Boolean { + override fun onEditMenuClick(node: Node): Boolean { finishNodeAction() when (node.type) { Type.GROUP -> { - mOldGroupToUpdate = node as GroupVersioned + mOldGroupToUpdate = node as Group GroupEditDialogFragment.build(mOldGroupToUpdate!!) .show(supportFragmentManager, GroupEditDialogFragment.TAG_CREATE_GROUP) } - Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as EntryVersioned) + Type.ENTRY -> EntryEditActivity.launch(this@GroupActivity, node as Entry) } return true } - override fun onCopyMenuClick(nodes: List): Boolean { + override fun onCopyMenuClick(nodes: List): Boolean { actionNodeMode?.invalidate() // Nothing here fragment calls onPasteMenuClick internally return true } - override fun onMoveMenuClick(nodes: List): Boolean { + override fun onMoveMenuClick(nodes: List): Boolean { actionNodeMode?.invalidate() // Nothing here fragment calls onPasteMenuClick internally @@ -567,56 +557,88 @@ class GroupActivity : LockingActivity(), } override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?, - nodes: List): Boolean { - when (pasteMode) { - ListNodesFragment.PasteMode.PASTE_FROM_COPY -> { - // Copy - mCurrentGroup?.let { newParent -> - progressDialogThread?.startDatabaseCopyNodes( - nodes, - newParent, - !mReadOnly - ) + nodes: List): Boolean { + // Move or copy only if allowed (in root if allowed) + if (mCurrentGroup != mDatabase?.rootGroup + || mDatabase?.rootCanContainsEntry() == true) { + + when (pasteMode) { + ListNodesFragment.PasteMode.PASTE_FROM_COPY -> { + // Copy + mCurrentGroup?.let { newParent -> + mProgressDialogThread?.startDatabaseCopyNodes( + nodes, + newParent, + !mReadOnly && mAutoSaveEnable + ) + } } - } - ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> { - // Move - mCurrentGroup?.let { newParent -> - progressDialogThread?.startDatabaseMoveNodes( - nodes, - newParent, - !mReadOnly - ) + ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> { + // Move + mCurrentGroup?.let { newParent -> + mProgressDialogThread?.startDatabaseMoveNodes( + nodes, + newParent, + !mReadOnly && mAutoSaveEnable + ) + } + } + else -> { } } - else -> {} + } else { + coordinatorLayout?.let { coordinatorLayout -> + Snackbar.make(coordinatorLayout, + R.string.error_copy_entry_here, + Snackbar.LENGTH_LONG).asError().show() + } } finishNodeAction() return true } - override fun onDeleteMenuClick(nodes: List): Boolean { - progressDialogThread?.startDatabaseDeleteNodes( - nodes, - !mReadOnly - ) + override fun onDeleteMenuClick(nodes: List): Boolean { + val database = mDatabase + + // If recycle bin enabled, ensure it exists + if (database != null && database.isRecycleBinEnabled) { + database.ensureRecycleBinExists(resources) + } + + // If recycle bin enabled and not in recycle bin, move in recycle bin + if (database != null + && database.isRecycleBinEnabled + && database.recycleBin != mCurrentGroup) { + mProgressDialogThread?.startDatabaseDeleteNodes( + nodes, + !mReadOnly && mAutoSaveEnable + ) + } + // else open the dialog to confirm deletion + else { + DeleteNodesDialogFragment.getInstance(nodes) + .show(supportFragmentManager, "deleteNodesDialogFragment") + } finishNodeAction() return true } + override fun permanentlyDeleteNodes(nodes: List) { + mProgressDialogThread?.startDatabaseDeleteNodes( + nodes, + !mReadOnly && mAutoSaveEnable + ) + } + override fun onResume() { super.onResume() // Refresh the elements assignGroupViewElements() // Refresh suggestions to change preferences mSearchSuggestionAdapter?.reInit(this) - - progressDialogThread?.registerProgressTask() } override fun onPause() { - progressDialogThread?.unregisterProgressTask() - super.onPause() finishNodeAction() @@ -626,12 +648,21 @@ class GroupActivity : LockingActivity(), val inflater = menuInflater inflater.inflate(R.menu.search, menu) - inflater.inflate(R.menu.database_lock, menu) + inflater.inflate(R.menu.database, menu) + if (mReadOnly) { + menu.findItem(R.id.menu_save_database)?.isVisible = false + } if (!mSelectionMode) { inflater.inflate(R.menu.default_menu, menu) MenuUtil.contributionMenuInflater(inflater, menu) } + // Menu for recycle bin + if (mDatabase?.isRecycleBinEnabled == true + && mDatabase?.recycleBin == mCurrentGroup) { + inflater.inflate(R.menu.recycle_bin, menu) + } + // Get the SearchView and set the searchable configuration val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager @@ -740,6 +771,17 @@ class GroupActivity : LockingActivity(), lockAndExit() return true } + R.id.menu_save_database -> { + mProgressDialogThread?.startDatabaseSave(!mReadOnly) + return true + } + R.id.menu_empty_recycle_bin -> { + mCurrentGroup?.getChildren()?.let { listChildren -> + // Automatically delete all elements + onDeleteMenuClick(listChildren) + } + return true + } else -> { // Check the time lock before launching settings MenuUtil.onDefaultMenuOptionsItemSelected(this, item, mReadOnly, true) @@ -750,7 +792,7 @@ class GroupActivity : LockingActivity(), override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?, name: String?, - icon: PwIcon?) { + icon: IconImage?) { if (name != null && name.isNotEmpty() && icon != null) { when (action) { @@ -764,28 +806,33 @@ class GroupActivity : LockingActivity(), // Not really needed here because added in runnable but safe newGroup.parent = currentGroup - progressDialogThread?.startDatabaseCreateGroup( - newGroup, currentGroup, !mReadOnly) + mProgressDialogThread?.startDatabaseCreateGroup( + newGroup, + currentGroup, + !mReadOnly && mAutoSaveEnable + ) } } } GroupEditDialogFragment.EditGroupDialogAction.UPDATE -> { // If update add new elements mOldGroupToUpdate?.let { oldGroupToUpdate -> - GroupVersioned(oldGroupToUpdate).let { updateGroup -> + val updateGroup = Group(oldGroupToUpdate).let { updateGroup -> updateGroup.apply { // WARNING remove parent and children to keep memory removeParent() - removeChildren() // TODO concurrent exception + removeChildren() title = name this.icon = icon // TODO custom icon } - - // If group updated save it in the database - progressDialogThread?.startDatabaseUpdateGroup( - oldGroupToUpdate, updateGroup, !mReadOnly) } + // If group updated save it in the database + mProgressDialogThread?.startDatabaseUpdateGroup( + oldGroupToUpdate, + updateGroup, + !mReadOnly && mAutoSaveEnable + ) } } else -> {} @@ -795,7 +842,7 @@ class GroupActivity : LockingActivity(), override fun cancelEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction?, name: String?, - icon: PwIcon?) { + icon: IconImage?) { // Do nothing here } @@ -906,7 +953,7 @@ class GroupActivity : LockingActivity(), private const val SEARCH_FRAGMENT_TAG = "SEARCH_FRAGMENT_TAG" private const val OLD_GROUP_TO_UPDATE_KEY = "OLD_GROUP_TO_UPDATE_KEY" - private fun buildAndLaunchIntent(context: Context, group: GroupVersioned?, readOnly: Boolean, + private fun buildAndLaunchIntent(context: Context, group: Group?, readOnly: Boolean, intentBuildLauncher: (Intent) -> Unit) { val checkTime = if (context is Activity) TimeoutHelper.checkTimeAndLockIfTimeout(context) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt index 470034783..600a089a4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/ListNodesFragment.kt @@ -18,16 +18,16 @@ import androidx.appcompat.view.ActionMode import com.kunzisoft.keepass.R import com.kunzisoft.keepass.adapters.NodeAdapter -import com.kunzisoft.keepass.database.SortNodeEnum -import com.kunzisoft.keepass.database.element.GroupVersioned -import com.kunzisoft.keepass.database.element.NodeVersioned +import com.kunzisoft.keepass.database.element.SortNodeEnum +import com.kunzisoft.keepass.database.element.Group +import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.activities.dialogs.SortDialogFragment import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.activities.stylish.StylishFragment import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.element.Type +import com.kunzisoft.keepass.database.element.node.Type import java.util.* class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionListener { @@ -36,7 +36,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis private var onScrollListener: OnScrollListener? = null private var listView: RecyclerView? = null - var mainGroup: GroupVersioned? = null + var mainGroup: Group? = null private set private var mAdapter: NodeAdapter? = null @@ -44,8 +44,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis private set var nodeActionPasteMode: PasteMode = PasteMode.UNDEFINED private set - private val listActionNodes = LinkedList() - private val listPasteNodes = LinkedList() + private val listActionNodes = LinkedList() + private val listPasteNodes = LinkedList() private var notFoundView: View? = null private var isASearchResult: Boolean = false @@ -103,7 +103,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis mAdapter = NodeAdapter(context) mAdapter?.apply { setOnNodeClickListener(object : NodeAdapter.NodeClickCallback { - override fun onNodeClick(node: NodeVersioned) { + override fun onNodeClick(node: Node) { if (nodeActionSelectionMode) { if (listActionNodes.contains(node)) { // Remove selected item if already selected @@ -120,7 +120,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis } } - override fun onNodeLongClick(node: NodeVersioned): Boolean { + override fun onNodeLongClick(node: Node): Boolean { if (nodeActionPasteMode == PasteMode.UNDEFINED) { // Select the first item after a long click if (!listActionNodes.contains(node)) @@ -228,8 +228,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis R.id.menu_sort -> { context?.let { context -> val sortDialogFragment: SortDialogFragment = - if (Database.getInstance().allowRecycleBin - && Database.getInstance().isRecycleBinEnabled) { + if (Database.getInstance().isRecycleBinEnabled) { SortDialogFragment.getInstance( PreferencesUtil.getListSort(context), PreferencesUtil.getAscendingSort(context), @@ -251,7 +250,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis } } - fun actionNodesCallback(nodes: List, + fun actionNodesCallback(nodes: List, menuListener: NodesActionMenuListener?) : ActionMode.Callback { return object : ActionMode.Callback { @@ -276,7 +275,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis // Open and Edit for a single item if (nodes.size == 1) { // Edition - if (readOnly || nodes[0] == database.recycleBin) { + if (readOnly + || (database.isRecycleBinEnabled && nodes[0] == database.recycleBin)) { menu?.removeItem(R.id.menu_edit) } } else { @@ -287,7 +287,6 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis // Copy and Move (not for groups) if (readOnly || isASearchResult - || nodes.any { it == database.recycleBin } || nodes.any { it.type == Type.GROUP }) { // TODO COPY For Group menu?.removeItem(R.id.menu_copy) @@ -295,7 +294,8 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis } // Deletion - if (readOnly || nodes.any { it == database.recycleBin }) { + if (readOnly + || (database.isRecycleBinEnabled && nodes.any { it == database.recycleBin })) { menu?.removeItem(R.id.menu_delete) } } @@ -354,7 +354,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE -> { if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE || resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) { - data?.getParcelableExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { newNode -> + data?.getParcelableExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY)?.let { newNode -> if (resultCode == EntryEditActivity.ADD_ENTRY_RESULT_CODE) mAdapter?.addNode(newNode) if (resultCode == EntryEditActivity.UPDATE_ENTRY_RESULT_CODE) { @@ -369,31 +369,31 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis } } - fun contains(node: NodeVersioned): Boolean { + fun contains(node: Node): Boolean { return mAdapter?.contains(node) ?: false } - fun addNode(newNode: NodeVersioned) { + fun addNode(newNode: Node) { mAdapter?.addNode(newNode) } - fun addNodes(newNodes: List) { + fun addNodes(newNodes: List) { mAdapter?.addNodes(newNodes) } - fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned? = null) { + fun updateNode(oldNode: Node, newNode: Node? = null) { mAdapter?.updateNode(oldNode, newNode ?: oldNode) } - fun updateNodes(oldNodes: List, newNodes: List) { + fun updateNodes(oldNodes: List, newNodes: List) { mAdapter?.updateNodes(oldNodes, newNodes) } - fun removeNode(pwNode: NodeVersioned) { + fun removeNode(pwNode: Node) { mAdapter?.removeNode(pwNode) } - fun removeNodes(nodes: List) { + fun removeNodes(nodes: List) { mAdapter?.removeNodes(nodes) } @@ -409,20 +409,20 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis * Callback listener to redefine to do an action when a node is click */ interface NodeClickListener { - fun onNodeClick(node: NodeVersioned) - fun onNodeSelected(nodes: List): Boolean + fun onNodeClick(node: Node) + fun onNodeSelected(nodes: List): Boolean } /** * Menu listener to redefine to do an action in menu */ interface NodesActionMenuListener { - fun onOpenMenuClick(node: NodeVersioned): Boolean - fun onEditMenuClick(node: NodeVersioned): Boolean - fun onCopyMenuClick(nodes: List): Boolean - fun onMoveMenuClick(nodes: List): Boolean - fun onDeleteMenuClick(nodes: List): Boolean - fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List): Boolean + fun onOpenMenuClick(node: Node): Boolean + fun onEditMenuClick(node: Node): Boolean + fun onCopyMenuClick(nodes: List): Boolean + fun onMoveMenuClick(nodes: List): Boolean + fun onDeleteMenuClick(nodes: List): Boolean + fun onPasteMenuClick(pasteMode: PasteMode?, nodes: List): Boolean } enum class PasteMode { @@ -447,7 +447,7 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis private const val GROUP_KEY = "GROUP_KEY" private const val IS_SEARCH = "IS_SEARCH" - fun newInstance(group: GroupVersioned?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment { + fun newInstance(group: Group?, readOnly: Boolean, isASearch: Boolean): ListNodesFragment { val bundle = Bundle() if (group != null) { bundle.putParcelable(GROUP_KEY, group) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt index 8340b7eff..2593baa95 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/PasswordActivity.kt @@ -58,7 +58,7 @@ import com.kunzisoft.keepass.autofill.AutofillHelper import com.kunzisoft.keepass.biometric.AdvancedUnlockedManager import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException +import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException import com.kunzisoft.keepass.education.PasswordActivityEducation import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_LOAD_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.CIPHER_ENTITY_KEY @@ -101,7 +101,7 @@ class PasswordActivity : StylishActivity() { private var readOnly: Boolean = false - private var progressDialogThread: ProgressDialogThread? = null + private var mProgressDialogThread: ProgressDialogThread? = null private var advancedUnlockedManager: AdvancedUnlockedManager? = null @@ -163,69 +163,71 @@ class PasswordActivity : StylishActivity() { enableOrNotTheConfirmationButton() } - progressDialogThread = ProgressDialogThread(this) { actionTask, result -> - when (actionTask) { - ACTION_DATABASE_LOAD_TASK -> { - // Recheck biometric if error - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) { - // Stay with the same mode and init it - advancedUnlockedManager?.initBiometricMode() + mProgressDialogThread = ProgressDialogThread(this).apply { + onActionFinish = { actionTask, result -> + when (actionTask) { + ACTION_DATABASE_LOAD_TASK -> { + // Recheck biometric if error + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (PreferencesUtil.isBiometricUnlockEnable(this@PasswordActivity)) { + // Stay with the same mode and init it + advancedUnlockedManager?.initBiometricMode() + } } - } - - // Remove the password in view in all cases - removePassword() - - if (result.isSuccess) { - launchGroupActivity() - } else { - var resultError = "" - val resultException = result.exception - val resultMessage = result.message - - if (resultException != null) { - resultError = resultException.getLocalizedMessage(resources) - - // Relaunch loading if we need to fix UUID - if (resultException is LoadDatabaseDuplicateUuidException) { - showLoadDatabaseDuplicateUuidMessage { - - var databaseUri: Uri? = null - var masterPassword: String? = null - var keyFileUri: Uri? = null - var readOnly = true - var cipherEntity: CipherDatabaseEntity? = null - - result.data?.let { resultData -> - databaseUri = resultData.getParcelable(DATABASE_URI_KEY) - masterPassword = resultData.getString(MASTER_PASSWORD_KEY) - keyFileUri = resultData.getParcelable(KEY_FILE_KEY) - readOnly = resultData.getBoolean(READ_ONLY_KEY) - cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY) - } - databaseUri?.let { databaseFileUri -> - showProgressDialogAndLoadDatabase( - databaseFileUri, - masterPassword, - keyFileUri, - readOnly, - cipherEntity, - true) + // Remove the password in view in all cases + removePassword() + + if (result.isSuccess) { + launchGroupActivity() + } else { + var resultError = "" + val resultException = result.exception + val resultMessage = result.message + + if (resultException != null) { + resultError = resultException.getLocalizedMessage(resources) + + // Relaunch loading if we need to fix UUID + if (resultException is DuplicateUuidDatabaseException) { + showLoadDatabaseDuplicateUuidMessage { + + var databaseUri: Uri? = null + var masterPassword: String? = null + var keyFileUri: Uri? = null + var readOnly = true + var cipherEntity: CipherDatabaseEntity? = null + + result.data?.let { resultData -> + databaseUri = resultData.getParcelable(DATABASE_URI_KEY) + masterPassword = resultData.getString(MASTER_PASSWORD_KEY) + keyFileUri = resultData.getParcelable(KEY_FILE_KEY) + readOnly = resultData.getBoolean(READ_ONLY_KEY) + cipherEntity = resultData.getParcelable(CIPHER_ENTITY_KEY) + } + + databaseUri?.let { databaseFileUri -> + showProgressDialogAndLoadDatabase( + databaseFileUri, + masterPassword, + keyFileUri, + readOnly, + cipherEntity, + true) + } } } } - } - // Show error message - if (resultMessage != null && resultMessage.isNotEmpty()) { - resultError = "$resultError $resultMessage" + // Show error message + if (resultMessage != null && resultMessage.isNotEmpty()) { + resultError = "$resultError $resultMessage" + } + Log.e(TAG, resultError, resultException) + Snackbar.make(activity_password_coordinator_layout, + resultError, + Snackbar.LENGTH_LONG).asError().show() } - Log.e(TAG, resultError, resultException) - Snackbar.make(activity_password_coordinator_layout, - resultError, - Snackbar.LENGTH_LONG).asError().show() } } } @@ -272,7 +274,7 @@ class PasswordActivity : StylishActivity() { // For check shutdown super.onResume() - progressDialogThread?.registerProgressTask() + mProgressDialogThread?.registerProgressTask() initUriFromIntent() } @@ -399,7 +401,7 @@ class PasswordActivity : StylishActivity() { } }) } - advancedUnlockedManager?.initBiometric() + advancedUnlockedManager?.checkBiometricAvailability() biometricInitialize = true } else { advancedUnlockedManager?.destroy() @@ -459,7 +461,7 @@ class PasswordActivity : StylishActivity() { } override fun onPause() { - progressDialogThread?.unregisterProgressTask() + mProgressDialogThread?.unregisterProgressTask() super.onPause() } @@ -527,7 +529,7 @@ class PasswordActivity : StylishActivity() { readOnly: Boolean, cipherDatabaseEntity: CipherDatabaseEntity?, fixDuplicateUUID: Boolean) { - progressDialogThread?.startDatabaseLoad( + mProgressDialogThread?.startDatabaseLoad( databaseUri, password, keyFile, diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DeleteNodesDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DeleteNodesDialogFragment.kt new file mode 100644 index 000000000..9ff06d9f2 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/DeleteNodesDialogFragment.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2019 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePass DX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.activities.dialogs + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.database.element.Database +import com.kunzisoft.keepass.database.element.node.Node +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getListNodesFromBundle + +class DeleteNodesDialogFragment : DialogFragment() { + + private var mNodesToDelete: List = ArrayList() + private var mListener: DeleteNodeListener? = null + + override fun onAttach(context: Context) { + super.onAttach(context) + try { + mListener = context as DeleteNodeListener + } catch (e: ClassCastException) { + throw ClassCastException(context.toString() + + " must implement " + DeleteNodeListener::class.java.name) + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + + arguments?.apply { + if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY) + && containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) { + mNodesToDelete = getListNodesFromBundle(Database.getInstance(), this) + } + } ?: savedInstanceState?.apply { + if (containsKey(DatabaseTaskNotificationService.GROUPS_ID_KEY) + && containsKey(DatabaseTaskNotificationService.ENTRIES_ID_KEY)) { + mNodesToDelete = getListNodesFromBundle(Database.getInstance(), savedInstanceState) + } + } + activity?.let { activity -> + // Use the Builder class for convenient dialog construction + val builder = AlertDialog.Builder(activity) + + builder.setMessage(getString(R.string.warning_permanently_delete_nodes)) + builder.setPositiveButton(android.R.string.yes) { _, _ -> + mListener?.permanentlyDeleteNodes(mNodesToDelete) + } + builder.setNegativeButton(android.R.string.no) { _, _ -> dismiss() } + // Create the AlertDialog object and return it + return builder.create() + } + return super.onCreateDialog(savedInstanceState) + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putAll(getBundleFromListNodes(mNodesToDelete)) + } + + interface DeleteNodeListener { + fun permanentlyDeleteNodes(nodes: List) + } + + companion object { + fun getInstance(nodesToDelete: List): DeleteNodesDialogFragment { + return DeleteNodesDialogFragment().apply { + arguments = getBundleFromListNodes(nodesToDelete) + } + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt index 8f126d083..1036d2733 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/GroupEditDialogFragment.kt @@ -33,8 +33,8 @@ import com.kunzisoft.keepass.R import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.CREATION import com.kunzisoft.keepass.activities.dialogs.GroupEditDialogFragment.EditGroupDialogAction.UPDATE import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.element.GroupVersioned -import com.kunzisoft.keepass.database.element.PwIcon +import com.kunzisoft.keepass.database.element.Group +import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.icons.assignDatabaseIcon class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconPickerListener { @@ -45,7 +45,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP private var editGroupDialogAction: EditGroupDialogAction? = null private var nameGroup: String? = null - private var iconGroup: PwIcon? = null + private var iconGroup: IconImage? = null private var nameTextLayoutView: TextInputLayout? = null private var nameTextView: TextView? = null @@ -186,8 +186,8 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP } interface EditGroupListener { - fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?) - fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: PwIcon?) + fun approveEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?) + fun cancelEditGroup(action: EditGroupDialogAction?, name: String?, icon: IconImage?) } companion object { @@ -206,7 +206,7 @@ class GroupEditDialogFragment : DialogFragment(), IconPickerDialogFragment.IconP return fragment } - fun build(group: GroupVersioned): GroupEditDialogFragment { + fun build(group: Group): GroupEditDialogFragment { val bundle = Bundle() bundle.putString(KEY_NAME, group.title) bundle.putParcelable(KEY_ICON, group.icon) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/IconPickerDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/IconPickerDialogFragment.kt index 7b0691ba9..5bd71068e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/IconPickerDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/IconPickerDialogFragment.kt @@ -35,7 +35,7 @@ import android.widget.GridView import android.widget.ImageView import com.kunzisoft.keepass.R import com.kunzisoft.keepass.activities.stylish.StylishActivity -import com.kunzisoft.keepass.database.element.PwIconStandard +import com.kunzisoft.keepass.database.element.icon.IconImageStandard import com.kunzisoft.keepass.icons.IconPack import com.kunzisoft.keepass.icons.IconPackChooser @@ -72,7 +72,7 @@ class IconPickerDialogFragment : DialogFragment() { currIconGridView.setOnItemClickListener { _, _, position, _ -> val bundle = Bundle() - bundle.putParcelable(KEY_ICON_STANDARD, PwIconStandard(position)) + bundle.putParcelable(KEY_ICON_STANDARD, IconImageStandard(position)) iconPickerListener?.iconPicked(bundle) dismiss() } @@ -128,7 +128,7 @@ class IconPickerDialogFragment : DialogFragment() { private const val KEY_ICON_STANDARD = "KEY_ICON_STANDARD" - fun getIconStandardFromBundle(bundle: Bundle): PwIconStandard? { + fun getIconStandardFromBundle(bundle: Bundle): IconImageStandard? { return bundle.getParcelable(KEY_ICON_STANDARD) } diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt index 5cb840453..79f762075 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SetOTPDialogFragment.kt @@ -29,6 +29,7 @@ import com.kunzisoft.keepass.otp.OtpElement.Companion.MIN_TOTP_PERIOD import com.kunzisoft.keepass.otp.OtpTokenType import com.kunzisoft.keepass.otp.OtpType import com.kunzisoft.keepass.otp.TokenCalculator +import java.util.* class SetOTPDialogFragment : DialogFragment() { @@ -246,7 +247,7 @@ class SetOTPDialogFragment : DialogFragment() { override fun afterTextChanged(s: Editable?) { s?.toString()?.let { userString -> try { - mOtpElement.setBase32Secret(userString) + mOtpElement.setBase32Secret(userString.toUpperCase(Locale.ENGLISH)) otpSecretContainer?.error = null } catch (exception: Exception) { otpSecretContainer?.error = getString(R.string.error_otp_secret_key) diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SortDialogFragment.kt b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SortDialogFragment.kt index 9c17defbb..4b16bd891 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SortDialogFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/dialogs/SortDialogFragment.kt @@ -29,7 +29,7 @@ import android.view.View import android.widget.CompoundButton import android.widget.RadioGroup import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.SortNodeEnum +import com.kunzisoft.keepass.database.element.SortNodeEnum class SortDialogFragment : DialogFragment() { diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt index 574ad9f95..94b6a6edc 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt @@ -32,6 +32,7 @@ import android.view.ViewGroup import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.stylish.StylishActivity +import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.notifications.KeyboardEntryNotificationService import com.kunzisoft.keepass.magikeyboard.MagikIME @@ -63,6 +64,10 @@ abstract class LockingActivity : StylishActivity() { return field || mSelectionMode } protected var mSelectionMode: Boolean = false + protected var mAutoSaveEnable: Boolean = true + + var mProgressDialogThread: ProgressDialogThread? = null + private set override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -86,6 +91,8 @@ abstract class LockingActivity : StylishActivity() { mExitLock = false mReadOnly = ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrIntent(savedInstanceState, intent) + + mProgressDialogThread = ProgressDialogThread(this) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -101,8 +108,13 @@ abstract class LockingActivity : StylishActivity() { override fun onResume() { super.onResume() + mProgressDialogThread?.registerProgressTask() + // To refresh when back to normal workflow from selection workflow mSelectionMode = EntrySelectionHelper.retrieveEntrySelectionModeFromIntent(intent) + mAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(this) + + invalidateOptionsMenu() if (mTimeoutEnable) { // End activity if database not loaded @@ -119,8 +131,6 @@ abstract class LockingActivity : StylishActivity() { if (!mExitLock) TimeoutHelper.recordTime(this) } - - invalidateOptionsMenu() } override fun onSaveInstanceState(outState: Bundle) { @@ -130,6 +140,8 @@ abstract class LockingActivity : StylishActivity() { } override fun onPause() { + mProgressDialogThread?.unregisterProgressTask() + super.onPause() if (mTimeoutEnable) { diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/EntryHistoryAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/EntryHistoryAdapter.kt index bd6744cf2..035f07577 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/EntryHistoryAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/EntryHistoryAdapter.kt @@ -7,13 +7,13 @@ import android.view.ViewGroup import android.widget.TextView import androidx.recyclerview.widget.RecyclerView import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.element.EntryVersioned +import com.kunzisoft.keepass.database.element.Entry class EntryHistoryAdapter(val context: Context) : RecyclerView.Adapter() { private val inflater: LayoutInflater = LayoutInflater.from(context) - var entryHistoryList: MutableList = ArrayList() - var onItemClickListener: ((item: EntryVersioned, position: Int)->Unit)? = null + var entryHistoryList: MutableList = ArrayList() + var onItemClickListener: ((item: Entry, position: Int)->Unit)? = null override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EntryHistoryViewHolder { return EntryHistoryViewHolder(inflater.inflate(R.layout.item_list_entry_history, parent, false)) diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt index f22e4cb88..8af26d21e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/NodeAdapter.kt @@ -34,8 +34,10 @@ import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.SortedListAdapterCallback import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.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.Type import com.kunzisoft.keepass.icons.assignDatabaseIcon import com.kunzisoft.keepass.settings.PreferencesUtil import java.util.* @@ -48,7 +50,7 @@ class NodeAdapter (private val context: Context) : RecyclerView.Adapter() { - private val nodeSortedList: SortedList + private val nodeSortedList: SortedList private val inflater: LayoutInflater = LayoutInflater.from(context) private var calculateViewTypeTextSize = Array(2) { true} // number of view type @@ -65,7 +67,7 @@ class NodeAdapter private var showUserNames: Boolean = true private var showNumberEntries: Boolean = true - private var actionNodesList = LinkedList() + private var actionNodesList = LinkedList() private var nodeClickCallback: NodeClickCallback? = null private val mDatabase: Database @@ -83,18 +85,18 @@ class NodeAdapter init { assignPreferences() - this.nodeSortedList = SortedList(NodeVersioned::class.java, object : SortedListAdapterCallback(this) { - override fun compare(item1: NodeVersioned, item2: NodeVersioned): Int { + this.nodeSortedList = SortedList(Node::class.java, object : SortedListAdapterCallback(this) { + override fun compare(item1: Node, item2: Node): Int { return listSort.getNodeComparator(ascendingSort, groupsBeforeSort, recycleBinBottomSort).compare(item1, item2) } - override fun areContentsTheSame(oldItem: NodeVersioned, newItem: NodeVersioned): Boolean { + override fun areContentsTheSame(oldItem: Node, newItem: Node): Boolean { return oldItem.type == newItem.type && oldItem.title == newItem.title && oldItem.icon == newItem.icon } - override fun areItemsTheSame(item1: NodeVersioned, item2: NodeVersioned): Boolean { + override fun areItemsTheSame(item1: Node, item2: Node): Boolean { return item1 == item2 } }) @@ -133,7 +135,7 @@ class NodeAdapter /** * Rebuild the list by clear and build children from the group */ - fun rebuildList(group: GroupVersioned) { + fun rebuildList(group: Group) { this.nodeSortedList.clear() assignPreferences() try { @@ -145,7 +147,7 @@ class NodeAdapter notifyDataSetChanged() } - fun contains(node: NodeVersioned): Boolean { + fun contains(node: Node): Boolean { return nodeSortedList.indexOf(node) != SortedList.INVALID_POSITION } @@ -153,7 +155,7 @@ class NodeAdapter * Add a node to the list * @param node Node to add */ - fun addNode(node: NodeVersioned) { + fun addNode(node: Node) { nodeSortedList.add(node) } @@ -161,7 +163,7 @@ class NodeAdapter * Add nodes to the list * @param nodes Nodes to add */ - fun addNodes(nodes: List) { + fun addNodes(nodes: List) { nodeSortedList.addAll(nodes) } @@ -169,7 +171,7 @@ class NodeAdapter * Remove a node in the list * @param node Node to delete */ - fun removeNode(node: NodeVersioned) { + fun removeNode(node: Node) { nodeSortedList.remove(node) } @@ -177,7 +179,7 @@ class NodeAdapter * Remove nodes in the list * @param nodes Nodes to delete */ - fun removeNodes(nodes: List) { + fun removeNodes(nodes: List) { nodes.forEach { node -> nodeSortedList.remove(node) } @@ -209,7 +211,7 @@ class NodeAdapter * @param oldNode Node before the update * @param newNode Node after the update */ - fun updateNode(oldNode: NodeVersioned, newNode: NodeVersioned) { + fun updateNode(oldNode: Node, newNode: Node) { nodeSortedList.beginBatchedUpdates() nodeSortedList.remove(oldNode) nodeSortedList.add(newNode) @@ -221,7 +223,7 @@ class NodeAdapter * @param oldNodes Nodes before the update * @param newNodes Node after the update */ - fun updateNodes(oldNodes: List, newNodes: List) { + fun updateNodes(oldNodes: List, newNodes: List) { nodeSortedList.beginBatchedUpdates() oldNodes.forEach { oldNode -> nodeSortedList.remove(oldNode) @@ -230,11 +232,11 @@ class NodeAdapter nodeSortedList.endBatchedUpdates() } - fun notifyNodeChanged(node: NodeVersioned) { + fun notifyNodeChanged(node: Node) { notifyItemChanged(nodeSortedList.indexOf(node)) } - fun setActionNodes(actionNodes: List) { + fun setActionNodes(actionNodes: List) { this.actionNodesList.apply { clear() addAll(actionNodes) @@ -315,7 +317,7 @@ class NodeAdapter paintFlags and Paint.STRIKE_THRU_TEXT_FLAG visibility = View.GONE if (subNode.type == Type.ENTRY) { - val entry = subNode as EntryVersioned + val entry = subNode as Entry mDatabase.startManageEntry(entry) @@ -336,7 +338,7 @@ class NodeAdapter if (subNode.type == Type.GROUP) { if (showNumberEntries) { holder.numberChildren?.apply { - text = (subNode as GroupVersioned).getChildEntries(true).size.toString() + text = (subNode as Group).getChildEntries(true).size.toString() setTextSize(textSizeUnit, numberChildrenTextSize) visibility = View.VISIBLE } @@ -361,8 +363,8 @@ class NodeAdapter * Callback listener to redefine to do an action when a node is click */ interface NodeClickCallback { - fun onNodeClick(node: NodeVersioned) - fun onNodeLongClick(node: NodeVersioned): Boolean + fun onNodeClick(node: Node) + fun onNodeLongClick(node: Node): Boolean } class NodeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { diff --git a/app/src/main/java/com/kunzisoft/keepass/adapters/SearchEntryCursorAdapter.kt b/app/src/main/java/com/kunzisoft/keepass/adapters/SearchEntryCursorAdapter.kt index 93d733df6..14a574643 100644 --- a/app/src/main/java/com/kunzisoft/keepass/adapters/SearchEntryCursorAdapter.kt +++ b/app/src/main/java/com/kunzisoft/keepass/adapters/SearchEntryCursorAdapter.kt @@ -22,7 +22,6 @@ package com.kunzisoft.keepass.adapters import android.content.Context import android.database.Cursor import android.graphics.Color -import androidx.cursoradapter.widget.CursorAdapter import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -31,8 +30,8 @@ import android.widget.TextView 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.EntryVersioned -import com.kunzisoft.keepass.database.element.PwIcon +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.settings.PreferencesUtil import java.util.* @@ -76,7 +75,7 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database) 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: PwIcon = iconFactory.getIcon( + 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) { @@ -94,7 +93,7 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database) viewHolder.imageViewIcon?.assignDatabaseIcon(database.drawFactory, icon, iconColor) // Assign title - val showTitle = EntryVersioned.getVisualTitle(false, title, username, url, uuid.toString()) + val showTitle = Entry.getVisualTitle(false, title, username, url, uuid.toString()) viewHolder.textViewTitle?.text = showTitle if (displayUsername && username.isNotEmpty()) { viewHolder.textViewSubTitle?.text = String.format("(%s)", username) @@ -113,8 +112,8 @@ class SearchEntryCursorAdapter(context: Context, private val database: Database) return database.searchEntries(constraint.toString()) } - fun getEntryFromPosition(position: Int): EntryVersioned? { - var pwEntry: EntryVersioned? = null + fun getEntryFromPosition(position: Int): Entry? { + var pwEntry: Entry? = null val cursor = this.cursor if (cursor.moveToFirst() && cursor.move(position)) { diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt index 6d8e14ae0..5530da408 100644 --- a/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/AdvancedUnlockedManager.kt @@ -10,6 +10,7 @@ import android.view.MenuInflater import android.widget.CompoundButton import android.widget.TextView import androidx.annotation.RequiresApi +import androidx.biometric.BiometricConstants import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt import androidx.fragment.app.FragmentActivity @@ -36,65 +37,59 @@ class AdvancedUnlockedManager(var context: FragmentActivity, private var cipherDatabaseAction = CipherDatabaseAction.getInstance(context.applicationContext) - // fingerprint related code here - fun initBiometric() { - - // Check if fingerprint well init (be called the first time the fingerprint is configured - // and the activity still active) - if (biometricUnlockDatabaseHelper == null || !biometricUnlockDatabaseHelper!!.isBiometricInitialized) { - biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context) - // callback for fingerprint findings - biometricUnlockDatabaseHelper?.biometricUnlockCallback = this - biometricUnlockDatabaseHelper?.authenticationCallback = biometricAuthenticationCallback - } - + init { // Add a check listener to change fingerprint mode checkboxPasswordView?.setOnCheckedChangeListener { compoundButton, checked -> - checkBiometricAvailability() - // Add old listener to enable the button, only be call here because of onCheckedChange bug onCheckedPasswordChangeListener?.onCheckedChanged(compoundButton, checked) } - - checkBiometricAvailability() } - @Synchronized - private fun checkBiometricAvailability() { + /** + * Check biometric availability and change the current mode depending of device's state + */ + fun checkBiometricAvailability() { - // fingerprint not supported (by API level or hardware) so keep option hidden + // biometric not supported (by API level or hardware) so keep option hidden // or manually disable val biometricCanAuthenticate = BiometricManager.from(context).canAuthenticate() if (!PreferencesUtil.isBiometricUnlockEnable(context) || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) { - toggleMode(Mode.UNAVAILABLE) - } else { - - // fingerprint is available but not configured, show icon but in disabled state with some information + // biometric is available but not configured, show icon but in disabled state with some information if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) { - - toggleMode(Mode.NOT_CONFIGURED) - + toggleMode(Mode.BIOMETRIC_NOT_CONFIGURED) } else { - if (checkboxPasswordView?.isChecked == true) { - // listen for encryption - toggleMode(Mode.STORE) + // Check if fingerprint well init (be called the first time the fingerprint is configured + // and the activity still active) + if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) { + biometricUnlockDatabaseHelper = BiometricUnlockDatabaseHelper(context) + // callback for fingerprint findings + biometricUnlockDatabaseHelper?.biometricUnlockCallback = this + biometricUnlockDatabaseHelper?.authenticationCallback = biometricAuthenticationCallback + } + // Recheck to change the mode + if (biometricUnlockDatabaseHelper?.isKeyManagerInitialized != true) { + toggleMode(Mode.KEY_MANAGER_UNAVAILABLE) } else { - cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher -> - - // fingerprint available but no stored password found yet for this DB so show info don't listen - toggleMode( if (containsCipher) { - // listen for decryption - Mode.OPEN - } else { - // wait for typing - Mode.WAIT_CREDENTIAL - }) + if (checkboxPasswordView?.isChecked == true) { + // listen for encryption + toggleMode(Mode.STORE_CREDENTIAL) + } else { + cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { containsCipher -> + // biometric available but no stored password found yet for this DB so show info don't listen + toggleMode( if (containsCipher) { + // listen for decryption + Mode.EXTRACT_CREDENTIAL + } else { + // wait for typing + Mode.WAIT_CREDENTIAL + }) + } } } } @@ -129,17 +124,15 @@ class AdvancedUnlockedManager(var context: FragmentActivity, override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { context.runOnUiThread { when (biometricMode) { - Mode.UNAVAILABLE -> { - } - Mode.NOT_CONFIGURED -> { - } - Mode.WAIT_CREDENTIAL -> { - } - Mode.STORE -> { + Mode.UNAVAILABLE -> {} + Mode.BIOMETRIC_NOT_CONFIGURED -> {} + Mode.KEY_MANAGER_UNAVAILABLE -> {} + Mode.WAIT_CREDENTIAL -> {} + Mode.STORE_CREDENTIAL -> { // newly store the entered password in encrypted way biometricUnlockDatabaseHelper?.encryptData(passwordView?.text.toString()) } - Mode.OPEN -> { + Mode.EXTRACT_CREDENTIAL -> { // retrieve the encrypted value from preferences cipherDatabaseAction.getCipherDatabase(databaseFileUri) { it?.encryptedValue?.let { value -> @@ -155,7 +148,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity, private fun initNotAvailable() { showFingerPrintViews(false) - advancedUnlockInfoView?.setIconViewClickListener(null) + advancedUnlockInfoView?.setIconViewClickListener(false, null) } private fun initNotConfigured() { @@ -163,7 +156,17 @@ class AdvancedUnlockedManager(var context: FragmentActivity, setAdvancedUnlockedTitleView(R.string.configure_biometric) setAdvancedUnlockedMessageView("") - advancedUnlockInfoView?.setIconViewClickListener { + advancedUnlockInfoView?.setIconViewClickListener(false) { + context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS)) + } + } + + private fun initKeyManagerNotAvailable() { + showFingerPrintViews(true) + setAdvancedUnlockedTitleView(R.string.keystore_not_accessible) + setAdvancedUnlockedMessageView("") + + advancedUnlockInfoView?.setIconViewClickListener(false) { context.startActivity(Intent(Settings.ACTION_SECURITY_SETTINGS)) } } @@ -173,7 +176,11 @@ class AdvancedUnlockedManager(var context: FragmentActivity, setAdvancedUnlockedTitleView(R.string.no_credentials_stored) setAdvancedUnlockedMessageView("") - advancedUnlockInfoView?.setIconViewClickListener(null) + advancedUnlockInfoView?.setIconViewClickListener(false) { + biometricAuthenticationCallback.onAuthenticationError( + BiometricConstants.ERROR_UNABLE_TO_PROCESS + , context.getString(R.string.credential_before_click_biometric_button)) + } } private fun openBiometricPrompt(biometricPrompt: BiometricPrompt?, @@ -235,10 +242,11 @@ class AdvancedUnlockedManager(var context: FragmentActivity, fun initBiometricMode() { when (biometricMode) { Mode.UNAVAILABLE -> initNotAvailable() - Mode.NOT_CONFIGURED -> initNotConfigured() + Mode.BIOMETRIC_NOT_CONFIGURED -> initNotConfigured() + Mode.KEY_MANAGER_UNAVAILABLE -> initKeyManagerNotAvailable() Mode.WAIT_CREDENTIAL -> initWaitData() - Mode.STORE -> initEncryptData() - Mode.OPEN -> initDecryptData() + Mode.STORE_CREDENTIAL -> initEncryptData() + Mode.EXTRACT_CREDENTIAL -> initDecryptData() } // Show fingerprint key deletion context.invalidateOptionsMenu() @@ -255,7 +263,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity, if (!addBiometricMenuInProgress) { addBiometricMenuInProgress = true cipherDatabaseAction.containsCipherDatabase(databaseFileUri) { - if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.NOT_CONFIGURED) + if ((biometricMode != Mode.UNAVAILABLE && biometricMode != Mode.BIOMETRIC_NOT_CONFIGURED) && it) { menuInflater.inflate(R.menu.advanced_unlock, menu) addBiometricMenuInProgress = false @@ -267,7 +275,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity, fun deleteEntryKey() { biometricUnlockDatabaseHelper?.deleteEntryKey() cipherDatabaseAction.deleteByDatabaseUri(databaseFileUri) - biometricMode = Mode.NOT_CONFIGURED + biometricMode = Mode.BIOMETRIC_NOT_CONFIGURED checkBiometricAvailability() } @@ -313,7 +321,7 @@ class AdvancedUnlockedManager(var context: FragmentActivity, } enum class Mode { - UNAVAILABLE, NOT_CONFIGURED, WAIT_CREDENTIAL, STORE, OPEN + UNAVAILABLE, BIOMETRIC_NOT_CONFIGURED, KEY_MANAGER_UNAVAILABLE, WAIT_CREDENTIAL, STORE_CREDENTIAL, EXTRACT_CREDENTIAL } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt index ad869885b..b31890a19 100644 --- a/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/BiometricUnlockDatabaseHelper.kt @@ -52,13 +52,14 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { private var keyguardManager: KeyguardManager? = null private var cryptoObject: BiometricPrompt.CryptoObject? = null - private var isBiometricInit = false + private var isKeyManagerInit = false var authenticationCallback: BiometricPrompt.AuthenticationCallback? = null var biometricUnlockCallback: BiometricUnlockCallback? = null private val promptInfoStoreCredential = BiometricPrompt.PromptInfo.Builder().apply { setTitle(context.getString(R.string.biometric_prompt_store_credential_title)) setDescription(context.getString(R.string.biometric_prompt_store_credential_message)) + setConfirmationRequired(true) // TODO device credential /* if (keyguardManager?.isDeviceSecure == true) @@ -70,7 +71,8 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { private val promptInfoExtractCredential = BiometricPrompt.PromptInfo.Builder().apply { setTitle(context.getString(R.string.biometric_prompt_extract_credential_title)) - setDescription(context.getString(R.string.biometric_prompt_extract_credential_message)) + //setDescription(context.getString(R.string.biometric_prompt_extract_credential_message)) + setConfirmationRequired(false) // TODO device credential /* if (keyguardManager?.isDeviceSecure == true) @@ -80,18 +82,18 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { setNegativeButtonText(context.getString(android.R.string.cancel)) }.build() - val isBiometricInitialized: Boolean + val isKeyManagerInitialized: Boolean get() { - if (!isBiometricInit) { + if (!isKeyManagerInit) { biometricUnlockCallback?.onBiometricException(Exception("Biometric not initialized")) } - return isBiometricInit + return isKeyManagerInit } init { if (BiometricManager.from(context).canAuthenticate() != BiometricManager.BIOMETRIC_SUCCESS) { // really not much to do when no fingerprint support found - isBiometricInit = false + isKeyManagerInit = false } else { this.keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager @@ -103,17 +105,19 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { + BIOMETRIC_BLOCKS_MODES + "/" + BIOMETRIC_ENCRYPTION_PADDING) this.cryptoObject = BiometricPrompt.CryptoObject(cipher!!) - isBiometricInit = true + isKeyManagerInit = (keyStore != null + && keyGenerator != null + && cipher != null) } catch (e: Exception) { Log.e(TAG, "Unable to initialize the keystore", e) - isBiometricInit = false + isKeyManagerInit = false biometricUnlockCallback?.onBiometricException(e) } } } private fun getSecretKey(): SecretKey? { - if (!isBiometricInitialized) { + if (!isKeyManagerInitialized) { return null } try { @@ -155,7 +159,7 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { : (biometricPrompt: BiometricPrompt?, cryptoObject: BiometricPrompt.CryptoObject?, promptInfo: BiometricPrompt.PromptInfo)->Unit) { - if (!isBiometricInitialized) { + if (!isKeyManagerInitialized) { return } try { @@ -176,11 +180,10 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { Log.e(TAG, "Unable to initialize encrypt data", e) biometricUnlockCallback?.onBiometricException(e) } - } fun encryptData(value: String) { - if (!isBiometricInitialized) { + if (!isKeyManagerInitialized) { return } try { @@ -197,14 +200,13 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { Log.e(TAG, "Unable to encrypt data", e) biometricUnlockCallback?.onBiometricException(e) } - } fun initDecryptData(ivSpecValue: String, actionIfCypherInit : (biometricPrompt: BiometricPrompt?, cryptoObject: BiometricPrompt.CryptoObject?, promptInfo: BiometricPrompt.PromptInfo)->Unit) { - if (!isBiometricInitialized) { + if (!isKeyManagerInitialized) { return } try { @@ -229,11 +231,10 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { Log.e(TAG, "Unable to initialize decrypt data", e) biometricUnlockCallback?.onBiometricException(e) } - } fun decryptData(encryptedValue: String) { - if (!isBiometricInitialized) { + if (!isKeyManagerInitialized) { return } try { @@ -249,7 +250,6 @@ class BiometricUnlockDatabaseHelper(private val context: FragmentActivity) { Log.e(TAG, "Unable to decrypt data", e) biometricUnlockCallback?.onBiometricException(e) } - } fun deleteEntryKey() { diff --git a/app/src/main/java/com/kunzisoft/keepass/biometric/FingerPrintAnimatedVector.kt b/app/src/main/java/com/kunzisoft/keepass/biometric/FingerPrintAnimatedVector.kt index 835d57998..678128d26 100644 --- a/app/src/main/java/com/kunzisoft/keepass/biometric/FingerPrintAnimatedVector.kt +++ b/app/src/main/java/com/kunzisoft/keepass/biometric/FingerPrintAnimatedVector.kt @@ -20,43 +20,44 @@ package com.kunzisoft.keepass.biometric import android.content.Context -import android.graphics.drawable.Animatable2 -import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.Drawable import android.os.Build import androidx.annotation.RequiresApi import android.widget.ImageView +import androidx.vectordrawable.graphics.drawable.Animatable2Compat +import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat import com.kunzisoft.keepass.R @RequiresApi(api = Build.VERSION_CODES.M) class FingerPrintAnimatedVector(context: Context, imageView: ImageView) { - private val scanFingerprint: AnimatedVectorDrawable = - context.getDrawable(R.drawable.scan_fingerprint) as AnimatedVectorDrawable + private val scanFingerprint: AnimatedVectorDrawableCompat? = + AnimatedVectorDrawableCompat.create(context, R.drawable.scan_fingerprint) init { imageView.setImageDrawable(scanFingerprint) } - private var animationCallback = object : Animatable2.AnimationCallback() { + private var animationCallback = object : Animatable2Compat.AnimationCallback() { override fun onAnimationEnd(drawable: Drawable) { - if (!scanFingerprint.isRunning) - scanFingerprint.start() + imageView.post { + scanFingerprint?.start() + } } } fun startScan() { - scanFingerprint.registerAnimationCallback(animationCallback) + scanFingerprint?.registerAnimationCallback(animationCallback) - if (!scanFingerprint.isRunning) - scanFingerprint.start() + if (scanFingerprint?.isRunning != true) + scanFingerprint?.start() } fun stopScan() { - scanFingerprint.unregisterAnimationCallback(animationCallback) + scanFingerprint?.unregisterAnimationCallback(animationCallback) - if (scanFingerprint.isRunning) + if (scanFingerprint?.isRunning == true) scanFingerprint.stop() } } diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/engine/AesEngine.kt b/app/src/main/java/com/kunzisoft/keepass/crypto/engine/AesEngine.kt index d2001a90a..01aabbc1f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/crypto/engine/AesEngine.kt +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/engine/AesEngine.kt @@ -21,8 +21,8 @@ package com.kunzisoft.keepass.crypto.engine import com.kunzisoft.keepass.crypto.CipherFactory -import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import java.security.InvalidAlgorithmParameterException import java.security.InvalidKeyException import java.security.NoSuchAlgorithmException @@ -41,13 +41,13 @@ class AesEngine : CipherEngine() { return cipher } - override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm { - return PwEncryptionAlgorithm.AESRijndael + override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm { + return EncryptionAlgorithm.AESRijndael } companion object { - val CIPHER_UUID: UUID = Types.bytestoUUID( + val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid( 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())) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/engine/ChaCha20Engine.kt b/app/src/main/java/com/kunzisoft/keepass/crypto/engine/ChaCha20Engine.kt index 46150ed66..2828c5e22 100644 --- a/app/src/main/java/com/kunzisoft/keepass/crypto/engine/ChaCha20Engine.kt +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/engine/ChaCha20Engine.kt @@ -19,8 +19,8 @@ */ package com.kunzisoft.keepass.crypto.engine -import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import org.spongycastle.jce.provider.BouncyCastleProvider import java.security.InvalidAlgorithmParameterException import java.security.InvalidKeyException @@ -44,13 +44,13 @@ class ChaCha20Engine : CipherEngine() { return cipher } - override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm { - return PwEncryptionAlgorithm.ChaCha20 + override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm { + return EncryptionAlgorithm.ChaCha20 } companion object { - val CIPHER_UUID: UUID = Types.bytestoUUID( + val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid( 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())) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/engine/CipherEngine.kt b/app/src/main/java/com/kunzisoft/keepass/crypto/engine/CipherEngine.kt index 260e1787d..64977d748 100644 --- a/app/src/main/java/com/kunzisoft/keepass/crypto/engine/CipherEngine.kt +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/engine/CipherEngine.kt @@ -19,7 +19,7 @@ */ package com.kunzisoft.keepass.crypto.engine -import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import java.security.InvalidAlgorithmParameterException import java.security.InvalidKeyException @@ -46,6 +46,6 @@ abstract class CipherEngine { return getCipher(opmode, key, IV, false) } - abstract fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm + abstract fun getPwEncryptionAlgorithm(): EncryptionAlgorithm } diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/engine/TwofishEngine.kt b/app/src/main/java/com/kunzisoft/keepass/crypto/engine/TwofishEngine.kt index 6a8b773a7..5718ac33c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/crypto/engine/TwofishEngine.kt +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/engine/TwofishEngine.kt @@ -20,8 +20,8 @@ package com.kunzisoft.keepass.crypto.engine import com.kunzisoft.keepass.crypto.CipherFactory -import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import java.security.InvalidAlgorithmParameterException import java.security.InvalidKeyException @@ -47,13 +47,13 @@ class TwofishEngine : CipherEngine() { return cipher } - override fun getPwEncryptionAlgorithm(): PwEncryptionAlgorithm { - return PwEncryptionAlgorithm.Twofish + override fun getPwEncryptionAlgorithm(): EncryptionAlgorithm { + return EncryptionAlgorithm.Twofish } companion object { - val CIPHER_UUID: UUID = Types.bytestoUUID( + val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid( 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())) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/AesKdf.kt b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/AesKdf.kt index 7d7060272..4612ee9dd 100644 --- a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/AesKdf.kt +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/AesKdf.kt @@ -23,7 +23,7 @@ import android.content.res.Resources import com.kunzisoft.keepass.R import com.kunzisoft.keepass.crypto.CryptoUtil import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import java.io.IOException import java.security.SecureRandom import java.util.* @@ -32,7 +32,7 @@ class AesKdf internal constructor() : KdfEngine() { override val defaultParameters: KdfParameters get() { - return KdfParameters(uuid).apply { + return KdfParameters(uuid!!).apply { setParamUUID() setUInt32(PARAM_ROUNDS, DEFAULT_ROUNDS.toLong()) } @@ -88,7 +88,7 @@ class AesKdf internal constructor() : KdfEngine() { private const val DEFAULT_ROUNDS = 6000 - val CIPHER_UUID: UUID = Types.bytestoUUID( + val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid( byteArrayOf(0xC9.toByte(), 0xD9.toByte(), 0xF3.toByte(), diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.kt b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.kt index a2d485ccb..8e60b330d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.kt +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/Argon2Kdf.kt @@ -21,7 +21,7 @@ package com.kunzisoft.keepass.crypto.keyDerivation import android.content.res.Resources import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import java.io.IOException import java.security.SecureRandom import java.util.* @@ -30,7 +30,7 @@ class Argon2Kdf internal constructor() : KdfEngine() { override val defaultParameters: KdfParameters get() { - val p = KdfParameters(uuid) + val p = KdfParameters(uuid!!) p.setParamUUID() p.setUInt32(PARAM_PARALLELISM, DEFAULT_PARALLELISM) @@ -126,7 +126,7 @@ class Argon2Kdf internal constructor() : KdfEngine() { companion object { - val CIPHER_UUID: UUID = Types.bytestoUUID( + val CIPHER_UUID: UUID = DatabaseInputOutputUtils.bytesToUuid( byteArrayOf(0xEF.toByte(), 0x63.toByte(), 0x6D.toByte(), diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfParameters.java b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfParameters.java deleted file mode 100644 index ebec90eef..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfParameters.java +++ /dev/null @@ -1,75 +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 . - * - */ -package com.kunzisoft.keepass.crypto.keyDerivation; - -import com.kunzisoft.keepass.utils.VariantDictionary; -import com.kunzisoft.keepass.stream.LEDataInputStream; -import com.kunzisoft.keepass.stream.LEDataOutputStream; -import com.kunzisoft.keepass.utils.Types; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.UUID; - -public class KdfParameters extends VariantDictionary { - - private UUID kdfUUID; - - private static final String ParamUUID = "$UUID"; - - KdfParameters(UUID uuid) { - kdfUUID = uuid; - } - - public UUID getUUID() { - return kdfUUID; - } - - protected void setParamUUID() { - setByteArray(ParamUUID, Types.UUIDtoBytes(kdfUUID)); - } - - public static KdfParameters deserialize(byte[] data) throws IOException { - ByteArrayInputStream bis = new ByteArrayInputStream(data); - LEDataInputStream lis = new LEDataInputStream(bis); - - VariantDictionary d = VariantDictionary.deserialize(lis); - if (d == null) { - return null; - } - - UUID uuid = Types.bytestoUUID(d.getByteArray(ParamUUID)); - - KdfParameters kdfP = new KdfParameters(uuid); - kdfP.copyTo(d); - return kdfP; - } - - public static byte[] serialize(KdfParameters kdf) throws IOException { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - LEDataOutputStream los = new LEDataOutputStream(bos); - - KdfParameters.serialize(kdf, los); - - return bos.toByteArray(); - } - -} diff --git a/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfParameters.kt b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfParameters.kt new file mode 100644 index 000000000..d8526545f --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/crypto/keyDerivation/KdfParameters.kt @@ -0,0 +1,67 @@ +/* + * 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 . + * + */ +package com.kunzisoft.keepass.crypto.keyDerivation + +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.ByteArrayOutputStream +import java.io.IOException +import java.util.UUID + +class KdfParameters internal constructor(val uuid: UUID) : VariantDictionary() { + + fun setParamUUID() { + setByteArray(PARAM_UUID, DatabaseInputOutputUtils.uuidToBytes(uuid)) + } + + companion object { + + private const val PARAM_UUID = "\$UUID" + + @Throws(IOException::class) + fun deserialize(data: ByteArray): KdfParameters? { + val bis = ByteArrayInputStream(data) + val lis = LEDataInputStream(bis) + + val d = deserialize(lis) ?: return null + + val uuid = DatabaseInputOutputUtils.bytesToUuid(d.getByteArray(PARAM_UUID)) + + val kdfP = KdfParameters(uuid) + kdfP.copyTo(d) + return kdfP + } + + @Throws(IOException::class) + fun serialize(kdf: KdfParameters): ByteArray { + val bos = ByteArrayOutputStream() + val los = LEDataOutputStream(bos) + + serialize(kdf, los) + + return bos.toByteArray() + } + } + +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt index e2499b24a..7cbb194e5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/AssignPasswordInDatabaseRunnable.kt @@ -32,9 +32,8 @@ open class AssignPasswordInDatabaseRunnable ( withMasterPassword: Boolean, masterPassword: String?, withKeyFile: Boolean, - keyFile: Uri?, - save: Boolean) - : SaveDatabaseRunnable(context, database, save) { + keyFile: Uri?) + : SaveDatabaseRunnable(context, database, true) { private var mMasterPassword: String? = null protected var mKeyFile: Uri? = null @@ -59,7 +58,7 @@ open class AssignPasswordInDatabaseRunnable ( database.retrieveMasterKey(mMasterPassword, uriInputStream) } catch (e: Exception) { erase(mBackupKey) - setError(e.message) + setError(e) } super.onStartRun() diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt index 188ba639c..2c80fdcdf 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/CreateDatabaseRunnable.kt @@ -28,24 +28,25 @@ import com.kunzisoft.keepass.database.element.Database class CreateDatabaseRunnable(context: Context, private val mDatabase: Database, databaseUri: Uri, + private val databaseName: String, + private val rootName: String, withMasterPassword: Boolean, masterPassword: String?, withKeyFile: Boolean, - keyFile: Uri?, - save: Boolean) - : AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, withMasterPassword, masterPassword, withKeyFile, keyFile, save) { + keyFile: Uri?) + : AssignPasswordInDatabaseRunnable(context, mDatabase, databaseUri, withMasterPassword, masterPassword, withKeyFile, keyFile) { override fun onStartRun() { try { // Create new database record mDatabase.apply { - createData(mDatabaseUri) + createData(mDatabaseUri, databaseName, rootName) // Set Database state loaded = true } } catch (e: Exception) { mDatabase.closeAndClear() - setError(e.message) + setError(e) } super.onStartRun() diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt index 8404fd650..993c50437 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/LoadDatabaseRunnable.kt @@ -25,7 +25,7 @@ import com.kunzisoft.keepass.app.database.CipherDatabaseAction import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException +import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.notifications.DatabaseOpenNotificationService import com.kunzisoft.keepass.settings.PreferencesUtil @@ -62,7 +62,7 @@ class LoadDatabaseRunnable(private val context: Context, mFixDuplicateUUID, progressTaskUpdater) } - catch (e: LoadDatabaseDuplicateUuidException) { + catch (e: DuplicateUuidDatabaseException) { mDuplicateUuidAction?.invoke(result) setError(e) } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDialogThread.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDialogThread.kt index 8e4beb7e5..ae9a48495 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDialogThread.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/ProgressDialogThread.kt @@ -11,6 +11,11 @@ import androidx.fragment.app.FragmentActivity import com.kunzisoft.keepass.app.database.CipherDatabaseEntity import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm +import com.kunzisoft.keepass.database.element.node.Node +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.Type +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_ASSIGN_PASSWORD_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_COPY_NODES_TASK @@ -20,18 +25,19 @@ import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Compa 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_MOVE_NODES_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_COLOR_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_COMPRESSION_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DESCRIPTION_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ENCRYPTION_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ITERATIONS_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_NAME_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_PARALLELISM_TASK +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_COMPRESSION_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ITERATIONS_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_NAME_TASK +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_PARALLELISM_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_GROUP_TASK import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.getBundleFromListNodes @@ -44,10 +50,10 @@ import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION import java.util.* import kotlin.collections.ArrayList +class ProgressDialogThread(private val activity: FragmentActivity) { -class ProgressDialogThread(private val activity: FragmentActivity, - var onActionFinish: (actionTask: String, - result: ActionRunnable.Result) -> Unit) { + var onActionFinish: ((actionTask: String, + result: ActionRunnable.Result) -> Unit)? = null private var intentDatabaseTask = Intent(activity, DatabaseTaskNotificationService::class.java) @@ -68,7 +74,7 @@ class ProgressDialogThread(private val activity: FragmentActivity, } override fun onStopAction(actionTask: String, result: ActionRunnable.Result) { - onActionFinish.invoke(actionTask, result) + onActionFinish?.invoke(actionTask, result) // Remove the progress task ProgressTaskDialogFragment.stop(activity) TimeoutHelper.releaseTemporarilyDisableTimeoutAndLockIfTimeout(activity) @@ -250,8 +256,8 @@ class ProgressDialogThread(private val activity: FragmentActivity, ---- */ - fun startDatabaseCreateGroup(newGroup: GroupVersioned, - parent: GroupVersioned, + fun startDatabaseCreateGroup(newGroup: Group, + parent: Group, save: Boolean) { start(Bundle().apply { putParcelable(DatabaseTaskNotificationService.GROUP_KEY, newGroup) @@ -261,8 +267,8 @@ class ProgressDialogThread(private val activity: FragmentActivity, , ACTION_DATABASE_CREATE_GROUP_TASK) } - fun startDatabaseUpdateGroup(oldGroup: GroupVersioned, - groupToUpdate: GroupVersioned, + fun startDatabaseUpdateGroup(oldGroup: Group, + groupToUpdate: Group, save: Boolean) { start(Bundle().apply { putParcelable(DatabaseTaskNotificationService.GROUP_ID_KEY, oldGroup.nodeId) @@ -272,8 +278,8 @@ class ProgressDialogThread(private val activity: FragmentActivity, , ACTION_DATABASE_UPDATE_GROUP_TASK) } - fun startDatabaseCreateEntry(newEntry: EntryVersioned, - parent: GroupVersioned, + fun startDatabaseCreateEntry(newEntry: Entry, + parent: Group, save: Boolean) { start(Bundle().apply { putParcelable(DatabaseTaskNotificationService.ENTRY_KEY, newEntry) @@ -283,8 +289,8 @@ class ProgressDialogThread(private val activity: FragmentActivity, , ACTION_DATABASE_CREATE_ENTRY_TASK) } - fun startDatabaseUpdateEntry(oldEntry: EntryVersioned, - entryToUpdate: EntryVersioned, + fun startDatabaseUpdateEntry(oldEntry: Entry, + entryToUpdate: Entry, save: Boolean) { start(Bundle().apply { putParcelable(DatabaseTaskNotificationService.ENTRY_ID_KEY, oldEntry.nodeId) @@ -295,20 +301,20 @@ class ProgressDialogThread(private val activity: FragmentActivity, } private fun startDatabaseActionListNodes(actionTask: String, - nodesPaste: List, - newParent: GroupVersioned?, + nodesPaste: List, + newParent: Group?, save: Boolean) { - val groupsIdToCopy = ArrayList>() - val entriesIdToCopy = ArrayList>() + val groupsIdToCopy = ArrayList>() + val entriesIdToCopy = ArrayList>() nodesPaste.forEach { nodeVersioned -> when (nodeVersioned.type) { Type.GROUP -> { - (nodeVersioned as GroupVersioned).nodeId?.let { groupId -> + (nodeVersioned as Group).nodeId?.let { groupId -> groupsIdToCopy.add(groupId) } } Type.ENTRY -> { - entriesIdToCopy.add((nodeVersioned as EntryVersioned).nodeId) + entriesIdToCopy.add((nodeVersioned as Entry).nodeId) } } } @@ -325,19 +331,19 @@ class ProgressDialogThread(private val activity: FragmentActivity, , actionTask) } - fun startDatabaseCopyNodes(nodesToCopy: List, - newParent: GroupVersioned, + fun startDatabaseCopyNodes(nodesToCopy: List, + newParent: Group, save: Boolean) { startDatabaseActionListNodes(ACTION_DATABASE_COPY_NODES_TASK, nodesToCopy, newParent, save) } - fun startDatabaseMoveNodes(nodesToMove: List, - newParent: GroupVersioned, + fun startDatabaseMoveNodes(nodesToMove: List, + newParent: Group, save: Boolean) { startDatabaseActionListNodes(ACTION_DATABASE_MOVE_NODES_TASK, nodesToMove, newParent, save) } - fun startDatabaseDeleteNodes(nodesToDelete: List, + fun startDatabaseDeleteNodes(nodesToDelete: List, save: Boolean) { startDatabaseActionListNodes(ACTION_DATABASE_DELETE_NODES_TASK, nodesToDelete, null, save) } @@ -349,66 +355,80 @@ class ProgressDialogThread(private val activity: FragmentActivity, */ fun startDatabaseSaveName(oldName: String, - newName: String) { + newName: String, + save: Boolean) { start(Bundle().apply { putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldName) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newName) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_NAME_TASK) + , ACTION_DATABASE_UPDATE_NAME_TASK) } fun startDatabaseSaveDescription(oldDescription: String, - newDescription: String) { + newDescription: String, + save: Boolean) { start(Bundle().apply { putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDescription) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDescription) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_DESCRIPTION_TASK) + , ACTION_DATABASE_UPDATE_DESCRIPTION_TASK) } fun startDatabaseSaveDefaultUsername(oldDefaultUsername: String, - newDefaultUsername: String) { + newDefaultUsername: String, + save: Boolean) { start(Bundle().apply { putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldDefaultUsername) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newDefaultUsername) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK) + , ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK) } fun startDatabaseSaveColor(oldColor: String, - newColor: String) { + newColor: String, + save: Boolean) { start(Bundle().apply { putString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldColor) putString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newColor) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_COLOR_TASK) + , ACTION_DATABASE_UPDATE_COLOR_TASK) } - fun startDatabaseSaveCompression(oldCompression: PwCompressionAlgorithm, - newCompression: PwCompressionAlgorithm) { + fun startDatabaseSaveCompression(oldCompression: CompressionAlgorithm, + newCompression: CompressionAlgorithm, + save: Boolean) { start(Bundle().apply { putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldCompression) putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newCompression) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_COMPRESSION_TASK) + , ACTION_DATABASE_UPDATE_COMPRESSION_TASK) } fun startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems: Int, - newMaxHistoryItems: Int) { + newMaxHistoryItems: Int, + save: Boolean) { start(Bundle().apply { putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistoryItems) putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistoryItems) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK) + , ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK) } fun startDatabaseSaveMaxHistorySize(oldMaxHistorySize: Long, - newMaxHistorySize: Long) { + newMaxHistorySize: Long, + save: Boolean) { start(Bundle().apply { putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMaxHistorySize) putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMaxHistorySize) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK) + , ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK) } /* @@ -417,48 +437,68 @@ class ProgressDialogThread(private val activity: FragmentActivity, ------------------- */ - fun startDatabaseSaveEncryption(oldEncryption: PwEncryptionAlgorithm, - newEncryption: PwEncryptionAlgorithm) { + fun startDatabaseSaveEncryption(oldEncryption: EncryptionAlgorithm, + newEncryption: EncryptionAlgorithm, + save: Boolean) { start(Bundle().apply { putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldEncryption) putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newEncryption) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_ENCRYPTION_TASK) + , ACTION_DATABASE_UPDATE_ENCRYPTION_TASK) } fun startDatabaseSaveKeyDerivation(oldKeyDerivation: KdfEngine, - newKeyDerivation: KdfEngine) { + newKeyDerivation: KdfEngine, + save: Boolean) { start(Bundle().apply { putSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldKeyDerivation) putSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newKeyDerivation) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK) + , ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK) } fun startDatabaseSaveIterations(oldIterations: Long, - newIterations: Long) { + newIterations: Long, + save: Boolean) { start(Bundle().apply { putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldIterations) putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newIterations) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_ITERATIONS_TASK) + , ACTION_DATABASE_UPDATE_ITERATIONS_TASK) } fun startDatabaseSaveMemoryUsage(oldMemoryUsage: Long, - newMemoryUsage: Long) { + newMemoryUsage: Long, + save: Boolean) { start(Bundle().apply { putLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldMemoryUsage) putLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newMemoryUsage) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK) + , ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK) } fun startDatabaseSaveParallelism(oldParallelism: Int, - newParallelism: Int) { + newParallelism: Int, + save: Boolean) { start(Bundle().apply { putInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY, oldParallelism) putInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY, newParallelism) + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) + } + , ACTION_DATABASE_UPDATE_PARALLELISM_TASK) + } + + /** + * Save Database without parameter + */ + fun startDatabaseSave(save: Boolean) { + start(Bundle().apply { + putBoolean(DatabaseTaskNotificationService.SAVE_DATABASE_KEY, save) } - , ACTION_DATABASE_SAVE_PARALLELISM_TASK) + , ACTION_DATABASE_SAVE) } } \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt index 092744fb8..4c8e2f689 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/SaveDatabaseRunnable.kt @@ -21,9 +21,8 @@ package com.kunzisoft.keepass.database.action import android.content.Context import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.exception.DatabaseOutputException +import com.kunzisoft.keepass.database.exception.DatabaseException import com.kunzisoft.keepass.tasks.ActionRunnable -import java.io.IOException open class SaveDatabaseRunnable(protected var context: Context, protected var database: Database, @@ -38,10 +37,8 @@ open class SaveDatabaseRunnable(protected var context: Context, if (saveDatabase && result.isSuccess) { try { database.saveData(context.contentResolver) - } catch (e: IOException) { - setError(e.message) - } catch (e: DatabaseOutputException) { - setError(e.message) + } catch (e: DatabaseException) { + setError(e) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/UpdateCompressionBinariesDatabaseRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/UpdateCompressionBinariesDatabaseRunnable.kt new file mode 100644 index 000000000..7a22f0167 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/UpdateCompressionBinariesDatabaseRunnable.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2019 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePass DX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.action + +import android.content.Context +import com.kunzisoft.keepass.database.element.Database +import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm + +class UpdateCompressionBinariesDatabaseRunnable ( + context: Context, + database: Database, + private val oldCompressionAlgorithm: CompressionAlgorithm, + private val newCompressionAlgorithm: CompressionAlgorithm, + saveDatabase: Boolean) + : SaveDatabaseRunnable(context, database, saveDatabase) { + + override fun onStartRun() { + // Set new compression + if (database.allowDataCompression) { + try { + database.apply { + updateDataBinaryCompression(oldCompressionAlgorithm, newCompressionAlgorithm) + compressionAlgorithm = newCompressionAlgorithm + } + } catch (e: Exception) { + setError(e) + } + } + + super.onStartRun() + } + + override fun onFinishRun() { + super.onFinishRun() + + if (database.allowDataCompression) { + if (!result.isSuccess) { + try { + database.apply { + compressionAlgorithm = oldCompressionAlgorithm + updateDataBinaryCompression(newCompressionAlgorithm, oldCompressionAlgorithm) + } + } catch (e: Exception) { + setError(e) + } + } + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/node/AddEntryRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/node/AddEntryRunnable.kt index 991b91be2..7c40fc626 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/node/AddEntryRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/node/AddEntryRunnable.kt @@ -21,15 +21,15 @@ package com.kunzisoft.keepass.database.action.node import android.content.Context import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.element.EntryVersioned -import com.kunzisoft.keepass.database.element.GroupVersioned -import com.kunzisoft.keepass.database.element.NodeVersioned +import com.kunzisoft.keepass.database.element.Entry +import com.kunzisoft.keepass.database.element.Group +import com.kunzisoft.keepass.database.element.node.Node class AddEntryRunnable constructor( context: Context, database: Database, - private val mNewEntry: EntryVersioned, - private val mParent: GroupVersioned, + private val mNewEntry: Entry, + private val mParent: Group, save: Boolean, afterActionNodesFinish: AfterActionNodesFinish?) : ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) { @@ -47,8 +47,8 @@ class AddEntryRunnable constructor( } } - val oldNodesReturn = ArrayList() - val newNodesReturn = ArrayList() + val oldNodesReturn = ArrayList() + val newNodesReturn = ArrayList() newNodesReturn.add(mNewEntry) return ActionNodesValues(oldNodesReturn, newNodesReturn) } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/node/AddGroupRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/node/AddGroupRunnable.kt index ebbce2abf..75b9ec14c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/node/AddGroupRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/node/AddGroupRunnable.kt @@ -21,14 +21,14 @@ package com.kunzisoft.keepass.database.action.node import android.content.Context import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.element.GroupVersioned -import com.kunzisoft.keepass.database.element.NodeVersioned +import com.kunzisoft.keepass.database.element.Group +import com.kunzisoft.keepass.database.element.node.Node class AddGroupRunnable constructor( context: Context, database: Database, - private val mNewGroup: GroupVersioned, - private val mParent: GroupVersioned, + private val mNewGroup: Group, + private val mParent: Group, save: Boolean, afterActionNodesFinish: AfterActionNodesFinish?) : ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) { @@ -44,8 +44,8 @@ class AddGroupRunnable constructor( database.removeGroupFrom(mNewGroup, mParent) } - val oldNodesReturn = ArrayList() - val newNodesReturn = ArrayList() + val oldNodesReturn = ArrayList() + val newNodesReturn = ArrayList() newNodesReturn.add(mNewGroup) return ActionNodesValues(oldNodesReturn, newNodesReturn) } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/node/AfterActionNodesFinish.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/node/AfterActionNodesFinish.kt index c509a3223..ab186c692 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/node/AfterActionNodesFinish.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/node/AfterActionNodesFinish.kt @@ -19,7 +19,7 @@ */ package com.kunzisoft.keepass.database.action.node -import com.kunzisoft.keepass.database.element.NodeVersioned +import com.kunzisoft.keepass.database.element.node.Node import com.kunzisoft.keepass.tasks.ActionRunnable /** @@ -30,7 +30,7 @@ import com.kunzisoft.keepass.tasks.ActionRunnable * - Move : @param oldNodes empty, @param newNodes NodesToMove * - Update : @param oldNodes NodesToUpdate, @param newNodes NodesUpdated */ -class ActionNodesValues(val oldNodes: List, val newNodes: List) +class ActionNodesValues(val oldNodes: List, val newNodes: List) abstract class AfterActionNodesFinish { abstract fun onActionNodesFinish(result: ActionRunnable.Result, actionNodesValues: ActionNodesValues) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/node/CopyNodesRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/node/CopyNodesRunnable.kt index e76479992..1b20c79ea 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/node/CopyNodesRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/node/CopyNodesRunnable.kt @@ -22,19 +22,21 @@ package com.kunzisoft.keepass.database.action.node import android.content.Context import android.util.Log import com.kunzisoft.keepass.database.element.* -import com.kunzisoft.keepass.database.exception.CopyDatabaseEntryException -import com.kunzisoft.keepass.database.exception.CopyDatabaseGroupException +import com.kunzisoft.keepass.database.element.node.Node +import com.kunzisoft.keepass.database.element.node.Type +import com.kunzisoft.keepass.database.exception.CopyEntryDatabaseException +import com.kunzisoft.keepass.database.exception.CopyGroupDatabaseException class CopyNodesRunnable constructor( context: Context, database: Database, - private val mNodesToCopy: List, - private val mNewParent: GroupVersioned, + private val mNodesToCopy: List, + private val mNewParent: Group, save: Boolean, afterActionNodesFinish: AfterActionNodesFinish?) : ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) { - private var mEntriesCopied = ArrayList() + private var mEntriesCopied = ArrayList() override fun nodeAction() { @@ -42,7 +44,7 @@ class CopyNodesRunnable constructor( when (currentNode.type) { Type.GROUP -> { Log.e(TAG, "Copy not allowed for group")// Only finish thread - setError(CopyDatabaseGroupException()) + setError(CopyGroupDatabaseException()) break@foreachNode } Type.ENTRY -> { @@ -51,18 +53,18 @@ class CopyNodesRunnable constructor( // Update entry with new values mNewParent.touch(modified = false, touchParents = true) - val entryCopied = database.copyEntryTo(currentNode as EntryVersioned, mNewParent) + val entryCopied = database.copyEntryTo(currentNode as Entry, mNewParent) if (entryCopied != null) { entryCopied.touch(modified = true, touchParents = true) mEntriesCopied.add(entryCopied) } else { Log.e(TAG, "Unable to create a copy of the entry") - setError(CopyDatabaseEntryException()) + setError(CopyEntryDatabaseException()) break@foreachNode } } else { // Only finish thread - setError(CopyDatabaseEntryException()) + setError(CopyEntryDatabaseException()) break@foreachNode } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/node/DeleteNodesRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/node/DeleteNodesRunnable.kt index e24619d46..59701196e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/node/DeleteNodesRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/node/DeleteNodesRunnable.kt @@ -21,18 +21,20 @@ package com.kunzisoft.keepass.database.action.node import android.content.Context import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.element.node.Node +import com.kunzisoft.keepass.database.element.node.Type class DeleteNodesRunnable(context: Context, database: Database, - private val mNodesToDelete: List, + private val mNodesToDelete: List, save: Boolean, afterActionNodesFinish: AfterActionNodesFinish) : ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) { - private var mParent: GroupVersioned? = null + private var mParent: Group? = null private var mCanRecycle: Boolean = false - private var mNodesToDeleteBackup = ArrayList() + private var mNodesToDeleteBackup = ArrayList() override fun nodeAction() { @@ -43,7 +45,7 @@ class DeleteNodesRunnable(context: Context, when (currentNode.type) { Type.GROUP -> { // Create a copy to keep the old ref and remove it visually - mNodesToDeleteBackup.add(GroupVersioned(currentNode as GroupVersioned)) + mNodesToDeleteBackup.add(Group(currentNode as Group)) // Remove Node from parent mCanRecycle = database.canRecycle(currentNode) if (mCanRecycle) { @@ -54,7 +56,7 @@ class DeleteNodesRunnable(context: Context, } Type.ENTRY -> { // Create a copy to keep the old ref and remove it visually - mNodesToDeleteBackup.add(EntryVersioned(currentNode as EntryVersioned)) + mNodesToDeleteBackup.add(Entry(currentNode as Entry)) // Remove Node from parent mCanRecycle = database.canRecycle(currentNode) if (mCanRecycle) { @@ -74,10 +76,10 @@ class DeleteNodesRunnable(context: Context, mNodesToDeleteBackup.forEach { backupNode -> when (backupNode.type) { Type.GROUP -> { - database.undoRecycle(backupNode as GroupVersioned, it) + database.undoRecycle(backupNode as Group, it) } Type.ENTRY -> { - database.undoRecycle(backupNode as EntryVersioned, it) + database.undoRecycle(backupNode as Entry, it) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/node/MoveNodesRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/node/MoveNodesRunnable.kt index ae8335f6b..1188a67db 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/node/MoveNodesRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/node/MoveNodesRunnable.kt @@ -22,20 +22,21 @@ package com.kunzisoft.keepass.database.action.node import android.content.Context import android.util.Log import com.kunzisoft.keepass.database.element.* -import com.kunzisoft.keepass.database.exception.LoadDatabaseException -import com.kunzisoft.keepass.database.exception.MoveDatabaseEntryException -import com.kunzisoft.keepass.database.exception.MoveDatabaseGroupException +import com.kunzisoft.keepass.database.element.node.Node +import com.kunzisoft.keepass.database.element.node.Type +import com.kunzisoft.keepass.database.exception.EntryDatabaseException +import com.kunzisoft.keepass.database.exception.MoveGroupDatabaseException class MoveNodesRunnable constructor( context: Context, database: Database, - private val mNodesToMove: List, - private val mNewParent: GroupVersioned, + private val mNodesToMove: List, + private val mNewParent: Group, save: Boolean, afterActionNodesFinish: AfterActionNodesFinish?) : ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) { - private var mOldParent: GroupVersioned? = null + private var mOldParent: Group? = null override fun nodeAction() { @@ -45,7 +46,7 @@ class MoveNodesRunnable constructor( when (nodeToMove.type) { Type.GROUP -> { - val groupToMove = nodeToMove as GroupVersioned + val groupToMove = nodeToMove as Group // Move group in new parent if not in the current group if (groupToMove != mNewParent && !mNewParent.isContainedIn(groupToMove)) { @@ -53,12 +54,12 @@ class MoveNodesRunnable constructor( database.moveGroupTo(groupToMove, mNewParent) } else { // Only finish thread - setError(MoveDatabaseGroupException()) + setError(MoveGroupDatabaseException()) break@foreachNode } } Type.ENTRY -> { - val entryToMove = nodeToMove as EntryVersioned + val entryToMove = nodeToMove as Entry // Move only if the parent change if (mOldParent != mNewParent // and root can contains entry @@ -67,7 +68,7 @@ class MoveNodesRunnable constructor( database.moveEntryTo(entryToMove, mNewParent) } else { // Only finish thread - setError(MoveDatabaseEntryException()) + setError(EntryDatabaseException()) break@foreachNode } } @@ -83,8 +84,8 @@ class MoveNodesRunnable constructor( if (mOldParent != null && mOldParent != nodeToMove.parent) { when (nodeToMove.type) { - Type.GROUP -> database.moveGroupTo(nodeToMove as GroupVersioned, mOldParent!!) - Type.ENTRY -> database.moveEntryTo(nodeToMove as EntryVersioned, mOldParent!!) + Type.GROUP -> database.moveGroupTo(nodeToMove as Group, mOldParent!!) + Type.ENTRY -> database.moveEntryTo(nodeToMove as Entry, mOldParent!!) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/NodeHandler.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/node/NodeHandler.kt similarity index 94% rename from app/src/main/java/com/kunzisoft/keepass/database/NodeHandler.kt rename to app/src/main/java/com/kunzisoft/keepass/database/action/node/NodeHandler.kt index bf0a46f2c..e0c321872 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/NodeHandler.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/node/NodeHandler.kt @@ -17,7 +17,7 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database +package com.kunzisoft.keepass.database.action.node /** "Delegate" class for operating on each group when traversing all of diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/node/UpdateEntryRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/node/UpdateEntryRunnable.kt index 14e1689f5..ceaae4143 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/node/UpdateEntryRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/node/UpdateEntryRunnable.kt @@ -21,20 +21,20 @@ package com.kunzisoft.keepass.database.action.node import android.content.Context import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.element.EntryVersioned -import com.kunzisoft.keepass.database.element.NodeVersioned +import com.kunzisoft.keepass.database.element.Entry +import com.kunzisoft.keepass.database.element.node.Node class UpdateEntryRunnable constructor( context: Context, database: Database, - private val mOldEntry: EntryVersioned, - private val mNewEntry: EntryVersioned, + private val mOldEntry: Entry, + private val mNewEntry: Entry, save: Boolean, afterActionNodesFinish: AfterActionNodesFinish?) : ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) { // Keep backup of original values in case save fails - private var mBackupEntryHistory: EntryVersioned = EntryVersioned(mOldEntry) + private var mBackupEntryHistory: Entry = Entry(mOldEntry) override fun nodeAction() { // WARNING : Re attribute parent removed in entry edit activity to save memory @@ -45,8 +45,8 @@ class UpdateEntryRunnable constructor( mNewEntry.touch(modified = true, touchParents = true) // Create an entry history (an entry history don't have history) - mOldEntry.addEntryToHistory(EntryVersioned(mBackupEntryHistory, copyHistory = false)) - database.removeOldestHistory(mOldEntry) + mOldEntry.addEntryToHistory(Entry(mBackupEntryHistory, copyHistory = false)) + database.removeOldestEntryHistory(mOldEntry) // Only change data in index database.updateEntry(mOldEntry) @@ -59,9 +59,9 @@ class UpdateEntryRunnable constructor( database.updateEntry(mOldEntry) } - val oldNodesReturn = ArrayList() + val oldNodesReturn = ArrayList() oldNodesReturn.add(mBackupEntryHistory) - val newNodesReturn = ArrayList() + val newNodesReturn = ArrayList() newNodesReturn.add(mOldEntry) return ActionNodesValues(oldNodesReturn, newNodesReturn) } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/action/node/UpdateGroupRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/database/action/node/UpdateGroupRunnable.kt index 273bb7227..d66c99da6 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/action/node/UpdateGroupRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/action/node/UpdateGroupRunnable.kt @@ -21,20 +21,20 @@ package com.kunzisoft.keepass.database.action.node import android.content.Context import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.element.GroupVersioned -import com.kunzisoft.keepass.database.element.NodeVersioned +import com.kunzisoft.keepass.database.element.Group +import com.kunzisoft.keepass.database.element.node.Node class UpdateGroupRunnable constructor( context: Context, database: Database, - private val mOldGroup: GroupVersioned, - private val mNewGroup: GroupVersioned, + private val mOldGroup: Group, + private val mNewGroup: Group, save: Boolean, afterActionNodesFinish: AfterActionNodesFinish?) : ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) { // Keep backup of original values in case save fails - private val mBackupGroup: GroupVersioned = GroupVersioned(mOldGroup) + private val mBackupGroup: Group = Group(mOldGroup) override fun nodeAction() { // WARNING : Re attribute parent and children removed in group activity to save memory @@ -56,9 +56,9 @@ class UpdateGroupRunnable constructor( database.updateGroup(mOldGroup) } - val oldNodesReturn = ArrayList() + val oldNodesReturn = ArrayList() oldNodesReturn.add(mBackupGroup) - val newNodesReturn = ArrayList() + val newNodesReturn = ArrayList() newNodesReturn.add(mOldGroup) return ActionNodesValues(oldNodesReturn, newNodesReturn) } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursor.kt b/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursor.kt index e9f59624b..2190de7b4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursor.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursor.kt @@ -2,11 +2,11 @@ package com.kunzisoft.keepass.database.cursor import android.database.MatrixCursor import android.provider.BaseColumns -import com.kunzisoft.keepass.database.element.PwEntry -import com.kunzisoft.keepass.database.element.PwIconFactory -import com.kunzisoft.keepass.database.element.PwNodeId +import com.kunzisoft.keepass.database.element.entry.EntryVersioned +import com.kunzisoft.keepass.database.element.icon.IconImageFactory +import com.kunzisoft.keepass.database.element.node.NodeId -abstract class EntryCursor> : MatrixCursor(arrayOf( +abstract class EntryCursor> : MatrixCursor(arrayOf( _ID, COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS, COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS, @@ -24,9 +24,9 @@ abstract class EntryCursor> : Matr abstract fun addEntry(entry: PwEntryV) - abstract fun getPwNodeId(): PwNodeId + abstract fun getPwNodeId(): NodeId - open fun populateEntry(pwEntry: PwEntryV, iconFactory: PwIconFactory) { + open fun populateEntry(pwEntry: PwEntryV, iconFactory: IconImageFactory) { pwEntry.nodeId = getPwNodeId() pwEntry.title = getString(getColumnIndex(COLUMN_INDEX_TITLE)) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorKDB.kt similarity index 53% rename from app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorKDB.kt index f50b65388..5dee502de 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorKDB.kt @@ -1,19 +1,19 @@ package com.kunzisoft.keepass.database.cursor -import com.kunzisoft.keepass.database.element.PwDatabase -import com.kunzisoft.keepass.database.element.PwEntryV3 +import com.kunzisoft.keepass.database.element.database.DatabaseVersioned +import com.kunzisoft.keepass.database.element.entry.EntryKDB -class EntryCursorV3 : EntryCursorUUID() { +class EntryCursorKDB : EntryCursorUUID() { - override fun addEntry(entry: PwEntryV3) { + override fun addEntry(entry: EntryKDB) { addRow(arrayOf( entryId, entry.id.mostSignificantBits, entry.id.leastSignificantBits, entry.title, entry.icon.iconId, - PwDatabase.UUID_ZERO.mostSignificantBits, - PwDatabase.UUID_ZERO.leastSignificantBits, + DatabaseVersioned.UUID_ZERO.mostSignificantBits, + DatabaseVersioned.UUID_ZERO.leastSignificantBits, entry.username, entry.password, entry.url, diff --git a/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorKDBX.kt similarity index 84% rename from app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorKDBX.kt index f4fcbb3a1..7a790e4c4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorKDBX.kt @@ -1,15 +1,15 @@ package com.kunzisoft.keepass.database.cursor -import com.kunzisoft.keepass.database.element.PwEntryV4 -import com.kunzisoft.keepass.database.element.PwIconFactory +import com.kunzisoft.keepass.database.element.entry.EntryKDBX +import com.kunzisoft.keepass.database.element.icon.IconImageFactory import java.util.UUID -class EntryCursorV4 : EntryCursorUUID() { +class EntryCursorKDBX : EntryCursorUUID() { private val extraFieldCursor: ExtraFieldCursor = ExtraFieldCursor() - override fun addEntry(entry: PwEntryV4) { + override fun addEntry(entry: EntryKDBX) { addRow(arrayOf( entryId, entry.id.mostSignificantBits, @@ -31,7 +31,7 @@ class EntryCursorV4 : EntryCursorUUID() { entryId++ } - override fun populateEntry(pwEntry: PwEntryV4, iconFactory: PwIconFactory) { + override fun populateEntry(pwEntry: EntryKDBX, iconFactory: IconImageFactory) { super.populateEntry(pwEntry, iconFactory) // Retrieve custom icon diff --git a/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorUUID.kt b/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorUUID.kt index 95be574a9..db89e6f00 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorUUID.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/cursor/EntryCursorUUID.kt @@ -1,14 +1,14 @@ package com.kunzisoft.keepass.database.cursor -import com.kunzisoft.keepass.database.element.PwEntry -import com.kunzisoft.keepass.database.element.PwNodeId -import com.kunzisoft.keepass.database.element.PwNodeIdUUID +import com.kunzisoft.keepass.database.element.entry.EntryVersioned +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.NodeIdUUID import java.util.* -abstract class EntryCursorUUID>: EntryCursor() { +abstract class EntryCursorUUID>: EntryCursor() { - override fun getPwNodeId(): PwNodeId { - return PwNodeIdUUID( + override fun getPwNodeId(): NodeId { + return NodeIdUUID( UUID(getLong(getColumnIndex(COLUMN_INDEX_UUID_MOST_SIGNIFICANT_BITS)), getLong(getColumnIndex(COLUMN_INDEX_UUID_LEAST_SIGNIFICANT_BITS)))) } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/cursor/ExtraFieldCursor.kt b/app/src/main/java/com/kunzisoft/keepass/database/cursor/ExtraFieldCursor.kt index f86a27b82..bdbc0ee40 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/cursor/ExtraFieldCursor.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/cursor/ExtraFieldCursor.kt @@ -3,7 +3,7 @@ package com.kunzisoft.keepass.database.cursor import android.database.MatrixCursor import android.provider.BaseColumns -import com.kunzisoft.keepass.database.element.PwEntryV4 +import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.security.ProtectedString class ExtraFieldCursor : MatrixCursor(arrayOf( @@ -22,7 +22,7 @@ class ExtraFieldCursor : MatrixCursor(arrayOf( fieldId++ } - fun populateExtraFieldInEntry(pwEntry: PwEntryV4) { + fun populateExtraFieldInEntry(pwEntry: EntryKDBX) { pwEntry.putExtraField(getString(getColumnIndex(COLUMN_LABEL)), ProtectedString(getInt(getColumnIndex(COLUMN_PROTECTION)) > 0, getString(getColumnIndex(COLUMN_VALUE)))) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt index 1cb8c505f..091c4b13e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Database.kt @@ -24,19 +24,26 @@ import android.content.res.Resources import android.database.Cursor import android.net.Uri import android.util.Log -import android.webkit.URLUtil import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine -import com.kunzisoft.keepass.database.NodeHandler -import com.kunzisoft.keepass.database.cursor.EntryCursorV3 -import com.kunzisoft.keepass.database.cursor.EntryCursorV4 +import com.kunzisoft.keepass.database.action.node.NodeHandler +import com.kunzisoft.keepass.database.cursor.EntryCursorKDB +import com.kunzisoft.keepass.database.cursor.EntryCursorKDBX +import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm +import com.kunzisoft.keepass.database.element.database.DatabaseKDB +import com.kunzisoft.keepass.database.element.database.DatabaseKDBX +import com.kunzisoft.keepass.database.element.icon.IconImageFactory +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.NodeIdInt +import com.kunzisoft.keepass.database.element.node.NodeIdUUID +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.exception.* -import com.kunzisoft.keepass.database.file.PwDbHeaderV3 -import com.kunzisoft.keepass.database.file.PwDbHeaderV4 -import com.kunzisoft.keepass.database.file.load.ImporterV3 -import com.kunzisoft.keepass.database.file.load.ImporterV4 -import com.kunzisoft.keepass.database.file.save.PwDbV3Output -import com.kunzisoft.keepass.database.file.save.PwDbV4Output -import com.kunzisoft.keepass.database.search.SearchDbHelper +import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB +import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX +import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB +import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX +import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB +import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDBX +import com.kunzisoft.keepass.database.search.SearchHelper import com.kunzisoft.keepass.icons.IconDrawableFactory import com.kunzisoft.keepass.stream.LEDataInputStream import com.kunzisoft.keepass.tasks.ProgressTaskUpdater @@ -50,13 +57,13 @@ import java.util.* class Database { // To keep a reference for specific methods provided by version - private var pwDatabaseV3: PwDatabaseV3? = null - private var pwDatabaseV4: PwDatabaseV4? = null + private var mDatabaseKDB: DatabaseKDB? = null + private var mDatabaseKDBX: DatabaseKDBX? = null var fileUri: Uri? = null private set - private var mSearchHelper: SearchDbHelper? = null + private var mSearchHelper: SearchHelper? = null var isReadOnly = false @@ -64,116 +71,124 @@ class Database { var loaded = false - val iconFactory: PwIconFactory + val iconFactory: IconImageFactory get() { - return pwDatabaseV3?.iconFactory ?: pwDatabaseV4?.iconFactory ?: PwIconFactory() + return mDatabaseKDB?.iconFactory ?: mDatabaseKDBX?.iconFactory ?: IconImageFactory() } val allowName: Boolean - get() = pwDatabaseV4 != null + get() = mDatabaseKDBX != null var name: String get() { - return pwDatabaseV4?.name ?: "" + return mDatabaseKDBX?.name ?: "" } set(name) { - pwDatabaseV4?.name = name - pwDatabaseV4?.nameChanged = PwDate() + mDatabaseKDBX?.name = name + mDatabaseKDBX?.nameChanged = DateInstant() } val allowDescription: Boolean - get() = pwDatabaseV4 != null + get() = mDatabaseKDBX != null var description: String get() { - return pwDatabaseV4?.description ?: "" + return mDatabaseKDBX?.description ?: "" } set(description) { - pwDatabaseV4?.description = description - pwDatabaseV4?.descriptionChanged = PwDate() + mDatabaseKDBX?.description = description + mDatabaseKDBX?.descriptionChanged = DateInstant() } val allowDefaultUsername: Boolean - get() = pwDatabaseV4 != null - // TODO get() = pwDatabaseV3 != null || pwDatabaseV4 != null + get() = mDatabaseKDBX != null + // TODO get() = mDatabaseKDB != null || mDatabaseKDBX != null var defaultUsername: String get() { - return pwDatabaseV4?.defaultUserName ?: "" // TODO pwDatabaseV3 default username + return mDatabaseKDBX?.defaultUserName ?: "" // TODO mDatabaseKDB default username } set(username) { - pwDatabaseV4?.defaultUserName = username - pwDatabaseV4?.defaultUserNameChanged = PwDate() + mDatabaseKDBX?.defaultUserName = username + mDatabaseKDBX?.defaultUserNameChanged = DateInstant() } val allowCustomColor: Boolean - get() = pwDatabaseV4 != null - // TODO get() = pwDatabaseV3 != null || pwDatabaseV4 != null + get() = mDatabaseKDBX != null + // TODO get() = mDatabaseKDB != null || mDatabaseKDBX != null // with format "#000000" var customColor: String get() { - return pwDatabaseV4?.color ?: "" // TODO pwDatabaseV3 color + return mDatabaseKDBX?.color ?: "" // TODO mDatabaseKDB color } set(value) { // TODO Check color string - pwDatabaseV4?.color = value + mDatabaseKDBX?.color = value } + val allowOTP: Boolean + get() = mDatabaseKDBX != null + val version: String - get() = pwDatabaseV3?.version ?: pwDatabaseV4?.version ?: "-" + get() = mDatabaseKDB?.version ?: mDatabaseKDBX?.version ?: "-" val allowDataCompression: Boolean - get() = pwDatabaseV4 != null + get() = mDatabaseKDBX != null - val availableCompressionAlgorithms: List - get() = pwDatabaseV4?.availableCompressionAlgorithms ?: ArrayList() + val availableCompressionAlgorithms: List + get() = mDatabaseKDBX?.availableCompressionAlgorithms ?: ArrayList() - var compressionAlgorithm: PwCompressionAlgorithm? - get() = pwDatabaseV4?.compressionAlgorithm + var compressionAlgorithm: CompressionAlgorithm? + get() = mDatabaseKDBX?.compressionAlgorithm set(value) { value?.let { - pwDatabaseV4?.compressionAlgorithm = it + mDatabaseKDBX?.compressionAlgorithm = it } } + fun updateDataBinaryCompression(oldCompression: CompressionAlgorithm, + newCompression: CompressionAlgorithm) { + mDatabaseKDBX?.changeBinaryCompression(oldCompression, newCompression) + } + val allowNoMasterKey: Boolean - get() = pwDatabaseV4 != null + get() = mDatabaseKDBX != null val allowEncryptionAlgorithmModification: Boolean get() = availableEncryptionAlgorithms.size > 1 fun getEncryptionAlgorithmName(resources: Resources): String { - return pwDatabaseV3?.encryptionAlgorithm?.getName(resources) - ?: pwDatabaseV4?.encryptionAlgorithm?.getName(resources) + return mDatabaseKDB?.encryptionAlgorithm?.getName(resources) + ?: mDatabaseKDBX?.encryptionAlgorithm?.getName(resources) ?: "" } - val availableEncryptionAlgorithms: List - get() = pwDatabaseV3?.availableEncryptionAlgorithms ?: pwDatabaseV4?.availableEncryptionAlgorithms ?: ArrayList() + val availableEncryptionAlgorithms: List + get() = mDatabaseKDB?.availableEncryptionAlgorithms ?: mDatabaseKDBX?.availableEncryptionAlgorithms ?: ArrayList() - var encryptionAlgorithm: PwEncryptionAlgorithm? - get() = pwDatabaseV3?.encryptionAlgorithm ?: pwDatabaseV4?.encryptionAlgorithm + var encryptionAlgorithm: EncryptionAlgorithm? + get() = mDatabaseKDB?.encryptionAlgorithm ?: mDatabaseKDBX?.encryptionAlgorithm set(algorithm) { algorithm?.let { - pwDatabaseV4?.encryptionAlgorithm = algorithm - pwDatabaseV4?.setDataEngine(algorithm.cipherEngine) - pwDatabaseV4?.dataCipher = algorithm.dataCipher + mDatabaseKDBX?.encryptionAlgorithm = algorithm + mDatabaseKDBX?.setDataEngine(algorithm.cipherEngine) + mDatabaseKDBX?.dataCipher = algorithm.dataCipher } } val availableKdfEngines: List - get() = pwDatabaseV3?.kdfAvailableList ?: pwDatabaseV4?.kdfAvailableList ?: ArrayList() + get() = mDatabaseKDB?.kdfAvailableList ?: mDatabaseKDBX?.kdfAvailableList ?: ArrayList() val allowKdfModification: Boolean get() = availableKdfEngines.size > 1 var kdfEngine: KdfEngine? - get() = pwDatabaseV3?.kdfEngine ?: pwDatabaseV4?.kdfEngine + get() = mDatabaseKDB?.kdfEngine ?: mDatabaseKDBX?.kdfEngine set(kdfEngine) { kdfEngine?.let { - if (pwDatabaseV4?.kdfParameters?.uuid != kdfEngine.defaultParameters.uuid) - pwDatabaseV4?.kdfParameters = kdfEngine.defaultParameters + if (mDatabaseKDBX?.kdfParameters?.uuid != kdfEngine.defaultParameters.uuid) + mDatabaseKDBX?.kdfParameters = kdfEngine.defaultParameters numberKeyEncryptionRounds = kdfEngine.defaultKeyRounds memoryUsage = kdfEngine.defaultMemoryUsage parallelism = kdfEngine.defaultParallelism @@ -185,62 +200,62 @@ class Database { } var numberKeyEncryptionRounds: Long - get() = pwDatabaseV3?.numberKeyEncryptionRounds ?: pwDatabaseV4?.numberKeyEncryptionRounds ?: 0 + get() = mDatabaseKDB?.numberKeyEncryptionRounds ?: mDatabaseKDBX?.numberKeyEncryptionRounds ?: 0 @Throws(NumberFormatException::class) set(numberRounds) { - pwDatabaseV3?.numberKeyEncryptionRounds = numberRounds - pwDatabaseV4?.numberKeyEncryptionRounds = numberRounds + mDatabaseKDB?.numberKeyEncryptionRounds = numberRounds + mDatabaseKDBX?.numberKeyEncryptionRounds = numberRounds } var memoryUsage: Long get() { - return pwDatabaseV4?.memoryUsage ?: return KdfEngine.UNKNOWN_VALUE.toLong() + return mDatabaseKDBX?.memoryUsage ?: return KdfEngine.UNKNOWN_VALUE.toLong() } set(memory) { - pwDatabaseV4?.memoryUsage = memory + mDatabaseKDBX?.memoryUsage = memory } var parallelism: Int - get() = pwDatabaseV4?.parallelism ?: KdfEngine.UNKNOWN_VALUE + get() = mDatabaseKDBX?.parallelism ?: KdfEngine.UNKNOWN_VALUE set(parallelism) { - pwDatabaseV4?.parallelism = parallelism + mDatabaseKDBX?.parallelism = parallelism } var masterKey: ByteArray - get() = pwDatabaseV3?.masterKey ?: pwDatabaseV4?.masterKey ?: ByteArray(32) + get() = mDatabaseKDB?.masterKey ?: mDatabaseKDBX?.masterKey ?: ByteArray(32) set(masterKey) { - pwDatabaseV3?.masterKey = masterKey - pwDatabaseV4?.masterKey = masterKey + mDatabaseKDB?.masterKey = masterKey + mDatabaseKDBX?.masterKey = masterKey } - val rootGroup: GroupVersioned? + val rootGroup: Group? get() { - pwDatabaseV3?.rootGroup?.let { - return GroupVersioned(it) + mDatabaseKDB?.rootGroup?.let { + return Group(it) } - pwDatabaseV4?.rootGroup?.let { - return GroupVersioned(it) + mDatabaseKDBX?.rootGroup?.let { + return Group(it) } return null } val manageHistory: Boolean - get() = pwDatabaseV4 != null + get() = mDatabaseKDBX != null var historyMaxItems: Int get() { - return pwDatabaseV4?.historyMaxItems ?: 0 + return mDatabaseKDBX?.historyMaxItems ?: 0 } set(value) { - pwDatabaseV4?.historyMaxItems = value + mDatabaseKDBX?.historyMaxItems = value } var historyMaxSize: Long get() { - return pwDatabaseV4?.historyMaxSize ?: 0 + return mDatabaseKDBX?.historyMaxSize ?: 0 } set(value) { - pwDatabaseV4?.historyMaxSize = value + mDatabaseKDBX?.historyMaxSize = value } /** @@ -248,43 +263,48 @@ class Database { * @return true if RecycleBin available */ val allowRecycleBin: Boolean - get() = pwDatabaseV4 != null + get() = mDatabaseKDBX != null - val isRecycleBinEnabled: Boolean - get() = pwDatabaseV4?.isRecycleBinEnabled ?: false + var isRecycleBinEnabled: Boolean + // TODO #394 isRecycleBinEnabled mDatabaseKDB + get() = mDatabaseKDB != null || mDatabaseKDBX?.isRecycleBinEnabled ?: false + set(value) { + mDatabaseKDBX?.isRecycleBinEnabled = value + } - val recycleBin: GroupVersioned? + val recycleBin: Group? get() { - pwDatabaseV4?.recycleBin?.let { - return GroupVersioned(it) + mDatabaseKDB?.backupGroup?.let { + return Group(it) + } + mDatabaseKDBX?.recycleBin?.let { + return Group(it) } return null } - private fun setDatabaseV3(pwDatabaseV3: PwDatabaseV3) { - this.pwDatabaseV3 = pwDatabaseV3 - this.pwDatabaseV4 = null + fun ensureRecycleBinExists(resources: Resources) { + mDatabaseKDB?.ensureRecycleBinExists() + mDatabaseKDBX?.ensureRecycleBinExists(resources) } - private fun setDatabaseV4(pwDatabaseV4: PwDatabaseV4) { - this.pwDatabaseV3 = null - this.pwDatabaseV4 = pwDatabaseV4 + fun removeRecycleBin() { + // TODO #394 delete backup mDatabaseKDB?.removeRecycleBin() + mDatabaseKDBX?.removeRecycleBin() } - private fun dbNameFromUri(databaseUri: Uri): String { - val filename = URLUtil.guessFileName(databaseUri.path, null, null) - if (filename == null || filename.isEmpty()) { - return "KeePass Database" - } - val lastExtDot = filename.lastIndexOf(".") - return if (lastExtDot == -1) { - filename - } else filename.substring(0, lastExtDot) + private fun setDatabaseKDB(databaseKDB: DatabaseKDB) { + this.mDatabaseKDB = databaseKDB + this.mDatabaseKDBX = null } - fun createData(databaseUri: Uri) { - // Always create a new database with the last version - setDatabaseV4(PwDatabaseV4(dbNameFromUri(databaseUri))) + private fun setDatabaseKDBX(databaseKDBX: DatabaseKDBX) { + this.mDatabaseKDB = null + this.mDatabaseKDBX = databaseKDBX + } + + fun createData(databaseUri: Uri, databaseName: String, rootName: String) { + setDatabaseKDBX(DatabaseKDBX(databaseName, rootName)) this.fileUri = databaseUri } @@ -310,7 +330,7 @@ class Database { inputStream = UriUtil.getUriInputStream(contentResolver, uri) } catch (e: Exception) { Log.e("KPD", "Database::loadData", e) - throw LoadDatabaseFileNotFoundException() + throw FileNotFoundDatabaseException() } // Pass KeyFile Uri as InputStreams @@ -320,7 +340,7 @@ class Database { keyFileInputStream = UriUtil.getUriInputStream(contentResolver, keyfile) } catch (e: Exception) { Log.e("KPD", "Database::loadData", e) - throw LoadDatabaseFileNotFoundException() + throw FileNotFoundDatabaseException() } } @@ -342,15 +362,15 @@ class Database { bufferedInputStream.reset() when { - // Header of database V3 - PwDbHeaderV3.matchesHeader(sig1, sig2) -> setDatabaseV3(ImporterV3() + // Header of database KDB + DatabaseHeaderKDB.matchesHeader(sig1, sig2) -> setDatabaseKDB(DatabaseInputKDB() .openDatabase(bufferedInputStream, password, keyFileInputStream, progressTaskUpdater)) - // Header of database V4 - PwDbHeaderV4.matchesHeader(sig1, sig2) -> setDatabaseV4(ImporterV4( + // Header of database KDBX + DatabaseHeaderKDBX.matchesHeader(sig1, sig2) -> setDatabaseKDBX(DatabaseInputKDBX( cacheDirectory, fixDuplicateUUID) .openDatabase(bufferedInputStream, @@ -359,64 +379,64 @@ class Database { progressTaskUpdater)) // Header not recognized - else -> throw LoadDatabaseSignatureException() + else -> throw SignatureDatabaseException() } - this.mSearchHelper = SearchDbHelper(omitBackup) + this.mSearchHelper = SearchHelper(omitBackup) loaded = true } - fun isGroupSearchable(group: GroupVersioned, isOmitBackup: Boolean): Boolean { - return pwDatabaseV3?.isGroupSearchable(group.pwGroupV3, isOmitBackup) ?: - pwDatabaseV4?.isGroupSearchable(group.pwGroupV4, isOmitBackup) ?: + fun isGroupSearchable(group: Group, isOmitBackup: Boolean): Boolean { + return mDatabaseKDB?.isGroupSearchable(group.groupKDB, isOmitBackup) ?: + mDatabaseKDBX?.isGroupSearchable(group.groupKDBX, isOmitBackup) ?: false } @JvmOverloads - fun search(str: String, max: Int = Integer.MAX_VALUE): GroupVersioned? { + fun search(str: String, max: Int = Integer.MAX_VALUE): Group? { return mSearchHelper?.search(this, str, max) } fun searchEntries(query: String): Cursor? { - var cursorV3: EntryCursorV3? = null - var cursorV4: EntryCursorV4? = null + var cursorKDB: EntryCursorKDB? = null + var cursorKDBX: EntryCursorKDBX? = null - if (pwDatabaseV3 != null) - cursorV3 = EntryCursorV3() - if (pwDatabaseV4 != null) - cursorV4 = EntryCursorV4() + if (mDatabaseKDB != null) + cursorKDB = EntryCursorKDB() + if (mDatabaseKDBX != null) + cursorKDBX = EntryCursorKDBX() - val searchResult = search(query, SearchDbHelper.MAX_SEARCH_ENTRY) + val searchResult = search(query, SearchHelper.MAX_SEARCH_ENTRY) if (searchResult != null) { for (entry in searchResult.getChildEntries(true)) { - entry.pwEntryV3?.let { - cursorV3?.addEntry(it) + entry.entryKDB?.let { + cursorKDB?.addEntry(it) } - entry.pwEntryV4?.let { - cursorV4?.addEntry(it) + entry.entryKDBX?.let { + cursorKDBX?.addEntry(it) } } } - return cursorV3 ?: cursorV4 + return cursorKDB ?: cursorKDBX } - fun getEntryFrom(cursor: Cursor): EntryVersioned? { - val iconFactory = pwDatabaseV3?.iconFactory ?: pwDatabaseV4?.iconFactory ?: PwIconFactory() + fun getEntryFrom(cursor: Cursor): Entry? { + val iconFactory = mDatabaseKDB?.iconFactory ?: mDatabaseKDBX?.iconFactory ?: IconImageFactory() val entry = createEntry() // TODO invert field reference manager entry?.let { entryVersioned -> startManageEntry(entryVersioned) - pwDatabaseV3?.let { - entryVersioned.pwEntryV3?.let { entryV3 -> - (cursor as EntryCursorV3).populateEntry(entryV3, iconFactory) + mDatabaseKDB?.let { + entryVersioned.entryKDB?.let { entryKDB -> + (cursor as EntryCursorKDB).populateEntry(entryKDB, iconFactory) } } - pwDatabaseV4?.let { - entryVersioned.pwEntryV4?.let { entryV4 -> - (cursor as EntryCursorV4).populateEntry(entryV4, iconFactory) + mDatabaseKDBX?.let { + entryVersioned.entryKDBX?.let { entryKDBX -> + (cursor as EntryCursorKDBX).populateEntry(entryKDBX, iconFactory) } } stopManageEntry(entryVersioned) @@ -425,16 +445,20 @@ class Database { return entry } - @Throws(IOException::class, DatabaseOutputException::class) + @Throws(DatabaseOutputException::class) fun saveData(contentResolver: ContentResolver) { - this.fileUri?.let { - saveData(contentResolver, it) + try { + this.fileUri?.let { + saveData(contentResolver, it) + } + } catch (e: Exception) { + Log.e(TAG, "Unable to save database", e) + throw DatabaseOutputException(e) } } @Throws(IOException::class, DatabaseOutputException::class) private fun saveData(contentResolver: ContentResolver, uri: Uri) { - val errorMessage = "Failed to store database." if (uri.scheme == "file") { uri.path?.let { filename -> @@ -443,12 +467,11 @@ class Database { var fileOutputStream: FileOutputStream? = null try { fileOutputStream = FileOutputStream(tempFile) - val pmo = pwDatabaseV3?.let { PwDbV3Output(it, fileOutputStream) } - ?: pwDatabaseV4?.let { PwDbV4Output(it, fileOutputStream) } + val pmo = mDatabaseKDB?.let { DatabaseOutputKDB(it, fileOutputStream) } + ?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, fileOutputStream) } pmo?.output() } catch (e: Exception) { - Log.e(TAG, errorMessage, e) - throw IOException(errorMessage, e) + throw IOException(e) } finally { fileOutputStream?.close() } @@ -461,7 +484,7 @@ class Database { } if (!tempFile.renameTo(File(filename))) { - throw IOException(errorMessage) + throw IOException() } } } else { @@ -469,12 +492,11 @@ class Database { try { outputStream = contentResolver.openOutputStream(uri) val pmo = - pwDatabaseV3?.let { PwDbV3Output(it, outputStream) } - ?: pwDatabaseV4?.let { PwDbV4Output(it, outputStream) } + mDatabaseKDB?.let { DatabaseOutputKDB(it, outputStream) } + ?: mDatabaseKDBX?.let { DatabaseOutputKDBX(it, outputStream) } pmo?.output() } catch (e: Exception) { - Log.e(TAG, errorMessage, e) - throw IOException(errorMessage, e) + throw IOException(e) } finally { outputStream?.close() } @@ -482,12 +504,11 @@ class Database { this.fileUri = uri } - // TODO Clear database when lock broadcast is receive in backstage fun closeAndClear(filesDirectory: File? = null) { drawFactory.clearCache() // Delete the cache of the database if present - pwDatabaseV3?.clearCache() - pwDatabaseV4?.clearCache() + mDatabaseKDB?.clearCache() + mDatabaseKDBX?.clearCache() // In all cases, delete all the files in the temp dir try { FileUtils.cleanDirectory(filesDirectory) @@ -495,36 +516,36 @@ class Database { Log.e(TAG, "Unable to clear the directory cache.", e) } - this.pwDatabaseV3 = null - this.pwDatabaseV4 = null + this.mDatabaseKDB = null + this.mDatabaseKDBX = null this.fileUri = null this.loaded = false } fun validatePasswordEncoding(password: String?, containsKeyFile: Boolean): Boolean { - return pwDatabaseV3?.validatePasswordEncoding(password, containsKeyFile) - ?: pwDatabaseV4?.validatePasswordEncoding(password, containsKeyFile) + return mDatabaseKDB?.validatePasswordEncoding(password, containsKeyFile) + ?: mDatabaseKDBX?.validatePasswordEncoding(password, containsKeyFile) ?: false } @Throws(IOException::class) fun retrieveMasterKey(key: String?, keyInputStream: InputStream?) { - pwDatabaseV3?.retrieveMasterKey(key, keyInputStream) - pwDatabaseV4?.retrieveMasterKey(key, keyInputStream) + mDatabaseKDB?.retrieveMasterKey(key, keyInputStream) + mDatabaseKDBX?.retrieveMasterKey(key, keyInputStream) } fun rootCanContainsEntry(): Boolean { - return pwDatabaseV3?.rootCanContainsEntry() ?: pwDatabaseV4?.rootCanContainsEntry() ?: false + return mDatabaseKDB?.rootCanContainsEntry() ?: mDatabaseKDBX?.rootCanContainsEntry() ?: false } - fun createEntry(): EntryVersioned? { - pwDatabaseV3?.let { database -> - return EntryVersioned(database.createEntry()).apply { + fun createEntry(): Entry? { + mDatabaseKDB?.let { database -> + return Entry(database.createEntry()).apply { nodeId = database.newEntryId() } } - pwDatabaseV4?.let { database -> - return EntryVersioned(database.createEntry()).apply { + mDatabaseKDBX?.let { database -> + return Entry(database.createEntry()).apply { nodeId = database.newEntryId() } } @@ -532,14 +553,14 @@ class Database { return null } - fun createGroup(): GroupVersioned? { - pwDatabaseV3?.let { database -> - return GroupVersioned(database.createGroup()).apply { + fun createGroup(): Group? { + mDatabaseKDB?.let { database -> + return Group(database.createGroup()).apply { setNodeId(database.newGroupId()) } } - pwDatabaseV4?.let { database -> - return GroupVersioned(database.createGroup()).apply { + mDatabaseKDBX?.let { database -> + return Group(database.createGroup()).apply { setNodeId(database.newGroupId()) } } @@ -547,82 +568,82 @@ class Database { return null } - fun getEntryById(id: PwNodeId): EntryVersioned? { - pwDatabaseV3?.getEntryById(id)?.let { - return EntryVersioned(it) + fun getEntryById(id: NodeId): Entry? { + mDatabaseKDB?.getEntryById(id)?.let { + return Entry(it) } - pwDatabaseV4?.getEntryById(id)?.let { - return EntryVersioned(it) + mDatabaseKDBX?.getEntryById(id)?.let { + return Entry(it) } return null } - fun getGroupById(id: PwNodeId<*>): GroupVersioned? { - if (id is PwNodeIdInt) - pwDatabaseV3?.getGroupById(id)?.let { - return GroupVersioned(it) + fun getGroupById(id: NodeId<*>): Group? { + if (id is NodeIdInt) + mDatabaseKDB?.getGroupById(id)?.let { + return Group(it) } - else if (id is PwNodeIdUUID) - pwDatabaseV4?.getGroupById(id)?.let { - return GroupVersioned(it) + else if (id is NodeIdUUID) + mDatabaseKDBX?.getGroupById(id)?.let { + return Group(it) } return null } - fun addEntryTo(entry: EntryVersioned, parent: GroupVersioned) { - entry.pwEntryV3?.let { entryV3 -> - pwDatabaseV3?.addEntryTo(entryV3, parent.pwGroupV3) + fun addEntryTo(entry: Entry, parent: Group) { + entry.entryKDB?.let { entryKDB -> + mDatabaseKDB?.addEntryTo(entryKDB, parent.groupKDB) } - entry.pwEntryV4?.let { entryV4 -> - pwDatabaseV4?.addEntryTo(entryV4, parent.pwGroupV4) + entry.entryKDBX?.let { entryKDBX -> + mDatabaseKDBX?.addEntryTo(entryKDBX, parent.groupKDBX) } entry.afterAssignNewParent() } - fun updateEntry(entry: EntryVersioned) { - entry.pwEntryV3?.let { entryV3 -> - pwDatabaseV3?.updateEntry(entryV3) + fun updateEntry(entry: Entry) { + entry.entryKDB?.let { entryKDB -> + mDatabaseKDB?.updateEntry(entryKDB) } - entry.pwEntryV4?.let { entryV4 -> - pwDatabaseV4?.updateEntry(entryV4) + entry.entryKDBX?.let { entryKDBX -> + mDatabaseKDBX?.updateEntry(entryKDBX) } } - fun removeEntryFrom(entry: EntryVersioned, parent: GroupVersioned) { - entry.pwEntryV3?.let { entryV3 -> - pwDatabaseV3?.removeEntryFrom(entryV3, parent.pwGroupV3) + fun removeEntryFrom(entry: Entry, parent: Group) { + entry.entryKDB?.let { entryKDB -> + mDatabaseKDB?.removeEntryFrom(entryKDB, parent.groupKDB) } - entry.pwEntryV4?.let { entryV4 -> - pwDatabaseV4?.removeEntryFrom(entryV4, parent.pwGroupV4) + entry.entryKDBX?.let { entryKDBX -> + mDatabaseKDBX?.removeEntryFrom(entryKDBX, parent.groupKDBX) } entry.afterAssignNewParent() } - fun addGroupTo(group: GroupVersioned, parent: GroupVersioned) { - group.pwGroupV3?.let { groupV3 -> - pwDatabaseV3?.addGroupTo(groupV3, parent.pwGroupV3) + fun addGroupTo(group: Group, parent: Group) { + group.groupKDB?.let { entryKDB -> + mDatabaseKDB?.addGroupTo(entryKDB, parent.groupKDB) } - group.pwGroupV4?.let { groupV4 -> - pwDatabaseV4?.addGroupTo(groupV4, parent.pwGroupV4) + group.groupKDBX?.let { entryKDBX -> + mDatabaseKDBX?.addGroupTo(entryKDBX, parent.groupKDBX) } group.afterAssignNewParent() } - fun updateGroup(group: GroupVersioned) { - group.pwGroupV3?.let { groupV3 -> - pwDatabaseV3?.updateGroup(groupV3) + fun updateGroup(group: Group) { + group.groupKDB?.let { entryKDB -> + mDatabaseKDB?.updateGroup(entryKDB) } - group.pwGroupV4?.let { groupV4 -> - pwDatabaseV4?.updateGroup(groupV4) + group.groupKDBX?.let { entryKDBX -> + mDatabaseKDBX?.updateGroup(entryKDBX) } } - fun removeGroupFrom(group: GroupVersioned, parent: GroupVersioned) { - group.pwGroupV3?.let { groupV3 -> - pwDatabaseV3?.removeGroupFrom(groupV3, parent.pwGroupV3) + fun removeGroupFrom(group: Group, parent: Group) { + group.groupKDB?.let { entryKDB -> + mDatabaseKDB?.removeGroupFrom(entryKDB, parent.groupKDB) } - group.pwGroupV4?.let { groupV4 -> - pwDatabaseV4?.removeGroupFrom(groupV4, parent.pwGroupV4) + group.groupKDBX?.let { entryKDBX -> + mDatabaseKDBX?.removeGroupFrom(entryKDBX, parent.groupKDBX) } group.afterAssignNewParent() } @@ -632,45 +653,45 @@ class Database { * @param entryToCopy * @param newParent */ - fun copyEntryTo(entryToCopy: EntryVersioned, newParent: GroupVersioned): EntryVersioned? { - val entryCopied = EntryVersioned(entryToCopy, false) - entryCopied.nodeId = pwDatabaseV3?.newEntryId() ?: pwDatabaseV4?.newEntryId() ?: PwNodeIdUUID() + fun copyEntryTo(entryToCopy: Entry, newParent: Group): Entry? { + val entryCopied = Entry(entryToCopy, false) + entryCopied.nodeId = mDatabaseKDB?.newEntryId() ?: mDatabaseKDBX?.newEntryId() ?: NodeIdUUID() entryCopied.parent = newParent entryCopied.title += " (~)" addEntryTo(entryCopied, newParent) return entryCopied } - fun moveEntryTo(entryToMove: EntryVersioned, newParent: GroupVersioned) { + fun moveEntryTo(entryToMove: Entry, newParent: Group) { entryToMove.parent?.let { removeEntryFrom(entryToMove, it) } addEntryTo(entryToMove, newParent) } - fun moveGroupTo(groupToMove: GroupVersioned, newParent: GroupVersioned) { + fun moveGroupTo(groupToMove: Group, newParent: Group) { groupToMove.parent?.let { removeGroupFrom(groupToMove, it) } addGroupTo(groupToMove, newParent) } - fun deleteEntry(entry: EntryVersioned) { + fun deleteEntry(entry: Entry) { entry.parent?.let { removeEntryFrom(entry, it) } } - fun deleteGroup(group: GroupVersioned) { + fun deleteGroup(group: Group) { group.doForEachChildAndForIt( - object : NodeHandler() { - override fun operate(node: EntryVersioned): Boolean { + object : NodeHandler() { + override fun operate(node: Entry): Boolean { deleteEntry(node) return true } }, - object : NodeHandler() { - override fun operate(node: GroupVersioned): Boolean { + object : NodeHandler() { + override fun operate(node: Group): Boolean { node.parent?.let { removeGroupFrom(node, it) } @@ -679,89 +700,144 @@ class Database { }) } - fun undoDeleteEntry(entry: EntryVersioned, parent: GroupVersioned) { - entry.pwEntryV3?.let { entryV3 -> - pwDatabaseV3?.undoDeleteEntryFrom(entryV3, parent.pwGroupV3) + fun undoDeleteEntry(entry: Entry, parent: Group) { + entry.entryKDB?.let { + mDatabaseKDB?.undoDeleteEntryFrom(it, parent.groupKDB) } - entry.pwEntryV4?.let { entryV4 -> - pwDatabaseV4?.undoDeleteEntryFrom(entryV4, parent.pwGroupV4) + entry.entryKDBX?.let { + mDatabaseKDBX?.undoDeleteEntryFrom(it, parent.groupKDBX) } } - fun undoDeleteGroup(group: GroupVersioned, parent: GroupVersioned) { - group.pwGroupV3?.let { groupV3 -> - pwDatabaseV3?.undoDeleteGroupFrom(groupV3, parent.pwGroupV3) + fun undoDeleteGroup(group: Group, parent: Group) { + group.groupKDB?.let { + mDatabaseKDB?.undoDeleteGroupFrom(it, parent.groupKDB) } - group.pwGroupV4?.let { groupV4 -> - pwDatabaseV4?.undoDeleteGroupFrom(groupV4, parent.pwGroupV4) + group.groupKDBX?.let { + mDatabaseKDBX?.undoDeleteGroupFrom(it, parent.groupKDBX) } } - fun canRecycle(entry: EntryVersioned): Boolean { + fun canRecycle(entry: Entry): Boolean { var canRecycle: Boolean? = null - entry.pwEntryV4?.let { entryV4 -> - canRecycle = pwDatabaseV4?.canRecycle(entryV4) + entry.entryKDB?.let { + canRecycle = mDatabaseKDB?.canRecycle(it) + } + entry.entryKDBX?.let { + canRecycle = mDatabaseKDBX?.canRecycle(it) } return canRecycle ?: false } - fun canRecycle(group: GroupVersioned): Boolean { + fun canRecycle(group: Group): Boolean { var canRecycle: Boolean? = null - group.pwGroupV4?.let { groupV4 -> - canRecycle = pwDatabaseV4?.canRecycle(groupV4) + group.groupKDB?.let { + canRecycle = mDatabaseKDB?.canRecycle(it) + } + group.groupKDBX?.let { + canRecycle = mDatabaseKDBX?.canRecycle(it) } return canRecycle ?: false } - fun recycle(entry: EntryVersioned, resources: Resources) { - entry.pwEntryV4?.let { - pwDatabaseV4?.recycle(it, resources) + fun recycle(entry: Entry, resources: Resources) { + entry.entryKDB?.let { + mDatabaseKDB?.recycle(it) + } + entry.entryKDBX?.let { + mDatabaseKDBX?.recycle(it, resources) } } - fun recycle(group: GroupVersioned, resources: Resources) { - group.pwGroupV4?.let { - pwDatabaseV4?.recycle(it, resources) + fun recycle(group: Group, resources: Resources) { + group.groupKDB?.let { + mDatabaseKDB?.recycle(it) + } + group.groupKDBX?.let { + mDatabaseKDBX?.recycle(it, resources) } } - fun undoRecycle(entry: EntryVersioned, parent: GroupVersioned) { - entry.pwEntryV4?.let { entryV4 -> - parent.pwGroupV4?.let { parentV4 -> - pwDatabaseV4?.undoRecycle(entryV4, parentV4) + fun undoRecycle(entry: Entry, parent: Group) { + entry.entryKDB?.let { entryKDB -> + parent.groupKDB?.let { parentKDB -> + mDatabaseKDB?.undoRecycle(entryKDB, parentKDB) + } + } + entry.entryKDBX?.let { entryKDBX -> + parent.groupKDBX?.let { parentKDBX -> + mDatabaseKDBX?.undoRecycle(entryKDBX, parentKDBX) } } } - fun undoRecycle(group: GroupVersioned, parent: GroupVersioned) { - group.pwGroupV4?.let { groupV4 -> - parent.pwGroupV4?.let { parentV4 -> - pwDatabaseV4?.undoRecycle(groupV4, parentV4) + fun undoRecycle(group: Group, parent: Group) { + group.groupKDB?.let { groupKDB -> + parent.groupKDB?.let { parentKDB -> + mDatabaseKDB?.undoRecycle(groupKDB, parentKDB) + } + } + group.groupKDBX?.let { entryKDBX -> + parent.groupKDBX?.let { parentKDBX -> + mDatabaseKDBX?.undoRecycle(entryKDBX, parentKDBX) } } } - fun startManageEntry(entry: EntryVersioned) { - pwDatabaseV4?.let { + fun startManageEntry(entry: Entry) { + mDatabaseKDBX?.let { entry.startToManageFieldReferences(it) } } - fun stopManageEntry(entry: EntryVersioned) { - pwDatabaseV4?.let { + fun stopManageEntry(entry: Entry) { + mDatabaseKDBX?.let { entry.stopToManageFieldReferences() } } - fun removeOldestHistory(entry: EntryVersioned) { + /** + * Remove oldest history for each entry if more than max items or max memory + */ + fun removeOldestHistoryForEachEntry() { + rootGroup?.doForEachChildAndForIt( + object : NodeHandler() { + override fun operate(node: Entry): Boolean { + removeOldestEntryHistory(node) + return true + } + }, + object : NodeHandler() { + override fun operate(node: Group): Boolean { + return true + } + }) + } + + fun removeEachEntryHistory() { + rootGroup?.doForEachChildAndForIt( + object : NodeHandler() { + override fun operate(node: Entry): Boolean { + node.removeAllHistory() + return true + } + }, + object : NodeHandler() { + override fun operate(node: Group): Boolean { + return true + } + }) + } - // Remove oldest history if more than max items or max memory - pwDatabaseV4?.let { - val history = entry.getHistory() + /** + * Remove oldest history if more than max items or max memory + */ + fun removeOldestEntryHistory(entry: Entry) { + mDatabaseKDBX?.let { val maxItems = historyMaxItems if (maxItems >= 0) { - while (history.size > maxItems) { + while (entry.getHistory().size > maxItems) { entry.removeOldestEntryFromHistory() } } @@ -770,7 +846,7 @@ class Database { if (maxSize >= 0) { while (true) { var historySize: Long = 0 - for (entryHistory in history) { + for (entryHistory in entry.getHistory()) { historySize += entryHistory.getSize() } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/DateInstant.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/DateInstant.kt new file mode 100644 index 000000000..5b3eded9a --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/DateInstant.kt @@ -0,0 +1,144 @@ +/* + * Copyright 2019 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePass DX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.element + +import android.content.res.Resources +import android.os.Parcel +import android.os.Parcelable +import androidx.core.os.ConfigurationCompat +import java.util.* + +class DateInstant : Parcelable { + + private var jDate: Date = Date() + + val date: Date + get() = jDate + + constructor(source: DateInstant) { + this.jDate = Date(source.jDate.time) + } + + constructor(date: Date) { + jDate = Date(date.time) + } + + constructor(millis: Long) { + jDate = Date(millis) + } + + constructor() { + jDate = Date() + } + + protected constructor(parcel: Parcel) { + jDate = parcel.readSerializable() as Date + } + + override fun describeContents(): Int { + return 0 + } + + fun getDateTimeString(resources: Resources): String { + return Companion.getDateTimeString(resources, this.date) + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeSerializable(date) + } + + override fun equals(other: Any?): Boolean { + if (this === other) { + return true + } + if (other == null) { + return false + } + if (javaClass != other.javaClass) { + return false + } + + val date = other as DateInstant + return isSameDate(jDate, date.jDate) + } + + override fun hashCode(): Int { + return jDate.hashCode() + } + + override fun toString(): String { + return jDate.toString() + } + + companion object { + + val NEVER_EXPIRE = neverExpire + + private val neverExpire: DateInstant + get() { + val cal = Calendar.getInstance() + cal.set(Calendar.YEAR, 2999) + cal.set(Calendar.MONTH, 11) + cal.set(Calendar.DAY_OF_MONTH, 28) + cal.set(Calendar.HOUR, 23) + cal.set(Calendar.MINUTE, 59) + cal.set(Calendar.SECOND, 59) + + return DateInstant(cal.time) + } + + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): DateInstant { + return DateInstant(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + + private fun isSameDate(d1: Date?, d2: Date?): Boolean { + val cal1 = Calendar.getInstance() + cal1.time = d1 + cal1.set(Calendar.MILLISECOND, 0) + + val cal2 = Calendar.getInstance() + cal2.time = d2 + cal2.set(Calendar.MILLISECOND, 0) + + return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && + cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) && + cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH) && + cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR) && + cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE) && + cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND) + + } + + fun getDateTimeString(resources: Resources, date: Date): String { + return java.text.DateFormat.getDateTimeInstance( + java.text.DateFormat.MEDIUM, + java.text.DateFormat.MEDIUM, + ConfigurationCompat.getLocales(resources.configuration)[0]) + .format(date) + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDeletedObject.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/DeletedObject.kt similarity index 88% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwDeletedObject.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/DeletedObject.kt index f0d78f5dd..17d329c48 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDeletedObject.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/DeletedObject.kt @@ -19,12 +19,13 @@ */ package com.kunzisoft.keepass.database.element +import com.kunzisoft.keepass.database.element.database.DatabaseVersioned import java.util.Date import java.util.UUID -class PwDeletedObject { +class DeletedObject { - var uuid: UUID = PwDatabase.UUID_ZERO + var uuid: UUID = DatabaseVersioned.UUID_ZERO var deletionTime: Date? = null get() = if (field == null) { Date(System.currentTimeMillis()) @@ -43,7 +44,7 @@ class PwDeletedObject { return true if (other == null) return false - if (other !is PwDeletedObject) + if (other !is DeletedObject) return false return uuid == other.uuid } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt new file mode 100644 index 000000000..a326c2ea9 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Entry.kt @@ -0,0 +1,406 @@ +package com.kunzisoft.keepass.database.element + +import android.os.Parcel +import android.os.Parcelable +import com.kunzisoft.keepass.database.element.database.DatabaseKDBX +import com.kunzisoft.keepass.database.element.entry.EntryKDB +import com.kunzisoft.keepass.database.element.entry.EntryKDBX +import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface +import com.kunzisoft.keepass.database.element.icon.IconImage +import com.kunzisoft.keepass.database.element.icon.IconImageCustom +import com.kunzisoft.keepass.database.element.icon.IconImageStandard +import com.kunzisoft.keepass.database.element.node.Node +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.NodeIdUUID +import com.kunzisoft.keepass.database.element.node.Type +import com.kunzisoft.keepass.database.element.security.ProtectedString +import com.kunzisoft.keepass.model.EntryInfo +import com.kunzisoft.keepass.model.Field +import com.kunzisoft.keepass.otp.OtpElement +import com.kunzisoft.keepass.otp.OtpEntryFields +import java.util.* +import kotlin.collections.ArrayList + +class Entry : Node, EntryVersionedInterface { + + var entryKDB: EntryKDB? = null + private set + var entryKDBX: EntryKDBX? = null + private set + + fun updateWith(entry: Entry, copyHistory: Boolean = true) { + entry.entryKDB?.let { + this.entryKDB?.updateWith(it) + } + entry.entryKDBX?.let { + this.entryKDBX?.updateWith(it, copyHistory) + } + } + + /** + * Use this constructor to copy an Entry with exact same values + */ + constructor(entry: Entry, copyHistory: Boolean = true) { + if (entry.entryKDB != null) { + this.entryKDB = EntryKDB() + } + if (entry.entryKDBX != null) { + this.entryKDBX = EntryKDBX() + } + updateWith(entry, copyHistory) + } + + constructor(entry: EntryKDB) { + this.entryKDBX = null + this.entryKDB = entry + } + + constructor(entry: EntryKDBX) { + this.entryKDB = null + this.entryKDBX = entry + } + + constructor(parcel: Parcel) { + entryKDB = parcel.readParcelable(EntryKDB::class.java.classLoader) + entryKDBX = parcel.readParcelable(EntryKDBX::class.java.classLoader) + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeParcelable(entryKDB, flags) + dest.writeParcelable(entryKDBX, flags) + } + + override var nodeId: NodeId + get() = entryKDBX?.nodeId ?: entryKDB?.nodeId ?: NodeIdUUID() + set(value) { + entryKDB?.nodeId = value + entryKDBX?.nodeId = value + } + + override var title: String + get() = entryKDB?.title ?: entryKDBX?.title ?: "" + set(value) { + entryKDB?.title = value + entryKDBX?.title = value + } + + override var icon: IconImage + get() { + return entryKDB?.icon ?: entryKDBX?.icon ?: IconImageStandard() + } + set(value) { + entryKDB?.icon = value + entryKDBX?.icon = value + } + + override val type: Type + get() = Type.ENTRY + + override var parent: Group? + get() { + entryKDB?.parent?.let { + return Group(it) + } + entryKDBX?.parent?.let { + return Group(it) + } + return null + } + set(value) { + entryKDB?.parent = value?.groupKDB + entryKDBX?.parent = value?.groupKDBX + } + + override fun containsParent(): Boolean { + return entryKDB?.containsParent() ?: entryKDBX?.containsParent() ?: false + } + + override fun afterAssignNewParent() { + entryKDBX?.afterChangeParent() + } + + override fun touch(modified: Boolean, touchParents: Boolean) { + entryKDB?.touch(modified, touchParents) + entryKDBX?.touch(modified, touchParents) + } + + override fun isContainedIn(container: Group): Boolean { + var contained: Boolean? = false + container.groupKDB?.let { + contained = entryKDB?.isContainedIn(it) + } + container.groupKDBX?.let { + contained = entryKDBX?.isContainedIn(it) + } + return contained ?: false + } + + override var creationTime: DateInstant + get() = entryKDB?.creationTime ?: entryKDBX?.creationTime ?: DateInstant() + set(value) { + entryKDB?.creationTime = value + entryKDBX?.creationTime = value + } + + override var lastModificationTime: DateInstant + get() = entryKDB?.lastModificationTime ?: entryKDBX?.lastModificationTime ?: DateInstant() + set(value) { + entryKDB?.lastModificationTime = value + entryKDBX?.lastModificationTime = value + } + + override var lastAccessTime: DateInstant + get() = entryKDB?.lastAccessTime ?: entryKDBX?.lastAccessTime ?: DateInstant() + set(value) { + entryKDB?.lastAccessTime = value + entryKDBX?.lastAccessTime = value + } + + override var expiryTime: DateInstant + get() = entryKDB?.expiryTime ?: entryKDBX?.expiryTime ?: DateInstant() + set(value) { + entryKDB?.expiryTime = value + entryKDBX?.expiryTime = value + } + + override var expires: Boolean + get() = entryKDB?.expires ?: entryKDBX?.expires ?: false + set(value) { + entryKDB?.expires = value + entryKDBX?.expires = value + } + + override val isCurrentlyExpires: Boolean + get() = entryKDB?.isCurrentlyExpires ?: entryKDBX?.isCurrentlyExpires ?: false + + override var username: String + get() = entryKDB?.username ?: entryKDBX?.username ?: "" + set(value) { + entryKDB?.username = value + entryKDBX?.username = value + } + + override var password: String + get() = entryKDB?.password ?: entryKDBX?.password ?: "" + set(value) { + entryKDB?.password = value + entryKDBX?.password = value + } + + override var url: String + get() = entryKDB?.url ?: entryKDBX?.url ?: "" + set(value) { + entryKDB?.url = value + entryKDBX?.url = value + } + + override var notes: String + get() = entryKDB?.notes ?: entryKDBX?.notes ?: "" + set(value) { + entryKDB?.notes = value + entryKDBX?.notes = value + } + + private fun isTan(): Boolean { + return title == PMS_TAN_ENTRY && username.isNotEmpty() + } + + fun getVisualTitle(): String { + return getVisualTitle(isTan(), + title, + username, + url, + nodeId.toString()) + } + + /* + ------------ + KDB Methods + ------------ + */ + + /** + * If it's a node with only meta information like Meta-info SYSTEM Database Color + * @return false by default, true if it's a meta stream + */ + val isMetaStream: Boolean + get() = entryKDB?.isMetaStream ?: false + + /* + ------------ + KDBX Methods + ------------ + */ + + var iconCustom: IconImageCustom + get() = entryKDBX?.iconCustom ?: IconImageCustom.UNKNOWN_ICON + set(value) { + entryKDBX?.iconCustom = value + } + + /** + * Retrieve custom fields to show, key is the label, value is the value of field (protected or not) + * @return Map of label/value + */ + val customFields: HashMap + get() = entryKDBX?.customFields ?: HashMap() + + /** + * To redefine if version of entry allow custom field, + * @return true if entry allows custom field + */ + fun allowCustomFields(): Boolean { + return entryKDBX?.allowCustomFields() ?: false + } + + fun removeAllFields() { + entryKDBX?.removeAllFields() + } + + /** + * Update or add an extra field to the list (standard or custom) + * @param label Label of field, must be unique + * @param value Value of field + */ + fun putExtraField(label: String, value: ProtectedString) { + entryKDBX?.putExtraField(label, value) + } + + fun getOtpElement(): OtpElement? { + return OtpEntryFields.parseFields { key -> + customFields[key]?.toString() + } + } + + fun startToManageFieldReferences(db: DatabaseKDBX) { + entryKDBX?.startToManageFieldReferences(db) + } + + fun stopToManageFieldReferences() { + entryKDBX?.stopToManageFieldReferences() + } + + fun getHistory(): ArrayList { + val history = ArrayList() + val entryV4History = entryKDBX?.history ?: ArrayList() + for (entryHistory in entryV4History) { + history.add(Entry(entryHistory)) + } + return history + } + + fun addEntryToHistory(entry: Entry) { + entry.entryKDBX?.let { + entryKDBX?.addEntryToHistory(it) + } + } + + fun removeAllHistory() { + entryKDBX?.removeAllHistory() + } + + fun removeOldestEntryFromHistory() { + entryKDBX?.removeOldestEntryFromHistory() + } + + fun getSize(): Long { + return entryKDBX?.size ?: 0L + } + + fun containsCustomData(): Boolean { + return entryKDBX?.containsCustomData() ?: false + } + + /* + ------------ + Converter + ------------ + */ + + /** + * Retrieve generated entry info, + * Remove parameter fields and add auto generated elements in auto custom fields + */ + fun getEntryInfo(database: Database?, raw: Boolean = false): EntryInfo { + val entryInfo = EntryInfo() + if (raw) + database?.stopManageEntry(this) + else + database?.startManageEntry(this) + entryInfo.id = nodeId.toString() + entryInfo.title = title + entryInfo.username = username + entryInfo.password = password + entryInfo.url = url + entryInfo.notes = notes + for (entry in customFields.entries) { + entryInfo.customFields.add( + Field(entry.key, entry.value)) + } + // Add otpElement to generate token + entryInfo.otpModel = getOtpElement()?.otpModel + // Replace parameter fields by generated OTP fields + entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields) + if (!raw) + database?.stopManageEntry(this) + return entryInfo + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Entry + + if (entryKDB != other.entryKDB) return false + if (entryKDBX != other.entryKDBX) return false + + return true + } + + override fun hashCode(): Int { + var result = entryKDB?.hashCode() ?: 0 + result = 31 * result + (entryKDBX?.hashCode() ?: 0) + return result + } + + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Entry { + return Entry(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + + const val PMS_TAN_ENTRY = "" + + /** + * {@inheritDoc} + * Get the display title from an entry,

+ * [.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 + } + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/EntryVersioned.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/EntryVersioned.kt deleted file mode 100644 index 900778505..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/EntryVersioned.kt +++ /dev/null @@ -1,395 +0,0 @@ -package com.kunzisoft.keepass.database.element - -import android.os.Parcel -import android.os.Parcelable -import com.kunzisoft.keepass.database.element.security.ProtectedString -import com.kunzisoft.keepass.model.EntryInfo -import com.kunzisoft.keepass.model.Field -import com.kunzisoft.keepass.otp.OtpElement -import com.kunzisoft.keepass.otp.OtpEntryFields -import java.util.* -import kotlin.collections.ArrayList - -class EntryVersioned : NodeVersioned, PwEntryInterface { - - var pwEntryV3: PwEntryV3? = null - private set - var pwEntryV4: PwEntryV4? = null - private set - - fun updateWith(entry: EntryVersioned, copyHistory: Boolean = true) { - entry.pwEntryV3?.let { - this.pwEntryV3?.updateWith(it) - } - entry.pwEntryV4?.let { - this.pwEntryV4?.updateWith(it, copyHistory) - } - } - - /** - * Use this constructor to copy an Entry with exact same values - */ - constructor(entry: EntryVersioned, copyHistory: Boolean = true) { - if (entry.pwEntryV3 != null) { - this.pwEntryV3 = PwEntryV3() - } - if (entry.pwEntryV4 != null) { - this.pwEntryV4 = PwEntryV4() - } - updateWith(entry, copyHistory) - } - - constructor(entry: PwEntryV3) { - this.pwEntryV4 = null - this.pwEntryV3 = entry - } - - constructor(entry: PwEntryV4) { - this.pwEntryV3 = null - this.pwEntryV4 = entry - } - - constructor(parcel: Parcel) { - pwEntryV3 = parcel.readParcelable(PwEntryV3::class.java.classLoader) - pwEntryV4 = parcel.readParcelable(PwEntryV4::class.java.classLoader) - } - - override fun describeContents(): Int { - return 0 - } - - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeParcelable(pwEntryV3, flags) - dest.writeParcelable(pwEntryV4, flags) - } - - override var nodeId: PwNodeId - get() = pwEntryV4?.nodeId ?: pwEntryV3?.nodeId ?: PwNodeIdUUID() - set(value) { - pwEntryV3?.nodeId = value - pwEntryV4?.nodeId = value - } - - override var title: String - get() = pwEntryV3?.title ?: pwEntryV4?.title ?: "" - set(value) { - pwEntryV3?.title = value - pwEntryV4?.title = value - } - - override var icon: PwIcon - get() { - return pwEntryV3?.icon ?: pwEntryV4?.icon ?: PwIconStandard() - } - set(value) { - pwEntryV3?.icon = value - pwEntryV4?.icon = value - } - - override val type: Type - get() = Type.ENTRY - - override var parent: GroupVersioned? - get() { - pwEntryV3?.parent?.let { - return GroupVersioned(it) - } - pwEntryV4?.parent?.let { - return GroupVersioned(it) - } - return null - } - set(value) { - pwEntryV3?.parent = value?.pwGroupV3 - pwEntryV4?.parent = value?.pwGroupV4 - } - - override fun containsParent(): Boolean { - return pwEntryV3?.containsParent() ?: pwEntryV4?.containsParent() ?: false - } - - override fun afterAssignNewParent() { - pwEntryV4?.afterChangeParent() - } - - override fun touch(modified: Boolean, touchParents: Boolean) { - pwEntryV3?.touch(modified, touchParents) - pwEntryV4?.touch(modified, touchParents) - } - - override fun isContainedIn(container: GroupVersioned): Boolean { - var contained: Boolean? = false - container.pwGroupV3?.let { - contained = pwEntryV3?.isContainedIn(it) - } - container.pwGroupV4?.let { - contained = pwEntryV4?.isContainedIn(it) - } - return contained ?: false - } - - override var creationTime: PwDate - get() = pwEntryV3?.creationTime ?: pwEntryV4?.creationTime ?: PwDate() - set(value) { - pwEntryV3?.creationTime = value - pwEntryV4?.creationTime = value - } - - override var lastModificationTime: PwDate - get() = pwEntryV3?.lastModificationTime ?: pwEntryV4?.lastModificationTime ?: PwDate() - set(value) { - pwEntryV3?.lastModificationTime = value - pwEntryV4?.lastModificationTime = value - } - - override var lastAccessTime: PwDate - get() = pwEntryV3?.lastAccessTime ?: pwEntryV4?.lastAccessTime ?: PwDate() - set(value) { - pwEntryV3?.lastAccessTime = value - pwEntryV4?.lastAccessTime = value - } - - override var expiryTime: PwDate - get() = pwEntryV3?.expiryTime ?: pwEntryV4?.expiryTime ?: PwDate() - set(value) { - pwEntryV3?.expiryTime = value - pwEntryV4?.expiryTime = value - } - - override var expires: Boolean - get() = pwEntryV3?.expires ?: pwEntryV4?.expires ?: false - set(value) { - pwEntryV3?.expires = value - pwEntryV4?.expires = value - } - - override val isCurrentlyExpires: Boolean - get() = pwEntryV3?.isCurrentlyExpires ?: pwEntryV4?.isCurrentlyExpires ?: false - - override var username: String - get() = pwEntryV3?.username ?: pwEntryV4?.username ?: "" - set(value) { - pwEntryV3?.username = value - pwEntryV4?.username = value - } - - override var password: String - get() = pwEntryV3?.password ?: pwEntryV4?.password ?: "" - set(value) { - pwEntryV3?.password = value - pwEntryV4?.password = value - } - - override var url: String - get() = pwEntryV3?.url ?: pwEntryV4?.url ?: "" - set(value) { - pwEntryV3?.url = value - pwEntryV4?.url = value - } - - override var notes: String - get() = pwEntryV3?.notes ?: pwEntryV4?.notes ?: "" - set(value) { - pwEntryV3?.notes = value - pwEntryV4?.notes = value - } - - private fun isTan(): Boolean { - return title == PMS_TAN_ENTRY && username.isNotEmpty() - } - - fun getVisualTitle(): String { - return getVisualTitle(isTan(), - title, - username, - url, - nodeId.toString()) - } - - /* - ------------ - V3 Methods - ------------ - */ - - /** - * If it's a node with only meta information like Meta-info SYSTEM Database Color - * @return false by default, true if it's a meta stream - */ - val isMetaStream: Boolean - get() = pwEntryV3?.isMetaStream ?: false - - /* - ------------ - V4 Methods - ------------ - */ - - var iconCustom: PwIconCustom - get() = pwEntryV4?.iconCustom ?: PwIconCustom.UNKNOWN_ICON - set(value) { - pwEntryV4?.iconCustom = value - } - - /** - * Retrieve custom fields to show, key is the label, value is the value of field (protected or not) - * @return Map of label/value - */ - val customFields: HashMap - get() = pwEntryV4?.customFields ?: HashMap() - - /** - * To redefine if version of entry allow custom field, - * @return true if entry allows custom field - */ - fun allowCustomFields(): Boolean { - return pwEntryV4?.allowCustomFields() ?: false - } - - fun removeAllFields() { - pwEntryV4?.removeAllFields() - } - - /** - * Update or add an extra field to the list (standard or custom) - * @param label Label of field, must be unique - * @param value Value of field - */ - fun putExtraField(label: String, value: ProtectedString) { - pwEntryV4?.putExtraField(label, value) - } - - fun getOtpElement(): OtpElement? { - return OtpEntryFields.parseFields { key -> - customFields[key]?.toString() - } - } - - fun startToManageFieldReferences(db: PwDatabaseV4) { - pwEntryV4?.startToManageFieldReferences(db) - } - - fun stopToManageFieldReferences() { - pwEntryV4?.stopToManageFieldReferences() - } - - fun getHistory(): ArrayList { - val history = ArrayList() - val entryV4History = pwEntryV4?.history ?: ArrayList() - for (entryHistory in entryV4History) { - history.add(EntryVersioned(entryHistory)) - } - return history - } - - fun addEntryToHistory(entry: EntryVersioned) { - entry.pwEntryV4?.let { - pwEntryV4?.addEntryToHistory(it) - } - } - - fun removeAllHistory() { - pwEntryV4?.removeAllHistory() - } - - fun removeOldestEntryFromHistory() { - pwEntryV4?.removeOldestEntryFromHistory() - } - - fun getSize(): Long { - return pwEntryV4?.size ?: 0L - } - - fun containsCustomData(): Boolean { - return pwEntryV4?.containsCustomData() ?: false - } - - /* - ------------ - Converter - ------------ - */ - - /** - * Retrieve generated entry info, - * Remove parameter fields and add auto generated elements in auto custom fields - */ - fun getEntryInfo(database: Database?, raw: Boolean = false): EntryInfo { - val entryInfo = EntryInfo() - if (raw) - database?.stopManageEntry(this) - else - database?.startManageEntry(this) - entryInfo.id = nodeId.toString() - entryInfo.title = title - entryInfo.username = username - entryInfo.password = password - entryInfo.url = url - entryInfo.notes = notes - for (entry in customFields.entries) { - entryInfo.customFields.add( - Field(entry.key, entry.value)) - } - // Add otpElement to generate token - entryInfo.otpModel = getOtpElement()?.otpModel - // Replace parameter fields by generated OTP fields - entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields) - if (!raw) - database?.stopManageEntry(this) - return entryInfo - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as EntryVersioned - - if (pwEntryV3 != other.pwEntryV3) return false - if (pwEntryV4 != other.pwEntryV4) return false - - return true - } - - override fun hashCode(): Int { - var result = pwEntryV3?.hashCode() ?: 0 - result = 31 * result + (pwEntryV4?.hashCode() ?: 0) - return result - } - - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): EntryVersioned { - return EntryVersioned(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - - const val PMS_TAN_ENTRY = "" - - /** - * {@inheritDoc} - * Get the display title from an entry,

- * [.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 - } - } - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/Group.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/Group.kt new file mode 100644 index 000000000..7e99cc989 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/Group.kt @@ -0,0 +1,358 @@ +package com.kunzisoft.keepass.database.element + +import android.os.Parcel +import android.os.Parcelable +import com.kunzisoft.keepass.database.element.group.GroupKDB +import com.kunzisoft.keepass.database.element.group.GroupKDBX +import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface +import com.kunzisoft.keepass.database.element.icon.IconImage +import com.kunzisoft.keepass.database.element.icon.IconImageStandard +import com.kunzisoft.keepass.database.element.node.* +import java.util.* +import kotlin.collections.ArrayList + +class Group : Node, GroupVersionedInterface { + + var groupKDB: GroupKDB? = null + private set + var groupKDBX: GroupKDBX? = null + private set + + fun updateWith(group: Group) { + group.groupKDB?.let { + this.groupKDB?.updateWith(it) + } + group.groupKDBX?.let { + this.groupKDBX?.updateWith(it) + } + } + + /** + * Use this constructor to copy a Group + */ + constructor(group: Group) { + if (group.groupKDB != null) { + if (this.groupKDB == null) + this.groupKDB = GroupKDB() + } + if (group.groupKDBX != null) { + if (this.groupKDBX == null) + this.groupKDBX = GroupKDBX() + } + updateWith(group) + } + + constructor(group: GroupKDB) { + this.groupKDBX = null + this.groupKDB = group + } + + constructor(group: GroupKDBX) { + this.groupKDB = null + this.groupKDBX = group + } + + constructor(parcel: Parcel) { + groupKDB = parcel.readParcelable(GroupKDB::class.java.classLoader) + groupKDBX = parcel.readParcelable(GroupKDBX::class.java.classLoader) + } + + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): Group { + return Group(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeParcelable(groupKDB, flags) + dest.writeParcelable(groupKDBX, flags) + } + + override val nodeId: NodeId<*>? + get() = groupKDBX?.nodeId ?: groupKDB?.nodeId + + override var title: String + get() = groupKDB?.title ?: groupKDBX?.title ?: "" + set(value) { + groupKDB?.title = value + groupKDBX?.title = value + } + + override var icon: IconImage + get() = groupKDB?.icon ?: groupKDBX?.icon ?: IconImageStandard() + set(value) { + groupKDB?.icon = value + groupKDBX?.icon = value + } + + override val type: Type + get() = Type.GROUP + + override var parent: Group? + get() { + groupKDB?.parent?.let { + return Group(it) + } + groupKDBX?.parent?.let { + return Group(it) + } + return null + } + set(value) { + groupKDB?.parent = value?.groupKDB + groupKDBX?.parent = value?.groupKDBX + } + + override fun containsParent(): Boolean { + return groupKDB?.containsParent() ?: groupKDBX?.containsParent() ?: false + } + + override fun afterAssignNewParent() { + groupKDB?.afterAssignNewParent() + groupKDBX?.afterAssignNewParent() + } + + fun addChildrenFrom(group: Group) { + group.groupKDB?.getChildEntries()?.forEach { entryToAdd -> + groupKDB?.addChildEntry(entryToAdd) + } + group.groupKDB?.getChildGroups()?.forEach { groupToAdd -> + groupKDB?.addChildGroup(groupToAdd) + } + + group.groupKDBX?.getChildEntries()?.forEach { entryToAdd -> + groupKDBX?.addChildEntry(entryToAdd) + } + group.groupKDBX?.getChildGroups()?.forEach { groupToAdd -> + groupKDBX?.addChildGroup(groupToAdd) + } + } + + override fun touch(modified: Boolean, touchParents: Boolean) { + groupKDB?.touch(modified, touchParents) + groupKDBX?.touch(modified, touchParents) + } + + override fun isContainedIn(container: Group): Boolean { + var contained: Boolean? = null + container.groupKDB?.let { + contained = groupKDB?.isContainedIn(it) + } + container.groupKDBX?.let { + contained = groupKDBX?.isContainedIn(it) + } + return contained ?: false + } + + override var creationTime: DateInstant + get() = groupKDB?.creationTime ?: groupKDBX?.creationTime ?: DateInstant() + set(value) { + groupKDB?.creationTime = value + groupKDBX?.creationTime = value + } + + override var lastModificationTime: DateInstant + get() = groupKDB?.lastModificationTime ?: groupKDBX?.lastModificationTime ?: DateInstant() + set(value) { + groupKDB?.lastModificationTime = value + groupKDBX?.lastModificationTime = value + } + + override var lastAccessTime: DateInstant + get() = groupKDB?.lastAccessTime ?: groupKDBX?.lastAccessTime ?: DateInstant() + set(value) { + groupKDB?.lastAccessTime = value + groupKDBX?.lastAccessTime = value + } + + override var expiryTime: DateInstant + get() = groupKDB?.expiryTime ?: groupKDBX?.expiryTime ?: DateInstant() + set(value) { + groupKDB?.expiryTime = value + groupKDBX?.expiryTime = value + } + + override var expires: Boolean + get() = groupKDB?.expires ?: groupKDBX?.expires ?: false + set(value) { + groupKDB?.expires = value + groupKDBX?.expires = value + } + + override val isCurrentlyExpires: Boolean + get() = groupKDB?.isCurrentlyExpires ?: groupKDBX?.isCurrentlyExpires ?: false + + override fun getChildGroups(): MutableList { + val children = ArrayList() + + groupKDB?.getChildGroups()?.forEach { + children.add(Group(it)) + } + groupKDBX?.getChildGroups()?.forEach { + children.add(Group(it)) + } + + return children + } + + override fun getChildEntries(): MutableList { + return getChildEntries(false) + } + + fun getChildEntries(withoutMetaStream: Boolean): MutableList { + val children = ArrayList() + + groupKDB?.getChildEntries()?.forEach { + val entryToAddAsChild = Entry(it) + if (!withoutMetaStream || (withoutMetaStream && !entryToAddAsChild.isMetaStream)) + children.add(entryToAddAsChild) + } + groupKDBX?.getChildEntries()?.forEach { + children.add(Entry(it)) + } + + return children + } + + /** + * Filter MetaStream entries and return children + * @return List of direct children (one level below) as NodeVersioned + */ + fun getChildren(withoutMetaStream: Boolean = true): List { + val children = ArrayList() + children.addAll(getChildGroups()) + + groupKDB?.let { + children.addAll(getChildEntries(withoutMetaStream)) + } + groupKDBX?.let { + // No MetasStream in V4 + children.addAll(getChildEntries(withoutMetaStream)) + } + + return children + } + + override fun addChildGroup(group: Group) { + group.groupKDB?.let { + groupKDB?.addChildGroup(it) + } + group.groupKDBX?.let { + groupKDBX?.addChildGroup(it) + } + } + + override fun addChildEntry(entry: Entry) { + entry.entryKDB?.let { + groupKDB?.addChildEntry(it) + } + entry.entryKDBX?.let { + groupKDBX?.addChildEntry(it) + } + } + + override fun removeChildGroup(group: Group) { + group.groupKDB?.let { + groupKDB?.removeChildGroup(it) + } + group.groupKDBX?.let { + groupKDBX?.removeChildGroup(it) + } + } + + override fun removeChildEntry(entry: Entry) { + entry.entryKDB?.let { + groupKDB?.removeChildEntry(it) + } + entry.entryKDBX?.let { + groupKDBX?.removeChildEntry(it) + } + } + + override fun removeChildren() { + groupKDB?.removeChildren() + groupKDBX?.removeChildren() + } + + override fun allowAddEntryIfIsRoot(): Boolean { + return groupKDB?.allowAddEntryIfIsRoot() ?: groupKDBX?.allowAddEntryIfIsRoot() ?: false + } + + /* + ------------ + KDB Methods + ------------ + */ + + var nodeIdKDB: NodeId + get() = groupKDB?.nodeId ?: NodeIdInt() + set(value) { groupKDB?.nodeId = value } + + fun setNodeId(id: NodeIdInt) { + groupKDB?.nodeId = id + } + + fun getLevel(): Int { + return groupKDB?.level ?: -1 + } + + fun setLevel(level: Int) { + groupKDB?.level = level + } + + /* + ------------ + KDBX Methods + ------------ + */ + + var nodeIdKDBX: NodeId + get() = groupKDBX?.nodeId ?: NodeIdUUID() + set(value) { groupKDBX?.nodeId = value } + + fun setNodeId(id: NodeIdUUID) { + groupKDBX?.nodeId = id + } + + fun setEnableAutoType(enableAutoType: Boolean?) { + groupKDBX?.enableAutoType = enableAutoType + } + + fun setEnableSearching(enableSearching: Boolean?) { + groupKDBX?.enableSearching = enableSearching + } + + fun setExpanded(expanded: Boolean) { + groupKDBX?.isExpanded = expanded + } + + fun containsCustomData(): Boolean { + return groupKDBX?.containsCustomData() ?: false + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Group + + if (groupKDB != other.groupKDB) return false + if (groupKDBX != other.groupKDBX) return false + + return true + } + + override fun hashCode(): Int { + var result = groupKDB?.hashCode() ?: 0 + result = 31 * result + (groupKDBX?.hashCode() ?: 0) + return result + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/GroupVersioned.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/GroupVersioned.kt deleted file mode 100644 index 5737738a4..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/GroupVersioned.kt +++ /dev/null @@ -1,363 +0,0 @@ -package com.kunzisoft.keepass.database.element - -import android.os.Parcel -import android.os.Parcelable -import java.util.* -import kotlin.collections.ArrayList - -class GroupVersioned : NodeVersioned, PwGroupInterface { - - var pwGroupV3: PwGroupV3? = null - private set - var pwGroupV4: PwGroupV4? = null - private set - - fun updateWith(group: GroupVersioned) { - group.pwGroupV3?.let { - this.pwGroupV3?.updateWith(it) - } - group.pwGroupV4?.let { - this.pwGroupV4?.updateWith(it) - } - } - - /** - * Use this constructor to copy a Group - */ - constructor(group: GroupVersioned) { - if (group.pwGroupV3 != null) { - if (this.pwGroupV3 == null) - this.pwGroupV3 = PwGroupV3() - } - if (group.pwGroupV4 != null) { - if (this.pwGroupV4 == null) - this.pwGroupV4 = PwGroupV4() - } - updateWith(group) - } - - constructor(group: PwGroupV3) { - this.pwGroupV4 = null - this.pwGroupV3 = group - } - - constructor(group: PwGroupV4) { - this.pwGroupV3 = null - this.pwGroupV4 = group - } - - constructor(parcel: Parcel) { - pwGroupV3 = parcel.readParcelable(PwGroupV3::class.java.classLoader) - pwGroupV4 = parcel.readParcelable(PwGroupV4::class.java.classLoader) - } - - companion object CREATOR : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): GroupVersioned { - return GroupVersioned(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } - - override fun describeContents(): Int { - return 0 - } - - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeParcelable(pwGroupV3, flags) - dest.writeParcelable(pwGroupV4, flags) - } - - override val nodeId: PwNodeId<*>? - get() = pwGroupV4?.nodeId ?: pwGroupV3?.nodeId - - override var title: String - get() = pwGroupV3?.title ?: pwGroupV4?.title ?: "" - set(value) { - pwGroupV3?.title = value - pwGroupV4?.title = value - } - - override var icon: PwIcon - get() = pwGroupV3?.icon ?: pwGroupV4?.icon ?: PwIconStandard() - set(value) { - pwGroupV3?.icon = value - pwGroupV4?.icon = value - } - - override val type: Type - get() = Type.GROUP - - override var parent: GroupVersioned? - get() { - pwGroupV3?.parent?.let { - return GroupVersioned(it) - } - pwGroupV4?.parent?.let { - return GroupVersioned(it) - } - return null - } - set(value) { - pwGroupV3?.parent = value?.pwGroupV3 - pwGroupV4?.parent = value?.pwGroupV4 - } - - override fun containsParent(): Boolean { - return pwGroupV3?.containsParent() ?: pwGroupV4?.containsParent() ?: false - } - - override fun afterAssignNewParent() { - pwGroupV3?.afterAssignNewParent() - pwGroupV4?.afterAssignNewParent() - } - - fun addChildrenFrom(group: GroupVersioned) { - group.pwGroupV3?.getChildEntries()?.forEach { entryToAdd -> - pwGroupV3?.addChildEntry(entryToAdd) - } - group.pwGroupV3?.getChildGroups()?.forEach { groupToAdd -> - pwGroupV3?.addChildGroup(groupToAdd) - } - - group.pwGroupV4?.getChildEntries()?.forEach { entryToAdd -> - pwGroupV4?.addChildEntry(entryToAdd) - } - group.pwGroupV4?.getChildGroups()?.forEach { groupToAdd -> - pwGroupV4?.addChildGroup(groupToAdd) - } - } - - fun removeChildren() { - pwGroupV3?.getChildEntries()?.forEach { entryToRemove -> - pwGroupV3?.removeChildEntry(entryToRemove) - } - pwGroupV3?.getChildGroups()?.forEach { groupToRemove -> - pwGroupV3?.removeChildGroup(groupToRemove) - } - - pwGroupV4?.getChildEntries()?.forEach { entryToRemove -> - pwGroupV4?.removeChildEntry(entryToRemove) - } - pwGroupV4?.getChildGroups()?.forEach { groupToRemove -> - pwGroupV4?.removeChildGroup(groupToRemove) - } - } - - override fun touch(modified: Boolean, touchParents: Boolean) { - pwGroupV3?.touch(modified, touchParents) - pwGroupV4?.touch(modified, touchParents) - } - - override fun isContainedIn(container: GroupVersioned): Boolean { - var contained: Boolean? = null - container.pwGroupV3?.let { - contained = pwGroupV3?.isContainedIn(it) - } - container.pwGroupV4?.let { - contained = pwGroupV4?.isContainedIn(it) - } - return contained ?: false - } - - override var creationTime: PwDate - get() = pwGroupV3?.creationTime ?: pwGroupV4?.creationTime ?: PwDate() - set(value) { - pwGroupV3?.creationTime = value - pwGroupV4?.creationTime = value - } - - override var lastModificationTime: PwDate - get() = pwGroupV3?.lastModificationTime ?: pwGroupV4?.lastModificationTime ?: PwDate() - set(value) { - pwGroupV3?.lastModificationTime = value - pwGroupV4?.lastModificationTime = value - } - - override var lastAccessTime: PwDate - get() = pwGroupV3?.lastAccessTime ?: pwGroupV4?.lastAccessTime ?: PwDate() - set(value) { - pwGroupV3?.lastAccessTime = value - pwGroupV4?.lastAccessTime = value - } - - override var expiryTime: PwDate - get() = pwGroupV3?.expiryTime ?: pwGroupV4?.expiryTime ?: PwDate() - set(value) { - pwGroupV3?.expiryTime = value - pwGroupV4?.expiryTime = value - } - - override var expires: Boolean - get() = pwGroupV3?.expires ?: pwGroupV4?.expires ?: false - set(value) { - pwGroupV3?.expires = value - pwGroupV4?.expires = value - } - - override val isCurrentlyExpires: Boolean - get() = pwGroupV3?.isCurrentlyExpires ?: pwGroupV4?.isCurrentlyExpires ?: false - - override fun getChildGroups(): MutableList { - val children = ArrayList() - - pwGroupV3?.getChildGroups()?.forEach { - children.add(GroupVersioned(it)) - } - pwGroupV4?.getChildGroups()?.forEach { - children.add(GroupVersioned(it)) - } - - return children - } - - override fun getChildEntries(): MutableList { - return getChildEntries(false) - } - - fun getChildEntries(withoutMetaStream: Boolean): MutableList { - val children = ArrayList() - - pwGroupV3?.getChildEntries()?.forEach { - val entryToAddAsChild = EntryVersioned(it) - if (!withoutMetaStream || (withoutMetaStream && !entryToAddAsChild.isMetaStream)) - children.add(entryToAddAsChild) - } - pwGroupV4?.getChildEntries()?.forEach { - children.add(EntryVersioned(it)) - } - - return children - } - - /** - * Filter MetaStream entries and return children - * @return List of direct children (one level below) as PwNode - */ - fun getChildren(withoutMetaStream: Boolean = true): List { - val children = ArrayList() - children.addAll(getChildGroups()) - - pwGroupV3?.let { - children.addAll(getChildEntries(withoutMetaStream)) - } - pwGroupV4?.let { - // No MetasStream in V4 - children.addAll(getChildEntries(withoutMetaStream)) - } - - return children - } - - override fun addChildGroup(group: GroupVersioned) { - group.pwGroupV3?.let { - pwGroupV3?.addChildGroup(it) - } - group.pwGroupV4?.let { - pwGroupV4?.addChildGroup(it) - } - } - - override fun addChildEntry(entry: EntryVersioned) { - entry.pwEntryV3?.let { - pwGroupV3?.addChildEntry(it) - } - entry.pwEntryV4?.let { - pwGroupV4?.addChildEntry(it) - } - } - - override fun removeChildGroup(group: GroupVersioned) { - group.pwGroupV3?.let { - pwGroupV3?.removeChildGroup(it) - } - group.pwGroupV4?.let { - pwGroupV4?.removeChildGroup(it) - } - } - - override fun removeChildEntry(entry: EntryVersioned) { - entry.pwEntryV3?.let { - pwGroupV3?.removeChildEntry(it) - } - entry.pwEntryV4?.let { - pwGroupV4?.removeChildEntry(it) - } - } - - override fun allowAddEntryIfIsRoot(): Boolean { - return pwGroupV3?.allowAddEntryIfIsRoot() ?: pwGroupV4?.allowAddEntryIfIsRoot() ?: false - } - - /* - ------------ - V3 Methods - ------------ - */ - - var nodeIdV3: PwNodeId - get() = pwGroupV3?.nodeId ?: PwNodeIdInt() - set(value) { pwGroupV3?.nodeId = value } - - fun setNodeId(id: PwNodeIdInt) { - pwGroupV3?.nodeId = id - } - - fun getLevel(): Int { - return pwGroupV3?.level ?: -1 - } - - fun setLevel(level: Int) { - pwGroupV3?.level = level - } - - /* - ------------ - V4 Methods - ------------ - */ - - var nodeIdV4: PwNodeId - get() = pwGroupV4?.nodeId ?: PwNodeIdUUID() - set(value) { pwGroupV4?.nodeId = value } - - fun setNodeId(id: PwNodeIdUUID) { - pwGroupV4?.nodeId = id - } - - fun setEnableAutoType(enableAutoType: Boolean?) { - pwGroupV4?.enableAutoType = enableAutoType - } - - fun setEnableSearching(enableSearching: Boolean?) { - pwGroupV4?.enableSearching = enableSearching - } - - fun setExpanded(expanded: Boolean) { - pwGroupV4?.isExpanded = expanded - } - - fun containsCustomData(): Boolean { - return pwGroupV4?.containsCustomData() ?: false - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as GroupVersioned - - if (pwGroupV3 != other.pwGroupV3) return false - if (pwGroupV4 != other.pwGroupV4) return false - - return true - } - - override fun hashCode(): Int { - var result = pwGroupV3?.hashCode() ?: 0 - result = 31 * result + (pwGroupV4?.hashCode() ?: 0) - return result - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDate.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/PwDate.kt deleted file mode 100644 index 1ce08df7c..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDate.kt +++ /dev/null @@ -1,275 +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 . - * - */ -package com.kunzisoft.keepass.database.element - -import android.content.res.Resources -import android.os.Parcel -import android.os.Parcelable -import androidx.core.os.ConfigurationCompat -import com.kunzisoft.keepass.utils.Types -import java.util.* - -/** - * Converting from the C Date format to the Java data format is - * expensive when done for every record at once. - */ -class PwDate : Parcelable { - - private var jDate: Date = Date() - private var jDateBuilt = false - @Transient - private var cDate: ByteArray? = null - @Transient - private var cDateBuilt = false - - val date: Date - get() { - if (!jDateBuilt) { - jDate = readTime(cDate, 0, calendar) - jDateBuilt = true - } - - return jDate - } - - val byteArrayDate: ByteArray? - get() { - if (!cDateBuilt) { - cDate = writeTime(jDate, calendar) - cDateBuilt = true - } - - return cDate - } - - constructor(buf: ByteArray, offset: Int) { - cDate = ByteArray(DATE_SIZE) - System.arraycopy(buf, offset, cDate!!, 0, DATE_SIZE) - cDateBuilt = true - } - - constructor(source: PwDate) { - this.jDate = Date(source.jDate.time) - this.jDateBuilt = source.jDateBuilt - - if (source.cDate != null) { - val dateLength = source.cDate!!.size - this.cDate = ByteArray(dateLength) - System.arraycopy(source.cDate!!, 0, this.cDate!!, 0, dateLength) - } - this.cDateBuilt = source.cDateBuilt - } - - constructor(date: Date) { - jDate = Date(date.time) - jDateBuilt = true - } - - constructor(millis: Long) { - jDate = Date(millis) - jDateBuilt = true - } - - constructor() { - jDate = Date() - jDateBuilt = true - } - - protected constructor(parcel: Parcel) { - jDate = parcel.readSerializable() as Date - jDateBuilt = parcel.readByte().toInt() != 0 - cDateBuilt = false - } - - override fun describeContents(): Int { - return 0 - } - - fun getDateTimeString(resources: Resources): String { - return Companion.getDateTimeString(resources, this.date) - } - - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeSerializable(date) - dest.writeByte((if (jDateBuilt) 1 else 0).toByte()) - } - - override fun equals(other: Any?): Boolean { - if (this === other) { - return true - } - if (other == null) { - return false - } - if (javaClass != other.javaClass) { - return false - } - - val date = other as PwDate? - return if (cDateBuilt && date!!.cDateBuilt) { - Arrays.equals(cDate, date.cDate) - } else if (jDateBuilt && date!!.jDateBuilt) { - isSameDate(jDate, date.jDate) - } else if (cDateBuilt && date!!.jDateBuilt) { - Arrays.equals(date.byteArrayDate, cDate) - } else { - isSameDate(date!!.date, jDate) - } - } - - override fun hashCode(): Int { - var result = jDate.hashCode() - result = 31 * result + jDateBuilt.hashCode() - result = 31 * result + (cDate?.contentHashCode() ?: 0) - result = 31 * result + cDateBuilt.hashCode() - return result - } - - companion object { - - private const val DATE_SIZE = 5 - - private var mCalendar: Calendar? = null - - val NEVER_EXPIRE = neverExpire - - private val calendar: Calendar? - get() { - if (mCalendar == null) { - mCalendar = Calendar.getInstance() - } - return mCalendar - } - - private val neverExpire: PwDate - get() { - val cal = Calendar.getInstance() - cal.set(Calendar.YEAR, 2999) - cal.set(Calendar.MONTH, 11) - cal.set(Calendar.DAY_OF_MONTH, 28) - cal.set(Calendar.HOUR, 23) - cal.set(Calendar.MINUTE, 59) - cal.set(Calendar.SECOND, 59) - - return PwDate(cal.time) - } - - @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): PwDate { - return PwDate(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } - - /** - * Unpack date from 5 byte format. The five bytes at 'offset' are unpacked - * to a java.util.Date instance. - */ - fun readTime(buf: ByteArray?, offset: Int, calendar: Calendar?): Date { - var time = calendar - val dw1 = Types.readUByte(buf!!, offset) - val dw2 = Types.readUByte(buf, offset + 1) - val dw3 = Types.readUByte(buf, offset + 2) - val dw4 = Types.readUByte(buf, offset + 3) - val dw5 = Types.readUByte(buf, offset + 4) - - // Unpack 5 byte structure to date and time - val year = dw1 shl 6 or (dw2 shr 2) - val month = dw2 and 0x00000003 shl 2 or (dw3 shr 6) - - val day = dw3 shr 1 and 0x0000001F - val hour = dw3 and 0x00000001 shl 4 or (dw4 shr 4) - val minute = dw4 and 0x0000000F shl 2 or (dw5 shr 6) - val second = dw5 and 0x0000003F - - if (time == null) { - time = Calendar.getInstance() - } - // File format is a 1 based month, java Calendar uses a zero based month - // File format is a 1 based day, java Calendar uses a 1 based day - time!!.set(year, month - 1, day, hour, minute, second) - - return time.time - - } - - @JvmOverloads - fun writeTime(date: Date?, calendar: Calendar? = null): ByteArray? { - var cal = calendar - if (date == null) { - return null - } - - val buf = ByteArray(5) - if (cal == null) { - cal = Calendar.getInstance() - } - cal!!.time = date - - val year = cal.get(Calendar.YEAR) - // File format is a 1 based month, java Calendar uses a zero based month - val month = cal.get(Calendar.MONTH) + 1 - // File format is a 0 based day, java Calendar uses a 1 based day - val day = cal.get(Calendar.DAY_OF_MONTH) - 1 - val hour = cal.get(Calendar.HOUR_OF_DAY) - val minute = cal.get(Calendar.MINUTE) - val second = cal.get(Calendar.SECOND) - - buf[0] = Types.writeUByte(year shr 6 and 0x0000003F) - buf[1] = Types.writeUByte(year and 0x0000003F shl 2 or (month shr 2 and 0x00000003)) - buf[2] = (month and 0x00000003 shl 6 - or (day and 0x0000001F shl 1) or (hour shr 4 and 0x00000001)).toByte() - buf[3] = (hour and 0x0000000F shl 4 or (minute shr 2 and 0x0000000F)).toByte() - buf[4] = (minute and 0x00000003 shl 6 or (second and 0x0000003F)).toByte() - - return buf - } - - private fun isSameDate(d1: Date?, d2: Date?): Boolean { - val cal1 = Calendar.getInstance() - cal1.time = d1 - cal1.set(Calendar.MILLISECOND, 0) - - val cal2 = Calendar.getInstance() - cal2.time = d2 - cal2.set(Calendar.MILLISECOND, 0) - - return cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && - cal1.get(Calendar.MONTH) == cal2.get(Calendar.MONTH) && - cal1.get(Calendar.DAY_OF_MONTH) == cal2.get(Calendar.DAY_OF_MONTH) && - cal1.get(Calendar.HOUR) == cal2.get(Calendar.HOUR) && - cal1.get(Calendar.MINUTE) == cal2.get(Calendar.MINUTE) && - cal1.get(Calendar.SECOND) == cal2.get(Calendar.SECOND) - - } - - fun getDateTimeString(resources: Resources, date: Date): String { - return java.text.DateFormat.getDateTimeInstance( - java.text.DateFormat.MEDIUM, - java.text.DateFormat.MEDIUM, - ConfigurationCompat.getLocales(resources.configuration)[0]) - .format(date) - } - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntry.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntry.kt deleted file mode 100644 index e5a1067ae..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntry.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.kunzisoft.keepass.database.element - -import android.os.Parcel -import java.util.* - -abstract class PwEntry - < - GroupId, - EntryId, - ParentGroup: PwGroup, - Entry: PwEntry - > - : PwNode, PwEntryInterface { - - constructor() : super() - - constructor(parcel: Parcel) : super(parcel) - -} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryInterface.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryInterface.kt deleted file mode 100644 index ba9ea4012..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryInterface.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.kunzisoft.keepass.database.element - -interface PwEntryInterface : PwNodeInterface { - - var username: String - - var password: String - - var url: String - - var notes: String -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/SortNodeEnum.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/SortNodeEnum.kt similarity index 77% rename from app/src/main/java/com/kunzisoft/keepass/database/SortNodeEnum.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/SortNodeEnum.kt index 7caddbe8e..8eb4643ef 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/SortNodeEnum.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/SortNodeEnum.kt @@ -18,18 +18,16 @@ * */ -package com.kunzisoft.keepass.database +package com.kunzisoft.keepass.database.element -import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.element.EntryVersioned -import com.kunzisoft.keepass.database.element.NodeVersioned -import com.kunzisoft.keepass.database.element.Type +import com.kunzisoft.keepass.database.element.node.Node +import com.kunzisoft.keepass.database.element.node.Type import java.util.* enum class SortNodeEnum { DB, TITLE, USERNAME, CREATION_TIME, LAST_MODIFY_TIME, LAST_ACCESS_TIME; - fun getNodeComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean): Comparator { + fun getNodeComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean): Comparator { return when (this) { DB -> NodeNaturalComparator(ascending, groupsBefore, false) // Force false because natural order contains recycle bin TITLE -> NodeTitleComparator(ascending, groupsBefore, recycleBinBottom) @@ -40,11 +38,11 @@ enum class SortNodeEnum { } } - abstract class NodeComparator(var ascending: Boolean, var groupsBefore: Boolean, var recycleBinBottom: Boolean) : Comparator { + abstract class NodeComparator(var ascending: Boolean, var groupsBefore: Boolean, var recycleBinBottom: Boolean) : Comparator { - abstract fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int + abstract fun compareBySpecificOrder(object1: Node, object2: Node): Int - private fun specificOrderOrHashIfEquals(object1: NodeVersioned, object2: NodeVersioned): Int { + private fun specificOrderOrHashIfEquals(object1: Node, object2: Node): Int { val specificOrderComp = compareBySpecificOrder(object1, object2) return if (specificOrderComp == 0) { @@ -52,20 +50,20 @@ enum class SortNodeEnum { } else if (!ascending) -specificOrderComp else specificOrderComp // If descending, revert } - override fun compare(object1: NodeVersioned,object2: NodeVersioned): Int { + override fun compare(object1: Node, object2: Node): Int { if (object1 == object2) return 0 if (object1.type == Type.GROUP) { return if (object2.type == Type.GROUP) { // RecycleBin at end of groups - if (recycleBinBottom) { - if (Database.getInstance().recycleBin == object1) + val database = Database.getInstance() + if (database.isRecycleBinEnabled && recycleBinBottom) { + if (database.recycleBin == object1) return 1 - if (Database.getInstance().recycleBin == object2) + if (database.recycleBin == object2) return -1 } - specificOrderOrHashIfEquals(object1, object2) } else if (object2.type == Type.ENTRY) { if (groupsBefore) @@ -99,7 +97,7 @@ enum class SortNodeEnum { class NodeNaturalComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) : NodeComparator(ascending, groupsBefore, recycleBinBottom) { - override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int { + override fun compareBySpecificOrder(object1: Node, object2: Node): Int { return object1.nodePositionInParent.compareTo(object2.nodePositionInParent) } } @@ -110,7 +108,7 @@ enum class SortNodeEnum { class NodeTitleComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) : NodeComparator(ascending, groupsBefore, recycleBinBottom) { - override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int { + override fun compareBySpecificOrder(object1: Node, object2: Node): Int { return object1.title.compareTo(object2.title, ignoreCase = true) } } @@ -121,11 +119,11 @@ enum class SortNodeEnum { class NodeUsernameComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) : NodeComparator(ascending, groupsBefore, recycleBinBottom) { - override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int { + override fun compareBySpecificOrder(object1: Node, object2: Node): Int { if (object1.type == Type.ENTRY && object2.type == Type.ENTRY) { // To get username if it's a ref - return (object1 as EntryVersioned).getEntryInfo(Database.getInstance()).username - .compareTo((object2 as EntryVersioned).getEntryInfo(Database.getInstance()).username, + return (object1 as Entry).getEntryInfo(Database.getInstance()).username + .compareTo((object2 as Entry).getEntryInfo(Database.getInstance()).username, ignoreCase = true) } return NodeTitleComparator(ascending, groupsBefore, recycleBinBottom).compare(object1, object2) @@ -138,7 +136,7 @@ enum class SortNodeEnum { class NodeCreationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) : NodeComparator(ascending, groupsBefore, recycleBinBottom) { - override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int { + override fun compareBySpecificOrder(object1: Node, object2: Node): Int { return object1.creationTime.date .compareTo(object2.creationTime.date) } @@ -150,7 +148,7 @@ enum class SortNodeEnum { class NodeLastModificationComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) : NodeComparator(ascending, groupsBefore, recycleBinBottom) { - override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int { + override fun compareBySpecificOrder(object1: Node, object2: Node): Int { return object1.lastModificationTime.date .compareTo(object2.lastModificationTime.date) } @@ -162,7 +160,7 @@ enum class SortNodeEnum { class NodeLastAccessComparator(ascending: Boolean, groupsBefore: Boolean, recycleBinBottom: Boolean) : NodeComparator(ascending, groupsBefore, recycleBinBottom) { - override fun compareBySpecificOrder(object1: NodeVersioned, object2: NodeVersioned): Int { + override fun compareBySpecificOrder(object1: Node, object2: Node): Int { return object1.lastAccessTime.date .compareTo(object2.lastAccessTime.date) } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/BinaryPool.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryPool.kt similarity index 68% rename from app/src/main/java/com/kunzisoft/keepass/database/element/BinaryPool.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryPool.kt index d8dd88a42..4fcfe71e4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/BinaryPool.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/BinaryPool.kt @@ -17,28 +17,30 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.database import android.util.SparseArray -import com.kunzisoft.keepass.database.element.security.ProtectedBinary +import com.kunzisoft.keepass.database.element.security.BinaryAttachment +import java.io.IOException class BinaryPool { - private val pool = SparseArray() + private val pool = SparseArray() - operator fun get(key: Int): ProtectedBinary? { + operator fun get(key: Int): BinaryAttachment? { return pool[key] } - fun put(key: Int, value: ProtectedBinary) { + fun put(key: Int, value: BinaryAttachment) { pool.put(key, value) } - fun doForEachBinary(action: (key: Int, binary: ProtectedBinary) -> Unit) { + fun doForEachBinary(action: (key: Int, binary: BinaryAttachment) -> Unit) { for (i in 0 until pool.size()) { action.invoke(i, pool.get(pool.keyAt(i))) } } + @Throws(IOException::class) fun clear() { doForEachBinary { _, binary -> binary.clear() @@ -46,9 +48,10 @@ class BinaryPool { pool.clear() } - fun add(protectedBinary: ProtectedBinary) { - if (findKey(protectedBinary) != -1) return - pool.put(findUnusedKey(), protectedBinary) + fun add(fileBinary: BinaryAttachment) { + if (findKey(fileBinary) == null) { + pool.put(findUnusedKey(), fileBinary) + } } fun findUnusedKey(): Int { @@ -58,10 +61,10 @@ class BinaryPool { return unusedKey } - fun findKey(pb: ProtectedBinary): Int { + fun findKey(pb: BinaryAttachment): Int? { for (i in 0 until pool.size()) { if (pool.get(pool.keyAt(i)) == pb) return i } - return -1 + return null } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwCompressionAlgorithm.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/CompressionAlgorithm.kt similarity index 62% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwCompressionAlgorithm.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/database/CompressionAlgorithm.kt index c4bd02208..cefc27891 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwCompressionAlgorithm.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/CompressionAlgorithm.kt @@ -17,20 +17,33 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.database import android.content.res.Resources +import android.os.Parcel +import android.os.Parcelable import com.kunzisoft.keepass.R import com.kunzisoft.keepass.utils.ObjectNameResource +import com.kunzisoft.keepass.utils.readEnum +import com.kunzisoft.keepass.utils.writeEnum + // Note: We can get away with using int's to store unsigned 32-bit ints // since we won't do arithmetic on these values (also unlikely to // reach negative ids). -enum class PwCompressionAlgorithm : ObjectNameResource { +enum class CompressionAlgorithm : ObjectNameResource, Parcelable { None, GZip; + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeEnum(this) + } + + override fun describeContents(): Int { + return 0 + } + override fun getName(resources: Resources): String { return when (this) { None -> resources.getString(R.string.compression_none) @@ -38,4 +51,14 @@ enum class PwCompressionAlgorithm : ObjectNameResource { } } + companion object CREATOR : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): CompressionAlgorithm { + return parcel.readEnum() ?: None + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt similarity index 55% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt index 2af4ff0f3..e24cc0ef6 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDB.kt @@ -17,11 +17,17 @@ * along with KeePass DX. If not, see . */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.database import com.kunzisoft.keepass.crypto.finalkey.FinalKeyFactory import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine 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.group.GroupKDB +import com.kunzisoft.keepass.database.element.node.NodeIdInt +import com.kunzisoft.keepass.database.element.node.NodeIdUUID +import com.kunzisoft.keepass.database.element.node.NodeVersioned import com.kunzisoft.keepass.stream.NullOutputStream import java.io.IOException import java.io.InputStream @@ -31,10 +37,12 @@ import java.security.NoSuchAlgorithmException import java.util.* import kotlin.collections.ArrayList -class PwDatabaseV3 : PwDatabase() { +class DatabaseKDB : DatabaseVersioned() { private var numKeyEncRounds: Int = 0 + var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID + private var kdfListV3: MutableList = ArrayList() override val version: String @@ -44,22 +52,32 @@ class PwDatabaseV3 : PwDatabase() { kdfListV3.add(KdfFactory.aesKdf) } + private fun getGroupById(groupId: Int): GroupKDB? { + if (groupId == -1) + return null + return getGroupById(NodeIdInt(groupId)) + } + + // Retrieve backup group in index + val backupGroup: GroupKDB? + get() = if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID) null else getGroupById(backupGroupId) + override val kdfEngine: KdfEngine? get() = kdfListV3[0] override val kdfAvailableList: List get() = kdfListV3 - override val availableEncryptionAlgorithms: List + override val availableEncryptionAlgorithms: List get() { - val list = ArrayList() - list.add(PwEncryptionAlgorithm.AESRijndael) + val list = ArrayList() + list.add(EncryptionAlgorithm.AESRijndael) return list } - val rootGroups: List + val rootGroups: List get() { - val kids = ArrayList() + val kids = ArrayList() doForEachGroupInIndex { group -> if (group.level == 0) kids.add(group) @@ -81,7 +99,7 @@ class PwDatabaseV3 : PwDatabase() { } init { - algorithm = PwEncryptionAlgorithm.AESRijndael + algorithm = EncryptionAlgorithm.AESRijndael numKeyEncRounds = DEFAULT_ENCRYPTION_ROUNDS } @@ -90,10 +108,10 @@ class PwDatabaseV3 : PwDatabase() { * * @return new tree id */ - override fun newGroupId(): PwNodeIdInt { - var newId: PwNodeIdInt + override fun newGroupId(): NodeIdInt { + var newId: NodeIdInt do { - newId = PwNodeIdInt() + newId = NodeIdInt() } while (isGroupIdUsed(newId)) return newId @@ -104,10 +122,10 @@ class PwDatabaseV3 : PwDatabase() { * * @return new tree id */ - override fun newEntryId(): PwNodeIdUUID { - var newId: PwNodeIdUUID + override fun newEntryId(): NodeIdUUID { + var newId: NodeIdUUID do { - newId = PwNodeIdUUID() + newId = NodeIdUUID() } while (isEntryIdUsed(newId)) return newId @@ -152,12 +170,12 @@ class PwDatabaseV3 : PwDatabase() { return null } - override fun createGroup(): PwGroupV3 { - return PwGroupV3() + override fun createGroup(): GroupKDB { + return GroupKDB() } - override fun createEntry(): PwEntryV3 { - return PwEntryV3() + override fun createEntry(): EntryKDB { + return EntryKDB() } override fun rootCanContainsEntry(): Boolean { @@ -168,10 +186,16 @@ class PwDatabaseV3 : PwDatabase() { return false } - override fun isBackup(group: PwGroupV3): Boolean { - var currentGroup: PwGroupV3? = group + override fun isInRecycleBin(group: GroupKDB): Boolean { + var currentGroup: GroupKDB? = group + + if (currentGroup == backupGroup) + return true + while (currentGroup != null) { - if (currentGroup.level == 0 && currentGroup.title.equals("Backup", ignoreCase = true)) { + if (currentGroup.level == 0 + && currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) { + backupGroupId = currentGroup.id return true } currentGroup = currentGroup.parent @@ -179,8 +203,68 @@ class PwDatabaseV3 : PwDatabase() { return false } + /** + * Ensure that the recycle bin tree exists, if enabled and create it + * if it doesn't exist + */ + fun ensureRecycleBinExists() { + rootGroups.forEach { currentGroup -> + if (currentGroup.level == 0 + && currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) { + backupGroupId = currentGroup.id + } + } + + if (backupGroup == null) { + // Create recycle bin + val recycleBinGroup = createGroup().apply { + title = BACKUP_FOLDER_TITLE + icon = iconFactory.trashIcon + } + addGroupTo(recycleBinGroup, rootGroup) + backupGroupId = recycleBinGroup.id + } + } + + /** + * Define if a Node must be delete or recycle when remove action is called + * @param node Node to remove + * @return true if node can be recycle, false elsewhere + */ + fun canRecycle(node: NodeVersioned<*, GroupKDB, EntryKDB>): Boolean { + // TODO #394 Backup pw3 + return true + } + + fun recycle(group: GroupKDB) { + ensureRecycleBinExists() + removeGroupFrom(group, group.parent) + addGroupTo(group, backupGroup) + group.afterAssignNewParent() + } + + fun recycle(entry: EntryKDB) { + ensureRecycleBinExists() + removeEntryFrom(entry, entry.parent) + addEntryTo(entry, backupGroup) + entry.afterAssignNewParent() + } + + fun undoRecycle(group: GroupKDB, origParent: GroupKDB) { + removeGroupFrom(group, backupGroup) + addGroupTo(group, origParent) + } + + fun undoRecycle(entry: EntryKDB, origParent: GroupKDB) { + removeEntryFrom(entry, backupGroup) + addEntryTo(entry, origParent) + } + companion object { + const val BACKUP_FOLDER_TITLE = "Backup" + private const val BACKUP_FOLDER_UNDEFINED_ID = -1 + private const val DEFAULT_ENCRYPTION_ROUNDS = 300 /** diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt similarity index 65% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt index 31b11f8e1..4d4b6f084 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseKDBX.kt @@ -17,17 +17,30 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.database import android.content.res.Resources +import android.util.Base64 import android.util.Log -import biz.source_code.base64Coder.Base64Coder import com.kunzisoft.keepass.R import com.kunzisoft.keepass.crypto.CryptoUtil import com.kunzisoft.keepass.crypto.engine.AesEngine import com.kunzisoft.keepass.crypto.engine.CipherEngine -import com.kunzisoft.keepass.crypto.keyDerivation.* +import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine +import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory +import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters +import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE +import com.kunzisoft.keepass.database.element.entry.EntryKDBX +import com.kunzisoft.keepass.database.element.group.GroupKDBX +import com.kunzisoft.keepass.database.element.icon.IconImageCustom +import com.kunzisoft.keepass.database.element.node.NodeIdUUID +import com.kunzisoft.keepass.database.element.node.NodeVersioned +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm +import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig import com.kunzisoft.keepass.database.exception.UnknownKDF +import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3 +import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4 import com.kunzisoft.keepass.utils.VariantDictionary import org.w3c.dom.Node import org.w3c.dom.Text @@ -41,29 +54,30 @@ import javax.xml.parsers.DocumentBuilderFactory import javax.xml.parsers.ParserConfigurationException -class PwDatabaseV4 : PwDatabase { +class DatabaseKDBX : DatabaseVersioned { var hmacKey: ByteArray? = null private set var dataCipher = AesEngine.CIPHER_UUID private var dataEngine: CipherEngine = AesEngine() - var compressionAlgorithm = PwCompressionAlgorithm.GZip + var compressionAlgorithm = CompressionAlgorithm.GZip var kdfParameters: KdfParameters? = null - private var kdfV4List: MutableList = ArrayList() + private var kdfList: MutableList = ArrayList() private var numKeyEncRounds: Long = 0 var publicCustomData = VariantDictionary() - var name = "KeePass DX database" - var nameChanged = PwDate() + var kdbxVersion: Long = 0 + var name = "" + var nameChanged = DateInstant() // TODO change setting date - var settingsChanged = PwDate() + var settingsChanged = DateInstant() var description = "" - var descriptionChanged = PwDate() + var descriptionChanged = DateInstant() var defaultUserName = "" - var defaultUserNameChanged = PwDate() + var defaultUserNameChanged = DateInstant() // TODO date - var keyLastChanged = PwDate() + var keyLastChanged = DateInstant() var keyChangeRecDays: Long = -1 var keyChangeForceDays: Long = 1 var isKeyChangeForceOnce = false @@ -78,57 +92,68 @@ class PwDatabaseV4 : PwDatabase { var recycleBinUUID: UUID = UUID_ZERO var recycleBinChanged = Date() var entryTemplatesGroup = UUID_ZERO - var entryTemplatesGroupChanged = PwDate() + var entryTemplatesGroupChanged = DateInstant() var historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS var historyMaxSize = DEFAULT_HISTORY_MAX_SIZE var lastSelectedGroupUUID = UUID_ZERO var lastTopVisibleGroupUUID = UUID_ZERO var memoryProtection = MemoryProtectionConfig() - val deletedObjects = ArrayList() - val customIcons = ArrayList() + val deletedObjects = ArrayList() + val customIcons = ArrayList() val customData = HashMap() - var binPool = BinaryPool() + var binaryPool = BinaryPool() - var localizedAppName = "KeePassDX" // TODO resource + var localizedAppName = "KeePassDX" init { - kdfV4List.add(KdfFactory.aesKdf) - kdfV4List.add(KdfFactory.argon2Kdf) + kdfList.add(KdfFactory.aesKdf) + kdfList.add(KdfFactory.argon2Kdf) } constructor() - constructor(databaseName: String) { - val groupV4 = createGroup().apply { - title = databaseName + /** + * Create a new database with a root group + */ + constructor(databaseName: String, rootName: String) { + name = databaseName + val group = createGroup().apply { + title = rootName icon = iconFactory.folderIcon } - rootGroup = groupV4 - addGroupIndex(groupV4) + rootGroup = group + addGroupIndex(group) } override val version: String - get() = "KeePass 2" + get() { + val kdbxStringVersion = when(kdbxVersion) { + FILE_VERSION_32_3 -> "3.1" + FILE_VERSION_32_4 -> "4.0" + else -> "UNKNOWN" + } + return "KeePass 2 - KDBX$kdbxStringVersion" + } override val kdfEngine: KdfEngine? get() = try { - getEngineV4(kdfParameters) + getEngineKDBX4(kdfParameters) } catch (unknownKDF: UnknownKDF) { Log.i(TAG, "Unable to retrieve KDF engine", unknownKDF) null } override val kdfAvailableList: List - get() = kdfV4List + get() = kdfList @Throws(UnknownKDF::class) - fun getEngineV4(kdfParameters: KdfParameters?): KdfEngine { + fun getEngineKDBX4(kdfParameters: KdfParameters?): KdfEngine { val unknownKDFException = UnknownKDF() if (kdfParameters == null) { throw unknownKDFException } - for (engine in kdfV4List) { + for (engine in kdfList) { if (engine.uuid == kdfParameters.uuid) { return engine } @@ -136,20 +161,53 @@ class PwDatabaseV4 : PwDatabase { throw unknownKDFException } - val availableCompressionAlgorithms: List + val availableCompressionAlgorithms: List get() { - val list = ArrayList() - list.add(PwCompressionAlgorithm.None) - list.add(PwCompressionAlgorithm.GZip) + val list = ArrayList() + list.add(CompressionAlgorithm.None) + list.add(CompressionAlgorithm.GZip) return list } - override val availableEncryptionAlgorithms: List + fun changeBinaryCompression(oldCompression: CompressionAlgorithm, + newCompression: CompressionAlgorithm) { + binaryPool.doForEachBinary { key, binary -> + + try { + when (oldCompression) { + CompressionAlgorithm.None -> { + when (newCompression) { + CompressionAlgorithm.None -> { + } + CompressionAlgorithm.GZip -> { + // To compress, create a new binary with file + binary.compress() + } + } + } + CompressionAlgorithm.GZip -> { + when (newCompression) { + CompressionAlgorithm.None -> { + // To decompress, create a new binary with file + binary.decompress() + } + CompressionAlgorithm.GZip -> { + } + } + } + } + } catch (e: Exception) { + Log.e(TAG, "Unable to change compression for $key") + } + } + } + + override val availableEncryptionAlgorithms: List get() { - val list = ArrayList() - list.add(PwEncryptionAlgorithm.AESRijndael) - list.add(PwEncryptionAlgorithm.Twofish) - list.add(PwEncryptionAlgorithm.ChaCha20) + val list = ArrayList() + list.add(EncryptionAlgorithm.AESRijndael) + list.add(EncryptionAlgorithm.Twofish) + list.add(EncryptionAlgorithm.ChaCha20) return list } @@ -197,31 +255,31 @@ class PwDatabaseV4 : PwDatabase { override val passwordEncoding: String get() = "UTF-8" - fun getGroupByUUID(groupUUID: UUID): PwGroupV4? { + private fun getGroupByUUID(groupUUID: UUID): GroupKDBX? { if (groupUUID == UUID_ZERO) return null - return getGroupById(PwNodeIdUUID(groupUUID)) + return getGroupById(NodeIdUUID(groupUUID)) } // Retrieve recycle bin in index - val recycleBin: PwGroupV4? - get() = getGroupByUUID(recycleBinUUID) + val recycleBin: GroupKDBX? + get() = if (recycleBinUUID == UUID_ZERO) null else getGroupByUUID(recycleBinUUID) - val lastSelectedGroup: PwGroupV4? + val lastSelectedGroup: GroupKDBX? get() = getGroupByUUID(lastSelectedGroupUUID) - val lastTopVisibleGroup: PwGroupV4? + val lastTopVisibleGroup: GroupKDBX? get() = getGroupByUUID(lastTopVisibleGroupUUID) fun setDataEngine(dataEngine: CipherEngine) { this.dataEngine = dataEngine } - fun getCustomIcons(): List { + fun getCustomIcons(): List { return customIcons } - fun addCustomIcon(customIcon: PwIconCustom) { + fun addCustomIcon(customIcon: IconImageCustom) { this.customIcons.add(customIcon) } @@ -264,7 +322,7 @@ class PwDatabaseV4 : PwDatabase { fun makeFinalKey(masterSeed: ByteArray) { kdfParameters?.let { keyDerivationFunctionParameters -> - val kdfEngine = getEngineV4(keyDerivationFunctionParameters) + val kdfEngine = getEngineKDBX4(keyDerivationFunctionParameters) var transformedMasterKey = kdfEngine.transform(masterKey, keyDerivationFunctionParameters) if (transformedMasterKey.size != 32) { @@ -326,7 +384,7 @@ class PwDatabaseV4 : PwDatabase { val text = children2.item(k) if (text.nodeType == Node.TEXT_NODE) { val txt = text as Text - return Base64Coder.decode(txt.nodeValue) + return Base64.decode(txt.nodeValue, BASE_64_FLAG) } } } @@ -340,42 +398,42 @@ class PwDatabaseV4 : PwDatabase { return null } - override fun newGroupId(): PwNodeIdUUID { - var newId: PwNodeIdUUID + override fun newGroupId(): NodeIdUUID { + var newId: NodeIdUUID do { - newId = PwNodeIdUUID() + newId = NodeIdUUID() } while (isGroupIdUsed(newId)) return newId } - override fun newEntryId(): PwNodeIdUUID { - var newId: PwNodeIdUUID + override fun newEntryId(): NodeIdUUID { + var newId: NodeIdUUID do { - newId = PwNodeIdUUID() + newId = NodeIdUUID() } while (isEntryIdUsed(newId)) return newId } - override fun createGroup(): PwGroupV4 { - return PwGroupV4() + override fun createGroup(): GroupKDBX { + return GroupKDBX() } - override fun createEntry(): PwEntryV4 { - return PwEntryV4() + override fun createEntry(): EntryKDBX { + return EntryKDBX() } override fun rootCanContainsEntry(): Boolean { return true } - override fun isBackup(group: PwGroupV4): Boolean { + override fun isInRecycleBin(group: GroupKDBX): Boolean { // To keep compatibility with old V1 databases - var currentGroup: PwGroupV4? = group + var currentGroup: GroupKDBX? = group while (currentGroup != null) { if (currentGroup.parent == rootGroup - && currentGroup.title.equals("Backup", ignoreCase = true)) { + && currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) { return true } currentGroup = currentGroup.parent @@ -393,7 +451,7 @@ class PwDatabaseV4 : PwDatabase { * Ensure that the recycle bin tree exists, if enabled and create it * if it doesn't exist */ - private fun ensureRecycleBin(resources: Resources) { + fun ensureRecycleBinExists(resources: Resources) { if (recycleBin == null) { // Create recycle bin val recycleBinGroup = createGroup().apply { @@ -409,61 +467,68 @@ class PwDatabaseV4 : PwDatabase { } } + fun removeRecycleBin() { + if (recycleBin != null) { + recycleBinUUID = UUID_ZERO + recycleBinChanged = DateInstant().date + } + } + /** * Define if a Node must be delete or recycle when remove action is called * @param node Node to remove * @return true if node can be recycle, false elsewhere */ - fun canRecycle(node: PwNode<*, PwGroupV4, PwEntryV4>): Boolean { + fun canRecycle(node: NodeVersioned<*, GroupKDBX, EntryKDBX>): Boolean { if (!isRecycleBinEnabled) return false if (recycleBin == null) - return true + return false if (!node.isContainedIn(recycleBin!!)) return true return false } - fun recycle(group: PwGroupV4, resources: Resources) { - ensureRecycleBin(resources) + fun recycle(group: GroupKDBX, resources: Resources) { + ensureRecycleBinExists(resources) removeGroupFrom(group, group.parent) addGroupTo(group, recycleBin) group.afterAssignNewParent() } - fun recycle(entry: PwEntryV4, resources: Resources) { - ensureRecycleBin(resources) + fun recycle(entry: EntryKDBX, resources: Resources) { + ensureRecycleBinExists(resources) removeEntryFrom(entry, entry.parent) addEntryTo(entry, recycleBin) entry.afterAssignNewParent() } - fun undoRecycle(group: PwGroupV4, origParent: PwGroupV4) { + fun undoRecycle(group: GroupKDBX, origParent: GroupKDBX) { removeGroupFrom(group, recycleBin) addGroupTo(group, origParent) } - fun undoRecycle(entry: PwEntryV4, origParent: PwGroupV4) { + fun undoRecycle(entry: EntryKDBX, origParent: GroupKDBX) { removeEntryFrom(entry, recycleBin) addEntryTo(entry, origParent) } - fun getDeletedObjects(): List { + fun getDeletedObjects(): List { return deletedObjects } - fun addDeletedObject(deletedObject: PwDeletedObject) { + fun addDeletedObject(deletedObject: DeletedObject) { this.deletedObjects.add(deletedObject) } - override fun removeEntryFrom(entryToRemove: PwEntryV4, parent: PwGroupV4?) { + override fun removeEntryFrom(entryToRemove: EntryKDBX, parent: GroupKDBX?) { super.removeEntryFrom(entryToRemove, parent) - deletedObjects.add(PwDeletedObject(entryToRemove.id)) + deletedObjects.add(DeletedObject(entryToRemove.id)) } - override fun undoDeleteEntryFrom(entry: PwEntryV4, origParent: PwGroupV4?) { + override fun undoDeleteEntryFrom(entry: EntryKDBX, origParent: GroupKDBX?) { super.undoDeleteEntryFrom(entry, origParent) - deletedObjects.remove(PwDeletedObject(entry.id)) + deletedObjects.remove(DeletedObject(entry.id)) } fun containsPublicCustomData(): Boolean { @@ -477,12 +542,16 @@ class PwDatabaseV4 : PwDatabase { } override fun clearCache() { - super.clearCache() - binPool.clear() + try { + super.clearCache() + binaryPool.clear() + } catch (e: Exception) { + Log.e(TAG, "Unable to clear cache", e) + } } companion object { - private val TAG = PwDatabaseV4::class.java.name + private val TAG = DatabaseKDBX::class.java.name private const val DEFAULT_HISTORY_MAX_ITEMS = 10 // -1 unlimited private const val DEFAULT_HISTORY_MAX_SIZE = (6 * 1024 * 1024).toLong() // -1 unlimited @@ -492,5 +561,9 @@ class PwDatabaseV4 : PwDatabase { //private const val VersionElementName = "Version"; private const val KeyElementName = "Key" private const val KeyDataElementName = "Data" + + const val BASE_64_FLAG = Base64.DEFAULT + + const val BUFFER_SIZE_BYTES = 3 * 128 } } \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabase.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt similarity index 83% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabase.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt index d15b1fe41..463aa82ec 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabase.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/database/DatabaseVersioned.kt @@ -17,26 +17,32 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.database import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine -import com.kunzisoft.keepass.database.exception.LoadDatabaseDuplicateUuidException -import com.kunzisoft.keepass.database.exception.LoadDatabaseKeyFileEmptyException -import com.kunzisoft.keepass.utils.MemoryUtil +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm +import com.kunzisoft.keepass.database.element.entry.EntryVersioned +import com.kunzisoft.keepass.database.element.group.GroupVersioned +import com.kunzisoft.keepass.database.element.icon.IconImageFactory +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.Type +import com.kunzisoft.keepass.database.exception.DuplicateUuidDatabaseException +import com.kunzisoft.keepass.database.exception.KeyFileEmptyDatabaseException +import org.apache.commons.io.IOUtils import java.io.* import java.security.MessageDigest import java.security.NoSuchAlgorithmException import java.util.* -abstract class PwDatabase< +abstract class DatabaseVersioned< GroupId, EntryId, - Group : PwGroup, - Entry : PwEntry + Group : GroupVersioned, + Entry : EntryVersioned > { // Algorithm used to encrypt the database - protected var algorithm: PwEncryptionAlgorithm? = null + protected var algorithm: EncryptionAlgorithm? = null abstract val kdfEngine: KdfEngine? @@ -46,13 +52,13 @@ abstract class PwDatabase< var finalKey: ByteArray? = null protected set - var iconFactory = PwIconFactory() + var iconFactory = IconImageFactory() protected set var changeDuplicateId = false - private var groupIndexes = LinkedHashMap, Group>() - private var entryIndexes = LinkedHashMap, Entry>() + private var groupIndexes = LinkedHashMap, Group>() + private var entryIndexes = LinkedHashMap, Entry>() abstract val version: String @@ -60,15 +66,15 @@ abstract class PwDatabase< abstract var numberKeyEncryptionRounds: Long - var encryptionAlgorithm: PwEncryptionAlgorithm + var encryptionAlgorithm: EncryptionAlgorithm get() { - return algorithm ?: PwEncryptionAlgorithm.AESRijndael + return algorithm ?: EncryptionAlgorithm.AESRijndael } set(algorithm) { this.algorithm = algorithm } - abstract val availableEncryptionAlgorithms: List + abstract val availableEncryptionAlgorithms: List var rootGroup: Group? = null @@ -124,7 +130,7 @@ abstract class PwDatabase< protected fun getFileKey(keyInputStream: InputStream): ByteArray { val keyByteArrayOutputStream = ByteArrayOutputStream() - MemoryUtil.copyStream(keyInputStream, keyByteArrayOutputStream) + IOUtils.copy(keyInputStream, keyByteArrayOutputStream) val keyData = keyByteArrayOutputStream.toByteArray() val keyByteArrayInputStream = ByteArrayInputStream(keyData) @@ -134,7 +140,7 @@ abstract class PwDatabase< } when (keyData.size.toLong()) { - 0L -> throw LoadDatabaseKeyFileEmptyException() + 0L -> throw KeyFileEmptyDatabaseException() 32L -> return keyData 64L -> try { return hexStringToByteArray(String(keyData)) @@ -192,9 +198,9 @@ abstract class PwDatabase< * ------------------------------------- */ - abstract fun newGroupId(): PwNodeId + abstract fun newGroupId(): NodeId - abstract fun newEntryId(): PwNodeId + abstract fun newEntryId(): NodeId abstract fun createGroup(): Group @@ -219,7 +225,7 @@ abstract class PwDatabase< * ID number to check for * @return True if the ID is used, false otherwise */ - fun isGroupIdUsed(id: PwNodeId): Boolean { + fun isGroupIdUsed(id: NodeId): Boolean { return groupIndexes.containsKey(id) } @@ -234,7 +240,7 @@ abstract class PwDatabase< } } - fun getGroupById(id: PwNodeId): Group? { + fun getGroupById(id: NodeId): Group? { return this.groupIndexes[id] } @@ -247,7 +253,7 @@ abstract class PwDatabase< group.parent?.addChildGroup(group) this.groupIndexes[newGroupId] = group } else { - throw LoadDatabaseDuplicateUuidException(Type.GROUP, groupId) + throw DuplicateUuidDatabaseException(Type.GROUP, groupId) } } else { this.groupIndexes[groupId] = group @@ -275,7 +281,7 @@ abstract class PwDatabase< } } - fun isEntryIdUsed(id: PwNodeId): Boolean { + fun isEntryIdUsed(id: NodeId): Boolean { return entryIndexes.containsKey(id) } @@ -283,7 +289,7 @@ abstract class PwDatabase< return entryIndexes.values } - fun getEntryById(id: PwNodeId): Entry? { + fun getEntryById(id: NodeId): Entry? { return this.entryIndexes[id] } @@ -296,7 +302,7 @@ abstract class PwDatabase< entry.parent?.addChildEntry(entry) this.entryIndexes[newEntryId] = entry } else { - throw LoadDatabaseDuplicateUuidException(Type.ENTRY, entryId) + throw DuplicateUuidDatabaseException(Type.ENTRY, entryId) } } else { this.entryIndexes[entryId] = entry @@ -376,19 +382,19 @@ abstract class PwDatabase< addEntryTo(entry, origParent) } - abstract fun isBackup(group: Group): Boolean + abstract fun isInRecycleBin(group: Group): Boolean fun isGroupSearchable(group: Group?, omitBackup: Boolean): Boolean { if (group == null) return false - if (omitBackup && isBackup(group)) + if (omitBackup && isInRecycleBin(group)) return false return true } companion object { - private const val TAG = "PwDatabase" + private const val TAG = "DatabaseVersioned" val UUID_ZERO = UUID(0, 0) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/AutoType.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/AutoType.kt similarity index 90% rename from app/src/main/java/com/kunzisoft/keepass/database/element/AutoType.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/entry/AutoType.kt index 8c4910acf..0800fcd3d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/AutoType.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/AutoType.kt @@ -17,12 +17,12 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.entry import android.os.Parcel import android.os.Parcelable -import com.kunzisoft.keepass.utils.MemoryUtil +import com.kunzisoft.keepass.utils.ParcelableUtil import java.util.HashMap @@ -48,7 +48,7 @@ class AutoType : Parcelable { this.enabled = parcel.readByte().toInt() != 0 this.obfuscationOptions = parcel.readLong() this.defaultSequence = parcel.readString() ?: defaultSequence - this.windowSeqPairs = MemoryUtil.readStringParcelableMap(parcel) + this.windowSeqPairs = ParcelableUtil.readStringParcelableMap(parcel) } override fun describeContents(): Int { @@ -59,7 +59,7 @@ class AutoType : Parcelable { dest.writeByte((if (enabled) 1 else 0).toByte()) dest.writeLong(obfuscationOptions) dest.writeString(defaultSequence) - MemoryUtil.writeStringParcelableMap(dest, windowSeqPairs) + ParcelableUtil.writeStringParcelableMap(dest, windowSeqPairs) } fun put(key: String, value: String) { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDB.kt similarity index 61% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDB.kt index 9c69dfd6c..b6be2bdf5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDB.kt @@ -17,15 +17,16 @@ * along with KeePass DX. If not, see . */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.entry import android.os.Parcel import android.os.Parcelable - -import java.io.UnsupportedEncodingException -import java.util.Arrays -import java.util.UUID - +import com.kunzisoft.keepass.database.element.group.GroupKDB +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.NodeIdUUID +import com.kunzisoft.keepass.database.element.node.NodeKDBInterface +import com.kunzisoft.keepass.database.element.node.Type +import java.util.* /** * Structure containing information about one entry. @@ -48,7 +49,7 @@ import java.util.UUID * @author Dominik Reichl @t-online.de> * @author Jeremy Jamet @kunzisoft.com> */ -class PwEntryV3 : PwEntry, PwNodeV3Interface { +class EntryKDB : EntryVersioned, NodeKDBInterface { /** A string describing what is in pBinaryData */ var binaryDesc = "" @@ -56,12 +57,11 @@ class PwEntryV3 : PwEntry, PwNodeV3Interface { * @return the actual binaryData byte array. */ var binaryData: ByteArray = ByteArray(0) - private set // Determine if this is a MetaStream entry val isMetaStream: Boolean get() { - if (Arrays.equals(binaryData, ByteArray(0))) return false + if (binaryData.contentEquals(ByteArray(0))) return false if (notes.isEmpty()) return false if (binaryDesc != PMS_ID_BINDESC) return false if (title.isEmpty()) return false @@ -72,12 +72,12 @@ class PwEntryV3 : PwEntry, PwNodeV3Interface { return if (url != PMS_ID_URL) false else icon.isMetaStreamIcon } - override fun initNodeId(): PwNodeId { - return PwNodeIdUUID() + override fun initNodeId(): NodeId { + return NodeIdUUID() } - override fun copyNodeId(nodeId: PwNodeId): PwNodeId { - return PwNodeIdUUID(nodeId.id) + override fun copyNodeId(nodeId: NodeId): NodeId { + return NodeIdUUID(nodeId.id) } constructor() : super() @@ -85,18 +85,19 @@ class PwEntryV3 : PwEntry, PwNodeV3Interface { constructor(parcel: Parcel) : super(parcel) { title = parcel.readString() ?: title username = parcel.readString() ?: username - parcel.readByteArray(passwordBytes) + password = parcel.readString() ?: password url = parcel.readString() ?: url notes = parcel.readString() ?: notes binaryDesc = parcel.readString() ?: binaryDesc + binaryData = ByteArray(parcel.readInt()) parcel.readByteArray(binaryData) } - override fun readParentParcelable(parcel: Parcel): PwGroupV3? { - return parcel.readParcelable(PwGroupV3::class.java.classLoader) + override fun readParentParcelable(parcel: Parcel): GroupKDB? { + return parcel.readParcelable(GroupKDB::class.java.classLoader) } - override fun writeParentParcelable(parent: PwGroupV3?, parcel: Parcel, flags: Int) { + override fun writeParentParcelable(parent: GroupKDB?, parcel: Parcel, flags: Int) { parcel.writeParcelable(parent, flags) } @@ -104,22 +105,19 @@ class PwEntryV3 : PwEntry, PwNodeV3Interface { super.writeToParcel(dest, flags) dest.writeString(title) dest.writeString(username) - dest.writeByteArray(passwordBytes) + dest.writeString(password) dest.writeString(url) dest.writeString(notes) dest.writeString(binaryDesc) + dest.writeInt(binaryData.size) dest.writeByteArray(binaryData) } - fun updateWith(source: PwEntryV3) { + fun updateWith(source: EntryKDB) { super.updateWith(source) title = source.title username = source.username - - val passLen = source.passwordBytes.size - passwordBytes = ByteArray(passLen) - System.arraycopy(source.passwordBytes, 0, passwordBytes, 0, passLen) - + password = source.password url = source.url notes = source.notes binaryDesc = source.binaryDesc @@ -131,32 +129,10 @@ class PwEntryV3 : PwEntry, PwNodeV3Interface { override var username = "" - var passwordBytes: ByteArray = ByteArray(0) - private set - - /** Securely erase old password before copying new. */ - fun setPassword(buf: ByteArray, offset: Int, len: Int) { - fill(passwordBytes, 0.toByte()) - passwordBytes = ByteArray(len) - System.arraycopy(buf, offset, passwordBytes, 0, len) - } - /** * @return the actual password byte array. */ - override var password: String - get() = String(passwordBytes) - set(pass) { - var password: ByteArray - try { - password = pass.toByteArray(charset("UTF-8")) - setPassword(password, 0, password.size) - } catch (e: UnsupportedEncodingException) { - password = pass.toByteArray() - setPassword(password, 0, password.size) - } - - } + override var password = "" override var url = "" @@ -167,13 +143,6 @@ class PwEntryV3 : PwEntry, PwNodeV3Interface { override val type: Type get() = Type.ENTRY - fun setBinaryData(buf: ByteArray, offset: Int, len: Int) { - /** Securely erase old data before copying new. */ - fill(binaryData, 0.toByte()) - binaryData = ByteArray(len) - System.arraycopy(buf, offset, binaryData, 0, len) - } - companion object { /** Size of byte buffer needed to hold this struct. */ @@ -183,22 +152,14 @@ class PwEntryV3 : PwEntry, PwNodeV3Interface { private const val PMS_ID_URL = "$" @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(`in`: Parcel): PwEntryV3 { - return PwEntryV3(`in`) + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): EntryKDB { + return EntryKDB(parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } - - /** - * fill byte array - */ - private fun fill(array: ByteArray, value: Byte) { - for (i in array.indices) - array[i] = value - } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDBX.kt similarity index 73% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDBX.kt index 465e72b16..0ccc82681 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEntryV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryKDBX.kt @@ -17,24 +17,34 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.entry import android.os.Parcel import android.os.Parcelable -import com.kunzisoft.keepass.database.element.security.ProtectedBinary +import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.element.database.DatabaseKDBX +import com.kunzisoft.keepass.database.element.group.GroupKDBX +import com.kunzisoft.keepass.database.element.icon.IconImage +import com.kunzisoft.keepass.database.element.icon.IconImageCustom +import com.kunzisoft.keepass.database.element.icon.IconImageStandard +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.NodeIdUUID +import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface +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.utils.MemoryUtil +import com.kunzisoft.keepass.utils.ParcelableUtil import java.util.* -class PwEntryV4 : PwEntry, PwNodeV4Interface { +class EntryKDBX : EntryVersioned, NodeKDBXInterface { // To decode each field not parcelable @Transient - private var mDatabase: PwDatabaseV4? = null + private var mDatabase: DatabaseKDBX? = null @Transient private var mDecodeRef = false - override var icon: PwIcon + override var icon: IconImage get() { return when { iconCustom.isUnknown -> super.icon @@ -42,19 +52,19 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { } } set(value) { - if (value is PwIconStandard) - iconCustom = PwIconCustom.UNKNOWN_ICON + if (value is IconImageStandard) + iconCustom = IconImageCustom.UNKNOWN_ICON super.icon = value } - var iconCustom = PwIconCustom.UNKNOWN_ICON + var iconCustom = IconImageCustom.UNKNOWN_ICON private var customData = HashMap() var fields = HashMap() - val binaries = HashMap() + var binaries = HashMap() var foregroundColor = "" var backgroundColor = "" var overrideURL = "" var autoType = AutoType() - var history = ArrayList() + var history = ArrayList() var additional = "" var tags = "" @@ -93,12 +103,12 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { constructor() : super() constructor(parcel: Parcel) : super(parcel) { - iconCustom = parcel.readParcelable(PwIconCustom::class.java.classLoader) ?: iconCustom + iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom usageCount = parcel.readLong() - locationChanged = parcel.readParcelable(PwDate::class.java.classLoader) ?: locationChanged - customData = MemoryUtil.readStringParcelableMap(parcel) - fields = MemoryUtil.readStringParcelableMap(parcel, ProtectedString::class.java) - // TODO binaries = MemoryUtil.readStringParcelableMap(parcel, ProtectedBinary.class); + locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged + customData = ParcelableUtil.readStringParcelableMap(parcel) + fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java) + binaries = ParcelableUtil.readStringParcelableMap(parcel, BinaryAttachment::class.java) foregroundColor = parcel.readString() ?: foregroundColor backgroundColor = parcel.readString() ?: backgroundColor overrideURL = parcel.readString() ?: overrideURL @@ -114,9 +124,9 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { dest.writeParcelable(iconCustom, flags) dest.writeLong(usageCount) dest.writeParcelable(locationChanged, flags) - MemoryUtil.writeStringParcelableMap(dest, customData) - MemoryUtil.writeStringParcelableMap(dest, flags, fields) - // TODO MemoryUtil.writeStringParcelableMap(dest, flags, binaries); + ParcelableUtil.writeStringParcelableMap(dest, customData) + ParcelableUtil.writeStringParcelableMap(dest, flags, fields) + ParcelableUtil.writeStringParcelableMap(dest, flags, binaries) dest.writeString(foregroundColor) dest.writeString(backgroundColor) dest.writeString(overrideURL) @@ -131,11 +141,11 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { * Update with deep copy of each entry element * @param source */ - fun updateWith(source: PwEntryV4, copyHistory: Boolean = true) { + fun updateWith(source: EntryKDBX, copyHistory: Boolean = true) { super.updateWith(source) - iconCustom = PwIconCustom(source.iconCustom) + iconCustom = IconImageCustom(source.iconCustom) usageCount = source.usageCount - locationChanged = PwDate(source.locationChanged) + locationChanged = DateInstant(source.locationChanged) // Add all custom elements in map customData.clear() customData.putAll(source.customData) @@ -155,7 +165,7 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { tags = source.tags } - fun startToManageFieldReferences(db: PwDatabaseV4) { + fun startToManageFieldReferences(db: DatabaseKDBX) { this.mDatabase = db this.mDecodeRef = true } @@ -165,24 +175,24 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { this.mDecodeRef = false } - override fun initNodeId(): PwNodeId { - return PwNodeIdUUID() + override fun initNodeId(): NodeId { + return NodeIdUUID() } - override fun copyNodeId(nodeId: PwNodeId): PwNodeId { - return PwNodeIdUUID(nodeId.id) + override fun copyNodeId(nodeId: NodeId): NodeId { + return NodeIdUUID(nodeId.id) } - override fun readParentParcelable(parcel: Parcel): PwGroupV4? { - return parcel.readParcelable(PwGroupV4::class.java.classLoader) + override fun readParentParcelable(parcel: Parcel): GroupKDBX? { + return parcel.readParcelable(GroupKDBX::class.java.classLoader) } - override fun writeParentParcelable(parent: PwGroupV4?, parcel: Parcel, flags: Int) { + override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) { parcel.writeParcelable(parent, flags) } /** - * Decode a reference key with the SprEngineV4 + * Decode a reference key with the FieldReferencesEngine * @param decodeRef * @param key * @return @@ -190,7 +200,7 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { private fun decodeRefKey(decodeRef: Boolean, key: String): String { return fields[key]?.toString()?.let { text -> return if (decodeRef) { - if (mDatabase == null) text else SprEngineV4().compile(text, this, mDatabase!!) + if (mDatabase == null) text else FieldReferencesEngine().compile(text, this, mDatabase!!) } else text } ?: "" } @@ -235,10 +245,10 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { override var usageCount: Long = 0 - override var locationChanged = PwDate() + override var locationChanged = DateInstant() fun afterChangeParent() { - locationChanged = PwDate() + locationChanged = DateInstant() } private fun isStandardField(key: String): Boolean { @@ -274,7 +284,7 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { fields[label] = value } - fun putProtectedBinary(key: String, value: ProtectedBinary) { + fun putProtectedBinary(key: String, value: BinaryAttachment) { binaries[key] = value } @@ -290,7 +300,7 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { return customData.isNotEmpty() } - fun addEntryToHistory(entry: PwEntryV4) { + fun addEntryToHistory(entry: EntryKDBX) { history.add(entry) } @@ -330,12 +340,12 @@ class PwEntryV4 : PwEntry, PwNodeV4Interface { const val STR_NOTES = "Notes" @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): PwEntryV4 { - return PwEntryV4(parcel) + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): EntryKDBX { + return EntryKDBX(parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryVersioned.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryVersioned.kt new file mode 100644 index 000000000..d19883495 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryVersioned.kt @@ -0,0 +1,20 @@ +package com.kunzisoft.keepass.database.element.entry + +import android.os.Parcel +import com.kunzisoft.keepass.database.element.group.GroupVersioned +import com.kunzisoft.keepass.database.element.node.NodeVersioned + +abstract class EntryVersioned + < + GroupId, + EntryId, + ParentGroup: GroupVersioned, + Entry: EntryVersioned + > + : NodeVersioned, EntryVersionedInterface { + + constructor() : super() + + constructor(parcel: Parcel) : super(parcel) + +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryVersionedInterface.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryVersionedInterface.kt new file mode 100644 index 000000000..64a7d16c5 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/EntryVersionedInterface.kt @@ -0,0 +1,14 @@ +package com.kunzisoft.keepass.database.element.entry + +import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface + +interface EntryVersionedInterface : NodeVersionedInterface { + + var username: String + + var password: String + + var url: String + + var notes: String +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/SprEngineV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/FieldReferencesEngine.kt similarity index 86% rename from app/src/main/java/com/kunzisoft/keepass/database/element/SprEngineV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/entry/FieldReferencesEngine.kt index 6a3285e07..a0b801f70 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/SprEngineV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/entry/FieldReferencesEngine.kt @@ -17,24 +17,26 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.entry -import com.kunzisoft.keepass.database.search.EntrySearchHandlerV4 -import com.kunzisoft.keepass.database.search.SearchParametersV4 +import com.kunzisoft.keepass.database.element.database.DatabaseKDBX +import com.kunzisoft.keepass.database.element.group.GroupKDBX +import com.kunzisoft.keepass.database.search.EntryKDBXSearchHandler +import com.kunzisoft.keepass.database.search.SearchParametersKDBX import com.kunzisoft.keepass.utils.StringUtil import java.util.* -class SprEngineV4 { +class FieldReferencesEngine { - inner class TargetResult(var entry: PwEntryV4?, var wanted: Char) + inner class TargetResult(var entry: EntryKDBX?, var wanted: Char) private inner class SprContextV4 { - var databaseV4: PwDatabaseV4? = null - var entry: PwEntryV4 + var databaseV4: DatabaseKDBX? = null + var entry: EntryKDBX var refsCache: MutableMap = HashMap() - internal constructor(db: PwDatabaseV4, entry: PwEntryV4) { + internal constructor(db: DatabaseKDBX, entry: EntryKDBX) { this.databaseV4 = db this.entry = entry } @@ -46,7 +48,7 @@ class SprEngineV4 { } } - fun compile(text: String, entry: PwEntryV4, database: PwDatabaseV4): String { + fun compile(text: String, entry: EntryKDBX, database: DatabaseKDBX): String { return compileInternal(text, SprContextV4(database, entry), 0) } @@ -139,7 +141,7 @@ class SprEngineV4 { val scan = Character.toUpperCase(ref[2]) val wanted = Character.toUpperCase(ref[0]) - val searchParametersV4 = SearchParametersV4() + val searchParametersV4 = SearchParametersKDBX() searchParametersV4.setupNone() searchParametersV4.searchString = ref.substring(4) @@ -161,7 +163,7 @@ class SprEngineV4 { return null } - val list = ArrayList() + val list = ArrayList() // TODO type parameter searchEntries(contextV4.databaseV4!!.rootGroup, searchParametersV4, list) @@ -195,7 +197,7 @@ class SprEngineV4 { return newText } - private fun searchEntries(root: PwGroupV4?, searchParametersV4: SearchParametersV4?, listStorage: MutableList?) { + private fun searchEntries(root: GroupKDBX?, searchParametersV4: SearchParametersKDBX?, listStorage: MutableList?) { if (searchParametersV4 == null) { return } @@ -205,7 +207,7 @@ class SprEngineV4 { val terms = StringUtil.splitStringTerms(searchParametersV4.searchString) if (terms.size <= 1 || searchParametersV4.regularExpression) { - root!!.doForEachChild(EntrySearchHandlerV4(searchParametersV4, listStorage), null) + root!!.doForEachChild(EntryKDBXSearchHandler(searchParametersV4, listStorage), null) return } @@ -214,9 +216,9 @@ class SprEngineV4 { Collections.sort(terms, stringLengthComparator) val fullSearch = searchParametersV4.searchString - var childEntries: List? = root!!.getChildEntries() + var childEntries: List? = root!!.getChildEntries() for (i in terms.indices) { - val pgNew = ArrayList() + val pgNew = ArrayList() searchParametersV4.searchString = terms[i] @@ -226,12 +228,12 @@ class SprEngineV4 { negate = searchParametersV4.searchString.isNotEmpty() } - if (!root.doForEachChild(EntrySearchHandlerV4(searchParametersV4, pgNew), null)) { + if (!root.doForEachChild(EntryKDBXSearchHandler(searchParametersV4, pgNew), null)) { childEntries = null break } - val complement = ArrayList() + val complement = ArrayList() if (negate) { for (entry in childEntries!!) { if (!pgNew.contains(entry)) { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwGroupV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupKDB.kt similarity index 60% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwGroupV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupKDB.kt index ed2aaec3d..9452b260f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwGroupV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupKDB.kt @@ -18,13 +18,18 @@ * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.group import android.os.Parcel import android.os.Parcelable +import com.kunzisoft.keepass.database.element.entry.EntryKDB +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.NodeIdInt +import com.kunzisoft.keepass.database.element.node.NodeKDBInterface +import com.kunzisoft.keepass.database.element.node.Type import java.util.* -class PwGroupV3 : PwGroup, PwNodeV3Interface { +class GroupKDB : GroupVersioned, NodeKDBInterface { var level = 0 // short /** Used by KeePass internally, don't use */ @@ -37,11 +42,11 @@ class PwGroupV3 : PwGroup, PwNodeV3Interface { flags = parcel.readInt() } - override fun readParentParcelable(parcel: Parcel): PwGroupV3? { - return parcel.readParcelable(PwGroupV3::class.java.classLoader) + override fun readParentParcelable(parcel: Parcel): GroupKDB? { + return parcel.readParcelable(GroupKDB::class.java.classLoader) } - override fun writeParentParcelable(parent: PwGroupV3?, parcel: Parcel, flags: Int) { + override fun writeParentParcelable(parent: GroupKDB?, parcel: Parcel, flags: Int) { parcel.writeParcelable(parent, flags) } @@ -51,7 +56,7 @@ class PwGroupV3 : PwGroup, PwNodeV3Interface { dest.writeInt(flags) } - fun updateWith(source: PwGroupV3) { + fun updateWith(source: GroupKDB) { super.updateWith(source) level = source.level flags = source.flags @@ -60,12 +65,12 @@ class PwGroupV3 : PwGroup, PwNodeV3Interface { override val type: Type get() = Type.GROUP - override fun initNodeId(): PwNodeId { - return PwNodeIdInt() + override fun initNodeId(): NodeId { + return NodeIdInt() } - override fun copyNodeId(nodeId: PwNodeId): PwNodeId { - return PwNodeIdInt(nodeId.id) + override fun copyNodeId(nodeId: NodeId): NodeId { + return NodeIdInt(nodeId.id) } override fun afterAssignNewParent() { @@ -74,7 +79,7 @@ class PwGroupV3 : PwGroup, PwNodeV3Interface { } fun setGroupId(groupId: Int) { - this.nodeId = PwNodeIdInt(groupId) + this.nodeId = NodeIdInt(groupId) } override fun allowAddEntryIfIsRoot(): Boolean { @@ -84,12 +89,12 @@ class PwGroupV3 : PwGroup, PwNodeV3Interface { companion object { @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): PwGroupV3 { - return PwGroupV3(parcel) + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): GroupKDB { + return GroupKDB(parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwGroupV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupKDBX.kt similarity index 62% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwGroupV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupKDBX.kt index e7eee8abc..34e7d2bd4 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwGroupV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupKDBX.kt @@ -17,18 +17,28 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.group import android.os.Parcel import android.os.Parcelable +import com.kunzisoft.keepass.database.element.database.DatabaseVersioned +import com.kunzisoft.keepass.database.element.DateInstant +import com.kunzisoft.keepass.database.element.entry.EntryKDBX +import com.kunzisoft.keepass.database.element.icon.IconImage +import com.kunzisoft.keepass.database.element.icon.IconImageCustom +import com.kunzisoft.keepass.database.element.icon.IconImageStandard +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.NodeIdUUID +import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface +import com.kunzisoft.keepass.database.element.node.Type import java.util.HashMap import java.util.UUID -class PwGroupV4 : PwGroup, PwNodeV4Interface { +class GroupKDBX : GroupVersioned, NodeKDBXInterface { // TODO Encapsulate - override var icon: PwIcon + override var icon: IconImage get() { return if (iconCustom.isUnknown) super.icon @@ -36,11 +46,11 @@ class PwGroupV4 : PwGroup, PwNodeV4Interface { iconCustom } set(value) { - if (value is PwIconStandard) - iconCustom = PwIconCustom.UNKNOWN_ICON + if (value is IconImageStandard) + iconCustom = IconImageCustom.UNKNOWN_ICON super.icon = value } - var iconCustom = PwIconCustom.UNKNOWN_ICON + var iconCustom = IconImageCustom.UNKNOWN_ICON private val customData = HashMap() var notes = "" @@ -48,28 +58,28 @@ class PwGroupV4 : PwGroup, PwNodeV4Interface { var defaultAutoTypeSequence = "" var enableAutoType: Boolean? = null var enableSearching: Boolean? = null - var lastTopVisibleEntry: UUID = PwDatabase.UUID_ZERO + var lastTopVisibleEntry: UUID = DatabaseVersioned.UUID_ZERO override var expires: Boolean = false override val type: Type get() = Type.GROUP - override fun initNodeId(): PwNodeId { - return PwNodeIdUUID() + override fun initNodeId(): NodeId { + return NodeIdUUID() } - override fun copyNodeId(nodeId: PwNodeId): PwNodeId { - return PwNodeIdUUID(nodeId.id) + override fun copyNodeId(nodeId: NodeId): NodeId { + return NodeIdUUID(nodeId.id) } constructor() : super() constructor(parcel: Parcel) : super(parcel) { - iconCustom = parcel.readParcelable(PwIconCustom::class.java.classLoader) ?: iconCustom + iconCustom = parcel.readParcelable(IconImageCustom::class.java.classLoader) ?: iconCustom usageCount = parcel.readLong() - locationChanged = parcel.readParcelable(PwDate::class.java.classLoader) ?: locationChanged - // TODO customData = MemoryUtil.readStringParcelableMap(in); + locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged + // TODO customData = ParcelableUtil.readStringParcelableMap(in); notes = parcel.readString() ?: notes isExpanded = parcel.readByte().toInt() != 0 defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence @@ -80,11 +90,11 @@ class PwGroupV4 : PwGroup, PwNodeV4Interface { lastTopVisibleEntry = parcel.readSerializable() as UUID } - override fun readParentParcelable(parcel: Parcel): PwGroupV4? { - return parcel.readParcelable(PwGroupV4::class.java.classLoader) + override fun readParentParcelable(parcel: Parcel): GroupKDBX? { + return parcel.readParcelable(GroupKDBX::class.java.classLoader) } - override fun writeParentParcelable(parent: PwGroupV4?, parcel: Parcel, flags: Int) { + override fun writeParentParcelable(parent: GroupKDBX?, parcel: Parcel, flags: Int) { parcel.writeParcelable(parent, flags) } @@ -93,7 +103,7 @@ class PwGroupV4 : PwGroup, PwNodeV4Interface { dest.writeParcelable(iconCustom, flags) dest.writeLong(usageCount) dest.writeParcelable(locationChanged, flags) - // TODO MemoryUtil.writeStringParcelableMap(dest, customData); + // TODO ParcelableUtil.writeStringParcelableMap(dest, customData); dest.writeString(notes) dest.writeByte((if (isExpanded) 1 else 0).toByte()) dest.writeString(defaultAutoTypeSequence) @@ -102,11 +112,11 @@ class PwGroupV4 : PwGroup, PwNodeV4Interface { dest.writeSerializable(lastTopVisibleEntry) } - fun updateWith(source: PwGroupV4) { + fun updateWith(source: GroupKDBX) { super.updateWith(source) - iconCustom = PwIconCustom(source.iconCustom) + iconCustom = IconImageCustom(source.iconCustom) usageCount = source.usageCount - locationChanged = PwDate(source.locationChanged) + locationChanged = DateInstant(source.locationChanged) // Add all custom elements in map customData.clear() for ((key, value) in source.customData) { @@ -122,10 +132,10 @@ class PwGroupV4 : PwGroup, PwNodeV4Interface { override var usageCount: Long = 0 - override var locationChanged = PwDate() + override var locationChanged = DateInstant() override fun afterAssignNewParent() { - locationChanged = PwDate() + locationChanged = DateInstant() } override fun putCustomData(key: String, value: String) { @@ -143,12 +153,12 @@ class PwGroupV4 : PwGroup, PwNodeV4Interface { companion object { @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): PwGroupV4 { - return PwGroupV4(parcel) + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): GroupKDBX { + return GroupKDBX(parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwGroup.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupVersioned.kt similarity index 72% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwGroup.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupVersioned.kt index 50d94dca0..6cc938a6a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwGroup.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupVersioned.kt @@ -1,15 +1,17 @@ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.group import android.os.Parcel +import com.kunzisoft.keepass.database.element.entry.EntryVersioned +import com.kunzisoft.keepass.database.element.node.NodeVersioned -abstract class PwGroup +abstract class GroupVersioned < GroupId, EntryId, - Group: PwGroup, - Entry: PwEntry + Group: GroupVersioned, + Entry: EntryVersioned > - : PwNode, PwGroupInterface { + : NodeVersioned, GroupVersionedInterface { private var titleGroup = "" @Transient @@ -28,7 +30,7 @@ abstract class PwGroup dest.writeString(titleGroup) } - protected fun updateWith(source: PwGroup) { + protected fun updateWith(source: GroupVersioned) { super.updateWith(source) titleGroup = source.titleGroup childGroups.clear() @@ -69,6 +71,11 @@ abstract class PwGroup this.childEntries.remove(entry) } + override fun removeChildren() { + this.childGroups.clear() + this.childEntries.clear() + } + override fun toString(): String { return titleGroup } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwGroupInterface.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupVersionedInterface.kt similarity index 79% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwGroupInterface.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupVersionedInterface.kt index 35d42bb31..588aef471 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwGroupInterface.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/group/GroupVersionedInterface.kt @@ -1,8 +1,9 @@ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.group -import com.kunzisoft.keepass.database.NodeHandler +import com.kunzisoft.keepass.database.action.node.NodeHandler +import com.kunzisoft.keepass.database.element.node.NodeVersionedInterface -interface PwGroupInterface, Entry> : PwNodeInterface { +interface GroupVersionedInterface, Entry> : NodeVersionedInterface { fun getChildGroups(): MutableList @@ -16,6 +17,8 @@ interface PwGroupInterface, Entry> : PwNod fun removeChildEntry(entry: Entry) + fun removeChildren() + fun allowAddEntryIfIsRoot(): Boolean fun doForEachChildAndForIt(entryHandler: NodeHandler, diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwIcon.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImage.kt similarity index 89% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwIcon.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImage.kt index b34ae0222..47cc76e32 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwIcon.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImage.kt @@ -17,11 +17,11 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.icon import android.os.Parcelable -abstract class PwIcon protected constructor() : Parcelable { +abstract class IconImage protected constructor() : Parcelable { abstract val iconId: Int abstract val isUnknown: Boolean diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwIconCustom.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImageCustom.kt similarity index 78% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwIconCustom.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImageCustom.kt index ce8f47801..9744d9dd8 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwIconCustom.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImageCustom.kt @@ -17,14 +17,15 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.icon import android.os.Parcel import android.os.Parcelable +import com.kunzisoft.keepass.database.element.database.DatabaseVersioned import java.util.UUID -class PwIconCustom : PwIcon { +class IconImageCustom : IconImage { val uuid: UUID @Transient @@ -40,7 +41,7 @@ class PwIconCustom : PwIcon { this.imageData = ByteArray(0) } - constructor(icon: PwIconCustom) : super() { + constructor(icon: IconImageCustom) : super() { uuid = icon.uuid imageData = icon.imageData } @@ -68,7 +69,7 @@ class PwIconCustom : PwIcon { return true if (other == null) return false - if (other !is PwIconCustom) + if (other !is IconImageCustom) return false return uuid == other.uuid } @@ -83,15 +84,15 @@ class PwIconCustom : PwIcon { get() = false companion object { - val UNKNOWN_ICON = PwIconCustom(PwDatabase.UUID_ZERO, ByteArray(0)) + val UNKNOWN_ICON = IconImageCustom(DatabaseVersioned.UUID_ZERO, ByteArray(0)) @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): PwIconCustom { - return PwIconCustom(parcel) + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): IconImageCustom { + return IconImageCustom(parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwIconFactory.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImageFactory.kt similarity index 62% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwIconFactory.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImageFactory.kt index d8a53fd7a..e15a3988b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwIconFactory.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImageFactory.kt @@ -17,61 +17,61 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.icon import org.apache.commons.collections.map.AbstractReferenceMap import org.apache.commons.collections.map.ReferenceMap import java.util.UUID -class PwIconFactory { +class IconImageFactory { /** customIconMap * Cache for icon drawable. - * Keys: Integer, Values: PwIconStandard + * Keys: Integer, Values: IconImageStandard */ private val cache = ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK) /** standardIconMap * Cache for icon drawable. - * Keys: UUID, Values: PwIconCustom + * Keys: UUID, Values: IconImageCustom */ private val customCache = ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK) - val unknownIcon: PwIconStandard - get() = getIcon(PwIcon.UNKNOWN_ID) + val unknownIcon: IconImageStandard + get() = getIcon(IconImage.UNKNOWN_ID) - val keyIcon: PwIconStandard - get() = getIcon(PwIconStandard.KEY) + val keyIcon: IconImageStandard + get() = getIcon(IconImageStandard.KEY) - val trashIcon: PwIconStandard - get() = getIcon(PwIconStandard.TRASH) + val trashIcon: IconImageStandard + get() = getIcon(IconImageStandard.TRASH) - val folderIcon: PwIconStandard - get() = getIcon(PwIconStandard.FOLDER) + val folderIcon: IconImageStandard + get() = getIcon(IconImageStandard.FOLDER) - fun getIcon(iconId: Int): PwIconStandard { - var icon: PwIconStandard? = cache[iconId] as PwIconStandard? + fun getIcon(iconId: Int): IconImageStandard { + var icon: IconImageStandard? = cache[iconId] as IconImageStandard? if (icon == null) { - icon = PwIconStandard(iconId) + icon = IconImageStandard(iconId) cache[iconId] = icon } return icon } - fun getIcon(iconUuid: UUID): PwIconCustom { - var icon: PwIconCustom? = customCache[iconUuid] as PwIconCustom? + fun getIcon(iconUuid: UUID): IconImageCustom { + var icon: IconImageCustom? = customCache[iconUuid] as IconImageCustom? if (icon == null) { - icon = PwIconCustom(iconUuid) + icon = IconImageCustom(iconUuid) customCache[iconUuid] = icon } return icon } - fun put(icon: PwIconCustom) { + fun put(icon: IconImageCustom) { customCache[icon.uuid] = icon } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwIconStandard.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImageStandard.kt similarity index 79% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwIconStandard.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImageStandard.kt index 4d7931986..9b507bdc3 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwIconStandard.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/icon/IconImageStandard.kt @@ -17,12 +17,12 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.icon import android.os.Parcel import android.os.Parcelable -class PwIconStandard : PwIcon { +class IconImageStandard : IconImage { constructor() { this.iconId = KEY @@ -32,7 +32,7 @@ class PwIconStandard : PwIcon { this.iconId = iconId } - constructor(icon: PwIconStandard) { + constructor(icon: IconImageStandard) { this.iconId = icon.iconId } @@ -56,7 +56,7 @@ class PwIconStandard : PwIcon { return true if (other == null) return false - if (other !is PwIconStandard) { + if (other !is IconImageStandard) { return false } return iconId == other.iconId @@ -77,12 +77,12 @@ class PwIconStandard : PwIcon { const val FOLDER = 48 @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): PwIconStandard { - return PwIconStandard(parcel) + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): IconImageStandard { + return IconImageStandard(parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/NodeVersioned.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/node/Node.kt similarity index 70% rename from app/src/main/java/com/kunzisoft/keepass/database/element/NodeVersioned.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/node/Node.kt index f231245f8..24852a27b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/NodeVersioned.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/node/Node.kt @@ -1,8 +1,10 @@ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.node -interface NodeVersioned: PwNodeInterface { +import com.kunzisoft.keepass.database.element.Group - val nodeId: PwNodeId<*>? +interface Node: NodeVersionedInterface { + + val nodeId: NodeId<*>? val nodePositionInParent: Int get() { @@ -15,7 +17,7 @@ interface NodeVersioned: PwNodeInterface { return -1 } - fun addParentFrom(node: NodeVersioned) { + fun addParentFrom(node: Node) { parent = node.parent } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeId.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeId.kt similarity index 89% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeId.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeId.kt index 38bc119b5..0a280b434 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeId.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeId.kt @@ -17,12 +17,12 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.node import android.os.Parcel import android.os.Parcelable -abstract class PwNodeId : Parcelable { +abstract class NodeId : Parcelable { abstract val id: Id @@ -34,7 +34,7 @@ abstract class PwNodeId : Parcelable { override fun equals(other: Any?): Boolean { if (this === other) return true - if (other !is PwNodeId<*>) return false + if (other !is NodeId<*>) return false if (id != other.id) return false diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeIdInt.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeIdInt.kt similarity index 78% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeIdInt.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeIdInt.kt index 6ff613d7f..507cec4ea 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeIdInt.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeIdInt.kt @@ -17,19 +17,19 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.node import android.os.Parcel import android.os.Parcelable import java.util.Random -class PwNodeIdInt : PwNodeId { +class NodeIdInt : NodeId { override var id: Int = -1 private set - constructor(source: PwNodeIdInt) : this(source.id) + constructor(source: NodeIdInt) : this(source.id) @JvmOverloads constructor(groupId: Int = Random().nextInt()) : super() { @@ -50,7 +50,7 @@ class PwNodeIdInt : PwNodeId { return true if (other == null) return false - if (other !is PwNodeIdInt) { + if (other !is NodeIdInt) { return false } return id == other.id @@ -66,12 +66,12 @@ class PwNodeIdInt : PwNodeId { companion object { @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): PwNodeIdInt { - return PwNodeIdInt(parcel) + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): NodeIdInt { + return NodeIdInt(parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeIdUUID.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeIdUUID.kt similarity index 78% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeIdUUID.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeIdUUID.kt index aac6d7cf8..27dc093c5 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeIdUUID.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeIdUUID.kt @@ -17,19 +17,19 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.node import android.os.Parcel import android.os.Parcelable import java.util.UUID -class PwNodeIdUUID : PwNodeId { +class NodeIdUUID : NodeId { override var id: UUID = UUID.randomUUID() private set - constructor(source: PwNodeIdUUID) : this(source.id) + constructor(source: NodeIdUUID) : this(source.id) @JvmOverloads constructor(uuid: UUID = UUID.randomUUID()) : super() { @@ -50,7 +50,7 @@ class PwNodeIdUUID : PwNodeId { return true if (other == null) return false - if (other !is PwNodeIdUUID) { + if (other !is NodeIdUUID) { return false } return this.id == other.id @@ -66,12 +66,12 @@ class PwNodeIdUUID : PwNodeId { companion object { @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): PwNodeIdUUID { - return PwNodeIdUUID(parcel) + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): NodeIdUUID { + return NodeIdUUID(parcel) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeV3Interface.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeKDBInterface.kt similarity index 52% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeV3Interface.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeKDBInterface.kt index ee39cc0e6..6151764ac 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeV3Interface.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeKDBInterface.kt @@ -1,18 +1,19 @@ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.node +import com.kunzisoft.keepass.database.element.DateInstant import org.joda.time.LocalDateTime -interface PwNodeV3Interface : NodeTimeInterface { +interface NodeKDBInterface : NodeTimeInterface { override var expires: Boolean // If expireDate is before NEVER_EXPIRE date less 1 month (to be sure) // it is not expires get() = LocalDateTime(expiryTime.date) - .isBefore(LocalDateTime.fromDateFields(PwDate.NEVER_EXPIRE.date) + .isBefore(LocalDateTime.fromDateFields(DateInstant.NEVER_EXPIRE.date) .minusMonths(1)) set(value) { if (!value) - expiryTime = PwDate.NEVER_EXPIRE + expiryTime = DateInstant.NEVER_EXPIRE } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeV4Interface.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeKDBXInterface.kt similarity index 81% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeV4Interface.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeKDBXInterface.kt index 60f87d2c7..b5588d246 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeV4Interface.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeKDBXInterface.kt @@ -17,13 +17,15 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.node -interface PwNodeV4Interface : NodeTimeInterface { +import com.kunzisoft.keepass.database.element.DateInstant + +interface NodeKDBXInterface : NodeTimeInterface { var usageCount: Long - var locationChanged: PwDate + var locationChanged: DateInstant fun putCustomData(key: String, value: String) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/NodeTimeInterface.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeTimeInterface.kt similarity index 76% rename from app/src/main/java/com/kunzisoft/keepass/database/element/NodeTimeInterface.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeTimeInterface.kt index 7decbd1c0..a97ed77a7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/NodeTimeInterface.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeTimeInterface.kt @@ -17,17 +17,19 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.node + +import com.kunzisoft.keepass.database.element.DateInstant interface NodeTimeInterface { - var creationTime: PwDate + var creationTime: DateInstant - var lastModificationTime: PwDate + var lastModificationTime: DateInstant - var lastAccessTime: PwDate + var lastAccessTime: DateInstant - var expiryTime: PwDate + var expiryTime: DateInstant var expires: Boolean diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNode.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeVersioned.kt similarity index 61% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwNode.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeVersioned.kt index 60e1a6028..a1ded7913 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNode.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeVersioned.kt @@ -18,18 +18,24 @@ * * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.node import android.os.Parcel import android.os.Parcelable +import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface +import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface +import com.kunzisoft.keepass.database.element.icon.IconImage +import com.kunzisoft.keepass.database.element.icon.IconImageStandard import org.joda.time.LocalDateTime /** * Abstract class who manage Groups and Entries */ -abstract class PwNode, Entry : PwEntryInterface> : PwNodeInterface, Parcelable { +abstract class NodeVersioned, Entry : EntryVersionedInterface> + : NodeVersionedInterface, NodeTimeInterface, Parcelable { - var nodeId: PwNodeId = this.initNodeId() + var nodeId: NodeId = this.initNodeId() val id: IdType get() = nodeId.id @@ -37,13 +43,13 @@ abstract class PwNode, Entry : protected constructor() protected constructor(parcel: Parcel) { - this.nodeId = parcel.readParcelable(PwNodeId::class.java.classLoader) ?: nodeId + this.nodeId = parcel.readParcelable(NodeId::class.java.classLoader) ?: nodeId this.parent = this.readParentParcelable(parcel) - this.icon = parcel.readParcelable(PwIcon::class.java.classLoader) ?: icon - this.creationTime = parcel.readParcelable(PwDate::class.java.classLoader) ?: creationTime - this.lastModificationTime = parcel.readParcelable(PwDate::class.java.classLoader) ?: lastModificationTime - this.lastAccessTime = parcel.readParcelable(PwDate::class.java.classLoader) ?: lastAccessTime - this.expiryTime = parcel.readParcelable(PwDate::class.java.classLoader) ?: expiryTime + this.icon = parcel.readParcelable(IconImage::class.java.classLoader) ?: icon + this.creationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: creationTime + this.lastModificationTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: lastModificationTime + this.lastAccessTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: lastAccessTime + this.expiryTime = parcel.readParcelable(DateInstant::class.java.classLoader) ?: expiryTime this.expires = parcel.readByte().toInt() != 0 } @@ -62,33 +68,33 @@ abstract class PwNode, Entry : return 0 } - protected fun updateWith(source: PwNode) { + protected fun updateWith(source: NodeVersioned) { this.nodeId = copyNodeId(source.nodeId) this.parent = source.parent this.icon = source.icon - this.creationTime = PwDate(source.creationTime) - this.lastModificationTime = PwDate(source.lastModificationTime) - this.lastAccessTime = PwDate(source.lastAccessTime) - this.expiryTime = PwDate(source.expiryTime) + this.creationTime = DateInstant(source.creationTime) + this.lastModificationTime = DateInstant(source.lastModificationTime) + this.lastAccessTime = DateInstant(source.lastAccessTime) + this.expiryTime = DateInstant(source.expiryTime) this.expires = source.expires } - protected abstract fun initNodeId(): PwNodeId - protected abstract fun copyNodeId(nodeId: PwNodeId): PwNodeId + protected abstract fun initNodeId(): NodeId + protected abstract fun copyNodeId(nodeId: NodeId): NodeId protected abstract fun readParentParcelable(parcel: Parcel): Parent? protected abstract fun writeParentParcelable(parent: Parent?, parcel: Parcel, flags: Int) final override var parent: Parent? = null - override var icon: PwIcon = PwIconStandard() + override var icon: IconImage = IconImageStandard() - final override var creationTime: PwDate = PwDate() + final override var creationTime: DateInstant = DateInstant() - final override var lastModificationTime: PwDate = PwDate() + final override var lastModificationTime: DateInstant = DateInstant() - final override var lastAccessTime: PwDate = PwDate() + final override var lastAccessTime: DateInstant = DateInstant() - final override var expiryTime: PwDate = PwDate() + final override var expiryTime: DateInstant = DateInstant.NEVER_EXPIRE final override val isCurrentlyExpires: Boolean get() = expires @@ -117,7 +123,7 @@ abstract class PwNode, Entry : } override fun touch(modified: Boolean, touchParents: Boolean) { - val now = PwDate() + val now = DateInstant() lastAccessTime = now if (modified) { @@ -134,7 +140,7 @@ abstract class PwNode, Entry : return true if (other == null) return false - if (other !is PwNode<*, *, *>) { + if (other !is NodeVersioned<*, *, *>) { return false } return type == other.type && nodeId == other.nodeId diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeInterface.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeVersionedInterface.kt similarity index 65% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeInterface.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeVersionedInterface.kt index 89db8b52c..8bcd39847 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwNodeInterface.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/node/NodeVersionedInterface.kt @@ -1,15 +1,16 @@ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.node import android.os.Parcelable +import com.kunzisoft.keepass.database.element.icon.IconImage -interface PwNodeInterface : NodeTimeInterface, Parcelable { +interface NodeVersionedInterface : NodeTimeInterface, Parcelable { var title: String /** * @return Visual icon */ - var icon: PwIcon + var icon: IconImage /** * @return Type of Node diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/security/BinaryAttachment.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/security/BinaryAttachment.kt new file mode 100644 index 000000000..d9137160d --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/security/BinaryAttachment.kt @@ -0,0 +1,184 @@ +/* + * Copyright 2018 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePass DX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.element.security + +import android.os.Parcel +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.readFromStream +import java.io.* +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream + +class BinaryAttachment : Parcelable { + + var isCompressed: Boolean? = null + private set + var isProtected: Boolean = false + private set + private var dataFile: File? = null + + fun length(): Long { + if (dataFile != null) + return dataFile!!.length() + return 0 + } + + /** + * Empty protected binary + */ + constructor() { + this.isCompressed = null + this.isProtected = false + this.dataFile = null + } + + constructor(dataFile: File, enableProtection: Boolean = false, compressed: Boolean? = null) { + this.isCompressed = compressed + this.isProtected = enableProtection + this.dataFile = dataFile + } + + private constructor(parcel: Parcel) { + val compressedByte = parcel.readByte().toInt() + isCompressed = if (compressedByte == 2) null else compressedByte != 0 + isProtected = parcel.readByte().toInt() != 0 + dataFile = File(parcel.readString()) + } + + @Throws(IOException::class) + fun getInputDataStream(): InputStream { + return when { + dataFile != null -> FileInputStream(dataFile!!) + else -> ByteArrayInputStream(ByteArray(0)) + } + } + + @Throws(IOException::class) + fun compress() { + if (dataFile != null) { + // To compress, create a new binary with file + if (isCompressed != true) { + val fileBinaryCompress = File(dataFile!!.parent, dataFile!!.name + "_temp") + val outputStream = GZIPOutputStream(FileOutputStream(fileBinaryCompress)) + readFromStream(getInputDataStream(), BUFFER_SIZE_BYTES, + object : ReadBytes { + override fun read(buffer: ByteArray) { + outputStream.write(buffer) + } + }) + outputStream.close() + + // Remove unGzip file + if (dataFile!!.delete()) { + if (fileBinaryCompress.renameTo(dataFile)) { + // Harmonize with database compression + isCompressed = true + } + } + } + } + } + + @Throws(IOException::class) + fun decompress() { + if (dataFile != null) { + if (isCompressed != false) { + val fileBinaryDecompress = File(dataFile!!.parent, dataFile!!.name + "_temp") + val outputStream = FileOutputStream(fileBinaryDecompress) + readFromStream(GZIPInputStream(getInputDataStream()), BUFFER_SIZE_BYTES, + object : ReadBytes { + override fun read(buffer: ByteArray) { + outputStream.write(buffer) + } + }) + outputStream.close() + + // Remove gzip file + if (dataFile!!.delete()) { + if (fileBinaryDecompress.renameTo(dataFile)) { + // Harmonize with database compression + isCompressed = false + } + } + } + } + } + + @Throws(IOException::class) + fun clear() { + if (dataFile != null && !dataFile!!.delete()) + throw IOException("Unable to delete temp file " + dataFile!!.absolutePath) + } + + override fun equals(other: Any?): Boolean { + if (this === other) + return true + if (other == null || javaClass != other.javaClass) + return false + if (other !is BinaryAttachment) + return false + + var sameData = false + if (dataFile != null && dataFile == other.dataFile) + sameData = true + + return isCompressed == other.isCompressed + && isProtected == other.isProtected + && sameData + } + + override fun hashCode(): Int { + + var result = 0 + result = 31 * result + if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0 + result = 31 * result + if (isProtected) 1 else 0 + result = 31 * result + dataFile!!.hashCode() + return result + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeByte((if (isCompressed == null) 2 else if (isCompressed!!) 1 else 0).toByte()) + dest.writeByte((if (isProtected) 1 else 0).toByte()) + dest.writeString(dataFile?.absolutePath) + } + + companion object { + + private val TAG = BinaryAttachment::class.java.name + + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): BinaryAttachment { + return BinaryAttachment(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEncryptionAlgorithm.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/security/EncryptionAlgorithm.kt similarity index 94% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwEncryptionAlgorithm.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/security/EncryptionAlgorithm.kt index d214bd9cd..cb87b0c3d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwEncryptionAlgorithm.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/security/EncryptionAlgorithm.kt @@ -17,7 +17,7 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.security import android.content.res.Resources @@ -30,7 +30,7 @@ import com.kunzisoft.keepass.utils.ObjectNameResource import java.util.UUID -enum class PwEncryptionAlgorithm : ObjectNameResource { +enum class EncryptionAlgorithm : ObjectNameResource { AESRijndael, Twofish, diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/MemoryProtectionConfig.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/security/MemoryProtectionConfig.kt similarity index 96% rename from app/src/main/java/com/kunzisoft/keepass/database/element/MemoryProtectionConfig.kt rename to app/src/main/java/com/kunzisoft/keepass/database/element/security/MemoryProtectionConfig.kt index 1e4dbda13..caae5289a 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/MemoryProtectionConfig.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/element/security/MemoryProtectionConfig.kt @@ -17,7 +17,7 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.element.security class MemoryProtectionConfig { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt b/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt deleted file mode 100644 index ff413ea05..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/security/ProtectedBinary.kt +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 2018 Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * KeePass DX is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.database.element.security - -import android.os.Parcel -import android.os.Parcelable -import android.util.Log - -import java.io.ByteArrayInputStream -import java.io.File -import java.io.FileInputStream -import java.io.IOException -import java.io.InputStream -import java.util.Arrays - -class ProtectedBinary : Parcelable { - - var isProtected: Boolean = false - private set - private var data: ByteArray? = null - private var dataFile: File? = null - private var size: Int = 0 - - fun length(): Long { - if (data != null) - return data!!.size.toLong() - return if (dataFile != null) size.toLong() else 0 - } - - /** - * Empty protected binary - */ - constructor() { - this.isProtected = false - this.data = null - this.dataFile = null - this.size = 0 - } - - constructor(protectedBinary: ProtectedBinary) { - this.isProtected = protectedBinary.isProtected - this.data = protectedBinary.data - this.dataFile = protectedBinary.dataFile - this.size = protectedBinary.size - } - - constructor(enableProtection: Boolean, data: ByteArray?) { - this.isProtected = enableProtection - this.data = data - this.dataFile = null - if (data != null) - this.size = data.size - else - this.size = 0 - } - - constructor(enableProtection: Boolean, dataFile: File, size: Int) { - this.isProtected = enableProtection - this.data = null - this.dataFile = dataFile - this.size = size - } - - private constructor(parcel: Parcel) { - isProtected = parcel.readByte().toInt() != 0 - parcel.readByteArray(data) - dataFile = File(parcel.readString()) - size = parcel.readInt() - } - - @Throws(IOException::class) - fun getData(): InputStream? { - return when { - data != null -> ByteArrayInputStream(data) - dataFile != null -> FileInputStream(dataFile!!) - else -> null - } - } - - fun clear() { - data = null - if (dataFile != null && !dataFile!!.delete()) - Log.e(TAG, "Unable to delete temp file " + dataFile!!.absolutePath) - } - - override fun equals(other: Any?): Boolean { - if (this === other) - return true - if (other == null || javaClass != other.javaClass) - return false - if (other !is ProtectedBinary) - return false - return isProtected == other.isProtected && - size == other.size && - Arrays.equals(data, other.data) && - dataFile != null && - dataFile == other.dataFile - } - - override fun hashCode(): Int { - - var result = 0 - result = 31 * result + if (isProtected) 1 else 0 - result = 31 * result + dataFile!!.hashCode() - result = 31 * result + Integer.valueOf(size).hashCode() - result = 31 * result + Arrays.hashCode(data) - return result - } - - override fun describeContents(): Int { - return 0 - } - - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeByte((if (isProtected) 1 else 0).toByte()) - dest.writeByteArray(data) - dest.writeString(dataFile!!.absolutePath) - dest.writeInt(size) - } - - companion object { - - private val TAG = ProtectedBinary::class.java.name - - @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(parcel: Parcel): ProtectedBinary { - return ProtectedBinary(parcel) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } - } - } - -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/exception/SamsungClipboardException.kt b/app/src/main/java/com/kunzisoft/keepass/database/exception/ClipboardException.kt similarity index 92% rename from app/src/main/java/com/kunzisoft/keepass/database/exception/SamsungClipboardException.kt rename to app/src/main/java/com/kunzisoft/keepass/database/exception/ClipboardException.kt index 0c3f13a8d..98950a1fa 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/exception/SamsungClipboardException.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/exception/ClipboardException.kt @@ -19,4 +19,4 @@ */ package com.kunzisoft.keepass.database.exception -class SamsungClipboardException(e: Exception) : Exception(e) +class ClipboardException(e: Exception) : Exception(e) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt b/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt index e16dadb92..f9b904560 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/exception/DatabaseException.kt @@ -3,8 +3,8 @@ package com.kunzisoft.keepass.database.exception import android.content.res.Resources import androidx.annotation.StringRes import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.element.PwNodeId -import com.kunzisoft.keepass.database.element.Type +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.Type abstract class DatabaseException : Exception { @@ -12,7 +12,8 @@ abstract class DatabaseException : Exception { var parameters: (Array)? = null constructor() : super() - + constructor(message: String) : super(message) + constructor(message: String, throwable: Throwable) : super(message, throwable) constructor(throwable: Throwable) : super(throwable) fun getLocalizedMessage(resources: Resources): String { @@ -26,33 +27,25 @@ open class LoadDatabaseException : DatabaseException { @StringRes override var errorId: Int = R.string.error_load_database - constructor() : super() - - constructor(vararg params: String) : super() { - parameters = params - } - constructor(throwable: Throwable) : super(throwable) } -class LoadDatabaseArcFourException : LoadDatabaseException { +class ArcFourDatabaseException : LoadDatabaseException { @StringRes override var errorId: Int = R.string.error_arc4 - constructor() : super() constructor(exception: Throwable) : super(exception) } -class LoadDatabaseFileNotFoundException : LoadDatabaseException { +class FileNotFoundDatabaseException : LoadDatabaseException { @StringRes override var errorId: Int = R.string.file_not_found_content - constructor() : super() constructor(exception: Throwable) : super(exception) } -class LoadDatabaseInvalidAlgorithmException : LoadDatabaseException { +class InvalidAlgorithmDatabaseException : LoadDatabaseException { @StringRes override var errorId: Int = R.string.invalid_algorithm @@ -60,102 +53,97 @@ class LoadDatabaseInvalidAlgorithmException : LoadDatabaseException { constructor(exception: Throwable) : super(exception) } -class LoadDatabaseDuplicateUuidException: LoadDatabaseException { +class DuplicateUuidDatabaseException: LoadDatabaseException { @StringRes override var errorId: Int = R.string.invalid_db_same_uuid - - constructor(type: Type, uuid: PwNodeId<*>) : super() { + constructor(type: Type, uuid: NodeId<*>) : super() { parameters = arrayOf(type.name, uuid.toString()) } constructor(exception: Throwable) : super(exception) } -class LoadDatabaseIOException : LoadDatabaseException { +class IODatabaseException : LoadDatabaseException { @StringRes override var errorId: Int = R.string.error_load_database - constructor() : super() constructor(exception: Throwable) : super(exception) } -class LoadDatabaseKDFMemoryException : LoadDatabaseException { +class KDFMemoryDatabaseException : LoadDatabaseException { @StringRes override var errorId: Int = R.string.error_load_database_KDF_memory - constructor() : super() constructor(exception: Throwable) : super(exception) } -class LoadDatabaseSignatureException : LoadDatabaseException { +class SignatureDatabaseException : LoadDatabaseException { @StringRes override var errorId: Int = R.string.invalid_db_sig - constructor() : super() constructor(exception: Throwable) : super(exception) } -class LoadDatabaseVersionException : LoadDatabaseException { +class VersionDatabaseException : LoadDatabaseException { @StringRes override var errorId: Int = R.string.unsupported_db_version - constructor() : super() constructor(exception: Throwable) : super(exception) } -class LoadDatabaseInvalidCredentialsException : LoadDatabaseException { +class InvalidCredentialsDatabaseException : LoadDatabaseException { @StringRes override var errorId: Int = R.string.invalid_credentials - constructor() : super() constructor(exception: Throwable) : super(exception) } -class LoadDatabaseKeyFileEmptyException : LoadDatabaseException { +class KeyFileEmptyDatabaseException : LoadDatabaseException { @StringRes override var errorId: Int = R.string.keyfile_is_empty constructor() : super() constructor(exception: Throwable) : super(exception) } -class LoadDatabaseNoMemoryException: LoadDatabaseException { +class NoMemoryDatabaseException: LoadDatabaseException { @StringRes override var errorId: Int = R.string.error_out_of_memory constructor() : super() constructor(exception: Throwable) : super(exception) } -class MoveDatabaseEntryException: LoadDatabaseException { +class EntryDatabaseException: LoadDatabaseException { @StringRes override var errorId: Int = R.string.error_move_entry_here constructor() : super() constructor(exception: Throwable) : super(exception) } -class MoveDatabaseGroupException: LoadDatabaseException { +class MoveGroupDatabaseException: LoadDatabaseException { @StringRes override var errorId: Int = R.string.error_move_folder_in_itself constructor() : super() constructor(exception: Throwable) : super(exception) } -class CopyDatabaseEntryException: LoadDatabaseException { +class CopyEntryDatabaseException: LoadDatabaseException { @StringRes override var errorId: Int = R.string.error_copy_entry_here constructor() : super() constructor(exception: Throwable) : super(exception) } -class CopyDatabaseGroupException: LoadDatabaseException { +class CopyGroupDatabaseException: LoadDatabaseException { @StringRes override var errorId: Int = R.string.error_copy_group_here constructor() : super() constructor(exception: Throwable) : super(exception) } -class DatabaseOutputException : Exception { +// TODO Output Exception +open class DatabaseOutputException : DatabaseException { + @StringRes + override var errorId: Int = R.string.error_save_database constructor(string: String) : super(string) - constructor(string: String, e: Exception) : super(string, e) - constructor(e: Exception) : super(e) } \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeader.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeader.kt similarity index 97% rename from app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeader.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeader.kt index d8499b25e..2a9432d6f 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeader.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeader.kt @@ -19,7 +19,7 @@ */ package com.kunzisoft.keepass.database.file -abstract class PwDbHeader { +abstract class DatabaseHeader { /** * Seed that gets hashed with the userkey to form the final key diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeaderKDB.kt similarity index 98% rename from app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeaderKDB.kt index 35375309e..12d1a629c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeaderKDB.kt @@ -25,7 +25,7 @@ import com.kunzisoft.keepass.stream.LEDataInputStream import java.io.IOException -class PwDbHeaderV3 : PwDbHeader() { +class DatabaseHeaderKDB : DatabaseHeader() { /** * Used for the dwKeyEncRounds AES transformations diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeaderKDBX.kt similarity index 86% rename from app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeaderKDBX.kt index 9cff24a2a..4236d187c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/PwDbHeaderV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseHeaderKDBX.kt @@ -23,13 +23,17 @@ import com.kunzisoft.keepass.crypto.CrsAlgorithm import com.kunzisoft.keepass.crypto.keyDerivation.AesKdf import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters -import com.kunzisoft.keepass.database.NodeHandler -import com.kunzisoft.keepass.database.element.* -import com.kunzisoft.keepass.database.exception.LoadDatabaseVersionException +import com.kunzisoft.keepass.database.action.node.NodeHandler +import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm +import com.kunzisoft.keepass.database.element.database.DatabaseKDBX +import com.kunzisoft.keepass.database.element.entry.EntryKDBX +import com.kunzisoft.keepass.database.element.group.GroupKDBX +import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface +import com.kunzisoft.keepass.database.exception.VersionDatabaseException import com.kunzisoft.keepass.stream.CopyInputStream import com.kunzisoft.keepass.stream.HmacBlockStream import com.kunzisoft.keepass.stream.LEDataInputStream -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import java.io.ByteArrayOutputStream import java.io.IOException import java.io.InputStream @@ -40,7 +44,7 @@ import java.security.NoSuchAlgorithmException import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec -class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() { +class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader() { var innerRandomStreamKey: ByteArray = ByteArray(32) var streamStartBytes: ByteArray = ByteArray(32) var innerRandomStream: CrsAlgorithm? = null @@ -89,7 +93,7 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() { this.masterSeed = ByteArray(32) } - private inner class NodeHasCustomData : NodeHandler() { + private inner class NodeHasCustomData : NodeHandler() { internal var containsCustomData = false @@ -102,7 +106,7 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() { } } - private fun getMinKdbxVersion(databaseV4: PwDatabaseV4): Long { + private fun getMinKdbxVersion(databaseV4: DatabaseKDBX): Long { // https://keepass.info/help/kb/kdbx_4.html // Return v4 if AES is not use @@ -115,8 +119,8 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() { return FILE_VERSION_32_3 } - val entryHandler = NodeHasCustomData() - val groupHandler = NodeHasCustomData() + val entryHandler = NodeHasCustomData() + val groupHandler = NodeHasCustomData() databaseV4.rootGroup?.doForEachChildAndForIt(entryHandler, groupHandler) return if (databaseV4.containsCustomData() || entryHandler.containsCustomData @@ -130,9 +134,9 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() { /** Assumes the input stream is at the beginning of the .kdbx file * @param inputStream * @throws IOException - * @throws LoadDatabaseVersionException + * @throws VersionDatabaseException */ - @Throws(IOException::class, LoadDatabaseVersionException::class) + @Throws(IOException::class, VersionDatabaseException::class) fun loadFromFile(inputStream: InputStream): HeaderAndHash { val messageDigest: MessageDigest try { @@ -150,12 +154,12 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() { val sig2 = littleEndianDataInputStream.readInt() if (!matchesHeader(sig1, sig2)) { - throw LoadDatabaseVersionException() + throw VersionDatabaseException() } version = littleEndianDataInputStream.readUInt() // Erase previous value if (!validVersion(version)) { - throw LoadDatabaseVersionException() + throw VersionDatabaseException() } var done = false @@ -216,7 +220,7 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() { PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData) PwDbHeaderV4Fields.PublicCustomData -> { - databaseV4.publicCustomData = KdfParameters.deserialize(fieldData) // TODO verify + databaseV4.publicCustomData = KdfParameters.deserialize(fieldData)!! // TODO verify throw IOException("Invalid header type: $fieldID") } else -> throw IOException("Invalid header type: $fieldID") @@ -239,7 +243,7 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() { throw IOException("Invalid cipher ID.") } - databaseV4.dataCipher = Types.bytestoUUID(pbId) + databaseV4.dataCipher = DatabaseInputOutputUtils.bytesToUuid(pbId) } private fun setTransformRound(roundsByte: ByteArray?) { @@ -256,7 +260,7 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() { } val flag = LEDataInputStream.readInt(pbFlags, 0) - if (flag < 0 || flag >= PwCompressionAlgorithm.values().size) { + if (flag < 0 || flag >= CompressionAlgorithm.values().size) { throw IOException("Unrecognized compression flag.") } @@ -298,17 +302,17 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() { const val FILE_VERSION_32_3: Long = 0x00030001 const val FILE_VERSION_32_4: Long = 0x00040000 - fun getCompressionFromFlag(flag: Int): PwCompressionAlgorithm? { + fun getCompressionFromFlag(flag: Int): CompressionAlgorithm? { return when (flag) { - 0 -> PwCompressionAlgorithm.None - 1 -> PwCompressionAlgorithm.GZip + 0 -> CompressionAlgorithm.None + 1 -> CompressionAlgorithm.GZip else -> null } } - fun getFlagFromCompression(compression: PwCompressionAlgorithm): Int { + fun getFlagFromCompression(compression: CompressionAlgorithm): Int { return when (compression) { - PwCompressionAlgorithm.GZip -> 1 + CompressionAlgorithm.GZip -> 1 else -> 0 } } @@ -319,7 +323,7 @@ class PwDbHeaderV4(private val databaseV4: PwDatabaseV4) : PwDbHeader() { @Throws(IOException::class) fun computeHeaderHmac(header: ByteArray, key: ByteArray): ByteArray { - val blockKey = HmacBlockStream.GetHmacKey64(key, Types.ULONG_MAX_VALUE) + val blockKey = HmacBlockStream.GetHmacKey64(key, DatabaseInputOutputUtils.ULONG_MAX_VALUE) val hmac: Mac try { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4XML.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseKDBXXML.kt similarity index 98% rename from app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4XML.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseKDBXXML.kt index 56c2e810d..9d6a4f9ba 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/element/PwDatabaseV4XML.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/DatabaseKDBXXML.kt @@ -17,12 +17,12 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.element +package com.kunzisoft.keepass.database.file import java.text.SimpleDateFormat import java.util.* -object PwDatabaseV4XML { +object DatabaseKDBXXML { const val ElemDocNode = "KeePassFile" const val ElemMeta = "Meta" diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/KDBX4DateUtil.java b/app/src/main/java/com/kunzisoft/keepass/database/file/DateKDBXUtil.java similarity index 98% rename from app/src/main/java/com/kunzisoft/keepass/database/file/KDBX4DateUtil.java rename to app/src/main/java/com/kunzisoft/keepass/database/file/DateKDBXUtil.java index 565393611..35c5a8c23 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/KDBX4DateUtil.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/DateKDBXUtil.java @@ -25,7 +25,7 @@ import java.util.Date; -public class KDBX4DateUtil { +public class DateKDBXUtil { private static final DateTime dotNetEpoch = new DateTime(1, 1, 1, 0, 0, 0, DateTimeZone.UTC); private static final DateTime javaEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeZone.UTC); diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/load/Importer.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt similarity index 85% rename from app/src/main/java/com/kunzisoft/keepass/database/file/load/Importer.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt index 8743eb855..1d71fc2a7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/load/Importer.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInput.kt @@ -17,21 +17,21 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.file.load +package com.kunzisoft.keepass.database.file.input -import com.kunzisoft.keepass.database.element.PwDatabase +import com.kunzisoft.keepass.database.element.database.DatabaseVersioned import com.kunzisoft.keepass.database.exception.LoadDatabaseException import com.kunzisoft.keepass.tasks.ProgressTaskUpdater import java.io.InputStream -abstract class Importer> { +abstract class DatabaseInput> { /** - * Load a versioned database file, return contents in a new PwDatabase. + * Load a versioned database file, return contents in a new DatabaseVersioned. * * @param databaseInputStream Existing file to load. * @param password Pass phrase for infile. - * @return new PwDatabase container. + * @return new DatabaseVersioned container. * * @throws LoadDatabaseException on database error (contains IO exceptions) */ diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt similarity index 71% rename from app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt index be42a451a..a900180eb 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDB.kt @@ -43,19 +43,24 @@ 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.load +package com.kunzisoft.keepass.database.file.input import android.util.Log import com.kunzisoft.keepass.R import com.kunzisoft.keepass.crypto.CipherFactory -import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.element.database.DatabaseKDB +import com.kunzisoft.keepass.database.element.entry.EntryKDB +import com.kunzisoft.keepass.database.element.group.GroupKDB +import com.kunzisoft.keepass.database.element.node.NodeIdInt +import com.kunzisoft.keepass.database.element.node.NodeIdUUID +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.database.exception.* -import com.kunzisoft.keepass.database.file.PwDbHeader -import com.kunzisoft.keepass.database.file.PwDbHeaderV3 +import com.kunzisoft.keepass.database.file.DatabaseHeader +import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.stream.LEDataInputStream import com.kunzisoft.keepass.stream.NullOutputStream import com.kunzisoft.keepass.tasks.ProgressTaskUpdater -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import javax.crypto.* import javax.crypto.spec.IvParameterSpec @@ -67,17 +72,17 @@ import java.security.* import java.util.Arrays /** - * Load a v3 database file. + * Load a KDB database file. */ -class ImporterV3 : Importer() { +class DatabaseInputKDB : DatabaseInput() { - private lateinit var mDatabaseToOpen: PwDatabaseV3 + private lateinit var mDatabaseToOpen: DatabaseKDB @Throws(LoadDatabaseException::class) override fun openDatabase(databaseInputStream: InputStream, password: String?, keyInputStream: InputStream?, - progressTaskUpdater: ProgressTaskUpdater?): PwDatabaseV3 { + progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDB { try { // Load entire file, most of it's encrypted. @@ -87,28 +92,28 @@ class ImporterV3 : Importer() { databaseInputStream.close() // Parse header (unencrypted) - if (fileSize < PwDbHeaderV3.BUF_SIZE) + if (fileSize < DatabaseHeaderKDB.BUF_SIZE) throw IOException("File too short for header") - val hdr = PwDbHeaderV3() + val hdr = DatabaseHeaderKDB() hdr.loadFromFile(filebuf, 0) - if (hdr.signature1 != PwDbHeader.PWM_DBSIG_1 || hdr.signature2 != PwDbHeaderV3.DBSIG_2) { - throw LoadDatabaseSignatureException() + if (hdr.signature1 != DatabaseHeader.PWM_DBSIG_1 || hdr.signature2 != DatabaseHeaderKDB.DBSIG_2) { + throw SignatureDatabaseException() } if (!hdr.matchesVersion()) { - throw LoadDatabaseVersionException() + throw VersionDatabaseException() } progressTaskUpdater?.updateMessage(R.string.retrieving_db_key) - mDatabaseToOpen = PwDatabaseV3() + mDatabaseToOpen = DatabaseKDB() mDatabaseToOpen.retrieveMasterKey(password, keyInputStream) // Select algorithm when { - hdr.flags and PwDbHeaderV3.FLAG_RIJNDAEL != 0 -> mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.AESRijndael - hdr.flags and PwDbHeaderV3.FLAG_TWOFISH != 0 -> mDatabaseToOpen.encryptionAlgorithm = PwEncryptionAlgorithm.Twofish - else -> throw LoadDatabaseInvalidAlgorithmException() + hdr.flags and DatabaseHeaderKDB.FLAG_RIJNDAEL != 0 -> mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.AESRijndael + hdr.flags and DatabaseHeaderKDB.FLAG_TWOFISH != 0 -> mDatabaseToOpen.encryptionAlgorithm = EncryptionAlgorithm.Twofish + else -> throw InvalidAlgorithmDatabaseException() } mDatabaseToOpen.numberKeyEncryptionRounds = hdr.numKeyEncRounds.toLong() @@ -120,9 +125,9 @@ class ImporterV3 : Importer() { // Initialize Rijndael algorithm val cipher: Cipher try { - if (mDatabaseToOpen.encryptionAlgorithm === PwEncryptionAlgorithm.AESRijndael) { + if (mDatabaseToOpen.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael) { cipher = CipherFactory.getInstance("AES/CBC/PKCS5Padding") - } else if (mDatabaseToOpen.encryptionAlgorithm === PwEncryptionAlgorithm.Twofish) { + } else if (mDatabaseToOpen.encryptionAlgorithm === EncryptionAlgorithm.Twofish) { cipher = CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING") } else { throw IOException("Encryption algorithm is not supported") @@ -145,13 +150,13 @@ class ImporterV3 : Importer() { // Decrypt! The first bytes aren't encrypted (that's the header) val encryptedPartSize: Int try { - encryptedPartSize = cipher.doFinal(filebuf, PwDbHeaderV3.BUF_SIZE, fileSize - PwDbHeaderV3.BUF_SIZE, filebuf, PwDbHeaderV3.BUF_SIZE) + encryptedPartSize = cipher.doFinal(filebuf, DatabaseHeaderKDB.BUF_SIZE, fileSize - DatabaseHeaderKDB.BUF_SIZE, filebuf, DatabaseHeaderKDB.BUF_SIZE) } catch (e1: ShortBufferException) { throw IOException("Buffer too short") } catch (e1: IllegalBlockSizeException) { throw IOException("Invalid block size") } catch (e1: BadPaddingException) { - throw LoadDatabaseInvalidCredentialsException() + throw InvalidCredentialsDatabaseException() } val md: MessageDigest @@ -163,23 +168,23 @@ class ImporterV3 : Importer() { val nos = NullOutputStream() val dos = DigestOutputStream(nos, md) - dos.write(filebuf, PwDbHeaderV3.BUF_SIZE, encryptedPartSize) + dos.write(filebuf, DatabaseHeaderKDB.BUF_SIZE, encryptedPartSize) dos.close() val hash = md.digest() if (!Arrays.equals(hash, hdr.contentsHash)) { Log.w(TAG, "Database file did not decrypt correctly. (checksum code is broken)") - throw LoadDatabaseInvalidCredentialsException() + throw InvalidCredentialsDatabaseException() } - // New manual root because V3 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() newRoot.level = -1 mDatabaseToOpen.rootGroup = newRoot // Import all groups - var pos = PwDbHeaderV3.BUF_SIZE + var pos = DatabaseHeaderKDB.BUF_SIZE var newGrp = mDatabaseToOpen.createGroup() run { var i = 0 @@ -223,9 +228,9 @@ class ImporterV3 : Importer() { } catch (e: LoadDatabaseException) { throw e } catch (e: IOException) { - throw LoadDatabaseIOException(e) + throw IODatabaseException(e) } catch (e: OutOfMemoryError) { - throw LoadDatabaseNoMemoryException(e) + throw NoMemoryDatabaseException(e) } catch (e: Exception) { throw LoadDatabaseException(e) } @@ -233,7 +238,7 @@ class ImporterV3 : Importer() { return mDatabaseToOpen } - private fun buildTreeGroups(previousGroup: PwGroupV3, currentGroup: PwGroupV3, groupIterator: Iterator) { + private fun buildTreeGroups(previousGroup: GroupKDB, currentGroup: GroupKDB, groupIterator: Iterator) { if (currentGroup.parent == null && (previousGroup.level + 1) == currentGroup.level) { // Current group has an increment level compare to the previous, current group is a child @@ -282,16 +287,16 @@ class ImporterV3 : Importer() { * @throws UnsupportedEncodingException */ @Throws(UnsupportedEncodingException::class) - private fun readGroupField(db: PwDatabaseV3, grp: PwGroupV3, fieldType: Int, buf: ByteArray, offset: Int) { + 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 = Types.readCString(buf, offset) - 0x0003 -> grp.creationTime = PwDate(buf, offset) - 0x0004 -> grp.lastModificationTime = PwDate(buf, offset) - 0x0005 -> grp.lastAccessTime = PwDate(buf, offset) - 0x0006 -> grp.expiryTime = PwDate(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) @@ -299,7 +304,7 @@ class ImporterV3 : Importer() { } @Throws(UnsupportedEncodingException::class) - private fun readEntryField(db: PwDatabaseV3, ent: PwEntryV3, buf: ByteArray, offset: Int) { + private fun readEntryField(db: DatabaseKDB, ent: EntryKDB, buf: ByteArray, offset: Int) { var offsetMutable = offset val fieldType = LEDataInputStream.readUShort(buf, offsetMutable) offsetMutable += 2 @@ -309,11 +314,11 @@ class ImporterV3 : Importer() { when (fieldType) { 0x0000 -> { } - 0x0001 -> ent.nodeId = PwNodeIdUUID(Types.bytestoUUID(buf, offsetMutable)) + 0x0001 -> ent.nodeId = NodeIdUUID(LEDataInputStream.readUuid(buf, offsetMutable)) 0x0002 -> { - val pwGroupV3 = mDatabaseToOpen.createGroup() - pwGroupV3.nodeId = PwNodeIdInt(LEDataInputStream.readInt(buf, offsetMutable)) - ent.parent = pwGroupV3 + val groupKDB = mDatabaseToOpen.createGroup() + groupKDB.nodeId = NodeIdInt(LEDataInputStream.readInt(buf, offsetMutable)) + ent.parent = groupKDB } 0x0003 -> { var iconId = LEDataInputStream.readInt(buf, offsetMutable) @@ -325,21 +330,21 @@ class ImporterV3 : Importer() { ent.icon = db.iconFactory.getIcon(iconId) } - 0x0004 -> ent.title = Types.readCString(buf, offsetMutable) - 0x0005 -> ent.url = Types.readCString(buf, offsetMutable) - 0x0006 -> ent.username = Types.readCString(buf, offsetMutable) - 0x0007 -> ent.setPassword(buf, offsetMutable, Types.strlen(buf, offsetMutable)) - 0x0008 -> ent.notes = Types.readCString(buf, offsetMutable) - 0x0009 -> ent.creationTime = PwDate(buf, offsetMutable) - 0x000A -> ent.lastModificationTime = PwDate(buf, offsetMutable) - 0x000B -> ent.lastAccessTime = PwDate(buf, offsetMutable) - 0x000C -> ent.expiryTime = PwDate(buf, offsetMutable) - 0x000D -> ent.binaryDesc = Types.readCString(buf, offsetMutable) - 0x000E -> ent.setBinaryData(buf, offsetMutable, fieldSize) + 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 { - private val TAG = ImporterV3::class.java.name + private val TAG = DatabaseInputKDB::class.java.name } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt similarity index 63% rename from app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt index 09adaba71..0b3082542 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/load/ImporterV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/input/DatabaseInputKDBX.kt @@ -17,26 +17,32 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.file.load +package com.kunzisoft.keepass.database.file.input -import biz.source_code.base64Coder.Base64Coder +import android.util.Base64 import com.kunzisoft.keepass.R import com.kunzisoft.keepass.crypto.CipherFactory import com.kunzisoft.keepass.crypto.StreamCipherFactory import com.kunzisoft.keepass.crypto.engine.CipherEngine import com.kunzisoft.keepass.database.element.* -import com.kunzisoft.keepass.database.element.security.ProtectedBinary +import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm +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.DatabaseVersioned +import com.kunzisoft.keepass.database.element.entry.EntryKDBX +import com.kunzisoft.keepass.database.element.group.GroupKDBX +import com.kunzisoft.keepass.database.element.icon.IconImageCustom +import com.kunzisoft.keepass.database.element.node.NodeIdUUID +import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface +import com.kunzisoft.keepass.database.element.security.BinaryAttachment import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.database.exception.* -import com.kunzisoft.keepass.database.file.KDBX4DateUtil -import com.kunzisoft.keepass.database.file.PwDbHeaderV4 -import com.kunzisoft.keepass.stream.BetterCipherInputStream -import com.kunzisoft.keepass.stream.HashedBlockInputStream -import com.kunzisoft.keepass.stream.HmacBlockInputStream -import com.kunzisoft.keepass.stream.LEDataInputStream +import com.kunzisoft.keepass.database.file.DateKDBXUtil +import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX +import com.kunzisoft.keepass.database.file.DatabaseKDBXXML +import com.kunzisoft.keepass.stream.* import com.kunzisoft.keepass.tasks.ProgressTaskUpdater -import com.kunzisoft.keepass.utils.MemoryUtil -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import org.spongycastle.crypto.StreamCipher import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException @@ -46,35 +52,35 @@ import java.nio.charset.Charset import java.text.ParseException import java.util.* import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream import javax.crypto.Cipher import kotlin.math.min -class ImporterV4(private val streamDir: File, - private val fixDuplicateUUID: Boolean = false) : Importer() { +class DatabaseInputKDBX(private val streamDir: File, + private val fixDuplicateUUID: Boolean = false) : DatabaseInput() { private var randomStream: StreamCipher? = null - private lateinit var mDatabase: PwDatabaseV4 + private lateinit var mDatabase: DatabaseKDBX private var hashOfHeader: ByteArray? = null - private var version: Long = 0 private val unusedCacheFileName: String - get() = mDatabase.binPool.findUnusedKey().toString() + get() = mDatabase.binaryPool.findUnusedKey().toString() private var readNextNode = true - private val ctxGroups = Stack() - private var ctxGroup: PwGroupV4? = null - private var ctxEntry: PwEntryV4? = null + private val ctxGroups = Stack() + private var ctxGroup: GroupKDBX? = null + private var ctxEntry: EntryKDBX? = null private var ctxStringName: String? = null private var ctxStringValue: ProtectedString? = null private var ctxBinaryName: String? = null - private var ctxBinaryValue: ProtectedBinary? = null + private var ctxBinaryValue: BinaryAttachment? = null private var ctxATName: String? = null private var ctxATSeq: String? = null private var entryInHistory = false - private var ctxHistoryBase: PwEntryV4? = null - private var ctxDeletedObject: PwDeletedObject? = null - private var customIconID = PwDatabase.UUID_ZERO + private var ctxHistoryBase: EntryKDBX? = null + private var ctxDeletedObject: DeletedObject? = null + private var customIconID = DatabaseVersioned.UUID_ZERO private var customIconData: ByteArray? = null private var customDataKey: String? = null private var customDataValue: String? = null @@ -87,20 +93,20 @@ class ImporterV4(private val streamDir: File, override fun openDatabase(databaseInputStream: InputStream, password: String?, keyInputStream: InputStream?, - progressTaskUpdater: ProgressTaskUpdater?): PwDatabaseV4 { + progressTaskUpdater: ProgressTaskUpdater?): DatabaseKDBX { try { // TODO performance progressTaskUpdater?.updateMessage(R.string.retrieving_db_key) - mDatabase = PwDatabaseV4() + mDatabase = DatabaseKDBX() mDatabase.changeDuplicateId = fixDuplicateUUID - val header = PwDbHeaderV4(mDatabase) + val header = DatabaseHeaderKDBX(mDatabase) val headerAndHash = header.loadFromFile(databaseInputStream) - version = header.version + mDatabase.kdbxVersion = header.version hashOfHeader = headerAndHash.hash val pbHeader = headerAndHash.header @@ -118,11 +124,11 @@ class ImporterV4(private val streamDir: File, mDatabase.encryptionAlgorithm = engine.getPwEncryptionAlgorithm() cipher = engine.getCipher(Cipher.DECRYPT_MODE, mDatabase.finalKey!!, header.encryptionIV) } catch (e: Exception) { - throw LoadDatabaseInvalidAlgorithmException(e) + throw InvalidAlgorithmDatabaseException(e) } val isPlain: InputStream - if (version < PwDbHeaderV4.FILE_VERSION_32_4) { + if (mDatabase.kdbxVersion < DatabaseHeaderKDBX.FILE_VERSION_32_4) { val decrypted = attachCipherStream(databaseInputStream, cipher) val dataDecrypted = LEDataInputStream(decrypted) @@ -130,14 +136,14 @@ class ImporterV4(private val streamDir: File, try { storedStartBytes = dataDecrypted.readBytes(32) if (storedStartBytes == null || storedStartBytes.size != 32) { - throw LoadDatabaseInvalidCredentialsException() + throw InvalidCredentialsDatabaseException() } } catch (e: IOException) { - throw LoadDatabaseInvalidCredentialsException() + throw InvalidCredentialsDatabaseException() } if (!Arrays.equals(storedStartBytes, header.streamStartBytes)) { - throw LoadDatabaseInvalidCredentialsException() + throw InvalidCredentialsDatabaseException() } isPlain = HashedBlockInputStream(dataDecrypted) @@ -145,18 +151,18 @@ class ImporterV4(private val streamDir: File, val isData = LEDataInputStream(databaseInputStream) val storedHash = isData.readBytes(32) if (!Arrays.equals(storedHash, hashOfHeader)) { - throw LoadDatabaseInvalidCredentialsException() + throw InvalidCredentialsDatabaseException() } val hmacKey = mDatabase.hmacKey ?: throw LoadDatabaseException() - val headerHmac = PwDbHeaderV4.computeHeaderHmac(pbHeader, hmacKey) + val headerHmac = DatabaseHeaderKDBX.computeHeaderHmac(pbHeader, hmacKey) val storedHmac = isData.readBytes(32) if (storedHmac == null || storedHmac.size != 32) { - throw LoadDatabaseInvalidCredentialsException() + throw InvalidCredentialsDatabaseException() } // Mac doesn't match if (!Arrays.equals(headerHmac, storedHmac)) { - throw LoadDatabaseInvalidCredentialsException() + throw InvalidCredentialsDatabaseException() } val hmIs = HmacBlockInputStream(isData, true, hmacKey) @@ -166,18 +172,18 @@ class ImporterV4(private val streamDir: File, val inputStreamXml: InputStream inputStreamXml = when (mDatabase.compressionAlgorithm) { - PwCompressionAlgorithm.GZip -> GZIPInputStream(isPlain) + CompressionAlgorithm.GZip -> GZIPInputStream(isPlain) else -> isPlain } - if (version >= PwDbHeaderV4.FILE_VERSION_32_4) { + if (mDatabase.kdbxVersion >= DatabaseHeaderKDBX.FILE_VERSION_32_4) { loadInnerHeader(inputStreamXml, header) } randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey) if (randomStream == null) { - throw LoadDatabaseArcFourException() + throw ArcFourDatabaseException() } readDocumentStreamed(createPullParser(inputStreamXml)) @@ -185,14 +191,14 @@ class ImporterV4(private val streamDir: File, } catch (e: LoadDatabaseException) { throw e } catch (e: XmlPullParserException) { - throw LoadDatabaseIOException(e) + throw IODatabaseException(e) } catch (e: IOException) { if (e.message?.contains("Hash failed with code") == true) - throw LoadDatabaseKDFMemoryException(e) + throw KDFMemoryDatabaseException(e) else - throw LoadDatabaseIOException(e) + throw IODatabaseException(e) } catch (e: OutOfMemoryError) { - throw LoadDatabaseNoMemoryException(e) + throw NoMemoryDatabaseException(e) } catch (e: Exception) { throw LoadDatabaseException(e) } @@ -205,7 +211,7 @@ class ImporterV4(private val streamDir: File, } @Throws(IOException::class) - private fun loadInnerHeader(inputStream: InputStream, header: PwDbHeaderV4) { + private fun loadInnerHeader(inputStream: InputStream, header: DatabaseHeaderKDBX) { val lis = LEDataInputStream(inputStream) while (true) { @@ -214,39 +220,46 @@ class ImporterV4(private val streamDir: File, } @Throws(IOException::class) - private fun readInnerHeader(lis: LEDataInputStream, header: PwDbHeaderV4): Boolean { - val fieldId = lis.read().toByte() + private fun readInnerHeader(dataInputStream: LEDataInputStream, header: DatabaseHeaderKDBX): Boolean { + val fieldId = dataInputStream.read().toByte() - val size = lis.readInt() + val size = dataInputStream.readInt() if (size < 0) throw IOException("Corrupted file") - var data = ByteArray(0) - if (size > 0) { - if (fieldId != PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary) - data = lis.readBytes(size) - } - - var result = true when (fieldId) { - PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader -> result = false - PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomStreamID -> header.setRandomStreamID(data) - PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomstreamKey -> header.innerRandomStreamKey = data - PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary -> { - val flag = lis.readBytes(1)[0].toInt() != 0 - val protectedFlag = flag && PwDbHeaderV4.KdbxBinaryFlags.Protected.toInt() != PwDbHeaderV4.KdbxBinaryFlags.None.toInt() + DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader -> { + return false + } + DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID -> { + val data = if (size > 0) dataInputStream.readBytes(size) else ByteArray(0) + header.setRandomStreamID(data) + } + DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey -> { + val data = if (size > 0) dataInputStream.readBytes(size) else ByteArray(0) + header.innerRandomStreamKey = data + } + DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary -> { + val flag = dataInputStream.readBytes(1)[0].toInt() != 0 + val protectedFlag = flag && DatabaseHeaderKDBX.KdbxBinaryFlags.Protected.toInt() != DatabaseHeaderKDBX.KdbxBinaryFlags.None.toInt() val byteLength = size - 1 // Read in a file val file = File(streamDir, unusedCacheFileName) - FileOutputStream(file).use { outputStream -> lis.readBytes(byteLength) { outputStream.write(it) } } - val protectedBinary = ProtectedBinary(protectedFlag, file, byteLength) - mDatabase.binPool.add(protectedBinary) + FileOutputStream(file).use { outputStream -> + dataInputStream.readBytes(byteLength, object : ReadBytes { + override fun read(buffer: ByteArray) { + outputStream.write(buffer) + } + }) + } + val protectedBinary = BinaryAttachment(file, protectedFlag) + mDatabase.binaryPool.add(protectedBinary) } - else -> { + return false } } - return result + return true } private enum class KdbContext { @@ -312,111 +325,111 @@ class ImporterV4(private val streamDir: File, private fun readXmlElement(ctx: KdbContext, xpp: XmlPullParser): KdbContext { val name = xpp.name when (ctx) { - KdbContext.Null -> if (name.equals(PwDatabaseV4XML.ElemDocNode, ignoreCase = true)) { + KdbContext.Null -> if (name.equals(DatabaseKDBXXML.ElemDocNode, ignoreCase = true)) { return switchContext(ctx, KdbContext.KeePassFile, xpp) } else readUnknown(xpp) - KdbContext.KeePassFile -> if (name.equals(PwDatabaseV4XML.ElemMeta, ignoreCase = true)) { + KdbContext.KeePassFile -> if (name.equals(DatabaseKDBXXML.ElemMeta, ignoreCase = true)) { return switchContext(ctx, KdbContext.Meta, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemRoot, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemRoot, ignoreCase = true)) { return switchContext(ctx, KdbContext.Root, xpp) } else { readUnknown(xpp) } - KdbContext.Meta -> if (name.equals(PwDatabaseV4XML.ElemGenerator, ignoreCase = true)) { + KdbContext.Meta -> if (name.equals(DatabaseKDBXXML.ElemGenerator, ignoreCase = true)) { readString(xpp) // Ignore - } else if (name.equals(PwDatabaseV4XML.ElemHeaderHash, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemHeaderHash, ignoreCase = true)) { val encodedHash = readString(xpp) if (encodedHash.isNotEmpty() && hashOfHeader != null) { - val hash = Base64Coder.decode(encodedHash) + val hash = Base64.decode(encodedHash, BASE_64_FLAG) if (!Arrays.equals(hash, hashOfHeader)) { throw LoadDatabaseException() } } - } else if (name.equals(PwDatabaseV4XML.ElemSettingsChanged, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemSettingsChanged, ignoreCase = true)) { mDatabase.settingsChanged = readPwTime(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemDbName, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbName, ignoreCase = true)) { mDatabase.name = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemDbNameChanged, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbNameChanged, ignoreCase = true)) { mDatabase.nameChanged = readPwTime(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemDbDesc, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbDesc, ignoreCase = true)) { mDatabase.description = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemDbDescChanged, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbDescChanged, ignoreCase = true)) { mDatabase.descriptionChanged = readPwTime(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemDbDefaultUser, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbDefaultUser, ignoreCase = true)) { mDatabase.defaultUserName = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemDbDefaultUserChanged, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbDefaultUserChanged, ignoreCase = true)) { mDatabase.defaultUserNameChanged = readPwTime(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemDbColor, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbColor, ignoreCase = true)) { mDatabase.color = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemDbMntncHistoryDays, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbMntncHistoryDays, ignoreCase = true)) { mDatabase.maintenanceHistoryDays = readUInt(xpp, DEFAULT_HISTORY_DAYS) - } else if (name.equals(PwDatabaseV4XML.ElemDbKeyChanged, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbKeyChanged, ignoreCase = true)) { mDatabase.keyLastChanged = readPwTime(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemDbKeyChangeRec, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbKeyChangeRec, ignoreCase = true)) { mDatabase.keyChangeRecDays = readLong(xpp, -1) - } else if (name.equals(PwDatabaseV4XML.ElemDbKeyChangeForce, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbKeyChangeForce, ignoreCase = true)) { mDatabase.keyChangeForceDays = readLong(xpp, -1) - } else if (name.equals(PwDatabaseV4XML.ElemDbKeyChangeForceOnce, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDbKeyChangeForceOnce, ignoreCase = true)) { mDatabase.isKeyChangeForceOnce = readBool(xpp, false) - } else if (name.equals(PwDatabaseV4XML.ElemMemoryProt, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemMemoryProt, ignoreCase = true)) { return switchContext(ctx, KdbContext.MemoryProtection, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemCustomIcons, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemCustomIcons, ignoreCase = true)) { return switchContext(ctx, KdbContext.CustomIcons, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemRecycleBinEnabled, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemRecycleBinEnabled, ignoreCase = true)) { mDatabase.isRecycleBinEnabled = readBool(xpp, true) - } else if (name.equals(PwDatabaseV4XML.ElemRecycleBinUuid, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemRecycleBinUuid, ignoreCase = true)) { mDatabase.recycleBinUUID = readUuid(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemRecycleBinChanged, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemRecycleBinChanged, ignoreCase = true)) { mDatabase.recycleBinChanged = readTime(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemEntryTemplatesGroup, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemEntryTemplatesGroup, ignoreCase = true)) { mDatabase.entryTemplatesGroup = readUuid(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemEntryTemplatesGroupChanged, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemEntryTemplatesGroupChanged, ignoreCase = true)) { mDatabase.entryTemplatesGroupChanged = readPwTime(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemHistoryMaxItems, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemHistoryMaxItems, ignoreCase = true)) { mDatabase.historyMaxItems = readInt(xpp, -1) - } else if (name.equals(PwDatabaseV4XML.ElemHistoryMaxSize, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemHistoryMaxSize, ignoreCase = true)) { mDatabase.historyMaxSize = readLong(xpp, -1) - } else if (name.equals(PwDatabaseV4XML.ElemLastSelectedGroup, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemLastSelectedGroup, ignoreCase = true)) { mDatabase.lastSelectedGroupUUID = readUuid(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemLastTopVisibleGroup, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemLastTopVisibleGroup, ignoreCase = true)) { mDatabase.lastTopVisibleGroupUUID = readUuid(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemBinaries, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemBinaries, ignoreCase = true)) { return switchContext(ctx, KdbContext.Binaries, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemCustomData, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) { return switchContext(ctx, KdbContext.CustomData, xpp) } - KdbContext.MemoryProtection -> if (name.equals(PwDatabaseV4XML.ElemProtTitle, ignoreCase = true)) { + KdbContext.MemoryProtection -> if (name.equals(DatabaseKDBXXML.ElemProtTitle, ignoreCase = true)) { mDatabase.memoryProtection.protectTitle = readBool(xpp, false) - } else if (name.equals(PwDatabaseV4XML.ElemProtUserName, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemProtUserName, ignoreCase = true)) { mDatabase.memoryProtection.protectUserName = readBool(xpp, false) - } else if (name.equals(PwDatabaseV4XML.ElemProtPassword, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemProtPassword, ignoreCase = true)) { mDatabase.memoryProtection.protectPassword = readBool(xpp, false) - } else if (name.equals(PwDatabaseV4XML.ElemProtURL, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemProtURL, ignoreCase = true)) { mDatabase.memoryProtection.protectUrl = readBool(xpp, false) - } else if (name.equals(PwDatabaseV4XML.ElemProtNotes, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemProtNotes, ignoreCase = true)) { mDatabase.memoryProtection.protectNotes = readBool(xpp, false) - } else if (name.equals(PwDatabaseV4XML.ElemProtAutoHide, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemProtAutoHide, ignoreCase = true)) { mDatabase.memoryProtection.autoEnableVisualHiding = readBool(xpp, false) } else { readUnknown(xpp) } - KdbContext.CustomIcons -> if (name.equals(PwDatabaseV4XML.ElemCustomIconItem, ignoreCase = true)) { + KdbContext.CustomIcons -> if (name.equals(DatabaseKDBXXML.ElemCustomIconItem, ignoreCase = true)) { return switchContext(ctx, KdbContext.CustomIcon, xpp) } else { readUnknown(xpp) } - KdbContext.CustomIcon -> if (name.equals(PwDatabaseV4XML.ElemCustomIconItemID, ignoreCase = true)) { + KdbContext.CustomIcon -> if (name.equals(DatabaseKDBXXML.ElemCustomIconItemID, ignoreCase = true)) { customIconID = readUuid(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemCustomIconItemData, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemCustomIconItemData, ignoreCase = true)) { val strData = readString(xpp) if (strData.isNotEmpty()) { - customIconData = Base64Coder.decode(strData) + customIconData = Base64.decode(strData, BASE_64_FLAG) } else { assert(false) } @@ -424,12 +437,12 @@ class ImporterV4(private val streamDir: File, readUnknown(xpp) } - KdbContext.Binaries -> if (name.equals(PwDatabaseV4XML.ElemBinary, ignoreCase = true)) { - val key = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrId) + KdbContext.Binaries -> if (name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) { + val key = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrId) if (key != null) { - val pbData = readProtectedBinary(xpp) + val pbData = readBinary(xpp) val id = Integer.parseInt(key) - mDatabase.binPool.put(id, pbData!!) + mDatabase.binaryPool.put(id, pbData!!) } else { readUnknown(xpp) } @@ -437,21 +450,21 @@ class ImporterV4(private val streamDir: File, readUnknown(xpp) } - KdbContext.CustomData -> if (name.equals(PwDatabaseV4XML.ElemStringDictExItem, ignoreCase = true)) { + KdbContext.CustomData -> if (name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) { return switchContext(ctx, KdbContext.CustomDataItem, xpp) } else { readUnknown(xpp) } - KdbContext.CustomDataItem -> if (name.equals(PwDatabaseV4XML.ElemKey, ignoreCase = true)) { + KdbContext.CustomDataItem -> if (name.equals(DatabaseKDBXXML.ElemKey, ignoreCase = true)) { customDataKey = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemValue, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemValue, ignoreCase = true)) { customDataValue = readString(xpp) } else { readUnknown(xpp) } - KdbContext.Root -> if (name.equals(PwDatabaseV4XML.ElemGroup, ignoreCase = true)) { + KdbContext.Root -> if (name.equals(DatabaseKDBXXML.ElemGroup, ignoreCase = true)) { if (ctxGroups.size != 0) throw IOException("Group list should be empty.") @@ -460,38 +473,38 @@ class ImporterV4(private val streamDir: File, ctxGroup = ctxGroups.peek() return switchContext(ctx, KdbContext.Group, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemDeletedObjects, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDeletedObjects, ignoreCase = true)) { return switchContext(ctx, KdbContext.RootDeletedObjects, xpp) } else { readUnknown(xpp) } - KdbContext.Group -> if (name.equals(PwDatabaseV4XML.ElemUuid, ignoreCase = true)) { - ctxGroup?.nodeId = PwNodeIdUUID(readUuid(xpp)) + KdbContext.Group -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) { + ctxGroup?.nodeId = NodeIdUUID(readUuid(xpp)) ctxGroup?.let { mDatabase.addGroupIndex(it) } - } else if (name.equals(PwDatabaseV4XML.ElemName, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemName, ignoreCase = true)) { ctxGroup?.title = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemNotes, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemNotes, ignoreCase = true)) { ctxGroup?.notes = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemIcon, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) { ctxGroup?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, 0).toInt()) - } else if (name.equals(PwDatabaseV4XML.ElemCustomIconID, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) { ctxGroup?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp)) - } else if (name.equals(PwDatabaseV4XML.ElemTimes, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) { return switchContext(ctx, KdbContext.GroupTimes, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemIsExpanded, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemIsExpanded, ignoreCase = true)) { ctxGroup?.isExpanded = readBool(xpp, true) - } else if (name.equals(PwDatabaseV4XML.ElemGroupDefaultAutoTypeSeq, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemGroupDefaultAutoTypeSeq, ignoreCase = true)) { ctxGroup?.defaultAutoTypeSequence = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemEnableAutoType, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemEnableAutoType, ignoreCase = true)) { ctxGroup?.enableAutoType = readOptionalBool(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemEnableSearching, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemEnableSearching, ignoreCase = true)) { ctxGroup?.enableSearching = readOptionalBool(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemLastTopVisibleEntry, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemLastTopVisibleEntry, ignoreCase = true)) { ctxGroup?.lastTopVisibleEntry = readUuid(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemCustomData, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) { return switchContext(ctx, KdbContext.GroupCustomData, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemGroup, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemGroup, ignoreCase = true)) { ctxGroup = mDatabase.createGroup() val groupPeek = ctxGroups.peek() ctxGroup?.let { @@ -501,7 +514,7 @@ class ImporterV4(private val streamDir: File, } return switchContext(ctx, KdbContext.Group, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemEntry, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemEntry, ignoreCase = true)) { ctxEntry = mDatabase.createEntry() ctxEntry?.let { ctxGroup?.addChildEntry(it) @@ -513,43 +526,43 @@ class ImporterV4(private val streamDir: File, } else { readUnknown(xpp) } - KdbContext.GroupCustomData -> if (name.equals(PwDatabaseV4XML.ElemStringDictExItem, ignoreCase = true)) { + KdbContext.GroupCustomData -> if (name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) { return switchContext(ctx, KdbContext.GroupCustomDataItem, xpp) } else { readUnknown(xpp) } KdbContext.GroupCustomDataItem -> when { - name.equals(PwDatabaseV4XML.ElemKey, ignoreCase = true) -> groupCustomDataKey = readString(xpp) - name.equals(PwDatabaseV4XML.ElemValue, ignoreCase = true) -> groupCustomDataValue = readString(xpp) + name.equals(DatabaseKDBXXML.ElemKey, ignoreCase = true) -> groupCustomDataKey = readString(xpp) + name.equals(DatabaseKDBXXML.ElemValue, ignoreCase = true) -> groupCustomDataValue = readString(xpp) else -> readUnknown(xpp) } - KdbContext.Entry -> if (name.equals(PwDatabaseV4XML.ElemUuid, ignoreCase = true)) { - ctxEntry?.nodeId = PwNodeIdUUID(readUuid(xpp)) - } else if (name.equals(PwDatabaseV4XML.ElemIcon, ignoreCase = true)) { + KdbContext.Entry -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) { + ctxEntry?.nodeId = NodeIdUUID(readUuid(xpp)) + } else if (name.equals(DatabaseKDBXXML.ElemIcon, ignoreCase = true)) { ctxEntry?.icon = mDatabase.iconFactory.getIcon(readUInt(xpp, 0).toInt()) - } else if (name.equals(PwDatabaseV4XML.ElemCustomIconID, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) { ctxEntry?.iconCustom = mDatabase.iconFactory.getIcon(readUuid(xpp)) - } else if (name.equals(PwDatabaseV4XML.ElemFgColor, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemFgColor, ignoreCase = true)) { ctxEntry?.foregroundColor = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemBgColor, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemBgColor, ignoreCase = true)) { ctxEntry?.backgroundColor = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemOverrideUrl, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemOverrideUrl, ignoreCase = true)) { ctxEntry?.overrideURL = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemTags, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemTags, ignoreCase = true)) { ctxEntry?.tags = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemTimes, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) { return switchContext(ctx, KdbContext.EntryTimes, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemString, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemString, ignoreCase = true)) { return switchContext(ctx, KdbContext.EntryString, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemBinary, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) { return switchContext(ctx, KdbContext.EntryBinary, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemAutoType, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemAutoType, ignoreCase = true)) { return switchContext(ctx, KdbContext.EntryAutoType, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemCustomData, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) { return switchContext(ctx, KdbContext.EntryCustomData, xpp) - } else if (name.equals(PwDatabaseV4XML.ElemHistory, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemHistory, ignoreCase = true)) { if (!entryInHistory) { ctxHistoryBase = ctxEntry return switchContext(ctx, KdbContext.EntryHistory, xpp) @@ -559,19 +572,19 @@ class ImporterV4(private val streamDir: File, } else { readUnknown(xpp) } - KdbContext.EntryCustomData -> if (name.equals(PwDatabaseV4XML.ElemStringDictExItem, ignoreCase = true)) { + KdbContext.EntryCustomData -> if (name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) { return switchContext(ctx, KdbContext.EntryCustomDataItem, xpp) } else { readUnknown(xpp) } KdbContext.EntryCustomDataItem -> when { - name.equals(PwDatabaseV4XML.ElemKey, ignoreCase = true) -> entryCustomDataKey = readString(xpp) - name.equals(PwDatabaseV4XML.ElemValue, ignoreCase = true) -> entryCustomDataValue = readString(xpp) + name.equals(DatabaseKDBXXML.ElemKey, ignoreCase = true) -> entryCustomDataKey = readString(xpp) + name.equals(DatabaseKDBXXML.ElemValue, ignoreCase = true) -> entryCustomDataValue = readString(xpp) else -> readUnknown(xpp) } KdbContext.GroupTimes, KdbContext.EntryTimes -> { - val tl: PwNodeV4Interface? = + val tl: NodeKDBXInterface? = if (ctx == KdbContext.GroupTimes) { ctxGroup } else { @@ -579,53 +592,53 @@ class ImporterV4(private val streamDir: File, } when { - name.equals(PwDatabaseV4XML.ElemLastModTime, ignoreCase = true) -> tl?.lastModificationTime = readPwTime(xpp) - name.equals(PwDatabaseV4XML.ElemCreationTime, ignoreCase = true) -> tl?.creationTime = readPwTime(xpp) - name.equals(PwDatabaseV4XML.ElemLastAccessTime, ignoreCase = true) -> tl?.lastAccessTime = readPwTime(xpp) - name.equals(PwDatabaseV4XML.ElemExpiryTime, ignoreCase = true) -> tl?.expiryTime = readPwTime(xpp) - name.equals(PwDatabaseV4XML.ElemExpires, ignoreCase = true) -> tl?.expires = readBool(xpp, false) - name.equals(PwDatabaseV4XML.ElemUsageCount, ignoreCase = true) -> tl?.usageCount = readULong(xpp, 0) - name.equals(PwDatabaseV4XML.ElemLocationChanged, ignoreCase = true) -> tl?.locationChanged = readPwTime(xpp) + name.equals(DatabaseKDBXXML.ElemLastModTime, ignoreCase = true) -> tl?.lastModificationTime = readPwTime(xpp) + name.equals(DatabaseKDBXXML.ElemCreationTime, ignoreCase = true) -> tl?.creationTime = readPwTime(xpp) + name.equals(DatabaseKDBXXML.ElemLastAccessTime, ignoreCase = true) -> tl?.lastAccessTime = readPwTime(xpp) + name.equals(DatabaseKDBXXML.ElemExpiryTime, ignoreCase = true) -> tl?.expiryTime = readPwTime(xpp) + name.equals(DatabaseKDBXXML.ElemExpires, ignoreCase = true) -> tl?.expires = readBool(xpp, false) + name.equals(DatabaseKDBXXML.ElemUsageCount, ignoreCase = true) -> tl?.usageCount = readULong(xpp, 0) + name.equals(DatabaseKDBXXML.ElemLocationChanged, ignoreCase = true) -> tl?.locationChanged = readPwTime(xpp) else -> readUnknown(xpp) } } - KdbContext.EntryString -> if (name.equals(PwDatabaseV4XML.ElemKey, ignoreCase = true)) { + KdbContext.EntryString -> if (name.equals(DatabaseKDBXXML.ElemKey, ignoreCase = true)) { ctxStringName = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemValue, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemValue, ignoreCase = true)) { ctxStringValue = readProtectedString(xpp) } else { readUnknown(xpp) } - KdbContext.EntryBinary -> if (name.equals(PwDatabaseV4XML.ElemKey, ignoreCase = true)) { + KdbContext.EntryBinary -> if (name.equals(DatabaseKDBXXML.ElemKey, ignoreCase = true)) { ctxBinaryName = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemValue, ignoreCase = true)) { - ctxBinaryValue = readProtectedBinary(xpp) + } else if (name.equals(DatabaseKDBXXML.ElemValue, ignoreCase = true)) { + ctxBinaryValue = readBinary(xpp) } - KdbContext.EntryAutoType -> if (name.equals(PwDatabaseV4XML.ElemAutoTypeEnabled, ignoreCase = true)) { + KdbContext.EntryAutoType -> if (name.equals(DatabaseKDBXXML.ElemAutoTypeEnabled, ignoreCase = true)) { ctxEntry?.autoType?.enabled = readBool(xpp, true) - } else if (name.equals(PwDatabaseV4XML.ElemAutoTypeObfuscation, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemAutoTypeObfuscation, ignoreCase = true)) { ctxEntry?.autoType?.obfuscationOptions = readUInt(xpp, 0) - } else if (name.equals(PwDatabaseV4XML.ElemAutoTypeDefaultSeq, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, ignoreCase = true)) { ctxEntry?.autoType?.defaultSequence = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemAutoTypeItem, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemAutoTypeItem, ignoreCase = true)) { return switchContext(ctx, KdbContext.EntryAutoTypeItem, xpp) } else { readUnknown(xpp) } - KdbContext.EntryAutoTypeItem -> if (name.equals(PwDatabaseV4XML.ElemWindow, ignoreCase = true)) { + KdbContext.EntryAutoTypeItem -> if (name.equals(DatabaseKDBXXML.ElemWindow, ignoreCase = true)) { ctxATName = readString(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemKeystrokeSequence, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemKeystrokeSequence, ignoreCase = true)) { ctxATSeq = readString(xpp) } else { readUnknown(xpp) } - KdbContext.EntryHistory -> if (name.equals(PwDatabaseV4XML.ElemEntry, ignoreCase = true)) { - ctxEntry = PwEntryV4() + KdbContext.EntryHistory -> if (name.equals(DatabaseKDBXXML.ElemEntry, ignoreCase = true)) { + ctxEntry = EntryKDBX() ctxEntry?.let { ctxHistoryBase?.addEntryToHistory(it) } entryInHistory = true @@ -634,8 +647,8 @@ class ImporterV4(private val streamDir: File, readUnknown(xpp) } - KdbContext.RootDeletedObjects -> if (name.equals(PwDatabaseV4XML.ElemDeletedObject, ignoreCase = true)) { - ctxDeletedObject = PwDeletedObject() + KdbContext.RootDeletedObjects -> if (name.equals(DatabaseKDBXXML.ElemDeletedObject, ignoreCase = true)) { + ctxDeletedObject = DeletedObject() ctxDeletedObject?.let { mDatabase.addDeletedObject(it) } return switchContext(ctx, KdbContext.DeletedObject, xpp) @@ -643,9 +656,9 @@ class ImporterV4(private val streamDir: File, readUnknown(xpp) } - KdbContext.DeletedObject -> if (name.equals(PwDatabaseV4XML.ElemUuid, ignoreCase = true)) { + KdbContext.DeletedObject -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) { ctxDeletedObject?.uuid = readUuid(xpp) - } else if (name.equals(PwDatabaseV4XML.ElemDeletionTime, ignoreCase = true)) { + } else if (name.equals(DatabaseKDBXXML.ElemDeletionTime, ignoreCase = true)) { ctxDeletedObject?.deletionTime = readTime(xpp) } else { readUnknown(xpp) @@ -660,32 +673,32 @@ class ImporterV4(private val streamDir: File, // (xpp.getEventType() == XmlPullParser.END_TAG); val name = xpp.name - if (ctx == KdbContext.KeePassFile && name.equals(PwDatabaseV4XML.ElemDocNode, ignoreCase = true)) { + if (ctx == KdbContext.KeePassFile && name.equals(DatabaseKDBXXML.ElemDocNode, ignoreCase = true)) { return KdbContext.Null - } else if (ctx == KdbContext.Meta && name.equals(PwDatabaseV4XML.ElemMeta, ignoreCase = true)) { + } else if (ctx == KdbContext.Meta && name.equals(DatabaseKDBXXML.ElemMeta, ignoreCase = true)) { return KdbContext.KeePassFile - } else if (ctx == KdbContext.Root && name.equals(PwDatabaseV4XML.ElemRoot, ignoreCase = true)) { + } else if (ctx == KdbContext.Root && name.equals(DatabaseKDBXXML.ElemRoot, ignoreCase = true)) { return KdbContext.KeePassFile - } else if (ctx == KdbContext.MemoryProtection && name.equals(PwDatabaseV4XML.ElemMemoryProt, ignoreCase = true)) { + } else if (ctx == KdbContext.MemoryProtection && name.equals(DatabaseKDBXXML.ElemMemoryProt, ignoreCase = true)) { return KdbContext.Meta - } else if (ctx == KdbContext.CustomIcons && name.equals(PwDatabaseV4XML.ElemCustomIcons, ignoreCase = true)) { + } else if (ctx == KdbContext.CustomIcons && name.equals(DatabaseKDBXXML.ElemCustomIcons, ignoreCase = true)) { return KdbContext.Meta - } else if (ctx == KdbContext.CustomIcon && name.equals(PwDatabaseV4XML.ElemCustomIconItem, ignoreCase = true)) { - if (customIconID != PwDatabase.UUID_ZERO && customIconData != null) { - val icon = PwIconCustom(customIconID, customIconData!!) + } else if (ctx == KdbContext.CustomIcon && name.equals(DatabaseKDBXXML.ElemCustomIconItem, ignoreCase = true)) { + if (customIconID != DatabaseVersioned.UUID_ZERO && customIconData != null) { + val icon = IconImageCustom(customIconID, customIconData!!) mDatabase.addCustomIcon(icon) mDatabase.iconFactory.put(icon) } - customIconID = PwDatabase.UUID_ZERO + customIconID = DatabaseVersioned.UUID_ZERO customIconData = null return KdbContext.CustomIcons - } else if (ctx == KdbContext.Binaries && name.equals(PwDatabaseV4XML.ElemBinaries, ignoreCase = true)) { + } else if (ctx == KdbContext.Binaries && name.equals(DatabaseKDBXXML.ElemBinaries, ignoreCase = true)) { return KdbContext.Meta - } else if (ctx == KdbContext.CustomData && name.equals(PwDatabaseV4XML.ElemCustomData, ignoreCase = true)) { + } else if (ctx == KdbContext.CustomData && name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) { return KdbContext.Meta - } else if (ctx == KdbContext.CustomDataItem && name.equals(PwDatabaseV4XML.ElemStringDictExItem, ignoreCase = true)) { + } else if (ctx == KdbContext.CustomDataItem && name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) { if (customDataKey != null && customDataValue != null) { mDatabase.putCustomData(customDataKey!!, customDataValue!!) } @@ -694,8 +707,8 @@ class ImporterV4(private val streamDir: File, customDataValue = null return KdbContext.CustomData - } else if (ctx == KdbContext.Group && name.equals(PwDatabaseV4XML.ElemGroup, ignoreCase = true)) { - if (ctxGroup != null && ctxGroup?.id == PwDatabase.UUID_ZERO) { + } else if (ctx == KdbContext.Group && name.equals(DatabaseKDBXXML.ElemGroup, ignoreCase = true)) { + if (ctxGroup != null && ctxGroup?.id == DatabaseVersioned.UUID_ZERO) { ctxGroup?.nodeId = mDatabase.newGroupId() mDatabase.addGroupIndex(ctxGroup!!) } @@ -709,11 +722,11 @@ class ImporterV4(private val streamDir: File, ctxGroup = ctxGroups.peek() return KdbContext.Group } - } else if (ctx == KdbContext.GroupTimes && name.equals(PwDatabaseV4XML.ElemTimes, ignoreCase = true)) { + } else if (ctx == KdbContext.GroupTimes && name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) { return KdbContext.Group - } else if (ctx == KdbContext.GroupCustomData && name.equals(PwDatabaseV4XML.ElemCustomData, ignoreCase = true)) { + } else if (ctx == KdbContext.GroupCustomData && name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) { return KdbContext.Group - } else if (ctx == KdbContext.GroupCustomDataItem && name.equals(PwDatabaseV4XML.ElemStringDictExItem, ignoreCase = true)) { + } else if (ctx == KdbContext.GroupCustomDataItem && name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) { if (groupCustomDataKey != null && groupCustomDataValue != null) { ctxGroup?.putCustomData(groupCustomDataKey!!, groupCustomDataValue!!) } @@ -723,9 +736,9 @@ class ImporterV4(private val streamDir: File, return KdbContext.GroupCustomData - } else if (ctx == KdbContext.Entry && name.equals(PwDatabaseV4XML.ElemEntry, ignoreCase = true)) { + } else if (ctx == KdbContext.Entry && name.equals(DatabaseKDBXXML.ElemEntry, ignoreCase = true)) { - if (ctxEntry?.id == PwDatabase.UUID_ZERO) + if (ctxEntry?.id == DatabaseVersioned.UUID_ZERO) ctxEntry?.nodeId = mDatabase.newEntryId() if (entryInHistory) { @@ -738,34 +751,34 @@ class ImporterV4(private val streamDir: File, } return KdbContext.Group - } else if (ctx == KdbContext.EntryTimes && name.equals(PwDatabaseV4XML.ElemTimes, ignoreCase = true)) { + } else if (ctx == KdbContext.EntryTimes && name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) { return KdbContext.Entry - } else if (ctx == KdbContext.EntryString && name.equals(PwDatabaseV4XML.ElemString, ignoreCase = true)) { + } else if (ctx == KdbContext.EntryString && name.equals(DatabaseKDBXXML.ElemString, ignoreCase = true)) { if (ctxStringName != null && ctxStringValue != null) ctxEntry?.putExtraField(ctxStringName!!, ctxStringValue!!) ctxStringName = null ctxStringValue = null return KdbContext.Entry - } else if (ctx == KdbContext.EntryBinary && name.equals(PwDatabaseV4XML.ElemBinary, ignoreCase = true)) { + } else if (ctx == KdbContext.EntryBinary && name.equals(DatabaseKDBXXML.ElemBinary, ignoreCase = true)) { if (ctxBinaryName != null && ctxBinaryValue != null) ctxEntry?.putProtectedBinary(ctxBinaryName!!, ctxBinaryValue!!) ctxBinaryName = null ctxBinaryValue = null return KdbContext.Entry - } else if (ctx == KdbContext.EntryAutoType && name.equals(PwDatabaseV4XML.ElemAutoType, ignoreCase = true)) { + } else if (ctx == KdbContext.EntryAutoType && name.equals(DatabaseKDBXXML.ElemAutoType, ignoreCase = true)) { return KdbContext.Entry - } else if (ctx == KdbContext.EntryAutoTypeItem && name.equals(PwDatabaseV4XML.ElemAutoTypeItem, ignoreCase = true)) { + } else if (ctx == KdbContext.EntryAutoTypeItem && name.equals(DatabaseKDBXXML.ElemAutoTypeItem, ignoreCase = true)) { if (ctxATName != null && ctxATSeq != null) ctxEntry?.autoType?.put(ctxATName!!, ctxATSeq!!) ctxATName = null ctxATSeq = null return KdbContext.EntryAutoType - } else if (ctx == KdbContext.EntryCustomData && name.equals(PwDatabaseV4XML.ElemCustomData, ignoreCase = true)) { + } else if (ctx == KdbContext.EntryCustomData && name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) { return KdbContext.Entry - } else if (ctx == KdbContext.EntryCustomDataItem && name.equals(PwDatabaseV4XML.ElemStringDictExItem, ignoreCase = true)) { + } else if (ctx == KdbContext.EntryCustomDataItem && name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) { if (entryCustomDataKey != null && entryCustomDataValue != null) { ctxEntry?.putCustomData(entryCustomDataKey!!, entryCustomDataValue!!) } @@ -774,12 +787,12 @@ class ImporterV4(private val streamDir: File, entryCustomDataValue = null return KdbContext.EntryCustomData - } else if (ctx == KdbContext.EntryHistory && name.equals(PwDatabaseV4XML.ElemHistory, ignoreCase = true)) { + } else if (ctx == KdbContext.EntryHistory && name.equals(DatabaseKDBXXML.ElemHistory, ignoreCase = true)) { entryInHistory = false return KdbContext.Entry - } else if (ctx == KdbContext.RootDeletedObjects && name.equals(PwDatabaseV4XML.ElemDeletedObjects, ignoreCase = true)) { + } else if (ctx == KdbContext.RootDeletedObjects && name.equals(DatabaseKDBXXML.ElemDeletedObjects, ignoreCase = true)) { return KdbContext.Root - } else if (ctx == KdbContext.DeletedObject && name.equals(PwDatabaseV4XML.ElemDeletedObject, ignoreCase = true)) { + } else if (ctx == KdbContext.DeletedObject && name.equals(DatabaseKDBXXML.ElemDeletedObject, ignoreCase = true)) { ctxDeletedObject = null return KdbContext.RootDeletedObjects } else { @@ -792,8 +805,8 @@ class ImporterV4(private val streamDir: File, } @Throws(IOException::class, XmlPullParserException::class) - private fun readPwTime(xpp: XmlPullParser): PwDate { - return PwDate(readTime(xpp)) + private fun readPwTime(xpp: XmlPullParser): DateInstant { + return DateInstant(readTime(xpp)) } @Throws(IOException::class, XmlPullParserException::class) @@ -801,8 +814,8 @@ class ImporterV4(private val streamDir: File, val sDate = readString(xpp) var utcDate: Date? = null - if (version >= PwDbHeaderV4.FILE_VERSION_32_4) { - var buf = Base64Coder.decode(sDate) + if (mDatabase.kdbxVersion >= DatabaseHeaderKDBX.FILE_VERSION_32_4) { + var buf = Base64.decode(sDate, BASE_64_FLAG) if (buf.size != 8) { val buf8 = ByteArray(8) System.arraycopy(buf, 0, buf8, 0, min(buf.size, 8)) @@ -810,12 +823,12 @@ class ImporterV4(private val streamDir: File, } val seconds = LEDataInputStream.readLong(buf, 0) - utcDate = KDBX4DateUtil.convertKDBX4Time(seconds) + utcDate = DateKDBXUtil.convertKDBX4Time(seconds) } else { try { - utcDate = PwDatabaseV4XML.dateFormatter.get()?.parse(sDate) + utcDate = DatabaseKDBXXML.dateFormatter.get()?.parse(sDate) } catch (e: ParseException) { // Catch with null test below } @@ -828,7 +841,7 @@ class ImporterV4(private val streamDir: File, private fun readUnknown(xpp: XmlPullParser) { if (xpp.isEmptyElementTag) return - processNode(xpp) + readProtectedBase64String(xpp) while (xpp.next() != XmlPullParser.END_DOCUMENT) { if (xpp.eventType == XmlPullParser.END_TAG) break if (xpp.eventType == XmlPullParser.START_TAG) continue @@ -842,8 +855,8 @@ class ImporterV4(private val streamDir: File, val str = readString(xpp) return when { - str.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true) -> true - str.equals(PwDatabaseV4XML.ValFalse, ignoreCase = true) -> false + str.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true) -> true + str.equals(DatabaseKDBXXML.ValFalse, ignoreCase = true) -> false else -> bDefault } } @@ -853,30 +866,23 @@ class ImporterV4(private val streamDir: File, val str = readString(xpp) return when { - str.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true) -> true - str.equals(PwDatabaseV4XML.ValFalse, ignoreCase = true) -> false - str.equals(PwDatabaseV4XML.ValNull, ignoreCase = true) -> null + str.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true) -> true + str.equals(DatabaseKDBXXML.ValFalse, ignoreCase = true) -> false + str.equals(DatabaseKDBXXML.ValNull, ignoreCase = true) -> null else -> bDefault } } - @Throws(IOException::class, XmlPullParserException::class) - private fun readPwNodeIdUuid(xpp: XmlPullParser): PwNodeIdUUID { - return PwNodeIdUUID(readUuid(xpp)) - } - @Throws(IOException::class, XmlPullParserException::class) private fun readUuid(xpp: XmlPullParser): UUID { val encoded = readString(xpp) if (encoded.isEmpty()) { - return PwDatabase.UUID_ZERO + return DatabaseVersioned.UUID_ZERO } + val buf = Base64.decode(encoded, BASE_64_FLAG) - // TODO: Switch to framework Base64 once API level 8 is the minimum - val buf = Base64Coder.decode(encoded) - - return Types.bytestoUUID(buf) + return DatabaseInputOutputUtils.bytesToUuid(buf) } @Throws(IOException::class, XmlPullParserException::class) @@ -926,7 +932,7 @@ class ImporterV4(private val streamDir: File, @Throws(XmlPullParserException::class, IOException::class) private fun readProtectedString(xpp: XmlPullParser): ProtectedString { - val buf = processNode(xpp) + val buf = readProtectedBase64String(xpp) if (buf != null) { try { @@ -935,61 +941,63 @@ class ImporterV4(private val streamDir: File, e.printStackTrace() throw IOException(e.localizedMessage) } - } return ProtectedString(false, readString(xpp)) } - @Throws(IOException::class) - private fun createProtectedBinaryFromData(protection: Boolean, data: ByteArray): ProtectedBinary { - return if (data.size > MemoryUtil.BUFFER_SIZE_BYTES) { - val file = File(streamDir, unusedCacheFileName) - FileOutputStream(file).use { outputStream -> outputStream.write(data) } - ProtectedBinary(protection, file, data.size) - } else { - ProtectedBinary(protection, data) - } - } - @Throws(XmlPullParserException::class, IOException::class) - private fun readProtectedBinary(xpp: XmlPullParser): ProtectedBinary? { - val ref = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrRef) + private fun readBinary(xpp: XmlPullParser): BinaryAttachment? { + + // Reference Id to a binary already present in binary pool + val ref = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrRef) if (ref != null) { xpp.next() // Consume end tag val id = Integer.parseInt(ref) - return mDatabase.binPool[id] - } - - var compressed = false - val comp = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrCompressed) - if (comp != null) { - compressed = comp.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true) + return mDatabase.binaryPool[id] } - val buf = processNode(xpp) + // New binary to retrieve + else { + var compressed: Boolean? = null + var protected = false - if (buf != null) { - createProtectedBinaryFromData(true, buf) - } + if (xpp.attributeCount > 0) { + val compress = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrCompressed) + if (compress != null) { + compressed = compress.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true) + } - val base64 = readString(xpp) - if (base64.isEmpty()) - return ProtectedBinary() + val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected) + if (protect != null) { + protected = protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true) + } + } - var data = Base64Coder.decode(base64) + val base64 = readString(xpp) + if (base64.isEmpty()) + return BinaryAttachment() + val data = Base64.decode(base64, BASE_64_FLAG) - if (compressed) { - data = MemoryUtil.decompress(data) + val file = File(streamDir, unusedCacheFileName) + return FileOutputStream(file).use { outputStream -> + // Force compression in this specific case + if (mDatabase.compressionAlgorithm == CompressionAlgorithm.GZip + && compressed == false) { + GZIPOutputStream(outputStream).write(data) + BinaryAttachment(file, protected, true) + } else { + outputStream.write(data) + BinaryAttachment(file, protected) + } + } } - - return createProtectedBinaryFromData(false, data) } @Throws(IOException::class, XmlPullParserException::class) private fun readString(xpp: XmlPullParser): String { - val buf = processNode(xpp) + val buf = readProtectedBase64String(xpp) if (buf != null) { try { @@ -997,32 +1005,30 @@ class ImporterV4(private val streamDir: File, } catch (e: UnsupportedEncodingException) { throw IOException(e) } - } return xpp.safeNextText() - } @Throws(XmlPullParserException::class, IOException::class) private fun readBase64String(xpp: XmlPullParser): ByteArray { //readNextNode = false; - Base64Coder.decode(xpp.safeNextText())?.let { buffer -> - val plainText = ByteArray(buffer.size) - randomStream?.processBytes(buffer, 0, buffer.size, plainText, 0) + Base64.decode(xpp.safeNextText(), BASE_64_FLAG)?.let { data -> + val plainText = ByteArray(data.size) + randomStream?.processBytes(data, 0, data.size, plainText, 0) return plainText } return ByteArray(0) } @Throws(XmlPullParserException::class, IOException::class) - private fun processNode(xpp: XmlPullParser): ByteArray? { + private fun readProtectedBase64String(xpp: XmlPullParser): ByteArray? { //(xpp.getEventType() == XmlPullParser.START_TAG); if (xpp.attributeCount > 0) { - val protect = xpp.getAttributeValue(null, PwDatabaseV4XML.AttrProtected) - if (protect != null && protect.equals(PwDatabaseV4XML.ValTrue, ignoreCase = true)) { + val protect = xpp.getAttributeValue(null, DatabaseKDBXXML.AttrProtected) + if (protect != null && protect.equals(DatabaseKDBXXML.ValTrue, ignoreCase = true)) { return readBase64String(xpp) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutput.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutput.kt similarity index 90% rename from app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutput.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutput.kt index c60316549..d279bd7f8 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutput.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutput.kt @@ -17,9 +17,9 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.file.save +package com.kunzisoft.keepass.database.file.output -open class PwDbHeaderOutput { +open class DatabaseHeaderOutput { var hashOfHeader: ByteArray? = null protected set } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDB.kt similarity index 89% rename from app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDB.kt index ee38fc881..749c87ad0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDB.kt @@ -17,15 +17,15 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.file.save +package com.kunzisoft.keepass.database.file.output -import com.kunzisoft.keepass.database.file.PwDbHeaderV3 +import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.stream.LEDataOutputStream import java.io.IOException import java.io.OutputStream -class PwDbHeaderOutputV3(private val mHeader: PwDbHeaderV3, private val mOS: OutputStream) { +class DatabaseHeaderOutputKDB(private val mHeader: DatabaseHeaderKDB, private val mOS: OutputStream) { @Throws(IOException::class) fun outputStart() { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt similarity index 60% rename from app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt index 6a5720825..520090d65 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbHeaderOutputV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseHeaderOutputKDBX.kt @@ -17,18 +17,18 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.file.save +package com.kunzisoft.keepass.database.file.output import com.kunzisoft.keepass.utils.VariantDictionary import com.kunzisoft.keepass.crypto.keyDerivation.KdfParameters -import com.kunzisoft.keepass.database.element.PwDatabaseV4 -import com.kunzisoft.keepass.database.file.PwDbHeader -import com.kunzisoft.keepass.database.file.PwDbHeaderV4 +import com.kunzisoft.keepass.database.element.database.DatabaseKDBX +import com.kunzisoft.keepass.database.file.DatabaseHeader +import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.exception.DatabaseOutputException import com.kunzisoft.keepass.stream.HmacBlockStream import com.kunzisoft.keepass.stream.LEDataOutputStream import com.kunzisoft.keepass.stream.MacOutputStream -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import java.io.ByteArrayOutputStream import java.io.IOException @@ -41,8 +41,8 @@ import java.security.NoSuchAlgorithmException import javax.crypto.Mac import javax.crypto.spec.SecretKeySpec -class PwDbHeaderOutputV4 @Throws(DatabaseOutputException::class) -constructor(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os: OutputStream) : PwDbHeaderOutput() { +class DatabaseHeaderOutputKDBX @Throws(DatabaseOutputException::class) +constructor(private val db: DatabaseKDBX, private val header: DatabaseHeaderKDBX, os: OutputStream) : DatabaseHeaderOutput() { private val los: LEDataOutputStream private val mos: MacOutputStream private val dos: DigestOutputStream @@ -54,7 +54,7 @@ constructor(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os: try { md = MessageDigest.getInstance("SHA-256") } catch (e: NoSuchAlgorithmException) { - throw DatabaseOutputException("SHA-256 not implemented here.") + throw DatabaseOutputException("SHA-256 not implemented here.", e) } try { @@ -66,7 +66,7 @@ constructor(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os: val hmac: Mac try { hmac = Mac.getInstance("HmacSHA256") - val signingKey = SecretKeySpec(HmacBlockStream.GetHmacKey64(db.hmacKey, Types.ULONG_MAX_VALUE), "HmacSHA256") + val signingKey = SecretKeySpec(HmacBlockStream.GetHmacKey64(db.hmacKey, DatabaseInputOutputUtils.ULONG_MAX_VALUE), "HmacSHA256") hmac.init(signingKey) } catch (e: NoSuchAlgorithmException) { throw DatabaseOutputException(e) @@ -82,39 +82,39 @@ constructor(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os: @Throws(IOException::class) fun output() { - los.writeUInt(PwDbHeader.PWM_DBSIG_1.toLong()) - los.writeUInt(PwDbHeaderV4.DBSIG_2.toLong()) + los.writeUInt(DatabaseHeader.PWM_DBSIG_1.toLong()) + los.writeUInt(DatabaseHeaderKDBX.DBSIG_2.toLong()) los.writeUInt(header.version) - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.CipherID, Types.UUIDtoBytes(db.dataCipher)) - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.CompressionFlags, LEDataOutputStream.writeIntBuf(PwDbHeaderV4.getFlagFromCompression(db.compressionAlgorithm))) - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.MasterSeed, header.masterSeed) + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CipherID, DatabaseInputOutputUtils.uuidToBytes(db.dataCipher)) + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, LEDataOutputStream.writeIntBuf(DatabaseHeaderKDBX.getFlagFromCompression(db.compressionAlgorithm))) + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed) - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.TransformSeed, header.transformSeed) - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.TransformRounds, LEDataOutputStream.writeLongBuf(db.numberKeyEncryptionRounds)) + if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed) + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, LEDataOutputStream.writeLongBuf(db.numberKeyEncryptionRounds)) } else { - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.KdfParameters, KdfParameters.serialize(db.kdfParameters)) + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.KdfParameters, KdfParameters.serialize(db.kdfParameters!!)) } if (header.encryptionIV.isNotEmpty()) { - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV) + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV) } - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey) - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes) - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.InnerRandomStreamID, LEDataOutputStream.writeIntBuf(header.innerRandomStream!!.id)) + if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey) + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes) + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, LEDataOutputStream.writeIntBuf(header.innerRandomStream!!.id)) } if (db.containsPublicCustomData()) { val bos = ByteArrayOutputStream() val los = LEDataOutputStream(bos) VariantDictionary.serialize(db.publicCustomData, los) - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray()) + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.PublicCustomData, bos.toByteArray()) } - writeHeaderField(PwDbHeaderV4.PwDbHeaderV4Fields.EndOfHeader, EndHeaderValue) + writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EndOfHeader, EndHeaderValue) los.flush() hashOfHeader = dos.messageDigest.digest() @@ -136,7 +136,7 @@ constructor(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os: @Throws(IOException::class) private fun writeHeaderFieldSize(size: Int) { - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { + if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { los.writeUShort(size) } else { los.writeInt(size) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseInnerHeaderOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseInnerHeaderOutputKDBX.kt new file mode 100644 index 000000000..02990a065 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseInnerHeaderOutputKDBX.kt @@ -0,0 +1,73 @@ +/* + * Copyright 2019 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePassDroid 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. + * + * KeePassDroid 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 KeePassDroid. If not, see . + * + */ +package com.kunzisoft.keepass.database.file.output + +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.file.DatabaseHeaderKDBX +import com.kunzisoft.keepass.stream.ReadBytes +import com.kunzisoft.keepass.stream.LEDataOutputStream +import com.kunzisoft.keepass.stream.readFromStream +import java.io.IOException +import java.io.OutputStream +import kotlin.experimental.or + +class DatabaseInnerHeaderOutputKDBX(private val database: DatabaseKDBX, + private val header: DatabaseHeaderKDBX, + outputStream: OutputStream) { + + private val dataOutputStream: LEDataOutputStream = LEDataOutputStream(outputStream) + + @Throws(IOException::class) + fun output() { + dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomStreamID.toInt()) + dataOutputStream.writeInt(4) + if (header.innerRandomStream == null) + throw IOException("Can't write innerRandomStream") + dataOutputStream.writeInt(header.innerRandomStream!!.id) + + val streamKeySize = header.innerRandomStreamKey.size + dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.InnerRandomstreamKey.toInt()) + dataOutputStream.writeInt(streamKeySize) + dataOutputStream.write(header.innerRandomStreamKey) + + database.binaryPool.doForEachBinary { _, protectedBinary -> + var flag = DatabaseHeaderKDBX.KdbxBinaryFlags.None + if (protectedBinary.isProtected) { + flag = flag or DatabaseHeaderKDBX.KdbxBinaryFlags.Protected + } + + dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.Binary.toInt()) + dataOutputStream.writeInt(protectedBinary.length().toInt() + 1) // TODO verify + dataOutputStream.write(flag.toInt()) + + readFromStream(protectedBinary.getInputDataStream(), BUFFER_SIZE_BYTES, + object : ReadBytes { + override fun read(buffer: ByteArray) { + dataOutputStream.write(buffer) + } + } + ) + } + + dataOutputStream.write(DatabaseHeaderKDBX.PwDbInnerHeaderV4Fields.EndOfHeader.toInt()) + dataOutputStream.writeInt(0) + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbOutput.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutput.kt similarity index 86% rename from app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbOutput.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutput.kt index a670182ca..27f5abdea 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbOutput.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutput.kt @@ -17,16 +17,16 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.file.save +package com.kunzisoft.keepass.database.file.output -import com.kunzisoft.keepass.database.file.PwDbHeader +import com.kunzisoft.keepass.database.file.DatabaseHeader import com.kunzisoft.keepass.database.exception.DatabaseOutputException import java.io.OutputStream import java.security.NoSuchAlgorithmException import java.security.SecureRandom -abstract class PwDbOutput
protected constructor(protected var mOS: OutputStream) { +abstract class DatabaseOutput
protected constructor(protected var mOS: OutputStream) { @Throws(DatabaseOutputException::class) protected open fun setIVs(header: Header): SecureRandom { @@ -34,7 +34,7 @@ abstract class PwDbOutput
protected constructor(protected v try { random = SecureRandom.getInstance("SHA1PRNG") } catch (e: NoSuchAlgorithmException) { - throw DatabaseOutputException("Does not support secure random number generation.") + throw DatabaseOutputException("Does not support secure random number generation.", e) } random.nextBytes(header.encryptionIV) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV3Output.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt similarity index 72% rename from app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV3Output.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt index 5a2710cbe..6a6a9bd33 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV3Output.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDB.kt @@ -17,37 +17,40 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.file.save +package com.kunzisoft.keepass.database.file.output import com.kunzisoft.keepass.crypto.CipherFactory -import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.element.database.DatabaseKDB +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm +import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.exception.DatabaseOutputException -import com.kunzisoft.keepass.database.file.PwDbHeader -import com.kunzisoft.keepass.database.file.PwDbHeaderV3 +import com.kunzisoft.keepass.database.file.DatabaseHeader +import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.stream.LEDataOutputStream import com.kunzisoft.keepass.stream.NullOutputStream - -import javax.crypto.Cipher -import javax.crypto.CipherOutputStream -import javax.crypto.spec.IvParameterSpec -import javax.crypto.spec.SecretKeySpec import java.io.BufferedOutputStream import java.io.ByteArrayOutputStream import java.io.IOException import java.io.OutputStream import java.security.* -import java.util.ArrayList +import java.util.* +import javax.crypto.Cipher +import javax.crypto.CipherOutputStream +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec -class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : PwDbOutput(os) { +class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB, + outputStream: OutputStream) + : DatabaseOutput(outputStream) { private var headerHashBlock: ByteArray? = null @Throws(DatabaseOutputException::class) - fun getFinalKey(header: PwDbHeader): ByteArray? { + fun getFinalKey(header: DatabaseHeader): ByteArray? { try { - val h3 = header as PwDbHeaderV3 - mDatabaseV3.makeFinalKey(h3.masterSeed, h3.transformSeed, mDatabaseV3.numberKeyEncryptionRounds) - return mDatabaseV3.finalKey + val headerKDB = header as DatabaseHeaderKDB + mDatabaseKDB.makeFinalKey(headerKDB.masterSeed, headerKDB.transformSeed, mDatabaseKDB.numberKeyEncryptionRounds) + return mDatabaseKDB.finalKey } catch (e: IOException) { throw DatabaseOutputException("Key creation failed.", e) } @@ -66,9 +69,9 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw val cipher: Cipher cipher = try { when { - mDatabaseV3.encryptionAlgorithm === PwEncryptionAlgorithm.AESRijndael-> + mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael-> CipherFactory.getInstance("AES/CBC/PKCS5Padding") - mDatabaseV3.encryptionAlgorithm === PwEncryptionAlgorithm.Twofish -> + mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> CipherFactory.getInstance("Twofish/CBC/PKCS7PADDING") else -> throw Exception() @@ -78,7 +81,9 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw } try { - cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(finalKey, "AES"), IvParameterSpec(header.encryptionIV)) + cipher.init(Cipher.ENCRYPT_MODE, + SecretKeySpec(finalKey, "AES"), + IvParameterSpec(header.encryptionIV)) val cos = CipherOutputStream(mOS, cipher) val bos = BufferedOutputStream(cos) outputPlanGroupAndEntries(bos) @@ -96,32 +101,34 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw } @Throws(DatabaseOutputException::class) - override fun setIVs(header: PwDbHeaderV3): SecureRandom { + override fun setIVs(header: DatabaseHeaderKDB): SecureRandom { val random = super.setIVs(header) random.nextBytes(header.transformSeed) return random } @Throws(DatabaseOutputException::class) - override fun outputHeader(outputStream: OutputStream): PwDbHeaderV3 { + override fun outputHeader(outputStream: OutputStream): DatabaseHeaderKDB { // Build header - val header = PwDbHeaderV3() - header.signature1 = PwDbHeader.PWM_DBSIG_1 - header.signature2 = PwDbHeaderV3.DBSIG_2 - header.flags = PwDbHeaderV3.FLAG_SHA2 - - if (mDatabaseV3.encryptionAlgorithm === PwEncryptionAlgorithm.AESRijndael) { - header.flags = header.flags or PwDbHeaderV3.FLAG_RIJNDAEL - } else if (mDatabaseV3.encryptionAlgorithm === PwEncryptionAlgorithm.Twofish) { - header.flags = header.flags or PwDbHeaderV3.FLAG_TWOFISH - } else { - throw DatabaseOutputException("Unsupported algorithm.") + val header = DatabaseHeaderKDB() + header.signature1 = DatabaseHeader.PWM_DBSIG_1 + header.signature2 = DatabaseHeaderKDB.DBSIG_2 + header.flags = DatabaseHeaderKDB.FLAG_SHA2 + + when { + mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.AESRijndael -> { + header.flags = header.flags or DatabaseHeaderKDB.FLAG_RIJNDAEL + } + mDatabaseKDB.encryptionAlgorithm === EncryptionAlgorithm.Twofish -> { + header.flags = header.flags or DatabaseHeaderKDB.FLAG_TWOFISH + } + else -> throw DatabaseOutputException("Unsupported algorithm.") } - header.version = PwDbHeaderV3.DBVER_DW - header.numGroups = mDatabaseV3.numberOfGroups() - header.numEntries = mDatabaseV3.numberOfEntries() - header.numKeyEncRounds = mDatabaseV3.numberKeyEncryptionRounds.toInt() + header.version = DatabaseHeaderKDB.DBVER_DW + header.numGroups = mDatabaseKDB.numberOfGroups() + header.numEntries = mDatabaseKDB.numberOfEntries() + header.numKeyEncRounds = mDatabaseKDB.numberKeyEncryptionRounds.toInt() setIVs(header) @@ -145,7 +152,7 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw val headerDos = DigestOutputStream(nos, headerDigest) // Output header for the purpose of calculating the header checksum - var pho = PwDbHeaderOutputV3(header, headerDos) + var pho = DatabaseHeaderOutputKDB(header, headerDos) try { pho.outputStart() pho.outputEnd() @@ -172,7 +179,7 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw header.contentsHash = messageDigest!!.digest() // Output header for real output, containing content hash - pho = PwDbHeaderOutputV3(header, outputStream) + pho = DatabaseHeaderOutputKDB(header, outputStream) try { pho.outputStart() dos.on(false) @@ -204,16 +211,16 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw } // Groups - mDatabaseV3.doForEachGroupInIndex { group -> - val pgo = PwGroupOutputV3(group, os) + mDatabaseKDB.doForEachGroupInIndex { group -> + val pgo = GroupOutputKDB(group, os) try { pgo.output() } catch (e: IOException) { throw DatabaseOutputException("Failed to output a tree", e) } } - mDatabaseV3.doForEachEntryInIndex { entry -> - val peo = PwEntryOutputV3(entry, os) + mDatabaseKDB.doForEachEntryInIndex { entry -> + val peo = EntryOutputKDB(entry, os) try { peo.output() } catch (e: IOException) { @@ -223,15 +230,15 @@ class PwDbV3Output(private val mDatabaseV3: PwDatabaseV3, os: OutputStream) : Pw } private fun sortGroupsForOutput() { - val groupList = ArrayList() + val groupList = ArrayList() // Rebuild list according to coalation sorting order removing any orphaned groups - for (rootGroup in mDatabaseV3.rootGroups) { + for (rootGroup in mDatabaseKDB.rootGroups) { sortGroup(rootGroup, groupList) } - mDatabaseV3.setGroupIndexes(groupList) + mDatabaseKDB.setGroupIndexes(groupList) } - private fun sortGroup(group: PwGroupV3, groupList: MutableList) { + private fun sortGroup(group: GroupKDB, groupList: MutableList) { // Add current tree groupList.add(group) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt new file mode 100644 index 000000000..1cdb764f7 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/DatabaseOutputKDBX.kt @@ -0,0 +1,708 @@ +/* + * Copyright 2019 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePass DX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.database.file.output + +import android.util.Base64 +import android.util.Log +import android.util.Xml +import com.kunzisoft.keepass.crypto.CipherFactory +import com.kunzisoft.keepass.crypto.CrsAlgorithm +import com.kunzisoft.keepass.crypto.StreamCipherFactory +import com.kunzisoft.keepass.crypto.engine.CipherEngine +import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory +import com.kunzisoft.keepass.database.action.node.NodeHandler +import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm +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.BUFFER_SIZE_BYTES +import com.kunzisoft.keepass.database.element.entry.AutoType +import com.kunzisoft.keepass.database.element.entry.EntryKDBX +import com.kunzisoft.keepass.database.element.group.GroupKDBX +import com.kunzisoft.keepass.database.element.icon.IconImageCustom +import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface +import com.kunzisoft.keepass.database.element.security.BinaryAttachment +import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig +import com.kunzisoft.keepass.database.element.security.ProtectedString +import com.kunzisoft.keepass.database.exception.DatabaseOutputException +import com.kunzisoft.keepass.database.exception.UnknownKDF +import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX +import com.kunzisoft.keepass.database.file.DatabaseKDBXXML +import com.kunzisoft.keepass.database.file.DateKDBXUtil +import com.kunzisoft.keepass.stream.* +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils +import org.joda.time.DateTime +import org.spongycastle.crypto.StreamCipher +import org.xmlpull.v1.XmlSerializer +import java.io.IOException +import java.io.OutputStream +import java.security.NoSuchAlgorithmException +import java.security.SecureRandom +import java.util.* +import java.util.zip.GZIPInputStream +import java.util.zip.GZIPOutputStream +import javax.crypto.Cipher +import javax.crypto.CipherOutputStream + + +class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX, + outputStream: OutputStream) + : DatabaseOutput(outputStream) { + + private var randomStream: StreamCipher? = null + private lateinit var xml: XmlSerializer + private var header: DatabaseHeaderKDBX? = null + private var hashOfHeader: ByteArray? = null + private var headerHmac: ByteArray? = null + private var engine: CipherEngine? = null + + @Throws(DatabaseOutputException::class) + override fun output() { + + try { + try { + engine = CipherFactory.getInstance(mDatabaseKDBX.dataCipher) + } catch (e: NoSuchAlgorithmException) { + throw DatabaseOutputException("No such cipher", e) + } + + header = outputHeader(mOS) + + val osPlain: OutputStream + osPlain = if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { + val cos = attachStreamEncryptor(header!!, mOS) + cos.write(header!!.streamStartBytes) + + HashedBlockOutputStream(cos) + } else { + mOS.write(hashOfHeader!!) + mOS.write(headerHmac!!) + + attachStreamEncryptor(header!!, HmacBlockOutputStream(mOS, mDatabaseKDBX.hmacKey)) + } + + val osXml: OutputStream + try { + osXml = when(mDatabaseKDBX.compressionAlgorithm) { + CompressionAlgorithm.GZip -> GZIPOutputStream(osPlain) + else -> osPlain + } + + if (header!!.version >= DatabaseHeaderKDBX.FILE_VERSION_32_4) { + val ihOut = DatabaseInnerHeaderOutputKDBX(mDatabaseKDBX, header!!, osXml) + ihOut.output() + } + + outputDatabase(osXml) + osXml.close() + } catch (e: IllegalArgumentException) { + throw DatabaseOutputException(e) + } catch (e: IllegalStateException) { + throw DatabaseOutputException(e) + } + + } catch (e: IOException) { + throw DatabaseOutputException(e) + } + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun outputDatabase(outputStream: OutputStream) { + + xml = Xml.newSerializer() + + xml.setOutput(outputStream, "UTF-8") + xml.startDocument("UTF-8", true) + + xml.startTag(null, DatabaseKDBXXML.ElemDocNode) + + writeMeta() + + mDatabaseKDBX.rootGroup?.let { root -> + xml.startTag(null, DatabaseKDBXXML.ElemRoot) + startGroup(root) + val groupStack = Stack() + groupStack.push(root) + + if (!root.doForEachChild( + object : NodeHandler() { + override fun operate(node: EntryKDBX): Boolean { + try { + writeEntry(node, false) + } catch (ex: IOException) { + throw RuntimeException(ex) + } + + return true + } + }, + object : NodeHandler() { + override fun operate(node: GroupKDBX): Boolean { + while (true) { + try { + if (node.parent === groupStack.peek()) { + groupStack.push(node) + startGroup(node) + break + } else { + groupStack.pop() + if (groupStack.size <= 0) return false + endGroup() + } + } catch (e: IOException) { + throw RuntimeException(e) + } + + } + return true + } + }) + ) + throw RuntimeException("Writing groups failed") + + while (groupStack.size > 1) { + xml.endTag(null, DatabaseKDBXXML.ElemGroup) + groupStack.pop() + } + } + + endGroup() + + writeDeletedObjects(mDatabaseKDBX.deletedObjects) + + xml.endTag(null, DatabaseKDBXXML.ElemRoot) + + xml.endTag(null, DatabaseKDBXXML.ElemDocNode) + xml.endDocument() + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeMeta() { + xml.startTag(null, DatabaseKDBXXML.ElemMeta) + + writeObject(DatabaseKDBXXML.ElemGenerator, mDatabaseKDBX.localizedAppName) + + if (hashOfHeader != null) { + writeObject(DatabaseKDBXXML.ElemHeaderHash, String(Base64.encode(hashOfHeader!!, BASE_64_FLAG))) + } + + writeObject(DatabaseKDBXXML.ElemDbName, mDatabaseKDBX.name, true) + writeObject(DatabaseKDBXXML.ElemDbNameChanged, mDatabaseKDBX.nameChanged.date) + writeObject(DatabaseKDBXXML.ElemDbDesc, mDatabaseKDBX.description, true) + writeObject(DatabaseKDBXXML.ElemDbDescChanged, mDatabaseKDBX.descriptionChanged.date) + writeObject(DatabaseKDBXXML.ElemDbDefaultUser, mDatabaseKDBX.defaultUserName, true) + writeObject(DatabaseKDBXXML.ElemDbDefaultUserChanged, mDatabaseKDBX.defaultUserNameChanged.date) + writeObject(DatabaseKDBXXML.ElemDbMntncHistoryDays, mDatabaseKDBX.maintenanceHistoryDays) + writeObject(DatabaseKDBXXML.ElemDbColor, mDatabaseKDBX.color) + writeObject(DatabaseKDBXXML.ElemDbKeyChanged, mDatabaseKDBX.keyLastChanged.date) + writeObject(DatabaseKDBXXML.ElemDbKeyChangeRec, mDatabaseKDBX.keyChangeRecDays) + writeObject(DatabaseKDBXXML.ElemDbKeyChangeForce, mDatabaseKDBX.keyChangeForceDays) + + writeMemoryProtection(mDatabaseKDBX.memoryProtection) + + writeCustomIconList() + + writeObject(DatabaseKDBXXML.ElemRecycleBinEnabled, mDatabaseKDBX.isRecycleBinEnabled) + writeUuid(DatabaseKDBXXML.ElemRecycleBinUuid, mDatabaseKDBX.recycleBinUUID) + writeObject(DatabaseKDBXXML.ElemRecycleBinChanged, mDatabaseKDBX.recycleBinChanged) + writeUuid(DatabaseKDBXXML.ElemEntryTemplatesGroup, mDatabaseKDBX.entryTemplatesGroup) + writeObject(DatabaseKDBXXML.ElemEntryTemplatesGroupChanged, mDatabaseKDBX.entryTemplatesGroupChanged.date) + writeObject(DatabaseKDBXXML.ElemHistoryMaxItems, mDatabaseKDBX.historyMaxItems.toLong()) + writeObject(DatabaseKDBXXML.ElemHistoryMaxSize, mDatabaseKDBX.historyMaxSize) + writeUuid(DatabaseKDBXXML.ElemLastSelectedGroup, mDatabaseKDBX.lastSelectedGroupUUID) + writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID) + + // Seem to work properly if always in meta + // if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) + writeMetaBinaries() + + writeCustomData(mDatabaseKDBX.customData) + + xml.endTag(null, DatabaseKDBXXML.ElemMeta) + } + + @Throws(DatabaseOutputException::class) + private fun attachStreamEncryptor(header: DatabaseHeaderKDBX, os: OutputStream): CipherOutputStream { + val cipher: Cipher + try { + //mDatabaseKDBX.makeFinalKey(header.masterSeed, mDatabaseKDBX.kdfParameters); + + cipher = engine!!.getCipher(Cipher.ENCRYPT_MODE, mDatabaseKDBX.finalKey!!, header.encryptionIV) + } catch (e: Exception) { + throw DatabaseOutputException("Invalid algorithm.", e) + } + + return CipherOutputStream(os, cipher) + } + + @Throws(DatabaseOutputException::class) + override fun setIVs(header: DatabaseHeaderKDBX): SecureRandom { + val random = super.setIVs(header) + random.nextBytes(header.masterSeed) + + val ivLength = engine!!.ivLength() + if (ivLength != header.encryptionIV.size) { + header.encryptionIV = ByteArray(ivLength) + } + random.nextBytes(header.encryptionIV) + + if (mDatabaseKDBX.kdfParameters == null) { + mDatabaseKDBX.kdfParameters = KdfFactory.aesKdf.defaultParameters + } + + try { + val kdf = mDatabaseKDBX.getEngineKDBX4(mDatabaseKDBX.kdfParameters) + kdf.randomize(mDatabaseKDBX.kdfParameters!!) + } catch (unknownKDF: UnknownKDF) { + Log.e(TAG, "Unable to retrieve header", unknownKDF) + } + + if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { + header.innerRandomStream = CrsAlgorithm.Salsa20 + header.innerRandomStreamKey = ByteArray(32) + } else { + header.innerRandomStream = CrsAlgorithm.ChaCha20 + header.innerRandomStreamKey = ByteArray(64) + } + random.nextBytes(header.innerRandomStreamKey) + + randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey) + if (randomStream == null) { + throw DatabaseOutputException("Invalid random cipher") + } + + if (header.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { + random.nextBytes(header.streamStartBytes) + } + + return random + } + + @Throws(DatabaseOutputException::class) + override fun outputHeader(outputStream: OutputStream): DatabaseHeaderKDBX { + + val header = DatabaseHeaderKDBX(mDatabaseKDBX) + setIVs(header) + + val pho = DatabaseHeaderOutputKDBX(mDatabaseKDBX, header, outputStream) + try { + pho.output() + } catch (e: IOException) { + throw DatabaseOutputException("Failed to output the header.", e) + } + + hashOfHeader = pho.hashOfHeader + headerHmac = pho.headerHmac + + return header + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun startGroup(group: GroupKDBX) { + xml.startTag(null, DatabaseKDBXXML.ElemGroup) + writeUuid(DatabaseKDBXXML.ElemUuid, group.id) + writeObject(DatabaseKDBXXML.ElemName, group.title) + writeObject(DatabaseKDBXXML.ElemNotes, group.notes) + writeObject(DatabaseKDBXXML.ElemIcon, group.icon.iconId.toLong()) + + if (group.iconCustom != IconImageCustom.UNKNOWN_ICON) { + writeUuid(DatabaseKDBXXML.ElemCustomIconID, group.iconCustom.uuid) + } + + writeTimes(group) + writeObject(DatabaseKDBXXML.ElemIsExpanded, group.isExpanded) + writeObject(DatabaseKDBXXML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence) + writeObject(DatabaseKDBXXML.ElemEnableAutoType, group.enableAutoType) + writeObject(DatabaseKDBXXML.ElemEnableSearching, group.enableSearching) + writeUuid(DatabaseKDBXXML.ElemLastTopVisibleEntry, group.lastTopVisibleEntry) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun endGroup() { + xml.endTag(null, DatabaseKDBXXML.ElemGroup) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeEntry(entry: EntryKDBX, isHistory: Boolean) { + + xml.startTag(null, DatabaseKDBXXML.ElemEntry) + + writeUuid(DatabaseKDBXXML.ElemUuid, entry.id) + writeObject(DatabaseKDBXXML.ElemIcon, entry.icon.iconId.toLong()) + + if (entry.iconCustom != IconImageCustom.UNKNOWN_ICON) { + writeUuid(DatabaseKDBXXML.ElemCustomIconID, entry.iconCustom.uuid) + } + + writeObject(DatabaseKDBXXML.ElemFgColor, entry.foregroundColor) + writeObject(DatabaseKDBXXML.ElemBgColor, entry.backgroundColor) + writeObject(DatabaseKDBXXML.ElemOverrideUrl, entry.overrideURL) + writeObject(DatabaseKDBXXML.ElemTags, entry.tags) + + writeTimes(entry) + + writeFields(entry.fields) + writeEntryBinaries(entry.binaries) + writeAutoType(entry.autoType) + + if (!isHistory) { + writeEntryHistory(entry.history) + } + + xml.endTag(null, DatabaseKDBXXML.ElemEntry) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String, value: String, filterXmlChars: Boolean = false) { + var xmlString = value + + xml.startTag(null, name) + + if (filterXmlChars) { + xmlString = safeXmlString(xmlString) + } + + xml.text(xmlString) + xml.endTag(null, name) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String, value: Date?) { + if (header!!.version < DatabaseHeaderKDBX.FILE_VERSION_32_4) { + writeObject(name, DatabaseKDBXXML.dateFormatter.get().format(value)) + } else { + val dt = DateTime(value) + val seconds = DateKDBXUtil.convertDateToKDBX4Time(dt) + val buf = LEDataOutputStream.writeLongBuf(seconds) + val b64 = String(Base64.encode(buf, BASE_64_FLAG)) + writeObject(name, b64) + } + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String, value: Long) { + writeObject(name, value.toString()) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String, value: Boolean?) { + val text: String = when { + value == null -> DatabaseKDBXXML.ValNull + value -> DatabaseKDBXXML.ValTrue + else -> DatabaseKDBXXML.ValFalse + } + + writeObject(name, text) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeUuid(name: String, uuid: UUID) { + val data = DatabaseInputOutputUtils.uuidToBytes(uuid) + writeObject(name, String(Base64.encode(data, BASE_64_FLAG))) + } + + @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()) + + // Force binary compression from database (compression was harmonized during import) + xml.attribute(null, DatabaseKDBXXML.AttrCompressed, + if (mDatabaseKDBX.compressionAlgorithm === CompressionAlgorithm.GZip) { + DatabaseKDBXXML.ValTrue + } else { + DatabaseKDBXXML.ValFalse + } + ) + + // Force decompression in this specific case + val binaryInputStream = if (mDatabaseKDBX.compressionAlgorithm == CompressionAlgorithm.None + && binary.isCompressed == true) { + GZIPInputStream(binary.getInputDataStream()) + } else { + binary.getInputDataStream() + } + + // Write the XML + readFromStream(binaryInputStream, BUFFER_SIZE_BYTES, + object : ReadBytes { + override fun read(buffer: ByteArray) { + val charArray = String(Base64.encode(buffer, BASE_64_FLAG)).toCharArray() + xml.text(charArray, 0, charArray.size) + } + } + ) + + xml.endTag(null, DatabaseKDBXXML.ElemBinary) + } + + xml.endTag(null, DatabaseKDBXXML.ElemBinaries) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeObject(name: String, keyName: String, keyValue: String, valueName: String, valueValue: String) { + xml.startTag(null, name) + + xml.startTag(null, keyName) + xml.text(safeXmlString(keyValue)) + xml.endTag(null, keyName) + + xml.startTag(null, valueName) + xml.text(safeXmlString(valueValue)) + xml.endTag(null, valueName) + + xml.endTag(null, name) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeAutoType(autoType: AutoType) { + xml.startTag(null, DatabaseKDBXXML.ElemAutoType) + + writeObject(DatabaseKDBXXML.ElemAutoTypeEnabled, autoType.enabled) + writeObject(DatabaseKDBXXML.ElemAutoTypeObfuscation, autoType.obfuscationOptions) + + if (autoType.defaultSequence.isNotEmpty()) { + writeObject(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true) + } + + for ((key, value) in autoType.entrySet()) { + writeObject(DatabaseKDBXXML.ElemAutoTypeItem, DatabaseKDBXXML.ElemWindow, key, DatabaseKDBXXML.ElemKeystrokeSequence, value) + } + + xml.endTag(null, DatabaseKDBXXML.ElemAutoType) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeFields(fields: Map) { + + for ((key, value) in fields) { + writeField(key, value) + } + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeField(key: String, value: ProtectedString) { + + xml.startTag(null, DatabaseKDBXXML.ElemString) + xml.startTag(null, DatabaseKDBXXML.ElemKey) + xml.text(safeXmlString(key)) + xml.endTag(null, DatabaseKDBXXML.ElemKey) + + xml.startTag(null, DatabaseKDBXXML.ElemValue) + var protect = value.isProtected + + when (key) { + MemoryProtectionConfig.ProtectDefinition.TITLE_FIELD -> protect = mDatabaseKDBX.memoryProtection.protectTitle + MemoryProtectionConfig.ProtectDefinition.USERNAME_FIELD -> protect = mDatabaseKDBX.memoryProtection.protectUserName + MemoryProtectionConfig.ProtectDefinition.PASSWORD_FIELD -> protect = mDatabaseKDBX.memoryProtection.protectPassword + MemoryProtectionConfig.ProtectDefinition.URL_FIELD -> protect = mDatabaseKDBX.memoryProtection.protectUrl + MemoryProtectionConfig.ProtectDefinition.NOTES_FIELD -> protect = mDatabaseKDBX.memoryProtection.protectNotes + } + + if (protect) { + xml.attribute(null, DatabaseKDBXXML.AttrProtected, DatabaseKDBXXML.ValTrue) + + val data = value.toString().toByteArray(charset("UTF-8")) + val valLength = data.size + + if (valLength > 0) { + val encoded = ByteArray(valLength) + randomStream!!.processBytes(data, 0, valLength, encoded, 0) + xml.text(String(Base64.encode(encoded, BASE_64_FLAG))) + } + } else { + xml.text(safeXmlString(value.toString())) + } + + xml.endTag(null, DatabaseKDBXXML.ElemValue) + xml.endTag(null, DatabaseKDBXXML.ElemString) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeDeletedObject(value: DeletedObject) { + xml.startTag(null, DatabaseKDBXXML.ElemDeletedObject) + + writeUuid(DatabaseKDBXXML.ElemUuid, value.uuid) + writeObject(DatabaseKDBXXML.ElemDeletionTime, value.deletionTime) + + xml.endTag(null, DatabaseKDBXXML.ElemDeletedObject) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeEntryBinaries(binaries: Map) { + for ((key, binary) in binaries) { + xml.startTag(null, DatabaseKDBXXML.ElemBinary) + xml.startTag(null, DatabaseKDBXXML.ElemKey) + xml.text(safeXmlString(key)) + xml.endTag(null, DatabaseKDBXXML.ElemKey) + + xml.startTag(null, DatabaseKDBXXML.ElemValue) + val ref = mDatabaseKDBX.binaryPool.findKey(binary) + if (ref != null) { + xml.attribute(null, DatabaseKDBXXML.AttrRef, ref.toString()) + } else { + val binaryLength = binary.length() + 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.ElemBinary) + } + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeDeletedObjects(value: List) { + xml.startTag(null, DatabaseKDBXXML.ElemDeletedObjects) + + for (pdo in value) { + writeDeletedObject(pdo) + } + + xml.endTag(null, DatabaseKDBXXML.ElemDeletedObjects) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeMemoryProtection(value: MemoryProtectionConfig) { + xml.startTag(null, DatabaseKDBXXML.ElemMemoryProt) + + writeObject(DatabaseKDBXXML.ElemProtTitle, value.protectTitle) + writeObject(DatabaseKDBXXML.ElemProtUserName, value.protectUserName) + writeObject(DatabaseKDBXXML.ElemProtPassword, value.protectPassword) + writeObject(DatabaseKDBXXML.ElemProtURL, value.protectUrl) + writeObject(DatabaseKDBXXML.ElemProtNotes, value.protectNotes) + + xml.endTag(null, DatabaseKDBXXML.ElemMemoryProt) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeCustomData(customData: Map) { + xml.startTag(null, DatabaseKDBXXML.ElemCustomData) + + for ((key, value) in customData) { + writeObject( + DatabaseKDBXXML.ElemStringDictExItem, + DatabaseKDBXXML.ElemKey, + key, + DatabaseKDBXXML.ElemValue, + value + ) + } + + xml.endTag(null, DatabaseKDBXXML.ElemCustomData) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeTimes(node: NodeKDBXInterface) { + xml.startTag(null, DatabaseKDBXXML.ElemTimes) + + writeObject(DatabaseKDBXXML.ElemLastModTime, node.lastModificationTime.date) + writeObject(DatabaseKDBXXML.ElemCreationTime, node.creationTime.date) + writeObject(DatabaseKDBXXML.ElemLastAccessTime, node.lastAccessTime.date) + writeObject(DatabaseKDBXXML.ElemExpiryTime, node.expiryTime.date) + writeObject(DatabaseKDBXXML.ElemExpires, node.expires) + writeObject(DatabaseKDBXXML.ElemUsageCount, node.usageCount) + writeObject(DatabaseKDBXXML.ElemLocationChanged, node.locationChanged.date) + + xml.endTag(null, DatabaseKDBXXML.ElemTimes) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeEntryHistory(value: List) { + val element = DatabaseKDBXXML.ElemHistory + + xml.startTag(null, element) + + for (entry in value) { + writeEntry(entry, true) + } + + xml.endTag(null, element) + } + + @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) + private fun writeCustomIconList() { + val customIcons = mDatabaseKDBX.customIcons + if (customIcons.size == 0) return + + xml.startTag(null, DatabaseKDBXXML.ElemCustomIcons) + + for (icon in customIcons) { + xml.startTag(null, DatabaseKDBXXML.ElemCustomIconItem) + + writeUuid(DatabaseKDBXXML.ElemCustomIconItemID, icon.uuid) + writeObject(DatabaseKDBXXML.ElemCustomIconItemData, String(Base64.encode(icon.imageData, BASE_64_FLAG))) + + xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem) + } + + xml.endTag(null, DatabaseKDBXXML.ElemCustomIcons) + } + + private fun safeXmlString(text: String): String { + if (text.isEmpty()) { + return text + } + + val stringBuilder = StringBuilder() + var ch: Char + for (element in text) { + ch = element + if ( + ch.toInt() in 0x20..0xD7FF || + ch.toInt() == 0x9 || ch.toInt() == 0xA || ch.toInt() == 0xD || + ch.toInt() in 0xE000..0xFFFD + ) { + stringBuilder.append(ch) + } + } + return stringBuilder.toString() + } + + companion object { + private val TAG = DatabaseOutputKDBX::class.java.name + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwEntryOutputV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/EntryOutputKDB.kt similarity index 57% rename from app/src/main/java/com/kunzisoft/keepass/database/file/save/PwEntryOutputV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/output/EntryOutputKDB.kt index 91d78a338..2429cedaf 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwEntryOutputV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/EntryOutputKDB.kt @@ -17,20 +17,20 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.file.save +package com.kunzisoft.keepass.database.file.output -import com.kunzisoft.keepass.database.element.PwEntryV3 +import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.stream.LEDataOutputStream -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import java.io.IOException import java.io.OutputStream -class PwEntryOutputV3 +class EntryOutputKDB /** - * Output the PwGroupV3 to the stream + * Output the GroupKDB to the stream */ -(private val mPE: PwEntryV3, private val mOS: OutputStream) { +(private val mEntry: EntryKDB, private val mOutputStream: OutputStream) { /** * Returns the number of bytes written by the stream * @return Number of bytes written @@ -45,98 +45,82 @@ class PwEntryOutputV3 length += 134 // Length of fixed size fields // UUID - mOS.write(UUID_FIELD_TYPE) - mOS.write(UUID_FIELD_SIZE) - mOS.write(Types.UUIDtoBytes(mPE.id)) + mOutputStream.write(UUID_FIELD_TYPE) + mOutputStream.write(UUID_FIELD_SIZE) + mOutputStream.write(DatabaseInputOutputUtils.uuidToBytes(mEntry.id)) // Group ID - mOS.write(GROUPID_FIELD_TYPE) - mOS.write(LONG_FOUR) - mOS.write(LEDataOutputStream.writeIntBuf(mPE.parent!!.id)) + mOutputStream.write(GROUPID_FIELD_TYPE) + mOutputStream.write(LONG_FOUR) + mOutputStream.write(LEDataOutputStream.writeIntBuf(mEntry.parent!!.id)) // Image ID - mOS.write(IMAGEID_FIELD_TYPE) - mOS.write(LONG_FOUR) - mOS.write(LEDataOutputStream.writeIntBuf(mPE.icon.iconId)) + mOutputStream.write(IMAGEID_FIELD_TYPE) + mOutputStream.write(LONG_FOUR) + mOutputStream.write(LEDataOutputStream.writeIntBuf(mEntry.icon.iconId)) // Title - //byte[] title = mPE.title.getBytes("UTF-8"); - mOS.write(TITLE_FIELD_TYPE) - val titleLen = Types.writeCString(mPE.title, mOS) - length += titleLen.toLong() + //byte[] title = mEntry.title.getBytes("UTF-8"); + mOutputStream.write(TITLE_FIELD_TYPE) + length += DatabaseInputOutputUtils.writeCString(mEntry.title, mOutputStream).toLong() // URL - mOS.write(URL_FIELD_TYPE) - val urlLen = Types.writeCString(mPE.url, mOS) - length += urlLen.toLong() + mOutputStream.write(URL_FIELD_TYPE) + length += DatabaseInputOutputUtils.writeCString(mEntry.url, mOutputStream).toLong() // Username - mOS.write(USERNAME_FIELD_TYPE) - val userLen = Types.writeCString(mPE.username, mOS) - length += userLen.toLong() + mOutputStream.write(USERNAME_FIELD_TYPE) + length += DatabaseInputOutputUtils.writeCString(mEntry.username, mOutputStream).toLong() // Password - val password = mPE.passwordBytes - mOS.write(PASSWORD_FIELD_TYPE) - mOS.write(LEDataOutputStream.writeIntBuf(password.size + 1)) - mOS.write(password) - mOS.write(0) - length += (password.size + 1).toLong() + mOutputStream.write(PASSWORD_FIELD_TYPE) + length += DatabaseInputOutputUtils.writePassword(mEntry.password, mOutputStream).toLong() // Additional - mOS.write(ADDITIONAL_FIELD_TYPE) - val addlLen = Types.writeCString(mPE.notes, mOS) - length += addlLen.toLong() + mOutputStream.write(ADDITIONAL_FIELD_TYPE) + length += DatabaseInputOutputUtils.writeCString(mEntry.notes, mOutputStream).toLong() // Create date - writeDate(CREATE_FIELD_TYPE, mPE.creationTime.byteArrayDate) + writeDate(CREATE_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.creationTime.date)) // Modification date - writeDate(MOD_FIELD_TYPE, mPE.lastModificationTime.byteArrayDate) + writeDate(MOD_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.lastModificationTime.date)) // Access date - writeDate(ACCESS_FIELD_TYPE, mPE.lastAccessTime.byteArrayDate) + writeDate(ACCESS_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.lastAccessTime.date)) // Expiration date - writeDate(EXPIRE_FIELD_TYPE, mPE.expiryTime.byteArrayDate) + writeDate(EXPIRE_FIELD_TYPE, DatabaseInputOutputUtils.writeCDate(mEntry.expiryTime.date)) - // Binary desc - mOS.write(BINARY_DESC_FIELD_TYPE) - val descLen = Types.writeCString(mPE.binaryDesc, mOS) - length += descLen.toLong() - - // Binary data - val dataLen = writeByteArray(mPE.binaryData) - length += dataLen.toLong() + // Binary + writeBinary(mEntry.binaryData) // End - mOS.write(END_FIELD_TYPE) - mOS.write(ZERO_FIELD_SIZE) - } - - @Throws(IOException::class) - private fun writeByteArray(data: ByteArray?): Int { - val dataLen: Int = data?.size ?: 0 - mOS.write(BINARY_DATA_FIELD_TYPE) - mOS.write(LEDataOutputStream.writeIntBuf(dataLen)) - if (data != null) { - mOS.write(data) - } - - return dataLen + mOutputStream.write(END_FIELD_TYPE) + mOutputStream.write(ZERO_FIELD_SIZE) } @Throws(IOException::class) private fun writeDate(type: ByteArray, date: ByteArray?) { - mOS.write(type) - mOS.write(DATE_FIELD_SIZE) + mOutputStream.write(type) + mOutputStream.write(DATE_FIELD_SIZE) if (date != null) { - mOS.write(date) + mOutputStream.write(date) } else { - mOS.write(ZERO_FIVE) + mOutputStream.write(ZERO_FIVE) } } + @Throws(IOException::class) + private fun writeBinary(data: ByteArray?) { + mOutputStream.write(BINARY_DESC_FIELD_TYPE) + length += DatabaseInputOutputUtils.writeCString(mEntry.binaryDesc, mOutputStream).toLong() + + val dataLen: Int = data?.size ?: 0 + mOutputStream.write(BINARY_DATA_FIELD_TYPE) + length += DatabaseInputOutputUtils.writeBytes(data, dataLen, mOutputStream) + } + companion object { // Constants val UUID_FIELD_TYPE:ByteArray = LEDataOutputStream.writeUShortBuf(1) diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwGroupOutputV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/output/GroupOutputKDB.kt similarity index 57% rename from app/src/main/java/com/kunzisoft/keepass/database/file/save/PwGroupOutputV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/file/output/GroupOutputKDB.kt index 178fcc867..34c488bf0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwGroupOutputV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/file/output/GroupOutputKDB.kt @@ -17,73 +17,71 @@ * along with KeePass DX. If not, see . * */ -package com.kunzisoft.keepass.database.file.save +package com.kunzisoft.keepass.database.file.output -import com.kunzisoft.keepass.database.element.PwGroupV3 +import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.stream.LEDataOutputStream -import com.kunzisoft.keepass.utils.Types +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils import java.io.IOException import java.io.OutputStream -class PwGroupOutputV3 -/** Output the PwGroupV3 to the stream - * @param pg - * @param os +/** + * Output the GroupKDB to the stream */ -(private val mPG: PwGroupV3, private val mOS: OutputStream) { +class GroupOutputKDB (private val mGroup: GroupKDB, private val mOutputStream: OutputStream) { @Throws(IOException::class) fun output() { //NOTE: Need be to careful about using ints. The actual type written to file is a unsigned int, but most values can't be greater than 2^31, so it probably doesn't matter. // Group ID - mOS.write(GROUPID_FIELD_TYPE) - mOS.write(GROUPID_FIELD_SIZE) - mOS.write(LEDataOutputStream.writeIntBuf(mPG.id)) + mOutputStream.write(GROUPID_FIELD_TYPE) + mOutputStream.write(GROUPID_FIELD_SIZE) + mOutputStream.write(LEDataOutputStream.writeIntBuf(mGroup.id)) // Name - mOS.write(NAME_FIELD_TYPE) - Types.writeCString(mPG.title, mOS) + mOutputStream.write(NAME_FIELD_TYPE) + DatabaseInputOutputUtils.writeCString(mGroup.title, mOutputStream) // Create date - mOS.write(CREATE_FIELD_TYPE) - mOS.write(DATE_FIELD_SIZE) - mOS.write(mPG.creationTime.byteArrayDate) + mOutputStream.write(CREATE_FIELD_TYPE) + mOutputStream.write(DATE_FIELD_SIZE) + mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.creationTime.date)) // Modification date - mOS.write(MOD_FIELD_TYPE) - mOS.write(DATE_FIELD_SIZE) - mOS.write(mPG.lastModificationTime.byteArrayDate) + mOutputStream.write(MOD_FIELD_TYPE) + mOutputStream.write(DATE_FIELD_SIZE) + mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.lastModificationTime.date)) // Access date - mOS.write(ACCESS_FIELD_TYPE) - mOS.write(DATE_FIELD_SIZE) - mOS.write(mPG.lastAccessTime.byteArrayDate) + mOutputStream.write(ACCESS_FIELD_TYPE) + mOutputStream.write(DATE_FIELD_SIZE) + mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.lastAccessTime.date)) // Expiration date - mOS.write(EXPIRE_FIELD_TYPE) - mOS.write(DATE_FIELD_SIZE) - mOS.write(mPG.expiryTime.byteArrayDate) + mOutputStream.write(EXPIRE_FIELD_TYPE) + mOutputStream.write(DATE_FIELD_SIZE) + mOutputStream.write(DatabaseInputOutputUtils.writeCDate(mGroup.expiryTime.date)) // Image ID - mOS.write(IMAGEID_FIELD_TYPE) - mOS.write(IMAGEID_FIELD_SIZE) - mOS.write(LEDataOutputStream.writeIntBuf(mPG.icon.iconId)) + mOutputStream.write(IMAGEID_FIELD_TYPE) + mOutputStream.write(IMAGEID_FIELD_SIZE) + mOutputStream.write(LEDataOutputStream.writeIntBuf(mGroup.icon.iconId)) // Level - mOS.write(LEVEL_FIELD_TYPE) - mOS.write(LEVEL_FIELD_SIZE) - mOS.write(LEDataOutputStream.writeUShortBuf(mPG.level)) + mOutputStream.write(LEVEL_FIELD_TYPE) + mOutputStream.write(LEVEL_FIELD_SIZE) + mOutputStream.write(LEDataOutputStream.writeUShortBuf(mGroup.level)) // Flags - mOS.write(FLAGS_FIELD_TYPE) - mOS.write(FLAGS_FIELD_SIZE) - mOS.write(LEDataOutputStream.writeIntBuf(mPG.flags)) + mOutputStream.write(FLAGS_FIELD_TYPE) + mOutputStream.write(FLAGS_FIELD_SIZE) + mOutputStream.write(LEDataOutputStream.writeIntBuf(mGroup.flags)) // End - mOS.write(END_FIELD_TYPE) - mOS.write(ZERO_FIELD_SIZE) + mOutputStream.write(END_FIELD_TYPE) + mOutputStream.write(ZERO_FIELD_SIZE) } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbInnerHeaderOutputV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbInnerHeaderOutputV4.kt deleted file mode 100644 index 5a2e2d2be..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbInnerHeaderOutputV4.kt +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2017 Brian Pellin. - * - * This file is part of KeePass DX. - * - * KeePassDroid 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. - * - * KeePassDroid 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 KeePassDroid. If not, see . - * - */ -package com.kunzisoft.keepass.database.file.save - -import com.kunzisoft.keepass.database.element.PwDatabaseV4 -import com.kunzisoft.keepass.database.file.PwDbHeaderV4 -import com.kunzisoft.keepass.stream.ActionReadBytes -import com.kunzisoft.keepass.stream.LEDataOutputStream -import com.kunzisoft.keepass.utils.MemoryUtil -import java.io.IOException -import java.io.OutputStream -import kotlin.experimental.or - -class PwDbInnerHeaderOutputV4(private val db: PwDatabaseV4, private val header: PwDbHeaderV4, os: OutputStream) { - - private val los: LEDataOutputStream = LEDataOutputStream(os) - - @Throws(IOException::class) - fun output() { - los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomStreamID.toInt()) - los.writeInt(4) - if (header.innerRandomStream == null) - throw IOException("Can't write innerRandomStream") - los.writeInt(header.innerRandomStream!!.id) - - val streamKeySize = header.innerRandomStreamKey.size - los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.InnerRandomstreamKey.toInt()) - los.writeInt(streamKeySize) - los.write(header.innerRandomStreamKey) - - db.binPool.doForEachBinary { _, protectedBinary -> - var flag = PwDbHeaderV4.KdbxBinaryFlags.None - if (protectedBinary.isProtected) { - flag = flag or PwDbHeaderV4.KdbxBinaryFlags.Protected - } - - los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.Binary.toInt()) - los.writeInt(protectedBinary.length().toInt() + 1) // TODO verify - los.write(flag.toInt()) - - protectedBinary.getData()?.let { - MemoryUtil.readBytes(it, ActionReadBytes { buffer -> - los.write(buffer) - }) - } ?: throw IOException("Can't write protected binary") - } - - los.write(PwDbHeaderV4.PwDbInnerHeaderV4Fields.EndOfHeader.toInt()) - los.writeInt(0) - } - -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV4Output.kt b/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV4Output.kt deleted file mode 100644 index 20227dded..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/database/file/save/PwDbV4Output.kt +++ /dev/null @@ -1,742 +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 . - * - */ -package com.kunzisoft.keepass.database.file.save - -import android.util.Log -import android.util.Xml -import biz.source_code.base64Coder.Base64Coder -import com.kunzisoft.keepass.crypto.CipherFactory -import com.kunzisoft.keepass.crypto.CrsAlgorithm -import com.kunzisoft.keepass.crypto.StreamCipherFactory -import com.kunzisoft.keepass.crypto.engine.CipherEngine -import com.kunzisoft.keepass.crypto.keyDerivation.KdfFactory -import com.kunzisoft.keepass.database.* -import com.kunzisoft.keepass.database.element.* -import com.kunzisoft.keepass.database.exception.DatabaseOutputException -import com.kunzisoft.keepass.database.exception.UnknownKDF -import com.kunzisoft.keepass.database.element.PwCompressionAlgorithm -import com.kunzisoft.keepass.database.file.PwDbHeaderV4 -import com.kunzisoft.keepass.database.element.security.ProtectedBinary -import com.kunzisoft.keepass.database.element.security.ProtectedString -import com.kunzisoft.keepass.stream.HashedBlockOutputStream -import com.kunzisoft.keepass.stream.HmacBlockOutputStream -import com.kunzisoft.keepass.stream.LEDataOutputStream -import com.kunzisoft.keepass.database.file.KDBX4DateUtil -import com.kunzisoft.keepass.utils.MemoryUtil -import com.kunzisoft.keepass.utils.Types -import org.joda.time.DateTime -import org.spongycastle.crypto.StreamCipher -import org.xmlpull.v1.XmlSerializer - -import javax.crypto.Cipher -import javax.crypto.CipherOutputStream -import java.io.IOException -import java.io.OutputStream -import java.security.NoSuchAlgorithmException -import java.security.SecureRandom -import java.util.* -import java.util.zip.GZIPOutputStream - -class PwDbV4Output(private val mDatabaseV4: PwDatabaseV4, outputStream: OutputStream) : PwDbOutput(outputStream) { - - private var randomStream: StreamCipher? = null - private lateinit var xml: XmlSerializer - private var header: PwDbHeaderV4? = null - private var hashOfHeader: ByteArray? = null - private var headerHmac: ByteArray? = null - private var engine: CipherEngine? = null - - @Throws(DatabaseOutputException::class) - override fun output() { - - try { - try { - engine = CipherFactory.getInstance(mDatabaseV4.dataCipher) - } catch (e: NoSuchAlgorithmException) { - throw DatabaseOutputException("No such cipher", e) - } - - header = outputHeader(mOS) - - val osPlain: OutputStream - if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { - val cos = attachStreamEncryptor(header!!, mOS) - cos.write(header!!.streamStartBytes) - - osPlain = HashedBlockOutputStream(cos) - } else { - mOS.write(hashOfHeader!!) - mOS.write(headerHmac!!) - - val hbos = HmacBlockOutputStream(mOS, mDatabaseV4.hmacKey) - osPlain = attachStreamEncryptor(header!!, hbos) - } - - val osXml: OutputStream - try { - osXml = when(mDatabaseV4.compressionAlgorithm) { - PwCompressionAlgorithm.GZip -> GZIPOutputStream(osPlain) - else -> osPlain - } - - if (header!!.version >= PwDbHeaderV4.FILE_VERSION_32_4) { - val ihOut = PwDbInnerHeaderOutputV4(mDatabaseV4, header!!, osXml) - ihOut.output() - } - - outputDatabase(osXml) - osXml.close() - } catch (e: IllegalArgumentException) { - throw DatabaseOutputException(e) - } catch (e: IllegalStateException) { - throw DatabaseOutputException(e) - } - - } catch (e: IOException) { - throw DatabaseOutputException(e) - } - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun outputDatabase(os: OutputStream) { - - xml = Xml.newSerializer() - - xml.setOutput(os, "UTF-8") - xml.startDocument("UTF-8", true) - - xml.startTag(null, PwDatabaseV4XML.ElemDocNode) - - writeMeta() - - mDatabaseV4.rootGroup?.let { root -> - xml.startTag(null, PwDatabaseV4XML.ElemRoot) - startGroup(root) - val groupStack = Stack() - groupStack.push(root) - - if (!root.doForEachChild( - object : NodeHandler() { - override fun operate(node: PwEntryV4): Boolean { - try { - writeEntry(node, false) - } catch (ex: IOException) { - throw RuntimeException(ex) - } - - return true - } - }, - object : NodeHandler() { - override fun operate(node: PwGroupV4): Boolean { - while (true) { - try { - if (node.parent === groupStack.peek()) { - groupStack.push(node) - startGroup(node) - break - } else { - groupStack.pop() - if (groupStack.size <= 0) return false - endGroup() - } - } catch (e: IOException) { - throw RuntimeException(e) - } - - } - return true - } - }) - ) - throw RuntimeException("Writing groups failed") - - while (groupStack.size > 1) { - xml.endTag(null, PwDatabaseV4XML.ElemGroup) - groupStack.pop() - } - } - - endGroup() - - writeList(PwDatabaseV4XML.ElemDeletedObjects, mDatabaseV4.deletedObjects) - - xml.endTag(null, PwDatabaseV4XML.ElemRoot) - - xml.endTag(null, PwDatabaseV4XML.ElemDocNode) - xml.endDocument() - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeMeta() { - xml.startTag(null, PwDatabaseV4XML.ElemMeta) - - writeObject(PwDatabaseV4XML.ElemGenerator, mDatabaseV4.localizedAppName) - - if (hashOfHeader != null) { - writeObject(PwDatabaseV4XML.ElemHeaderHash, String(Base64Coder.encode(hashOfHeader!!))) - } - - writeObject(PwDatabaseV4XML.ElemDbName, mDatabaseV4.name, true) - writeObject(PwDatabaseV4XML.ElemDbNameChanged, mDatabaseV4.nameChanged.date) - writeObject(PwDatabaseV4XML.ElemDbDesc, mDatabaseV4.description, true) - writeObject(PwDatabaseV4XML.ElemDbDescChanged, mDatabaseV4.descriptionChanged.date) - writeObject(PwDatabaseV4XML.ElemDbDefaultUser, mDatabaseV4.defaultUserName, true) - writeObject(PwDatabaseV4XML.ElemDbDefaultUserChanged, mDatabaseV4.defaultUserNameChanged.date) - writeObject(PwDatabaseV4XML.ElemDbMntncHistoryDays, mDatabaseV4.maintenanceHistoryDays) - writeObject(PwDatabaseV4XML.ElemDbColor, mDatabaseV4.color) - writeObject(PwDatabaseV4XML.ElemDbKeyChanged, mDatabaseV4.keyLastChanged.date) - writeObject(PwDatabaseV4XML.ElemDbKeyChangeRec, mDatabaseV4.keyChangeRecDays) - writeObject(PwDatabaseV4XML.ElemDbKeyChangeForce, mDatabaseV4.keyChangeForceDays) - - writeList(PwDatabaseV4XML.ElemMemoryProt, mDatabaseV4.memoryProtection) - - writeCustomIconList() - - writeObject(PwDatabaseV4XML.ElemRecycleBinEnabled, mDatabaseV4.isRecycleBinEnabled) - writeObject(PwDatabaseV4XML.ElemRecycleBinUuid, mDatabaseV4.recycleBinUUID) - writeObject(PwDatabaseV4XML.ElemRecycleBinChanged, mDatabaseV4.recycleBinChanged) - writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroup, mDatabaseV4.entryTemplatesGroup) - writeObject(PwDatabaseV4XML.ElemEntryTemplatesGroupChanged, mDatabaseV4.entryTemplatesGroupChanged.date) - writeObject(PwDatabaseV4XML.ElemHistoryMaxItems, mDatabaseV4.historyMaxItems.toLong()) - writeObject(PwDatabaseV4XML.ElemHistoryMaxSize, mDatabaseV4.historyMaxSize) - writeObject(PwDatabaseV4XML.ElemLastSelectedGroup, mDatabaseV4.lastSelectedGroupUUID) - writeObject(PwDatabaseV4XML.ElemLastTopVisibleGroup, mDatabaseV4.lastTopVisibleGroupUUID) - - if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { - writeBinPool() - } - writeList(PwDatabaseV4XML.ElemCustomData, mDatabaseV4.customData) - - xml.endTag(null, PwDatabaseV4XML.ElemMeta) - } - - @Throws(DatabaseOutputException::class) - private fun attachStreamEncryptor(header: PwDbHeaderV4, os: OutputStream): CipherOutputStream { - val cipher: Cipher - try { - //mDatabaseV4.makeFinalKey(header.masterSeed, mDatabaseV4.kdfParameters); - - cipher = engine!!.getCipher(Cipher.ENCRYPT_MODE, mDatabaseV4.finalKey!!, header.encryptionIV) - } catch (e: Exception) { - throw DatabaseOutputException("Invalid algorithm.", e) - } - - return CipherOutputStream(os, cipher) - } - - @Throws(DatabaseOutputException::class) - override fun setIVs(header: PwDbHeaderV4): SecureRandom { - val random = super.setIVs(header) - random.nextBytes(header.masterSeed) - - val ivLength = engine!!.ivLength() - if (ivLength != header.encryptionIV.size) { - header.encryptionIV = ByteArray(ivLength) - } - random.nextBytes(header.encryptionIV) - - if (mDatabaseV4.kdfParameters == null) { - mDatabaseV4.kdfParameters = KdfFactory.aesKdf.defaultParameters - } - - try { - val kdf = mDatabaseV4.getEngineV4(mDatabaseV4.kdfParameters) - kdf.randomize(mDatabaseV4.kdfParameters!!) - } catch (unknownKDF: UnknownKDF) { - Log.e(TAG, "Unable to retrieve header", unknownKDF) - } - - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { - header.innerRandomStream = CrsAlgorithm.Salsa20 - header.innerRandomStreamKey = ByteArray(32) - } else { - header.innerRandomStream = CrsAlgorithm.ChaCha20 - header.innerRandomStreamKey = ByteArray(64) - } - random.nextBytes(header.innerRandomStreamKey) - - randomStream = StreamCipherFactory.getInstance(header.innerRandomStream, header.innerRandomStreamKey) - if (randomStream == null) { - throw DatabaseOutputException("Invalid random cipher") - } - - if (header.version < PwDbHeaderV4.FILE_VERSION_32_4) { - random.nextBytes(header.streamStartBytes) - } - - return random - } - - @Throws(DatabaseOutputException::class) - override fun outputHeader(outputStream: OutputStream): PwDbHeaderV4 { - - val header = PwDbHeaderV4(mDatabaseV4) - setIVs(header) - - val pho = PwDbHeaderOutputV4(mDatabaseV4, header, outputStream) - try { - pho.output() - } catch (e: IOException) { - throw DatabaseOutputException("Failed to output the header.", e) - } - - hashOfHeader = pho.hashOfHeader - headerHmac = pho.headerHmac - - return header - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun startGroup(group: PwGroupV4) { - xml.startTag(null, PwDatabaseV4XML.ElemGroup) - writeObject(PwDatabaseV4XML.ElemUuid, group.id) - writeObject(PwDatabaseV4XML.ElemName, group.title) - writeObject(PwDatabaseV4XML.ElemNotes, group.notes) - writeObject(PwDatabaseV4XML.ElemIcon, group.icon.iconId.toLong()) - - if (group.iconCustom != PwIconCustom.UNKNOWN_ICON) { - writeObject(PwDatabaseV4XML.ElemCustomIconID, group.iconCustom.uuid) - } - - writeList(PwDatabaseV4XML.ElemTimes, group) - writeObject(PwDatabaseV4XML.ElemIsExpanded, group.isExpanded) - writeObject(PwDatabaseV4XML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence) - writeObject(PwDatabaseV4XML.ElemEnableAutoType, group.enableAutoType) - writeObject(PwDatabaseV4XML.ElemEnableSearching, group.enableSearching) - writeObject(PwDatabaseV4XML.ElemLastTopVisibleEntry, group.lastTopVisibleEntry) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun endGroup() { - xml.endTag(null, PwDatabaseV4XML.ElemGroup) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeEntry(entry: PwEntryV4, isHistory: Boolean) { - - xml.startTag(null, PwDatabaseV4XML.ElemEntry) - - writeObject(PwDatabaseV4XML.ElemUuid, entry.id) - writeObject(PwDatabaseV4XML.ElemIcon, entry.icon.iconId.toLong()) - - if (entry.iconCustom != PwIconCustom.UNKNOWN_ICON) { - writeObject(PwDatabaseV4XML.ElemCustomIconID, entry.iconCustom.uuid) - } - - writeObject(PwDatabaseV4XML.ElemFgColor, entry.foregroundColor) - writeObject(PwDatabaseV4XML.ElemBgColor, entry.backgroundColor) - writeObject(PwDatabaseV4XML.ElemOverrideUrl, entry.overrideURL) - writeObject(PwDatabaseV4XML.ElemTags, entry.tags) - - writeList(PwDatabaseV4XML.ElemTimes, entry) - - writeList(entry.fields, true) - writeList(entry.binaries) - writeList(PwDatabaseV4XML.ElemAutoType, entry.autoType) - - if (!isHistory) { - writeList(PwDatabaseV4XML.ElemHistory, entry.history, true) - } - // else entry.sizeOfHistory() == 0 - - xml.endTag(null, PwDatabaseV4XML.ElemEntry) - } - - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeObject(key: String, value: ProtectedBinary) { - - xml.startTag(null, PwDatabaseV4XML.ElemBinary) - xml.startTag(null, PwDatabaseV4XML.ElemKey) - xml.text(safeXmlString(key)) - xml.endTag(null, PwDatabaseV4XML.ElemKey) - - xml.startTag(null, PwDatabaseV4XML.ElemValue) - val ref = mDatabaseV4.binPool.findKey(value) - val strRef = Integer.toString(ref) - - if (strRef != null) { - xml.attribute(null, PwDatabaseV4XML.AttrRef, strRef) - } else { - subWriteValue(value) - } - xml.endTag(null, PwDatabaseV4XML.ElemValue) - - xml.endTag(null, PwDatabaseV4XML.ElemBinary) - } - - /* - TODO Make with pipe - private void subWriteValue(ProtectedBinary value) throws IllegalArgumentException, IllegalStateException, IOException { - try (InputStream inputStream = value.getData()) { - if (inputStream == null) { - Log.e(TAG, "Can't write a null input stream."); - return; - } - - if (value.isProtected()) { - xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue); - - try (InputStream cypherInputStream = - IOUtil.pipe(inputStream, - o -> new org.spongycastle.crypto.io.CipherOutputStream(o, randomStream))) { - writeInputStreamInBase64(cypherInputStream); - } - - } else { - if (mDatabaseV4.getCompressionAlgorithm() == PwCompressionAlgorithm.GZip) { - - xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue); - - try (InputStream gZipInputStream = - IOUtil.pipe(inputStream, GZIPOutputStream::new, (int) value.length())) { - writeInputStreamInBase64(gZipInputStream); - } - - } else { - writeInputStreamInBase64(inputStream); - } - } - } - } - - private void writeInputStreamInBase64(InputStream inputStream) throws IOException { - try (InputStream base64InputStream = - IOUtil.pipe(inputStream, - o -> new Base64OutputStream(o, DEFAULT))) { - MemoryUtil.readBytes(base64InputStream, - buffer -> xml.text(Arrays.toString(buffer))); - } - } - */ - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun subWriteValue(value: ProtectedBinary) { - - val valLength = value.length().toInt() - if (valLength > 0) { - val buffer = ByteArray(valLength) - if (valLength == value.getData()!!.read(buffer, 0, valLength)) { - - if (value.isProtected) { - xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue) - - val encoded = ByteArray(valLength) - randomStream!!.processBytes(buffer, 0, valLength, encoded, 0) - xml.text(String(Base64Coder.encode(encoded))) - - } else { - if (mDatabaseV4.compressionAlgorithm === PwCompressionAlgorithm.GZip) { - xml.attribute(null, PwDatabaseV4XML.AttrCompressed, PwDatabaseV4XML.ValTrue) - - val compressData = MemoryUtil.compress(buffer) - xml.text(String(Base64Coder.encode(compressData))) - - } else { - xml.text(String(Base64Coder.encode(buffer))) - } - } - } else { - Log.e(TAG, "Unable to read the stream of the protected binary") - } - } - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeObject(name: String, value: String, filterXmlChars: Boolean = false) { - var xmlString = value - - xml.startTag(null, name) - - if (filterXmlChars) { - xmlString = safeXmlString(xmlString) - } - - xml.text(xmlString) - xml.endTag(null, name) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeObject(name: String, value: Date?) { - if (header!!.version < PwDbHeaderV4.FILE_VERSION_32_4) { - writeObject(name, PwDatabaseV4XML.dateFormatter.get().format(value)) - } else { - val dt = DateTime(value) - val seconds = KDBX4DateUtil.convertDateToKDBX4Time(dt) - val buf = LEDataOutputStream.writeLongBuf(seconds) - val b64 = String(Base64Coder.encode(buf)) - writeObject(name, b64) - } - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeObject(name: String, value: Long) { - writeObject(name, value.toString()) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeObject(name: String, value: Boolean?) { - val text: String = when { - value == null -> PwDatabaseV4XML.ValNull - value -> PwDatabaseV4XML.ValTrue - else -> PwDatabaseV4XML.ValFalse - } - - writeObject(name, text) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeObject(name: String, uuid: UUID) { - val data = Types.UUIDtoBytes(uuid) - writeObject(name, String(Base64Coder.encode(data))) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeObject(name: String, keyName: String, keyValue: String, valueName: String, valueValue: String) { - xml.startTag(null, name) - - xml.startTag(null, keyName) - xml.text(safeXmlString(keyValue)) - xml.endTag(null, keyName) - - xml.startTag(null, valueName) - xml.text(safeXmlString(valueValue)) - xml.endTag(null, valueName) - - xml.endTag(null, name) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeList(name: String, autoType: AutoType) { - xml.startTag(null, name) - - writeObject(PwDatabaseV4XML.ElemAutoTypeEnabled, autoType.enabled) - writeObject(PwDatabaseV4XML.ElemAutoTypeObfuscation, autoType.obfuscationOptions) - - if (autoType.defaultSequence.isNotEmpty()) { - writeObject(PwDatabaseV4XML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true) - } - - for ((key, value) in autoType.entrySet()) { - writeObject(PwDatabaseV4XML.ElemAutoTypeItem, PwDatabaseV4XML.ElemWindow, key, PwDatabaseV4XML.ElemKeystrokeSequence, value) - } - - xml.endTag(null, name) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeList(strings: Map, isEntryString: Boolean) { - - for ((key, value) in strings) { - writeObject(key, value, isEntryString) - } - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeObject(key: String, value: ProtectedString, isEntryString: Boolean) { - - xml.startTag(null, PwDatabaseV4XML.ElemString) - xml.startTag(null, PwDatabaseV4XML.ElemKey) - xml.text(safeXmlString(key)) - xml.endTag(null, PwDatabaseV4XML.ElemKey) - - xml.startTag(null, PwDatabaseV4XML.ElemValue) - var protect = value.isProtected - if (isEntryString) { - when (key) { - MemoryProtectionConfig.ProtectDefinition.TITLE_FIELD -> protect = mDatabaseV4.memoryProtection.protectTitle - MemoryProtectionConfig.ProtectDefinition.USERNAME_FIELD -> protect = mDatabaseV4.memoryProtection.protectUserName - MemoryProtectionConfig.ProtectDefinition.PASSWORD_FIELD -> protect = mDatabaseV4.memoryProtection.protectPassword - MemoryProtectionConfig.ProtectDefinition.URL_FIELD -> protect = mDatabaseV4.memoryProtection.protectUrl - MemoryProtectionConfig.ProtectDefinition.NOTES_FIELD -> protect = mDatabaseV4.memoryProtection.protectNotes - } - } - - if (protect) { - xml.attribute(null, PwDatabaseV4XML.AttrProtected, PwDatabaseV4XML.ValTrue) - - val data = value.toString().toByteArray(charset("UTF-8")) - val valLength = data.size - - if (valLength > 0) { - val encoded = ByteArray(valLength) - randomStream!!.processBytes(data, 0, valLength, encoded, 0) - xml.text(String(Base64Coder.encode(encoded))) - } - } else { - xml.text(safeXmlString(value.toString())) - } - - xml.endTag(null, PwDatabaseV4XML.ElemValue) - xml.endTag(null, PwDatabaseV4XML.ElemString) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeObject(name: String?, value: PwDeletedObject) { - assert(name != null) - - xml.startTag(null, name) - - writeObject(PwDatabaseV4XML.ElemUuid, value.uuid) - writeObject(PwDatabaseV4XML.ElemDeletionTime, value.deletionTime) - - xml.endTag(null, name) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeList(binaries: Map) { - for ((key, value) in binaries) { - writeObject(key, value) - } - } - - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeList(name: String?, value: List) { - assert(name != null) - - xml.startTag(null, name) - - for (pdo in value) { - writeObject(PwDatabaseV4XML.ElemDeletedObject, pdo) - } - - xml.endTag(null, name) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeList(name: String?, value: MemoryProtectionConfig) { - assert(name != null) - - xml.startTag(null, name) - - writeObject(PwDatabaseV4XML.ElemProtTitle, value.protectTitle) - writeObject(PwDatabaseV4XML.ElemProtUserName, value.protectUserName) - writeObject(PwDatabaseV4XML.ElemProtPassword, value.protectPassword) - writeObject(PwDatabaseV4XML.ElemProtURL, value.protectUrl) - writeObject(PwDatabaseV4XML.ElemProtNotes, value.protectNotes) - - xml.endTag(null, name) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeList(name: String?, customData: Map) { - assert(name != null) - - xml.startTag(null, name) - - for ((key, value) in customData) { - writeObject(PwDatabaseV4XML.ElemStringDictExItem, PwDatabaseV4XML.ElemKey, key, PwDatabaseV4XML.ElemValue, value) - - } - - xml.endTag(null, name) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeList(name: String?, it: PwNodeV4Interface) { - assert(name != null) - - xml.startTag(null, name) - - writeObject(PwDatabaseV4XML.ElemLastModTime, it.lastModificationTime.date) - writeObject(PwDatabaseV4XML.ElemCreationTime, it.creationTime.date) - writeObject(PwDatabaseV4XML.ElemLastAccessTime, it.lastAccessTime.date) - writeObject(PwDatabaseV4XML.ElemExpiryTime, it.expiryTime.date) - writeObject(PwDatabaseV4XML.ElemExpires, it.expires) - writeObject(PwDatabaseV4XML.ElemUsageCount, it.usageCount) - writeObject(PwDatabaseV4XML.ElemLocationChanged, it.locationChanged.date) - - xml.endTag(null, name) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeList(name: String?, value: List, isHistory: Boolean) { - assert(name != null) - - xml.startTag(null, name) - - for (entry in value) { - writeEntry(entry, isHistory) - } - - xml.endTag(null, name) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeCustomIconList() { - val customIcons = mDatabaseV4.customIcons - if (customIcons.size == 0) return - - xml.startTag(null, PwDatabaseV4XML.ElemCustomIcons) - - for (icon in customIcons) { - xml.startTag(null, PwDatabaseV4XML.ElemCustomIconItem) - - writeObject(PwDatabaseV4XML.ElemCustomIconItemID, icon.uuid) - writeObject(PwDatabaseV4XML.ElemCustomIconItemData, String(Base64Coder.encode(icon.imageData))) - - xml.endTag(null, PwDatabaseV4XML.ElemCustomIconItem) - } - - xml.endTag(null, PwDatabaseV4XML.ElemCustomIcons) - } - - @Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class) - private fun writeBinPool() { - xml.startTag(null, PwDatabaseV4XML.ElemBinaries) - - mDatabaseV4.binPool.doForEachBinary { key, binary -> - xml.startTag(null, PwDatabaseV4XML.ElemBinary) - xml.attribute(null, PwDatabaseV4XML.AttrId, Integer.toString(key)) - - subWriteValue(binary) - - xml.endTag(null, PwDatabaseV4XML.ElemBinary) - } - - xml.endTag(null, PwDatabaseV4XML.ElemBinaries) - } - - private fun safeXmlString(text: String): String { - if (text.isEmpty()) { - return text - } - - val stringBuilder = StringBuilder() - var ch: Char - for (i in 0 until text.length) { - ch = text[i] - if ( - ch.toInt() in 0x20..0xD7FF || - ch.toInt() == 0x9 || ch.toInt() == 0xA || ch.toInt() == 0xD || - ch.toInt() in 0xE000..0xFFFD - ) { - stringBuilder.append(ch) - } - } - return stringBuilder.toString() - } - - companion object { - private val TAG = PwDbV4Output::class.java.name - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/database/search/EntrySearchHandlerV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/EntryKDBXSearchHandler.kt similarity index 68% rename from app/src/main/java/com/kunzisoft/keepass/database/search/EntrySearchHandlerV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/search/EntryKDBXSearchHandler.kt index a14670220..6fbf3ffb0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/search/EntrySearchHandlerV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/EntryKDBXSearchHandler.kt @@ -19,24 +19,24 @@ */ package com.kunzisoft.keepass.database.search -import com.kunzisoft.keepass.database.NodeHandler -import com.kunzisoft.keepass.database.element.PwEntryV4 -import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorV4 +import com.kunzisoft.keepass.database.action.node.NodeHandler +import com.kunzisoft.keepass.database.element.entry.EntryKDBX +import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX import com.kunzisoft.keepass.utils.StringUtil import java.util.Locale -class EntrySearchHandlerV4(private val mSearchParametersV4: SearchParametersV4, private val mListStorage: MutableList) : NodeHandler() { +class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParametersKDBX, private val mListStorage: MutableList) : NodeHandler() { - override fun operate(node: PwEntryV4): Boolean { + override fun operate(node: EntryKDBX): Boolean { - if (mSearchParametersV4.excludeExpired + if (mSearchParametersKDBX.excludeExpired && node.isCurrentlyExpires) { return true } - var term = mSearchParametersV4.searchString - if (mSearchParametersV4.ignoreCase) { + var term = mSearchParametersKDBX.searchString + if (mSearchParametersKDBX.ignoreCase) { term = term.toLowerCase() } @@ -45,11 +45,11 @@ class EntrySearchHandlerV4(private val mSearchParametersV4: SearchParametersV4, return true } - if (mSearchParametersV4.searchInGroupNames) { + if (mSearchParametersKDBX.searchInGroupNames) { val parent = node.parent if (parent != null) { var groupName = parent.title - if (mSearchParametersV4.ignoreCase) { + if (mSearchParametersKDBX.ignoreCase) { groupName = groupName.toLowerCase() } @@ -68,21 +68,21 @@ class EntrySearchHandlerV4(private val mSearchParametersV4: SearchParametersV4, return true } - private fun searchID(entry: PwEntryV4): Boolean { - if (mSearchParametersV4.searchInUUIDs) { + private fun searchID(entry: EntryKDBX): Boolean { + if (mSearchParametersKDBX.searchInUUIDs) { val hex = UuidUtil.toHexString(entry.id) - return StringUtil.indexOfIgnoreCase(hex, mSearchParametersV4.searchString, Locale.ENGLISH) >= 0 + return StringUtil.indexOfIgnoreCase(hex, mSearchParametersKDBX.searchString, Locale.ENGLISH) >= 0 } return false } - private fun searchStrings(entry: PwEntryV4, term: String): Boolean { - val iterator = EntrySearchStringIteratorV4(entry, mSearchParametersV4) + private fun searchStrings(entry: EntryKDBX, term: String): Boolean { + val iterator = EntrySearchStringIteratorKDBX(entry, mSearchParametersKDBX) while (iterator.hasNext()) { var str = iterator.next() if (str.isNotEmpty()) { - if (mSearchParametersV4.ignoreCase) { + if (mSearchParametersKDBX.ignoreCase) { str = str.toLowerCase() } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchDbHelper.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt similarity index 78% rename from app/src/main/java/com/kunzisoft/keepass/database/search/SearchDbHelper.kt rename to app/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt index 626b3eb41..881e701ad 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchDbHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchHelper.kt @@ -19,16 +19,16 @@ */ package com.kunzisoft.keepass.database.search -import com.kunzisoft.keepass.database.NodeHandler +import com.kunzisoft.keepass.database.action.node.NodeHandler import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.element.EntryVersioned -import com.kunzisoft.keepass.database.element.GroupVersioned +import com.kunzisoft.keepass.database.element.Entry +import com.kunzisoft.keepass.database.element.Group import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIterator -import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorV3 -import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorV4 +import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDB +import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX import java.util.* -class SearchDbHelper(private val isOmitBackup: Boolean) { +class SearchHelper(private val isOmitBackup: Boolean) { companion object { const val MAX_SEARCH_ENTRY = 6 @@ -36,7 +36,7 @@ class SearchDbHelper(private val isOmitBackup: Boolean) { private var incrementEntry = 0 - fun search(database: Database, qStr: String, max: Int): GroupVersioned? { + fun search(database: Database, qStr: String, max: Int): Group? { val searchGroup = database.createGroup() searchGroup?.title = "\"" + qStr + "\"" @@ -47,8 +47,8 @@ class SearchDbHelper(private val isOmitBackup: Boolean) { incrementEntry = 0 database.rootGroup?.doForEachChild( - object : NodeHandler() { - override fun operate(node: EntryVersioned): Boolean { + object : NodeHandler() { + override fun operate(node: Entry): Boolean { if (incrementEntry >= max) return false if (entryContainsString(node, finalQStr, loc)) { @@ -59,8 +59,8 @@ class SearchDbHelper(private val isOmitBackup: Boolean) { return incrementEntry < max } }, - object : NodeHandler() { - override fun operate(node: GroupVersioned): Boolean { + object : NodeHandler() { + override fun operate(node: Group): Boolean { return when { incrementEntry >= max -> false database.isGroupSearchable(node, isOmitBackup) -> true @@ -73,7 +73,7 @@ class SearchDbHelper(private val isOmitBackup: Boolean) { return searchGroup } - private fun entryContainsString(entry: EntryVersioned, searchString: String, locale: Locale): Boolean { + private fun entryContainsString(entry: Entry, searchString: String, locale: Locale): Boolean { // Entry don't contains string if the search string is empty if (searchString.isEmpty()) @@ -81,11 +81,11 @@ class SearchDbHelper(private val isOmitBackup: Boolean) { // Search all strings in the entry var iterator: EntrySearchStringIterator? = null - entry.pwEntryV3?.let { - iterator = EntrySearchStringIteratorV3(it) + entry.entryKDB?.let { + iterator = EntrySearchStringIteratorKDB(it) } - entry.pwEntryV4?.let { - iterator = EntrySearchStringIteratorV4(it) + entry.entryKDBX?.let { + iterator = EntrySearchStringIteratorKDBX(it) } iterator?.let { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchParametersV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchParametersKDBX.kt similarity index 90% rename from app/src/main/java/com/kunzisoft/keepass/database/search/SearchParametersV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/search/SearchParametersKDBX.kt index 740e7e3fc..c3dac8cd6 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/search/SearchParametersV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/SearchParametersKDBX.kt @@ -19,7 +19,7 @@ */ package com.kunzisoft.keepass.database.search -class SearchParametersV4 : SearchParameters { +class SearchParametersKDBX : SearchParameters { var searchInOther = true var searchInUUIDs = false @@ -27,7 +27,7 @@ class SearchParametersV4 : SearchParameters { constructor() : super() - constructor(searchParametersV4: SearchParametersV4) : super(searchParametersV4) { + constructor(searchParametersV4: SearchParametersKDBX) : super(searchParametersV4) { this.searchInOther = searchParametersV4.searchInOther this.searchInUUIDs = searchParametersV4.searchInUUIDs this.searchInTags = searchParametersV4.searchInTags diff --git a/app/src/main/java/com/kunzisoft/keepass/database/search/UuidUtil.java b/app/src/main/java/com/kunzisoft/keepass/database/search/UuidUtil.java index da8cdde7f..b3119ca01 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/search/UuidUtil.java +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/UuidUtil.java @@ -19,16 +19,16 @@ */ package com.kunzisoft.keepass.database.search; -import com.kunzisoft.keepass.utils.Types; +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils; import java.util.UUID; public class UuidUtil { + public static String toHexString(UUID uuid) { if (uuid == null) { return null; } - byte[] buf = Types.UUIDtoBytes(uuid); - if (buf == null) { return null; } + byte[] buf = DatabaseInputOutputUtils.INSTANCE.uuidToBytes(uuid); int len = buf.length; if (len == 0) { return ""; } @@ -37,17 +37,10 @@ public static String toHexString(UUID uuid) { short bt; char high, low; - for (int i = 0; i < len; i++) { - bt = (short)(buf[i] & 0xFF); - high = (char)(bt >>> 4); - - - low = (char)(bt & 0x0F); - - char h,l; - h = byteToChar(high); - l = byteToChar(low); - + for (byte b : buf) { + bt = (short) (b & 0xFF); + high = (char) (bt >>> 4); + low = (char) (bt & 0x0F); sb.append(byteToChar(high)); sb.append(byteToChar(low)); } diff --git a/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV3.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorKDB.kt similarity index 94% rename from app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV3.kt rename to app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorKDB.kt index cf786054d..7fcc2c4e2 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV3.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorKDB.kt @@ -19,15 +19,15 @@ */ package com.kunzisoft.keepass.database.search.iterator -import com.kunzisoft.keepass.database.element.PwEntryV3 +import com.kunzisoft.keepass.database.element.entry.EntryKDB import com.kunzisoft.keepass.database.search.SearchParameters import java.util.NoSuchElementException -class EntrySearchStringIteratorV3 +class EntrySearchStringIteratorKDB @JvmOverloads -constructor(private val mEntry: PwEntryV3, +constructor(private val mEntry: EntryKDB, private val mSearchParameters: SearchParameters? = SearchParameters()) : EntrySearchStringIterator() { diff --git a/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV4.kt b/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorKDBX.kt similarity index 74% rename from app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV4.kt rename to app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorKDBX.kt index 564e5b4f4..0b0896308 100644 --- a/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorV4.kt +++ b/app/src/main/java/com/kunzisoft/keepass/database/search/iterator/EntrySearchStringIteratorKDBX.kt @@ -19,25 +19,25 @@ */ package com.kunzisoft.keepass.database.search.iterator -import com.kunzisoft.keepass.database.element.PwEntryV4 -import com.kunzisoft.keepass.database.search.SearchParametersV4 +import com.kunzisoft.keepass.database.element.entry.EntryKDBX +import com.kunzisoft.keepass.database.search.SearchParametersKDBX import com.kunzisoft.keepass.database.element.security.ProtectedString import java.util.* import kotlin.collections.Map.Entry -class EntrySearchStringIteratorV4 : EntrySearchStringIterator { +class EntrySearchStringIteratorKDBX : EntrySearchStringIterator { private var mCurrent: String? = null private var mSetIterator: Iterator>? = null - private var mSearchParametersV4: SearchParametersV4 + private var mSearchParametersV4: SearchParametersKDBX - constructor(entry: PwEntryV4) { - this.mSearchParametersV4 = SearchParametersV4() + constructor(entry: EntryKDBX) { + this.mSearchParametersV4 = SearchParametersKDBX() mSetIterator = entry.fields.entries.iterator() advance() } - constructor(entry: PwEntryV4, searchParametersV4: SearchParametersV4) { + constructor(entry: EntryKDBX, searchParametersV4: SearchParametersKDBX) { this.mSearchParametersV4 = searchParametersV4 mSetIterator = entry.fields.entries.iterator() advance() @@ -75,11 +75,11 @@ class EntrySearchStringIteratorV4 : EntrySearchStringIterator { private fun searchInField(key: String): Boolean { return when (key) { - PwEntryV4.STR_TITLE -> mSearchParametersV4.searchInTitles - PwEntryV4.STR_USERNAME -> mSearchParametersV4.searchInUserNames - PwEntryV4.STR_PASSWORD -> mSearchParametersV4.searchInPasswords - PwEntryV4.STR_URL -> mSearchParametersV4.searchInUrls - PwEntryV4.STR_NOTES -> mSearchParametersV4.searchInNotes + EntryKDBX.STR_TITLE -> mSearchParametersV4.searchInTitles + EntryKDBX.STR_USERNAME -> mSearchParametersV4.searchInUserNames + EntryKDBX.STR_PASSWORD -> mSearchParametersV4.searchInPasswords + EntryKDBX.STR_URL -> mSearchParametersV4.searchInUrls + EntryKDBX.STR_NOTES -> mSearchParametersV4.searchInNotes else -> mSearchParametersV4.searchInOther } } diff --git a/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt b/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt index aef481e31..0bdb44f14 100644 --- a/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt +++ b/app/src/main/java/com/kunzisoft/keepass/icons/IconDrawableFactory.kt @@ -33,9 +33,9 @@ import androidx.core.widget.ImageViewCompat import android.util.Log import android.widget.ImageView import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.element.PwIcon -import com.kunzisoft.keepass.database.element.PwIconCustom -import com.kunzisoft.keepass.database.element.PwIconStandard +import com.kunzisoft.keepass.database.element.icon.IconImage +import com.kunzisoft.keepass.database.element.icon.IconImageCustom +import com.kunzisoft.keepass.database.element.icon.IconImageStandard import org.apache.commons.collections.map.AbstractReferenceMap import org.apache.commons.collections.map.ReferenceMap @@ -73,13 +73,13 @@ class IconDrawableFactory { /** * Get the [SuperDrawable] [icon] (from cache, or build it and add it to the cache if not exists yet), then [tint] it with [tintColor] if needed */ - fun getIconSuperDrawable(context: Context, icon: PwIcon, width: Int, tint: Boolean = false, tintColor: Int = Color.WHITE): SuperDrawable { + fun getIconSuperDrawable(context: Context, icon: IconImage, width: Int, tint: Boolean = false, tintColor: Int = Color.WHITE): SuperDrawable { return when (icon) { - is PwIconStandard -> { + is IconImageStandard -> { val resId = IconPackChooser.getSelectedIconPack(context)?.iconToResId(icon.iconId) ?: R.drawable.ic_blank_32dp getIconSuperDrawable(context, resId, width, tint, tintColor) } - is PwIconCustom -> { + is IconImageCustom -> { SuperDrawable(getIconDrawable(context.resources, icon), true) } else -> { @@ -89,7 +89,7 @@ class IconDrawableFactory { } /** - * Get the [SuperDrawable] PwIconStandard from [iconId] (cache, or build it and add it to the cache if not exists yet) + * Get the [SuperDrawable] IconImageStandard from [iconId] (cache, or build it and add it to the cache if not exists yet) * , then [tint] it with [tintColor] if needed */ fun getIconSuperDrawable(context: Context, iconId: Int, width: Int, tint: Boolean, tintColor: Int): SuperDrawable { @@ -128,7 +128,7 @@ class IconDrawableFactory { /** * Build a custom [Drawable] from custom [icon] */ - private fun getIconDrawable(resources: Resources, icon: PwIconCustom): Drawable { + private fun getIconDrawable(resources: Resources, icon: IconImageCustom): Drawable { val patternIcon = PatternIcon(resources) var draw: Drawable? = customIconMap[icon.uuid] as Drawable? @@ -249,7 +249,7 @@ fun ImageView.assignDefaultDatabaseIcon(iconFactory: IconDrawableFactory, tintCo /** * Assign a database [icon] to an ImageView and tint it with [tintColor] if needed */ -fun ImageView.assignDatabaseIcon(iconFactory: IconDrawableFactory, icon: PwIcon, tintColor: Int = Color.WHITE) { +fun ImageView.assignDatabaseIcon(iconFactory: IconDrawableFactory, icon: IconImage, tintColor: Int = Color.WHITE) { IconPackChooser.getSelectedIconPack(context)?.let { selectedIconPack -> iconFactory.assignDrawableToImageView( diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/ClipboardEntryNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/notifications/ClipboardEntryNotificationService.kt index 620819f9f..4c20e6b53 100644 --- a/app/src/main/java/com/kunzisoft/keepass/notifications/ClipboardEntryNotificationService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/notifications/ClipboardEntryNotificationService.kt @@ -25,7 +25,7 @@ import android.content.Intent import android.preference.PreferenceManager import android.util.Log import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.exception.SamsungClipboardException +import com.kunzisoft.keepass.database.exception.ClipboardException import com.kunzisoft.keepass.model.EntryInfo import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.timeout.ClipboardHelper @@ -208,7 +208,7 @@ class ClipboardEntryNotificationService : LockNotificationService() { private fun cleanClipboard() { try { clipboardHelper?.cleanClipboard() - } catch (e: SamsungClipboardException) { + } catch (e: ClipboardException) { Log.e(TAG, "Clipboard can't be cleaned", e) } } diff --git a/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt b/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt index 8ca0dafb1..f727cfa94 100644 --- a/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt +++ b/app/src/main/java/com/kunzisoft/keepass/notifications/DatabaseTaskNotificationService.kt @@ -8,12 +8,12 @@ import android.os.Bundle import android.os.IBinder import com.kunzisoft.keepass.R import com.kunzisoft.keepass.app.database.CipherDatabaseEntity -import com.kunzisoft.keepass.database.action.AssignPasswordInDatabaseRunnable -import com.kunzisoft.keepass.database.action.CreateDatabaseRunnable -import com.kunzisoft.keepass.database.action.LoadDatabaseRunnable -import com.kunzisoft.keepass.database.action.SaveDatabaseRunnable +import com.kunzisoft.keepass.database.action.* import com.kunzisoft.keepass.database.action.node.* import com.kunzisoft.keepass.database.element.* +import com.kunzisoft.keepass.database.element.node.Node +import com.kunzisoft.keepass.database.element.node.NodeId +import com.kunzisoft.keepass.database.element.node.Type import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.tasks.ActionRunnable import com.kunzisoft.keepass.tasks.ProgressTaskUpdater @@ -70,17 +70,28 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat val intentAction = intent.action + var saveAction = true + if (intent.hasExtra(SAVE_DATABASE_KEY)) { + saveAction = intent.getBooleanExtra(SAVE_DATABASE_KEY, saveAction) + } + val titleId: Int = when (intentAction) { ACTION_DATABASE_CREATE_TASK -> R.string.creating_database ACTION_DATABASE_LOAD_TASK -> R.string.loading_database - else -> R.string.saving_database + else -> { + if (saveAction) + R.string.saving_database + else + R.string.command_execution + } } val messageId: Int? = when (intentAction) { ACTION_DATABASE_LOAD_TASK -> null else -> null } val warningId: Int? = - if (intentAction == ACTION_DATABASE_LOAD_TASK) + if (!saveAction + || intentAction == ACTION_DATABASE_LOAD_TASK) null else R.string.do_not_kill_app @@ -96,18 +107,19 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat ACTION_DATABASE_COPY_NODES_TASK -> buildDatabaseCopyNodesActionTask(intent) ACTION_DATABASE_MOVE_NODES_TASK -> buildDatabaseMoveNodesActionTask(intent) ACTION_DATABASE_DELETE_NODES_TASK -> buildDatabaseDeleteNodesActionTask(intent) - ACTION_DATABASE_SAVE_NAME_TASK, - ACTION_DATABASE_SAVE_DESCRIPTION_TASK, - ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK, - ACTION_DATABASE_SAVE_COLOR_TASK, - ACTION_DATABASE_SAVE_COMPRESSION_TASK, - ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK, - ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK, - ACTION_DATABASE_SAVE_ENCRYPTION_TASK, - ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK, - ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK, - ACTION_DATABASE_SAVE_PARALLELISM_TASK, - ACTION_DATABASE_SAVE_ITERATIONS_TASK -> buildDatabaseSaveElementActionTask(intent) + ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> buildDatabaseUpdateCompressionActionTask(intent) + ACTION_DATABASE_UPDATE_NAME_TASK, + ACTION_DATABASE_UPDATE_DESCRIPTION_TASK, + ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK, + ACTION_DATABASE_UPDATE_COLOR_TASK, + ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK, + ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK, + ACTION_DATABASE_UPDATE_ENCRYPTION_TASK, + ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK, + ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK, + ACTION_DATABASE_UPDATE_PARALLELISM_TASK, + ACTION_DATABASE_UPDATE_ITERATIONS_TASK -> buildDatabaseUpdateElementActionTask(intent) + ACTION_DATABASE_SAVE -> buildDatabaseSave(intent) else -> null } @@ -179,11 +191,12 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat return CreateDatabaseRunnable(this, Database.getInstance(), databaseUri, + getString(R.string.database_default_name), + getString(R.string.database), intent.getBooleanExtra(MASTER_PASSWORD_CHECKED_KEY, false), intent.getStringExtra(MASTER_PASSWORD_KEY), intent.getBooleanExtra(KEY_FILE_CHECKED_KEY, false), - keyFileUri, - true // TODO get readonly + keyFileUri ) } else { return null @@ -241,12 +254,12 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat ) { AssignPasswordInDatabaseRunnable(this, Database.getInstance(), - intent.getParcelableExtra(DATABASE_URI_KEY), + intent.getParcelableExtra(DATABASE_URI_KEY), intent.getBooleanExtra(MASTER_PASSWORD_CHECKED_KEY, false), intent.getStringExtra(MASTER_PASSWORD_KEY), intent.getBooleanExtra(KEY_FILE_CHECKED_KEY, false), - intent.getParcelableExtra(KEY_FILE_KEY), - true) + intent.getParcelableExtra(KEY_FILE_KEY) + ) } else { null } @@ -288,7 +301,7 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat ) { val database = Database.getInstance() database.getGroupById(intent.getParcelableExtra(GROUP_ID_KEY))?.let { oldGroup -> - val newGroup: GroupVersioned = intent.getParcelableExtra(GROUP_KEY) + val newGroup: Group = intent.getParcelableExtra(GROUP_KEY) UpdateGroupRunnable(this, database, oldGroup, @@ -327,7 +340,7 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat ) { val database = Database.getInstance() database.getEntryById(intent.getParcelableExtra(ENTRY_ID_KEY))?.let { oldEntry -> - val newEntry: EntryVersioned = intent.getParcelableExtra(ENTRY_KEY) + val newEntry: Entry = intent.getParcelableExtra(ENTRY_KEY) UpdateEntryRunnable(this, database, oldEntry, @@ -355,7 +368,6 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat intent.getBooleanExtra(SAVE_DATABASE_KEY, false), AfterActionNodesRunnable()) } - } else { null } @@ -376,7 +388,6 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat intent.getBooleanExtra(SAVE_DATABASE_KEY, false), AfterActionNodesRunnable()) } - } else { null } @@ -393,20 +404,55 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat getListNodesFromBundle(database, intent.extras!!), intent.getBooleanExtra(SAVE_DATABASE_KEY, false), AfterActionNodesRunnable()) + } else { + null + } + } + private fun buildDatabaseUpdateCompressionActionTask(intent: Intent): ActionRunnable? { + return if (intent.hasExtra(OLD_ELEMENT_KEY) + && intent.hasExtra(NEW_ELEMENT_KEY) + && intent.hasExtra(SAVE_DATABASE_KEY)) { + return UpdateCompressionBinariesDatabaseRunnable(this, + Database.getInstance(), + intent.getParcelableExtra(OLD_ELEMENT_KEY), + intent.getParcelableExtra(NEW_ELEMENT_KEY), + intent.getBooleanExtra(SAVE_DATABASE_KEY, false) + ).apply { + mAfterSaveDatabase = { result -> + result.data = intent.extras + } + } } else { null } } - private fun buildDatabaseSaveElementActionTask(intent: Intent): ActionRunnable? { - return SaveDatabaseRunnable(this, - Database.getInstance(), - true - ).apply { - mAfterSaveDatabase = { result -> - result.data = intent.extras + private fun buildDatabaseUpdateElementActionTask(intent: Intent): ActionRunnable? { + return if (intent.hasExtra(SAVE_DATABASE_KEY)) { + return SaveDatabaseRunnable(this, + Database.getInstance(), + intent.getBooleanExtra(SAVE_DATABASE_KEY, false) + ).apply { + mAfterSaveDatabase = { result -> + result.data = intent.extras + } } + } else { + null + } + } + + /** + * Save database without parameter + */ + private fun buildDatabaseSave(intent: Intent): ActionRunnable? { + return if (intent.hasExtra(SAVE_DATABASE_KEY)) { + SaveDatabaseRunnable(this, + Database.getInstance(), + intent.getBooleanExtra(SAVE_DATABASE_KEY, false)) + } else { + null } } @@ -457,18 +503,19 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat 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_DELETE_NODES_TASK = "ACTION_DATABASE_DELETE_NODES_TASK" - const val ACTION_DATABASE_SAVE_NAME_TASK = "ACTION_DATABASE_SAVE_NAME_TASK" - const val ACTION_DATABASE_SAVE_DESCRIPTION_TASK = "ACTION_DATABASE_SAVE_DESCRIPTION_TASK" - const val ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK = "ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK" - const val ACTION_DATABASE_SAVE_COLOR_TASK = "ACTION_DATABASE_SAVE_COLOR_TASK" - const val ACTION_DATABASE_SAVE_COMPRESSION_TASK = "ACTION_DATABASE_SAVE_COMPRESSION_TASK" - const val ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK = "ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK" - const val ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK = "ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK" - const val ACTION_DATABASE_SAVE_ENCRYPTION_TASK = "ACTION_DATABASE_SAVE_ENCRYPTION_TASK" - const val ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK = "ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK" - const val ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK = "ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK" - const val ACTION_DATABASE_SAVE_PARALLELISM_TASK = "ACTION_DATABASE_SAVE_PARALLELISM_TASK" - const val ACTION_DATABASE_SAVE_ITERATIONS_TASK = "ACTION_DATABASE_SAVE_ITERATIONS_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_DEFAULT_USERNAME_TASK = "ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK" + const val ACTION_DATABASE_UPDATE_COLOR_TASK = "ACTION_DATABASE_UPDATE_COLOR_TASK" + const val ACTION_DATABASE_UPDATE_COMPRESSION_TASK = "ACTION_DATABASE_UPDATE_COMPRESSION_TASK" + const val ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK = "ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK" + const val ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK = "ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK" + const val ACTION_DATABASE_UPDATE_ENCRYPTION_TASK = "ACTION_DATABASE_UPDATE_ENCRYPTION_TASK" + const val ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK = "ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK" + const val ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK = "ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK" + const val ACTION_DATABASE_UPDATE_PARALLELISM_TASK = "ACTION_DATABASE_UPDATE_PARALLELISM_TASK" + const val ACTION_DATABASE_UPDATE_ITERATIONS_TASK = "ACTION_DATABASE_UPDATE_ITERATIONS_TASK" + const val ACTION_DATABASE_SAVE = "ACTION_DATABASE_SAVE" const val DATABASE_URI_KEY = "DATABASE_URI_KEY" const val MASTER_PASSWORD_CHECKED_KEY = "MASTER_PASSWORD_CHECKED_KEY" @@ -491,14 +538,14 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat const val OLD_ELEMENT_KEY = "OLD_ELEMENT_KEY" // Warning type of this thing change every time const val NEW_ELEMENT_KEY = "NEW_ELEMENT_KEY" // Warning type of this thing change every time - fun getListNodesFromBundle(database: Database, bundle: Bundle): List { - val nodesAction = ArrayList() - bundle.getParcelableArrayList>(GROUPS_ID_KEY)?.forEach { + fun getListNodesFromBundle(database: Database, bundle: Bundle): List { + val nodesAction = ArrayList() + bundle.getParcelableArrayList>(GROUPS_ID_KEY)?.forEach { database.getGroupById(it)?.let { groupRetrieve -> nodesAction.add(groupRetrieve) } } - bundle.getParcelableArrayList>(ENTRIES_ID_KEY)?.forEach { + bundle.getParcelableArrayList>(ENTRIES_ID_KEY)?.forEach { database.getEntryById(it)?.let { entryRetrieve -> nodesAction.add(entryRetrieve) } @@ -506,24 +553,24 @@ class DatabaseTaskNotificationService : NotificationService(), ProgressTaskUpdat return nodesAction } - fun getBundleFromListNodes(nodes: List): Bundle { - val groupsIdToCopy = ArrayList>() - val entriesIdToCopy = ArrayList>() + fun getBundleFromListNodes(nodes: List): Bundle { + val groupsId = ArrayList>() + val entriesId = ArrayList>() nodes.forEach { nodeVersioned -> when (nodeVersioned.type) { Type.GROUP -> { - (nodeVersioned as GroupVersioned).nodeId?.let { groupId -> - groupsIdToCopy.add(groupId) + (nodeVersioned as Group).nodeId?.let { groupId -> + groupsId.add(groupId) } } Type.ENTRY -> { - entriesIdToCopy.add((nodeVersioned as EntryVersioned).nodeId) + entriesId.add((nodeVersioned as Entry).nodeId) } } } return Bundle().apply { - putParcelableArrayList(GROUPS_ID_KEY, groupsIdToCopy) - putParcelableArrayList(ENTRIES_ID_KEY, entriesIdToCopy) + putParcelableArrayList(GROUPS_ID_KEY, groupsId) + putParcelableArrayList(ENTRIES_ID_KEY, entriesId) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt b/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt index ce9393d7e..2b094666b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt +++ b/app/src/main/java/com/kunzisoft/keepass/otp/OtpElement.kt @@ -125,8 +125,9 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) { @Throws(IllegalArgumentException::class) fun setBase32Secret(secret: String) { - if (secret.isNotEmpty() && checkBase32Secret(secret)) - otpModel.secret = Base32().decode(secret.toByteArray()) + val secretChars = replaceBase32Chars(secret) + if (secretChars.isNotEmpty() && checkBase32Secret(secretChars)) + otpModel.secret = Base32().decode(secretChars.toByteArray()) else throw IllegalArgumentException() } @@ -169,6 +170,19 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) { const val MIN_OTP_DIGITS = 4 const val MAX_OTP_DIGITS = 18 + fun replaceSpaceChars(parameter: String): String { + return parameter.replace("[\\r|\\n|\\t|\\s|\\u00A0]+".toRegex(), "") + } + + fun replaceBase32Chars(parameter: String): String { + // Add 'A' at end if not Base32 length + var parameterNewSize = replaceSpaceChars(parameter.toUpperCase(Locale.ENGLISH)) + while (parameterNewSize.length % 8 != 0) { + parameterNewSize += 'A' + } + return parameterNewSize + } + fun checkBase32Secret(secret: String): Boolean { return (Pattern.matches("^(?:[A-Z2-7]{8})*(?:[A-Z2-7]{2}={6}|[A-Z2-7]{4}={4}|[A-Z2-7]{5}={3}|[A-Z2-7]{7}=)?$", secret)) } diff --git a/app/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt b/app/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt index 6af0f1028..73bfba63d 100644 --- a/app/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt +++ b/app/src/main/java/com/kunzisoft/keepass/otp/OtpEntryFields.kt @@ -24,6 +24,7 @@ import android.net.Uri import android.util.Log import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.model.Field +import com.kunzisoft.keepass.otp.OtpElement.Companion.replaceSpaceChars import com.kunzisoft.keepass.otp.TokenCalculator.* import java.lang.Exception import java.lang.StringBuilder @@ -106,7 +107,7 @@ object OtpEntryFields { private fun parseOTPUri(getField: (id: String) -> String?, otpElement: OtpElement): Boolean { val otpPlainText = getField(OTP_FIELD) if (otpPlainText != null && otpPlainText.isNotEmpty()) { - val uri = Uri.parse(replaceChars(otpPlainText)) + val uri = Uri.parse(replaceSpaceChars(otpPlainText)) if (uri.scheme == null || OTP_SCHEME != uri.scheme!!.toLowerCase(Locale.ENGLISH)) { Log.e(TAG, "Invalid or missing scheme in uri") @@ -235,11 +236,7 @@ object OtpEntryFields { } private fun replaceCharsForUrl(parameter: String): String { - return URLEncoder.encode(replaceChars(parameter), "UTF-8") - } - - private fun replaceChars(parameter: String): String { - return parameter.replace("([\\r|\\n|\\t|\\s|\\u00A0]+)", "") + return URLEncoder.encode(replaceSpaceChars(parameter), "UTF-8") } private fun parseTOTPKeyValues(getField: (id: String) -> String?, otpElement: OtpElement): Boolean { diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt new file mode 100644 index 000000000..a2c7d3928 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedAppSettingsFragment.kt @@ -0,0 +1,351 @@ +package com.kunzisoft.keepass.settings + +import android.content.ActivityNotFoundException +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.os.Bundle +import android.provider.Settings +import android.util.Log +import android.view.autofill.AutofillManager +import android.widget.Toast +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AlertDialog +import androidx.biometric.BiometricManager +import androidx.preference.ListPreference +import androidx.preference.Preference +import androidx.preference.SwitchPreference +import com.kunzisoft.keepass.BuildConfig +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.activities.dialogs.ProFeatureDialogFragment +import com.kunzisoft.keepass.activities.dialogs.UnavailableFeatureDialogFragment +import com.kunzisoft.keepass.activities.stylish.Stylish +import com.kunzisoft.keepass.app.database.CipherDatabaseAction +import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction +import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper +import com.kunzisoft.keepass.education.Education +import com.kunzisoft.keepass.icons.IconPackChooser +import com.kunzisoft.keepass.settings.preference.IconPackListPreference +import com.kunzisoft.keepass.utils.UriUtil + +class NestedAppSettingsFragment : NestedSettingsFragment() { + + override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) { + + // Load the preferences from an XML resource + when (screen) { + Screen.APPLICATION -> { + onCreateApplicationPreferences(rootKey) + } + Screen.FORM_FILLING -> { + onCreateFormFillingPreference(rootKey) + } + Screen.ADVANCED_UNLOCK -> { + onCreateAdvancedUnlockPreferences(rootKey) + } + Screen.APPEARANCE -> { + onCreateAppearancePreferences(rootKey) + } + else -> {} + } + } + + private fun onCreateApplicationPreferences(rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_application, rootKey) + + activity?.let { activity -> + allowCopyPassword() + + findPreference(getString(R.string.keyfile_key))?.setOnPreferenceChangeListener { _, newValue -> + if (!(newValue as Boolean)) { + FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAllKeyFiles() + } + true + } + + findPreference(getString(R.string.recentfile_key))?.setOnPreferenceChangeListener { _, newValue -> + if (!(newValue as Boolean)) { + FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAll() + } + true + } + } + } + + private fun onCreateFormFillingPreference(rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_form_filling, rootKey) + + activity?.let { activity -> + val autoFillEnablePreference: SwitchPreference? = findPreference(getString(R.string.settings_autofill_enable_key)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val autofillManager = activity.getSystemService(AutofillManager::class.java) + if (autofillManager != null && autofillManager.hasEnabledAutofillServices()) + autoFillEnablePreference?.isChecked = autofillManager.hasEnabledAutofillServices() + autoFillEnablePreference?.onPreferenceClickListener = object : Preference.OnPreferenceClickListener { + @RequiresApi(api = Build.VERSION_CODES.O) + override fun onPreferenceClick(preference: Preference): Boolean { + if ((preference as SwitchPreference).isChecked) { + try { + startEnableService() + } catch (e: ActivityNotFoundException) { + val error = getString(R.string.error_autofill_enable_service) + preference.isChecked = false + Log.d(javaClass.name, error, e) + Toast.makeText(context, error, Toast.LENGTH_SHORT).show() + } + + } else { + disableService() + } + return false + } + + @RequiresApi(api = Build.VERSION_CODES.O) + private fun disableService() { + if (autofillManager != null && autofillManager.hasEnabledAutofillServices()) { + autofillManager.disableAutofillServices() + } else { + Log.d(javaClass.name, "Sample service already disabled.") + } + } + + @RequiresApi(api = Build.VERSION_CODES.O) + @Throws(ActivityNotFoundException::class) + private fun startEnableService() { + if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) { + val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE) + // TODO Autofill + intent.data = Uri.parse("package:com.example.android.autofill.service") + Log.d(javaClass.name, "enableService(): intent=$intent") + startActivityForResult(intent, REQUEST_CODE_AUTOFILL) + } else { + Log.d(javaClass.name, "Sample service already enabled.") + } + } + } + } else { + autoFillEnablePreference?.setOnPreferenceClickListener { preference -> + (preference as SwitchPreference).isChecked = false + val fragmentManager = fragmentManager!! + UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.O) + .show(fragmentManager, "unavailableFeatureDialog") + false + } + } + } + + findPreference(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener { + UriUtil.gotoUrl(context!!, R.string.magic_keyboard_explanation_url) + false + } + + findPreference(getString(R.string.magic_keyboard_key))?.setOnPreferenceClickListener { + startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + }) + false + } + + findPreference(getString(R.string.magic_keyboard_preference_key))?.setOnPreferenceClickListener { + startActivity(Intent(context, MagikIMESettings::class.java)) + false + } + + findPreference(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener { + UriUtil.gotoUrl(context!!, R.string.clipboard_explanation_url) + false + } + + findPreference(getString(R.string.autofill_explanation_key))?.setOnPreferenceClickListener { + UriUtil.gotoUrl(context!!, R.string.autofill_explanation_url) + false + } + + // Present in two places + allowCopyPassword() + } + + private fun allowCopyPassword() { + val copyPasswordPreference: SwitchPreference? = findPreference(getString(R.string.allow_copy_password_key)) + copyPasswordPreference?.setOnPreferenceChangeListener { _, newValue -> + if (newValue as Boolean && context != null) { + val message = getString(R.string.allow_copy_password_warning) + + "\n\n" + + getString(R.string.clipboard_warning) + AlertDialog + .Builder(context!!) + .setMessage(message) + .create() + .apply { + setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) + { dialog, _ -> + dialog.dismiss() + } + setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable)) + { dialog, _ -> + copyPasswordPreference.isChecked = false + dialog.dismiss() + } + show() + } + } + true + } + } + + private fun onCreateAdvancedUnlockPreferences(rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_advanced_unlock, rootKey) + + activity?.let { activity -> + val biometricUnlockEnablePreference: SwitchPreference? = findPreference(getString(R.string.biometric_unlock_enable_key)) + // < M solve verifyError exception + var biometricUnlockSupported = false + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + val biometricCanAuthenticate = BiometricManager.from(activity).canAuthenticate() + biometricUnlockSupported = biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED + || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS + } + if (!biometricUnlockSupported) { + // False if under Marshmallow + biometricUnlockEnablePreference?.apply { + isChecked = false + setOnPreferenceClickListener { preference -> + fragmentManager?.let { fragmentManager -> + (preference as SwitchPreference).isChecked = false + UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M) + .show(fragmentManager, "unavailableFeatureDialog") + } + false + } + } + } + + val deleteKeysFingerprints: Preference? = findPreference(getString(R.string.biometric_delete_all_key_key)) + if (!biometricUnlockSupported) { + deleteKeysFingerprints?.isEnabled = false + } else { + deleteKeysFingerprints?.setOnPreferenceClickListener { + context?.let { context -> + AlertDialog.Builder(context) + .setMessage(resources.getString(R.string.biometric_delete_all_key_warning)) + .setIcon(android.R.drawable.ic_dialog_alert) + .setPositiveButton(resources.getString(android.R.string.yes) + ) { _, _ -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + BiometricUnlockDatabaseHelper.deleteEntryKeyInKeystoreForBiometric( + activity, + object : BiometricUnlockDatabaseHelper.BiometricUnlockErrorCallback { + override fun onInvalidKeyException(e: Exception) {} + + override fun onBiometricException(e: Exception) { + Toast.makeText(context, + getString(R.string.biometric_scanning_error, e.localizedMessage), + Toast.LENGTH_SHORT).show() + } + }) + } + CipherDatabaseAction.getInstance(context.applicationContext).deleteAll() + } + .setNegativeButton(resources.getString(android.R.string.no)) + { _, _ -> }.show() + } + false + } + } + } + + findPreference(getString(R.string.advanced_unlock_explanation_key))?.setOnPreferenceClickListener { + UriUtil.gotoUrl(context!!, R.string.advanced_unlock_explanation_url) + false + } + } + + private fun onCreateAppearancePreferences(rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_appearance, rootKey) + + activity?.let { activity -> + findPreference(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue -> + var styleEnabled = true + val styleIdString = newValue as String + if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!)) + for (themeIdDisabled in BuildConfig.STYLES_DISABLED) { + if (themeIdDisabled == styleIdString) { + styleEnabled = false + fragmentManager?.let { fragmentManager -> + ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog") + } + } + } + if (styleEnabled) { + Stylish.assignStyle(styleIdString) + activity.recreate() + } + styleEnabled + } + + findPreference(getString(R.string.setting_icon_pack_choose_key))?.setOnPreferenceChangeListener { _, newValue -> + var iconPackEnabled = true + val iconPackId = newValue as String + if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!)) + for (iconPackIdDisabled in BuildConfig.ICON_PACKS_DISABLED) { + if (iconPackIdDisabled == iconPackId) { + iconPackEnabled = false + fragmentManager?.let { fragmentManager -> + ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog") + } + } + } + if (iconPackEnabled) { + IconPackChooser.setSelectedIconPack(iconPackId) + } + iconPackEnabled + } + + findPreference(getString(R.string.reset_education_screens_key))?.setOnPreferenceClickListener { + // To allow only one toast + if (mCount == 0) { + val sharedPreferences = Education.getEducationSharedPreferences(context!!) + val editor = sharedPreferences.edit() + for (resourceId in Education.educationResourcesKeys) { + editor.putBoolean(getString(resourceId), false) + } + editor.apply() + Toast.makeText(context, R.string.reset_education_screens_text, Toast.LENGTH_SHORT).show() + } + mCount++ + false + } + } + } + + override fun onResume() { + super.onResume() + + activity?.let { activity -> + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val autoFillEnablePreference: SwitchPreference? = findPreference(getString(R.string.settings_autofill_enable_key)) + if (autoFillEnablePreference != null) { + val autofillManager = activity.getSystemService(AutofillManager::class.java) + autoFillEnablePreference.isChecked = autofillManager != null + && autofillManager.hasEnabledAutofillServices() + } + } + } + } + + private var mCount = 0 + override fun onStop() { + super.onStop() + activity?.let { activity -> + if (mCount == 10) { + Education.getEducationSharedPreferences(activity).edit() + .putBoolean(getString(R.string.education_screen_reclicked_key), true).apply() + } + } + } + + companion object { + + private const val REQUEST_CODE_AUTOFILL = 5201 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt new file mode 100644 index 000000000..3daefa0f2 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedDatabaseSettingsFragment.kt @@ -0,0 +1,564 @@ +package com.kunzisoft.keepass.settings + +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.view.* +import androidx.fragment.app.DialogFragment +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.SwitchPreference +import com.kunzisoft.androidclearchroma.ChromaUtil +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment +import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper +import com.kunzisoft.keepass.activities.lock.lock +import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine +import com.kunzisoft.keepass.database.element.Database +import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm +import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService +import com.kunzisoft.keepass.settings.preference.* +import com.kunzisoft.keepass.settings.preferencedialogfragment.* +import com.kunzisoft.keepass.tasks.ActionRunnable +import com.kunzisoft.keepass.utils.MenuUtil + +class NestedDatabaseSettingsFragment : NestedSettingsFragment() { + + private var mDatabase: Database = Database.getInstance() + private var mDatabaseReadOnly: Boolean = false + private var mDatabaseAutoSaveEnabled: Boolean = true + + private var dbNamePref: InputTextPreference? = null + private var dbDescriptionPref: InputTextPreference? = null + private var dbDefaultUsername: InputTextPreference? = null + private var dbCustomColorPref: DialogColorPreference? = null + private var dbDataCompressionPref: Preference? = null + private var recycleBinGroupPref: Preference? = null + private var dbMaxHistoryItemsPref: InputNumberPreference? = null + private var dbMaxHistorySizePref: InputNumberPreference? = null + private var mEncryptionAlgorithmPref: DialogListExplanationPreference? = null + private var mKeyDerivationPref: DialogListExplanationPreference? = null + private var mRoundPref: InputKdfNumberPreference? = null + private var mMemoryPref: InputKdfNumberPreference? = null + private var mParallelismPref: InputKdfNumberPreference? = null + + override fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) { + setHasOptionsMenu(true) + + mDatabaseReadOnly = mDatabase.isReadOnly + || ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments) + + // Load the preferences from an XML resource + when (screen) { + Screen.DATABASE -> { + onCreateDatabasePreference(rootKey) + } + Screen.DATABASE_SECURITY -> { + onCreateDatabaseSecurityPreference(rootKey) + } + Screen.DATABASE_MASTER_KEY -> { + onCreateDatabaseMasterKeyPreference(rootKey) + } + else -> {} + } + } + + private fun onCreateDatabasePreference(rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_database, rootKey) + + if (mDatabase.loaded) { + + val dbGeneralPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_general_key)) + + // Database name + dbNamePref = findPreference(getString(R.string.database_name_key)) + if (mDatabase.allowName) { + dbNamePref?.summary = mDatabase.name + } else { + dbGeneralPrefCategory?.removePreference(dbNamePref) + } + + // Database description + dbDescriptionPref = findPreference(getString(R.string.database_description_key)) + if (mDatabase.allowDescription) { + dbDescriptionPref?.summary = mDatabase.description + } else { + dbGeneralPrefCategory?.removePreference(dbDescriptionPref) + } + + // Database default username + dbDefaultUsername = findPreference(getString(R.string.database_default_username_key)) + if (mDatabase.allowDefaultUsername) { + dbDefaultUsername?.summary = mDatabase.defaultUsername + } else { + dbDefaultUsername?.isEnabled = false + // TODO dbGeneralPrefCategory?.removePreference(dbDefaultUsername) + } + + // Database custom color + dbCustomColorPref = findPreference(getString(R.string.database_custom_color_key)) + if (mDatabase.allowCustomColor) { + dbCustomColorPref?.apply { + try { + color = Color.parseColor(mDatabase.customColor) + summary = mDatabase.customColor + } catch (e: Exception) { + color = DialogColorPreference.DISABLE_COLOR + summary = "" + } + } + } else { + dbCustomColorPref?.isEnabled = false + // TODO dbGeneralPrefCategory?.removePreference(dbCustomColorPref) + } + + // Version + findPreference(getString(R.string.database_version_key)) + ?.summary = mDatabase.version + + val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_compression_key)) + + // Database compression + dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key)) + if (mDatabase.allowDataCompression) { + dbDataCompressionPref?.summary = (mDatabase.compressionAlgorithm + ?: CompressionAlgorithm.None).getName(resources) + } else { + dbCompressionPrefCategory?.isVisible = false + } + + val dbRecycleBinPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_recycle_bin_key)) + recycleBinGroupPref = findPreference(getString(R.string.recycle_bin_group_key)) + + // Recycle bin + if (mDatabase.allowRecycleBin) { + val recycleBinEnablePref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_enable_key)) + recycleBinEnablePref?.apply { + isChecked = mDatabase.isRecycleBinEnabled + isEnabled = if (!mDatabaseReadOnly) { + setOnPreferenceChangeListener { _, newValue -> + val recycleBinEnabled = newValue as Boolean + mDatabase.isRecycleBinEnabled = recycleBinEnabled + if (recycleBinEnabled) { + mDatabase.ensureRecycleBinExists(resources) + } else { + mDatabase.removeRecycleBin() + } + refreshRecycleBinGroup() + // Save the database if not in readonly mode + (context as SettingsActivity?)?. + mProgressDialogThread?.startDatabaseSave(mDatabaseAutoSaveEnabled) + true + } + true + } else { + false + } + } + // Recycle Bin group + refreshRecycleBinGroup() + } else { + dbRecycleBinPrefCategory?.isVisible = false + } + + // History + findPreference(getString(R.string.database_category_history_key)) + ?.isVisible = mDatabase.manageHistory == true + + // Max history items + dbMaxHistoryItemsPref = findPreference(getString(R.string.max_history_items_key))?.apply { + summary = mDatabase.historyMaxItems.toString() + } + + // Max history size + dbMaxHistorySizePref = findPreference(getString(R.string.max_history_size_key))?.apply { + summary = mDatabase.historyMaxSize.toString() + } + + } else { + Log.e(javaClass.name, "Database isn't ready") + } + } + + private fun refreshRecycleBinGroup() { + recycleBinGroupPref?.apply { + if (mDatabase.isRecycleBinEnabled) { + summary = mDatabase.recycleBin?.title + isEnabled = true + } else { + summary = null + isEnabled = false + } + } + } + + private fun onCreateDatabaseSecurityPreference(rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_database_security, rootKey) + + if (mDatabase.loaded) { + // Encryption Algorithm + mEncryptionAlgorithmPref = findPreference(getString(R.string.encryption_algorithm_key))?.apply { + summary = mDatabase.getEncryptionAlgorithmName(resources) + } + + // Key derivation function + mKeyDerivationPref = findPreference(getString(R.string.key_derivation_function_key))?.apply { + summary = mDatabase.getKeyDerivationName(resources) + } + + // Round encryption + mRoundPref = findPreference(getString(R.string.transform_rounds_key))?.apply { + summary = mDatabase.numberKeyEncryptionRounds.toString() + } + + // Memory Usage + mMemoryPref = findPreference(getString(R.string.memory_usage_key))?.apply { + summary = mDatabase.memoryUsage.toString() + } + + // Parallelism + mParallelismPref = findPreference(getString(R.string.parallelism_key))?.apply { + summary = mDatabase.parallelism.toString() + } + } else { + Log.e(javaClass.name, "Database isn't ready") + } + } + + private fun onCreateDatabaseMasterKeyPreference(rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_database_master_key, rootKey) + + if (mDatabase.loaded) { + findPreference(getString(R.string.settings_database_change_credentials_key))?.apply { + isEnabled = if (!mDatabaseReadOnly) { + onPreferenceClickListener = Preference.OnPreferenceClickListener { + fragmentManager?.let { fragmentManager -> + AssignMasterKeyDialogFragment.getInstance(mDatabase.allowNoMasterKey) + .show(fragmentManager, "passwordDialog") + } + false + } + true + } else { + false + } + } + } else { + Log.e(javaClass.name, "Database isn't ready") + } + } + + private val colorSelectedListener: ((Boolean, Int)-> Unit)? = { enable, color -> + dbCustomColorPref?.summary = ChromaUtil.getFormattedColorString(color, false) + if (enable) { + dbCustomColorPref?.color = color + } else { + dbCustomColorPref?.color = DialogColorPreference.DISABLE_COLOR + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = super.onCreateView(inflater, container, savedInstanceState) + + try { + // To reassign color listener after orientation change + val chromaDialog = fragmentManager?.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat? + chromaDialog?.onColorSelectedListener = colorSelectedListener + } catch (e: Exception) {} + + return view + } + + override fun onProgressDialogThreadResult(actionTask: String, + result: ActionRunnable.Result) { + result.data?.let { data -> + if (data.containsKey(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) + && data.containsKey(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)) { + when (actionTask) { + /* + -------- + Main preferences + -------- + */ + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_NAME_TASK -> { + val oldName = data.getString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)!! + val newName = data.getString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)!! + val nameToShow = + if (result.isSuccess) { + newName + } else { + mDatabase.name = oldName + oldName + } + dbNamePref?.summary = nameToShow + } + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_DESCRIPTION_TASK -> { + val oldDescription = data.getString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)!! + val newDescription = data.getString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)!! + val descriptionToShow = + if (result.isSuccess) { + newDescription + } else { + mDatabase.description = oldDescription + oldDescription + } + dbDescriptionPref?.summary = descriptionToShow + } + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_DEFAULT_USERNAME_TASK -> { + val oldDefaultUsername = data.getString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)!! + val newDefaultUsername = data.getString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)!! + val defaultUsernameToShow = + if (result.isSuccess) { + newDefaultUsername + } else { + mDatabase.defaultUsername = oldDefaultUsername + oldDefaultUsername + } + dbDefaultUsername?.summary = defaultUsernameToShow + } + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_COLOR_TASK -> { + val oldColor = data.getString(DatabaseTaskNotificationService.OLD_ELEMENT_KEY)!! + val newColor = data.getString(DatabaseTaskNotificationService.NEW_ELEMENT_KEY)!! + + val defaultColorToShow = + if (result.isSuccess) { + newColor + } else { + mDatabase.customColor = oldColor + oldColor + } + dbCustomColorPref?.summary = defaultColorToShow + } + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_COMPRESSION_TASK -> { + val oldCompression = data.getSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) as CompressionAlgorithm + val newCompression = data.getSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) as CompressionAlgorithm + val algorithmToShow = + if (result.isSuccess) { + newCompression + } else { + mDatabase.compressionAlgorithm = oldCompression + oldCompression + } + dbDataCompressionPref?.summary = algorithmToShow.getName(resources) + } + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_MAX_HISTORY_ITEMS_TASK -> { + val oldMaxHistoryItems = data.getInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) + val newMaxHistoryItems = data.getInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) + val maxHistoryItemsToShow = + if (result.isSuccess) { + newMaxHistoryItems + } else { + mDatabase.historyMaxItems = oldMaxHistoryItems + oldMaxHistoryItems + } + dbMaxHistoryItemsPref?.summary = maxHistoryItemsToShow.toString() + } + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_MAX_HISTORY_SIZE_TASK -> { + val oldMaxHistorySize = data.getLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) + val newMaxHistorySize = data.getLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) + val maxHistorySizeToShow = + if (result.isSuccess) { + newMaxHistorySize + } else { + mDatabase.historyMaxSize = oldMaxHistorySize + oldMaxHistorySize + } + dbMaxHistorySizePref?.summary = maxHistorySizeToShow.toString() + } + + /* + -------- + Security + -------- + */ + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_ENCRYPTION_TASK -> { + val oldEncryption = data.getSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) as EncryptionAlgorithm + val newEncryption = data.getSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) as EncryptionAlgorithm + val algorithmToShow = + if (result.isSuccess) { + newEncryption + } else { + mDatabase.encryptionAlgorithm = oldEncryption + oldEncryption + } + mEncryptionAlgorithmPref?.summary = algorithmToShow.getName(resources) + } + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_KEY_DERIVATION_TASK -> { + val oldKeyDerivationEngine = data.getSerializable(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) as KdfEngine + val newKeyDerivationEngine = data.getSerializable(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) as KdfEngine + val kdfEngineToShow = + if (result.isSuccess) { + newKeyDerivationEngine + } else { + mDatabase.kdfEngine = oldKeyDerivationEngine + oldKeyDerivationEngine + } + mKeyDerivationPref?.summary = kdfEngineToShow.getName(resources) + + mRoundPref?.summary = kdfEngineToShow.defaultKeyRounds.toString() + // Disable memory and parallelism if not available + mMemoryPref?.summary = kdfEngineToShow.defaultMemoryUsage.toString() + mParallelismPref?.summary = kdfEngineToShow.defaultParallelism.toString() + } + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_ITERATIONS_TASK -> { + val oldIterations = data.getLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) + val newIterations = data.getLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) + val roundsToShow = + if (result.isSuccess) { + newIterations + } else { + mDatabase.numberKeyEncryptionRounds = oldIterations + oldIterations + } + mRoundPref?.summary = roundsToShow.toString() + } + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_MEMORY_USAGE_TASK -> { + val oldMemoryUsage = data.getLong(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) + val newMemoryUsage = data.getLong(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) + val memoryToShow = + if (result.isSuccess) { + newMemoryUsage + } else { + mDatabase.memoryUsage = oldMemoryUsage + oldMemoryUsage + } + mMemoryPref?.summary = memoryToShow.toString() + } + DatabaseTaskNotificationService.ACTION_DATABASE_UPDATE_PARALLELISM_TASK -> { + val oldParallelism = data.getInt(DatabaseTaskNotificationService.OLD_ELEMENT_KEY) + val newParallelism = data.getInt(DatabaseTaskNotificationService.NEW_ELEMENT_KEY) + val parallelismToShow = + if (result.isSuccess) { + newParallelism + } else { + mDatabase.parallelism = oldParallelism + oldParallelism + } + mParallelismPref?.summary = parallelismToShow.toString() + } + } + } + } + } + + override fun onDisplayPreferenceDialog(preference: Preference?) { + + var otherDialogFragment = false + + fragmentManager?.let { fragmentManager -> + preference?.let { preference -> + var dialogFragment: DialogFragment? = null + when { + // Main Preferences + preference.key == getString(R.string.database_name_key) -> { + dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.key) + } + preference.key == getString(R.string.database_description_key) -> { + dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.key) + } + preference.key == getString(R.string.database_default_username_key) -> { + dialogFragment = DatabaseDefaultUsernamePreferenceDialogFragmentCompat.newInstance(preference.key) + } + preference.key == getString(R.string.database_custom_color_key) -> { + dialogFragment = DatabaseColorPreferenceDialogFragmentCompat.newInstance(preference.key).apply { + onColorSelectedListener = colorSelectedListener + } + } + preference.key == getString(R.string.database_data_compression_key) -> { + dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key) + } + preference.key == getString(R.string.max_history_items_key) -> { + dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key) + } + preference.key == getString(R.string.max_history_size_key) -> { + dialogFragment = MaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key) + } + + // Security + preference.key == getString(R.string.encryption_algorithm_key) -> { + dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.key) + } + preference.key == getString(R.string.key_derivation_function_key) -> { + val keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.key) + // Add other prefs to manage + keyDerivationDialogFragment.setRoundPreference(mRoundPref) + keyDerivationDialogFragment.setMemoryPreference(mMemoryPref) + keyDerivationDialogFragment.setParallelismPreference(mParallelismPref) + dialogFragment = keyDerivationDialogFragment + } + preference.key == getString(R.string.transform_rounds_key) -> { + dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.key) + } + preference.key == getString(R.string.memory_usage_key) -> { + dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key) + } + preference.key == getString(R.string.parallelism_key) -> { + dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.key) + } + else -> otherDialogFragment = true + } + + if (dialogFragment != null && !mDatabaseReadOnly) { + dialogFragment.setTargetFragment(this, 0) + dialogFragment.show(fragmentManager, TAG_PREF_FRAGMENT) + } + // Could not be handled here. Try with the super method. + else if (otherDialogFragment) { + super.onDisplayPreferenceDialog(preference) + } + } + } + } + + override fun onResume() { + super.onResume() + + context?.let { context -> + mDatabaseAutoSaveEnabled = PreferencesUtil.isAutoSaveDatabaseEnabled(context) + } + } + + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + super.onCreateOptionsMenu(menu, inflater) + + inflater.inflate(R.menu.database, menu) + if (mDatabaseReadOnly) { + menu.findItem(R.id.menu_save_database)?.isVisible = false + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + + val settingActivity = activity as SettingsActivity? + + when (item.itemId) { + R.id.menu_lock -> { + settingActivity?.lock() + return true + } + R.id.menu_save_database -> { + settingActivity?.mProgressDialogThread?.startDatabaseSave(!mDatabaseReadOnly) + return true + } + + else -> { + // Check the time lock before launching settings + settingActivity?.let { + MenuUtil.onDefaultMenuOptionsItemSelected(it, item, mDatabaseReadOnly, true) + } + return super.onOptionsItemSelected(item) + } + } + } + + override fun onSaveInstanceState(outState: Bundle) { + ReadOnlyHelper.onSaveInstanceState(outState, mDatabaseReadOnly) + super.onSaveInstanceState(outState) + } + + companion object { + + private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt b/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt index 9e6837050..2ebd4c605 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/NestedSettingsFragment.kt @@ -19,548 +19,37 @@ */ package com.kunzisoft.keepass.settings -import android.content.ActivityNotFoundException -import android.content.Intent import android.content.res.Resources -import android.graphics.Color -import android.net.Uri -import android.os.Build import android.os.Bundle -import android.provider.Settings -import android.util.Log -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.view.autofill.AutofillManager -import android.widget.Toast -import androidx.annotation.RequiresApi -import androidx.appcompat.app.AlertDialog -import androidx.biometric.BiometricManager -import androidx.fragment.app.DialogFragment -import androidx.preference.* -import com.kunzisoft.androidclearchroma.ChromaUtil -import com.kunzisoft.keepass.BuildConfig +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.SwitchPreference import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.activities.dialogs.* +import com.kunzisoft.keepass.activities.dialogs.UnderDevelopmentFeatureDialogFragment import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper -import com.kunzisoft.keepass.activities.stylish.Stylish -import com.kunzisoft.keepass.app.database.CipherDatabaseAction -import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction -import com.kunzisoft.keepass.biometric.BiometricUnlockDatabaseHelper -import com.kunzisoft.keepass.crypto.keyDerivation.KdfEngine -import com.kunzisoft.keepass.database.element.Database -import com.kunzisoft.keepass.database.element.PwCompressionAlgorithm -import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm -import com.kunzisoft.keepass.education.Education -import com.kunzisoft.keepass.icons.IconPackChooser -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_COLOR_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_COMPRESSION_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_DESCRIPTION_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ENCRYPTION_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_ITERATIONS_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_NAME_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_SAVE_PARALLELISM_TASK -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.NEW_ELEMENT_KEY -import com.kunzisoft.keepass.notifications.DatabaseTaskNotificationService.Companion.OLD_ELEMENT_KEY -import com.kunzisoft.keepass.settings.preference.* -import com.kunzisoft.keepass.settings.preference.DialogColorPreference.Companion.DISABLE_COLOR -import com.kunzisoft.keepass.settings.preferencedialogfragment.* import com.kunzisoft.keepass.tasks.ActionRunnable -import com.kunzisoft.keepass.utils.UriUtil -class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferenceClickListener { - - private var mDatabase: Database = Database.getInstance() - private var mDatabaseReadOnly: Boolean = false - - private var mCount = 0 - - private var dbNamePref: InputTextPreference? = null - private var dbDescriptionPref: InputTextPreference? = null - private var dbDefaultUsername: InputTextPreference? = null - private var dbCustomColorPref: DialogColorPreference? = null - private var dbDataCompressionPref: Preference? = null - private var dbMaxHistoryItemsPref: InputNumberPreference? = null - private var dbMaxHistorySizePref: InputNumberPreference? = null - private var mEncryptionAlgorithmPref: DialogListExplanationPreference? = null - private var mKeyDerivationPref: DialogListExplanationPreference? = null - private var mRoundPref: InputKdfNumberPreference? = null - private var mMemoryPref: InputKdfNumberPreference? = null - private var mParallelismPref: InputKdfNumberPreference? = null +abstract class NestedSettingsFragment : PreferenceFragmentCompat() { enum class Screen { APPLICATION, FORM_FILLING, ADVANCED_UNLOCK, APPEARANCE, DATABASE, DATABASE_SECURITY, DATABASE_MASTER_KEY } - override fun onResume() { - super.onResume() - - activity?.let { activity -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val autoFillEnablePreference: SwitchPreference? = findPreference(getString(R.string.settings_autofill_enable_key)) - if (autoFillEnablePreference != null) { - val autofillManager = activity.getSystemService(AutofillManager::class.java) - autoFillEnablePreference.isChecked = autofillManager != null - && autofillManager.hasEnabledAutofillServices() - } - } - } - } - override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { var key = 0 if (arguments != null) key = arguments!!.getInt(TAG_KEY) - mDatabaseReadOnly = mDatabase.isReadOnly - || ReadOnlyHelper.retrieveReadOnlyFromInstanceStateOrArguments(savedInstanceState, arguments) - - // Load the preferences from an XML resource - when (Screen.values()[key]) { - Screen.APPLICATION -> { - onCreateApplicationPreferences(rootKey) - } - Screen.FORM_FILLING -> { - onCreateFormFillingPreference(rootKey) - } - Screen.ADVANCED_UNLOCK -> { - onCreateAdvancedUnlockPreferences(rootKey) - } - Screen.APPEARANCE -> { - onCreateAppearancePreferences(rootKey) - } - Screen.DATABASE -> { - onCreateDatabasePreference(rootKey) - } - Screen.DATABASE_SECURITY -> { - onCreateDatabaseSecurityPreference(rootKey) - } - Screen.DATABASE_MASTER_KEY -> { - onCreateDatabaseMasterKeyPreference(rootKey) - } - } - } - - private fun onCreateApplicationPreferences(rootKey: String?) { - setPreferencesFromResource(R.xml.preferences_application, rootKey) - - activity?.let { activity -> - allowCopyPassword() - - findPreference(getString(R.string.keyfile_key))?.setOnPreferenceChangeListener { _, newValue -> - if (!(newValue as Boolean)) { - FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAllKeyFiles() - } - true - } - - findPreference(getString(R.string.recentfile_key))?.setOnPreferenceChangeListener { _, newValue -> - if (!(newValue as Boolean)) { - FileDatabaseHistoryAction.getInstance(activity.applicationContext).deleteAll() - } - true - } - } - } - - private fun onCreateFormFillingPreference(rootKey: String?) { - setPreferencesFromResource(R.xml.preferences_form_filling, rootKey) - - activity?.let { activity -> - val autoFillEnablePreference: SwitchPreference? = findPreference(getString(R.string.settings_autofill_enable_key)) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - val autofillManager = activity.getSystemService(AutofillManager::class.java) - if (autofillManager != null && autofillManager.hasEnabledAutofillServices()) - autoFillEnablePreference?.isChecked = autofillManager.hasEnabledAutofillServices() - autoFillEnablePreference?.onPreferenceClickListener = object : Preference.OnPreferenceClickListener { - @RequiresApi(api = Build.VERSION_CODES.O) - override fun onPreferenceClick(preference: Preference): Boolean { - if ((preference as SwitchPreference).isChecked) { - try { - startEnableService() - } catch (e: ActivityNotFoundException) { - val error = getString(R.string.error_autofill_enable_service) - preference.isChecked = false - Log.d(javaClass.name, error, e) - Toast.makeText(context, error, Toast.LENGTH_SHORT).show() - } - - } else { - disableService() - } - return false - } - - @RequiresApi(api = Build.VERSION_CODES.O) - private fun disableService() { - if (autofillManager != null && autofillManager.hasEnabledAutofillServices()) { - autofillManager.disableAutofillServices() - } else { - Log.d(javaClass.name, "Sample service already disabled.") - } - } - - @RequiresApi(api = Build.VERSION_CODES.O) - @Throws(ActivityNotFoundException::class) - private fun startEnableService() { - if (autofillManager != null && !autofillManager.hasEnabledAutofillServices()) { - val intent = Intent(Settings.ACTION_REQUEST_SET_AUTOFILL_SERVICE) - // TODO Autofill - intent.data = Uri.parse("package:com.example.android.autofill.service") - Log.d(javaClass.name, "enableService(): intent=$intent") - startActivityForResult(intent, REQUEST_CODE_AUTOFILL) - } else { - Log.d(javaClass.name, "Sample service already enabled.") - } - } - } - } else { - autoFillEnablePreference?.setOnPreferenceClickListener { preference -> - (preference as SwitchPreference).isChecked = false - val fragmentManager = fragmentManager!! - UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.O) - .show(fragmentManager, "unavailableFeatureDialog") - false - } - } - } - - findPreference(getString(R.string.magic_keyboard_explanation_key))?.setOnPreferenceClickListener { - UriUtil.gotoUrl(context!!, R.string.magic_keyboard_explanation_url) - false - } - - findPreference(getString(R.string.magic_keyboard_key))?.setOnPreferenceClickListener { - startActivity(Intent(Settings.ACTION_INPUT_METHOD_SETTINGS).apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - }) - false - } - - findPreference(getString(R.string.magic_keyboard_preference_key))?.setOnPreferenceClickListener { - startActivity(Intent(context, MagikIMESettings::class.java)) - false - } - - findPreference(getString(R.string.clipboard_explanation_key))?.setOnPreferenceClickListener { - UriUtil.gotoUrl(context!!, R.string.clipboard_explanation_url) - false - } - - findPreference(getString(R.string.autofill_explanation_key))?.setOnPreferenceClickListener { - UriUtil.gotoUrl(context!!, R.string.autofill_explanation_url) - false - } - - // Present in two places - allowCopyPassword() - } - - private fun onCreateAdvancedUnlockPreferences(rootKey: String?) { - setPreferencesFromResource(R.xml.preferences_advanced_unlock, rootKey) - - activity?.let { activity -> - val biometricUnlockEnablePreference: SwitchPreference? = findPreference(getString(R.string.biometric_unlock_enable_key)) - // < M solve verifyError exception - var biometricUnlockSupported = false - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - val biometricCanAuthenticate = BiometricManager.from(activity).canAuthenticate() - biometricUnlockSupported = biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED - || biometricCanAuthenticate == BiometricManager.BIOMETRIC_SUCCESS - } - if (!biometricUnlockSupported) { - // False if under Marshmallow - biometricUnlockEnablePreference?.apply { - isChecked = false - setOnPreferenceClickListener { preference -> - fragmentManager?.let { fragmentManager -> - (preference as SwitchPreference).isChecked = false - UnavailableFeatureDialogFragment.getInstance(Build.VERSION_CODES.M) - .show(fragmentManager, "unavailableFeatureDialog") - } - false - } - } - } - - val deleteKeysFingerprints: Preference? = findPreference(getString(R.string.biometric_delete_all_key_key)) - if (!biometricUnlockSupported) { - deleteKeysFingerprints?.isEnabled = false - } else { - deleteKeysFingerprints?.setOnPreferenceClickListener { - context?.let { context -> - AlertDialog.Builder(context) - .setMessage(resources.getString(R.string.biometric_delete_all_key_warning)) - .setIcon(android.R.drawable.ic_dialog_alert) - .setPositiveButton(resources.getString(android.R.string.yes) - ) { _, _ -> - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - BiometricUnlockDatabaseHelper.deleteEntryKeyInKeystoreForBiometric( - activity, - object : BiometricUnlockDatabaseHelper.BiometricUnlockErrorCallback { - override fun onInvalidKeyException(e: Exception) {} - - override fun onBiometricException(e: Exception) { - Toast.makeText(context, - getString(R.string.biometric_scanning_error, e.localizedMessage), - Toast.LENGTH_SHORT).show() - } - }) - } - CipherDatabaseAction.getInstance(context.applicationContext).deleteAll() - } - .setNegativeButton(resources.getString(android.R.string.no)) - { _, _ -> }.show() - } - false - } - } - } - - findPreference(getString(R.string.advanced_unlock_explanation_key))?.setOnPreferenceClickListener { - UriUtil.gotoUrl(context!!, R.string.advanced_unlock_explanation_url) - false - } - } - - private fun onCreateAppearancePreferences(rootKey: String?) { - setPreferencesFromResource(R.xml.preferences_appearance, rootKey) - - activity?.let { activity -> - findPreference(getString(R.string.setting_style_key))?.setOnPreferenceChangeListener { _, newValue -> - var styleEnabled = true - val styleIdString = newValue as String - if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!)) - for (themeIdDisabled in BuildConfig.STYLES_DISABLED) { - if (themeIdDisabled == styleIdString) { - styleEnabled = false - fragmentManager?.let { fragmentManager -> - ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog") - } - } - } - if (styleEnabled) { - Stylish.assignStyle(styleIdString) - activity.recreate() - } - styleEnabled - } - - findPreference(getString(R.string.setting_icon_pack_choose_key))?.setOnPreferenceChangeListener { _, newValue -> - var iconPackEnabled = true - val iconPackId = newValue as String - if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context!!)) - for (iconPackIdDisabled in BuildConfig.ICON_PACKS_DISABLED) { - if (iconPackIdDisabled == iconPackId) { - iconPackEnabled = false - fragmentManager?.let { fragmentManager -> - ProFeatureDialogFragment().show(fragmentManager, "pro_feature_dialog") - } - } - } - if (iconPackEnabled) { - IconPackChooser.setSelectedIconPack(iconPackId) - } - iconPackEnabled - } - - findPreference(getString(R.string.reset_education_screens_key))?.setOnPreferenceClickListener { - // To allow only one toast - if (mCount == 0) { - val sharedPreferences = Education.getEducationSharedPreferences(context!!) - val editor = sharedPreferences.edit() - for (resourceId in Education.educationResourcesKeys) { - editor.putBoolean(getString(resourceId), false) - } - editor.apply() - Toast.makeText(context, R.string.reset_education_screens_text, Toast.LENGTH_SHORT).show() - } - mCount++ - false - } - } - } - - private fun onCreateDatabasePreference(rootKey: String?) { - setPreferencesFromResource(R.xml.preferences_database, rootKey) - - if (mDatabase.loaded) { - - val dbGeneralPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_general_key)) - - // Database name - dbNamePref = findPreference(getString(R.string.database_name_key)) - if (mDatabase.allowName) { - dbNamePref?.summary = mDatabase.name - } else { - dbGeneralPrefCategory?.removePreference(dbNamePref) - } - - // Database description - dbDescriptionPref = findPreference(getString(R.string.database_description_key)) - if (mDatabase.allowDescription) { - dbDescriptionPref?.summary = mDatabase.description - } else { - dbGeneralPrefCategory?.removePreference(dbDescriptionPref) - } - - // Database default username - dbDefaultUsername = findPreference(getString(R.string.database_default_username_key)) - if (mDatabase.allowDefaultUsername) { - dbDefaultUsername?.summary = mDatabase.defaultUsername - } else { - dbDefaultUsername?.isEnabled = false - // TODO dbGeneralPrefCategory?.removePreference(dbDefaultUsername) - } - - // Database custom color - dbCustomColorPref = findPreference(getString(R.string.database_custom_color_key)) - if (mDatabase.allowCustomColor) { - dbCustomColorPref?.apply { - try { - color = Color.parseColor(mDatabase.customColor) - summary = mDatabase.customColor - } catch (e: Exception) { - color = DISABLE_COLOR - summary = "" - } - } - } else { - dbCustomColorPref?.isEnabled = false - // TODO dbGeneralPrefCategory?.removePreference(dbCustomColorPref) - } - - // Version - findPreference(getString(R.string.database_version_key)) - ?.summary = mDatabase.version - - val dbCompressionPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_compression_key)) - - // Database compression - dbDataCompressionPref = findPreference(getString(R.string.database_data_compression_key)) - if (mDatabase.allowDataCompression) { - dbDataCompressionPref?.summary = (mDatabase.compressionAlgorithm - ?: PwCompressionAlgorithm.None).getName(resources) - } else { - dbCompressionPrefCategory?.isVisible = false - } - - val dbRecycleBinPrefCategory: PreferenceCategory? = findPreference(getString(R.string.database_category_recycle_bin_key)) - - // Recycle bin - val recycleBinPref: SwitchPreference? = findPreference(getString(R.string.recycle_bin_key)) - if (mDatabase.allowRecycleBin) { - recycleBinPref?.isChecked = mDatabase.isRecycleBinEnabled - // TODO Recycle Bin - recycleBinPref?.isEnabled = false - } else { - dbRecycleBinPrefCategory?.isVisible = false - } - - findPreference(getString(R.string.database_category_history_key)) - ?.isVisible = mDatabase.manageHistory == true - - // Max history items - dbMaxHistoryItemsPref = findPreference(getString(R.string.max_history_items_key))?.apply { - summary = mDatabase.historyMaxItems.toString() - } - - // Max history size - dbMaxHistorySizePref = findPreference(getString(R.string.max_history_size_key))?.apply { - summary = mDatabase.historyMaxSize.toString() - } - - } else { - Log.e(javaClass.name, "Database isn't ready") - } + onCreateScreenPreference(Screen.values()[key], savedInstanceState, rootKey) } - private fun onCreateDatabaseSecurityPreference(rootKey: String?) { - setPreferencesFromResource(R.xml.preferences_database_security, rootKey) - - if (mDatabase.loaded) { - // Encryption Algorithm - mEncryptionAlgorithmPref = findPreference(getString(R.string.encryption_algorithm_key))?.apply { - summary = mDatabase.getEncryptionAlgorithmName(resources) - } + abstract fun onCreateScreenPreference(screen: Screen, savedInstanceState: Bundle?, rootKey: String?) - // Key derivation function - mKeyDerivationPref = findPreference(getString(R.string.key_derivation_function_key))?.apply { - summary = mDatabase.getKeyDerivationName(resources) - } - - // Round encryption - mRoundPref = findPreference(getString(R.string.transform_rounds_key))?.apply { - summary = mDatabase.numberKeyEncryptionRounds.toString() - } - - // Memory Usage - mMemoryPref = findPreference(getString(R.string.memory_usage_key))?.apply { - summary = mDatabase.memoryUsage.toString() - } - - // Parallelism - mParallelismPref = findPreference(getString(R.string.parallelism_key))?.apply { - summary = mDatabase.parallelism.toString() - } - } else { - Log.e(javaClass.name, "Database isn't ready") - } - } - - private fun onCreateDatabaseMasterKeyPreference(rootKey: String?) { - setPreferencesFromResource(R.xml.preferences_database_master_key, rootKey) - - if (mDatabase.loaded) { - findPreference(getString(R.string.settings_database_change_credentials_key))?.apply { - onPreferenceClickListener = Preference.OnPreferenceClickListener { - fragmentManager?.let { fragmentManager -> - AssignMasterKeyDialogFragment.getInstance(mDatabase.allowNoMasterKey) - .show(fragmentManager, "passwordDialog") - } - false - } - } - } else { - Log.e(javaClass.name, "Database isn't ready") - } - } + open fun onProgressDialogThreadResult(actionTask: String, + result: ActionRunnable.Result) {} - private fun allowCopyPassword() { - val copyPasswordPreference: SwitchPreference? = findPreference(getString(R.string.allow_copy_password_key)) - copyPasswordPreference?.setOnPreferenceChangeListener { _, newValue -> - if (newValue as Boolean && context != null) { - val message = getString(R.string.allow_copy_password_warning) + - "\n\n" + - getString(R.string.clipboard_warning) - AlertDialog - .Builder(context!!) - .setMessage(message) - .create() - .apply { - setButton(AlertDialog.BUTTON_POSITIVE, getText(R.string.enable)) - { dialog, _ -> - dialog.dismiss() - } - setButton(AlertDialog.BUTTON_NEGATIVE, getText(R.string.disable)) - { dialog, _ -> - copyPasswordPreference.isChecked = false - dialog.dismiss() - } - show() - } - } - true - } - } - - private fun preferenceInDevelopment(preferenceInDev: Preference) { + protected fun preferenceInDevelopment(preferenceInDev: Preference) { preferenceInDev.setOnPreferenceClickListener { preference -> fragmentManager?.let { fragmentManager -> try { // don't check if we can @@ -573,300 +62,22 @@ class NestedSettingsFragment : PreferenceFragmentCompat(), Preference.OnPreferen } } - override fun onStop() { - super.onStop() - activity?.let { activity -> - if (mCount == 10) { - Education.getEducationSharedPreferences(activity).edit() - .putBoolean(getString(R.string.education_screen_reclicked_key), true).apply() - } - } - } - - private val colorSelectedListener: ((Boolean, Int)-> Unit)? = { enable, color -> - dbCustomColorPref?.summary = ChromaUtil.getFormattedColorString(color, false) - if (enable) { - dbCustomColorPref?.color = color - } else { - dbCustomColorPref?.color = DISABLE_COLOR - } - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = super.onCreateView(inflater, container, savedInstanceState) - - try { - // To reassign color listener after orientation change - val chromaDialog = fragmentManager?.findFragmentByTag(TAG_PREF_FRAGMENT) as DatabaseColorPreferenceDialogFragmentCompat? - chromaDialog?.onColorSelectedListener = colorSelectedListener - } catch (e: Exception) {} - - return view - } - - fun onProgressDialogThreadResult(actionTask: String, - result: ActionRunnable.Result) { - result.data?.let { data -> - if (data.containsKey(OLD_ELEMENT_KEY) - && data.containsKey(NEW_ELEMENT_KEY)) { - when (actionTask) { - /* - -------- - Main preferences - -------- - */ - ACTION_DATABASE_SAVE_NAME_TASK -> { - val oldName = data.getString(OLD_ELEMENT_KEY)!! - val newName = data.getString(NEW_ELEMENT_KEY)!! - val nameToShow = - if (result.isSuccess) { - newName - } else { - mDatabase.name = oldName - oldName - } - dbNamePref?.summary = nameToShow - } - ACTION_DATABASE_SAVE_DESCRIPTION_TASK -> { - val oldDescription = data.getString(OLD_ELEMENT_KEY)!! - val newDescription = data.getString(NEW_ELEMENT_KEY)!! - val descriptionToShow = - if (result.isSuccess) { - newDescription - } else { - mDatabase.description = oldDescription - oldDescription - } - dbDescriptionPref?.summary = descriptionToShow - } - ACTION_DATABASE_SAVE_DEFAULT_USERNAME_TASK -> { - val oldDefaultUsername = data.getString(OLD_ELEMENT_KEY)!! - val newDefaultUsername = data.getString(NEW_ELEMENT_KEY)!! - val defaultUsernameToShow = - if (result.isSuccess) { - newDefaultUsername - } else { - mDatabase.defaultUsername = oldDefaultUsername - oldDefaultUsername - } - dbDefaultUsername?.summary = defaultUsernameToShow - } - ACTION_DATABASE_SAVE_COLOR_TASK -> { - val oldColor = data.getString(OLD_ELEMENT_KEY)!! - val newColor = data.getString(NEW_ELEMENT_KEY)!! - - val defaultColorToShow = - if (result.isSuccess) { - newColor - } else { - mDatabase.customColor = oldColor - oldColor - } - dbCustomColorPref?.summary = defaultColorToShow - } - ACTION_DATABASE_SAVE_COMPRESSION_TASK -> { - val oldCompression = data.getSerializable(OLD_ELEMENT_KEY) as PwCompressionAlgorithm - val newCompression = data.getSerializable(NEW_ELEMENT_KEY) as PwCompressionAlgorithm - val algorithmToShow = - if (result.isSuccess) { - newCompression - } else { - mDatabase.compressionAlgorithm = oldCompression - oldCompression - } - dbDataCompressionPref?.summary = algorithmToShow.getName(resources) - } - ACTION_DATABASE_SAVE_MAX_HISTORY_ITEMS_TASK -> { - val oldMaxHistoryItems = data.getInt(OLD_ELEMENT_KEY) - val newMaxHistoryItems = data.getInt(NEW_ELEMENT_KEY) - val maxHistoryItemsToShow = - if (result.isSuccess) { - newMaxHistoryItems - } else { - mDatabase.historyMaxItems = oldMaxHistoryItems - oldMaxHistoryItems - } - dbMaxHistoryItemsPref?.summary = maxHistoryItemsToShow.toString() - } - ACTION_DATABASE_SAVE_MAX_HISTORY_SIZE_TASK -> { - val oldMaxHistorySize = data.getLong(OLD_ELEMENT_KEY) - val newMaxHistorySize = data.getLong(NEW_ELEMENT_KEY) - val maxHistorySizeToShow = - if (result.isSuccess) { - newMaxHistorySize - } else { - mDatabase.historyMaxSize = oldMaxHistorySize - oldMaxHistorySize - } - dbMaxHistorySizePref?.summary = maxHistorySizeToShow.toString() - } - - /* - -------- - Security - -------- - */ - ACTION_DATABASE_SAVE_ENCRYPTION_TASK -> { - val oldEncryption = data.getSerializable(OLD_ELEMENT_KEY) as PwEncryptionAlgorithm - val newEncryption = data.getSerializable(NEW_ELEMENT_KEY) as PwEncryptionAlgorithm - val algorithmToShow = - if (result.isSuccess) { - newEncryption - } else { - mDatabase.encryptionAlgorithm = oldEncryption - oldEncryption - } - mEncryptionAlgorithmPref?.summary = algorithmToShow.getName(resources) - } - ACTION_DATABASE_SAVE_KEY_DERIVATION_TASK -> { - val oldKeyDerivationEngine = data.getSerializable(OLD_ELEMENT_KEY) as KdfEngine - val newKeyDerivationEngine = data.getSerializable(NEW_ELEMENT_KEY) as KdfEngine - val kdfEngineToShow = - if (result.isSuccess) { - newKeyDerivationEngine - } else { - mDatabase.kdfEngine = oldKeyDerivationEngine - oldKeyDerivationEngine - } - mKeyDerivationPref?.summary = kdfEngineToShow.getName(resources) - - mRoundPref?.summary = kdfEngineToShow.defaultKeyRounds.toString() - // Disable memory and parallelism if not available - mMemoryPref?.summary = kdfEngineToShow.defaultMemoryUsage.toString() - mParallelismPref?.summary = kdfEngineToShow.defaultParallelism.toString() - } - ACTION_DATABASE_SAVE_ITERATIONS_TASK -> { - val oldIterations = data.getLong(OLD_ELEMENT_KEY) - val newIterations = data.getLong(NEW_ELEMENT_KEY) - val roundsToShow = - if (result.isSuccess) { - newIterations - } else { - mDatabase.numberKeyEncryptionRounds = oldIterations - oldIterations - } - mRoundPref?.summary = roundsToShow.toString() - } - ACTION_DATABASE_SAVE_MEMORY_USAGE_TASK -> { - val oldMemoryUsage = data.getLong(OLD_ELEMENT_KEY) - val newMemoryUsage = data.getLong(NEW_ELEMENT_KEY) - val memoryToShow = - if (result.isSuccess) { - newMemoryUsage - } else { - mDatabase.memoryUsage = oldMemoryUsage - oldMemoryUsage - } - mMemoryPref?.summary = memoryToShow.toString() - } - ACTION_DATABASE_SAVE_PARALLELISM_TASK -> { - val oldParallelism = data.getInt(OLD_ELEMENT_KEY) - val newParallelism = data.getInt(NEW_ELEMENT_KEY) - val parallelismToShow = - if (result.isSuccess) { - newParallelism - } else { - mDatabase.parallelism = oldParallelism - oldParallelism - } - mParallelismPref?.summary = parallelismToShow.toString() - } - } - } - } - } - - override fun onDisplayPreferenceDialog(preference: Preference?) { - - var otherDialogFragment = false - - fragmentManager?.let { fragmentManager -> - preference?.let { preference -> - var dialogFragment: DialogFragment? = null - when { - // Main Preferences - preference.key == getString(R.string.database_name_key) -> { - dialogFragment = DatabaseNamePreferenceDialogFragmentCompat.newInstance(preference.key) - } - preference.key == getString(R.string.database_description_key) -> { - dialogFragment = DatabaseDescriptionPreferenceDialogFragmentCompat.newInstance(preference.key) - } - preference.key == getString(R.string.database_default_username_key) -> { - dialogFragment = DatabaseDefaultUsernamePreferenceDialogFragmentCompat.newInstance(preference.key) - } - preference.key == getString(R.string.database_custom_color_key) -> { - dialogFragment = DatabaseColorPreferenceDialogFragmentCompat.newInstance(preference.key).apply { - onColorSelectedListener = colorSelectedListener - } - } - preference.key == getString(R.string.database_data_compression_key) -> { - dialogFragment = DatabaseDataCompressionPreferenceDialogFragmentCompat.newInstance(preference.key) - } - preference.key == getString(R.string.max_history_items_key) -> { - dialogFragment = MaxHistoryItemsPreferenceDialogFragmentCompat.newInstance(preference.key) - } - preference.key == getString(R.string.max_history_size_key) -> { - dialogFragment = MaxHistorySizePreferenceDialogFragmentCompat.newInstance(preference.key) - } - - // Security - preference.key == getString(R.string.encryption_algorithm_key) -> { - dialogFragment = DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.newInstance(preference.key) - } - preference.key == getString(R.string.key_derivation_function_key) -> { - val keyDerivationDialogFragment = DatabaseKeyDerivationPreferenceDialogFragmentCompat.newInstance(preference.key) - // Add other prefs to manage - keyDerivationDialogFragment.setRoundPreference(mRoundPref) - keyDerivationDialogFragment.setMemoryPreference(mMemoryPref) - keyDerivationDialogFragment.setParallelismPreference(mParallelismPref) - dialogFragment = keyDerivationDialogFragment - } - preference.key == getString(R.string.transform_rounds_key) -> { - dialogFragment = RoundsPreferenceDialogFragmentCompat.newInstance(preference.key) - } - preference.key == getString(R.string.memory_usage_key) -> { - dialogFragment = MemoryUsagePreferenceDialogFragmentCompat.newInstance(preference.key) - } - preference.key == getString(R.string.parallelism_key) -> { - dialogFragment = ParallelismPreferenceDialogFragmentCompat.newInstance(preference.key) - } - else -> otherDialogFragment = true - } - - if (dialogFragment != null && !mDatabaseReadOnly) { - dialogFragment.setTargetFragment(this, 0) - dialogFragment.show(fragmentManager, TAG_PREF_FRAGMENT) - } - // Could not be handled here. Try with the super method. - else if (otherDialogFragment) { - super.onDisplayPreferenceDialog(preference) - } - } - } - } - - override fun onSaveInstanceState(outState: Bundle) { - ReadOnlyHelper.onSaveInstanceState(outState, mDatabaseReadOnly) - super.onSaveInstanceState(outState) - } - - override fun onPreferenceClick(preference: Preference?): Boolean { - // TODO encapsulate - return false - } - companion object { private const val TAG_KEY = "NESTED_KEY" - private const val TAG_PREF_FRAGMENT = "TAG_PREF_FRAGMENT" - - private const val REQUEST_CODE_AUTOFILL = 5201 - @JvmOverloads fun newInstance(key: Screen, databaseReadOnly: Boolean = ReadOnlyHelper.READ_ONLY_DEFAULT) : NestedSettingsFragment { - val fragment = NestedSettingsFragment() + val fragment: NestedSettingsFragment = when (key) { + Screen.APPLICATION, + Screen.FORM_FILLING, + Screen.ADVANCED_UNLOCK, + Screen.APPEARANCE -> NestedAppSettingsFragment() + Screen.DATABASE, + Screen.DATABASE_SECURITY, + Screen.DATABASE_MASTER_KEY -> NestedDatabaseSettingsFragment() + } // supply arguments to bundle. val args = Bundle() args.putInt(TAG_KEY, key.ordinal) diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt index 4a74f62eb..c5097fc1e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/PreferencesUtil.kt @@ -22,7 +22,7 @@ package com.kunzisoft.keepass.settings import android.content.Context import android.preference.PreferenceManager import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.SortNodeEnum +import com.kunzisoft.keepass.database.element.SortNodeEnum import com.kunzisoft.keepass.timeout.TimeoutHelper import java.util.* @@ -134,6 +134,12 @@ object PreferencesUtil { context.resources.getBoolean(R.bool.lock_database_back_root_default)) } + fun isAutoSaveDatabaseEnabled(context: Context): Boolean { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + return prefs.getBoolean(context.getString(R.string.enable_auto_save_database_key), + context.resources.getBoolean(R.bool.enable_auto_save_database_default)) + } + fun isPersistentNotificationEnable(context: Context): Boolean { val prefs = PreferenceManager.getDefaultSharedPreferences(context) return prefs.getBoolean(context.getString(R.string.persistent_notification_key), diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt index d8300bb26..e03b2ce05 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/SettingsActivity.kt @@ -33,7 +33,6 @@ import com.kunzisoft.keepass.activities.dialogs.AssignMasterKeyDialogFragment import com.kunzisoft.keepass.activities.dialogs.PasswordEncodingDialogFragment import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper import com.kunzisoft.keepass.activities.lock.LockingActivity -import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.element.Database import com.kunzisoft.keepass.timeout.TimeoutHelper @@ -46,8 +45,6 @@ open class SettingsActivity private var toolbar: Toolbar? = null - var progressDialogThread: ProgressDialogThread? = null - companion object { private const val TAG_NESTED = "TAG_NESTED" @@ -90,7 +87,7 @@ open class SettingsActivity backupManager = BackupManager(this) - progressDialogThread = ProgressDialogThread(this) { actionTask, result -> + mProgressDialogThread?.onActionFinish = { actionTask, result -> // Call result in fragment (supportFragmentManager .findFragmentByTag(TAG_NESTED) as NestedSettingsFragment?) @@ -98,19 +95,6 @@ open class SettingsActivity } } - override fun onResume() { - super.onResume() - - progressDialogThread?.registerProgressTask() - } - - override fun onPause() { - - progressDialogThread?.unregisterProgressTask() - - super.onPause() - } - override fun onOptionsItemSelected(item: MenuItem): Boolean { when (item.itemId) { android.R.id.home -> onBackPressed() @@ -132,7 +116,7 @@ open class SettingsActivity database.fileUri?.let { databaseUri -> // Show the progress dialog now or after dialog confirmation if (database.validatePasswordEncoding(masterPassword, keyFileChecked)) { - progressDialogThread?.startDatabaseAssignPassword( + mProgressDialogThread?.startDatabaseAssignPassword( databaseUri, masterPasswordChecked, masterPassword, @@ -142,7 +126,7 @@ open class SettingsActivity } else { PasswordEncodingDialogFragment().apply { positiveButtonClickListener = DialogInterface.OnClickListener { _, _ -> - progressDialogThread?.startDatabaseAssignPassword( + mProgressDialogThread?.startDatabaseAssignPassword( databaseUri, masterPasswordChecked, masterPassword, diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseColorPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseColorPreferenceDialogFragmentCompat.kt index 2cd54591c..ce4837f1e 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseColorPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseColorPreferenceDialogFragmentCompat.kt @@ -87,7 +87,7 @@ class DatabaseColorPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialog } val oldColor = database.customColor database.customColor = newColor - progressDialogThread?.startDatabaseSaveColor(oldColor, newColor) + mProgressDialogThread?.startDatabaseSaveColor(oldColor, newColor, mDatabaseAutoSaveEnable) } onDialogClosed(true) diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDataCompressionPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDataCompressionPreferenceDialogFragmentCompat.kt index f20873115..3e75acb7c 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDataCompressionPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDataCompressionPreferenceDialogFragmentCompat.kt @@ -24,14 +24,14 @@ import android.view.View import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.element.PwCompressionAlgorithm +import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter class DatabaseDataCompressionPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat(), - ListRadioItemAdapter.RadioItemSelectedCallback { + ListRadioItemAdapter.RadioItemSelectedCallback { - private var compressionSelected: PwCompressionAlgorithm? = null + private var compressionSelected: CompressionAlgorithm? = null override fun onBindDialogView(view: View) { super.onBindDialogView(view) @@ -42,7 +42,7 @@ class DatabaseDataCompressionPreferenceDialogFragmentCompat recyclerView.layoutManager = LinearLayoutManager(context) activity?.let { activity -> - val compressionAdapter = ListRadioItemAdapter(activity) + val compressionAdapter = ListRadioItemAdapter(activity) compressionAdapter.setRadioItemSelectedCallback(this) recyclerView.adapter = compressionAdapter @@ -64,13 +64,13 @@ class DatabaseDataCompressionPreferenceDialogFragmentCompat database.compressionAlgorithm = newCompression if (oldCompression != null && newCompression != null) - progressDialogThread?.startDatabaseSaveCompression(oldCompression, newCompression) + mProgressDialogThread?.startDatabaseSaveCompression(oldCompression, newCompression, mDatabaseAutoSaveEnable) } } } } - override fun onItemSelected(item: PwCompressionAlgorithm) { + override fun onItemSelected(item: CompressionAlgorithm) { this.compressionSelected = item } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDefaultUsernamePreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDefaultUsernamePreferenceDialogFragmentCompat.kt index 2430616b4..9b3fcfeb2 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDefaultUsernamePreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDefaultUsernamePreferenceDialogFragmentCompat.kt @@ -36,8 +36,7 @@ class DatabaseDefaultUsernamePreferenceDialogFragmentCompat : DatabaseSavePrefer val newDefaultUsername = inputText val oldDefaultUsername = database.defaultUsername database.defaultUsername = newDefaultUsername - - progressDialogThread?.startDatabaseSaveDefaultUsername(oldDefaultUsername, newDefaultUsername) + mProgressDialogThread?.startDatabaseSaveDefaultUsername(oldDefaultUsername, newDefaultUsername, mDatabaseAutoSaveEnable) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDescriptionPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDescriptionPreferenceDialogFragmentCompat.kt index 199f42373..261b3a2c6 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDescriptionPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseDescriptionPreferenceDialogFragmentCompat.kt @@ -36,8 +36,7 @@ class DatabaseDescriptionPreferenceDialogFragmentCompat : DatabaseSavePreference val newDescription = inputText val oldDescription = database.description database.description = newDescription - - progressDialogThread?.startDatabaseSaveDescription(oldDescription, newDescription) + mProgressDialogThread?.startDatabaseSaveDescription(oldDescription, newDescription, mDatabaseAutoSaveEnable) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.kt index 61c1f3edb..083e52ee7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat.kt @@ -24,14 +24,14 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import android.view.View import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.element.PwEncryptionAlgorithm +import com.kunzisoft.keepass.database.element.security.EncryptionAlgorithm import com.kunzisoft.keepass.settings.preferencedialogfragment.adapter.ListRadioItemAdapter class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmentCompat(), - ListRadioItemAdapter.RadioItemSelectedCallback { + ListRadioItemAdapter.RadioItemSelectedCallback { - private var algorithmSelected: PwEncryptionAlgorithm? = null + private var algorithmSelected: EncryptionAlgorithm? = null override fun onBindDialogView(view: View) { super.onBindDialogView(view) @@ -42,7 +42,7 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat recyclerView.layoutManager = LinearLayoutManager(context) activity?.let { activity -> - val encryptionAlgorithmAdapter = ListRadioItemAdapter(activity) + val encryptionAlgorithmAdapter = ListRadioItemAdapter(activity) encryptionAlgorithmAdapter.setRadioItemSelectedCallback(this) recyclerView.adapter = encryptionAlgorithmAdapter @@ -65,14 +65,14 @@ class DatabaseEncryptionAlgorithmPreferenceDialogFragmentCompat database.encryptionAlgorithm = newAlgorithm if (oldAlgorithm != null && newAlgorithm != null) - progressDialogThread?.startDatabaseSaveEncryption(oldAlgorithm, newAlgorithm) + mProgressDialogThread?.startDatabaseSaveEncryption(oldAlgorithm, newAlgorithm, mDatabaseAutoSaveEnable) } } } } } - override fun onItemSelected(item: PwEncryptionAlgorithm) { + override fun onItemSelected(item: EncryptionAlgorithm) { this.algorithmSelected = item } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseKeyDerivationPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseKeyDerivationPreferenceDialogFragmentCompat.kt index dc82d55cb..0066b687b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseKeyDerivationPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseKeyDerivationPreferenceDialogFragmentCompat.kt @@ -66,7 +66,7 @@ class DatabaseKeyDerivationPreferenceDialogFragmentCompat val oldKdfEngine = database.kdfEngine if (newKdfEngine != null && oldKdfEngine != null) { database.kdfEngine = newKdfEngine - progressDialogThread?.startDatabaseSaveKeyDerivation(oldKdfEngine, newKdfEngine) + mProgressDialogThread?.startDatabaseSaveKeyDerivation(oldKdfEngine, newKdfEngine, mDatabaseAutoSaveEnable) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseNamePreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseNamePreferenceDialogFragmentCompat.kt index 6eb74e17a..8ff01749b 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseNamePreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseNamePreferenceDialogFragmentCompat.kt @@ -36,8 +36,7 @@ class DatabaseNamePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogF val newName = inputText val oldName = database.name database.name = newName - - progressDialogThread?.startDatabaseSaveName(oldName, newName) + mProgressDialogThread?.startDatabaseSaveName(oldName, newName, mDatabaseAutoSaveEnable) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseSavePreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseSavePreferenceDialogFragmentCompat.kt index 2d4182679..a0287c115 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseSavePreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/DatabaseSavePreferenceDialogFragmentCompat.kt @@ -23,13 +23,14 @@ import android.content.Context import android.os.Bundle import com.kunzisoft.keepass.database.action.ProgressDialogThread import com.kunzisoft.keepass.database.element.Database +import com.kunzisoft.keepass.settings.PreferencesUtil import com.kunzisoft.keepass.settings.SettingsActivity abstract class DatabaseSavePreferenceDialogFragmentCompat : InputPreferenceDialogFragmentCompat() { protected var database: Database? = null - - protected var progressDialogThread: ProgressDialogThread? = null + protected var mDatabaseAutoSaveEnable = true + protected var mProgressDialogThread: ProgressDialogThread? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -41,8 +42,10 @@ abstract class DatabaseSavePreferenceDialogFragmentCompat : InputPreferenceDialo super.onAttach(context) // Attach dialog thread to start action if (context is SettingsActivity) { - progressDialogThread = context.progressDialogThread + mProgressDialogThread = context.mProgressDialogThread } + + this.mDatabaseAutoSaveEnable = PreferencesUtil.isAutoSaveDatabaseEnabled(context) } companion object { diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistoryItemsPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistoryItemsPreferenceDialogFragmentCompat.kt index f9f45731e..70183b1c0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistoryItemsPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistoryItemsPreferenceDialogFragmentCompat.kt @@ -33,11 +33,12 @@ class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDial inputText = maxItemsDatabase.toString() setSwitchAction({ isChecked -> inputText = if (!isChecked) { - INFINITE_MAX_HISTORY_ITEMS.toString() - } else + NONE_MAX_HISTORY_ITEMS.toString() + } else { DEFAULT_MAX_HISTORY_ITEMS.toString() + } showInputText(isChecked) - }, maxItemsDatabase > INFINITE_MAX_HISTORY_ITEMS) + }, maxItemsDatabase > NONE_MAX_HISTORY_ITEMS) } } @@ -49,14 +50,17 @@ class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDial } catch (e: NumberFormatException) { DEFAULT_MAX_HISTORY_ITEMS } - if (maxHistoryItems < INFINITE_MAX_HISTORY_ITEMS) { - maxHistoryItems = INFINITE_MAX_HISTORY_ITEMS + if (maxHistoryItems < NONE_MAX_HISTORY_ITEMS) { + maxHistoryItems = NONE_MAX_HISTORY_ITEMS } val oldMaxHistoryItems = database.historyMaxItems database.historyMaxItems = maxHistoryItems - progressDialogThread?.startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems, maxHistoryItems) + // Remove all history items + database.removeOldestHistoryForEachEntry() + + mProgressDialogThread?.startDatabaseSaveMaxHistoryItems(oldMaxHistoryItems, maxHistoryItems, mDatabaseAutoSaveEnable) } } } @@ -64,7 +68,7 @@ class MaxHistoryItemsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDial companion object { const val DEFAULT_MAX_HISTORY_ITEMS = 10 - const val INFINITE_MAX_HISTORY_ITEMS = -1 + const val NONE_MAX_HISTORY_ITEMS = -1 fun newInstance(key: String): MaxHistoryItemsPreferenceDialogFragmentCompat { val fragment = MaxHistoryItemsPreferenceDialogFragmentCompat() diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistorySizePreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistorySizePreferenceDialogFragmentCompat.kt index ca709a2b5..2967e6731 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistorySizePreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MaxHistorySizePreferenceDialogFragmentCompat.kt @@ -56,7 +56,7 @@ class MaxHistorySizePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialo val oldMaxHistorySize = database.historyMaxSize database.historyMaxSize = maxHistorySize - progressDialogThread?.startDatabaseSaveMaxHistorySize(oldMaxHistorySize, maxHistorySize) + mProgressDialogThread?.startDatabaseSaveMaxHistorySize(oldMaxHistorySize, maxHistorySize, mDatabaseAutoSaveEnable) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MemoryUsagePreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MemoryUsagePreferenceDialogFragmentCompat.kt index c2f6e41a0..06b70e0cb 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MemoryUsagePreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/MemoryUsagePreferenceDialogFragmentCompat.kt @@ -48,7 +48,7 @@ class MemoryUsagePreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr val oldMemoryUsage = database.memoryUsage database.memoryUsage = memoryUsage - progressDialogThread?.startDatabaseSaveMemoryUsage(oldMemoryUsage, memoryUsage) + mProgressDialogThread?.startDatabaseSaveMemoryUsage(oldMemoryUsage, memoryUsage, mDatabaseAutoSaveEnable) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/ParallelismPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/ParallelismPreferenceDialogFragmentCompat.kt index 2d675a54b..e391a77ab 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/ParallelismPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/ParallelismPreferenceDialogFragmentCompat.kt @@ -48,7 +48,7 @@ class ParallelismPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFr val oldParallelism = database.parallelism database.parallelism = parallelism - progressDialogThread?.startDatabaseSaveParallelism(oldParallelism, parallelism) + mProgressDialogThread?.startDatabaseSaveParallelism(oldParallelism, parallelism, mDatabaseAutoSaveEnable) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/RoundsPreferenceDialogFragmentCompat.kt b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/RoundsPreferenceDialogFragmentCompat.kt index 7faa0d370..c7cf2c2fa 100644 --- a/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/RoundsPreferenceDialogFragmentCompat.kt +++ b/app/src/main/java/com/kunzisoft/keepass/settings/preferencedialogfragment/RoundsPreferenceDialogFragmentCompat.kt @@ -54,7 +54,7 @@ class RoundsPreferenceDialogFragmentCompat : DatabaseSavePreferenceDialogFragmen database.numberKeyEncryptionRounds = Long.MAX_VALUE } - progressDialogThread?.startDatabaseSaveIterations(oldRounds, rounds) + mProgressDialogThread?.startDatabaseSaveIterations(oldRounds, rounds, mDatabaseAutoSaveEnable) } } } diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/ActionReadBytes.java b/app/src/main/java/com/kunzisoft/keepass/stream/ActionReadBytes.java deleted file mode 100644 index d72ecb2a4..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/stream/ActionReadBytes.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.kunzisoft.keepass.stream; - -import java.io.IOException; - -public interface ActionReadBytes { - /** - * Called after each buffer fill - * @param buffer filled - */ - void doAction(byte[] buffer) throws IOException; -} diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/HashedBlockInputStream.java b/app/src/main/java/com/kunzisoft/keepass/stream/HashedBlockInputStream.java deleted file mode 100644 index 9adafcbfc..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/stream/HashedBlockInputStream.java +++ /dev/null @@ -1,165 +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 . - * - */ -package com.kunzisoft.keepass.stream; - -import com.kunzisoft.keepass.utils.Types; - -import java.io.IOException; -import java.io.InputStream; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - - -public class HashedBlockInputStream extends InputStream { - - private final static int HASH_SIZE = 32; - - private LEDataInputStream baseStream; - private int bufferPos = 0; - private byte[] buffer = new byte[0]; - private long bufferIndex = 0; - private boolean atEnd = false; - - - @Override - public int read(byte[] b) throws IOException { - return read(b, 0, b.length); - } - - public HashedBlockInputStream(InputStream is) { - baseStream = new LEDataInputStream(is); - } - - @Override - public int read(byte[] b, int offset, int length) throws IOException { - if ( atEnd ) return -1; - - int remaining = length; - - while ( remaining > 0 ) { - if ( bufferPos == buffer.length ) { - // Get more from the source into the buffer - if ( ! ReadHashedBlock() ) { - return length - remaining; - } - - } - - // Copy from buffer out - int copyLen = Math.min(buffer.length - bufferPos, remaining); - - System.arraycopy(buffer, bufferPos, b, offset, copyLen); - - offset += copyLen; - bufferPos += copyLen; - - remaining -= copyLen; - } - - return length; - } - - /** - * @return false, when the end of the source stream is reached - * @throws IOException - */ - private boolean ReadHashedBlock() throws IOException { - if ( atEnd ) return false; - - bufferPos = 0; - - long index = baseStream.readUInt(); - if ( index != bufferIndex ) { - throw new IOException("Invalid data format"); - } - bufferIndex++; - - byte[] storedHash = baseStream.readBytes(32); - if ( storedHash == null || storedHash.length != HASH_SIZE) { - throw new IOException("Invalid data format"); - } - - int bufferSize = LEDataInputStream.readInt(baseStream); - if ( bufferSize < 0 ) { - throw new IOException("Invalid data format"); - } - - if ( bufferSize == 0 ) { - for (int hash = 0; hash < HASH_SIZE; hash++) { - if ( storedHash[hash] != 0 ) { - throw new IOException("Invalid data format"); - } - } - - atEnd = true; - buffer = new byte[0]; - return false; - } - - buffer = baseStream.readBytes(bufferSize); - if ( buffer == null || buffer.length != bufferSize ) { - throw new IOException("Invalid data format"); - } - - MessageDigest md = null; - try { - md = MessageDigest.getInstance("SHA-256"); - } catch (NoSuchAlgorithmException e) { - throw new IOException("SHA-256 not implemented here."); - } - - byte[] computedHash = md.digest(buffer); - if ( computedHash == null || computedHash.length != HASH_SIZE ) { - throw new IOException("Hash wrong size"); - } - - if ( ! Arrays.equals(storedHash, computedHash) ) { - throw new IOException("Hashes didn't match."); - } - - return true; - } - - @Override - public long skip(long n) throws IOException { - return 0; - } - - @Override - public int read() throws IOException { - if ( atEnd ) return -1; - - if ( bufferPos == buffer.length ) { - if ( ! ReadHashedBlock() ) return -1; - } - - int output = Types.readUByte(buffer, bufferPos); - bufferPos++; - - return output; - } - - @Override - public void close() throws IOException { - baseStream.close(); - } - -} diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/HashedBlockInputStream.kt b/app/src/main/java/com/kunzisoft/keepass/stream/HashedBlockInputStream.kt new file mode 100644 index 000000000..10877dfac --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/stream/HashedBlockInputStream.kt @@ -0,0 +1,164 @@ +/* + * Copyright 2019 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePass DX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.stream + +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils + +import java.io.IOException +import java.io.InputStream +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.util.Arrays + + +class HashedBlockInputStream(inputStream: InputStream) : InputStream() { + + private val baseStream: LEDataInputStream = LEDataInputStream(inputStream) + private var bufferPos = 0 + private var buffer: ByteArray = ByteArray(0) + private var bufferIndex: Long = 0 + private var atEnd = false + + @Throws(IOException::class) + override fun read(b: ByteArray): Int { + return read(b, 0, b.size) + } + + @Throws(IOException::class) + override fun read(outBuffer: ByteArray, byteOffset: Int, length: Int): Int { + var offset = byteOffset + if (atEnd) return -1 + + var remaining = length + + while (remaining > 0) { + if (bufferPos == buffer.size) { + // Get more from the source into the buffer + if (!readHashedBlock()) { + return length - remaining + } + + } + + // Copy from buffer out + val copyLen = (buffer.size - bufferPos).coerceAtMost(remaining) + + System.arraycopy(buffer, bufferPos, outBuffer, offset, copyLen) + + offset += copyLen + bufferPos += copyLen + + remaining -= copyLen + } + + return length + } + + /** + * @return false, when the end of the source stream is reached + * @throws IOException + */ + @Throws(IOException::class) + private fun readHashedBlock(): Boolean { + if (atEnd) return false + + bufferPos = 0 + + val index = baseStream.readUInt() + if (index != bufferIndex) { + throw IOException("Invalid data format") + } + bufferIndex++ + + val storedHash = baseStream.readBytes(32) + if (storedHash == null || storedHash.size != HASH_SIZE) { + throw IOException("Invalid data format") + } + + val bufferSize = LEDataInputStream.readInt(baseStream) + if (bufferSize < 0) { + throw IOException("Invalid data format") + } + + if (bufferSize == 0) { + for (hash in 0 until HASH_SIZE) { + if (storedHash[hash].toInt() != 0) { + throw IOException("Invalid data format") + } + } + + atEnd = true + buffer = ByteArray(0) + return false + } + + buffer = baseStream.readBytes(bufferSize) + if (buffer.size != bufferSize) { + throw IOException("Invalid data format") + } + + val messageDigest: MessageDigest + try { + messageDigest = MessageDigest.getInstance("SHA-256") + } catch (e: NoSuchAlgorithmException) { + throw IOException("SHA-256 not implemented here.") + } + + val computedHash = messageDigest.digest(buffer) + if (computedHash == null || computedHash.size != HASH_SIZE) { + throw IOException("Hash wrong size") + } + + if (!Arrays.equals(storedHash, computedHash)) { + throw IOException("Hashes didn't match.") + } + + return true + } + + @Throws(IOException::class) + override fun skip(n: Long): Long { + return 0 + } + + @Throws(IOException::class) + override fun read(): Int { + if (atEnd) return -1 + + if (bufferPos == buffer.size) { + if (!readHashedBlock()) return -1 + } + + val output = DatabaseInputOutputUtils.readUByte(buffer, bufferPos) + bufferPos++ + + return output + } + + @Throws(IOException::class) + override fun close() { + baseStream.close() + } + + companion object { + + private const val HASH_SIZE = 32 + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockInputStream.java b/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockInputStream.java deleted file mode 100644 index 485700e80..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockInputStream.java +++ /dev/null @@ -1,175 +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 . - * - */ -package com.kunzisoft.keepass.stream; - -import com.kunzisoft.keepass.utils.Types; - -import java.io.IOException; -import java.io.InputStream; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; - -public class HmacBlockInputStream extends InputStream { - private LEDataInputStream baseStream; - private boolean verify; - private byte[] key; - private byte[] buffer; - private int bufferPos = 0; - private long blockIndex = 0; - private boolean endOfStream = false; - - public HmacBlockInputStream(InputStream baseStream, boolean verify, byte[] key) { - super(); - - this.baseStream = new LEDataInputStream(baseStream); - this.verify = verify; - this.key = key; - buffer = new byte[0]; - } - - @Override - public int read() throws IOException { - if (endOfStream) return -1; - - if (bufferPos == buffer.length) { - if (!readSafeBlock()) return -1; - } - - int output = Types.readUByte(buffer, bufferPos); - bufferPos++; - - return output; - } - - @Override - public int read(byte[] outBuffer, int byteOffset, int byteCount) throws IOException { - int remaining = byteCount; - while (remaining > 0) { - if (bufferPos == buffer.length) { - if (!readSafeBlock()) { - int read = byteCount - remaining; - if (read <= 0) { - return -1; - } else { - return byteCount - remaining; - } - } - } - - int copy = Math.min(buffer.length - bufferPos, remaining); - assert(copy > 0); - - System.arraycopy(buffer, bufferPos, outBuffer, byteOffset, copy); - byteOffset += copy; - bufferPos += copy; - - remaining -= copy; - } - - return byteCount; - } - - @Override - public int read(byte[] outBuffer) throws IOException { - return read(outBuffer, 0, outBuffer.length); - } - - private boolean readSafeBlock() throws IOException { - if (endOfStream) return false; - - byte[] storedHmac = baseStream.readBytes(32); - if (storedHmac == null || storedHmac.length != 32) { - throw new IOException("File corrupted"); - } - - byte[] pbBlockIndex = LEDataOutputStream.writeLongBuf(blockIndex); - byte[] pbBlockSize = baseStream.readBytes(4); - if (pbBlockSize == null || pbBlockSize.length != 4) { - throw new IOException("File corrupted"); - } - int blockSize = LEDataInputStream.readInt(pbBlockSize, 0); - bufferPos = 0; - - buffer = baseStream.readBytes(blockSize); - - if (verify) { - byte[] cmpHmac; - byte[] blockKey = HmacBlockStream.GetHmacKey64(key, blockIndex); - Mac hmac; - try { - hmac = Mac.getInstance("HmacSHA256"); - SecretKeySpec signingKey = new SecretKeySpec(blockKey, "HmacSHA256"); - hmac.init(signingKey); - } catch (NoSuchAlgorithmException e) { - throw new IOException("Invalid Hmac"); - } catch (InvalidKeyException e) { - throw new IOException("Invalid Hmac"); - } - - hmac.update(pbBlockIndex); - hmac.update(pbBlockSize); - - if (buffer.length > 0) { - hmac.update(buffer); - } - - cmpHmac = hmac.doFinal(); - Arrays.fill(blockKey, (byte)0); - - if (!Arrays.equals(cmpHmac, storedHmac)) { - throw new IOException("Invalid Hmac"); - } - - } - - blockIndex++; - - if (blockSize == 0) { - endOfStream = true; - return false; - } - - return true; - } - - @Override - public boolean markSupported() { - return false; - } - - @Override - public void close() throws IOException { - baseStream.close(); - } - - @Override - public long skip(long byteCount) throws IOException { - return 0; - } - - @Override - public int available() throws IOException { - return buffer.length - bufferPos; - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockInputStream.kt b/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockInputStream.kt new file mode 100644 index 000000000..93d244271 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/stream/HmacBlockInputStream.kt @@ -0,0 +1,166 @@ +/* + * 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 . + * + */ +package com.kunzisoft.keepass.stream + +import com.kunzisoft.keepass.utils.DatabaseInputOutputUtils + +import java.io.IOException +import java.io.InputStream +import java.security.InvalidKeyException +import java.security.NoSuchAlgorithmException +import java.util.Arrays + +import javax.crypto.Mac +import javax.crypto.spec.SecretKeySpec + +class HmacBlockInputStream(baseStream: InputStream, private val verify: Boolean, private val key: ByteArray) : InputStream() { + + private val baseStream: LEDataInputStream = LEDataInputStream(baseStream) + private var buffer: ByteArray = ByteArray(0) + private var bufferPos = 0 + private var blockIndex: Long = 0 + private var endOfStream = false + + @Throws(IOException::class) + override fun read(): Int { + if (endOfStream) return -1 + + if (bufferPos == buffer.size) { + if (!readSafeBlock()) return -1 + } + + val output = DatabaseInputOutputUtils.readUByte(buffer, bufferPos) + bufferPos++ + + return output + } + + @Throws(IOException::class) + override fun read(outBuffer: ByteArray, byteOffset: Int, byteCount: Int): Int { + var offset = byteOffset + var remaining = byteCount + while (remaining > 0) { + if (bufferPos == buffer.size) { + if (!readSafeBlock()) { + val read = byteCount - remaining + return if (read <= 0) { + -1 + } else { + byteCount - remaining + } + } + } + + val copy = (buffer.size - bufferPos).coerceAtMost(remaining) + assert(copy > 0) + + System.arraycopy(buffer, bufferPos, outBuffer, offset, copy) + offset += copy + bufferPos += copy + + remaining -= copy + } + + return byteCount + } + + @Throws(IOException::class) + override fun read(outBuffer: ByteArray): Int { + return read(outBuffer, 0, outBuffer.size) + } + + @Throws(IOException::class) + private fun readSafeBlock(): Boolean { + if (endOfStream) return false + + val storedHmac = baseStream.readBytes(32) + if (storedHmac == null || storedHmac.size != 32) { + throw IOException("File corrupted") + } + + val pbBlockIndex = LEDataOutputStream.writeLongBuf(blockIndex) + val pbBlockSize = baseStream.readBytes(4) + if (pbBlockSize == null || pbBlockSize.size != 4) { + throw IOException("File corrupted") + } + val blockSize = LEDataInputStream.readInt(pbBlockSize, 0) + bufferPos = 0 + + buffer = baseStream.readBytes(blockSize) + + if (verify) { + val cmpHmac: ByteArray + val blockKey = HmacBlockStream.GetHmacKey64(key, blockIndex) + val hmac: Mac + try { + hmac = Mac.getInstance("HmacSHA256") + val signingKey = SecretKeySpec(blockKey, "HmacSHA256") + hmac.init(signingKey) + } catch (e: NoSuchAlgorithmException) { + throw IOException("Invalid Hmac") + } catch (e: InvalidKeyException) { + throw IOException("Invalid Hmac") + } + + hmac.update(pbBlockIndex) + hmac.update(pbBlockSize) + + if (buffer.isNotEmpty()) { + hmac.update(buffer) + } + + cmpHmac = hmac.doFinal() + Arrays.fill(blockKey, 0.toByte()) + + if (!Arrays.equals(cmpHmac, storedHmac)) { + throw IOException("Invalid Hmac") + } + + } + + blockIndex++ + + if (blockSize == 0) { + endOfStream = true + return false + } + + return true + } + + override fun markSupported(): Boolean { + return false + } + + @Throws(IOException::class) + override fun close() { + baseStream.close() + } + + @Throws(IOException::class) + override fun skip(byteCount: Long): Long { + return 0 + } + + @Throws(IOException::class) + override fun available(): Int { + return buffer.size - bufferPos + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/LEDataInputStream.java b/app/src/main/java/com/kunzisoft/keepass/stream/LEDataInputStream.java index fb3ef1615..ead0413de 100644 --- a/app/src/main/java/com/kunzisoft/keepass/stream/LEDataInputStream.java +++ b/app/src/main/java/com/kunzisoft/keepass/stream/LEDataInputStream.java @@ -22,11 +22,12 @@ import java.io.IOException; import java.io.InputStream; import java.util.Arrays; +import java.util.UUID; -/** Little endian version of the DataInputStream +/** + * Little endian version of the DataInputStream * @author bpellin - * */ public class LEDataInputStream extends InputStream { @@ -34,17 +35,16 @@ public class LEDataInputStream extends InputStream { private InputStream baseStream; - public LEDataInputStream(InputStream in) { - baseStream = in; + public LEDataInputStream(InputStream inputStream) { + baseStream = inputStream; } - /** Read a 32-bit value and return it as a long, so that it can + /** + * Read a 32-bit value and return it as a long, so that it can * be interpreted as an unsigned integer. - * @return - * @throws IOException */ public long readUInt() throws IOException { - return readUInt(baseStream); + return readInt(baseStream) & INT_TO_LONG_MASK; } public int readInt() throws IOException { @@ -53,7 +53,6 @@ public int readInt() throws IOException { public long readLong() throws IOException { byte[] buf = readBytes(8); - return readLong(buf, 0); } @@ -125,7 +124,7 @@ public byte[] readBytes(int length) throws IOException { return buf; } - public void readBytes(int length, ActionReadBytes actionReadBytes) throws IOException { + public void readBytes(int length, ReadBytes readBytes) throws IOException { int bufferSize = 256 * 3; // TODO Buffer size byte[] buffer = new byte[bufferSize]; @@ -147,67 +146,60 @@ public void readBytes(int length, ActionReadBytes actionReadBytes) throws IOExce } else { optimizedBuffer = buffer; } - actionReadBytes.doAction(optimizedBuffer); + readBytes.read(optimizedBuffer); offset += read; } } - public static int readUShort(InputStream is) throws IOException { + public int readUShort() throws IOException { byte[] buf = new byte[2]; - - is.read(buf, 0, 2); - + baseStream.read(buf, 0, 2); return readUShort(buf, 0); } - public int readUShort() throws IOException { - return readUShort(baseStream); - } - /** * Read an unsigned 16-bit value. - * - * @param buf - * @param offset - * @return */ public static int readUShort( byte[] buf, int offset ) { return (buf[offset] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8); } - public static long readLong( byte buf[], int offset ) { + public static long readLong(byte[] buf, int offset ) { return ((long)buf[offset] & 0xFF) + (((long)buf[offset + 1] & 0xFF) << 8) + (((long)buf[offset + 2] & 0xFF) << 16) + (((long)buf[offset + 3] & 0xFF) << 24) + (((long)buf[offset + 4] & 0xFF) << 32) + (((long)buf[offset + 5] & 0xFF) << 40) + (((long)buf[offset + 6] & 0xFF) << 48) + (((long)buf[offset + 7] & 0xFF) << 56); } - public static long readUInt( byte buf[], int offset ) { + public static long readUInt(byte[] buf, int offset ) { return (readInt(buf, offset) & INT_TO_LONG_MASK); } public static int readInt(InputStream is) throws IOException { byte[] buf = new byte[4]; - is.read(buf, 0, 4); - return readInt(buf, 0); } - public static long readUInt(InputStream is) throws IOException { - return (readInt(is) & INT_TO_LONG_MASK); - } - /** * Read a 32-bit value. - * - * @param buf - * @param offset - * @return */ - public static int readInt( byte buf[], int offset ) { + public static int readInt(byte[] buf, int offset ) { return (buf[offset] & 0xFF) + ((buf[offset + 1] & 0xFF) << 8) + ((buf[offset + 2] & 0xFF) << 16) + ((buf[offset + 3] & 0xFF) << 24); } + public static UUID readUuid( byte[] buf, int offset ) { + long lsb = 0; + for (int i = 15; i >= 8; i--) { + lsb = (lsb << 8) | (buf[i + offset] & 0xff); + } + + long msb = 0; + for (int i = 7; i >= 0; i--) { + msb = (msb << 8) | (buf[i + offset] & 0xff); + } + + return new UUID(msb, lsb); + } } diff --git a/app/src/main/java/com/kunzisoft/keepass/stream/ReadBytes.kt b/app/src/main/java/com/kunzisoft/keepass/stream/ReadBytes.kt new file mode 100644 index 000000000..3c03dabd4 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/stream/ReadBytes.kt @@ -0,0 +1,30 @@ +package com.kunzisoft.keepass.stream + +import java.io.IOException +import java.io.InputStream + +interface ReadBytes { + /** + * Called after each buffer fill + * @param buffer filled + */ + @Throws(IOException::class) + fun read(buffer: ByteArray) +} + +@Throws(IOException::class) +fun readFromStream(inputStream: InputStream, bufferSize: Int, readBytes: ReadBytes) { + val buffer = ByteArray(bufferSize) + var read = 0 + while (read != -1) { + read = inputStream.read(buffer, 0, buffer.size) + if (read != -1) { + val optimizedBuffer: ByteArray = if (buffer.size == read) { + buffer + } else { + buffer.copyOf(read) + } + readBytes.read(optimizedBuffer) + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/tasks/ActionRunnable.kt b/app/src/main/java/com/kunzisoft/keepass/tasks/ActionRunnable.kt index a0eba5888..e75362079 100644 --- a/app/src/main/java/com/kunzisoft/keepass/tasks/ActionRunnable.kt +++ b/app/src/main/java/com/kunzisoft/keepass/tasks/ActionRunnable.kt @@ -20,7 +20,8 @@ package com.kunzisoft.keepass.tasks import android.os.Bundle -import com.kunzisoft.keepass.database.exception.LoadDatabaseException +import com.kunzisoft.keepass.database.exception.DatabaseException +import java.lang.Exception /** * Callback after a task is completed. @@ -44,24 +45,29 @@ abstract class ActionRunnable: Runnable { */ abstract fun onFinishRun() - protected fun setError(message: String? = null) { - setError(null, message) - } - - protected fun setError(exception: LoadDatabaseException?, - message: String? = null) { + protected fun setError(message: String) { result.isSuccess = false - result.exception = exception + result.exception = null result.message = message } + protected fun setError(exception: Exception) { + result.isSuccess = false + result.exception = null + result.message = exception.message + } + protected fun setError(exception: DatabaseException) { + result.isSuccess = false + result.exception = exception + result.message = exception.message + } /** * Class to manage result from ActionRunnable */ data class Result(var isSuccess: Boolean = true, var message: String? = null, - var exception: LoadDatabaseException? = null, + var exception: DatabaseException? = null, var data: Bundle? = null) } diff --git a/app/src/main/java/com/kunzisoft/keepass/timeout/ClipboardHelper.kt b/app/src/main/java/com/kunzisoft/keepass/timeout/ClipboardHelper.kt index 646b47601..1697c8e77 100644 --- a/app/src/main/java/com/kunzisoft/keepass/timeout/ClipboardHelper.kt +++ b/app/src/main/java/com/kunzisoft/keepass/timeout/ClipboardHelper.kt @@ -24,16 +24,15 @@ import android.app.AlertDialog import android.content.ClipData import android.content.ClipboardManager import android.content.Context -import android.os.Handler +import android.os.Build import android.preference.PreferenceManager -import androidx.annotation.IntegerRes import android.text.SpannableString import android.text.method.LinkMovementMethod import android.text.util.Linkify import android.widget.TextView import android.widget.Toast import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.exception.SamsungClipboardException +import com.kunzisoft.keepass.database.exception.ClipboardException import java.util.* class ClipboardHelper(private val context: Context) { @@ -42,17 +41,15 @@ class ClipboardHelper(private val context: Context) { context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager private val mTimer = Timer() - // Setup to allow the toast to happen in the foreground - private val uiThreadCallback = Handler() - @JvmOverloads fun timeoutCopyToClipboard(text: String, toastString: String = "") { if (toastString.isNotEmpty()) Toast.makeText(context, toastString, Toast.LENGTH_LONG).show() + try { copyToClipboard(text) - } catch (e: SamsungClipboardException) { - showSamsungDialog() + } catch (e: ClipboardException) { + showClipboardErrorDialog() return } @@ -79,53 +76,49 @@ class ClipboardHelper(private val context: Context) { return "" } - @Throws(SamsungClipboardException::class) + @Throws(ClipboardException::class) fun copyToClipboard(value: String) { copyToClipboard("", value) } - @Throws(SamsungClipboardException::class) + @Throws(ClipboardException::class) fun copyToClipboard(label: String, value: String) { try { clipboardManager.primaryClip = ClipData.newPlainText(label, value) } catch (e: Exception) { - throw SamsungClipboardException(e) + throw ClipboardException(e) } } - @Throws(SamsungClipboardException::class) + @Throws(ClipboardException::class) @JvmOverloads fun cleanClipboard(label: String = "") { - copyToClipboard(label, "") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + clipboardManager.clearPrimaryClip() + } else { + copyToClipboard(label, "") + } } // Task which clears the clipboard, and sends a toast to the foreground. private inner class ClearClipboardTask (private val mCtx: Context, private val mClearText: String) : TimerTask() { - override fun run() { val currentClip = getClipboard(mCtx).toString() if (currentClip == mClearText) { - - @IntegerRes - val stringErrorId = try { + try { cleanClipboard() R.string.clipboard_cleared - } catch (e: SamsungClipboardException) { + } catch (e: ClipboardException) { R.string.clipboard_error_clear } - uiThreadCallback.post { - Toast.makeText(mCtx, stringErrorId, Toast.LENGTH_LONG).show() - } } } } - private fun showSamsungDialog() { - val textDescription = context.getString(R.string.clipboard_error)+ - System.getProperty("line.separator") + - context.getString(R.string.clipboard_error_url) + private fun showClipboardErrorDialog() { + val textDescription = context.getString(R.string.clipboard_error) val spannableString = SpannableString(textDescription) val textView = TextView(context).apply { text = spannableString diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/DatabaseInputOutputUtils.kt b/app/src/main/java/com/kunzisoft/keepass/utils/DatabaseInputOutputUtils.kt new file mode 100644 index 000000000..1f65bba78 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/utils/DatabaseInputOutputUtils.kt @@ -0,0 +1,233 @@ +/* + * 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 . + * + * +KeePass for J2ME + +Copyright 2007 Naomaru Itoi + +This file was derived from + +Java clone of KeePass - A KeePass file viewer for Java +Copyright 2006 Bill Zwicky + +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.utils + +import com.kunzisoft.keepass.database.element.DateInstant +import com.kunzisoft.keepass.stream.LEDataInputStream +import com.kunzisoft.keepass.stream.LEDataOutputStream +import java.io.IOException +import java.io.OutputStream +import java.nio.charset.Charset +import java.util.* + + +/** + * Tools for slicing and dicing Java and KeePass data types. + * + * @author Bill Zwicky @pobox.com> + */ +object DatabaseInputOutputUtils { + + var ULONG_MAX_VALUE: Long = -1 + + private val defaultCharset = Charset.forName("UTF-8") + + private val CRLFbuf = byteArrayOf(0x0D, 0x0A) + private val CRLF = String(CRLFbuf) + private val SEP = System.getProperty("line.separator") + private val REPLACE = SEP != CRLF + + /** Read an unsigned byte */ + fun readUByte(buf: ByteArray, offset: Int): Int { + return buf[offset].toInt() and 0xFF + } + + /** + * Write an unsigned byte + */ + fun writeUByte(`val`: Int, buf: ByteArray, offset: Int) { + buf[offset] = (`val` and 0xFF).toByte() + } + + fun writeUByte(`val`: Int): Byte { + val buf = ByteArray(1) + + writeUByte(`val`, buf, 0) + + return buf[0] + } + + /** + * Return len of null-terminated string (i.e. distance to null) + * within a byte buffer. + */ + private fun strlen(buf: ByteArray, offset: Int): Int { + var len = 0 + while (buf[offset + len].toInt() != 0) + len++ + return len + } + + fun readCString(buf: ByteArray, offset: Int): String { + var jstring = String(buf, offset, strlen(buf, offset), defaultCharset) + + if (REPLACE) { + jstring = jstring.replace(CRLF, SEP!!) + } + + return jstring + } + + @Throws(IOException::class) + fun writeCString(string: String?, os: OutputStream): Int { + var str = string + if (str == null) { + // Write out a null character + os.write(LEDataOutputStream.writeIntBuf(1)) + os.write(0x00) + return 0 + } + + if (REPLACE) { + str = str.replace(SEP!!, CRLF) + } + + val initial = str.toByteArray(defaultCharset) + + val length = initial.size + 1 + os.write(LEDataOutputStream.writeIntBuf(length)) + os.write(initial) + os.write(0x00) + + return length + } + + /** + * Unpack date from 5 byte format. The five bytes at 'offset' are unpacked + * to a java.util.Date instance. + */ + fun readCDate(buf: ByteArray, offset: Int, calendar: Calendar = Calendar.getInstance()): DateInstant { + val dateSize = 5 + val cDate = ByteArray(dateSize) + System.arraycopy(buf, offset, cDate, 0, dateSize) + + val readOffset = 0 + val dw1 = readUByte(cDate, readOffset) + val dw2 = readUByte(cDate, readOffset + 1) + val dw3 = readUByte(cDate, readOffset + 2) + val dw4 = readUByte(cDate, readOffset + 3) + val dw5 = readUByte(cDate, readOffset + 4) + + // Unpack 5 byte structure to date and time + val year = dw1 shl 6 or (dw2 shr 2) + val month = dw2 and 0x00000003 shl 2 or (dw3 shr 6) + + val day = dw3 shr 1 and 0x0000001F + val hour = dw3 and 0x00000001 shl 4 or (dw4 shr 4) + val minute = dw4 and 0x0000000F shl 2 or (dw5 shr 6) + val second = dw5 and 0x0000003F + + // File format is a 1 based month, java Calendar uses a zero based month + // File format is a 1 based day, java Calendar uses a 1 based day + calendar.set(year, month - 1, day, hour, minute, second) + + return DateInstant(calendar.time) + } + + fun writeCDate(date: Date?, calendar: Calendar = Calendar.getInstance()): ByteArray? { + if (date == null) { + return null + } + + val buf = ByteArray(5) + calendar.time = date + + val year = calendar.get(Calendar.YEAR) + // File format is a 1 based month, java Calendar uses a zero based month + val month = calendar.get(Calendar.MONTH) + 1 + // File format is a 1 based day, java Calendar uses a 1 based day + val day = calendar.get(Calendar.DAY_OF_MONTH) + val hour = calendar.get(Calendar.HOUR_OF_DAY) + val minute = calendar.get(Calendar.MINUTE) + val second = calendar.get(Calendar.SECOND) + + buf[0] = writeUByte(year shr 6 and 0x0000003F) + buf[1] = writeUByte(year and 0x0000003F shl 2 or (month shr 2 and 0x00000003)) + buf[2] = (month and 0x00000003 shl 6 + or (day and 0x0000001F shl 1) or (hour shr 4 and 0x00000001)).toByte() + buf[3] = (hour and 0x0000000F shl 4 or (minute shr 2 and 0x0000000F)).toByte() + buf[4] = (minute and 0x00000003 shl 6 or (second and 0x0000003F)).toByte() + + return buf + } + + fun readPassword(buf: ByteArray, offset: Int): String { + return String(buf, offset, strlen(buf, offset), defaultCharset) + } + + @Throws(IOException::class) + fun writePassword(str: String, os: OutputStream): Int { + val initial = str.toByteArray(defaultCharset) + val length = initial.size + 1 + os.write(LEDataOutputStream.writeIntBuf(length)) + os.write(initial) + os.write(0x00) + return length + } + + fun readBytes(buf: ByteArray, offset: Int, len: Int): ByteArray { + val binaryData = ByteArray(len) + System.arraycopy(buf, offset, binaryData, 0, len) + return binaryData + } + + @Throws(IOException::class) + fun writeBytes(data: ByteArray?, dataLen: Int, os: OutputStream): Int { + os.write(LEDataOutputStream.writeIntBuf(dataLen)) + if (data != null) { + os.write(data) + } + return dataLen + } + + fun bytesToUuid(buf: ByteArray): UUID { + return LEDataInputStream.readUuid(buf, 0) + } + + fun uuidToBytes(uuid: UUID): ByteArray { + val buf = ByteArray(16) + LEDataOutputStream.writeLong(uuid.mostSignificantBits, buf, 0) + LEDataOutputStream.writeLong(uuid.leastSignificantBits, buf, 8) + return buf + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/MemoryUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/MemoryUtil.kt deleted file mode 100644 index d5266008a..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/utils/MemoryUtil.kt +++ /dev/null @@ -1,206 +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 . - * - */ -package com.kunzisoft.keepass.utils - -import android.os.Parcel -import android.os.Parcelable -import android.util.Log - -import com.kunzisoft.keepass.stream.ActionReadBytes - -import org.apache.commons.io.IOUtils - -import java.io.ByteArrayInputStream -import java.io.ByteArrayOutputStream -import java.io.IOException -import java.io.InputStream -import java.io.OutputStream -import java.io.PipedInputStream -import java.io.PipedOutputStream -import java.util.HashMap -import java.util.zip.GZIPInputStream -import java.util.zip.GZIPOutputStream - -object MemoryUtil { - - private val TAG = MemoryUtil::class.java.name - const val BUFFER_SIZE_BYTES = 3 * 128 - - @Throws(IOException::class) - fun copyStream(inputStream: InputStream, out: OutputStream) { - val buffer = ByteArray(BUFFER_SIZE_BYTES) - try { - var read = inputStream.read(buffer) - while (read != -1) { - out.write(buffer, 0, read) - read = inputStream.read(buffer) - if (Thread.interrupted()) { - throw InterruptedException() - } - } - } catch (error: OutOfMemoryError) { - throw IOException(error) - } - } - - @Throws(IOException::class) - fun readBytes(inputStream: InputStream, actionReadBytes: ActionReadBytes) { - val buffer = ByteArray(BUFFER_SIZE_BYTES) - var read = 0 - while (read != -1) { - read = inputStream.read(buffer, 0, buffer.size) - if (read != -1) { - val optimizedBuffer: ByteArray = if (buffer.size == read) { - buffer - } else { - buffer.copyOf(read) - } - actionReadBytes.doAction(optimizedBuffer) - } - } - } - - @Throws(IOException::class) - fun decompress(input: ByteArray): ByteArray { - val bais = ByteArrayInputStream(input) - val gzis = GZIPInputStream(bais) - - val baos = ByteArrayOutputStream() - copyStream(gzis, baos) - - return baos.toByteArray() - } - - @Throws(IOException::class) - fun compress(input: ByteArray): ByteArray { - val bais = ByteArrayInputStream(input) - - val baos = ByteArrayOutputStream() - val gzos = GZIPOutputStream(baos) - copyStream(bais, gzos) - gzos.close() - - return baos.toByteArray() - } - - /** - * Compresses the input data using GZip and outputs the compressed data. - * - * @param input - * An [InputStream] containing the input raw data. - * - * @return An [InputStream] to the compressed data. - */ - fun compress(input: InputStream): InputStream { - val compressedDataStream = PipedInputStream(3 * 128) - Log.d(TAG, "About to compress input data using gzip asynchronously...") - val compressionOutput: PipedOutputStream - var gzipCompressedDataStream: GZIPOutputStream? = null - try { - compressionOutput = PipedOutputStream(compressedDataStream) - gzipCompressedDataStream = GZIPOutputStream(compressionOutput) - IOUtils.copy(input, gzipCompressedDataStream) - Log.e(TAG, "Successfully compressed input data using gzip.") - } catch (e: IOException) { - Log.e(TAG, "Failed to compress input data.", e) - } finally { - if (gzipCompressedDataStream != null) { - try { - gzipCompressedDataStream.close() - } catch (e: IOException) { - Log.e(TAG, "Failed to close gzip output stream.", e) - } - - } - } - return compressedDataStream - } - - // For writing to a Parcel - fun writeParcelableMap( - parcel: Parcel, flags: Int, map: Map) { - parcel.writeInt(map.size) - for ((key, value) in map) { - parcel.writeParcelable(key, flags) - parcel.writeParcelable(value, flags) - } - } - - // For reading from a Parcel - fun readParcelableMap( - parcel: Parcel, kClass: Class, vClass: Class): Map { - val size = parcel.readInt() - val map = HashMap(size) - for (i in 0 until size) { - val key: K? = kClass.cast(parcel.readParcelable(kClass.classLoader)) - val value: V? = vClass.cast(parcel.readParcelable(vClass.classLoader)) - if (key != null && value != null) - map[key] = value - } - return map - } - - // For writing map with string key to a Parcel - fun writeStringParcelableMap( - parcel: Parcel, flags: Int, map: Map) { - parcel.writeInt(map.size) - for ((key, value) in map) { - parcel.writeString(key) - parcel.writeParcelable(value, flags) - } - } - - // For reading map with string key from a Parcel - fun readStringParcelableMap( - parcel: Parcel, vClass: Class): HashMap { - val size = parcel.readInt() - val map = HashMap(size) - for (i in 0 until size) { - val key: String? = parcel.readString() - val value: V? = vClass.cast(parcel.readParcelable(vClass.classLoader)) - if (key != null && value != null) - map[key] = value - } - return map - } - - - // For writing map with string key and string value to a Parcel - fun writeStringParcelableMap(dest: Parcel, map: Map) { - dest.writeInt(map.size) - for ((key, value) in map) { - dest.writeString(key) - dest.writeString(value) - } - } - - // For reading map with string key and string value from a Parcel - fun readStringParcelableMap(parcel: Parcel): HashMap { - val size = parcel.readInt() - val map = HashMap(size) - for (i in 0 until size) { - val key: String? = parcel.readString() - val value: String? = parcel.readString() - if (key != null && value != null) - map[key] = value - } - return map - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt index 36282be95..efd5d24a0 100644 --- a/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt +++ b/app/src/main/java/com/kunzisoft/keepass/utils/MenuUtil.kt @@ -19,6 +19,8 @@ */ package com.kunzisoft.keepass.utils +import android.app.Activity +import android.content.Context import android.content.Intent import android.view.Menu import android.view.MenuInflater @@ -27,10 +29,8 @@ import com.kunzisoft.keepass.BuildConfig import com.kunzisoft.keepass.R import com.kunzisoft.keepass.activities.AboutActivity import com.kunzisoft.keepass.activities.helpers.ReadOnlyHelper.READ_ONLY_DEFAULT -import com.kunzisoft.keepass.activities.stylish.StylishActivity import com.kunzisoft.keepass.settings.SettingsActivity - object MenuUtil { fun contributionMenuInflater(inflater: MenuInflater, menu: Menu) { @@ -43,33 +43,33 @@ object MenuUtil { inflater.inflate(R.menu.default_menu, menu) } - fun onContributionItemSelected(activity: StylishActivity) { - UriUtil.gotoUrl(activity, R.string.contribution_url) + fun onContributionItemSelected(context: Context) { + UriUtil.gotoUrl(context, R.string.contribution_url) } /* * @param checkLock Check the time lock before launch settings in LockingActivity */ @JvmOverloads - fun onDefaultMenuOptionsItemSelected(activity: StylishActivity, item: MenuItem, readOnly: Boolean = READ_ONLY_DEFAULT, timeoutEnable: Boolean = false): Boolean { + fun onDefaultMenuOptionsItemSelected(activity: Activity, + item: MenuItem, + readOnly: Boolean = READ_ONLY_DEFAULT, + timeoutEnable: Boolean = false): Boolean { when (item.itemId) { R.id.menu_contribute -> { onContributionItemSelected(activity) return true } - R.id.menu_app_settings -> { // To avoid flickering when launch settings in a LockingActivity SettingsActivity.launch(activity, readOnly, timeoutEnable) return true } - R.id.menu_about -> { val intent = Intent(activity, AboutActivity::class.java) activity.startActivity(intent) return true } - else -> return true } } diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/ParcelableUtil.kt b/app/src/main/java/com/kunzisoft/keepass/utils/ParcelableUtil.kt new file mode 100644 index 000000000..ada214fc3 --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/utils/ParcelableUtil.kt @@ -0,0 +1,104 @@ +/* + * Copyright 2019 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePass DX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePass DX. If not, see . + * + */ +package com.kunzisoft.keepass.utils + +import android.os.Parcel +import android.os.Parcelable +import java.util.* + +object ParcelableUtil { + + // For writing to a Parcel + fun writeParcelableMap( + parcel: Parcel, flags: Int, map: Map) { + parcel.writeInt(map.size) + for ((key, value) in map) { + parcel.writeParcelable(key, flags) + parcel.writeParcelable(value, flags) + } + } + + // For reading from a Parcel + fun readParcelableMap( + parcel: Parcel, kClass: Class, vClass: Class): Map { + val size = parcel.readInt() + val map = HashMap(size) + for (i in 0 until size) { + val key: K? = kClass.cast(parcel.readParcelable(kClass.classLoader)) + val value: V? = vClass.cast(parcel.readParcelable(vClass.classLoader)) + if (key != null && value != null) + map[key] = value + } + return map + } + + // For writing map with string key to a Parcel + fun writeStringParcelableMap( + parcel: Parcel, flags: Int, map: Map) { + parcel.writeInt(map.size) + for ((key, value) in map) { + parcel.writeString(key) + parcel.writeParcelable(value, flags) + } + } + + // For reading map with string key from a Parcel + fun readStringParcelableMap( + parcel: Parcel, vClass: Class): HashMap { + val size = parcel.readInt() + val map = HashMap(size) + for (i in 0 until size) { + val key: String? = parcel.readString() + val value: V? = vClass.cast(parcel.readParcelable(vClass.classLoader)) + if (key != null && value != null) + map[key] = value + } + return map + } + + + // For writing map with string key and string value to a Parcel + fun writeStringParcelableMap(dest: Parcel, map: Map) { + dest.writeInt(map.size) + for ((key, value) in map) { + dest.writeString(key) + dest.writeString(value) + } + } + + // For reading map with string key and string value from a Parcel + fun readStringParcelableMap(parcel: Parcel): HashMap { + val size = parcel.readInt() + val map = HashMap(size) + for (i in 0 until size) { + val key: String? = parcel.readString() + val value: String? = parcel.readString() + if (key != null && value != null) + map[key] = value + } + return map + } +} + +inline fun > Parcel.readEnum() = + readString()?.let { enumValueOf(it) } + +inline fun > Parcel.writeEnum(value: T?) = + writeString(value?.name) diff --git a/app/src/main/java/com/kunzisoft/keepass/utils/Types.java b/app/src/main/java/com/kunzisoft/keepass/utils/Types.java deleted file mode 100644 index 71eaacf12..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/utils/Types.java +++ /dev/null @@ -1,182 +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 . - * - * -KeePass for J2ME - -Copyright 2007 Naomaru Itoi - -This file was derived from - -Java clone of KeePass - A KeePass file viewer for Java -Copyright 2006 Bill Zwicky - -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.utils; - -import com.kunzisoft.keepass.stream.LEDataOutputStream; - -import java.io.IOException; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.util.UUID; - - -/** - * Tools for slicing and dicing Java and KeePass data types. - * - * @author Bill Zwicky - */ -public class Types { - - public static long ULONG_MAX_VALUE = -1; - - /** Read an unsigned byte */ - public static int readUByte( byte[] buf, int offset ) { - return ((int)buf[offset] & 0xFF); - } - - /** Write an unsigned byte - * - * @param val - * @param buf - * @param offset - */ - public static void writeUByte(int val, byte[] buf, int offset) { - buf[offset] = (byte)(val & 0xFF); - } - - public static byte writeUByte(int val) { - byte[] buf = new byte[1]; - - writeUByte(val, buf, 0); - - return buf[0]; - } - - /** - * Return len of null-terminated string (i.e. distance to null) - * within a byte buffer. - * - * @param buf - * @param offset - * @return - */ - public static int strlen( byte[] buf, int offset ) { - int len = 0; - while( buf[offset + len] != 0 ) - len++; - return len; - } - - - - /** - * Copy a sequence of bytes into a new array. - * - * @param b - source array - * @param offset - first byte - * @param len - number of bytes - * @return new byte[len] - */ - public static byte[] extract( byte[] b, int offset, int len ) { - byte[] b2 = new byte[len]; - System.arraycopy( b, offset, b2, 0, len ); - return b2; - } - - - private static final byte[] CRLFbuf = { 0x0D, 0x0A }; - private static final String CRLF = new String(CRLFbuf); - private static final String SEP = System.getProperty("line.separator"); - private static final boolean REPLACE = ! SEP.equals(CRLF); - - public static String readCString(byte[] buf, int offset) throws UnsupportedEncodingException { - String jstring = new String(buf, offset, strlen(buf, offset), "UTF-8"); - - if ( REPLACE ) { - jstring = jstring.replace(CRLF, SEP); - } - - return jstring; - } - - public static int writeCString(String str, OutputStream os) throws IOException { - if ( str == null ) { - // Write out a null character - os.write(LEDataOutputStream.writeIntBuf(1)); - os.write(0x00); - return 0; - } - - if ( REPLACE ) { - str = str.replace(SEP, CRLF); - } - - byte[] initial = str.getBytes("UTF-8"); - - int length = initial.length+1; - os.write(LEDataOutputStream.writeIntBuf(length)); - os.write(initial); - os.write(0x00); - - return length; - } - - public static UUID bytestoUUID(byte[] buf) { - return bytestoUUID(buf, 0); - } - - public static UUID bytestoUUID(byte[] buf, int offset) { - long lsb = 0; - for (int i = 15; i >= 8; i--) { - lsb = (lsb << 8) | (buf[i + offset] & 0xff); - } - - long msb = 0; - for (int i = 7; i >= 0; i--) { - msb = (msb << 8) | (buf[i + offset] & 0xff); - } - - return new UUID(msb, lsb); - - } - - public static byte[] UUIDtoBytes(UUID uuid) { - byte[] buf = new byte[16]; - - LEDataOutputStream.writeLong(uuid.getMostSignificantBits(), buf, 0); - LEDataOutputStream.writeLong(uuid.getLeastSignificantBits(), buf, 8); - - return buf; - } - -} diff --git a/app/src/main/java/com/kunzisoft/keepass/view/AdvancedUnlockInfoView.kt b/app/src/main/java/com/kunzisoft/keepass/view/AdvancedUnlockInfoView.kt index df550bb4e..366d730cc 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/AdvancedUnlockInfoView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/AdvancedUnlockInfoView.kt @@ -51,12 +51,18 @@ class AdvancedUnlockInfoView @JvmOverloads constructor(context: Context, } } - fun setIconViewClickListener(listener: ((view: View)->Unit)?) { + fun setIconViewClickListener(animation: Boolean = true, + listener: ((view: View)->Unit)?) { + var animateButton = animation if (listener == null) - stopIconViewAnimation() - else + animateButton = false + if (animateButton) { startIconViewAnimation() - unlockContainerView.alpha = if (listener == null) 0.8f else 1f + unlockContainerView.alpha = 1f + } else { + stopIconViewAnimation() + unlockContainerView.alpha = 0.8f + } unlockIconImageView?.setOnClickListener(listener) } diff --git a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt index d4da8e9ff..fdfbff4a7 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/EntryContentsView.kt @@ -33,8 +33,8 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.kunzisoft.keepass.R import com.kunzisoft.keepass.adapters.EntryHistoryAdapter -import com.kunzisoft.keepass.database.element.EntryVersioned -import com.kunzisoft.keepass.database.element.PwDate +import com.kunzisoft.keepass.database.element.Entry +import com.kunzisoft.keepass.database.element.DateInstant import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.otp.OtpElement import com.kunzisoft.keepass.otp.OtpType @@ -304,15 +304,15 @@ class EntryContentsView @JvmOverloads constructor(context: Context, extrasContainerView.visibility = View.GONE } - fun assignCreationDate(date: PwDate) { + fun assignCreationDate(date: DateInstant) { creationDateView.text = date.getDateTimeString(resources) } - fun assignModificationDate(date: PwDate) { + fun assignModificationDate(date: DateInstant) { modificationDateView.text = date.getDateTimeString(resources) } - fun assignLastAccessDate(date: PwDate) { + fun assignLastAccessDate(date: DateInstant) { lastAccessDateView.text = date.getDateTimeString(resources) } @@ -320,7 +320,7 @@ class EntryContentsView @JvmOverloads constructor(context: Context, expiresImageView.visibility = if (isExpires) View.VISIBLE else View.GONE } - fun assignExpiresDate(date: PwDate) { + fun assignExpiresDate(date: DateInstant) { assignExpiresDate(date.getDateTimeString(resources)) } @@ -336,12 +336,16 @@ class EntryContentsView @JvmOverloads constructor(context: Context, historyContainerView.visibility = if (show) View.VISIBLE else View.GONE } - fun assignHistory(history: ArrayList) { + fun refreshHistory() { + historyAdapter.notifyDataSetChanged() + } + + fun assignHistory(history: ArrayList) { historyAdapter.clear() historyAdapter.entryHistoryList.addAll(history) } - fun onHistoryClick(action: (historyItem: EntryVersioned, position: Int)->Unit) { + fun onHistoryClick(action: (historyItem: Entry, position: Int)->Unit) { historyAdapter.onItemClickListener = { item, position -> action.invoke(item, position) } diff --git a/app/src/main/java/com/kunzisoft/keepass/view/EntryEditContentsView.kt b/app/src/main/java/com/kunzisoft/keepass/view/EntryEditContentsView.kt index d3354da34..e92ca87ff 100644 --- a/app/src/main/java/com/kunzisoft/keepass/view/EntryEditContentsView.kt +++ b/app/src/main/java/com/kunzisoft/keepass/view/EntryEditContentsView.kt @@ -11,7 +11,7 @@ import android.widget.EditText import android.widget.ImageView import android.widget.LinearLayout import com.kunzisoft.keepass.R -import com.kunzisoft.keepass.database.element.PwIcon +import com.kunzisoft.keepass.database.element.icon.IconImage import com.kunzisoft.keepass.database.element.security.ProtectedString import com.kunzisoft.keepass.icons.IconDrawableFactory import com.kunzisoft.keepass.icons.assignDatabaseIcon @@ -81,7 +81,7 @@ class EntryEditContentsView @JvmOverloads constructor(context: Context, entryIconView.assignDefaultDatabaseIcon(iconFactory, iconColor) } - fun setIcon(iconFactory: IconDrawableFactory, icon: PwIcon) { + fun setIcon(iconFactory: IconDrawableFactory, icon: IconImage) { entryIconView.assignDatabaseIcon(iconFactory, icon, iconColor) } diff --git a/app/src/main/res/layout/view_entry_contents.xml b/app/src/main/res/layout/view_entry_contents.xml index ae5343346..772662756 100644 --- a/app/src/main/res/layout/view_entry_contents.xml +++ b/app/src/main/res/layout/view_entry_contents.xml @@ -245,12 +245,14 @@ + \ No newline at end of file diff --git a/app/src/main/res/menu/edit_entry.xml b/app/src/main/res/menu/entry_otp.xml similarity index 100% rename from app/src/main/res/menu/edit_entry.xml rename to app/src/main/res/menu/entry_otp.xml diff --git a/app/src/main/res/menu/recycle_bin.xml b/app/src/main/res/menu/recycle_bin.xml new file mode 100644 index 000000000..1b6cc50a1 --- /dev/null +++ b/app/src/main/res/menu/recycle_bin.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index dabf34100..81a757d6c 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -18,8 +18,7 @@ along with KeePass DX. If not, see . Czech translation by Jan Vaněk ---> - +--> Nahlášení problému Domovská stránka Androidová verze správce hesel KeePass @@ -240,7 +239,7 @@ Při zhasnutí obrazovky uzamknout databázi Pokročilé odemčení Biometrické odemčení - Nechá otevřít databázi snímáním otisku prstu + Nechá otevřít databázi snímáním biometrického údaje Smazat šifrovací klíče Smazat všechny šifrovací klíče související s biometrickým rozlišením Opravdu chcete smazat všechny klíče související s biometrickým rozlišením\? @@ -315,15 +314,12 @@ Zapojit se Zapojte se a pomozte zvýšit stabilitu, bezpečnost a přidávání dalších funkcí. Narozdíl od mnoha aplikací pro správu hesel, tato je bez reklam, je svobodným softwarem a neodesílá nikam žádné osobní údaje, bez ohledu na to, jakou verzi používáte. - Zakoupením varianty „pro“ získáte přístup k této vizuální funkci a hlavně pomůžete uskutečnění komunitních projektů. - + Zakoupením varianty „pro“ získáte přístup k této vizuální funkci a hlavně pomůžete uskutečnění komunitních projektů. Tato vizuální funkce je k dispozici díky vaší štědrosti. - Pro zajištění svobody nás všech a pokračování aktivity, počítáme s vaším přispěním. - + Pro zajištění svobody nás všech a pokračování aktivity, počítáme s vaším přispěním. Tato funkce je ve vývoji a potřebuje váš příspěvek, aby byla brzy k dispozici. Zakoupením pro varianty, - - Zapojením se, + Zapojením se, povzbudíte vývojáře k přidávání nových funkcí a opravování chyb dle vašich připomínek. Mnohé díky za vaše přispění. Tvrdě pracujeme na brzkém vydání této funkce. @@ -358,7 +354,7 @@ Vydat zvuk při stisku klávesy Režim výběru Neshoďte aplikaci… - K uzamknutí stiskněte Zpět v hlavním panelu + K uzamknutí stiskněte Zpět Zamknout obrazovku, pokud uživatel stiskne tlačítko Zpět v hlavním panelu Vymazat při ukončení Uzavřít databázi při uzavření oznámení @@ -401,4 +397,51 @@ Automaticky otevřít biometrickou pobídku, je-li biometrický klíč pro databázi definován Zapnout Vypnout + Hlavní klíč + Zabezpečení + Historie + Nastavit heslo na jedno použití (OTP) + Typ OTP + Tajnost + Interval (vteřiny) + Čítač + Číslice + Algoritmus + OTP + Neplatná OTP tajnost. + Nejméně jeden přihlašovací údaj musí být zadán. + Sem skupinu kopírovat nemůžete. + Tajný klíč musí mít formát Base32. + Čítač musít být mezi %1$d a %2$d. + Interval musít být mezi %1$d a %2$d vteřinami. + Token musí obsahovat mezi %1$d a %2$d číslicemi. + %1$s s totožným UUID %2$s již existuje. + Zakládám databázi… + Nastavení zabezpečení + Nastavení hlavního klíče + Databáze obsahuje duplikátní UUID. + Prověřením toho dialogu opraví KeePassDX chybu (založením nového UUID pro duplikáty) a bude pokračovat. + Databáze otevřena + Kopírujte pole záznamů pomocí schránky Vašeho zařízení + Trvalé oznámení + Přidat oznámení, když je databáze otevřena + K snadnějšímu otevření databáze použijte pokročilé odemknutí + Komprese dat + Komprese dat snižuje velikost databáze. + Max. položek v historii + Omezit počet položek v historii záznamu + Max. velikost historie + Omezit velikost historie na záznam (v binárních bajtech) + Doporučit změnu + Dporučit změnu hlavního klíče (dny) + Vynutit změnu + Vynutit změnu hlavního klíče (dny) + Vynutit změnu příště + Vynutit změnu hlavního klíče příště (jednou) + Výchozí uživatelské jméno + Vlastní barva databáze + Komprese + Žádná + GZip + Nastavení klávesnice zařízení \ No newline at end of file diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 0a920e5e2..363e0302e 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -18,8 +18,7 @@ along with KeePass DX. If not, see . Danish translation by Frederik Svarre (fsvarre@gmail.com) ---> - +--> Tilbagemelding Hjemmeside Android implementering af KeePass password manager @@ -357,7 +356,7 @@ Noter Valgstilstand Luk ikke programmet… - Lås ved retur + Tryk på \'Tilbage\' for at låse Lås databasen, når brugeren klikker på tilbage-knappen fra startskærmen Ryd ved lukning Luk databasen ved lukning af underretning @@ -400,4 +399,51 @@ Åbn automatisk biometrisk prompt, når der er defineret en biometrisk nøgle for en database Aktiver Deaktiver + Hovednøgle + Sikkerhed + Historik + Indstilling af engangsadgangskode + OTP-type + Hemmelig + Periode (sekunder) + Tæller + Cifre + Algoritme + OTP + Ugyldig OTP-hemmelighed. + Der skal angives mindst en legitimationsoplysning. + En gruppe kan ikke kopieres her. + Den hemmelige nøgle skal være i Base32-format. + Tæller skal være mellem %1$d og %2$d. + Perioden skal være mellem %1$d og %2$d sekunder. + Token skal indeholder %1$d til %2$d cifre. + %1$s med det samme UUID %2$s findes allerede. + Opretter database… + Sikkerhedsindstillinger + Indstillinger for hovednøgle + Databasen indeholder dublerede UUID\'er. + Ved at godkende dialogboksen, vil KeePass DX løse problemet (ved at generere nye UUID\'er for dubletter) og fortsætte. + Database åbnet + Kopier indtastningsfelter ved hjælp af enhedens udklipsholder + Vedvarende meddelelse + Tilføj en meddelelse, når databasen er åben + Brug avanceret oplåsning for at gøre det lettere at åbne en database + Datakomprimering + Datakomprimering reducerer størrelsen på databasen. + Maks. historikposter + Begræns antallet af historikposter pr. indtastning + Maks. historiestørrelse + Begræns historikstørrelse pr. post (i binære bytes) + Anbefalet ændring + Anbefal ændring af hovednøglen (dage) + Gennemtving ændring + Gennemtving ændring af hovednøglen (dage) + Gennemtving ændring næste gang + Gennemtving ændring af hovednøglen næste gang (én gang) + Standard brugernavn + Brugerdefineret databasefarve + Komprimering + Ingen + Gzip + Indstillinger for enhedens tastatur \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 964b2aa82..ce6b71c76 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -20,8 +20,7 @@ Translations from BoDEAN Translations from Matthias Dill Translations from David Ramiro ---> - +--> Rückmeldung Webseite Android-Implementierung des Passwortmanagers KeePass @@ -29,24 +28,24 @@ Eintrag hinzufügen Gruppe hinzufügen Verschlüsselungsalgorithmus - App-Sperre - Dauer der Inaktivität bis die App gesperrt wird + App-Timeout + Inaktivität vor dem Sperren der App App - App Einstellungen + App-Einstellungen Nicht mehr anzeigen Klammern - Durchsuchen Sie Ihre Dateien, indem Sie den OpenIntents File Manager installieren + Das Erstellen, Öffnen und Speichern einer Datenbankdatei erfordert die Installation eines Dateimanagers, der die beabsichtigten Aktionen ACTION_CREATE_DOCUMENT und ACTION_OPEN_DOCUMENT akzeptiert. Zwischenablage geleert - Zwischenablagefehler + Zwischenablage-Fehler Einige Samsung Android-Smartphones lassen keine Nutzung der Zwischenablage durch Apps zu. Leeren der Zwischenablage fehlgeschlagen - Zwischenablagesperre + Zwischenablage-Timeout Dauer der Speicherung in der Zwischenablage %1$s in die Zwischenablage kopieren Datenbank-Schlüsseldatei erzeugen … Datenbank Entschlüsselung der Datenbankinhalte … - Als Standard-Datenbank benutzen + Als Standard-Datenbank verwenden Zahlen KeePass DX © %1$d Kunzisoft. Alle Rechte vorbehalten. Die Nutzung der Software erfolgt auf eigene Verantwortung und ohne jegliche Garantie. Die Applikation ist frei und wird unter den Bedingungen der GNU GPL Version 3 (oder später) verbreitet und lizenziert. Vorhandene Datenbank öffnen @@ -58,7 +57,7 @@ Ablaufdatum Schlüsseldatei Änderungsdatum - Eintrag wurde nicht gefunden. + Zugriffsdaten wurden nicht gefunden. Passwort Speichern Titel @@ -72,9 +71,9 @@ Namen eingeben. Schlüsseldatei wählen. Zu wenig Speicherplatz, um die ganze Datenbank zu laden. - Mindestens eine Art der Passwortgenerierung muss ausgewählt werden. + Mindestens eine Art der Passwortgenerierung muss ausgewählt sein. Die Passwörter stimmen nicht überein. - „Transformationsrunden“ zu hoch. Wird auf 214748364848 eingestellt. + „Transformationsrunden“ zu hoch. Wird auf 2147483648 eingestellt. Für jede Zeichenfolge ist ein Feldname notwendig. Titel hinzufügen. Eine positive ganze Zahl in das Feld „Länge“ eingeben. @@ -98,8 +97,8 @@ Es existiert keine Schlüsseldatei. Die Schlüsseldatei ist leer. Länge - Gruppenliste - Schriftgröße der Gruppenliste + Größe der Listenelemente + Schriftgröße der Listenelemente Datenbank wird geladen … Kleinbuchstaben Passwort verstecken @@ -110,8 +109,8 @@ Datenbank-Einstellungen Löschen Spenden - Ändern - Passwort verstecken + Bearbeiten + Passwort ausblenden Datenbank sperren Öffnen Suchen @@ -143,22 +142,22 @@ Datenbank wird gespeichert … Leerzeichen Suchen - Natürliche Datenbank + Natürliche Sortierung Spezialsymbole - Titel/Beschreibung des Eintrags + Suchen Suchergebnisse Twofish Unterstriche Datenbankversion wird nicht unterstützt. Großbuchstaben Warnung - Passwortzeichen in .kbd-Dateien vermeiden, die nicht Teil des Latin-1-Zeichensatzes sind, da sie alle in ein und dasselbe Zeichen umgewandelt werden. + Passwortzeichen in der Datenbank vermeiden, die kein Text-Encoding-Format besitzen (nicht erkannte Zeichen werden in denselben Buchstaben umgewandelt). Schreibzugriff auf die SD-Karte gewähren, um Datenbankänderungen speichern zu können. Die SD-Karte einbinden, um eine Datenbank erstellen oder laden zu können. Version %1$s - Das Passwort und/oder eine Schlüsseldatei zum Entsperren der Datenbank eingeben. -\n -\nDaran denken, nach jeder Änderung die .kdbx-Datei an einem sicheren Ort abzuspeichern. + Geben Sie das Passwort und/oder die Schlüsseldatei ein, um Ihre Datenbank zu entsperren. +\n +\nSichern Sie Ihre Datenbankdatei nach jeder Änderung an einem sicheren Ort. 5 Sekunden 10 Sekunden @@ -181,7 +180,7 @@ Generierte Passwortlänge Legt die Standardlänge des generierten Passworts fest Zwischenablagenbenachrichtigungen - Benachrichtigungen für die Zwischenablage einschalten, um Felder eines Eintrags kopieren zu können. + Benachrichtigungen für die Zwischenablage einschalten, um beim Anzeigen von Eingabefeldern diese kopieren zu können Bildschirmsperre Datenbank sperren, wenn der Bildschirm ausgeschaltet wird Neue Datenbank erstellen @@ -190,31 +189,31 @@ Pfad Dateiname Dieses Feature konnte nicht gestartet werden. - Ermöglicht die Datenbanköffnung mit dem Fingerabdruck + Ermöglicht Ihre Biometrie zu scannen, um die Datenbank zu öffnen Erweiterte Entsperrung - Fingerabdruckscanner + Biometrische Entsperrung Sperre Erlaubte Zeichen für Passwortgenerator festlegen Passwortzeichen - Fingerabdruck-Scannen wird unterstützt, ist jedoch nicht konfiguriert. - Fingerabdruck wird gescannt + Die biometrische Abfrage wird unterstützt, ist aber nicht eingerichtet. + Biometrische Abfrage öffnen, um die Datenbank zu entsperren Verschlüsseltes Passwort wurde gespeichert - Fingerabdruckschlüssel nicht lesbar. Bitte das Passwort wiederherstellen. - Problem mit dem Fingerabdruck: %1$s + Biometrischer Schlüssel konnte nicht gelesen werden. Stellen Sie Ihre Anmeldedaten wieder her. + Biometrischer Fehler: %1$s Verlauf Allgemein - Fingerabdruck verwenden, um dieses Passwort zu speichern + Biometrische Abfrage öffnen, um Anmeldedaten zu speichern Diese Datenbank hat noch kein Passwort. App-Design, das in der App genutzt wird Verschlüsselung Schlüsselableitungsfunktion - Erweitertes ASCII + Erweiterte ASCII Erlauben Wischen, um Zwischenablage jetzt zu leeren Autofill-Dienst kann nicht aktiviert werden. Kopie von %1$s Formularausfüllung - Gespeicherten Fingerabdruck löschen + Gespeicherten biometrischen Schlüssel löschen Verschlüsselungsalgorithmus der Datenbank wird für sämtliche Daten verwendet. Um den Schlüssel für den Verschlüsselungsalgorithmus zu generieren, wird der Hauptschlüssel umgewandelt, wobei ein zufälliger Salt in der Schlüsselberechnung verwendet wird. Speichernutzung @@ -227,19 +226,19 @@ Papierkorb unten Titel Benutzername - Erzeugt am + Erstelldatum Änderungsdatum Zugriffsdatum - Fingerabdruck nicht erkannt - Auto-Einfügen + Biometrische Daten nicht erkannt + Autofill KeePass DX autom. Formularausfüllung Mit KeePass DX anmelden - Standard Autofill-Dienst auswählen + Autofill-Dienstvorgabe festlegen Autofill aktivieren, um automatisch Eingabefelder in anderen Apps auszufüllen Zwischenablage Verschlüsselungsschlüssel löschen - Alle Verschlüsselungsschlüssel für Fingerabdruckerkennung löschen - Sollen wirklich alle mit der Fingerabdruckerkennung verknüpften Schlüssel gelöscht werden\? + Alle Verschlüsselungsschlüssel für biometrischen Erkennung löschen + Sind Sie sicher, dass Sie alle Schlüssel zur biometrischen Erkennung löschen möchten\? Die Android-Version, %1$s, erfüllt nicht die Mindestanforderung für Version %2$s. Keine entsprechende Hardware. Bytes @@ -273,10 +272,10 @@ \nGruppen (wie Ordner) helfen, Einträge in der Datenbank zu ordnen. Einträge durchsuchen Titel, Benutzernamen oder Inhalte anderer Feldern eingeben, um die Passwörter wiederzufinden. - Datenbank mit Fingerabdruck entsperren - Passwort und eigenen Fingerabdruck verknüpfen, um die Datenbank schnell zu entsperren. + Datenbank-Entsperrung durch Biometrie + Verknüpft Ihr Passwort mit Ihrer gescannten Biometrie, um Ihre Datenbank schnell zu entsperren. Eintrag bearbeiten - Einträge mit benutzerdefinierten Feldern bearbeiten. Pooldaten können zwischen verschiedenen Eingabefeldern referenziert werden. + Bearbeiten Sie Ihren Eintrag mit persönlichen Feldern. Persönliche Felder können mit Querverweisen aus anderen Einträgen ergänzt werden. Ein starkes Passwort für den Eintrag erstellen. Erzeugung eines starken Passworts, das mit dem Eintrag verknüpft wird, nach Kriterien, die einfach in einem Formular festgelegt werden. Dabei an die Passwortsicherheit denken. Benutzerdefinierte Felder hinzufügen @@ -319,7 +318,7 @@ Abbrechen Wenn das automatische Löschen der Zwischenablage fehlschlägt, bitte den Verlauf manuell löschen. WARNUNG: Alle Apps teilen sich die Zwischenablage. Wenn sensible Daten kopiert werden, kann andere Software darauf zugreifen. - Zulassen, dass kein Master-Passwort verwendet wird. + Entsperren ohne Hauptschlüssel „Öffnen“-Taste aktivieren, wenn keine Passwort-Identifikation festgelegt ist Hilfe-Anzeige Bedienelemente hervorheben, um die Funktionsweise der App zu lernen @@ -342,8 +341,8 @@ Magikeyboard (KeePass DX) Magikeyboard-Einstellungen Eintrag - Zeitsperre - Zeit nach der Tastatureingaben gelöscht werden + Timeout + Timeout zum Löschen der Tastatureingabe Benachrichtigung Benachrichtigung anzeigen, wenn ein Eintrag abrufbar ist Eintrag @@ -358,7 +357,7 @@ Ton bei Tastendruck Auswahlmodus Nicht die App beenden … - Schließe die Datenbank wenn auf der Startseite der Zurück-Button gedrückt wird + Datenbank sperren, wenn auf dem Hauptbildschirm der Zurück-Button gedrückt wird Beim Schließen löschen Papierkorb Eintragsauswahl @@ -376,29 +375,76 @@ Feld hinzufügen Feld entfernen UUID - Zeige die Anzahl der Einträge - Zeige die Anzahl der Einträge in einer Gruppe + Anzahl der Einträge anzeigen + Anzahl der Einträge in einer Gruppe anzeigen Knoten hinzufügen - Drücke Zurück auf der Startseite zum Schließen + \"Zurück\" drücken, um zu sperren Die Datenbank schließen, wenn die Benachrichtigung geschlossen wird Untergeordneter Knotenpunkt Schlüsseldatei-Kontrollkästchen - Eintrag kann nicht hierher verschoben werden. - Eintrag kann nicht hierher kopiert werden. - Passwort-Kontrollkästchen + Du kannst keinen Eintrag hierher verschieben. + Du kannst keinen Eintrag hierher kopieren. + Kontrollkästchen Umschalten der Kennwortanzeige wiederholen Hintergrund Aktualisieren - Fehler schließen + Felder schließen Es ist nicht möglich, eine Datenbank mit diesem Passwort und dieser Schlüsseldatei zu erstellen. Erweiterte Entsperrung - Speichern der Fingerabdruck-Erkennung - Datenbank mit Fingerabdruck-Erkennung öffnen - Fingerabdruck + Biometrische Erkennung speichern + Datenbank mit biometrische Erkennung öffnen + Biometrisch Aktivieren Deaktivieren Datenbank-Anmeldeinformationen biometrisch speichern Datenbank-Anmeldeinformationen mit biometrischen Daten extrahieren Biometrische Eingabeaufforderung automatisch öffnen Biometrische Eingabeaufforderung automatisch öffnen, wenn ein biometrischer Schlüssel für eine Datenbank definiert ist + Hauptschlüssel + Sicherheit + Verlauf + Einmal-Passwort (OTP) einrichten + OTP-Typ + Geheimnis + Zeitraum (Sekunden) + Zähler + Stellen + Algorithmus + OTP + Ungültiges OTP-Geheimnis. + Mindestens eine Anmeldeinformation muss festgelegt sein. + Eine Gruppe kann nicht hierher kopiert werden. + Geheimschlüssel muss im Base32-Format vorliegen. + Zähler muss zwischen %1$d und %2$d eingestellt sein. + Zeitraum muss zwischen %1$d und %2$d Sekunden liegen. + Token muss %1$d bis %2$d Stellen enthalten. + %1$s mit derselben UUID %2$s existiert bereits. + Datenbank wird erstellt… + Sicherheits-Einstellungen + Hauptschlüssel-Einstellungen + Die Datenbank enthält UUID-Duplikate. + Durch die Validierung dieses Dialogs wird KeePass DX das Problem (durch Erzeugung neuer UUIDs für Duplikate) beheben und weiter ausgeführt. + Datenbank geöffnet + Eintragsfelder mithilfe der Zwischenablage des Geräts kopieren + Dauerhafte Benachrichtigung + Bei geöffneter Datenbank eine Benachrichtigung hinzufügen + Erweitertes Entsperren verwenden, um eine Datenbank einfacher zu öffnen + Datenkompression + Datenkompression reduziert die Datenbankgröße. + Max. Verlaufseinträge + Anzahl der Verlaufseinträge pro Eintrag begrenzen + Max. Verlaufsumfang + Verlaufsumfang pro Eintrag (in binären Bytes) begrenzen + Änderung empfehlen + (Nach Tagen) Änderung des Hauptschlüssels empfehlen + Änderung erzwingen + (Nach Tagen) Änderung des Hauptschlüssels erzwingen + Änderung beim nächsten Mal erzwingen + Änderung des Hauptschlüssels beim nächsten Mal erzwingen (einmalig) + Vorgegebener Benutzername + Benutzerdefinierte Datenbankfarbe + Kompression + Keine + GZip + Gerätetastatur-Einstellungen \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index c3f4f2b60..f167dacd9 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -16,8 +16,7 @@ You should have received a copy of the GNU General Public License along with KeePass DX. If not, see . ---> - +--> Commentaires Site Web Implémentation Android du gestionnaire de mots de passe KeePass @@ -119,7 +118,7 @@ Ouvrir Rechercher Afficher le mot de passe - Supprimer l’empreinte digitale enregistrée + Supprimer l’empreinte biométrique enregistrée Ouvrir l’URL Moins Jamais @@ -203,12 +202,12 @@ Verrouiller Verrouillage d’écran Verrouiller la base de données lorsque l’écran est éteint - Empreinte digitale - Reconnaissance d’empreinte digitale - Vous permet de numériser votre empreinte digitale pour ouvrir la base de données + Déverouillage avancée + Déverouillage biométrique + Vous permet de numériser votre empreinte biométrique pour ouvrir la base de données Supprimer les clés de chiffrement - Supprimer toutes les clés de chiffrement liées à la reconnaissance d’empreinte digitale - Êtes-vous sûr de vouloir supprimer toutes les clés liées à la reconnaissance d’empreinte digitale \? + Supprimer toutes les clés de chiffrement liées à la reconnaissance biométrique + Êtes-vous sûr de vouloir supprimer toutes les clés liées à la reconnaissance biométrique \? Impossible de démarrer cette fonctionnalité. Votre version Android %1$s est inférieure à la version minimale %2$s requise. Impossible de trouver le matériel correspondant. @@ -220,7 +219,7 @@ Chemin de fichier Afficher le chemin complet du fichier Utiliser la corbeille - Déplace les groupes et les entrées dans la « Corbeille » avant suppression + Déplace les groupes et les entrées dans le groupe \"Corbeille\" avant suppression Police de champ Changer la police utilisée dans les champs pour une meilleure visibilité des caractères Confiance dans le presse-papier @@ -250,8 +249,8 @@ \nLes groupes (~ dossiers) organisent les entrées dans votre base de données. Rechercher dans les entrées Entrer le titre, le nom d’utilisateur ou le contenu des autres champs pour récupérer vos mots de passe. - Déverrouillage de la base de données par empreinte digitale - Associer votre mot de passe à votre empreinte digitale numérisée pour déverrouiller rapidement votre base de données. + Déverrouillage de la base de données par biométrique + Associer votre mot de passe à votre empreinte biométrique numérisée pour déverrouiller rapidement votre base de données. Modifier l’entrée Modifier votre entrée avec des champs personnalisés. La collection de données peut être référencée entre différents champs de l’entrée. Créer un mot de passe fort pour votre entrée. @@ -291,7 +290,7 @@ Twofish ChaCha20 - Fonction de dérivation de clé AES + AES KDF Argon2 5 secondes @@ -333,10 +332,9 @@ Protégé en écriture Ouvrir votre base de données en lecture seule par défaut Protéger en écriture votre base de données - Changer le mode d’ouverture pour la session. -\n -\n« Protégé en écriture » empêche les modifications involontaires de la base de données. -\n + Changer le mode d’ouverture pour la session. +\n +\n« Protégé en écriture » empêche les modifications involontaires de la base de données. \n« Modifiable » vous permet d’ajouter, de supprimer ou de modifier tous les éléments. Modifier l’entrée Impossible de charger votre base de données. @@ -364,7 +362,7 @@ Son au toucher Mode sélection Ne pas tuer l\'application… - Déverouillage du bouton retour + Appuyer sur \"Retour\" pour verouiller Verrouille la base de données lorsque l\'utilisateur clique sur le bouton Précédent de l\'écran racine Suppression à la fermeture Ferme la base de données lors de la fermeture de notification @@ -407,4 +405,51 @@ Biométrique Ouvrir automatiquement la boite de dialogue biométrique Ouvrir automatiquement la boite de dialogue biométrique lorsqu\'une clé biométrique est définie pour une base de données + Clé Maîtresse + Sécurité + Historique + Configuration d\'un mot de passe à usage unique + Type OTP + Secret + Période (Secondes) + Compteur + Chiffres + Algorithme + OTP + Secret OTP invalide. + Au moins un identifiant doit être défini. + Vous ne pouvez pas copier un groupe ici. + La clé secrète doit être au format Base32. + Le compteur doit être entre %1$d et %2$d. + La période doit être entre %1$d et %2$d secondes. + Le token doit contenir de %1$d à %2$d chiffres. + %1$s avec le même UUID %2$s existe déjà. + Création de base de données… + Paramètres de sécurité + Paramètres de clé maîtresse + La base de données contient des UUID en double. + En validant cette boîte de dialogue, KeePass DX corrigera le problème (en générant de nouveaux UUID pour les doublons) et continuera. + Base de données ouverte + Copier les champs de saisie à l\'aide du presse-papiers de votre appareil + Notification persistante + Ajouter une notification lorsque la base de données est ouverte + Utilisez le déverrouillage avancé pour ouvrir plus facilement une base de données + Compression de données + La compression des données réduit la taille de la base de données. + Eléments d\'historique max. + Limiter le nombre d\'éléments d\'historique par entrée + Taille de l\'historique max. + Limiter la taille de l\'historique par entrée (en octets binaires) + Recommander changement + Recommander le changement de clé maîtresse (jours) + Forcer changement + Force le changement de clé maîtresse (jours) + Force changement la prochaine fois + Force le changement de clé maîtresse la prochaine fois (une fois) + Nom d\'utilisateur par défaut + Couleur de base de données personnalisée + Compression + Aucune + GZip + Paramètres clavier de l\'appareil \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 045e125f3..075c1405b 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -1,3 +1,96 @@ - + प्रतिक्रिया + होमपेज + एंड्रॉयड पर आधारित KeePass पासवर्ड मैनेजर + स्वीकार + रिकॉर्ड जोड़ें + रिकॉर्ड संपादित करें + समूह जोड़ें + एन्क्रिप्शन + एन्क्रिप्शन एल्गोरिथम + कुंजी उत्पत्ति कृत्य + ऐप का समय समाप्त + ऐप लॉक होने से पहले निष्क्रियता + ऐप + फिर मत दिखाना + कोष्ठक + विस्तारित ASCII + अनुमति दें + क्लिपबोर्ड साफ कर दिया + क्लिपबोर्ड त्रुटि + सैमसंग के कुछ एंड्रॉइड फोन क्लिपबोर्ड का उपयोग नहीं करने देंगे। + क्लिपबोर्ड को साफ़ नहीं किया जा सका + क्लिपबोर्ड टाइमआउट + क्लिपबोर्ड में भंडारण की अवधि + अब क्लिपबोर्ड को साफ करने के लिए स्वाइप करें + क्लिपबोर्ड पर% 1 $ s को कॉपी करने के लिए चयन करें + डेटाबेस कुंजी पुनर्प्राप्त कर रहा है… + डेटाबेस + डेटाबेस सामग्री डिक्रिप्टिंग… + डिफ़ॉल्ट डेटाबेस के रूप में उपयोग करें + अंक + KeePass DX © %1 $ डी Kunzisoft बिल्कुल कोई वारंटी के साथ आता है । यह लाइब्रे सॉफ्टवेयर है, और जीपीएल संस्करण 3 या बाद की शर्तों के तहत इसे पुनर्वितरित करने के लिए आपका स्वागत है। + एक्सेस किया गया + रद्द करें + टिप्पणियाँ + पासवर्ड की पुष्टि करें + बनाया + समयसीमा समाप्त + कीफाइल + संशोधित + प्रवेश का डाटा नहीं मिल सका। + पासवर्ड + सहेजें + शीर्षक + यू.आर.एल + उपयोगकर्ता का नाम + KeePass DX में इस URI को संभाल नहीं सका। + फाइल नहीं बना सका: + डाटाबेस नहीं पढ़ सका। + सुनिश्चित करें कि रास्ता सही है। + एक नाम दर्ज करें। + कीफाइल का चयन करें। + आपके पूरे डेटाबेस को लोड करने के लिए कोई मेमोरी नहीं। + आपके डेटाबेस को लोड नहीं किया जा सका। + कुंजी लोड नहीं कर सका। केडीएफ \"मेमोरी उपयोग\" को कम करने का प्रयास करें। + कम से कम एक पासवर्ड जनरेशन प्रकार का चयन करना होगा। + पासवर्ड मेल नहीं खाते हैं। + \"परिवर्तन राउंड\" बहुत अधिक है। 2147483648 पर सेट हो रहा है। + प्रत्येक स्ट्रिंग में फ़ील्ड नाम होना चाहिए। + एक शीर्षक जोड़ें। + फ़ाइल खोलें + नोड के बच्चे + नोड जोड़ें + रिकॉर्ड जोड़ें + समूह जोड़ें + फ़ाइल की जानकारी + पासवर्ड चेकबॉक्स + कीफाइल चेकबॉक्स + पासवर्ड दृश्यता टॉगल दोहराएं + प्रवेश आइकन + प्रविष्टि सहेजें + पासवर्ड जनरेटर + पासवर्ड की लंबाई + फ़ील्ड जोड़ें + फ़ील्ड निकालें + UUID + "डेटाबेस फाइल को बनाने खोलने और सेव करने के लिए आपके फोन में फाइल मैनेजर ऐप होना चाहिए जोकि दिए गए एक्शन को सपोर्ट कर सके ACTION_CREATE_DOCUMENT और ACTION_OPEN_DOCUMENT" + पृष्ठभूमि + अपडेट करें + निकालें + बंद क्षेत्र + मास्टर कुंजी + सुरक्षा + इतिहास + सेटअप वन-टाइम पासवर्ड + ओटीपी प्रकार + गुप्त + अवधि (सेकंड) + काउंटर + अंक + एल्गोरिथ्म + OTP + अमान्य ओटीपी गुप्त। + कम से कम एक क्रेडेंशियल सेट किया जाना चाहिए। + \ No newline at end of file diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 72fec885e..9cddb9ed6 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -294,11 +294,9 @@ Delta Delta for å øke stabiliteten, sikkerheten, og med å legge til flere funksjoner. Ulikt mange passordbehandlingsprogrammer, er dette reklamefri, copyleftbasert fri programvare og samler ikke inn personlig data på tjenerne sine, selv i sin gratisversjon. - Ved kjøp av pro-versjonen, vil du få tilgang til denne visuelle funksjonen og du vil spesielt hjelpe realiseringen av gemenskapsprosjekter. - + Ved kjøp av pro-versjonen, vil du få tilgang til denne visuelle funksjonen og du vil spesielt hjelpe realiseringen av gemenskapsprosjekter. Denne visuelle funksjonen er tilgjengelig takket være din generøsitet. - For å beholde vår frihet og alltid være aktive, stoler vi på dine bidrag. - + For å beholde vår frihet og alltid være aktive, stoler vi på dine bidrag. Denne funksjonen er under utvikling og krever bidrag for å bli tilgjengelig snart. Ved å kjøpe pro-versjonen, Ved å bidra, diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index a3a0fa60d..3b7d63224 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -18,8 +18,7 @@ along with KeePass DX. If not, see . Dutch translation by Erik Devriendt, corrected by Erik Jan Meijer ---> - +--> Reacties Website Android-implementatie van de KeePass-wachtwoordbeheerder @@ -240,7 +239,7 @@ Databank vergrendelen als het scherm uitgaat Vingerafdruk Vingerafdrukherkenning - Hiermee wordt je vingerafdruk gebruikt om de databank te openen + Laat u uw biometrische gegevens scannen om de database te openen Sleutels voor versleuteling verwijderen Alle aan vingerafdrukherkenning gerelateerde sleutels wilt verwijderen Weet je zeker dat je alle sleutels van de vingerafdrukherkenning wilt verwijderen\? @@ -296,7 +295,7 @@ Genereer een sterk wachtwoord voor het item. Genereer een sterk wachtwoord om aan het item te koppelen, definieer het eenvoudig volgens de criteria van het formulier en vergeet het veilige wachtwoord niet. Voeg aangepaste velden toe - Je kan een nieuw, aanvullend veld aanmaken en tevens beschermen. + Registreer een aanvullend veld door een nieuw aan te maken en dit tevens te beschermen. Ontgrendel je databank Databank alleen-lezen Wijzig de opstartmodus van de sessie. @@ -357,7 +356,7 @@ Geluid bij toetsaanslag Selectiemodus App niet afsluiten… - Druk op Terug om te vergrendelen + Druk op \'Terug\' om te vergrendelen Vergrendel de databank wanneer de gebruiker op de knop Terug in het hoofdscherm klikt Wissen bij afsluiten Sluit de databank tegelijk met de melding @@ -367,7 +366,7 @@ Wachtwoord wissen Wist het ingevoerde wachtwoord na een verbindingspoging Bestand openen - Items onder het knooppunt + Onderliggende items Knooppunt toevoegen Item toevoegen Groep toevoegen @@ -386,4 +385,65 @@ Je kan hier geen item kopiëren. Het aantal items tonen Toon het aantal items in een groep + Achtergrond + Update + Velden sluiten + Kan geen database aanmaken met dit wachtwoord en sleutelbestand. + Geavanceerd ontgrendelen + Biometrische herkenning opslaan + Databasereferenties opslaan met biometrische gegevens + Database met biometrische herkenning openen + Database-referenties uitpakken met biometrische gegevens + Biometrisch + Automatisch biometrische prompt openen + Biometrische prompt automatisch openen wanneer een biometrische sleutel is gedefinieerd voor een database + Inschakelen + Uitschakelen + Hoofdsleutel + Beveiliging + Geschiedenis + Eenmalig wachtwoord (OTP) instellen + OTP-type + Geheim + Duur (seconden) + Teller + Cijfers + Algoritme + OTP + Ongeldig OTP-geheim. + Tenminste één referentie moet worden ingesteld. + Je kunt hier geen groep kopiëren. + Geheime sleutel moet in Base32-indeling zijn. + Teller moet tussen %1$d en %2$d liggen. + De duur moet tussen %1$d en %2$d seconden liggen. + Token moet %1$d tot %2$d cijfers bevatten. + %1$s met dezelfde UUID %2$s bestaat al. + Database aanmaken … + Beveiligingsinstellingen + Instellingen hoofdsleutel + De database bevat dubbele UUID\'s. + Door dit dialoogvenster te valideren, zal KeePassDX het probleem oplossen (door nieuwe UUID\'s voor duplicaten te genereren) en doorgaan. + Database geopend + Kopieer invoervelden met behulp van het klembord van uw apparaat + Aanhoudende melding + Voeg een melding toe wanneer de database is geopend + Geavanceerde ontgrendeling gebruiken om een database gemakkelijker te openen + Gegevenscompressie + Gegevenscompressie verkleint de omvang van de database. + Max. geschiedenis items + Beperk het aantal geschiedenisitems per item + Max. grootte van de geschiedenis + Grootte van geschiedenis beperken per invoer (in binaire bytes) + Aanbevolen wijzigen + Aanbeveling de hoofdsleutel te wijzigen (dagen) + Wijziging afdwingen + De hoofdsleutel verplicht wijzigen (dagen) + Volgende keer verplicht wijzigen + Het hoofdwachtwoord de volgende keer verplicht wijzigen (eenmalig) + Standaard gebruikersnaam + Aangepaste databasekleur + Compressie + Geen + GZip + Toetsenbordinstellingen voor apparaat \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 4870a7144..c0f99c347 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -16,8 +16,7 @@ You should have received a copy of the GNU General Public License along with KeePass DX. If not, see . ---> - +--> Informacje zwrotne Strona domowa Implementacja Android dla menedżera haseł KeePass @@ -238,7 +237,7 @@ Zablokuj bazę danych, gdy ekran jest wyłączony Odcisk palca Skanowanie odcisków palców - Umożliwia skanowanie odcisku palca w celu otwarcia bazy danych + Umożliwia zeskanowanie danych biometrycznych w celu otwarcia bazy danych Usuń klucze szyfrowania Usuń wszystkie klucze szyfrowania związane z rozpoznawaniem linii papilarnych Czy na pewno chcesz usunąć wszystkie klucze związane z rozpoznawaniem linii papilarnych\? @@ -313,11 +312,9 @@ Weź udział Pomóż zwiększyć stabilność, bezpieczeństwo i dodawanie kolejnych funkcji. W przeciwieństwie do wielu aplikacji do zarządzania hasłami, ta jest wolna od reklam, jest oprogramowaniem darmowym typu copylefted libre i nie zbiera danych osobowych na swoich serwerach, bez względu na to, jakiej wersji używasz. - Kupując wersję pro, będziesz mieć dostęp do tej funkcji wizualnej a szczególnie pomożesz zrealizować projekty społecznościowe. - + Kupując wersję pro, będziesz mieć dostęp do tej funkcji wizualnej a szczególnie pomożesz zrealizować projekty społecznościowe. Ta funkcja wizualna jest dostępna dzięki Twojej hojności. - Aby zachować naszą wolność i być zawsze aktywnym, liczymy na Twój wkład. - + Aby zachować naszą wolność i być zawsze aktywnym, liczymy na Twój wkład. Ta funkcja jest rozwojowa i wymaga twojego wkładu aby być wkrótce dostępną. Kupując wersję pro wersja, Przez przyczynianie się, @@ -354,7 +351,7 @@ Wibracja po naciśnięciu klawisza Dźwięk przy naciśnięciu Nie zabijaj aplikacji… - Naciśnij Wstecz na stronie głównej, aby zablokować + Wciśnij \'Powrót\', aby zablokować Zablokuj bazę danych, gdy użytkownik kliknie przycisk \"Wstecz\" na ekranie głównym Wyczyść po zamknięciu Zamknij bazę danych podczas zamykania powiadomienia @@ -398,4 +395,51 @@ Wyłącz Automatycznie otwieraj monit biometryczny, gdy klucz biometryczny jest zdefiniowany dla bazy danych Węzły podrzędne + Klucz główny + Zabezpieczenia + Historia + Ustaw hasło jednorazowe + Typ OTP + Tajne + Okres (w sekundach) + Licznik + Cyfry + Algorytm + OTP + Nie można tutaj skopiować wpisu. + Tworzenie bazy danych… + Ustawienia zabezpieczeń + Ustawienia klucza głównego + Baza danych zawiera zduplikowane identyfikatory UUID. + Baza danych otwarta + Stałe powiadamianie + Dodaj powiadomienie, gdy baza danych jest otwarta + Kompresja danych + Ogranicz liczbę elementów historii na wpis + Max. rozmiar historii + Zalecane zmiany + Zaleca się zmianę klucza głównego (dni) + Wymuś zmianę klucza głównego (dni) + Domyślna nazwa użytkownika + Niestandardowy kolor bazy danych + Kompresja + Żaden + GZip + Ustawienia klawiatury urządzenia + Nieprawidłowy klucz tajny OTP. + Należy ustawić co najmniej jedno poświadczenie. + Klucz tajny musi być w formacie Base32. + Licznik musi być między %1$d a %2$d. + Okres musi wynosić od %1$d do %2$d sekund. + Token musi zawierać %1$d do %2$d cyfr. + %1$s o tym samym identyfikatorze UUID %2$s już istnieje. + Weryfikując to okno dialogowe, KeePass DX rozwiąże problem (poprzez wygenerowanie nowych UUID dla duplikatów) i będzie kontynuował. + Skopiuj pola wprowadzania danych za pomocą schowka urządzenia + Użyj zaawansowanego odblokowywania w celu łatwiejszego otwierania bazy danych + Kompresja danych zmniejsza rozmiar bazy danych. + Maksymalne pozycje historii + Zmień rozmiar historii na wpis (w bajtach binarnych) + Wymuś zmianę + Wymuś zmianę następnym razem + Wymuś zmianę klucza głównego następnym razem (raz) \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index bbbc7ff16..45a7812af 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -16,8 +16,7 @@ You should have received a copy of the GNU General Public License along with KeePass DX. If not, see . ---> - +--> Comentários Página inicial KeePass DX é uma implementação para Android do gerenciador de senhas KeePass @@ -164,7 +163,7 @@ Você não pode mover um grupo para dentro de si mesmo. Nome do campo Valor do campo - Arquivo não encontrado. Tente reabrí-lo de seu provedor de conteúdo. + Arquivo não encontrado. Tente reabri-lo de seu provedor de conteúdo. Algoritmo errado. Não existem arquivos-chave. O arquivo-chave está vazio. @@ -231,7 +230,7 @@ Bloqueie o banco de dados quando a tela estiver desligada Impressão Digital Escaneamento de impressão digital - Permite que você escaneie sua impressão digital para a abertura do banco de dados + Permite que você escaneie sua biometria para a abertura do banco de dados Apague chaves de encriptação Apague todas as chaves de encriptação relacionadas ao reconhecimento de Impressões digitais Tem certeza que você quer apagar todas as chaves relacionadas ao reconhecimento de Impressões digitais\? @@ -306,12 +305,15 @@ Participar Ajude a aumentar a estabilidade, segurança e na adição de mais recursos. Ao contrário de muitos aplicativos de gerenciamento de senhas, este aplicativo é livre de anúncios, software livre e não recupera dados pessoais em seus servidores, mesmo em sua versão gratuita. - Ao comprar a versão pro, você terá acesso a este recurso visual e ajudará especialmente a realização de projetos comunitários. + Ao comprar a versão pro, você terá acesso a este recurso visual e ajudará especialmente a realização de projetos comunitários. + Este recurso visual está disponível graças à sua generosidade. - Para manter a nossa liberdade e estarmos sempre ativos, nós contamos com a sua contribuição. + Para manter a nossa liberdade e estarmos sempre ativos, nós contamos com a sua contribuição. + Esse recurso está em desenvolvimento e exige que sua contribuição para que esteja disponível em breve. Ao comprar a versão pro, - Contribuindo , + + Contribuindo , Você está incentivando os desenvolvedores a criar novos recursos e a corrigir erros de acordo com suas observações. Obrigado por sua contribuição. Estamos trabalhando duro para lançar esse recurso o mais rápido possível. @@ -353,7 +355,7 @@ Som ao clicar Modo de seleção Não feche o aplicativo… - Pressione Voltar na raiz para trancar + Pressione \'Voltar\' para trancar Tranca a base de dados quando o usuário pressiona o botão Voltar na tela inicial Limpar a área de transferência ao fechar Fecha a base de dados ao dispensar a notificação @@ -396,4 +398,51 @@ Abrir automaticamente o prompt de biometria quando a chave biométrica for definida para o banco de dados Habilitado Desabilitado + Chave mestre + Segurança + Histórico + Configurar TOTP + Tipo OTP + Segredo + Período (segundos) + Contador + Dígitos + Algorítimo + OTP + Segredo OTP inválido. + Ao menos uma credencial deve ser definida. + Você não pode copiar um grupo aqui. + A chave secreta deve estar em formato Base32. + O contador deve estar entre %1$d e %2$d. + O período deve estar entre %1$d e %2$d segundos. + O token deve conter de %1$d até %2$d digitos. + %1$s com o mesmo UUID %2$s já existe. + Criando banco de dados… + Configurações de segurança + Configurações da chave mestre + O banco de dados contém UUIDs duplicados. + Ao validar este diálogo, o KeePass DX irá consertar o problema (gerando um novo UUID para os duplicados) e continuar. + Banco de dados aberto + Copiar campos de entrada usando a área de transferência do seu aparelho + Notificação persistente + Adicionar uma notificação quando o banco de dados for aberto + Usar destravamento avançado para abrir o banco de dados mais facilmente + Compressão dos dados + Compressão dos dados reduz o tamanho do banco de dados. + Máx. itens no histórico + Limitar o número de itens do histórico por entrada + Máx. tamanho do histórico + Limite de tamanho do histórico por entrada (em bytes binários) + Mudança recomendada + Recomendar mudança da chave mestre (em dias) + Forçar mudança + Forçar mudança da chave metre (em dias) + Forçar mudança na próxima vez + Forçar mudança da chave mestre na próxima vez (uma vez) + Usuário padrão + Cor personalizada do banco de dados + Compressão + Nada + GZip + Configurações do teclado do aparelho \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index d153afd78..07ea2b58b 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -87,7 +87,7 @@ Пароль Google Play F-Droid - Неверный пароль или файл ключа. + Неправильный главный пароль или файл ключа. Неправильный алгоритм. Невозможно определить формат базы. Файл ключа не найден. @@ -100,7 +100,7 @@ Скрывать пароли Скрывать пароли за (***) по умолчанию Сведения - Изменить пароль базы + Изменить главный пароль Настройки Настройки базы Удалить @@ -151,13 +151,13 @@ Предоставьте доступ к SD-карте на запись для сохранения изменений в базе. Подключите SD-карту для создания или загрузки базы. Версия %1$s - Отпечатки пальцев поддерживаются, но не настроены. - Ожидание отпечатка пальца + Биометрия поддерживается, но не настроена. + Ожидание биометрического ключа для разблокировки базы Зашифрованный пароль сохранён - Неправильный отпечаток пальца. Восстановите пароль. - Проблема с отпечатком пальца: %1$s - Используйте отпечаток пальца, чтобы сохранить пароль - Для этой базы пароль ещё не сохранён. + Неправильный биометрический ключ. Восстановите главный пароль. + Проблема с биометрическим ключом: %1$s + Используйте биометрический ключ, чтобы сохранить главный пароль + Для этой базы главный пароль ещё не сохранён. Введите пароль и/или файл ключа, чтобы разблокировать базу. \n \nНе забывайте сохранять копию файла базы в безопасном месте после каждого изменения. @@ -184,7 +184,7 @@ %1$s скопировано Заполнение форм Алгоритм шифрования базы для всех данных. - При создании ключа для алгоритма шифрования, пароль базы преобразуется при помощи функции формирования ключа со случайной солью. + При создании ключа для алгоритма шифрования, главный пароль преобразуется при помощи функции формирования ключа со случайной солью. Использование памяти Объём памяти (в байтах), которое будет использоваться функцией формирования ключа. Уровень параллелизма @@ -207,7 +207,7 @@ Переместить Вставить Отмена - Удалить отпечаток + Удалить биометрический ключ Только чтение Чтение и запись Сначала группы @@ -215,7 +215,7 @@ Название записи Вы действительно не хотите использовать пароль для защиты базы\? Вы действительно не хотите использовать ключ шифрования? - Отпечаток пальца не распознан + Биометрический ключ не распознан История Внешний вид Общие @@ -236,17 +236,17 @@ Блокировка экрана Блокировать базу при отключении экрана Отпечаток пальца - Сканирование отпечатка пальца - Включить разблокировку базы с помощью отпечатка пальца + Сканирование биометрического ключа + Включить разблокировку базы при помощи биометрического ключа Удалить ключи шифрования - Удалить все ключи шифрования, связанные с распознаванием отпечатка пальца - Вы уверены, что хотите удалить все ключи, связанные с отпечатком пальца? + Удалить все ключи шифрования, связанные с распознаванием биометрического ключа + Вы уверены, что хотите удалить все ключи, связанные с биометрическим ключом\? Невозможно запустить эту функцию. Ваша версия Android %1$s ниже минимально необходимой %2$s. Соответствующее оборудование не найдено. Имя файла Путь - Установить пароль базы + Установить главный пароль Создать новую базу Байт Путь к файлу @@ -267,8 +267,8 @@ Клавиатура Magikeyboard Активировать пользовательскую клавиатуру для простого заполнения паролей и всех ваших идентификаторов - Разрешить без пароля - Включить кнопку \"Открыть\", если пароль не указан + Разрешить без главного пароля + Включить кнопку \"Открыть\", если главный пароль не указан Только чтение По умолчанию открывать базу только для чтения Экраны обучения @@ -285,13 +285,13 @@ \n \nДобавляйте группы (аналог папок) для организации записей и баз. Легко находите ваши записи - Разблокируйте базу с помощью отпечатка пальца - Установите связь между паролем и отпечатком пальца для быстрой разблокировки базы. + Разблокируйте базу с помощью биометрического ключа + Установите взаимосвязь между паролем и биометрическим ключом для быстрой разблокировки базы. Ищите записи по названию, имени или другим полям для быстрого доступа к своим паролям. Редактируйте записи Редактируйте записи с настраиваемыми полями. Возможны перекрёстные ссылки между полями разных записей. Создайте надёжный пароль для записи. - Создайте надёжный пароль, связанный с записью, легко настраиваемый под критерии формы. И не забудьте пароль от базы. + Создайте надёжный пароль, связанный с записью, легко настраиваемый под критерии формы. И не забудьте главный пароль от базы. Добавляйте настраиваемые поля Зарегистрируйте дополнительное поле, просто заполнив его, добавьте значение и при необходимости защитите. Разблокируйте базу @@ -311,15 +311,12 @@ Участвуйте Примите участие в проекте для повышения стабильности, безопасности и добавления новых возможностей. В отличие от многих приложений управления паролями, это без рекламысвободное программное обеспечение (copyleft) и не хранит ваши личные данные на своих серверах независимо от того, какую версию вы используете. - При покупке Pro-версии вы будете иметь доступ к этим визуальным функциям и особенно поможете реализации общественных проектов. - + При покупке Pro-версии вы будете иметь доступ к этим визуальным функциям и особенно поможете реализации общественных проектов. Эти визуальные функции доступны благодаря вашей щедрости. - Для того, чтобы сохранить нашу независимость и быть всегда активными, мы рассчитываем на ваш вклад. - + Для того, чтобы сохранить нашу независимость и быть всегда активными, мы рассчитываем на ваш вклад. Эта функция находится в разработке и требует вашего участия, чтобы стать доступной в ближайшее время. Покупая Pro-версию, - - Участвуя в проекте, + Участвуя в проекте, вы поощряете разработчиков добавлять новые возможности и исправлять ошибки в соответствии с вашими замечаниями. Спасибо большое за ваш вклад. Мы прилагаем все усилия, чтобы быстро выпустить эту функцию. @@ -353,7 +350,7 @@ Вибрация при нажатии Звук при нажатии Не убивать приложение… - \"Назад\" для блокировки + Нажмите \"Назад\" для блокировки Блокировка базы при нажатии кнопки \"Назад\" на начальном экране Очищать при закрытии Закрывать базу при закрытии уведомления @@ -387,14 +384,61 @@ Закрыть поля Невозможно создать базу с этим паролем и ключевым файлом. Дополнительная разблокировка - Сохранение отпечатка пальца - Сохранять пароль базы отпечатком пальца - Открывать базу отпечатком пальца - Извлекать пароль отпечатком пальца - Отпечаток пальца - Автозапрос отпечатка пальца - Автоматически открывать запрос отпечатка пальца, если отпечаток установлен для базы + Сохранение биометрического ключа + Сохранять главный пароль биометрическим ключом + Открывать базу биометрическим ключом + Извлекать главный пароль биометрическим ключом + Биометрический ключ + Автозапрос биометрического ключа + Автоматически открывать запрос биометрического ключа, если он установлен для базы Включить Отключить Режим выбора + Главный пароль + Безопасность + История + Настройка одноразового пароля + Тип одноразового пароля + Секретный ключ + Время (в секундах) + Счётчик + Цифры + Алгоритм + Одноразовый пароль + Недействительный секретный ключ одноразового пароля. + Должен быть установлен, по крайней мере, один пароль. + Вы не можете копировать группу сюда. + Секретный ключ должен быть в формате BASE32. + Счётчик должен быть в диапазоне между %1$d и %2$d. + Время должно быть в диапазоне между %1$d и %2$d сек. + Токен должен содержать от %1$d до %2$d цифр. + %1$s с таким же UUID %2$s уже существует. + Создание базы… + Настройки безопасности + Настройки главного пароля + База содержит повторяющиеся UUID. + Если вы разрешите, KeePass DX исправит проблему (путём создания новых UUID для дубликатов) и продолжит работу. + База открыта + Копирование полей ввода с помощью буфера обмена устройства + Постоянное уведомление + Показывать уведомление, пока открыта база + Использовать дополнительную разблокировку для более лёгкого открытия базы данных + Сжатие данных + Сжатие данных уменьшает размер базы + Максимум записей в истории + Ограничение числа элементов истории каждой записи + Максимальный размер истории + Ограничение размера истории каждой записи (в байтах) + Рекомендуемая смена + Рекомендовать менять главный пароль (в днях) + Принудительная смена + Принудительно менять главный пароль (в днях) + Принудительная смена в следующий раз + Принудительная смена главного пароля при следующем запуске (один раз) + Имя по умолчанию + Произвольный цвет базы + Сжатие + Нет + GZip + Настройки клавиатуры устройства \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 12edf7f84..5cf59ea62 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -19,31 +19,31 @@ Swedish translation by Niclas Burgren (burgren@gmail.com) --> - Feedback: - Hemsida: - KeePass DX är en Android-implementation av KeePass password manager. + Feedback + Hemsida + Android-implementation av KeePass password manager OK Ny post Ny grupp Krypterings algoritm - Tidsgräns för applikation - Tid innan låsning när applikationen är inaktiv. - Applikation + Tidsgräns för app + Inaktivitet innan appen låses + App Inställningar för applikation Visa inte igen Parenteser - Filhantering kräver Open Intents File Manager, klicka nedan för att installera. Filhanteraren kanske inte fungerar korrekt vid första användningen. - Urklippet är rensat. + Skapa, öppna och spara en databasfil kräver att du installerar en filhanterare som accepterar avsiktlig åtgärd ACTION_CREATE_DOCUMENT och ACTION_OPEN_DOCUMENT + Urklipp har rensats Urklippsfel - Vissa Samsung-telefoner har en bugg som gör att applikationer inte kan kopiera till urklipp. För mer detaljer, gå till: - Urklippet kunde inte rensas + Vissa Samsung Android-telefoner låter inte appar använda Urklipp. + Det gick inte att rensa urklipp Tidsgräns för urklipp - Tiden innan användarnamn och lösenord rensas från urklipp + Lagringens varaktighet i urklippet Kopiera %1$s till urklipp Skapar databasnyckel… Databas Dekrypterar databasinnehåll… - Använda denna databasen som standard + Använd som standarddatabas Siffror KeePass DX \u00A9 %1$d Kunzisoft kommer HELT UTAN GARANTIER; Detta är fri programvara och du är välkommen att distribuera den utifrån villkoren i GPL version 3 eller senare. Ange databasnamn @@ -160,6 +160,29 @@ Medium Stor - Uppdatera post + Redigera post Kryptering - + Nyckelhärledningsfunktion + Utökad ASCII + Tillåt + Svep för att rensa urklipp nu + Öppna fil + Lägg till nod + Lägg till post + Lägg till grupp + Filinformation + Lösenord kryssruta + Nyckelfil kryssruta + Repetera växla lösenordssynlighet + Post ikon + Post spara + Lösenordsgenerator + Lösenords längd + Lägg till fält + Ta bort fält + Bakgrund + Uppdatera + Stäng fält + Huvudnyckel + Säkerhet + \ No newline at end of file diff --git a/app/src/main/res/values-sw600dp-port/dimens.xml b/app/src/main/res/values-sw600dp-port/dimens.xml new file mode 100644 index 000000000..f6bb99ee0 --- /dev/null +++ b/app/src/main/res/values-sw600dp-port/dimens.xml @@ -0,0 +1,4 @@ + + + 0.7 + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 61267754e..5d947edea 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -16,8 +16,7 @@ You should have received a copy of the GNU General Public License along with KeePass DX. If not, see . ---> - +--> Geri Bildirim Ana sayfa KeePass parola yöneticisinin Android uygulaması @@ -220,7 +219,7 @@ Ekran kapalıyken veritabanını kilitle Parmakizi Parmak izi tarama - Veritabanını açmak için parmak izinizi taramanızı sağlar + Veritabanını açmak için biyometriklerinizi taramanızı sağlar Şifreleme anahtarlarını silin Parmak izi tanıma ile ilgili tüm şifreleme anahtarlarını silin Parmak izi tanıma ile ilgili tüm tuşları silmek istediğinizden emin misiniz\? @@ -319,9 +318,9 @@ Özgürlüğümüzü korumak ve daima aktif olmak için katkılarınıza güveniyoruz Bu özellik geliştirme aşamasındadır ve katkılarınızın yakında kullanıma sunulmasını gerektirir. - pro sürümü satın alarak, + Pro sürümü satın alarak, - katkıda bulunarak, + Katkıda bulunarak, Geliştiricilerin yeni özellikler oluşturmasını ve söz konusu hatalara göre hataları düzeltmesini teşvik ediyorsunuz. Katkınız için çok teşekkür ederim. Bu özelliği çabucak yayınlamak için çok çalışıyoruz. @@ -359,7 +358,7 @@ Buraya bir girişi kopyalayamazsınız. Giriş sayısını göster Bir gruptaki girişlerin sayısını göster - Kilitlemek için Root\'a Geri basın + Kilitlemek için \'Geri\'ye basın Geri Dönüşüm Kutusu Giriş seçimi Bir girişi görüntülerken Magikeyboard\'da giriş alanlarını göster @@ -382,4 +381,51 @@ Bir veritabanı için bir biyometrik anahtar tanımlandığında biyometrik komut istemini otomatik olarak aç Etkinleştir Devre dışı + Ana Anahtar + Güvenlik + Geçmiş + Tek Seferlik Parola Ayarla + OTP türü + Sır + Periyot (saniye) + Sayaç + Basamak + Algoritma + OTP + Geçersiz OTP sırrı. + En azından bir kimlik bilgisi ayarlanmalıdır. + Bir grubu buraya kopyalayamazsınız. + Gizli anahtar Base32 formatında olmalıdır. + Sayaç %1$d ile %2$d arasında olmalıdır. + Periyot %1$d ile %2$d saniye arasında olmalıdır. + Belirteç %1$d ile %2$d basamak içermelidir. + %1$s aynı UUID değerine sahip %2$s zaten var. + Veritabanı oluşturuluyor… + Güvenlik ayarları + Ana Anahtar ayarları + Veritabanı tekrarlanan UUID\'ler içermektedir. + Bu iletişim kutusunu doğrulayarak, KeePass DX sorunu çözecek (tekrarlananlar için yeni UUID\'ler oluşturarak) ve devam edecektir. + Veritabanı açıldı + Cihazınızın panosunu kullanarak giriş alanlarını kopyala + Kalıcı bildirim + Veritabanı açıkken bir bildirim ekle + Veritabanını daha kolay açmak için gelişmiş kilit açma özelliğini kullan + Veri sıkıştırma + Veri sıkıştırma veritabanı boyutunu azaltır. + Maks. geçmiş ögeleri + Giriş başına geçmiş öge sayısını sınırla + Maks. geçmiş boyutu + Giriş başına geçmiş boyutunu sınırla (ikili bayt olarak) + Değiştirmeyi öner + Ana anahtarın değiştirilmesini öner (gün) + Değiştirmeyi zorla + Ana anahtarı değiştirmeye zorla (gün) + Bir dahaki sefere değiştirmeye zorla + Bir dahaki sefere ana anahtarı değiştirmeye zorla (bir kez) + Varsayılan kullanıcı adı + Özel veritabanı rengi + Sıkıştırma + Yok + GZip + Cihaz Klavye Ayarları \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 05347382d..a0fe97fce 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -16,8 +16,7 @@ You should have received a copy of the GNU General Public License along with KeePass DX. If not, see . ---> - +--> 反馈 主页 Android平台上的KeePass密码管理器 @@ -179,7 +178,7 @@ 粘贴 取消 显示密码 - 删除保存的指纹 + 删除保存的生物识别密钥 只读 可修改 搜索时忽略备份条目 @@ -205,7 +204,7 @@ 搜索结果 警告 版本 %1$s - 使用指纹保存此密码 + 使用生物识别保存此密码 历史 外观 一般设置 @@ -215,7 +214,7 @@ 剪贴板 剪贴板通知 锁定 - 指纹 + 高级解锁 文件名 路径 创建新数据库 @@ -246,12 +245,12 @@ 确定不用密码解锁保护? 确认使用空密钥吗? 版本 %1$s - 支持但未配置指纹解锁。 - 指纹扫描 + 支持但未配置生物识别。 + 打开生物识别提示以解锁数据库 加密密码已保存 - 不能读取指纹密码,请恢复你的密码。 - 无法识别指纹 - 指纹识别问题:%1$s + 不能读取生物识别密钥,还原你的凭据。 + 无法识别生物识别信息 + 生物识别错误:%1$s 当前数据库无密码。 设为默认填充服务 启用自动填充功能,以便捷地在其他应用中填写信息 @@ -263,11 +262,11 @@ 如自动清空剪切板失败,则请手动清空。 锁定屏幕 当屏幕熄灭时锁定数据库 - 指纹扫描 - 通过扫描指纹解锁数据库 + 生物识别解锁 + 通过生物识别解锁数据库 删除加密密钥 - 删除所有与指纹相关的加密密钥 - 要删除所有指纹密钥吗? + 删除所有与生物相关的加密密钥 + 要删除所有生物识别密钥吗? 无法启动此功能。 你的Android版本 %1$s 无法满足软件对系统版本 %2$s 的要求。 找不到所需的硬件。 @@ -279,7 +278,7 @@ 让剪切板保存条目密码及保密字段 警告:复制密码时密码在剪贴板中,而所有应用都可访问剪切板。因此复制密码时,您设备上的其他应用也能看到密码。 激活自定义键盘以填写密码和所有身份字段 - 对剪贴板的信任 + 信任剪贴板 输入密码专用键盘 输入密码专用键盘(KeePass DX) 输入密码专用键盘设置 @@ -298,7 +297,7 @@ 按键 点击按键时震动 点击按键时的声音 - 允许空主密码 + 允许主密钥为空 未选择验证方式也启用“打开”按钮 新手引导 突出界面元素来学习如何使用本应用 @@ -315,8 +314,8 @@ \n您还可以使用群组来管理数据库中的条目。 搜索条目 输入标题、用户名或其他字段的内容来搜索密码。 - 通过指纹解锁数据库 - 将主密码与指纹关联,以快速解锁数据库。 + 通过生物识别解锁数据库 + 将主密钥与生物识别信息关联,以快速解锁数据库。 编辑此条目 使用自定义字段编辑条。自定义字段可以在不同的条目间引用。 为您的记录创建一个强密码。 @@ -342,7 +341,7 @@ 不同于大多数的密码管理软件,不论您使用哪个版本的KeePass DX,都没有广告,并且免费开源,也不收集任何用户的个人信息。 通过购买高级版本,您将解锁全部主题样式,重要的是,您会为社区项目的实现提供的帮助 主题样式现在已经可用,感谢您的慷慨相助。 - 为继续建设此免费项目,我们需要您的捐助。 + 为继续建设此自由项目,我们需要您的捐助。 这个特性目前仍在开发中,您的捐助将使这个特性在未来变得可用。 通过购买专业版, @@ -353,7 +352,7 @@ 别忘了更新应用。 选择模式 不要终止应用程序… - 在根目录返回以锁定 + 按返回键以锁定 在点按根屏幕上的后退按钮时锁定数据库 关闭应用时清空剪贴板 关闭通知时关闭数据库 @@ -396,4 +395,51 @@ 生物识别密钥已配置时自动打开提示 启用 禁用 + 主密钥 + 安全 + 历史 + 设置一次性密码 + 一次性密码类型 + 密钥 + 时长(秒) + 计数器 + 数字位数 + 算法 + 一次性密码 + 一次性密码密钥错误。 + 至少需要设置一个凭据。 + 不能将群组复制到这里。 + 密钥必须是BASE32格式。 + 计数器必须在%1$d和%2$d之间。 + 时长必须在%1$d秒到%2$d秒之间。 + 令牌必须包括%1$d到%2$d个数字。 + 与%1$s的UUID相同的%2$s已经存在。 + 创建数据库… + 安全设置 + 主密钥设置 + 数据库包含了重复的UUID。 + 通过验证此对话框,KeePass DX将解决这个问题(通过给重复项生成新的UUID)并继续。 + 数据库开启 + 使用您设备的剪贴板来复制输入字段 + 保持通知 + 当数据库打开时产生一条通知 + 使用高级解锁轻松打开数据库 + 数据压缩 + 数据压缩可降低数据库大小。 + 最大历史记录条数 + 限制每个条目的历史记录条数 + 最大历史记录大小 + 限制每个条目的历史记录大小(以字节为单位) + 建议修改 + 建议修改主密钥(以天为单位) + 强制修改 + 强制修改主密钥(以天为单位) + 下次强制修改 + 下次强制修改主密钥(一次) + 默认用户名 + 自定义数据库颜色 + 压缩 + + GZip压缩 + 设备键盘设置 \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a0a211159..850cdf587 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -19,27 +19,27 @@ --> 回饋 首頁 - KeePass DX是兼容KeePass密碼管理軟體的Android版本實現。 + KeePass密碼管理軟體的Android版本。 接受 添加條目 添加群組 加密演算法 應用程式超時 - 應用程式處於非活動時鎖定資料庫。 - 應用程式 + 閒置時間後鎖定資料庫 + 程式 應用程式設定 括弧 流覽檔需要安裝Open Intents File Manager軟體,點下面安裝。由於一些檔管理軟體的差異,在你第一次瀏覽時可能無法正常工作。 剪貼板已清除 剪貼板超時 - 複製用戶名或密碼到剪貼板後清除的時間 + 用戶名或密碼留在剪貼板的時間 複製%1$s到剪貼板 創建資料庫密鑰… 資料庫 解密資料庫內容中… - 使用這作為我的預設資料庫 + 以此作為預設資料庫 數字 - KeePass DX \u00A9 %1$d Kunzisoft;軟體不帶有絕對擔保;是自由軟體,您可在遵循GPL 3或者更高版本的情況下重新發佈。中文簡繁體翻譯:wangkf@gmail.com + KeePass DX © %1$d Kunzisoft;軟體不帶有任何擔保。這是自由軟體,您可在遵循GPL 3或者更高版本的情況下重新發佈。 選擇一個已存在之資料庫 訪問時間 取消 @@ -57,15 +57,15 @@ ArcFour流密碼不被支援。 KeePass DX無法處理此URI。 不能創建檔案: - 無效的資料庫或無法辨識的主密鑰。 - 非法路徑。 - 用戶名是必須的。 - 密碼或者密鑰檔是必須的。 - 這款手機運行記憶體不足而不能解析資料庫。這可能是資料庫太大的緣故。 - 至少必須選擇一個密碼生成類型 + 無法閱讀資料庫。 + 請確保路徑正確。 + 請輸入用戶名。 + 選擇一個密鑰檔。 + 記憶體不足,無法解析資料庫。 + 必須至少選擇一個密碼生成類型。 密碼不正確。 次數太多。最大設置到2147483648。 - 標題為必填。 + 添加標題。 長度欄位輸入一個正整數 檔案管理器 生成密碼 @@ -113,7 +113,7 @@ Root 加密次數 更高級的加密次數對暴力攻擊能提供額外保護,但也會增加讀取和保存的時間。 - 次數 + 加密次數 正在保存資料庫… 空格 搜尋 @@ -146,10 +146,10 @@ 增強的ASCII 允許 剪貼簿錯誤 - 部份 Samsung 手機在複製到剪貼簿時會產生錯誤,詳細內容: - 清除剪貼簿錯誤 - 現在清除剪貼簿 - 自動填入服務無法啟用。 + 部份 Samsung 手機不容許其他程式使用剪貼簿。 + 無法清除剪貼簿 + 滑动即可清除剪贴板 + 無法啟用自動填入服務。 無法移動一個群組至自己本身。 無效的演算法。 密鑰檔不存在。 @@ -169,6 +169,93 @@ KeePass DX 無法存取資料庫所在位置,將以唯讀模式開啟資料庫。 Adnroid 4.4 開始,許多裝置不允許應用程式對 SD 卡進行寫入。 加密 - 金鑰派生函數 + 金鑰推算函數 編輯入口 + 開啟檔案 + 密碼產生器 + UUID + 背景 + 更新 + 關閉欄位 + 安全 + 歷史 + 為生成加密演算法的金鑰,主金鑰使用隨機跳轉金鑰推算函數進行轉換。 + 分配主金鑰 + 允許沒有主金鑰 + 子節點 + 添加節點 + 添加條目 + 添加群组 + 檔案資訊 + 密碼核取方塊 + 金鑰檔案核取方塊 + 重複切換密碼可見性 + 條目圖示 + 條目保存 + 密碼長度 + 添加欄位 + 刪除欄位 + 主密鑰 + 主密鑰設定 + 建議更換 + 建議更改主金鑰(天) + 強制更改 + 強制更改主金鑰(天) + 下次強制更改 + 下次強制更改主金鑰(一次) + 找不到條目數據。 + 每個字串必須具有欄位名稱。 + 欄位名 + 欄位值 + 找不到檔案。嘗試從檔案瀏覽器重新打開它。 + 從搜尋結果中省略\"備份\"和\"回收站\"組 + 最近的檔案歷史記錄 + 記住最近的檔案名 + 用於所有資料的資料庫加密演算法。 + 記憶體使用情況 + 密钥推導函數要使用的記憶體量(以二进制字节为单位)。 + 平行 + 密鑰推導函數使用的平行性程度(即執行緒數)。 + 排序 + 最低的優先↓ + 群組在前面 + 回收箱在末尾 + 標題 + 用戶名 + 建立時間 + 修改時間 + 訪問時間 + 搜尋結果 + 警告 + 創建新資料庫 + 無法載入資料庫。 + 無法載入金鑰。嘗試降低 KDF\"記憶體使用率\"。 + 顯示用戶名 + 在條目清單中顯示使用者名 + 選擇模式 + 不要殺死應用程式… + 不能在此處移動條目。 + 不能在此處複製條目。 + 顯示條目數 + 顯示群組中的條目數 + 無法使用此密碼和金鑰檔創建資料庫。 + 高級解鎖 + 設置一次性密碼 + OTP 類型 + 密碼 + 期間(秒) + 計數器 + 數位 + 算灋 + OTP + 無效 OTP 金鑰。 + 至少需要設置一個憑據。 + 不能在此處複製群組。 + 金鑰必須採用 Base32 格式。 + 計數器必須介於%1$d和%2$d之間。 + 時段必須介於%1$d和%2$d秒之間。 + 正在創建資料庫… + 安全設定 + 資料庫包含重複的 UUID。 + 通過驗證此對話方塊,KeePass DX 將修復問題(通過為重複項生成新的 UUID)並繼續。 \ No newline at end of file diff --git a/app/src/main/res/values/donottranslate.xml b/app/src/main/res/values/donottranslate.xml index b946d1cf0..942dd4d9a 100644 --- a/app/src/main/res/values/donottranslate.xml +++ b/app/src/main/res/values/donottranslate.xml @@ -31,7 +31,6 @@ https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro - http://code.google.com/p/android/issues/detail?id=35732 https://www.keepassdx.com/contribution https://www.keepassdx.com https://github.com/Kunzisoft/KeePassDX/issues @@ -50,6 +49,7 @@ @string/database_file_extension_default + KeePass DX Database - +--> Feedback Homepage Android implementation of the KeePass password manager @@ -40,7 +39,7 @@ Allow Clipboard cleared Clipboard error - Some Samsung Android phones won\'t let apps use the clipboard. + Some devices won\'t let apps use the clipboard. Could not clear clipboard Clipboard timeout Duration of storage in the clipboard @@ -64,7 +63,6 @@ Update Remove Close fields - Select to copy %1$s to clipboard Retrieving database key… @@ -109,7 +107,7 @@ Could not load your database. Could not load the key. Try to lower the KDF \"Memory Usage\". At least one password generation type must be selected. - At lest one credential must be set. + At least one credential must be set. The passwords do not match. \"Transformation rounds\" too high. Setting to 2147483648. Each string must have a field name. @@ -121,7 +119,8 @@ You can not copy an entry here. You can not copy a group here. Unable to create database with this password and key file. - Secret key lust be in Base32 format. + Could not save database. + Secret key must be in Base32 format. Counter must be between %1$d and %2$d. Period must be between %1$d and %2$d seconds. Token must contains %1$d to %2$d digits. @@ -139,7 +138,7 @@ Password Install from F-Droid Install from Play Store - Could not read credentials. + Could not read credentials. If this reoccurs, your database file may be corrupt. Wrong algorithm. %1$s with the same UUID %2$s already exists. Could not recognize the database format. @@ -176,6 +175,7 @@ Cancel Hide password Lock database + Save database Open Search Show password @@ -183,6 +183,7 @@ Go to URL Write-protected Modifiable + Empty Recycle Bin Minus Never No search results @@ -216,6 +217,7 @@ Parallelism Degree of parallelism (i.e. number of threads) used by the key derivation function. Saving database… + Executing the command… Do not kill the app… Space Search @@ -242,21 +244,24 @@ Mount the SD card to create or load a database. Do you really want no password unlocking protection? Are you sure you do not want to use any encryption key? + Are you sure you want to permanently delete the selected nodes? Version %1$s Build %1$s Biometric prompt is supported but not set up. + The keystore is not properly initialized. Open the biometric prompt to unlock the database Open the biometric prompt to store credentials Save biometric recognition - Store database credentials with biometric data + WARNING: The use of biometric recognition does not exempt you from knowing your master password. Open database with biometric recognition Extract database credential with biometric data Encrypted password stored - Could not read biometric key. Restore your credential. + Can not read the biometric key. Please delete it and repeat the biometric recognition procedure. Could not recognize biometric Biometric error: %1$s This database does not have stored credential yet. + Type the password before clicking the biometric button. History Appearance Biometric @@ -279,7 +284,7 @@ Lock Screen lock Lock the database when the screen is off - Press Back on root to lock + Press \'Back\' to lock Lock the database when the user clicks the back button on the root screen Persistent notification Add a notification when the database is open @@ -304,7 +309,8 @@ Data compression Data compression reduces the size of the database. Use recycle bin - Moves groups and entries to \"Recycle bin\" before deleting + Moves groups and entries to \"Recycle bin\" group before deleting + Recycle bin group Max. history items Limit number of history items per entry Max. history size @@ -362,10 +368,12 @@ Sound on keypress Allow no master key Enable the \"Open\" button if no credentials are selected - Write-protected - Open your database read-only by default Delete password Deletes the password entered after a connection attempt + Write-protected + Open your database read-only by default + Auto save database + Automatically save the database after an important action (only in \"Modifiable\" mode) Educational screens Highlight the elements to learn how the app works Reset educational screens diff --git a/app/src/main/res/xml/preferences_application.xml b/app/src/main/res/xml/preferences_application.xml index 4e94562df..995d211ff 100644 --- a/app/src/main/res/xml/preferences_application.xml +++ b/app/src/main/res/xml/preferences_application.xml @@ -28,16 +28,21 @@ android:title="@string/allow_no_password_title" android:summary="@string/allow_no_password_summary" android:defaultValue="@bool/allow_no_password_default"/> + + android:key="@string/enable_auto_save_database_key" + android:title="@string/enable_auto_save_database_title" + android:summary="@string/enable_auto_save_database_summary" + android:defaultValue="@bool/enable_auto_save_database_default"/> diff --git a/app/src/main/res/xml/preferences_database.xml b/app/src/main/res/xml/preferences_database.xml index 42772b401..9dab9f174 100644 --- a/app/src/main/res/xml/preferences_database.xml +++ b/app/src/main/res/xml/preferences_database.xml @@ -74,11 +74,15 @@ android:title="@string/recycle_bin"> + diff --git a/fastlane/metadata/android/en-US/changelogs/25.txt b/fastlane/metadata/android/en-US/changelogs/25.txt new file mode 100644 index 000000000..e8dac0614 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/25.txt @@ -0,0 +1,6 @@ + * Setting for Recycle Bin + * Fix Recycle bin issues + * Fix TOTP + * Fix infinite save + * Fix update group + * Fix OOM \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 99cb2bb6f..dec1ad63f 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -6,7 +6,7 @@ - Compatible with the majority of alternative programs (KeePass, KeePassX, KeePass XC...) - Allows fast copy of fields and opening of URI / URL - 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 - AutoFill and Integration - Field filling keyboard diff --git a/fastlane/metadata/android/fr-FR/changelogs/25.txt b/fastlane/metadata/android/fr-FR/changelogs/25.txt new file mode 100644 index 000000000..b16937389 --- /dev/null +++ b/fastlane/metadata/android/fr-FR/changelogs/25.txt @@ -0,0 +1,6 @@ + * Paramètre pour la corbeille + * Correction de bug pour la corbeille + * Correction TOTP + * Correction sauvegarde infinie + * Correction lors de la mise à jour d'un groupe + * Correction OOM \ No newline at end of file diff --git a/fastlane/metadata/android/fr-FR/full_description.txt b/fastlane/metadata/android/fr-FR/full_description.txt index 4e5fd5975..fcb244300 100644 --- a/fastlane/metadata/android/fr-FR/full_description.txt +++ b/fastlane/metadata/android/fr-FR/full_description.txt @@ -6,7 +6,7 @@ - Compatible avec la majorité des programmes alternatifs (KeePass, KeePassX, KeePass XC...) - Permet la copie rapide de champs et l'ouverture d'URI /URL - Reconnaissance biométrique pour un déblocage rapide (Empreintes digitales / Déverouillage par visage / ...) - - Gestion One-Time Password (HOTP / TOTP) + - Gestion des mots de passe à usage unique (One-Time Password HOTP / TOTP) pour l'authentification à deux facteurs (2FA) - Material design avec thèmes - Remplissage automatique de champs - Clavier de remplissage de champs diff --git a/fastlane/pro/metadata/android/en-US/full_description.txt b/fastlane/pro/metadata/android/en-US/full_description.txt index 83dbbc034..f6997e641 100644 --- a/fastlane/pro/metadata/android/en-US/full_description.txt +++ b/fastlane/pro/metadata/android/en-US/full_description.txt @@ -8,7 +8,8 @@ This pro version is under development, buying it encourages faster developmen - Support for .kdb and .kdbx files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm - Compatible with the majority of alternative programs (KeePass, KeePassX, KeePass XC...) - Allows fast copy of fields and opening of URI / URL - - Fingerprint for fast unlocking + - Biometric recognition for fast unlocking (Fingerprint / Face unlock / ...) + - One-Time Password management (HOTP / TOTP) - Material design with themes - AutoFill and Integration - Field filling keyboard @@ -17,6 +18,7 @@ This pro version is under development, buying it encourages faster developmen Pro Features : - Access to custom themes (including the classic deep black theme) + - TOTP Steam creation - Deleting donation buttons Keepass DX is opensource and ad-free. diff --git a/fastlane/pro/metadata/android/fr-FR/full_description.txt b/fastlane/pro/metadata/android/fr-FR/full_description.txt index 9f42a0192..8a1c4d422 100644 --- a/fastlane/pro/metadata/android/fr-FR/full_description.txt +++ b/fastlane/pro/metadata/android/fr-FR/full_description.txt @@ -8,7 +8,8 @@ Cette version pro est en cours de développement, en l'achetant vous encouragez - Support des fichiers .kdb et .kdbx (version 1 à 4) avec algorithme AES - Twofish - ChaCha20 - Argon2 - Compatible avec la majorité des programmes alternatifs (KeePass, KeePassX, KeePass XC...) - Permet la copie rapide de champs et l'ouverture d'URI /URL - - Empreintes digitales pour un déblocage rapide des bases + - Reconnaissance biométrique pour un déblocage rapide (Empreintes digitales / Déverouillage par visage / ...) + - Gestion de One-Time Password (HOTP / TOTP) - Material design avec thèmes - Remplissage automatique de champs - Clavier de remplissage de champs @@ -17,6 +18,7 @@ Cette version pro est en cours de développement, en l'achetant vous encouragez Fonctionnalités Pro : - Accès aux thèmes personnalisés (y compris le thème noir profond classique) + - Création de TOTP Steam - Suppression des boutons de don Keepass DX est opensource et sans publicité.