Compare commits

...

93 Commits

Author SHA1 Message Date
J-Jamet
d476574d05 Merge branch 'release/2.10.2' 2021-06-02 10:37:21 +02:00
J-Jamet
fe08d034bb Merge branch 'iArchitSharma-patch-1' into develop 2021-05-31 16:16:10 +02:00
J-Jamet
18f4714410 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2021-05-31 16:14:24 +02:00
J-Jamet
1b6c416893 Fix autotype #997 2021-05-31 16:10:31 +02:00
Archit Sharma
6153a28b4b fixed and added some hindi translation 2021-05-31 20:47:20 +07:00
J-Jamet
9574cf16fb Fix custom fields iteration 2021-05-31 15:32:35 +02:00
J-Jamet
d309a67416 Capture exception when restart service 2021-05-31 14:48:59 +02:00
J-Jamet
fb865af088 Capture placeholder exception 2021-05-31 14:45:19 +02:00
Yudong
c1e7039357 Translated using Weblate (Chinese (Simplified))
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/zh_Hans/
2021-05-29 17:34:07 +02:00
C. Rüdinger
0fd3b37641 Translated using Weblate (German)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-05-28 12:34:14 +02:00
VfBFan
cea91f7b2f Translated using Weblate (German)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-05-26 10:34:21 +02:00
Yngvar Skjaldulfsson
3959896832 Translated using Weblate (Spanish)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2021-05-21 15:41:22 +02:00
Joan Jaume Oliver
d55dccdeb1 Translated using Weblate (Spanish)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2021-05-21 07:19:58 +02:00
Yngvar Skjaldulfsson
c46c286b51 Translated using Weblate (Spanish)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2021-05-21 07:19:57 +02:00
zer0-x
aa15d261f3 Translated using Weblate (Arabic)
Currently translated at 65.3% (345 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ar/
2021-05-17 12:32:27 +02:00
Paco Chan
00a32463c7 Translated using Weblate (Spanish)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/es/
2021-05-17 12:32:26 +02:00
Sebastian
dd60ff8b74 Translated using Weblate (Danish)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/da/
2021-05-17 12:32:26 +02:00
Paco Chan
4588611cbf Translated using Weblate (Catalan)
Currently translated at 47.5% (251 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ca/
2021-05-17 12:32:25 +02:00
J-Jamet
1460c1364a Fix search fields references #987 2021-05-11 12:05:09 +02:00
J-Jamet
37f38fe988 Fix fields references #987 2021-05-11 11:49:25 +02:00
J-Jamet
cf025b9135 Update version and CHANGELOG 2021-05-11 11:47:20 +02:00
ssantos
283ff7a280 Translated using Weblate (Portuguese)
Currently translated at 85.9% (454 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2021-05-11 11:34:49 +02:00
Reza Almanda
e668f016b4 Translated using Weblate (Indonesian)
Currently translated at 72.1% (381 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/id/
2021-05-11 11:34:49 +02:00
J-Jamet
256c2c955a Merge tag '2.10.1' into develop
2.10.1
2021-05-10 07:24:57 +02:00
J-Jamet
d560c3e8de Merge branch 'release/2.10.1' 2021-05-10 07:24:42 +02:00
J-Jamet
4f8e8e6669 Upgrade version code for deployment 2021-05-10 06:05:22 +02:00
J-Jamet
7cdc2e0915 Fix custom data item #986 2021-05-10 06:03:12 +02:00
J-Jamet
74b236b317 Fix class cast exception #986 2021-05-09 22:29:46 +02:00
J-Jamet
89ffeaf03b Fix crash with parcelable cast 2021-05-08 12:52:56 +02:00
J-Jamet
8b779a0fca Upgrade version 2021-05-07 17:45:12 +02:00
J-Jamet
4b71dc8445 Merge tag '2.10.0' into develop
2.10.0
2021-05-07 17:36:12 +02:00
J-Jamet
780875d5f2 Merge branch 'release/2.10.0' 2021-05-07 17:36:06 +02:00
J-Jamet
7356d4b0e2 Merge branch 'develop' of https://hosted.weblate.org/git/keepass-dx/strings into translations 2021-05-07 17:08:31 +02:00
J-Jamet
654dea6b7e Manage new database format 4.1 #956 2021-05-07 17:06:41 +02:00
J-Jamet
1204374637 Merge branch 'feature/Database_4.1' into develop 2021-05-07 17:03:12 +02:00
J-Jamet
880fde2148 Change version to 2.10.0 2021-05-07 17:02:57 +02:00
J-Jamet
d776d76100 Fix null context with advanced unlocking 2021-05-07 17:01:54 +02:00
J-Jamet
2a7af826a8 Update CHANGELOG 2021-05-07 10:41:24 +02:00
J-Jamet
9d2fd53073 Fix lock 2021-05-07 08:31:05 +02:00
J-Jamet
edc5985a7e Fix show button consistency #980 2021-05-05 11:01:53 +02:00
C. Rüdinger
96fc79103b Translated using Weblate (German)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-05-04 00:18:35 +02:00
VfBFan
76879f3a73 Translated using Weblate (German)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-05-04 00:18:34 +02:00
J-Jamet
328629fe88 Fix writing previous parent group in 4.0 2021-05-03 15:41:39 +02:00
ssantos
9754535055 Translated using Weblate (Portuguese)
Currently translated at 85.9% (454 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pt/
2021-05-02 22:32:18 +02:00
J. Lavoie
26f701d890 Translated using Weblate (Italian)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/it/
2021-05-02 22:32:17 +02:00
VfBFan
f3df8024e6 Translated using Weblate (German)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-05-02 22:32:17 +02:00
J-Jamet
bc9fdfc7a4 Fix header custom data condition 2021-05-01 18:24:11 +02:00
J-Jamet
e1e37989e4 Fix custom icon condition 2021-05-01 18:06:47 +02:00
J-Jamet
dc67cb1807 Set previous parent group during a move or a deletion 2021-05-01 17:48:48 +02:00
J-Jamet
2e8a2457bc Show custom icon name #976 2021-05-01 14:36:11 +02:00
J-Jamet
6d4398c6fd Remove custom data last modification time recognition in Entry and Group 2021-05-01 13:03:44 +02:00
J-Jamet
9f67ad872d Remove contains parent group as condition to 4.1 migration 2021-05-01 12:58:28 +02:00
J-Jamet
aba396f274 Fix parcelable UUID 2021-04-30 21:57:59 +02:00
J-Jamet
4315f34398 Add previous parent group as condition to migrate to 4.1 2021-04-30 21:46:01 +02:00
J-Jamet
47b456e1ee Custom data refactoring 2021-04-30 21:24:37 +02:00
J-Jamet
529810b2dc Fix tags and implements previous parent group 2021-04-30 21:03:29 +02:00
J-Jamet
6f67b8e788 Fix tags string length representation 2021-04-30 20:10:49 +02:00
J-Jamet
a9c9d12444 Better write XML nodes implementation 2021-04-30 20:03:59 +02:00
J-Jamet
f8428cec61 Better date XML implementation and DeletedObject as parcelable 2021-04-30 19:58:21 +02:00
J-Jamet
ac46bce807 Fix custom data writing 2021-04-30 19:39:57 +02:00
J-Jamet
23e899042d Add custom data objects 2021-04-30 19:23:56 +02:00
J-Jamet
80c4a3c06d Fix custom icon parser 2021-04-30 17:19:39 +02:00
Hosted Weblate
d2a31601ba Merge branch 'origin/develop' into Weblate. 2021-04-30 17:04:40 +02:00
J-Jamet
7ddb4f3486 Fix saving custom icons 2021-04-30 14:57:48 +02:00
J-Jamet
e56da87e0e Add last modification to custom icons 2021-04-30 14:11:48 +02:00
J-Jamet
2e4ebecf67 Add name to custom icons #956 2021-04-30 12:49:36 +02:00
J-Jamet
1b4ccaed91 Manage quality check parameter #956 2021-04-30 12:08:21 +02:00
J-Jamet
1e2d41c7fb Merge branch 'develop' into feature/Database_4.1 2021-04-30 11:41:38 +02:00
J-Jamet
1b2ead054a Upgrade to version 3.0.0 2021-04-30 11:37:32 +02:00
J-Jamet
468c1b95b7 Fix CHANGELOG version 2021-04-30 11:29:58 +02:00
J-Jamet
60d8eff71f Merge tag '2.9.20' into develop
2.9.20
2021-04-30 11:28:16 +02:00
J-Jamet
8baae8b801 Merge branch 'release/2.9.20' 2021-04-30 11:28:03 +02:00
J-Jamet
9f16f26347 skip unchanged fastlane elements to deploy 2021-04-29 22:23:51 +02:00
J-Jamet
69b4cacab4 Upgrade version code to deploy beta 2021-04-29 22:23:13 +02:00
J-Jamet
b74b5040b1 Better timeout setting integration #974 2021-04-29 21:57:25 +02:00
J-Jamet
a28decc854 Fix timeout with 0s #974 2021-04-29 21:45:39 +02:00
J-Jamet
cb59cef1b8 Remove unused dependency 2021-04-29 12:37:19 +02:00
J-Jamet
d9b600466c Rollback ignore accents #945 2021-04-29 12:09:53 +02:00
J-Jamet
4edf2f8cd1 Upgrade CHANGELOG 2021-04-29 11:04:33 +02:00
J-Jamet
c60dfdf0d7 Fix search during a node action #972 2021-04-29 10:58:16 +02:00
J-Jamet
006afc6841 Fix search with non-latin chars #971 2021-04-29 10:44:12 +02:00
J-Jamet
f70879581d Change version to fix bugs 2021-04-29 10:35:34 +02:00
J-Jamet
7a2d2b0376 Add database version 4.1 2021-04-28 16:26:11 +02:00
J-Jamet
b5368fa239 First commit to allow tags in groups 2021-04-28 13:16:09 +02:00
J-Jamet
db1d71af9f Upgrade version 2021-04-28 12:04:22 +02:00
J-Jamet
6b03ef35a6 Merge tag '2.9.19' into develop
2.9.19
2021-04-28 10:01:22 +02:00
Oymate
b32c00f455 Translated using Weblate (Bengali (Bangladesh))
Currently translated at 5.3% (28 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/bn_BD/
2021-04-27 15:32:17 +02:00
Milo Ivir
fbe9fb41ed Translated using Weblate (Croatian)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/hr/
2021-04-27 15:32:17 +02:00
gnu-ewm
cc0a7f7d76 Translated using Weblate (Polish)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/pl/
2021-04-27 15:32:17 +02:00
Y. Sakamoto
db33fc60b9 Translated using Weblate (Japanese)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/ja/
2021-04-27 15:32:16 +02:00
VfBFan
6feaee4f86 Translated using Weblate (German)
Currently translated at 99.8% (527 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-04-27 15:32:16 +02:00
C. Rüdinger
1a27a31a32 Translated using Weblate (German)
Currently translated at 99.8% (527 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/de/
2021-04-27 15:32:15 +02:00
zeritti
5b611e71d5 Translated using Weblate (Czech)
Currently translated at 100.0% (528 of 528 strings)

Translation: KeePassDX/Strings
Translate-URL: https://hosted.weblate.org/projects/keepass-dx/strings/cs/
2021-04-27 15:32:15 +02:00
68 changed files with 1128 additions and 612 deletions

View File

@@ -1,3 +1,20 @@
KeePassDX(2.10.2)
* Fix search fields references #987
* Fix Auto-Types with same key #997
KeePassDX(2.10.1)
* Fix parcelable with custom data #986
KeePassDX(2.10.0)
* Manage new database format 4.1 #956
* Fix show button consistency #980
* Fix persistent notification #979
KeePassDX(2.9.20)
* Fix search with non-latin chars #971
* Fix action mode with search #972 (rollback ignore accents #945)
* Fix timeout with 0s #974
KeePassDX(2.9.19)
* Fix search slowdown #964
* Fix closing notification after lock request #965

View File

@@ -11,8 +11,8 @@ android {
applicationId "com.kunzisoft.keepass"
minSdkVersion 15
targetSdkVersion 30
versionCode = 73
versionName = "2.9.19"
versionCode = 80
versionName = "2.10.2"
multiDexEnabled true
testApplicationId = "com.kunzisoft.keepass.tests"

View File

@@ -382,6 +382,7 @@ class GroupActivity : LockingActivity(),
Log.d(TAG, "setNewIntent: $intentNotNull")
setIntent(intentNotNull)
if (Intent.ACTION_SEARCH == intentNotNull.action) {
finishNodeAction()
// only one instance of search in backstack
deletePreviousSearchGroup()
openGroup(retrieveCurrentGroup(intentNotNull, null), true)

View File

@@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.icon.IconImageDraw
@@ -95,6 +96,12 @@ class IconPickerAdapter<I: IconImageDraw>(val context: Context, private val tint
override fun onBindViewHolder(holder: CustomIconViewHolder, position: Int) {
val icon = iconList[position]
iconDrawableFactory?.assignDatabaseIcon(holder.iconImageView, icon, tintIcon)
icon.getIconImageToDraw().custom.name.let { iconName ->
holder.iconTextView.apply {
text = iconName
visibility = if (iconName.isNotEmpty()) View.VISIBLE else View.GONE
}
}
holder.iconContainerView.isSelected = icon.selected
holder.itemView.setOnClickListener {
iconPickerListener?.onIconClickListener(icon)
@@ -117,5 +124,6 @@ class IconPickerAdapter<I: IconImageDraw>(val context: Context, private val tint
inner class CustomIconViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var iconContainerView: ViewGroup = itemView.findViewById(R.id.icon_container)
var iconImageView: ImageView = itemView.findViewById(R.id.icon_image)
var iconTextView: TextView = itemView.findViewById(R.id.icon_name)
}
}

View File

@@ -124,8 +124,10 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
override fun onResume() {
super.onResume()
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(requireContext())
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(requireContext())
context?.let {
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(it)
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(it)
}
keepConnection = false
}
@@ -175,15 +177,16 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
* Check unlock availability and change the current mode depending of device's state
*/
fun checkUnlockAvailability() {
context?.let { context ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
allowOpenBiometricPrompt = true
if (PreferencesUtil.isBiometricUnlockEnable(requireContext())) {
if (PreferencesUtil.isBiometricUnlockEnable(context)) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.fingerprint)
// biometric not supported (by API level or hardware) so keep option hidden
// or manually disable
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
if (!PreferencesUtil.isAdvancedUnlockEnable(requireContext())
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(context)
if (!PreferencesUtil.isAdvancedUnlockEnable(context)
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
@@ -197,9 +200,9 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
selectMode()
}
}
} else if (PreferencesUtil.isDeviceCredentialUnlockEnable(requireContext())) {
} else if (PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.bolt)
if (AdvancedUnlockManager.isDeviceSecure(requireContext())) {
if (AdvancedUnlockManager.isDeviceSecure(context)) {
selectMode()
} else {
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
@@ -207,6 +210,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
}
}
}
}
@RequiresApi(Build.VERSION_CODES.M)
private fun selectMode() {
@@ -260,7 +264,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
private fun openBiometricSetting() {
mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices...
requireContext().startActivity(Intent(Settings.ACTION_SETTINGS))
context?.startActivity(Intent(Settings.ACTION_SETTINGS))
}
}
@@ -295,9 +299,11 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
setAdvancedUnlockedMessageView("")
context?.let { context ->
mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
requireContext().getString(R.string.credential_before_click_advanced_unlock_button))
context.getString(R.string.credential_before_click_advanced_unlock_button))
}
}
}

View File

@@ -31,41 +31,47 @@ class DeleteNodesRunnable(context: Context,
afterActionNodesFinish: AfterActionNodesFinish)
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
private var mParent: Group? = null
private var mOldParent: Group? = null
private var mCanRecycle: Boolean = false
private var mNodesToDeleteBackup = ArrayList<Node>()
override fun nodeAction() {
foreachNode@ for(currentNode in mNodesToDelete) {
mParent = currentNode.parent
mParent?.touch(modified = false, touchParents = true)
foreachNode@ for(nodeToDelete in mNodesToDelete) {
mOldParent = nodeToDelete.parent
mOldParent?.touch(modified = false, touchParents = true)
when (currentNode.type) {
when (nodeToDelete.type) {
Type.GROUP -> {
val groupToDelete = nodeToDelete as Group
// Create a copy to keep the old ref and remove it visually
mNodesToDeleteBackup.add(Group(currentNode as Group))
mNodesToDeleteBackup.add(Group(groupToDelete))
// Remove Node from parent
mCanRecycle = database.canRecycle(currentNode)
mCanRecycle = database.canRecycle(groupToDelete)
if (mCanRecycle) {
database.recycle(currentNode, context.resources)
groupToDelete.touch(modified = false, touchParents = true)
database.recycle(groupToDelete, context.resources)
groupToDelete.setPreviousParentGroup(mOldParent)
} else {
database.deleteGroup(currentNode)
database.deleteGroup(groupToDelete)
}
}
Type.ENTRY -> {
val entryToDelete = nodeToDelete as Entry
// Create a copy to keep the old ref and remove it visually
mNodesToDeleteBackup.add(Entry(currentNode as Entry))
mNodesToDeleteBackup.add(Entry(entryToDelete))
// Remove Node from parent
mCanRecycle = database.canRecycle(currentNode)
mCanRecycle = database.canRecycle(entryToDelete)
if (mCanRecycle) {
database.recycle(currentNode, context.resources)
entryToDelete.touch(modified = false, touchParents = true)
database.recycle(entryToDelete, context.resources)
entryToDelete.setPreviousParentGroup(mOldParent)
} else {
database.deleteEntry(currentNode)
database.deleteEntry(entryToDelete)
}
// Remove the oldest attachments
currentNode.getAttachments(database.attachmentPool).forEach {
entryToDelete.getAttachments(database.attachmentPool).forEach {
database.removeAttachmentIfNotUsed(it)
}
}
@@ -76,7 +82,7 @@ class DeleteNodesRunnable(context: Context,
override fun nodeFinish(): ActionNodesValues {
if (!result.isSuccess) {
if (mCanRecycle) {
mParent?.let {
mOldParent?.let {
mNodesToDeleteBackup.forEach { backupNode ->
when (backupNode.type) {
Type.GROUP -> {

View File

@@ -52,8 +52,9 @@ class MoveNodesRunnable constructor(
// and if not in the current group
&& groupToMove != mNewParent
&& !mNewParent.isContainedIn(groupToMove)) {
nodeToMove.touch(modified = true, touchParents = true)
groupToMove.touch(modified = true, touchParents = true)
database.moveGroupTo(groupToMove, mNewParent)
groupToMove.setPreviousParentGroup(mOldParent)
} else {
// Only finish thread
setError(MoveGroupDatabaseException())
@@ -66,8 +67,9 @@ class MoveNodesRunnable constructor(
if (mOldParent != mNewParent
// and root can contains entry
&& (mNewParent != database.rootGroup || database.rootCanContainsEntry())) {
nodeToMove.touch(modified = true, touchParents = true)
entryToMove.touch(modified = true, touchParents = true)
database.moveEntryTo(entryToMove, mNewParent)
entryToMove.setPreviousParentGroup(mOldParent)
} else {
// Only finish thread
setError(MoveEntryDatabaseException())

View File

@@ -45,8 +45,8 @@ class EntryCursorKDBX : EntryCursorUUID<EntryKDBX>() {
entry.expires
))
for (element in entry.customFields.entries) {
extraFieldCursor.addExtraField(entryId, element.key, element.value)
entry.doForEachDecodedCustomField { key, value ->
extraFieldCursor.addExtraField(entryId, key, value)
}
entryId++

View File

@@ -42,7 +42,7 @@ class ExtraFieldCursor : MatrixCursor(arrayOf(
}
fun populateExtraFieldInEntry(pwEntry: EntryKDBX) {
pwEntry.putExtraField(getString(getColumnIndex(COLUMN_LABEL)),
pwEntry.putField(getString(getColumnIndex(COLUMN_LABEL)),
ProtectedString(getInt(getColumnIndex(COLUMN_PROTECTION)) > 0,
getString(getColumnIndex(COLUMN_VALUE))))
}

View File

@@ -0,0 +1,66 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.ParcelableUtil
import java.util.*
class CustomData : Parcelable {
private val mCustomDataItems = HashMap<String, CustomDataItem>()
constructor()
constructor(toCopy: CustomData) {
mCustomDataItems.clear()
mCustomDataItems.putAll(toCopy.mCustomDataItems)
}
constructor(parcel: Parcel) {
ParcelableUtil.readStringParcelableMap(parcel, CustomDataItem::class.java)
}
fun get(key: String): CustomDataItem? {
return mCustomDataItems[key]
}
fun put(customDataItem: CustomDataItem) {
mCustomDataItems[customDataItem.key] = customDataItem
}
fun containsItemWithValue(value: String): Boolean {
return mCustomDataItems.any { mapEntry -> mapEntry.value.value.equals(value, true) }
}
fun containsItemWithLastModificationTime(): Boolean {
return mCustomDataItems.any { mapEntry -> mapEntry.value.lastModificationTime != null }
}
fun isNotEmpty(): Boolean {
return mCustomDataItems.isNotEmpty()
}
fun doForEachItems(action: (CustomDataItem) -> Unit) {
for ((_, value) in mCustomDataItems) {
action.invoke(value)
}
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
ParcelableUtil.writeStringParcelableMap(parcel, flags, mCustomDataItems)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<CustomData> {
override fun createFromParcel(parcel: Parcel): CustomData {
return CustomData(parcel)
}
override fun newArray(size: Int): Array<CustomData?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -0,0 +1,43 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
class CustomDataItem : Parcelable {
val key: String
var value: String
var lastModificationTime: DateInstant? = null
constructor(parcel: Parcel) {
key = parcel.readString() ?: ""
value = parcel.readString() ?: ""
lastModificationTime = parcel.readParcelable(DateInstant::class.java.classLoader)
}
constructor(key: String, value: String, lastModificationTime: DateInstant? = null) {
this.key = key
this.value = value
this.lastModificationTime = lastModificationTime
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(key)
parcel.writeString(value)
parcel.writeParcelable(lastModificationTime, flags)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<CustomDataItem> {
override fun createFromParcel(parcel: Parcel): CustomDataItem {
return CustomDataItem(parcel)
}
override fun newArray(size: Int): Array<CustomDataItem?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -42,7 +42,7 @@ import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
@@ -222,7 +222,7 @@ class Database {
// Default compression not necessary if stored in header
mDatabaseKDBX?.let {
return it.compressionAlgorithm == CompressionAlgorithm.GZip
&& it.kdbxVersion.isBefore(FILE_VERSION_32_4)
&& it.kdbxVersion.isBefore(FILE_VERSION_40)
}
return false
}

View File

@@ -55,7 +55,7 @@ class DateInstant : Parcelable {
jDate = Date()
}
protected constructor(parcel: Parcel) {
constructor(parcel: Parcel) {
jDate = parcel.readSerializable() as Date
}

View File

@@ -19,30 +19,37 @@
*/
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.Date
import java.util.UUID
import java.util.*
class DeletedObject {
class DeletedObject : Parcelable {
var uuid: UUID = DatabaseVersioned.UUID_ZERO
private var mDeletionTime: Date? = null
private var mDeletionTime: DateInstant? = null
fun getDeletionTime(): Date {
constructor()
constructor(uuid: UUID, deletionTime: DateInstant = DateInstant()) {
this.uuid = uuid
this.mDeletionTime = deletionTime
}
constructor(parcel: Parcel) {
uuid = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
mDeletionTime = parcel.readParcelable(DateInstant::class.java.classLoader)
}
fun getDeletionTime(): DateInstant {
if (mDeletionTime == null) {
mDeletionTime = Date(System.currentTimeMillis())
mDeletionTime = DateInstant(System.currentTimeMillis())
}
return mDeletionTime!!
}
fun setDeletionTime(deletionTime: Date) {
this.mDeletionTime = deletionTime
}
constructor()
constructor(uuid: UUID, deletionTime: Date = Date()) {
this.uuid = uuid
fun setDeletionTime(deletionTime: DateInstant) {
this.mDeletionTime = deletionTime
}
@@ -59,4 +66,23 @@ class DeletedObject {
override fun hashCode(): Int {
return uuid.hashCode()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(ParcelUuid(uuid), flags)
parcel.writeParcelable(mDeletionTime, flags)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<DeletedObject> {
override fun createFromParcel(parcel: Parcel): DeletedObject {
return DeletedObject(parcel)
}
override fun newArray(size: Int): Array<DeletedObject?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -23,6 +23,7 @@ import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.entry.EntryKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
@@ -114,6 +115,20 @@ class Entry : Node, EntryVersionedInterface<Group> {
entryKDBX?.icon = value
}
var tags: Tags
get() = entryKDBX?.tags ?: Tags()
set(value) {
entryKDBX?.tags = value
}
var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
get() = entryKDBX?.previousParentGroup ?: DatabaseVersioned.UUID_ZERO
private set
fun setPreviousParentGroup(previousParent: Group?) {
entryKDBX?.previousParentGroup = previousParent?.groupKDBX?.id ?: DatabaseVersioned.UUID_ZERO
}
override val type: Type
get() = Type.ENTRY
@@ -268,8 +283,8 @@ class Entry : Node, EntryVersionedInterface<Group> {
fun getExtraFields(): List<Field> {
val extraFields = ArrayList<Field>()
entryKDBX?.let {
for (field in it.customFields) {
extraFields.add(Field(field.key, field.value))
it.doForEachDecodedCustomField { key, value ->
extraFields.add(Field(key, value))
}
}
return extraFields
@@ -279,7 +294,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
* Update or add an extra field to the list (standard or custom)
*/
fun putExtraField(field: Field) {
entryKDBX?.putExtraField(field.name, field.protectedValue)
entryKDBX?.putField(field.name, field.protectedValue)
}
private fun addExtraFields(fields: List<Field>) {
@@ -295,7 +310,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
fun getOtpElement(): OtpElement? {
entryKDBX?.let {
return OtpEntryFields.parseFields { key ->
it.customFields[key]?.toString()
it.getField(key)?.toString()
}
}
return null
@@ -373,10 +388,6 @@ class Entry : Node, EntryVersionedInterface<Group> {
return entryKDBX?.getSize(attachmentPool) ?: 0L
}
fun containsCustomData(): Boolean {
return entryKDBX?.containsCustomData() ?: false
}
/*
------------
Converter

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.database.element
import android.content.Context
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
@@ -134,6 +135,20 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
groupKDBX?.icon = value
}
var tags: Tags
get() = groupKDBX?.tags ?: Tags()
set(value) {
groupKDBX?.tags = value
}
var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
get() = groupKDBX?.previousParentGroup ?: DatabaseVersioned.UUID_ZERO
private set
fun setPreviousParentGroup(previousParent: Group?) {
groupKDBX?.previousParentGroup = previousParent?.groupKDBX?.id ?: DatabaseVersioned.UUID_ZERO
}
override val type: Type
get() = Type.GROUP
@@ -394,10 +409,6 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
groupKDBX?.isExpanded = expanded
}
fun containsCustomData(): Boolean {
return groupKDBX?.containsCustomData() ?: false
}
/*
------------
Converter

View File

@@ -0,0 +1,45 @@
package com.kunzisoft.keepass.database.element
import android.os.Parcel
import android.os.Parcelable
class Tags: Parcelable {
private val mTags = ArrayList<String>()
constructor()
constructor(values: String): this() {
mTags.addAll(values.split(';'))
}
constructor(parcel: Parcel) : this() {
parcel.readStringList(mTags)
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeStringList(mTags)
}
override fun describeContents(): Int {
return 0
}
fun isEmpty(): Boolean {
return mTags.isEmpty()
}
override fun toString(): String {
return mTags.joinToString(";")
}
companion object CREATOR : Parcelable.Creator<Tags> {
override fun createFromParcel(parcel: Parcel): Tags {
return Tags(parcel)
}
override fun newArray(size: Int): Array<Tags?> {
return arrayOfNulls(size)
}
}
}

View File

@@ -1,8 +1,27 @@
package com.kunzisoft.keepass.database.element.binary
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
import java.util.*
class CustomIconPool(binaryCache: BinaryCache) : BinaryPool<UUID>(binaryCache) {
class CustomIconPool(private val binaryCache: BinaryCache) : BinaryPool<UUID>(binaryCache) {
private val customIcons = HashMap<UUID, IconImageCustom>()
fun put(key: UUID? = null,
name: String,
lastModificationTime: DateInstant?,
smallSize: Boolean,
result: (IconImageCustom, BinaryData?) -> Unit) {
val keyBinary = super.put(key) { uniqueBinaryId ->
// Create a byte array for better performance with small data
binaryCache.getBinaryData(uniqueBinaryId, smallSize)
}
val uuid = keyBinary.keys.first()
val customIcon = IconImageCustom(uuid, name, lastModificationTime)
customIcons[uuid] = customIcon
result.invoke(customIcon, keyBinary.binary)
}
override fun findUnusedKey(): UUID {
var newUUID = UUID.randomUUID()
@@ -11,4 +30,14 @@ class CustomIconPool(binaryCache: BinaryCache) : BinaryPool<UUID>(binaryCache) {
}
return newUUID
}
fun any(predicate: (IconImageCustom)-> Boolean): Boolean {
return customIcons.any { predicate(it.value) }
}
fun doForEachCustomIcon(action: (customIcon: IconImageCustom, binary: BinaryData) -> Unit) {
doForEachBinary { key, binary ->
action.invoke(customIcons[key] ?: IconImageCustom(key), binary)
}
}
}

View File

@@ -157,10 +157,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
return this.iconsManager.getIcon(iconId)
}
override fun containsCustomData(): Boolean {
return false
}
override fun isInRecycleBin(group: GroupKDB): Boolean {
var currentGroup: GroupKDB? = group
val currentBackupGroup = backupGroup ?: return false

View File

@@ -32,6 +32,7 @@ import com.kunzisoft.keepass.database.crypto.VariantDictionary
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters
import com.kunzisoft.keepass.database.element.CustomData
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.binary.BinaryData
@@ -45,8 +46,9 @@ import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.database.element.node.NodeVersioned
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.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_31
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_41
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.UnsignedInt
@@ -102,7 +104,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
*/
var isRecycleBinEnabled = true
var recycleBinUUID: UUID = UUID_ZERO
var recycleBinChanged = Date()
var recycleBinChanged = DateInstant()
var entryTemplatesGroup = UUID_ZERO
var entryTemplatesGroupChanged = DateInstant()
var historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS
@@ -111,7 +113,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var lastTopVisibleGroupUUID = UUID_ZERO
var memoryProtection = MemoryProtectionConfig()
val deletedObjects = ArrayList<DeletedObject>()
val customData = HashMap<String, String>()
val customData = CustomData()
var localizedAppName = "KeePassDX"
@@ -128,7 +130,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
*/
constructor(databaseName: String, rootName: String) {
name = databaseName
kdbxVersion = FILE_VERSION_32_3
kdbxVersion = FILE_VERSION_31
val group = createGroup().apply {
title = rootName
icon.standard = getStandardIcon(IconImageStandard.FOLDER_ID)
@@ -139,8 +141,9 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
override val version: String
get() {
val kdbxStringVersion = when(kdbxVersion) {
FILE_VERSION_32_3 -> "3.1"
FILE_VERSION_32_4 -> "4.0"
FILE_VERSION_31 -> "3.1"
FILE_VERSION_40 -> "4.0"
FILE_VERSION_41 -> "4.1"
else -> "UNKNOWN"
}
return "KeePass 2 - KDBX$kdbxStringVersion"
@@ -188,7 +191,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
CompressionAlgorithm.GZip -> {
// Only in databaseV3.1, in databaseV4 the header is zipped during the save
if (kdbxVersion.isBefore(FILE_VERSION_32_4)) {
if (kdbxVersion.isBefore(FILE_VERSION_40)) {
compressAllBinaries()
}
}
@@ -196,7 +199,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
CompressionAlgorithm.GZip -> {
// In databaseV4 the header is zipped during the save, so not necessary here
if (kdbxVersion.isBefore(FILE_VERSION_32_4)) {
if (kdbxVersion.isBefore(FILE_VERSION_40)) {
when (newCompression) {
CompressionAlgorithm.None -> {
decompressAllBinaries()
@@ -314,9 +317,11 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
fun addCustomIcon(customIconId: UUID? = null,
name: String,
lastModificationTime: DateInstant?,
smallSize: Boolean,
result: (IconImageCustom, BinaryData?) -> Unit) {
iconsManager.addCustomIcon(customIconId, smallSize, result)
iconsManager.addCustomIcon(customIconId, name, lastModificationTime, smallSize, result)
}
fun isCustomIconBinaryDuplicate(binary: BinaryData): Boolean {
@@ -327,17 +332,43 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return this.iconsManager.getIcon(iconUuid)
}
fun putCustomData(label: String, value: String) {
this.customData[label] = value
/*
* Search methods
*/
fun getEntryByTitle(title: String, recursionLevel: Int): EntryKDBX? {
return this.entryIndexes.values.find { entry ->
entry.decodeTitleKey(recursionLevel).equals(title, true)
}
}
override fun containsCustomData(): Boolean {
return customData.isNotEmpty()
fun getEntryByUsername(username: String, recursionLevel: Int): EntryKDBX? {
return this.entryIndexes.values.find { entry ->
entry.decodeUsernameKey(recursionLevel).equals(username, true)
}
}
fun getEntryByURL(url: String, recursionLevel: Int): EntryKDBX? {
return this.entryIndexes.values.find { entry ->
entry.decodeUrlKey(recursionLevel).equals(url, true)
}
}
fun getEntryByPassword(password: String, recursionLevel: Int): EntryKDBX? {
return this.entryIndexes.values.find { entry ->
entry.decodePasswordKey(recursionLevel).equals(password, true)
}
}
fun getEntryByNotes(notes: String, recursionLevel: Int): EntryKDBX? {
return this.entryIndexes.values.find { entry ->
entry.decodeNotesKey(recursionLevel).equals(notes, true)
}
}
fun getEntryByCustomData(customDataValue: String): EntryKDBX? {
return entryIndexes.values.find { entry ->
entry.customData.containsValue(customDataValue)
entry.customData.containsItemWithValue(customDataValue)
}
}
@@ -608,14 +639,14 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
}
addGroupTo(recycleBinGroup, rootGroup)
recycleBinUUID = recycleBinGroup.id
recycleBinChanged = recycleBinGroup.lastModificationTime.date
recycleBinChanged = recycleBinGroup.lastModificationTime
}
}
fun removeRecycleBin() {
if (recycleBin != null) {
recycleBinUUID = UUID_ZERO
recycleBinChanged = DateInstant().date
recycleBinChanged = DateInstant()
}
}

View File

@@ -271,26 +271,6 @@ abstract class DatabaseVersioned<
return this.entryIndexes[id]
}
fun getEntryByTitle(title: String): Entry? {
return this.entryIndexes.values.find { entry -> entry.title.equals(title, true) }
}
fun getEntryByUsername(username: String): Entry? {
return this.entryIndexes.values.find { entry -> entry.username.equals(username, true) }
}
fun getEntryByURL(url: String): Entry? {
return this.entryIndexes.values.find { entry -> entry.url.equals(url, true) }
}
fun getEntryByPassword(password: String): Entry? {
return this.entryIndexes.values.find { entry -> entry.password.equals(password, true) }
}
fun getEntryByNotes(notes: String): Entry? {
return this.entryIndexes.values.find { entry -> entry.notes.equals(notes, true) }
}
fun addEntryIndex(entry: Entry) {
val entryId = entry.nodeId
if (entryIndexes.containsKey(entryId)) {
@@ -337,8 +317,6 @@ abstract class DatabaseVersioned<
abstract fun getStandardIcon(iconId: Int): IconImageStandard
abstract fun containsCustomData(): Boolean
fun addGroupTo(newGroup: Group, parent: Group?) {
// Add tree to parent tree
parent?.addChildGroup(newGroup)

View File

@@ -21,8 +21,6 @@ package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.UnsignedInt
class AutoType : Parcelable {
@@ -30,7 +28,7 @@ class AutoType : Parcelable {
var enabled = true
var obfuscationOptions = OBF_OPT_NONE
var defaultSequence = ""
private var windowSeqPairs = LinkedHashMap<String, String>()
private var windowSeqPairs = ArrayList<AutoTypeItem>()
constructor()
@@ -38,16 +36,15 @@ class AutoType : Parcelable {
this.enabled = autoType.enabled
this.obfuscationOptions = autoType.obfuscationOptions
this.defaultSequence = autoType.defaultSequence
for ((key, value) in autoType.windowSeqPairs) {
this.windowSeqPairs[key] = value
}
this.windowSeqPairs.clear()
this.windowSeqPairs.addAll(autoType.windowSeqPairs)
}
constructor(parcel: Parcel) {
this.enabled = parcel.readByte().toInt() != 0
this.obfuscationOptions = UnsignedInt(parcel.readInt())
this.defaultSequence = parcel.readString() ?: defaultSequence
this.windowSeqPairs = ParcelableUtil.readStringParcelableMap(parcel)
parcel.readTypedList(this.windowSeqPairs, AutoTypeItem.CREATOR)
}
override fun describeContents(): Int {
@@ -58,15 +55,43 @@ class AutoType : Parcelable {
dest.writeByte((if (enabled) 1 else 0).toByte())
dest.writeInt(obfuscationOptions.toKotlinInt())
dest.writeString(defaultSequence)
ParcelableUtil.writeStringParcelableMap(dest, windowSeqPairs)
dest.writeTypedList(windowSeqPairs)
}
fun put(key: String, value: String) {
windowSeqPairs[key] = value
fun add(key: String, value: String) {
windowSeqPairs.add(AutoTypeItem(key, value))
}
fun entrySet(): Set<MutableMap.MutableEntry<String, String>> {
return windowSeqPairs.entries
fun doForEachAutoTypeItem(action: (key: String, value: String) -> Unit) {
windowSeqPairs.forEach {
action.invoke(it.key, it.value)
}
}
private data class AutoTypeItem(var key: String, var value: String): Parcelable {
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readString() ?: "") {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(key)
parcel.writeString(value)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<AutoTypeItem> {
override fun createFromParcel(parcel: Parcel): AutoTypeItem {
return AutoTypeItem(parcel)
}
override fun newArray(size: Int): Array<AutoTypeItem?> {
return arrayOfNulls(size)
}
}
}
companion object {

View File

@@ -20,12 +20,15 @@
package com.kunzisoft.keepass.database.element.entry
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.utils.UnsignedLong
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.CustomData
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Tags
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.node.NodeId
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
@@ -33,6 +36,7 @@ import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.node.Type
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.ParcelableUtil
import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.LinkedHashMap
@@ -45,16 +49,20 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
@Transient
private var mDecodeRef = false
var customData = LinkedHashMap<String, String>()
override var usageCount = UnsignedLong(0)
override var locationChanged = DateInstant()
override var customData = CustomData()
var fields = LinkedHashMap<String, ProtectedString>()
var binaries = LinkedHashMap<String, Int>() // Map<Label, PoolId>
var foregroundColor = ""
var backgroundColor = ""
var overrideURL = ""
override var tags = Tags()
override var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
var qualityCheck = true
var autoType = AutoType()
var history = ArrayList<EntryKDBX>()
var additional = ""
var tags = ""
override var expires: Boolean = false
@@ -63,17 +71,18 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
constructor(parcel: Parcel) : super(parcel) {
usageCount = UnsignedLong(parcel.readLong())
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
customData = ParcelableUtil.readStringParcelableMap(parcel)
customData = parcel.readParcelable(CustomData::class.java.classLoader) ?: CustomData()
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
binaries = ParcelableUtil.readStringIntMap(parcel)
foregroundColor = parcel.readString() ?: foregroundColor
backgroundColor = parcel.readString() ?: backgroundColor
overrideURL = parcel.readString() ?: overrideURL
tags = parcel.readParcelable(Tags::class.java.classLoader) ?: tags
previousParentGroup = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
autoType = parcel.readParcelable(AutoType::class.java.classLoader) ?: autoType
parcel.readTypedList(history, CREATOR)
url = parcel.readString() ?: url
additional = parcel.readString() ?: additional
tags = parcel.readString() ?: tags
}
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
@@ -88,17 +97,18 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
super.writeToParcel(dest, flags)
dest.writeLong(usageCount.toKotlinLong())
dest.writeParcelable(locationChanged, flags)
ParcelableUtil.writeStringParcelableMap(dest, customData)
dest.writeParcelable(customData, flags)
ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
ParcelableUtil.writeStringIntMap(dest, binaries)
dest.writeString(foregroundColor)
dest.writeString(backgroundColor)
dest.writeString(overrideURL)
dest.writeParcelable(tags, flags)
dest.writeParcelable(ParcelUuid(previousParentGroup), flags)
dest.writeParcelable(autoType, flags)
dest.writeTypedList(history)
dest.writeString(url)
dest.writeString(additional)
dest.writeString(tags)
}
/**
@@ -109,9 +119,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
super.updateWith(source)
usageCount = source.usageCount
locationChanged = DateInstant(source.locationChanged)
// Add all custom elements in map
customData.clear()
customData.putAll(source.customData)
customData = CustomData(source.customData)
fields.clear()
fields.putAll(source.fields)
binaries.clear()
@@ -119,13 +127,14 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
foregroundColor = source.foregroundColor
backgroundColor = source.backgroundColor
overrideURL = source.overrideURL
tags = source.tags
previousParentGroup = source.previousParentGroup
autoType = AutoType(source.autoType)
history.clear()
if (copyHistory)
history.addAll(source.history)
url = source.url
additional = source.additional
tags = source.tags
}
fun startToManageFieldReferences(database: DatabaseKDBX) {
@@ -218,10 +227,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
fields[STR_NOTES] = ProtectedString(protect, value)
}
override var usageCount = UnsignedLong(0)
override var locationChanged = DateInstant()
fun getSize(attachmentPool: AttachmentPool): Long {
var size = FIXED_LENGTH_SIZE
@@ -233,7 +238,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
size += getAttachmentsSize(attachmentPool)
size += autoType.defaultSequence.length.toLong()
for ((key, value) in autoType.entrySet()) {
autoType.doForEachAutoTypeItem { key, value ->
size += key.length.toLong()
size += value.length.toLong()
}
@@ -243,7 +248,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
}
size += overrideURL.length.toLong()
size += tags.length.toLong()
size += tags.toString().length
return size
}
@@ -260,25 +265,32 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|| key == STR_NOTES)
}
var customFields = LinkedHashMap<String, ProtectedString>()
get() {
field.clear()
for ((key, value) in fields) {
if (!isStandardField(key)) {
field[key] = ProtectedString(value.isProtected, decodeRefKey(mDecodeRef, key, 0))
fun doForEachDecodedCustomField(action: (key: String, value: ProtectedString) -> Unit) {
val iterator = fields.entries.iterator()
while (iterator.hasNext()) {
val mapEntry = iterator.next()
if (!isStandardField(mapEntry.key)) {
action.invoke(mapEntry.key,
ProtectedString(mapEntry.value.isProtected,
decodeRefKey(mDecodeRef, mapEntry.key, 0)
)
)
}
}
return field
}
fun getField(key: String): ProtectedString? {
return fields[key]
}
fun putField(label: String, value: ProtectedString) {
fields[label] = value
}
fun removeAllFields() {
fields.clear()
}
fun putExtraField(label: String, value: ProtectedString) {
fields[label] = value
}
/**
* It's a list because history labels can be defined multiple times
*/
@@ -322,14 +334,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
return size
}
override fun putCustomData(key: String, value: String) {
customData[key] = value
}
override fun containsCustomData(): Boolean {
return customData.isNotEmpty()
}
fun addEntryToHistory(entry: EntryKDBX) {
history.add(entry)
}

View File

@@ -19,16 +19,17 @@
*/
package com.kunzisoft.keepass.database.element.entry
import android.util.Log
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.utils.UuidUtil
import java.util.*
import java.util.concurrent.ConcurrentHashMap
class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) {
// Key : <WantedField>@<SearchIn>:<Text>
// Value : content
private var refsCache: MutableMap<String, String?> = HashMap()
private var refsCache = ConcurrentHashMap<String, String?>()
fun clear() {
refsCache.clear()
@@ -53,6 +54,7 @@ class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) {
&& numberInlineRef <= MAX_INLINE_REF) {
numberInlineRef++
try {
textValue = fillReferencesUsingCache(textValue)
val start = textValue.indexOf(STR_REF_START, offset, true)
@@ -68,9 +70,9 @@ class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) {
val fullReference = "$STR_REF_START$reference$STR_REF_END"
if (!refsCache.containsKey(fullReference)) {
val result = findReferenceTarget(reference)
val entryFound = result.entry
val newRecursionLevel = recursionLevel + 1
val result = findReferenceTarget(reference, newRecursionLevel)
val entryFound = result.entry
val data: String? = when (result.wanted) {
'T' -> entryFound?.decodeTitleKey(newRecursionLevel)
'U' -> entryFound?.decodeUsernameKey(newRecursionLevel)
@@ -85,20 +87,23 @@ class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) {
}
offset = end
} catch (e: Exception) {
Log.e(TAG, "Error when fill placeholders by reference", e)
}
}
return textValue
}
private fun fillReferencesUsingCache(text: String): String {
var newText = text
for ((key, value) in refsCache) {
refsCache.keys.forEach { key ->
// Replace by key if value not found
newText = newText.replace(key, value ?: key, true)
newText = newText.replace(key, refsCache[key] ?: key, true)
}
return newText
}
private fun findReferenceTarget(reference: String): TargetResult {
private fun findReferenceTarget(reference: String, recursionLevel: Int): TargetResult {
val targetResult = TargetResult(null, 'J')
@@ -116,11 +121,11 @@ class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) {
val searchIn = Character.toUpperCase(reference[2])
val searchQuery = reference.substring(4)
targetResult.entry = when (searchIn) {
'T' -> mDatabase.getEntryByTitle(searchQuery)
'U' -> mDatabase.getEntryByUsername(searchQuery)
'A' -> mDatabase.getEntryByURL(searchQuery)
'P' -> mDatabase.getEntryByPassword(searchQuery)
'N' -> mDatabase.getEntryByNotes(searchQuery)
'T' -> mDatabase.getEntryByTitle(searchQuery, recursionLevel)
'U' -> mDatabase.getEntryByUsername(searchQuery, recursionLevel)
'A' -> mDatabase.getEntryByURL(searchQuery, recursionLevel)
'P' -> mDatabase.getEntryByPassword(searchQuery, recursionLevel)
'N' -> mDatabase.getEntryByNotes(searchQuery, recursionLevel)
'I' -> {
UuidUtil.fromHexString(searchQuery)?.let { uuid ->
mDatabase.getEntryById(NodeIdUUID(uuid))
@@ -139,5 +144,7 @@ class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) {
private const val MAX_INLINE_REF = 10
private const val STR_REF_START = "{REF:"
private const val STR_REF_END = "}"
private val TAG = FieldReferencesEngine::class.java.name
}
}

View File

@@ -20,8 +20,11 @@
package com.kunzisoft.keepass.database.element.group
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.CustomData
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Tags
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.node.NodeId
@@ -33,14 +36,17 @@ import java.util.*
class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
private val customData = HashMap<String, String>()
override var usageCount = UnsignedLong(0)
override var locationChanged = DateInstant()
override var customData = CustomData()
var notes = ""
var isExpanded = true
var defaultAutoTypeSequence = ""
var enableAutoType: Boolean? = null
var enableSearching: Boolean? = null
var lastTopVisibleEntry: UUID = DatabaseVersioned.UUID_ZERO
override var tags = Tags()
override var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
override var expires: Boolean = false
@@ -60,7 +66,7 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
constructor(parcel: Parcel) : super(parcel) {
usageCount = UnsignedLong(parcel.readLong())
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
// TODO customData = ParcelableUtil.readStringParcelableMap(parcel);
customData = parcel.readParcelable(CustomData::class.java.classLoader) ?: CustomData()
notes = parcel.readString() ?: notes
isExpanded = parcel.readByte().toInt() != 0
defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
@@ -69,6 +75,8 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
val isSearchingEnabled = parcel.readInt()
enableSearching = if (isSearchingEnabled == -1) null else isSearchingEnabled == 1
lastTopVisibleEntry = parcel.readSerializable() as UUID
tags = parcel.readParcelable(Tags::class.java.classLoader) ?: tags
previousParentGroup = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
}
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
@@ -83,13 +91,15 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
super.writeToParcel(dest, flags)
dest.writeLong(usageCount.toKotlinLong())
dest.writeParcelable(locationChanged, flags)
// TODO ParcelableUtil.writeStringParcelableMap(dest, customData);
dest.writeParcelable(customData, flags)
dest.writeString(notes)
dest.writeByte((if (isExpanded) 1 else 0).toByte())
dest.writeString(defaultAutoTypeSequence)
dest.writeInt(if (enableAutoType == null) -1 else if (enableAutoType!!) 1 else 0)
dest.writeInt(if (enableSearching == null) -1 else if (enableSearching!!) 1 else 0)
dest.writeSerializable(lastTopVisibleEntry)
dest.writeParcelable(tags, flags)
dest.writeParcelable(ParcelUuid(previousParentGroup), flags)
}
fun updateWith(source: GroupKDBX) {
@@ -97,34 +107,21 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
usageCount = source.usageCount
locationChanged = DateInstant(source.locationChanged)
// Add all custom elements in map
customData.clear()
for ((key, value) in source.customData) {
customData[key] = value
}
customData = CustomData(source.customData)
notes = source.notes
isExpanded = source.isExpanded
defaultAutoTypeSequence = source.defaultAutoTypeSequence
enableAutoType = source.enableAutoType
enableSearching = source.enableSearching
lastTopVisibleEntry = source.lastTopVisibleEntry
tags = source.tags
previousParentGroup = source.previousParentGroup
}
override var usageCount = UnsignedLong(0)
override var locationChanged = DateInstant()
override fun afterAssignNewParent() {
locationChanged = DateInstant()
}
override fun putCustomData(key: String, value: String) {
customData[key] = value
}
override fun containsCustomData(): Boolean {
return customData.isNotEmpty()
}
companion object {
@JvmField

View File

@@ -22,27 +22,38 @@ package com.kunzisoft.keepass.database.element.icon
import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.*
class IconImageCustom : IconImageDraw {
var uuid: UUID
val uuid: UUID
var name: String = ""
var lastModificationTime: DateInstant? = null
constructor() {
uuid = DatabaseVersioned.UUID_ZERO
constructor(name: String = "", lastModificationTime: DateInstant? = null) {
this.uuid = DatabaseVersioned.UUID_ZERO
this.name = name
this.lastModificationTime = lastModificationTime
}
constructor(uuid: UUID) {
constructor(uuid: UUID, name: String = "", lastModificationTime: DateInstant? = null) {
this.uuid = uuid
this.name = name
this.lastModificationTime = lastModificationTime
}
constructor(parcel: Parcel) {
uuid = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
name = parcel.readString() ?: name
lastModificationTime = parcel.readParcelable(DateInstant::class.java.classLoader)
}
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(ParcelUuid(uuid), flags)
dest.writeString(name)
dest.writeParcelable(lastModificationTime, flags)
}
override fun describeContents(): Int {

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.database.element.icon
import android.util.Log
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.binary.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.CustomIconPool
@@ -27,7 +28,7 @@ import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.K
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
import java.util.*
class IconsManager(private val binaryCache: BinaryCache) {
class IconsManager(binaryCache: BinaryCache) {
private val standardCache = List(NB_ICONS) {
IconImageStandard(it)
@@ -52,17 +53,15 @@ class IconsManager(private val binaryCache: BinaryCache) {
fun buildNewCustomIcon(key: UUID? = null,
result: (IconImageCustom, BinaryData?) -> Unit) {
// Create a binary file for a brand new custom icon
addCustomIcon(key, false, result)
addCustomIcon(key, "", null, false, result)
}
fun addCustomIcon(key: UUID? = null,
name: String,
lastModificationTime: DateInstant?,
smallSize: Boolean,
result: (IconImageCustom, BinaryData?) -> Unit) {
val keyBinary = customCache.put(key) { uniqueBinaryId ->
// Create a byte array for better performance with small data
binaryCache.getBinaryData(uniqueBinaryId, smallSize)
}
result.invoke(IconImageCustom(keyBinary.keys.first()), keyBinary.binary)
customCache.put(key, name, lastModificationTime, smallSize, result)
}
fun getIcon(iconUuid: UUID): IconImageCustom {
@@ -88,8 +87,12 @@ class IconsManager(private val binaryCache: BinaryCache) {
}
fun doForEachCustomIcon(action: (IconImageCustom, BinaryData) -> Unit) {
customCache.doForEachBinary { key, binary ->
action.invoke(IconImageCustom(key), binary)
customCache.doForEachCustomIcon(action)
}
fun containsCustomIconWithNameOrLastModificationTime(): Boolean {
return customCache.any { customIcon ->
customIcon.name.isNotEmpty() || customIcon.lastModificationTime != null
}
}

View File

@@ -19,17 +19,17 @@
*/
package com.kunzisoft.keepass.database.element.node
import com.kunzisoft.keepass.database.element.CustomData
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.Tags
import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.*
interface NodeKDBXInterface : NodeTimeInterface {
var usageCount: UnsignedLong
var locationChanged: DateInstant
fun putCustomData(key: String, value: String)
fun containsCustomData(): Boolean
var customData: CustomData
var tags: Tags
var previousParentGroup: UUID
}

View File

@@ -25,15 +25,7 @@ import com.kunzisoft.keepass.database.element.icon.IconImage
interface NodeVersionedInterface<ParentGroup> : NodeTimeInterface, Parcelable {
var title: String
/**
* @return Visual icon
*/
var icon: IconImage
/**
* @return Type of Node
*/
val type: Type
/**

View File

@@ -19,9 +19,9 @@
*/
package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.keepass.database.crypto.VariantDictionary
import com.kunzisoft.keepass.database.crypto.kdf.AesKdf
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
@@ -91,41 +91,64 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
this.masterSeed = ByteArray(32)
}
private inner class NodeHasCustomData<T: NodeKDBXInterface> : NodeHandler<T>() {
internal var containsCustomData = false
private open class NodeOperationHandler<T: NodeKDBXInterface> : NodeHandler<T>() {
var containsCustomData = false
override fun operate(node: T): Boolean {
if (node.containsCustomData()) {
if (node.customData.isNotEmpty()) {
containsCustomData = true
return false
}
return true
}
}
private fun getMinKdbxVersion(databaseV4: DatabaseKDBX): UnsignedInt {
private inner class EntryOperationHandler: NodeOperationHandler<EntryKDBX>() {
var passwordQualityEstimationDisabled = false
override fun operate(node: EntryKDBX): Boolean {
if (!node.qualityCheck) {
passwordQualityEstimationDisabled = true
}
return super.operate(node)
}
}
private inner class GroupOperationHandler: NodeOperationHandler<GroupKDBX>() {
var containsTags = false
override fun operate(node: GroupKDBX): Boolean {
if (!node.tags.isEmpty())
containsTags = true
return super.operate(node)
}
}
private fun getMinKdbxVersion(databaseKDBX: DatabaseKDBX): UnsignedInt {
val entryHandler = EntryOperationHandler()
val groupHandler = GroupOperationHandler()
databaseKDBX.rootGroup?.doForEachChildAndForIt(entryHandler, groupHandler)
// https://keepass.info/help/kb/kdbx_4.1.html
val containsGroupWithTag = groupHandler.containsTags
val containsEntryWithPasswordQualityEstimationDisabled = entryHandler.passwordQualityEstimationDisabled
val containsCustomIconWithNameOrLastModificationTime = databaseKDBX.iconsManager.containsCustomIconWithNameOrLastModificationTime()
val containsHeaderCustomDataWithLastModificationTime = databaseKDBX.customData.containsItemWithLastModificationTime()
// https://keepass.info/help/kb/kdbx_4.html
// If AES is not use, it's at least 4.0
val kdfIsNotAes = databaseKDBX.kdfParameters?.uuid != AesKdf.CIPHER_UUID
val containsHeaderCustomData = databaseKDBX.customData.isNotEmpty()
val containsNodeCustomData = entryHandler.containsCustomData || groupHandler.containsCustomData
// Return v4 if AES is not use
if (databaseV4.kdfParameters != null
&& databaseV4.kdfParameters!!.uuid != AesKdf.CIPHER_UUID) {
return FILE_VERSION_32_4
}
if (databaseV4.rootGroup == null) {
return FILE_VERSION_32_3
}
val entryHandler = NodeHasCustomData<EntryKDBX>()
val groupHandler = NodeHasCustomData<GroupKDBX>()
databaseV4.rootGroup?.doForEachChildAndForIt(entryHandler, groupHandler)
return if (databaseV4.containsCustomData()
|| entryHandler.containsCustomData
|| groupHandler.containsCustomData) {
FILE_VERSION_32_4
// Check each condition to determine version
return if (containsGroupWithTag
|| containsEntryWithPasswordQualityEstimationDisabled
|| containsCustomIconWithNameOrLastModificationTime
|| containsHeaderCustomDataWithLastModificationTime) {
FILE_VERSION_41
} else if (kdfIsNotAes
|| containsHeaderCustomData
|| containsNodeCustomData) {
FILE_VERSION_40
} else {
FILE_VERSION_32_3
FILE_VERSION_31
}
}
@@ -167,7 +190,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
private fun readHeaderField(dis: InputStream): Boolean {
val fieldID = dis.read().toByte()
val fieldSize: Int = if (version.isBefore(FILE_VERSION_32_4)) {
val fieldSize: Int = if (version.isBefore(FILE_VERSION_40)) {
dis.readBytes2ToUShort()
} else {
dis.readBytes4ToUInt().toKotlinInt()
@@ -194,20 +217,20 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData
PwDbHeaderV4Fields.TransformSeed -> if (version.isBefore(FILE_VERSION_32_4))
PwDbHeaderV4Fields.TransformSeed -> if (version.isBefore(FILE_VERSION_40))
transformSeed = fieldData
PwDbHeaderV4Fields.TransformRounds -> if (version.isBefore(FILE_VERSION_32_4))
PwDbHeaderV4Fields.TransformRounds -> if (version.isBefore(FILE_VERSION_40))
setTransformRound(fieldData)
PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.isBefore(FILE_VERSION_32_4))
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.isBefore(FILE_VERSION_40))
innerRandomStreamKey = fieldData
PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.isBefore(FILE_VERSION_32_4))
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.isBefore(FILE_VERSION_40))
setRandomStreamID(fieldData)
PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData)
@@ -283,7 +306,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
*/
private fun validVersion(version: UnsignedInt): Boolean {
return version.toKotlinInt() and FILE_VERSION_CRITICAL_MASK.toKotlinInt() <=
FILE_VERSION_32_4.toKotlinInt() and FILE_VERSION_CRITICAL_MASK.toKotlinInt()
FILE_VERSION_40.toKotlinInt() and FILE_VERSION_CRITICAL_MASK.toKotlinInt()
}
companion object {
@@ -292,8 +315,9 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
val DBSIG_2 = UnsignedInt(-0x4ab40499)
private val FILE_VERSION_CRITICAL_MASK = UnsignedInt(-0x10000)
val FILE_VERSION_32_3 = UnsignedInt(0x00030001)
val FILE_VERSION_32_4 = UnsignedInt(0x00040000)
val FILE_VERSION_31 = UnsignedInt(0x00030001)
val FILE_VERSION_40 = UnsignedInt(0x00040000)
val FILE_VERSION_41 = UnsignedInt(0x00040001)
fun getCompressionFromFlag(flag: UnsignedInt): CompressionAlgorithm? {
return when (flag.toKotlinInt()) {

View File

@@ -79,8 +79,10 @@ object DatabaseKDBXXML {
const val ElemFgColor = "ForegroundColor"
const val ElemBgColor = "BackgroundColor"
const val ElemOverrideUrl = "OverrideURL"
const val ElemQualityCheck = "QualityCheck"
const val ElemTimes = "Times"
const val ElemTags = "Tags"
const val ElemPreviousParentGroup = "PreviousParentGroup"
const val ElemCreationTime = "CreationTime"
const val ElemLastModTime = "LastModificationTime"

View File

@@ -26,9 +26,7 @@ import com.kunzisoft.keepass.database.crypto.CipherEngine
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.HmacBlock
import com.kunzisoft.keepass.database.element.Attachment
import com.kunzisoft.keepass.database.element.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.*
import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.LoadedKey
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
@@ -42,7 +40,7 @@ import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.database.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.HashedBlockInputStream
@@ -88,9 +86,12 @@ class DatabaseInputKDBX(cacheDirectory: File,
private var ctxHistoryBase: EntryKDBX? = null
private var ctxDeletedObject: DeletedObject? = null
private var customIconID = DatabaseVersioned.UUID_ZERO
private var customIconName: String = ""
private var customIconLastModificationTime: DateInstant? = null
private var customIconData: ByteArray? = null
private var customDataKey: String? = null
private var customDataValue: String? = null
private var customDataLastModificationTime: DateInstant? = null
private var groupCustomDataKey: String? = null
private var groupCustomDataValue: String? = null
private var entryCustomDataKey: String? = null
@@ -161,7 +162,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
}
val plainInputStream: InputStream
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_40)) {
val dataDecrypted = CipherInputStream(databaseInputStream, cipher)
val storedStartBytes: ByteArray?
@@ -210,7 +211,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
else -> plainInputStream
}
if (!mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
if (!mDatabase.kdbxVersion.isBefore(FILE_VERSION_40)) {
readInnerHeader(inputStreamXml, header)
}
@@ -386,25 +387,25 @@ class DatabaseInputKDBX(cacheDirectory: File,
}
}
} else if (name.equals(DatabaseKDBXXML.ElemSettingsChanged, ignoreCase = true)) {
mDatabase.settingsChanged = readPwTime(xpp)
mDatabase.settingsChanged = readDateInstant(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemDbName, ignoreCase = true)) {
mDatabase.name = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemDbNameChanged, ignoreCase = true)) {
mDatabase.nameChanged = readPwTime(xpp)
mDatabase.nameChanged = readDateInstant(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemDbDesc, ignoreCase = true)) {
mDatabase.description = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemDbDescChanged, ignoreCase = true)) {
mDatabase.descriptionChanged = readPwTime(xpp)
mDatabase.descriptionChanged = readDateInstant(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemDbDefaultUser, ignoreCase = true)) {
mDatabase.defaultUserName = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemDbDefaultUserChanged, ignoreCase = true)) {
mDatabase.defaultUserNameChanged = readPwTime(xpp)
mDatabase.defaultUserNameChanged = readDateInstant(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemDbColor, ignoreCase = true)) {
mDatabase.color = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemDbMntncHistoryDays, ignoreCase = true)) {
mDatabase.maintenanceHistoryDays = readUInt(xpp, DEFAULT_HISTORY_DAYS)
} else if (name.equals(DatabaseKDBXXML.ElemDbKeyChanged, ignoreCase = true)) {
mDatabase.keyLastChanged = readPwTime(xpp)
mDatabase.keyLastChanged = readDateInstant(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemDbKeyChangeRec, ignoreCase = true)) {
mDatabase.keyChangeRecDays = readLong(xpp, -1)
} else if (name.equals(DatabaseKDBXXML.ElemDbKeyChangeForce, ignoreCase = true)) {
@@ -420,11 +421,11 @@ class DatabaseInputKDBX(cacheDirectory: File,
} else if (name.equals(DatabaseKDBXXML.ElemRecycleBinUuid, ignoreCase = true)) {
mDatabase.recycleBinUUID = readUuid(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemRecycleBinChanged, ignoreCase = true)) {
mDatabase.recycleBinChanged = readTime(xpp)
mDatabase.recycleBinChanged = readDateInstant(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemEntryTemplatesGroup, ignoreCase = true)) {
mDatabase.entryTemplatesGroup = readUuid(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemEntryTemplatesGroupChanged, ignoreCase = true)) {
mDatabase.entryTemplatesGroupChanged = readPwTime(xpp)
mDatabase.entryTemplatesGroupChanged = readDateInstant(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemHistoryMaxItems, ignoreCase = true)) {
mDatabase.historyMaxItems = readInt(xpp, -1)
} else if (name.equals(DatabaseKDBXXML.ElemHistoryMaxSize, ignoreCase = true)) {
@@ -468,6 +469,10 @@ class DatabaseInputKDBX(cacheDirectory: File,
if (strData.isNotEmpty()) {
customIconData = Base64.decode(strData, BASE_64_FLAG)
}
} else if (name.equals(DatabaseKDBXXML.ElemName, ignoreCase = true)) {
customIconName = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemLastModTime, ignoreCase = true)) {
customIconLastModificationTime = readDateInstant(xpp)
} else {
readUnknown(xpp)
}
@@ -488,6 +493,8 @@ class DatabaseInputKDBX(cacheDirectory: File,
customDataKey = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemValue, ignoreCase = true)) {
customDataValue = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemLastModTime, ignoreCase = true)) {
customDataLastModificationTime = readDateInstant(xpp)
} else {
readUnknown(xpp)
}
@@ -518,6 +525,10 @@ class DatabaseInputKDBX(cacheDirectory: File,
ctxGroup?.icon?.standard = mDatabase.getStandardIcon(readUInt(xpp, UnsignedInt(0)).toKotlinInt())
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
ctxGroup?.icon?.custom = mDatabase.getCustomIcon(readUuid(xpp))
} else if (name.equals(DatabaseKDBXXML.ElemTags, ignoreCase = true)) {
ctxGroup?.tags = readTags(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemPreviousParentGroup, ignoreCase = true)) {
ctxGroup?.previousParentGroup = readUuid(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) {
return switchContext(ctx, KdbContext.GroupTimes, xpp)
} else if (name.equals(DatabaseKDBXXML.ElemIsExpanded, ignoreCase = true)) {
@@ -562,6 +573,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
KdbContext.GroupCustomDataItem -> when {
name.equals(DatabaseKDBXXML.ElemKey, ignoreCase = true) -> groupCustomDataKey = readString(xpp)
name.equals(DatabaseKDBXXML.ElemValue, ignoreCase = true) -> groupCustomDataValue = readString(xpp)
name.equals(DatabaseKDBXXML.ElemLastModTime, ignoreCase = true) -> readDateInstant(xpp) // Ignore
else -> readUnknown(xpp)
}
@@ -578,8 +590,12 @@ class DatabaseInputKDBX(cacheDirectory: File,
ctxEntry?.backgroundColor = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemOverrideUrl, ignoreCase = true)) {
ctxEntry?.overrideURL = readString(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemQualityCheck, ignoreCase = true)) {
ctxEntry?.qualityCheck = readBool(xpp, true)
} else if (name.equals(DatabaseKDBXXML.ElemTags, ignoreCase = true)) {
ctxEntry?.tags = readString(xpp)
ctxEntry?.tags = readTags(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemPreviousParentGroup, ignoreCase = true)) {
ctxEntry?.previousParentGroup = readUuid(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) {
return switchContext(ctx, KdbContext.EntryTimes, xpp)
} else if (name.equals(DatabaseKDBXXML.ElemString, ignoreCase = true)) {
@@ -608,6 +624,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
KdbContext.EntryCustomDataItem -> when {
name.equals(DatabaseKDBXXML.ElemKey, ignoreCase = true) -> entryCustomDataKey = readString(xpp)
name.equals(DatabaseKDBXXML.ElemValue, ignoreCase = true) -> entryCustomDataValue = readString(xpp)
name.equals(DatabaseKDBXXML.ElemLastModTime, ignoreCase = true) -> readDateInstant(xpp) // Ignore
else -> readUnknown(xpp)
}
@@ -620,13 +637,13 @@ class DatabaseInputKDBX(cacheDirectory: File,
}
when {
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.ElemLastModTime, ignoreCase = true) -> tl?.lastModificationTime = readDateInstant(xpp)
name.equals(DatabaseKDBXXML.ElemCreationTime, ignoreCase = true) -> tl?.creationTime = readDateInstant(xpp)
name.equals(DatabaseKDBXXML.ElemLastAccessTime, ignoreCase = true) -> tl?.lastAccessTime = readDateInstant(xpp)
name.equals(DatabaseKDBXXML.ElemExpiryTime, ignoreCase = true) -> tl?.expiryTime = readDateInstant(xpp)
name.equals(DatabaseKDBXXML.ElemExpires, ignoreCase = true) -> tl?.expires = readBool(xpp, false)
name.equals(DatabaseKDBXXML.ElemUsageCount, ignoreCase = true) -> tl?.usageCount = readULong(xpp, UnsignedLong(0))
name.equals(DatabaseKDBXXML.ElemLocationChanged, ignoreCase = true) -> tl?.locationChanged = readPwTime(xpp)
name.equals(DatabaseKDBXXML.ElemLocationChanged, ignoreCase = true) -> tl?.locationChanged = readDateInstant(xpp)
else -> readUnknown(xpp)
}
}
@@ -687,7 +704,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
KdbContext.DeletedObject -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) {
ctxDeletedObject?.uuid = readUuid(xpp)
} else if (name.equals(DatabaseKDBXXML.ElemDeletionTime, ignoreCase = true)) {
ctxDeletedObject?.setDeletionTime(readTime(xpp))
ctxDeletedObject?.setDeletionTime(readDateInstant(xpp))
} else {
readUnknown(xpp)
}
@@ -714,29 +731,34 @@ class DatabaseInputKDBX(cacheDirectory: File,
} else if (ctx == KdbContext.CustomIcon && name.equals(DatabaseKDBXXML.ElemCustomIconItem, ignoreCase = true)) {
val iconData = customIconData
if (customIconID != DatabaseVersioned.UUID_ZERO && iconData != null) {
mDatabase.addCustomIcon(customIconID, isRAMSufficient.invoke(iconData.size.toLong())) { _, binary ->
mDatabase.addCustomIcon(customIconID,
customIconName,
customIconLastModificationTime,
isRAMSufficient.invoke(iconData.size.toLong())) { _, binary ->
binary?.getOutputDataStream(mDatabase.binaryCache)?.use { outputStream ->
outputStream.write(iconData)
}
}
}
customIconID = DatabaseVersioned.UUID_ZERO
customIconName = ""
customIconLastModificationTime = null
customIconData = null
return KdbContext.CustomIcons
} else if (ctx == KdbContext.Binaries && name.equals(DatabaseKDBXXML.ElemBinaries, ignoreCase = true)) {
return KdbContext.Meta
} else if (ctx == KdbContext.CustomData && name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) {
return KdbContext.Meta
} else if (ctx == KdbContext.CustomDataItem && name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) {
if (customDataKey != null && customDataValue != null) {
mDatabase.putCustomData(customDataKey!!, customDataValue!!)
customDataKey?.let { dataKey ->
customDataValue?.let { dataValue ->
mDatabase.customData.put(CustomDataItem(dataKey,
dataValue, customDataLastModificationTime))
}
}
customDataKey = null
customDataValue = null
customDataLastModificationTime = null
return KdbContext.CustomData
} else if (ctx == KdbContext.Group && name.equals(DatabaseKDBXXML.ElemGroup, ignoreCase = true)) {
if (ctxGroup != null && ctxGroup?.id == DatabaseVersioned.UUID_ZERO) {
@@ -758,13 +780,13 @@ class DatabaseInputKDBX(cacheDirectory: File,
} else if (ctx == KdbContext.GroupCustomData && name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) {
return KdbContext.Group
} else if (ctx == KdbContext.GroupCustomDataItem && name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) {
if (groupCustomDataKey != null && groupCustomDataValue != null) {
ctxGroup?.putCustomData(groupCustomDataKey!!, groupCustomDataValue!!)
groupCustomDataKey?.let { customDataKey ->
groupCustomDataValue?.let { customDataValue ->
ctxGroup?.customData?.put(CustomDataItem(customDataKey, customDataValue))
}
}
groupCustomDataKey = null
groupCustomDataValue = null
return KdbContext.GroupCustomData
} else if (ctx == KdbContext.Entry && name.equals(DatabaseKDBXXML.ElemEntry, ignoreCase = true)) {
@@ -785,7 +807,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
return KdbContext.Entry
} else if (ctx == KdbContext.EntryString && name.equals(DatabaseKDBXXML.ElemString, ignoreCase = true)) {
if (ctxStringName != null && ctxStringValue != null)
ctxEntry?.putExtraField(ctxStringName!!, ctxStringValue!!)
ctxEntry?.putField(ctxStringName!!, ctxStringValue!!)
ctxStringName = null
ctxStringValue = null
@@ -802,7 +824,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
return KdbContext.Entry
} else if (ctx == KdbContext.EntryAutoTypeItem && name.equals(DatabaseKDBXXML.ElemAutoTypeItem, ignoreCase = true)) {
if (ctxATName != null && ctxATSeq != null)
ctxEntry?.autoType?.put(ctxATName!!, ctxATSeq!!)
ctxEntry?.autoType?.add(ctxATName!!, ctxATSeq!!)
ctxATName = null
ctxATSeq = null
@@ -810,13 +832,13 @@ class DatabaseInputKDBX(cacheDirectory: File,
} else if (ctx == KdbContext.EntryCustomData && name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) {
return KdbContext.Entry
} else if (ctx == KdbContext.EntryCustomDataItem && name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) {
if (entryCustomDataKey != null && entryCustomDataValue != null) {
ctxEntry?.putCustomData(entryCustomDataKey!!, entryCustomDataValue!!)
entryCustomDataKey?.let { customDataKey ->
entryCustomDataValue?.let { customDataValue ->
ctxEntry?.customData?.put(CustomDataItem(customDataKey, customDataValue))
}
}
entryCustomDataKey = null
entryCustomDataValue = null
return KdbContext.EntryCustomData
} else if (ctx == KdbContext.EntryHistory && name.equals(DatabaseKDBXXML.ElemHistory, ignoreCase = true)) {
entryInHistory = false
@@ -836,16 +858,11 @@ class DatabaseInputKDBX(cacheDirectory: File,
}
@Throws(IOException::class, XmlPullParserException::class)
private fun readPwTime(xpp: XmlPullParser): DateInstant {
return DateInstant(readTime(xpp))
}
@Throws(IOException::class, XmlPullParserException::class)
private fun readTime(xpp: XmlPullParser): Date {
private fun readDateInstant(xpp: XmlPullParser): DateInstant {
val sDate = readString(xpp)
var utcDate: Date? = null
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_40)) {
try {
utcDate = DatabaseKDBXXML.DateFormatter.parse(sDate)
} catch (e: ParseException) {
@@ -863,7 +880,12 @@ class DatabaseInputKDBX(cacheDirectory: File,
utcDate = DateKDBXUtil.convertKDBX4Time(seconds)
}
return utcDate ?: Date(0L)
return DateInstant(utcDate ?: Date(0L))
}
@Throws(IOException::class, XmlPullParserException::class)
private fun readTags(xpp: XmlPullParser): Tags {
return Tags(readString(xpp))
}
@Throws(XmlPullParserException::class, IOException::class)

View File

@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
import com.kunzisoft.keepass.database.file.DatabaseHeader
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
import com.kunzisoft.keepass.stream.MacOutputStream
import com.kunzisoft.keepass.utils.*
import java.io.ByteArrayOutputStream
@@ -76,7 +76,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, uIntTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed)
if (header.version.isBefore(FILE_VERSION_32_4)) {
if (header.version.isBefore(FILE_VERSION_40)) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds))
} else {
@@ -87,7 +87,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV)
}
if (header.version.isBefore(FILE_VERSION_32_4)) {
if (header.version.isBefore(FILE_VERSION_40)) {
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes)
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, uIntTo4Bytes(header.innerRandomStream!!.id))
@@ -121,7 +121,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
@Throws(IOException::class)
private fun writeHeaderFieldSize(size: Int) {
if (header.version.isBefore(FILE_VERSION_32_4)) {
if (header.version.isBefore(FILE_VERSION_40)) {
mos.write2BytesUShort(size)
} else {
mos.write4BytesUInt(UnsignedInt(size))

View File

@@ -23,15 +23,16 @@ import android.util.Base64
import android.util.Log
import android.util.Xml
import com.kunzisoft.encrypt.StreamCipher
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.keepass.database.action.node.NodeHandler
import com.kunzisoft.keepass.database.crypto.CipherEngine
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.element.DeletedObject
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.DatabaseVersioned
import com.kunzisoft.keepass.database.element.entry.AutoType
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.group.GroupKDBX
@@ -41,7 +42,8 @@ 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.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_41
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
import com.kunzisoft.keepass.database.file.DateKDBXUtil
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
@@ -83,7 +85,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
header = outputHeader(mOutputStream)
val osPlain: OutputStream = if (header!!.version.isBefore(FILE_VERSION_32_4)) {
val osPlain: OutputStream = if (header!!.version.isBefore(FILE_VERSION_40)) {
val cos = attachStreamEncryptor(header!!, mOutputStream)
cos.write(header!!.streamStartBytes)
@@ -102,7 +104,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
else -> osPlain
}
if (!header!!.version.isBefore(FILE_VERSION_32_4)) {
if (!header!!.version.isBefore(FILE_VERSION_40)) {
outputInnerHeader(mDatabaseKDBX, header!!, xmlOutputStream)
}
@@ -234,40 +236,40 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
private fun writeMeta() {
xml.startTag(null, DatabaseKDBXXML.ElemMeta)
writeObject(DatabaseKDBXXML.ElemGenerator, mDatabaseKDBX.localizedAppName)
writeString(DatabaseKDBXXML.ElemGenerator, mDatabaseKDBX.localizedAppName)
if (hashOfHeader != null) {
writeObject(DatabaseKDBXXML.ElemHeaderHash, String(Base64.encode(hashOfHeader!!, BASE_64_FLAG)))
writeString(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.toKotlinLong())
writeObject(DatabaseKDBXXML.ElemDbColor, mDatabaseKDBX.color)
writeObject(DatabaseKDBXXML.ElemDbKeyChanged, mDatabaseKDBX.keyLastChanged.date)
writeObject(DatabaseKDBXXML.ElemDbKeyChangeRec, mDatabaseKDBX.keyChangeRecDays)
writeObject(DatabaseKDBXXML.ElemDbKeyChangeForce, mDatabaseKDBX.keyChangeForceDays)
writeString(DatabaseKDBXXML.ElemDbName, mDatabaseKDBX.name, true)
writeDateInstant(DatabaseKDBXXML.ElemDbNameChanged, mDatabaseKDBX.nameChanged)
writeString(DatabaseKDBXXML.ElemDbDesc, mDatabaseKDBX.description, true)
writeDateInstant(DatabaseKDBXXML.ElemDbDescChanged, mDatabaseKDBX.descriptionChanged)
writeString(DatabaseKDBXXML.ElemDbDefaultUser, mDatabaseKDBX.defaultUserName, true)
writeDateInstant(DatabaseKDBXXML.ElemDbDefaultUserChanged, mDatabaseKDBX.defaultUserNameChanged)
writeLong(DatabaseKDBXXML.ElemDbMntncHistoryDays, mDatabaseKDBX.maintenanceHistoryDays.toKotlinLong())
writeString(DatabaseKDBXXML.ElemDbColor, mDatabaseKDBX.color)
writeDateInstant(DatabaseKDBXXML.ElemDbKeyChanged, mDatabaseKDBX.keyLastChanged)
writeLong(DatabaseKDBXXML.ElemDbKeyChangeRec, mDatabaseKDBX.keyChangeRecDays)
writeLong(DatabaseKDBXXML.ElemDbKeyChangeForce, mDatabaseKDBX.keyChangeForceDays)
writeMemoryProtection(mDatabaseKDBX.memoryProtection)
writeCustomIconList()
writeObject(DatabaseKDBXXML.ElemRecycleBinEnabled, mDatabaseKDBX.isRecycleBinEnabled)
writeBoolean(DatabaseKDBXXML.ElemRecycleBinEnabled, mDatabaseKDBX.isRecycleBinEnabled)
writeUuid(DatabaseKDBXXML.ElemRecycleBinUuid, mDatabaseKDBX.recycleBinUUID)
writeObject(DatabaseKDBXXML.ElemRecycleBinChanged, mDatabaseKDBX.recycleBinChanged)
writeDateInstant(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)
writeDateInstant(DatabaseKDBXXML.ElemEntryTemplatesGroupChanged, mDatabaseKDBX.entryTemplatesGroupChanged)
writeLong(DatabaseKDBXXML.ElemHistoryMaxItems, mDatabaseKDBX.historyMaxItems.toLong())
writeLong(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.isBefore(FILE_VERSION_32_4))
if (header!!.version.isBefore(FILE_VERSION_40))
writeMetaBinaries()
writeCustomData(mDatabaseKDBX.customData)
@@ -309,7 +311,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
Log.e(TAG, "Unable to retrieve header", unknownKDF)
}
if (header.version.isBefore(FILE_VERSION_32_4)) {
if (header.version.isBefore(FILE_VERSION_40)) {
header.innerRandomStream = CrsAlgorithm.Salsa20
header.innerRandomStreamKey = ByteArray(32)
} else {
@@ -324,7 +326,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
throw DatabaseOutputException(e)
}
if (header.version.isBefore(FILE_VERSION_32_4)) {
if (header.version.isBefore(FILE_VERSION_40)) {
random.nextBytes(header.streamStartBytes)
}
@@ -353,19 +355,21 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
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.standard.id.toLong())
writeString(DatabaseKDBXXML.ElemName, group.title)
writeString(DatabaseKDBXXML.ElemNotes, group.notes)
writeLong(DatabaseKDBXXML.ElemIcon, group.icon.standard.id.toLong())
if (!group.icon.custom.isUnknown) {
writeUuid(DatabaseKDBXXML.ElemCustomIconID, group.icon.custom.uuid)
}
writeTags(group.tags)
writePreviousParentGroup(group.previousParentGroup)
writeTimes(group)
writeObject(DatabaseKDBXXML.ElemIsExpanded, group.isExpanded)
writeObject(DatabaseKDBXXML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence)
writeObject(DatabaseKDBXXML.ElemEnableAutoType, group.enableAutoType)
writeObject(DatabaseKDBXXML.ElemEnableSearching, group.enableSearching)
writeBoolean(DatabaseKDBXXML.ElemIsExpanded, group.isExpanded)
writeString(DatabaseKDBXXML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence)
writeBoolean(DatabaseKDBXXML.ElemEnableAutoType, group.enableAutoType)
writeBoolean(DatabaseKDBXXML.ElemEnableSearching, group.enableSearching)
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleEntry, group.lastTopVisibleEntry)
}
@@ -380,24 +384,26 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
xml.startTag(null, DatabaseKDBXXML.ElemEntry)
writeUuid(DatabaseKDBXXML.ElemUuid, entry.id)
writeObject(DatabaseKDBXXML.ElemIcon, entry.icon.standard.id.toLong())
writeLong(DatabaseKDBXXML.ElemIcon, entry.icon.standard.id.toLong())
if (!entry.icon.custom.isUnknown) {
writeUuid(DatabaseKDBXXML.ElemCustomIconID, entry.icon.custom.uuid)
}
writeObject(DatabaseKDBXXML.ElemFgColor, entry.foregroundColor)
writeObject(DatabaseKDBXXML.ElemBgColor, entry.backgroundColor)
writeObject(DatabaseKDBXXML.ElemOverrideUrl, entry.overrideURL)
writeObject(DatabaseKDBXXML.ElemTags, entry.tags)
writeString(DatabaseKDBXXML.ElemFgColor, entry.foregroundColor)
writeString(DatabaseKDBXXML.ElemBgColor, entry.backgroundColor)
writeString(DatabaseKDBXXML.ElemOverrideUrl, entry.overrideURL)
// Write quality check only if false
if (!entry.qualityCheck) {
writeBoolean(DatabaseKDBXXML.ElemQualityCheck, entry.qualityCheck)
}
writeTags(entry.tags)
writePreviousParentGroup(entry.previousParentGroup)
writeTimes(entry)
writeFields(entry.fields)
writeEntryBinaries(entry.binaries)
if (entry.containsCustomData()) {
writeCustomData(entry.customData)
}
writeAutoType(entry.autoType)
if (!isHistory) {
@@ -408,7 +414,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, value: String, filterXmlChars: Boolean = false) {
private fun writeString(name: String, value: String, filterXmlChars: Boolean = false) {
var xmlString = value
xml.startTag(null, name)
@@ -422,38 +428,37 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, value: Date) {
if (header!!.version.isBefore(FILE_VERSION_32_4)) {
writeObject(name, DatabaseKDBXXML.DateFormatter.format(value))
private fun writeDateInstant(name: String, value: DateInstant) {
val date = value.date
if (header!!.version.isBefore(FILE_VERSION_40)) {
writeString(name, DatabaseKDBXXML.DateFormatter.format(date))
} else {
val dt = DateTime(value)
val seconds = DateKDBXUtil.convertDateToKDBX4Time(dt)
val buf = longTo8Bytes(seconds)
val buf = longTo8Bytes(DateKDBXUtil.convertDateToKDBX4Time(DateTime(date)))
val b64 = String(Base64.encode(buf, BASE_64_FLAG))
writeObject(name, b64)
writeString(name, b64)
}
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, value: Long) {
writeObject(name, value.toString())
private fun writeLong(name: String, value: Long) {
writeString(name, value.toString())
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeObject(name: String, value: Boolean?) {
private fun writeBoolean(name: String, value: Boolean?) {
val text: String = when {
value == null -> DatabaseKDBXXML.ValNull
value -> DatabaseKDBXXML.ValTrue
else -> DatabaseKDBXXML.ValFalse
}
writeObject(name, text)
writeString(name, text)
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeUuid(name: String, uuid: UUID) {
val data = uuidTo16Bytes(uuid)
writeObject(name, String(Base64.encode(data, BASE_64_FLAG)))
writeString(name, String(Base64.encode(data, BASE_64_FLAG)))
}
/*
@@ -514,34 +519,29 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
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.toKotlinLong())
writeBoolean(DatabaseKDBXXML.ElemAutoTypeEnabled, autoType.enabled)
writeLong(DatabaseKDBXXML.ElemAutoTypeObfuscation, autoType.obfuscationOptions.toKotlinLong())
if (autoType.defaultSequence.isNotEmpty()) {
writeObject(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true)
writeString(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true)
}
for ((key, value) in autoType.entrySet()) {
writeObject(DatabaseKDBXXML.ElemAutoTypeItem, DatabaseKDBXXML.ElemWindow, key, DatabaseKDBXXML.ElemKeystrokeSequence, value)
autoType.doForEachAutoTypeItem { key, value ->
xml.startTag(null, DatabaseKDBXXML.ElemAutoTypeItem)
xml.startTag(null, DatabaseKDBXXML.ElemWindow)
xml.text(safeXmlString(key))
xml.endTag(null, DatabaseKDBXXML.ElemWindow)
xml.startTag(null, DatabaseKDBXXML.ElemKeystrokeSequence)
xml.text(safeXmlString(value))
xml.endTag(null, DatabaseKDBXXML.ElemKeystrokeSequence)
xml.endTag(null, DatabaseKDBXXML.ElemAutoTypeItem)
}
xml.endTag(null, DatabaseKDBXXML.ElemAutoType)
@@ -592,7 +592,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
xml.startTag(null, DatabaseKDBXXML.ElemDeletedObject)
writeUuid(DatabaseKDBXXML.ElemUuid, value.uuid)
writeObject(DatabaseKDBXXML.ElemDeletionTime, value.getDeletionTime())
writeDateInstant(DatabaseKDBXXML.ElemDeletionTime, value.getDeletionTime())
xml.endTag(null, DatabaseKDBXXML.ElemDeletedObject)
}
@@ -632,43 +632,72 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
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)
writeBoolean(DatabaseKDBXXML.ElemProtTitle, value.protectTitle)
writeBoolean(DatabaseKDBXXML.ElemProtUserName, value.protectUserName)
writeBoolean(DatabaseKDBXXML.ElemProtPassword, value.protectPassword)
writeBoolean(DatabaseKDBXXML.ElemProtURL, value.protectUrl)
writeBoolean(DatabaseKDBXXML.ElemProtNotes, value.protectNotes)
xml.endTag(null, DatabaseKDBXXML.ElemMemoryProt)
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeCustomData(customData: Map<String, String>) {
private fun writeCustomData(customData: CustomData) {
if (customData.isNotEmpty()) {
xml.startTag(null, DatabaseKDBXXML.ElemCustomData)
for ((key, value) in customData) {
writeObject(
DatabaseKDBXXML.ElemStringDictExItem,
DatabaseKDBXXML.ElemKey,
key,
DatabaseKDBXXML.ElemValue,
value
)
customData.doForEachItems { customDataItem ->
writeCustomDataItem(customDataItem)
}
xml.endTag(null, DatabaseKDBXXML.ElemCustomData)
}
}
private fun writeCustomDataItem(customDataItem: CustomDataItem) {
xml.startTag(null, DatabaseKDBXXML.ElemStringDictExItem)
xml.startTag(null, DatabaseKDBXXML.ElemKey)
xml.text(safeXmlString(customDataItem.key))
xml.endTag(null, DatabaseKDBXXML.ElemKey)
xml.startTag(null, DatabaseKDBXXML.ElemValue)
xml.text(safeXmlString(customDataItem.value))
xml.endTag(null, DatabaseKDBXXML.ElemValue)
customDataItem.lastModificationTime?.let { lastModificationTime ->
writeDateInstant(DatabaseKDBXXML.ElemLastModTime, lastModificationTime)
}
xml.endTag(null, DatabaseKDBXXML.ElemStringDictExItem)
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writeTags(tags: Tags) {
if (!tags.isEmpty()) {
writeString(DatabaseKDBXXML.ElemTags, tags.toString())
}
}
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
private fun writePreviousParentGroup(previousParentGroup: UUID) {
if (!header!!.version.isBefore(FILE_VERSION_41)
&& previousParentGroup != DatabaseVersioned.UUID_ZERO) {
writeUuid(DatabaseKDBXXML.ElemPreviousParentGroup, previousParentGroup)
}
}
@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.toKotlinLong())
writeObject(DatabaseKDBXXML.ElemLocationChanged, node.locationChanged.date)
writeDateInstant(DatabaseKDBXXML.ElemLastModTime, node.lastModificationTime)
writeDateInstant(DatabaseKDBXXML.ElemCreationTime, node.creationTime)
writeDateInstant(DatabaseKDBXXML.ElemLastAccessTime, node.lastAccessTime)
writeDateInstant(DatabaseKDBXXML.ElemExpiryTime, node.expiryTime)
writeBoolean(DatabaseKDBXXML.ElemExpires, node.expires)
writeLong(DatabaseKDBXXML.ElemUsageCount, node.usageCount.toKotlinLong())
writeDateInstant(DatabaseKDBXXML.ElemLocationChanged, node.locationChanged)
xml.endTag(null, DatabaseKDBXXML.ElemTimes)
}
@@ -709,9 +738,15 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
} catch (e: Exception) {
Log.e(TAG, "Unable to write custom icon", e)
} finally {
writeObject(DatabaseKDBXXML.ElemCustomIconItemData,
writeString(DatabaseKDBXXML.ElemCustomIconItemData,
String(Base64.encode(customImageData, BASE_64_FLAG)))
}
if (iconCustom.name.isNotEmpty()) {
writeString(DatabaseKDBXXML.ElemName, iconCustom.name)
}
iconCustom.lastModificationTime?.let { lastModificationTime ->
writeDateInstant(DatabaseKDBXXML.ElemLastModTime, lastModificationTime)
}
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
}

View File

@@ -29,7 +29,6 @@ import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_FIELD
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.StringUtil.flattenToAscii
import com.kunzisoft.keepass.utils.UuidUtil
class SearchHelper {
@@ -177,13 +176,13 @@ class SearchHelper {
// TODO Search settings
var regularExpression = false
var ignoreCase = true
var flattenToASCII = true
var removeAccents = true <- Too much time, to study
var excludeExpired = false
var searchOnlyInCurrentGroup = false
*/
return stringToCheck.isNotEmpty()
&& stringToCheck.flattenToAscii().contains(
searchParameters.searchQuery.flattenToAscii(), true)
&& stringToCheck.contains(
searchParameters.searchQuery, true)
}
}
}

View File

@@ -37,7 +37,6 @@ class ClipboardEntryNotificationService : LockNotificationService() {
override val notificationId = 485
private var mEntryInfo: EntryInfo? = null
private var clipboardHelper: ClipboardHelper? = null
private var mNotificationTimeoutMilliSecs: Long = 0
override fun retrieveChannelId(): String {
return CHANNEL_CLIPBOARD_ID
@@ -68,9 +67,6 @@ class ClipboardEntryNotificationService : LockNotificationService() {
// Get entry info from intent
mEntryInfo = intent?.getParcelableExtra(EXTRA_ENTRY_INFO)
//Get settings
mNotificationTimeoutMilliSecs = PreferencesUtil.getClipboardTimeout(this)
when {
intent == null -> Log.w(TAG, "null intent")
ACTION_NEW_NOTIFICATION == intent.action -> {
@@ -169,8 +165,10 @@ class ClipboardEntryNotificationService : LockNotificationService() {
this, 0, cleanIntent, PendingIntent.FLAG_UPDATE_CURRENT)
builder.setDeleteIntent(cleanPendingIntent)
if (mNotificationTimeoutMilliSecs != NEVER) {
defineTimerJob(builder, mNotificationTimeoutMilliSecs, {
//Get settings
val notificationTimeoutMilliSecs = PreferencesUtil.getClipboardTimeout(this)
if (notificationTimeoutMilliSecs != NEVER) {
defineTimerJob(builder, notificationTimeoutMilliSecs, {
val newGeneratedValue = fieldToCopy.getGeneratedValue(mEntryInfo)
// New auto generated value
if (generatedValue != newGeneratedValue) {

View File

@@ -280,8 +280,10 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
stopSelf()
} else {
// Restart the service to open lock notification
try {
startService(Intent(applicationContext,
DatabaseTaskNotificationService::class.java))
} catch (e: IllegalStateException) {}
}
}
}

View File

@@ -26,8 +26,9 @@ import com.kunzisoft.keepass.utils.unregisterLockReceiver
abstract class LockNotificationService : NotificationService() {
private var onStart: Boolean = false
private var mLockReceiver: LockReceiver? = null
private var mLockReceiver: LockReceiver = LockReceiver {
actionOnLock()
}
protected open fun actionOnLock() {
// Stop the service in all cases
@@ -36,30 +37,17 @@ abstract class LockNotificationService : NotificationService() {
override fun onCreate() {
super.onCreate()
// Register a lock receiver to stop notification service when lock on keyboard is performed
mLockReceiver = LockReceiver {
if (onStart)
actionOnLock()
}
registerLockReceiver(mLockReceiver)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
onStart = true
return super.onStartCommand(intent, flags, startId)
}
override fun onTaskRemoved(rootIntent: Intent?) {
notificationManager?.cancel(notificationId)
stopSelf()
super.onTaskRemoved(rootIntent)
}
override fun onDestroy() {
unregisterLockReceiver(mLockReceiver)
mLockReceiver = null
super.onDestroy()
}
}

View File

@@ -80,6 +80,7 @@ abstract class NotificationService : Service() {
actionEnd: () -> Unit) {
mTimerJob?.cancel()
mTimerJob = CoroutineScope(Dispatchers.Main).launch {
if (timeoutMilliseconds > 0) {
val timeoutInSeconds = timeoutMilliseconds / 1000L
for (currentTime in timeoutInSeconds downTo 0) {
actionAfterASecond?.invoke()
@@ -92,6 +93,10 @@ abstract class NotificationService : Service() {
actionEnd()
}
}
} else {
// If timeout is 0, run action once
actionEnd()
}
notificationManager?.cancel(notificationId)
mTimerJob = null
cancel()

View File

@@ -42,8 +42,12 @@ object ParcelableUtil {
val size = parcel.readInt()
val map = HashMap<K, V>(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))
val key: K? = try {
parcel.readParcelable(kClass.classLoader)
} catch (e: Exception) { null }
val value: V? = try {
parcel.readParcelable(vClass.classLoader)
} catch (e: Exception) { null }
if (key != null && value != null)
map[key] = value
}
@@ -52,7 +56,7 @@ object ParcelableUtil {
// For writing map with string key to a Parcel
fun <V : Parcelable> writeStringParcelableMap(
parcel: Parcel, flags: Int, map: LinkedHashMap<String, V>) {
parcel: Parcel, flags: Int, map: HashMap<String, V>) {
parcel.writeInt(map.size)
for ((key, value) in map) {
parcel.writeString(key)
@@ -76,7 +80,9 @@ object ParcelableUtil {
val map = LinkedHashMap<String, V>(size)
for (i in 0 until size) {
val key: String? = parcel.readString()
val value: V? = vClass.cast(parcel.readParcelable(vClass.classLoader))
val value: V? = try {
parcel.readParcelable(vClass.classLoader)
} catch (e: Exception) { null }
if (key != null && value != null)
map[key] = value
}

View File

@@ -12,20 +12,5 @@ object StringUtil {
return this.replace("[\\r|\\n|\\t|\\s|\\u00A0]+".toRegex(), "")
}
fun String.flattenToAscii(): String {
var string = this
val out = CharArray(string.length)
string = Normalizer.normalize(string, Normalizer.Form.NFD)
var j = 0
var i = 0
val n = string.length
while (i < n) {
val c = string[i]
if (c <= '\u007F') out[j++] = c
++i
}
return String(out)
}
fun ByteArray.toHexString() = joinToString("") { "%02X".format(it) }
}

View File

@@ -19,8 +19,6 @@
*/
package com.kunzisoft.keepass.utils
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
class UnsignedInt(private var unsignedValue: Int) {
constructor(unsignedValue: UnsignedInt) : this(unsignedValue.toKotlinInt())

View File

@@ -46,10 +46,10 @@ class EntryField @JvmOverloads constructor(context: Context,
var hiddenProtectedValue: Boolean
get() {
return showButtonView.isSelected
return !showButtonView.isSelected
}
set(value) {
showButtonView.isSelected = !value
showButtonView.isSelected = value
changeProtectedValueParameters()
}
@@ -101,7 +101,7 @@ class EntryField @JvmOverloads constructor(context: Context,
} else {
setTextIsSelectable(true)
}
applyHiddenStyle(isProtected && !showButtonView.isSelected)
applyHiddenStyle(isProtected && showButtonView.isSelected)
if (!isProtected) linkify()
}
}

View File

@@ -17,17 +17,42 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/icon_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_height="80dp"
android:background="@drawable/background_item_selection">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/icon_name"
android:layout_gravity="center"
android:layout_margin="16dp">
</androidx.appcompat.widget.AppCompatImageView>
</FrameLayout>
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/icon_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@+id/icon_image"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_gravity="center"
android:gravity="center"
android:maxLines="3"
android:paddingStart="4dp"
android:paddingLeft="4dp"
android:paddingEnd="4dp"
android:paddingRight="4dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@@ -17,7 +17,7 @@
You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--><resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation">
<string name="homepage">الصفحة الرئيسية</string>
<string name="homepage">الصفحة الرئيسة</string>
<string name="accept">قبول</string>
<string name="add_group">إضافة مجموعة</string>
<string name="encryption">التعمية</string>
@@ -106,8 +106,8 @@
<string name="education_entry_new_field_title">إضافة حقول مخصصة</string>
<string name="education_field_copy_title">نسخ حقل</string>
<string name="education_lock_title">تأمين قاعدة البيانات</string>
<string name="feedback">الأصداء</string>
<string name="about_description">تنفيذ أندرويد لمدير كلمات السر «كي‌باس»</string>
<string name="feedback">التغذية الراجعة</string>
<string name="about_description">التنفيذ لمُدير كلمات المرور «كي‌ باس» على نظام أندرويد</string>
<string name="add_entry">إضافة مدخلة</string>
<string name="edit_entry">تحرير مدخلة</string>
<string name="key_derivation_function">وظيفة اشتقاق المفتاح</string>

View File

@@ -22,4 +22,11 @@
<string name="clipboard_error_title">ক্লিপবোর্ড ত্রুটি</string>
<string name="allow">অনুমোদন</string>
<string name="file_manager_install_description">ACTION_CREATE_DOCUMENT এবং ACTION_OPEN_DOCUMENT অভিপ্রায় গ্রহণ করে এমন একটি ফাইল ম্যানেজার ডাটাবেস ফাইলগুলো তৈরি করা, খোলা এবং সংরক্ষণ করতে প্রয়োজন।</string>
<string name="digits">ডিজিট</string>
<string name="database">তথ্যভিত্তি</string>
<string name="content_description_remove_from_list">সরাও</string>
<string name="content_description_update_from_list">হালনাগাদ</string>
<string name="discard">বাতিল</string>
<string name="validate">সত্যায়ন</string>
<string name="content_description_background">পটভূমি</string>
</resources>

View File

@@ -25,7 +25,7 @@
<string name="add_entry">Afegeix entrada</string>
<string name="add_group">Afegeix grup</string>
<string name="encryption_algorithm">Algoritme de xifrat</string>
<string name="app_timeout">Temps d\'espera de l\'aplicació</string>
<string name="app_timeout">Temps d\'espera esgotat</string>
<string name="app_timeout_summary">Temps d\'inactivitat abans de blocar la base de dades</string>
<string name="application">Aplicació</string>
<string name="menu_app_settings">Configuració de l\'aplicació</string>
@@ -289,4 +289,6 @@
<string name="error_otp_type">L\'OTP existent no està reconegut per aquest formulari, la seva validació ja no pot generar correctament el token.</string>
<string name="error_create_database_file">No s\'ha pogut crear una base de dades amb aquesta contrasenya i arxiu de clau.</string>
<string name="error_autofill_enable_service">No s\'ha pogut habilitar el servei d\'autocompletat.</string>
<string name="content_description_node_children">Nodes fill</string>
<string name="key_derivation_function">Funció de derivació de clau</string>
</resources>

View File

@@ -24,7 +24,7 @@
<string name="add_entry">Přidat záznam</string>
<string name="add_group">Přidat skupinu</string>
<string name="encryption_algorithm">Šifrovací algoritmus</string>
<string name="app_timeout">Časový limit</string>
<string name="app_timeout">Časový limit překročen</string>
<string name="app_timeout_summary">Doba nečinnosti, po které se aplikace zamkne</string>
<string name="application">Aplikace</string>
<string name="menu_app_settings">Nastavení aplikace</string>
@@ -548,4 +548,19 @@
<string name="error_upload_file">Během nahrávání souboru došlo k chybě.</string>
<string name="error_file_to_big">Soubor, který se pokoušíte nahrát, je příliš velký.</string>
<string name="content_description_otp_information">Info o jednorázovém hesle</string>
<string name="properties">Vlastnosti</string>
<string name="error_export_app_properties">Během exportu vlastností aplikace došlo k chybě</string>
<string name="success_export_app_properties">Vlastnosti aplikace byly exportovány</string>
<string name="error_import_app_properties">Během importu vlastností aplikace došlo k chybě</string>
<string name="success_import_app_properties">Vlastnosti aplikace byly importovány</string>
<string name="description_app_properties">Vlastnosti KeePassDX pro správu aplikačních nastavení</string>
<string name="export_app_properties_summary">Pro export vlastností aplikace založte soubor</string>
<string name="export_app_properties_title">Exportovat vlastnosti aplikace</string>
<string name="import_app_properties_summary">Pro import vlastostí aplikace zvolte soubor</string>
<string name="import_app_properties_title">Importovat vlastnosti aplikace</string>
<string name="error_start_database_action">Během akce v databázi došlo k chybě.</string>
<string name="error_remove_file">Při odstraňování dat soboru došlo k chybě.</string>
<string name="error_duplicate_file">Datový soubor již existuje.</string>
<string name="error_move_group_here">Sem skupinu přesunout nemůžete.</string>
<string name="error_word_reserved">Toto slovo je rezervováno a nelze je použít.</string>
</resources>

View File

@@ -223,7 +223,7 @@
<string name="application_appearance">Brugerflade</string>
<string name="other">Øvrige</string>
<string name="keyboard">Tastatur</string>
<string name="magic_keyboard_title">Magikeyboard</string>
<string name="magic_keyboard_title">Magi keyboard</string>
<string name="magic_keyboard_explanation_summary">Aktiver et brugerdefineret tastatur, der udfylder adgangskoder og alle identitetsfelter</string>
<string name="allow_no_password_title">Tillad ingen hovednøgle</string>
<string name="allow_no_password_summary">Tillader at trykke på knappen \"Åbn\", hvis der ikke er valgt nogen legitimationsoplysninger</string>
@@ -283,8 +283,8 @@
<string name="style_choose_summary">Tema, der bruges i programmet</string>
<string name="icon_pack_choose_title">Ikonpakke</string>
<string name="icon_pack_choose_summary">Ikonpakke, der anvendes</string>
<string name="keyboard_name">Magikeyboard</string>
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
<string name="keyboard_name">Magi keyboard</string>
<string name="keyboard_label">Magi keyboard (KeePassDX)</string>
<string name="keyboard_setting_label">Magikeyboard indstillinger</string>
<string name="keyboard_entry_category">Post</string>
<string name="keyboard_entry_timeout_title">Udløbstid</string>
@@ -300,7 +300,7 @@
<string name="keyboard_keys_category">Taster</string>
<string name="keyboard_key_vibrate_title">Vibrerende tastetryk</string>
<string name="keyboard_key_sound_title">Hørbare tastetryk</string>
<string name="build_label">Build %1$s</string>
<string name="build_label">Byg %1$s</string>
<string name="keyboard_entry_timeout_summary">Tidsudløb for at rydde indtastning</string>
<string name="entry_notes">Noter</string>
<string name="selection_mode">Valgstilstand</string>
@@ -426,7 +426,7 @@
<string name="hide_broken_locations_title">Skjule brudte databaselinks</string>
<string name="hide_broken_locations_summary">Skjul brudte links på listen over seneste databaser</string>
<string name="warning_database_read_only">Giv fil skriveadgang for at gemme databasændringer</string>
<string name="education_setup_OTP_summary">Opsætning af engangs-adgangskode-styring (HOTP / TOTP) for at generere et token anmodet af tofaktor-autentisering (2FA).</string>
<string name="education_setup_OTP_summary">Sæt op engangs-adgangskode-styring (HOTP / TOTP) for at generere et token anmodet af tofaktor-autentisering (2FA).</string>
<string name="education_setup_OTP_title">Opsætning af OTP</string>
<string name="error_create_database">Databasefilen kunne ikke oprettes.</string>
<string name="entry_add_attachment">Tilføj vedhæng</string>
@@ -537,7 +537,7 @@
<string name="error_start_database_action">Der opstod en fejl under udførelsen af en handling på databasen.</string>
<string name="error_remove_file">Der opstod en fejl med at fjerne fildata.</string>
<string name="error_otp_type">Den existerende OTP type kunne ikke genkendes, den kan være tiden er udløbet for at lave dette token.</string>
<string name="education_advanced_unlock_summary">Sammenkæd din adgangskode, med din scannede biometriske oplysninger eller enheds legitimationsoplysninger for, hurtigt at låse din database op.</string>
<string name="education_advanced_unlock_summary">Sammenkæd din adgangskode, med din scannede biometriske oplysninger eller enheds legitimationsoplysninger for hurtigt oplåsning af din database.</string>
<string name="enter">Enter</string>
<string name="temp_advanced_unlock_timeout_summary">Varigheden af avanceret oplåsning, før indholdet slettes</string>
<string name="device_credential_unlock_enable_summary">Giver dig mulighed for at bruge dine enhedsoplysninger for at åbne databasen</string>
@@ -561,4 +561,6 @@
<string name="export_app_properties_title">Eksporter app-egenskaber</string>
<string name="import_app_properties_summary">Vælg en fil for at importere app-egenskaber</string>
<string name="import_app_properties_title">Importer appegenskaber</string>
<string name="error_move_group_here">Du kan flytte en gruppe her.</string>
<string name="error_word_reserved">Dette ord er reseveret og kan ikke bruges.</string>
</resources>

View File

@@ -30,17 +30,17 @@
<string name="add_entry">Eintrag hinzufügen</string>
<string name="add_group">Gruppe hinzufügen</string>
<string name="encryption_algorithm">Verschlüsselungsalgorithmus</string>
<string name="app_timeout">Zeitüberschreitung</string>
<string name="app_timeout_summary">Inaktivitätszeit vor dem Sperren der Datenbank</string>
<string name="app_timeout">Inaktivitätszeit</string>
<string name="app_timeout_summary">Zeit bis zum Sperren der Datenbank</string>
<string name="application">App</string>
<string name="menu_app_settings">App-Einstellungen</string>
<string name="menu_app_settings">Einstellungen</string>
<string name="brackets">Klammern</string>
<string name="file_manager_install_description">Zum Erstellen, Öffnen und Speichern von Datenbankdateien wird ein Dateimanager benötigt, der die beabsichtigte Aktion ACTION_CREATE_DOCUMENT und ACTION_OPEN_DOCUMENT akzeptiert.</string>
<string name="clipboard_cleared">Zwischenablage geleert</string>
<string name="clipboard_error_title">Zwischenablage-Fehler</string>
<string name="clipboard_error">Einige Geräte lassen keine Nutzung der Zwischenablage durch Apps zu.</string>
<string name="clipboard_error_clear">Leeren der Zwischenablage fehlgeschlagen</string>
<string name="clipboard_timeout">Zwischenablage-Timeout</string>
<string name="clipboard_timeout">Zwischenablage-Inaktivitätszeit</string>
<string name="clipboard_timeout_summary">Dauer der Speicherung in der Zwischenablage (falls vom Gerät unterstützt)</string>
<string name="select_to_copy">%1$s in die Zwischenablage kopieren</string>
<string name="retrieving_db_key">Datenbank-Schlüsseldatei erzeugen </string>
@@ -71,7 +71,7 @@
<string name="error_invalid_db">Datenbank nicht lesbar.</string>
<string name="error_invalid_path">Sicherstellen, dass der Pfad korrekt ist.</string>
<string name="error_no_name">Namen eingeben.</string>
<string name="error_nokeyfile">Schlüsseldatei wählen.</string>
<string name="error_nokeyfile">Schlüsseldatei auswählen.</string>
<string name="error_out_of_memory">Zu wenig Speicherplatz, um die ganze Datenbank zu laden.</string>
<string name="error_pass_gen_type">Mindestens eine Art der Passwortgenerierung muss ausgewählt sein.</string>
<string name="error_pass_match">Die Passwörter stimmen nicht überein.</string>
@@ -83,7 +83,7 @@
<string name="file_not_found_content">Datei nicht gefunden. Bitte versuchen, sie über den Dateimanager zu öffnen.</string>
<string name="file_browser">Dateimanager</string>
<string name="generate_password">Passwort generieren</string>
<string name="hint_conf_pass">Passwort wiederholen</string>
<string name="hint_conf_pass">Passwort bestätigen</string>
<string name="hint_generated_password">Generiertes Passwort</string>
<string name="hint_group_name">Name der Gruppe</string>
<string name="hint_keyfile">Schlüsseldatei</string>
@@ -104,14 +104,14 @@
<string name="about">Über</string>
<string name="menu_change_key_settings">Hauptschlüssel ändern</string>
<string name="settings">Einstellungen</string>
<string name="menu_database_settings">Datenbank-Einstellungen</string>
<string name="menu_database_settings">Datenbankeinstellungen</string>
<string name="menu_delete">Löschen</string>
<string name="menu_donate">Spenden</string>
<string name="menu_edit">Bearbeiten</string>
<string name="menu_hide_password">Passwort verstecken</string>
<string name="menu_lock">Datenbank sperren</string>
<string name="menu_open">Öffnen</string>
<string name="menu_search">Suchen</string>
<string name="menu_search">Suche</string>
<string name="menu_showpass">Passwort anzeigen</string>
<string name="menu_url">URL öffnen</string>
<string name="minus">Bindestrich</string>
@@ -202,14 +202,14 @@
<string name="autofill_service_name">KeePassDX autom. Formularausfüllung</string>
<string name="autofill_sign_in_prompt">Mit KeePassDX anmelden</string>
<string name="set_autofill_service_title">Standarddienst für automatisches Ausfüllen festlegen</string>
<string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um schnell Formulare in anderen Apps auszufüllen</string>
<string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um Formulare schnell in anderen Apps auszufüllen</string>
<string name="clipboard">Zwischenablage</string>
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string>
<string name="biometric_delete_all_key_summary">Lösche alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen</string>
<string name="biometric_delete_all_key_summary">Alle Verschlüsselungsschlüssel löschen, die mit der modernen Entsperrerkennung zusammenhängen</string>
<string name="unavailable_feature_version">Auf dem Gerät läuft Android %1$s, eine Version ab %2$s wäre aber nötig.</string>
<string name="unavailable_feature_hardware">Keine entsprechende Hardware.</string>
<string name="recycle_bin_title">Papierkorb-Nutzung</string>
<string name="recycle_bin_summary">Verschiebt Gruppen oder Einträge in den Papierkorb, bevor sie gelöscht werden.</string>
<string name="recycle_bin_summary">Verschiebt Gruppen oder Einträge in den Papierkorb, bevor sie gelöscht werden</string>
<string name="monospace_font_fields_enable_title">Feldschriftart</string>
<string name="monospace_font_fields_enable_summary">Schriftart in Feldern ändern, um Lesbarkeit zu verbessern</string>
<string name="allow_copy_password_title">Zwischenablage vertrauen</string>
@@ -218,8 +218,8 @@
<string name="database_description_title">Datenbankbeschreibung</string>
<string name="database_version_title">Datenbankversion</string>
<string name="text_appearance">Text</string>
<string name="application_appearance">Interface</string>
<string name="other">Andere</string>
<string name="application_appearance">Benutzeroberfläche</string>
<string name="other">Sonstiges</string>
<string name="keyboard">Tastatur</string>
<string name="magic_keyboard_title">Magikeyboard</string>
<string name="magic_keyboard_explanation_summary">Eine eigene Tastatur zum einfachen Ausfüllen aller Passwort- und Identitätsfelder aktivieren</string>
@@ -297,8 +297,8 @@
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
<string name="keyboard_setting_label">Magikeyboard-Einstellungen</string>
<string name="keyboard_entry_category">Eintrag</string>
<string name="keyboard_entry_timeout_title">Timeout</string>
<string name="keyboard_entry_timeout_summary">Timeout zum Löschen der Tastatureingabe</string>
<string name="keyboard_entry_timeout_title">Inaktivitätszeit</string>
<string name="keyboard_entry_timeout_summary">Zeit bis zum Löschen der Tastatureingabe</string>
<string name="keyboard_notification_entry_title">Benachrichtigung</string>
<string name="keyboard_notification_entry_summary">Benachrichtigung anzeigen, wenn ein Eintrag abrufbar ist</string>
<string name="keyboard_notification_entry_content_title_text">Eintrag</string>
@@ -379,8 +379,8 @@
<string name="error_otp_digits">Token muss %1$d bis %2$d Stellen enthalten.</string>
<string name="invalid_db_same_uuid">%1$s mit derselben UUID %2$s existiert bereits.</string>
<string name="creating_database">Datenbank wird erstellt </string>
<string name="menu_security_settings">Sicherheits-Einstellungen</string>
<string name="menu_master_key_settings">Hauptschlüssel-Einstellungen</string>
<string name="menu_security_settings">Sicherheitseinstellungen</string>
<string name="menu_master_key_settings">Hauptschlüsseleinstellungen</string>
<string name="contains_duplicate_uuid">Die Datenbank enthält UUID-Duplikate.</string>
<string name="contains_duplicate_uuid_procedure">Problem lösen, indem neue UUIDs für Duplikate generiert werden und danach fortfahren\?</string>
<string name="database_opened">Datenbank geöffnet</string>
@@ -398,8 +398,8 @@
<string name="settings_database_force_changing_master_key_summary">Ändern des Hauptschlüssels erforderlich (Tage)</string>
<string name="settings_database_force_changing_master_key_next_time_title">Erneuerung beim nächsten Mal erzwingen</string>
<string name="settings_database_force_changing_master_key_next_time_summary">Änderung des Hauptschlüssels beim nächsten Mal erfordern (einmalig)</string>
<string name="database_default_username_title">Vorgegebener Benutzername</string>
<string name="database_custom_color_title">Benutzerdefinierte Datenbankfarbe</string>
<string name="database_default_username_title">Standard-Benutzername</string>
<string name="database_custom_color_title">Eigene Datenbankfarbe</string>
<string name="compression">Kompression</string>
<string name="compression_none">Keine</string>
<string name="compression_gzip">Gzip</string>
@@ -426,7 +426,7 @@
<string name="hide_expired_entries_title">Abgelaufene Einträge ausblenden</string>
<string name="hide_expired_entries_summary">Abgelaufene Einträge werden nicht angezeigt</string>
<string name="style_choose_title">App-Design</string>
<string name="style_choose_summary">Design, das in der App genutzt wird</string>
<string name="style_choose_summary">In der App verwendetes Design</string>
<string-array name="list_style_names">
<item>Wald</item>
<item>Göttlich</item>
@@ -443,7 +443,7 @@
<string name="discard">Verwerfen</string>
<string name="discard_changes">Änderungen verwerfen\?</string>
<string name="validate">Validieren</string>
<string name="autofill_auto_search_summary">Automatisch Suchergebnisse nach Web-Domain oder Anwendungs-ID vorschlagen</string>
<string name="autofill_auto_search_summary">Suchergebnisse automatisch nach Web-Domain oder Anwendungs-ID vorschlagen</string>
<string name="autofill_auto_search_title">Automatische Suche</string>
<string name="lock_database_show_button_summary">Zeigt die Sperrtaste in der Benutzeroberfläche an</string>
<string name="lock_database_show_button_title">Sperrtaste anzeigen</string>
@@ -454,7 +454,7 @@
<string name="keyboard_search_share_title">Gemeinsame Infos durchsuchen</string>
<string name="autofill_block_restart">Starten Sie die Anwendung, die das Formular enthält, neu, um die Sperrung zu aktivieren.</string>
<string name="autofill_block">Automatisches Ausfüllen sperren</string>
<string name="autofill_web_domain_blocklist_summary">Liste der Domains, auf denen ein automatisches Ausfüllen unterlassen wird</string>
<string name="autofill_web_domain_blocklist_summary">Liste der Domains, auf denen ein automatisches Ausfüllen verhindert wird</string>
<string name="autofill_web_domain_blocklist_title">Liste gesperrter Web-Domains</string>
<string name="autofill_application_id_blocklist_summary">Liste der Apps, in denen ein automatisches Ausfüllen verhindert wird</string>
<string name="autofill_application_id_blocklist_title">Liste gesperrter Anwendungen</string>
@@ -497,11 +497,11 @@
<string name="save_mode">Speichermodus</string>
<string name="search_mode">Suchmodus</string>
<string name="error_registration_read_only">Das Speichern eines neuen Elements in einer schreibgeschützten Datenbank ist nicht zulässig</string>
<string name="autofill_save_search_info_summary">Versuche bei manueller Eingabeauswahl gemeinsam genutzte Informationen zu speichern</string>
<string name="autofill_ask_to_save_data_summary">Speichern von Daten anfordern, wenn ein Formular überprüft wird</string>
<string name="autofill_ask_to_save_data_title">Speichern von Daten anfordern</string>
<string name="autofill_save_search_info_summary">Suchdaten bei manueller Auswahl einer Eingabe wenn möglich speichern</string>
<string name="autofill_ask_to_save_data_summary">Nachfragen, ob die Daten gespeichert werden sollen, wenn ein Formular ausgefüllt ist</string>
<string name="autofill_ask_to_save_data_title">Speichern von Daten abfragen</string>
<string name="autofill_save_search_info_title">Suchinformationen speichern</string>
<string name="autofill_close_database_summary">Datenbank nach der Auswahl des automatischen Ausfüllens schließen</string>
<string name="autofill_close_database_summary">Datenbank nach Auswahl eines Eintrags zum automatischen Ausfüllen schließen</string>
<string name="keyboard_save_search_info_summary">Versuche bei manueller Eingabeauswahl gemeinsam genutzte Informationen zu speichern</string>
<string name="keyboard_save_search_info_title">Gemeinsam genutzte Informationen speichern</string>
<string name="warning_empty_recycle_bin">Sollen alle ausgewählten Knoten wirklich aus dem Papierkorb gelöscht werden\?</string>
@@ -512,13 +512,13 @@
<string name="menu_keystore_remove_key">Schlüssel für modernes Entsperren löschen</string>
<string name="advanced_unlock_prompt_store_credential_title">Moderne Entsperrerkennung</string>
<string name="education_advanced_unlock_summary">Verknüpfen Sie Ihr Passwort mit Ihren gescannten Biometriedaten oder Daten zur Geräteanmeldung, um schnell Ihre Datenbank zu entsperren.</string>
<string name="education_advanced_unlock_title">Erweiterte Entsperrung der Datenbank</string>
<string name="advanced_unlock_timeout">Verfallzeit der erweiterten Entsperrung</string>
<string name="education_advanced_unlock_title">Moderne Entsperrung der Datenbank</string>
<string name="advanced_unlock_timeout">Verfallzeit der modernen Entsperrung</string>
<string name="temp_advanced_unlock_timeout_summary">Laufzeit der modernen Entsperrung bevor ihr Inhalt gelöscht wird</string>
<string name="temp_advanced_unlock_timeout_title">Verfall der modernen Entsperrung</string>
<string name="temp_advanced_unlock_enable_summary">Keinen verschlüsselten Inhalt speichern, um moderne Entsperrung zu benutzen</string>
<string name="temp_advanced_unlock_enable_title">Temporär moderne Entsperrung</string>
<string name="device_credential_unlock_enable_summary">Erlaubt Ihn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden</string>
<string name="temp_advanced_unlock_enable_summary">Keine zur modernen Entsperrung verwendeten, verschlüsselten Inhalte speichern</string>
<string name="temp_advanced_unlock_enable_title">Befristete moderne Entsperrung</string>
<string name="device_credential_unlock_enable_summary">Ermöglicht das Öffnen der Datenbank mit Ihren Geräte-Anmeldeinformationen</string>
<string name="advanced_unlock_tap_delete">Drücken, um Schlüssel für modernes Entsperren zu löschen</string>
<string name="content">Inhalt</string>
<string name="advanced_unlock_prompt_extract_credential_title">Datenbank mit moderner Entsperrerkennung öffnen</string>
@@ -527,7 +527,7 @@
<string name="select_entry">Wähle Eintrag</string>
<string name="back_to_previous_keyboard">Zurück zur vorherigen Tastatur</string>
<string name="custom_fields">Benutzerdefinierte Felder</string>
<string name="advanced_unlock_delete_all_key_warning">Alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen, löschen\?</string>
<string name="advanced_unlock_delete_all_key_warning">Alle Verschlüsselungsschlüssel, die mit der modernen Entsperrerkennung zusammenhängen, löschen\?</string>
<string name="device_credential_unlock_enable_title">Geräteanmeldedaten entsperren</string>
<string name="device_credential">Geräteanmeldedaten</string>
<string name="credential_before_click_advanced_unlock_button">Geben Sie das Passwort ein und klicken Sie dann auf diesen Knopf.</string>
@@ -543,9 +543,9 @@
<string name="error_file_to_big">Die Datei, die hochgeladen werden soll, ist zu groß.</string>
<string name="warning_database_info_changed">Die in Ihrer Datenbank enthaltenen Informationen wurden außerhalb der App geändert.</string>
<string name="error_remove_file">Beim Löschen der Datei trat ein Fehler auf.</string>
<string name="error_duplicate_file">Die Datei gibt es bereits.</string>
<string name="error_duplicate_file">Die Datei existiert bereits.</string>
<string name="error_upload_file">Beim Hochladen der Datei trat ein Fehler auf.</string>
<string name="import_app_properties_title">Importieren von Anwendungeneigenschaften</string>
<string name="import_app_properties_title">App-Eigenschaften importieren</string>
<string name="error_start_database_action">Beim Ausführen einer Aktion in der Datenbank ist ein Fehler aufgetreten.</string>
<string name="error_otp_type">Der vorhandene Einmalpassworttyp wird von diesem Formular nicht erkannt, seine Validierung erzeugt das Token möglicherweise nicht mehr korrekt.</string>
<string name="content_description_otp_information">Informationen zu Einmalpasswörtern</string>
@@ -554,8 +554,23 @@
<string name="success_export_app_properties">App-Eigenschaften exportiert</string>
<string name="error_import_app_properties">Fehler beim Importieren der App-Eigenschaften</string>
<string name="success_import_app_properties">App-Eigenschaften importiert</string>
<string name="export_app_properties_summary">Erstellen einer Datei zum Exportieren von App-Eigenschaften</string>
<string name="export_app_properties_summary">Datei zum Exportieren von App-Eigenschaften erstellen</string>
<string name="export_app_properties_title">App-Eigenschaften exportieren</string>
<string name="import_app_properties_summary">Wählen Sie eine Datei zum Importieren von App-Eigenschaften</string>
<string name="import_app_properties_summary">Datei zum Importieren von App-Eigenschaften auswählen</string>
<string name="error_move_group_here">Sie können hier keine Gruppe verschieben.</string>
<string name="autofill_inline_suggestions_title">Ausfüllvorschläge</string>
<string name="error_word_reserved">Dieses Wort ist reserviert und kann nicht verwendet werden.</string>
<string name="icon_section_custom">Benutzerdefiniert</string>
<string name="icon_section_standard">Standard</string>
<string name="style_brightness_summary">Helles oder dunkles Design auswählen</string>
<string name="style_brightness_title">Designhelligkeit</string>
<string name="unit_gibibyte">GiB</string>
<string name="unit_mebibyte">MiB</string>
<string name="unit_kibibyte">KiB</string>
<string name="unit_byte">B</string>
<string name="download_canceled">Abgebrochen!</string>
<string name="autofill_inline_suggestions_keyboard">Vorschläge für automatisches Ausfüllen hinzugefügt.</string>
<string name="autofill_inline_suggestions_summary">Wenn möglich unmittelbar Vorschläge zum automatischen Ausfüllen auf einer kompatiblen Tastatur anzeigen</string>
<string name="properties">Eigenschaften</string>
<string name="description_app_properties">KeePassDX-Eigenschaften zur Verwaltung der App-Einstellungen</string>
</resources>

View File

@@ -26,7 +26,7 @@
<string name="add_entry">Añadir entrada</string>
<string name="add_group">Añadir grupo</string>
<string name="encryption_algorithm">Algoritmo de cifrado</string>
<string name="app_timeout">Tiempo de espera de la aplicación excedido</string>
<string name="app_timeout">Tiempo de espera excedido</string>
<string name="app_timeout_summary">Inactividad antes del bloqueo de aplicación</string>
<string name="application">Aplicación</string>
<string name="menu_app_settings">Configuración de la aplicación</string>
@@ -436,7 +436,7 @@
<string name="database_opened">Base de datos abierta</string>
<string name="education_add_attachment_title">Adjuntar</string>
<string name="education_add_attachment_summary">Cargue un archivo adjunto a la entrada para guardar datos externos importantes.</string>
<string name="hide_expired_entries_summary">Las entradas caducadas no se muestran</string>
<string name="hide_expired_entries_summary">Las entradas caducadas no se están mostrando</string>
<string name="warning_remove_unlinked_attachment">La eliminación de datos no vinculados puede disminuir el tamaño de su base de datos, pero también puede eliminar los datos usados por los complementos de KeePass.</string>
<string name="warning_file_too_big">Se supone que una base de datos KeePass contiene solo pequeños archivos de utilidad (como archivos de clave PGP).
\n
@@ -492,7 +492,7 @@
<string name="hide_expired_entries_title">Ocultar las entradas expiradas</string>
<string name="keyboard_search_share_title">Buscar información compartida</string>
<string name="upload_attachment">Subir %1$s</string>
<string name="education_setup_OTP_summary">Configurar la gestión de contraseñas de una sola vez (HOTP / TOTP) para generar un token solicitado para la autenticación de dos factores (2FA).</string>
<string name="education_setup_OTP_summary">Configurar la gestión de contraseñas de un solo uso (HOTP / TOTP) para generar un token solicitado para la autenticación de dos factores (2FA).</string>
<string name="education_setup_OTP_title">Establecer la contaseña de un solo uso</string>
<string name="education_advanced_unlock_summary">Vincule su contraseña con su credencial biométrica o del dispositivo escaneada para desbloquear rápidamente su base de datos.</string>
<string name="education_advanced_unlock_title">Desbloqueo avanzado de la base de datos</string>
@@ -542,6 +542,26 @@
<string name="menu_reload_database">Recargar la base de datos</string>
<string name="error_otp_type">El tipo de OTP existente no es reconocido por este formulario, su validación ya no puede generar correctamente el token.</string>
<string name="download_canceled">¡Cancelado!</string>
<string name="error_duplicate_file">Los datos de archivo ya existen.</string>
<string name="error_upload_file">Ha habido un error al subir el archivo de datos.</string>
<string name="error_duplicate_file">Los datos del archivo ya existen.</string>
<string name="error_upload_file">Error al subir datos del archivo.</string>
<string name="description_app_properties">Propiedades de KeePassDX para gestionar la configuración de la aplicación</string>
<string name="content_description_otp_information">Información de contraseña de un solo uso</string>
<string name="icon_section_custom">Personalizado</string>
<string name="icon_section_standard">Estándar</string>
<string name="style_brightness_summary">Seleccionar temas oscuros o claros</string>
<string name="style_brightness_title">Brillo del tema</string>
<string name="properties">Propiedades</string>
<string name="error_import_app_properties">Error al importar las propiedades de la aplicación</string>
<string name="error_export_app_properties">Error al exportar las propiedades de la aplicación</string>
<string name="success_export_app_properties">Propiedades de la aplicación exportadas</string>
<string name="success_import_app_properties">Propiedades de la aplicación importadas</string>
<string name="export_app_properties_summary">Cree un archivo para exportar las propiedades de la aplicación</string>
<string name="export_app_properties_title">Exportar propiedades de la aplicación</string>
<string name="import_app_properties_summary">Seleccione un archivo para importar las propiedades de la aplicación</string>
<string name="import_app_properties_title">Importar propiedades de la aplicación</string>
<string name="error_start_database_action">Ocurrió un error al realizar una acción en la base de datos.</string>
<string name="error_remove_file">Ocurrió un error al eliminar los datos del archivo.</string>
<string name="error_file_to_big">El archivo que está tratando de cargar es demasiado grande.</string>
<string name="error_move_group_here">No puede mover un grupo aquí.</string>
<string name="error_word_reserved">Esta palabra está reservada y no se puede usar.</string>
</resources>

View File

@@ -18,7 +18,7 @@
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
-->
<resources>
<string name="feedback">प्रतिक्रिय</string>
<string name="feedback">प्प्रतिपुष्टि</string>
<string name="homepage">होमपेज</string>
<string name="about_description">एंड्रॉयड पर आधारित KeePass पासवर्ड मैनेजर</string>
<string name="accept">स्वीकार</string>
@@ -35,7 +35,7 @@
<string name="extended_ASCII">विस्तारित ASCII</string>
<string name="allow">अनुमति दें</string>
<string name="clipboard_cleared">क्लिपबोर्ड साफ कर दिया</string>
<string name="clipboard_error_title">क्लिपबोर्ड त्रुटि</string>
<string name="clipboard_error_title">क्लिपबोर्ड एरर</string>
<string name="clipboard_error">सैमसंग के कुछ एंड्रॉइड फोन क्लिपबोर्ड का उपयोग नहीं करने देंगे।</string>
<string name="clipboard_error_clear">क्लिपबोर्ड को साफ़ नहीं किया जा सका</string>
<string name="clipboard_timeout">क्लिपबोर्ड टाइमआउट</string>
@@ -104,7 +104,7 @@
<string name="otp_counter">काउंटर</string>
<string name="otp_digits">अंक</string>
<string name="otp_algorithm">एल्गोरिथ्म</string>
<string name="entry_otp">OTP</string>
<string name="entry_otp">ओटीप</string>
<string name="error_invalid_OTP">अमान्य ओटीपी गुप्त।</string>
<string name="error_disallow_no_credentials">कम से कम एक क्रेडेंशियल सेट किया जाना चाहिए।</string>
</resources>

View File

@@ -367,7 +367,7 @@
<string name="contribution">Doprinos</string>
<string name="error_label_exists">Ova oznaka već postoji.</string>
<string name="warning_database_read_only">Za spremanje promjena u bazi podataka, datoteci dozvoli pisanje</string>
<string name="app_timeout">Istek vremena aplikacije</string>
<string name="app_timeout">Istek vremena</string>
<string name="content_description_repeat_toggle_password_visibility">Ponovo uklj/isklj vidljivosti lozinke</string>
<string name="warning_password_encoding">Izbjegni u lozinkama koristiti znakove koji su izvan formata kodiranja teksta u datoteci baze podataka (neprepoznati znakovi pretvaraju se u isto slovo).</string>
<string name="rounds_explanation">Dodatni prolazi šifriranja pružaju veću zaštitu od brutalnih napada, ali stvarno mogu usporiti učitavanje i spremanje.</string>
@@ -555,4 +555,6 @@
<string name="import_app_properties_summary">Odaberi datoteku za uvoz svojstva aplikacije</string>
<string name="import_app_properties_title">Uvezi svojstva aplikacije</string>
<string name="error_start_database_action">Došlo je do greške tijekom izvođenja radnje u bazi podataka.</string>
<string name="error_move_group_here">Grupa se ne može ovdje premjestiti.</string>
<string name="error_word_reserved">Ova je riječ rezervirana i ne može se koristiti.</string>
</resources>

View File

@@ -160,7 +160,7 @@
<string name="extended_ASCII">ASCII Diperluas</string>
<string name="brackets">Tanda Kurung</string>
<string name="application">Aplikasi</string>
<string name="app_timeout">Batas Waktu Aplikasi</string>
<string name="app_timeout">Waktu habis</string>
<string name="key_derivation_function">Fungsi Derivasi Kunci</string>
<string name="encryption_algorithm">Algoritma Enkripsi</string>
<string name="encryption">Enkripsi</string>

View File

@@ -564,4 +564,5 @@
<string name="import_app_properties_summary">Seleziona un file da cui importare le proprietà dell\'app</string>
<string name="import_app_properties_title">Importa le proprietà dell\'app</string>
<string name="error_word_reserved">Questa parola è riservata e non può essere usata.</string>
<string name="error_move_group_here">Non è possibile spostare un gruppo qui.</string>
</resources>

View File

@@ -31,7 +31,7 @@
<string name="encryption">暗号化</string>
<string name="encryption_algorithm">暗号化アルゴリズム</string>
<string name="key_derivation_function">鍵導出関数</string>
<string name="app_timeout">アプリのタイムアウト</string>
<string name="app_timeout">タイムアウト</string>
<string name="app_timeout_summary">この期間アプリの操作がなかった場合、データベースをロックします</string>
<string name="application">アプリ</string>
<string name="brackets">かっこ</string>
@@ -559,4 +559,6 @@
<string name="export_app_properties_summary">アプリのプロパティをエクスポートするファイルを作成します</string>
<string name="export_app_properties_title">アプリのプロパティをエクスポートする</string>
<string name="error_start_database_action">データベースに対するアクションの実行中にエラーが発生しました。</string>
<string name="error_word_reserved">この単語は予約語のため使用できません。</string>
<string name="error_move_group_here">グループをここに移動できません。</string>
</resources>

View File

@@ -560,4 +560,6 @@
<string name="import_app_properties_summary">Wybierz plik, aby zaimportować właściwości aplikacji</string>
<string name="import_app_properties_title">Importuj właściwości aplikacji</string>
<string name="error_start_database_action">Wystąpił błąd podczas wykonywania akcji w bazie danych.</string>
<string name="error_move_group_here">Nie możesz przenieść tutaj grupy.</string>
<string name="error_word_reserved">To słowo jest zastrzeżone i nie może być używane.</string>
</resources>

View File

@@ -255,7 +255,7 @@
<string name="auto_focus_search_summary">Solicitar uma pesquisa quando abrir a base de dados</string>
<string name="auto_focus_search_title">Pesquisa rápida</string>
<string name="omit_backup_search_summary">Omite os grupos \"Backup\" e \"Cesto da reciclagem\" dos resultados da busca</string>
<string name="omit_backup_search_title">Não procurar por entradas no backup ou na lixeira</string>
<string name="omit_backup_search_title">Não procurar por entradas no backup ou no lixo</string>
<string name="about">Sobre</string>
<string name="hide_password_summary">Mascarar palavras-passe (***) por predefinição</string>
<string name="hide_password_title">Esconder palavras-passe</string>
@@ -318,7 +318,7 @@
<string name="error_otp_period">O período deve estar entre %1$d e %2$d segundos.</string>
<string name="error_otp_counter">O contador deve estar entre %1$d e %2$d.</string>
<string name="error_otp_secret_key">A chave secreta deve estar em formato Base32.</string>
<string name="error_copy_group_here">Mão pode copiar um grupo aqui.</string>
<string name="error_copy_group_here">Não pode copiar um grupo aqui.</string>
<string name="error_disallow_no_credentials">Ao menos uma credencial deve ser definida.</string>
<string name="error_invalid_OTP">Segredo OTP inválido.</string>
<string name="entry_otp">OTP</string>
@@ -400,7 +400,7 @@
<string name="build_label">Compilação %1$s</string>
<string name="retrieving_db_key">A criar a chave da base de dados…</string>
<string name="clipboard">Área de transferência</string>
<string name="list_entries_show_username_summary">Mostrar nomes de utilizador em listas de entrada</string>
<string name="list_entries_show_username_summary">Mostrar nomes de utilizador na lista entradas</string>
<string name="list_entries_show_username_title">Mostrar nomes de utilizador</string>
<string name="error_load_database_KDF_memory">Não foi possível carregar a chave. Tente descarregar o \"Uso de Memória\" do KDF.</string>
<string name="error_load_database">Não foi possível abrir a sua base de dados.</string>
@@ -441,7 +441,7 @@
<string name="clipboard_error">Alguns aparelhos não deixam as apps usarem a área de transferência.</string>
<string name="clipboard_error_title">Erro na área de transferência</string>
<string name="allow">Permitir</string>
<string name="extended_ASCII">ASCII Estendido</string>
<string name="extended_ASCII">ASCII Extendido</string>
<string name="brackets">Parênteses</string>
<string name="application">App</string>
<string name="app_timeout_summary">Inatividade antes de bloquear a base de dados</string>
@@ -452,4 +452,30 @@
<string name="add_group">Adicionar grupo</string>
<string name="add_entry">Adicionar entrada</string>
<string name="accept">Aceitar</string>
<string name="device_credential">Credencial do aparelho</string>
<string name="advanced_unlock_prompt_not_initialized">Incapaz de inicializar o desbloqueio antecipado.</string>
<string name="advanced_unlock_scanning_error">Erro de desbloqueio avançado: %1$s</string>
<string name="advanced_unlock_not_recognized">Não conseguia reconhecer impressão de desbloqueio avançado</string>
<string name="advanced_unlock_invalid_key">Não consegue ler a chave de desbloqueio avançada. Por favor, apague-a e repita o procedimento de desbloqueio de reconhecimento.</string>
<string name="advanced_unlock_prompt_extract_credential_message">Extrair credencial de base de dados com dados de desbloqueio avançados</string>
<string name="advanced_unlock_prompt_extract_credential_title">Base de dados aberta com reconhecimento avançado de desbloqueio</string>
<string name="advanced_unlock_prompt_store_credential_message">Advertência: Ainda precisa de se lembrar da sua palavra-passe principal se usar o reconhecimento avançado de desbloqueio.</string>
<string name="advanced_unlock_prompt_store_credential_title">Reconhecimento avançado de desbloqueio</string>
<string name="open_advanced_unlock_prompt_store_credential">Abrir o alerta de desbloqueio avançado para armazenar as credenciais</string>
<string name="open_advanced_unlock_prompt_unlock_database">Abrir o alerta de desbloqueio avançado para desbloquear a base de dados</string>
<string name="biometric_security_update_required">É necessária uma actualização de segurança biométrica.</string>
<string name="configure_biometric">O escaneamento biométrico é suportado, mas não configurado.</string>
<string name="warning_database_revoked">Acesso ao ficheiro revogado pelo gestor do ficheiro, fechar a base de dados e reabri-la a partir da sua localização.</string>
<string name="warning_database_info_changed_options">Sobregravar as modificações externas, guardando a base de dados ou recarregando-a com as últimas alterações.</string>
<string name="warning_database_info_changed">A informação contida no seu ficheiro de base de dados foi modificada fora da aplicação.</string>
<string name="warning_empty_recycle_bin">Apagar permanentemente todos os nós do caixote do lixo da reciclagem\?</string>
<string name="registration_mode">Modo de registo</string>
<string name="save_mode">Modo Guardar</string>
<string name="search_mode">Modo de pesquisa</string>
<string name="menu_keystore_remove_key">Apagar chave de desbloqueio avançada</string>
<string name="menu_reload_database">Recarregar base de dados</string>
<string name="error_rebuild_list">Incapaz de reconstruir adequadamente a lista.</string>
<string name="error_database_uri_null">O URI da base de dados não pode ser recuperado.</string>
<string name="error_field_name_already_exists">O nome do campo já existe.</string>
<string name="error_registration_read_only">Salvar um novo item não é permitido numa base de dados só de leitura</string>
</resources>

View File

@@ -27,7 +27,7 @@
<string name="app_timeout">延时</string>
<string name="app_timeout_summary">在锁定数据库前处于非活动状态的时长</string>
<string name="application">应用</string>
<string name="menu_app_settings">程序设置</string>
<string name="menu_app_settings">应用设置</string>
<string name="brackets">括号</string>
<string name="file_manager_install_description">需要一款接受意图操作 ACTION_CREATE_DOCUMENT 和 ACTION_OPEN_DOCUMENT 的文件管理器来创建、打开和保存数据库文件。</string>
<string name="clipboard_cleared">剪贴板已清空</string>
@@ -46,14 +46,14 @@
<string name="entry_cancel">取消</string>
<string name="entry_notes">备注</string>
<string name="entry_confpassword">确认密码</string>
<string name="entry_created">建时间</string>
<string name="entry_created">建时间</string>
<string name="entry_expires">过期时间</string>
<string name="entry_keyfile">密钥文件</string>
<string name="entry_modified">修改时间</string>
<string name="entry_password">密码</string>
<string name="save">保存</string>
<string name="entry_title">名称</string>
<string name="entry_url">链接</string>
<string name="entry_url">网址</string>
<string name="entry_user_name">用户名</string>
<string name="error_arc4">不支持Arcfour流式加密。</string>
<string name="error_can_not_handle_uri">无法在KeePassDX中处理此URI。</string>
@@ -96,7 +96,7 @@
<string name="menu_lock">锁定数据库</string>
<string name="menu_open">打开</string>
<string name="menu_search">搜索</string>
<string name="menu_url">打开链接</string>
<string name="menu_url">打开网址</string>
<string name="minus">减号</string>
<string name="never">从不</string>
<string name="no_results">没有搜索结果</string>
@@ -128,14 +128,14 @@
<string name="extended_ASCII">ASCII拓展区字符</string>
<string name="allow">允许</string>
<string name="clipboard_error_title">剪切板错误</string>
<string name="clipboard_error">些设备不允许程序使用剪板。</string>
<string name="clipboard_error">些设备不允许应用程序使用剪板。</string>
<string name="clipboard_error_clear">无法清空剪切板</string>
<string name="style_choose_title">主题</string>
<string name="icon_pack_choose_title">图标包</string>
<string name="icon_pack_choose_summary">程序中使用的图标包</string>
<string name="edit_entry">编辑条目</string>
<string name="key_derivation_function">密钥推导函数</string>
<string name="entry_not_found">找不到条目。</string>
<string name="entry_not_found">找不到条目数据</string>
<string name="error_load_database">无法加载数据库。</string>
<string name="error_load_database_KDF_memory">无法加载密钥。尝试降低KDF的“内存使用”值。</string>
<string name="error_autofill_enable_service">无法启用自动填充服务。</string>
@@ -153,10 +153,10 @@
<string name="menu_file_selection_read_only">只读</string>
<string name="menu_open_file_read_and_write">可修改</string>
<string name="omit_backup_search_title">搜索时忽略备份条目</string>
<string name="omit_backup_search_summary">搜索忽略“备份”“回收站”群组</string>
<string name="omit_backup_search_summary">搜索结果中忽略“备份”“回收站”群组</string>
<string name="protection">保护</string>
<string name="read_only">只读</string>
<string name="read_only_warning">KeePassDX需要写入权限以修改数据</string>
<string name="read_only_warning">根据您的文件管理器KeePassDX 可能不允许在您的存储中写入数据。</string>
<string name="show_recent_files_title">最近文件历史</string>
<string name="show_recent_files_summary">记住最近使用的文件名</string>
<string name="encryption_explanation">加密所有数据时采用的算法。</string>
@@ -178,8 +178,8 @@
<string name="menu_appearance_settings">外观</string>
<string name="general">常规</string>
<string name="autofill">自动填充</string>
<string name="autofill_service_name">使用KeePassDX自动填充</string>
<string name="autofill_sign_in_prompt">使用KeePassDX密码登录</string>
<string name="autofill_service_name">KeePassDX 自动填充</string>
<string name="autofill_sign_in_prompt">使用 KeePassDX 登录</string>
<string name="clipboard">剪贴板</string>
<string name="clipboard_notifications_title">剪贴板通知</string>
<string name="lock">锁定</string>
@@ -334,7 +334,7 @@
<string name="content_description_remove_field">删除字段</string>
<string name="entry_UUID">UUID</string>
<string name="error_move_entry_here">无法移动条目到此处。</string>
<string name="error_copy_entry_here">无法复制条目到此处</string>
<string name="error_copy_entry_here">您不能在此处复制条目。</string>
<string name="list_groups_show_number_entries_title">显示条目数量</string>
<string name="list_groups_show_number_entries_summary">显示群组中的条目数</string>
<string name="content_description_background">背景</string>
@@ -350,17 +350,17 @@
<string name="master_key">主密钥</string>
<string name="security">安全</string>
<string name="entry_history">历史</string>
<string name="entry_setup_otp">设置一次性密码</string>
<string name="otp_type">一次性密码类型</string>
<string name="entry_setup_otp">设置 OTP</string>
<string name="otp_type">OTP 类型</string>
<string name="otp_secret">密钥</string>
<string name="otp_period">时长(秒)</string>
<string name="otp_counter">计数器</string>
<string name="otp_digits">数字位数</string>
<string name="otp_algorithm">算法</string>
<string name="entry_otp">一次性密码</string>
<string name="entry_otp">OTP</string>
<string name="error_invalid_OTP">错误的一次性密码密钥。</string>
<string name="error_disallow_no_credentials">至少需要设置一个凭据。</string>
<string name="error_copy_group_here">这里不能复制组。</string>
<string name="error_copy_group_here">您无法在此处复制组。</string>
<string name="error_otp_secret_key">密钥必须是BASE32格式。</string>
<string name="error_otp_counter">计数器必须在%1$d和%2$d之间。</string>
<string name="error_otp_period">时长必须在%1$d秒到%2$d秒之间。</string>
@@ -369,11 +369,11 @@
<string name="creating_database">新建数据库…</string>
<string name="menu_security_settings">安全设置</string>
<string name="menu_master_key_settings">主密钥设置</string>
<string name="contains_duplicate_uuid">数据库包含重复UUID。</string>
<string name="contains_duplicate_uuid_procedure">通过为重复项生成新的UUID以解决问题</string>
<string name="contains_duplicate_uuid">数据库包含重复UUID。</string>
<string name="contains_duplicate_uuid_procedure">通过为重复项生成新的 UUID 以解决问题?</string>
<string name="database_opened">数据库开启</string>
<string name="clipboard_explanation_summary">使用设备的剪贴板来复制输入字段</string>
<string name="advanced_unlock_explanation_summary">使用高级解锁轻松打开数据库</string>
<string name="advanced_unlock_explanation_summary">使用高级解锁以便快速解锁数据库</string>
<string name="database_data_compression_title">数据压缩</string>
<string name="database_data_compression_summary">数据压缩减少了数据库的大小</string>
<string name="max_history_items_title">最大数量</string>
@@ -444,7 +444,7 @@
<string name="autofill_block_restart">重新启动包含该表单的应用程序以激活拦截。</string>
<string name="autofill_block">阻止自动填充</string>
<string name="autofill_web_domain_blocklist_summary">禁止在下列域名中自动填充凭证</string>
<string name="autofill_web_domain_blocklist_title">Web域名黑名单</string>
<string name="autofill_web_domain_blocklist_title">Web 域名黑名单</string>
<string name="autofill_application_id_blocklist_summary">禁止应用程序自动填充的黑名单</string>
<string name="autofill_application_id_blocklist_title">应用拦截列表</string>
<string name="filter">过滤器</string>
@@ -508,8 +508,8 @@
<string name="advanced_unlock_prompt_extract_credential_title">用高级解锁识别打开数据库</string>
<string name="advanced_unlock_prompt_store_credential_message">警告:即使您使用高级解锁识别,您仍然需要记住您的主密码。</string>
<string name="advanced_unlock_prompt_store_credential_title">高级解锁识别</string>
<string name="open_advanced_unlock_prompt_store_credential">打开高级解锁提示来存储凭证</string>
<string name="open_advanced_unlock_prompt_unlock_database">打开高级解锁提示来解锁数据库</string>
<string name="open_advanced_unlock_prompt_store_credential">点击以打开高级解锁提示来存储凭证</string>
<string name="open_advanced_unlock_prompt_unlock_database">点击以使用生物识别解锁</string>
<string name="menu_keystore_remove_key">删除高级解锁密钥</string>
<string name="enter">输入</string>
<string name="backspace">退格键</string>
@@ -550,15 +550,15 @@
<string name="error_remove_file">删除文件数据时发生了一个错误。</string>
<string name="error_duplicate_file">文件数据已存在。</string>
<string name="properties">属性</string>
<string name="error_export_app_properties">应用属性导出期间出错</string>
<string name="success_export_app_properties">已导出应用属性</string>
<string name="error_import_app_properties">导入应用属性期间出错</string>
<string name="success_import_app_properties">已导入应用属性</string>
<string name="description_app_properties">管理应用设置的 KeePassDX 属性</string>
<string name="export_app_properties_summary">创建一个文件导出应用属性</string>
<string name="export_app_properties_title">导出应用属性</string>
<string name="import_app_properties_summary">选择一个文件导入应用属性</string>
<string name="import_app_properties_title">导入应用属性</string>
<string name="error_export_app_properties">导出应用配置时出错</string>
<string name="success_export_app_properties">已导出应用配置</string>
<string name="error_import_app_properties">导入应用配置时出错</string>
<string name="success_import_app_properties">已导入应用配置</string>
<string name="description_app_properties">管理应用设置的 KeePassDX 配置</string>
<string name="export_app_properties_summary">创建一个文件导出应用配置</string>
<string name="export_app_properties_title">导出配置</string>
<string name="import_app_properties_summary">选择一个文件导入应用配置</string>
<string name="import_app_properties_title">导入配置</string>
<string name="error_start_database_action">对数据库执行操作时发生了一个错误。</string>
<string name="error_move_group_here">你不能把一个组移动到此处。</string>
<string name="error_word_reserved">这个单词是保留的,不能使用。</string>

View File

@@ -49,9 +49,9 @@ platform :android do
lane :deploy_beta_free do
upload_to_play_store(
track: "beta",
skip_upload_metadata: "false",
skip_upload_images: "false",
skip_upload_screenshots: "false",
skip_upload_metadata: "true",
skip_upload_images: "true",
skip_upload_screenshots: "true",
apk: "./app/build/outputs/apk/free/release/app-free-release.apk",
validate_only: "false",
)
@@ -62,9 +62,9 @@ platform :android do
sh("cp", "-a", "./pro/.", "./")
upload_to_play_store(
track: "beta",
skip_upload_metadata: "false",
skip_upload_images: "false",
skip_upload_screenshots: "false",
skip_upload_metadata: "true",
skip_upload_images: "true",
skip_upload_screenshots: "true",
apk: "./app/build/outputs/apk/pro/release/app-pro-release.apk",
validate_only: "false",
)

View File

@@ -0,0 +1,3 @@
* Fix search with non-latin chars #971
* Fix action mode with search #972 (rollback ignore accents #945)
* Fix timeout with 0s #974

View File

@@ -0,0 +1,3 @@
* Manage new database format 4.1 #956
* Fix show button consistency #980
* Fix persistent notification #979

View File

@@ -0,0 +1 @@
* Fix parcelable with custom data #986

View File

@@ -0,0 +1,2 @@
* Fix search fields references #987
* Fix Auto-Types with same key #997

View File

@@ -0,0 +1,3 @@
* Correction de la recherche avec des caractères non latin #971
* Correction du mode action avec la recherche #972 (retour arrière des accents ignorés #945)
* Correction de l'expiration de temps à 0s #974

View File

@@ -0,0 +1,3 @@
* Gestion du nouveau format de base de données 4.1 #956
* Correction de la consistance du bouton de visibilité #980
* Correction de la notification persistante #979

View File

@@ -0,0 +1 @@
* Correction des parcelable avec données customisées #986

View File

@@ -0,0 +1,2 @@
* Correction de la recherche des références de champs #987
* Correction des Auto-Types avec la même clé #997