Merge branch 'develop' into feature/Database_Call

This commit is contained in:
J-Jamet
2021-06-02 11:41:07 +02:00
61 changed files with 1095 additions and 578 deletions

View File

@@ -1,4 +1,16 @@
KeePassDX(3.0.0) 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 search with non-latin chars #971
* Fix action mode with search #972 (rollback ignore accents #945) * Fix action mode with search #972 (rollback ignore accents #945)
* Fix timeout with 0s #974 * Fix timeout with 0s #974

View File

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

View File

@@ -5,6 +5,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.kunzisoft.keepass.R import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.icon.IconImageDraw 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) { override fun onBindViewHolder(holder: CustomIconViewHolder, position: Int) {
val icon = iconList[position] val icon = iconList[position]
iconDrawableFactory?.assignDatabaseIcon(holder.iconImageView, icon, tintIcon) 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.iconContainerView.isSelected = icon.selected
holder.itemView.setOnClickListener { holder.itemView.setOnClickListener {
iconPickerListener?.onIconClickListener(icon) 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) { inner class CustomIconViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var iconContainerView: ViewGroup = itemView.findViewById(R.id.icon_container) var iconContainerView: ViewGroup = itemView.findViewById(R.id.icon_container)
var iconImageView: ImageView = itemView.findViewById(R.id.icon_image) 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() { override fun onResume() {
super.onResume() super.onResume()
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(requireContext()) context?.let {
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(requireContext()) mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(it)
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(it)
}
keepConnection = false keepConnection = false
} }
@@ -175,34 +177,36 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
* Check unlock availability and change the current mode depending of device's state * Check unlock availability and change the current mode depending of device's state
*/ */
fun checkUnlockAvailability() { fun checkUnlockAvailability() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { context?.let { context ->
allowOpenBiometricPrompt = true if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (PreferencesUtil.isBiometricUnlockEnable(requireContext())) { allowOpenBiometricPrompt = true
mAdvancedUnlockInfoView?.setIconResource(R.drawable.fingerprint) if (PreferencesUtil.isBiometricUnlockEnable(context)) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.fingerprint)
// biometric not supported (by API level or hardware) so keep option hidden // biometric not supported (by API level or hardware) so keep option hidden
// or manually disable // or manually disable
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext()) val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(context)
if (!PreferencesUtil.isAdvancedUnlockEnable(requireContext()) if (!PreferencesUtil.isAdvancedUnlockEnable(context)
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) { || biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
toggleMode(Mode.BIOMETRIC_UNAVAILABLE) toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED) { } else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED) {
toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED) toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
} else {
// biometric is available but not configured, show icon but in disabled state with some information
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
} else { } else {
selectMode() // biometric is available but not configured, show icon but in disabled state with some information
if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
} else {
selectMode()
}
}
} else if (PreferencesUtil.isDeviceCredentialUnlockEnable(context)) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.bolt)
if (AdvancedUnlockManager.isDeviceSecure(context)) {
selectMode()
} else {
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
} }
}
} else if (PreferencesUtil.isDeviceCredentialUnlockEnable(requireContext())) {
mAdvancedUnlockInfoView?.setIconResource(R.drawable.bolt)
if (AdvancedUnlockManager.isDeviceSecure(requireContext())) {
selectMode()
} else {
toggleMode(Mode.DEVICE_CREDENTIAL_OR_BIOMETRIC_NOT_CONFIGURED)
} }
} }
} }
@@ -260,7 +264,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
private fun openBiometricSetting() { private fun openBiometricSetting() {
mAdvancedUnlockInfoView?.setIconViewClickListener(false) { mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices... // 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) setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
setAdvancedUnlockedMessageView("") setAdvancedUnlockedMessageView("")
mAdvancedUnlockInfoView?.setIconViewClickListener(false) { context?.let { context ->
onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS, mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
requireContext().getString(R.string.credential_before_click_advanced_unlock_button)) onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
context.getString(R.string.credential_before_click_advanced_unlock_button))
}
} }
} }

View File

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

View File

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

View File

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

View File

@@ -42,7 +42,7 @@ class ExtraFieldCursor : MatrixCursor(arrayOf(
} }
fun populateExtraFieldInEntry(pwEntry: EntryKDBX) { fun populateExtraFieldInEntry(pwEntry: EntryKDBX) {
pwEntry.putExtraField(getString(getColumnIndex(COLUMN_LABEL)), pwEntry.putField(getString(getColumnIndex(COLUMN_LABEL)),
ProtectedString(getInt(getColumnIndex(COLUMN_PROTECTION)) > 0, ProtectedString(getInt(getColumnIndex(COLUMN_PROTECTION)) > 0,
getString(getColumnIndex(COLUMN_VALUE)))) 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.exception.*
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
import com.kunzisoft.keepass.database.file.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.DatabaseInputKDB
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
@@ -222,7 +222,7 @@ class Database {
// Default compression not necessary if stored in header // Default compression not necessary if stored in header
mDatabaseKDBX?.let { mDatabaseKDBX?.let {
return it.compressionAlgorithm == CompressionAlgorithm.GZip return it.compressionAlgorithm == CompressionAlgorithm.GZip
&& it.kdbxVersion.isBefore(FILE_VERSION_32_4) && it.kdbxVersion.isBefore(FILE_VERSION_40)
} }
return false return false
} }

View File

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

View File

@@ -19,30 +19,37 @@
*/ */
package com.kunzisoft.keepass.database.element 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 com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import java.util.Date import java.util.*
import java.util.UUID
class DeletedObject { class DeletedObject : Parcelable {
var uuid: UUID = DatabaseVersioned.UUID_ZERO 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) { if (mDeletionTime == null) {
mDeletionTime = Date(System.currentTimeMillis()) mDeletionTime = DateInstant(System.currentTimeMillis())
} }
return mDeletionTime!! return mDeletionTime!!
} }
fun setDeletionTime(deletionTime: Date) { fun setDeletionTime(deletionTime: DateInstant) {
this.mDeletionTime = deletionTime
}
constructor()
constructor(uuid: UUID, deletionTime: Date = Date()) {
this.uuid = uuid
this.mDeletionTime = deletionTime this.mDeletionTime = deletionTime
} }
@@ -59,4 +66,23 @@ class DeletedObject {
override fun hashCode(): Int { override fun hashCode(): Int {
return uuid.hashCode() 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 android.os.Parcelable
import com.kunzisoft.keepass.database.element.binary.AttachmentPool import com.kunzisoft.keepass.database.element.binary.AttachmentPool
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX 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.EntryKDB
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
@@ -114,6 +115,20 @@ class Entry : Node, EntryVersionedInterface<Group> {
entryKDBX?.icon = value 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 override val type: Type
get() = Type.ENTRY get() = Type.ENTRY
@@ -268,8 +283,8 @@ class Entry : Node, EntryVersionedInterface<Group> {
fun getExtraFields(): List<Field> { fun getExtraFields(): List<Field> {
val extraFields = ArrayList<Field>() val extraFields = ArrayList<Field>()
entryKDBX?.let { entryKDBX?.let {
for (field in it.customFields) { it.doForEachDecodedCustomField { key, value ->
extraFields.add(Field(field.key, field.value)) extraFields.add(Field(key, value))
} }
} }
return extraFields return extraFields
@@ -279,7 +294,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
* Update or add an extra field to the list (standard or custom) * Update or add an extra field to the list (standard or custom)
*/ */
fun putExtraField(field: Field) { fun putExtraField(field: Field) {
entryKDBX?.putExtraField(field.name, field.protectedValue) entryKDBX?.putField(field.name, field.protectedValue)
} }
private fun addExtraFields(fields: List<Field>) { private fun addExtraFields(fields: List<Field>) {
@@ -295,7 +310,7 @@ class Entry : Node, EntryVersionedInterface<Group> {
fun getOtpElement(): OtpElement? { fun getOtpElement(): OtpElement? {
entryKDBX?.let { entryKDBX?.let {
return OtpEntryFields.parseFields { key -> return OtpEntryFields.parseFields { key ->
it.customFields[key]?.toString() it.getField(key)?.toString()
} }
} }
return null return null
@@ -373,10 +388,6 @@ class Entry : Node, EntryVersionedInterface<Group> {
return entryKDBX?.getSize(attachmentPool) ?: 0L return entryKDBX?.getSize(attachmentPool) ?: 0L
} }
fun containsCustomData(): Boolean {
return entryKDBX?.containsCustomData() ?: false
}
/* /*
------------ ------------
Converter Converter

View File

@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.database.element
import android.content.Context import android.content.Context
import android.os.Parcel import android.os.Parcel
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.group.GroupKDB import com.kunzisoft.keepass.database.element.group.GroupKDB
import com.kunzisoft.keepass.database.element.group.GroupKDBX import com.kunzisoft.keepass.database.element.group.GroupKDBX
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
@@ -134,6 +135,20 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
groupKDBX?.icon = value 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 override val type: Type
get() = Type.GROUP get() = Type.GROUP
@@ -394,10 +409,6 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
groupKDBX?.isExpanded = expanded groupKDBX?.isExpanded = expanded
} }
fun containsCustomData(): Boolean {
return groupKDBX?.containsCustomData() ?: false
}
/* /*
------------ ------------
Converter 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 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.* 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 { override fun findUnusedKey(): UUID {
var newUUID = UUID.randomUUID() var newUUID = UUID.randomUUID()
@@ -11,4 +30,14 @@ class CustomIconPool(binaryCache: BinaryCache) : BinaryPool<UUID>(binaryCache) {
} }
return newUUID 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) return this.iconsManager.getIcon(iconId)
} }
override fun containsCustomData(): Boolean {
return false
}
override fun isInRecycleBin(group: GroupKDB): Boolean { override fun isInRecycleBin(group: GroupKDB): Boolean {
var currentGroup: GroupKDB? = group var currentGroup: GroupKDB? = group
val currentBackupGroup = backupGroup ?: return false 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.KdfEngine
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters 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.DateInstant
import com.kunzisoft.keepass.database.element.DeletedObject import com.kunzisoft.keepass.database.element.DeletedObject
import com.kunzisoft.keepass.database.element.binary.BinaryData 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.node.NodeVersioned
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
import com.kunzisoft.keepass.database.exception.UnknownKDF 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_31
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.utils.StringUtil.removeSpaceChars import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
import com.kunzisoft.keepass.utils.StringUtil.toHexString import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.UnsignedInt import com.kunzisoft.keepass.utils.UnsignedInt
@@ -102,7 +104,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
*/ */
var isRecycleBinEnabled = true var isRecycleBinEnabled = true
var recycleBinUUID: UUID = UUID_ZERO var recycleBinUUID: UUID = UUID_ZERO
var recycleBinChanged = Date() var recycleBinChanged = DateInstant()
var entryTemplatesGroup = UUID_ZERO var entryTemplatesGroup = UUID_ZERO
var entryTemplatesGroupChanged = DateInstant() var entryTemplatesGroupChanged = DateInstant()
var historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS var historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS
@@ -111,7 +113,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
var lastTopVisibleGroupUUID = UUID_ZERO var lastTopVisibleGroupUUID = UUID_ZERO
var memoryProtection = MemoryProtectionConfig() var memoryProtection = MemoryProtectionConfig()
val deletedObjects = ArrayList<DeletedObject>() val deletedObjects = ArrayList<DeletedObject>()
val customData = HashMap<String, String>() val customData = CustomData()
var localizedAppName = "KeePassDX" var localizedAppName = "KeePassDX"
@@ -128,7 +130,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
*/ */
constructor(databaseName: String, rootName: String) { constructor(databaseName: String, rootName: String) {
name = databaseName name = databaseName
kdbxVersion = FILE_VERSION_32_3 kdbxVersion = FILE_VERSION_31
val group = createGroup().apply { val group = createGroup().apply {
title = rootName title = rootName
icon.standard = getStandardIcon(IconImageStandard.FOLDER_ID) icon.standard = getStandardIcon(IconImageStandard.FOLDER_ID)
@@ -139,8 +141,9 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
override val version: String override val version: String
get() { get() {
val kdbxStringVersion = when(kdbxVersion) { val kdbxStringVersion = when(kdbxVersion) {
FILE_VERSION_32_3 -> "3.1" FILE_VERSION_31 -> "3.1"
FILE_VERSION_32_4 -> "4.0" FILE_VERSION_40 -> "4.0"
FILE_VERSION_41 -> "4.1"
else -> "UNKNOWN" else -> "UNKNOWN"
} }
return "KeePass 2 - KDBX$kdbxStringVersion" return "KeePass 2 - KDBX$kdbxStringVersion"
@@ -188,7 +191,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} }
CompressionAlgorithm.GZip -> { CompressionAlgorithm.GZip -> {
// Only in databaseV3.1, in databaseV4 the header is zipped during the save // 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() compressAllBinaries()
} }
} }
@@ -196,7 +199,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} }
CompressionAlgorithm.GZip -> { CompressionAlgorithm.GZip -> {
// In databaseV4 the header is zipped during the save, so not necessary here // 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) { when (newCompression) {
CompressionAlgorithm.None -> { CompressionAlgorithm.None -> {
decompressAllBinaries() decompressAllBinaries()
@@ -314,9 +317,11 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
} }
fun addCustomIcon(customIconId: UUID? = null, fun addCustomIcon(customIconId: UUID? = null,
name: String,
lastModificationTime: DateInstant?,
smallSize: Boolean, smallSize: Boolean,
result: (IconImageCustom, BinaryData?) -> Unit) { result: (IconImageCustom, BinaryData?) -> Unit) {
iconsManager.addCustomIcon(customIconId, smallSize, result) iconsManager.addCustomIcon(customIconId, name, lastModificationTime, smallSize, result)
} }
fun isCustomIconBinaryDuplicate(binary: BinaryData): Boolean { fun isCustomIconBinaryDuplicate(binary: BinaryData): Boolean {
@@ -327,17 +332,43 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
return this.iconsManager.getIcon(iconUuid) 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 { fun getEntryByUsername(username: String, recursionLevel: Int): EntryKDBX? {
return customData.isNotEmpty() 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? { fun getEntryByCustomData(customDataValue: String): EntryKDBX? {
return entryIndexes.values.find { entry -> 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) addGroupTo(recycleBinGroup, rootGroup)
recycleBinUUID = recycleBinGroup.id recycleBinUUID = recycleBinGroup.id
recycleBinChanged = recycleBinGroup.lastModificationTime.date recycleBinChanged = recycleBinGroup.lastModificationTime
} }
} }
fun removeRecycleBin() { fun removeRecycleBin() {
if (recycleBin != null) { if (recycleBin != null) {
recycleBinUUID = UUID_ZERO recycleBinUUID = UUID_ZERO
recycleBinChanged = DateInstant().date recycleBinChanged = DateInstant()
} }
} }

View File

@@ -271,26 +271,6 @@ abstract class DatabaseVersioned<
return this.entryIndexes[id] 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) { fun addEntryIndex(entry: Entry) {
val entryId = entry.nodeId val entryId = entry.nodeId
if (entryIndexes.containsKey(entryId)) { if (entryIndexes.containsKey(entryId)) {
@@ -337,8 +317,6 @@ abstract class DatabaseVersioned<
abstract fun getStandardIcon(iconId: Int): IconImageStandard abstract fun getStandardIcon(iconId: Int): IconImageStandard
abstract fun containsCustomData(): Boolean
fun addGroupTo(newGroup: Group, parent: Group?) { fun addGroupTo(newGroup: Group, parent: Group?) {
// Add tree to parent tree // Add tree to parent tree
parent?.addChildGroup(newGroup) parent?.addChildGroup(newGroup)

View File

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

View File

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

View File

@@ -19,16 +19,17 @@
*/ */
package com.kunzisoft.keepass.database.element.entry 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.database.DatabaseKDBX
import com.kunzisoft.keepass.database.element.node.NodeIdUUID import com.kunzisoft.keepass.database.element.node.NodeIdUUID
import com.kunzisoft.keepass.utils.UuidUtil import com.kunzisoft.keepass.utils.UuidUtil
import java.util.* import java.util.concurrent.ConcurrentHashMap
class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) { class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) {
// Key : <WantedField>@<SearchIn>:<Text> // Key : <WantedField>@<SearchIn>:<Text>
// Value : content // Value : content
private var refsCache: MutableMap<String, String?> = HashMap() private var refsCache = ConcurrentHashMap<String, String?>()
fun clear() { fun clear() {
refsCache.clear() refsCache.clear()
@@ -53,52 +54,56 @@ class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) {
&& numberInlineRef <= MAX_INLINE_REF) { && numberInlineRef <= MAX_INLINE_REF) {
numberInlineRef++ numberInlineRef++
textValue = fillReferencesUsingCache(textValue) try {
val start = textValue.indexOf(STR_REF_START, offset, true)
if (start < 0) {
break
}
val end = textValue.indexOf(STR_REF_END, offset, true)
if (end <= start) {
break
}
val reference = textValue.substring(start + STR_REF_START.length, end)
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 data: String? = when (result.wanted) {
'T' -> entryFound?.decodeTitleKey(newRecursionLevel)
'U' -> entryFound?.decodeUsernameKey(newRecursionLevel)
'A' -> entryFound?.decodeUrlKey(newRecursionLevel)
'P' -> entryFound?.decodePasswordKey(newRecursionLevel)
'N' -> entryFound?.decodeNotesKey(newRecursionLevel)
'I' -> UuidUtil.toHexString(entryFound?.nodeId?.id)
else -> null
}
refsCache[fullReference] = data
textValue = fillReferencesUsingCache(textValue) textValue = fillReferencesUsingCache(textValue)
}
offset = end val start = textValue.indexOf(STR_REF_START, offset, true)
if (start < 0) {
break
}
val end = textValue.indexOf(STR_REF_END, offset, true)
if (end <= start) {
break
}
val reference = textValue.substring(start + STR_REF_START.length, end)
val fullReference = "$STR_REF_START$reference$STR_REF_END"
if (!refsCache.containsKey(fullReference)) {
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)
'A' -> entryFound?.decodeUrlKey(newRecursionLevel)
'P' -> entryFound?.decodePasswordKey(newRecursionLevel)
'N' -> entryFound?.decodeNotesKey(newRecursionLevel)
'I' -> UuidUtil.toHexString(entryFound?.nodeId?.id)
else -> null
}
refsCache[fullReference] = data
textValue = fillReferencesUsingCache(textValue)
}
offset = end
} catch (e: Exception) {
Log.e(TAG, "Error when fill placeholders by reference", e)
}
} }
return textValue return textValue
} }
private fun fillReferencesUsingCache(text: String): String { private fun fillReferencesUsingCache(text: String): String {
var newText = text var newText = text
for ((key, value) in refsCache) { refsCache.keys.forEach { key ->
// Replace by key if value not found // Replace by key if value not found
newText = newText.replace(key, value ?: key, true) newText = newText.replace(key, refsCache[key] ?: key, true)
} }
return newText return newText
} }
private fun findReferenceTarget(reference: String): TargetResult { private fun findReferenceTarget(reference: String, recursionLevel: Int): TargetResult {
val targetResult = TargetResult(null, 'J') val targetResult = TargetResult(null, 'J')
@@ -116,11 +121,11 @@ class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) {
val searchIn = Character.toUpperCase(reference[2]) val searchIn = Character.toUpperCase(reference[2])
val searchQuery = reference.substring(4) val searchQuery = reference.substring(4)
targetResult.entry = when (searchIn) { targetResult.entry = when (searchIn) {
'T' -> mDatabase.getEntryByTitle(searchQuery) 'T' -> mDatabase.getEntryByTitle(searchQuery, recursionLevel)
'U' -> mDatabase.getEntryByUsername(searchQuery) 'U' -> mDatabase.getEntryByUsername(searchQuery, recursionLevel)
'A' -> mDatabase.getEntryByURL(searchQuery) 'A' -> mDatabase.getEntryByURL(searchQuery, recursionLevel)
'P' -> mDatabase.getEntryByPassword(searchQuery) 'P' -> mDatabase.getEntryByPassword(searchQuery, recursionLevel)
'N' -> mDatabase.getEntryByNotes(searchQuery) 'N' -> mDatabase.getEntryByNotes(searchQuery, recursionLevel)
'I' -> { 'I' -> {
UuidUtil.fromHexString(searchQuery)?.let { uuid -> UuidUtil.fromHexString(searchQuery)?.let { uuid ->
mDatabase.getEntryById(NodeIdUUID(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 MAX_INLINE_REF = 10
private const val STR_REF_START = "{REF:" private const val STR_REF_START = "{REF:"
private const val STR_REF_END = "}" 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 package com.kunzisoft.keepass.database.element.group
import android.os.Parcel import android.os.Parcel
import android.os.ParcelUuid
import android.os.Parcelable import android.os.Parcelable
import com.kunzisoft.keepass.database.element.CustomData
import com.kunzisoft.keepass.database.element.DateInstant 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.database.DatabaseVersioned
import com.kunzisoft.keepass.database.element.entry.EntryKDBX import com.kunzisoft.keepass.database.element.entry.EntryKDBX
import com.kunzisoft.keepass.database.element.node.NodeId import com.kunzisoft.keepass.database.element.node.NodeId
@@ -33,14 +36,17 @@ import java.util.*
class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface { 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 notes = ""
var isExpanded = true var isExpanded = true
var defaultAutoTypeSequence = "" var defaultAutoTypeSequence = ""
var enableAutoType: Boolean? = null var enableAutoType: Boolean? = null
var enableSearching: Boolean? = null var enableSearching: Boolean? = null
var lastTopVisibleEntry: UUID = DatabaseVersioned.UUID_ZERO var lastTopVisibleEntry: UUID = DatabaseVersioned.UUID_ZERO
override var tags = Tags()
override var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
override var expires: Boolean = false override var expires: Boolean = false
@@ -60,7 +66,7 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
constructor(parcel: Parcel) : super(parcel) { constructor(parcel: Parcel) : super(parcel) {
usageCount = UnsignedLong(parcel.readLong()) usageCount = UnsignedLong(parcel.readLong())
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged 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 notes = parcel.readString() ?: notes
isExpanded = parcel.readByte().toInt() != 0 isExpanded = parcel.readByte().toInt() != 0
defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
@@ -69,6 +75,8 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
val isSearchingEnabled = parcel.readInt() val isSearchingEnabled = parcel.readInt()
enableSearching = if (isSearchingEnabled == -1) null else isSearchingEnabled == 1 enableSearching = if (isSearchingEnabled == -1) null else isSearchingEnabled == 1
lastTopVisibleEntry = parcel.readSerializable() as UUID 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? { override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
@@ -83,13 +91,15 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
super.writeToParcel(dest, flags) super.writeToParcel(dest, flags)
dest.writeLong(usageCount.toKotlinLong()) dest.writeLong(usageCount.toKotlinLong())
dest.writeParcelable(locationChanged, flags) dest.writeParcelable(locationChanged, flags)
// TODO ParcelableUtil.writeStringParcelableMap(dest, customData); dest.writeParcelable(customData, flags)
dest.writeString(notes) dest.writeString(notes)
dest.writeByte((if (isExpanded) 1 else 0).toByte()) dest.writeByte((if (isExpanded) 1 else 0).toByte())
dest.writeString(defaultAutoTypeSequence) dest.writeString(defaultAutoTypeSequence)
dest.writeInt(if (enableAutoType == null) -1 else if (enableAutoType!!) 1 else 0) 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.writeInt(if (enableSearching == null) -1 else if (enableSearching!!) 1 else 0)
dest.writeSerializable(lastTopVisibleEntry) dest.writeSerializable(lastTopVisibleEntry)
dest.writeParcelable(tags, flags)
dest.writeParcelable(ParcelUuid(previousParentGroup), flags)
} }
fun updateWith(source: GroupKDBX) { fun updateWith(source: GroupKDBX) {
@@ -97,34 +107,21 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
usageCount = source.usageCount usageCount = source.usageCount
locationChanged = DateInstant(source.locationChanged) locationChanged = DateInstant(source.locationChanged)
// Add all custom elements in map // Add all custom elements in map
customData.clear() customData = CustomData(source.customData)
for ((key, value) in source.customData) {
customData[key] = value
}
notes = source.notes notes = source.notes
isExpanded = source.isExpanded isExpanded = source.isExpanded
defaultAutoTypeSequence = source.defaultAutoTypeSequence defaultAutoTypeSequence = source.defaultAutoTypeSequence
enableAutoType = source.enableAutoType enableAutoType = source.enableAutoType
enableSearching = source.enableSearching enableSearching = source.enableSearching
lastTopVisibleEntry = source.lastTopVisibleEntry lastTopVisibleEntry = source.lastTopVisibleEntry
tags = source.tags
previousParentGroup = source.previousParentGroup
} }
override var usageCount = UnsignedLong(0)
override var locationChanged = DateInstant()
override fun afterAssignNewParent() { override fun afterAssignNewParent() {
locationChanged = DateInstant() locationChanged = DateInstant()
} }
override fun putCustomData(key: String, value: String) {
customData[key] = value
}
override fun containsCustomData(): Boolean {
return customData.isNotEmpty()
}
companion object { companion object {
@JvmField @JvmField

View File

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

View File

@@ -20,6 +20,7 @@
package com.kunzisoft.keepass.database.element.icon package com.kunzisoft.keepass.database.element.icon
import android.util.Log 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.BinaryCache
import com.kunzisoft.keepass.database.element.binary.BinaryData import com.kunzisoft.keepass.database.element.binary.BinaryData
import com.kunzisoft.keepass.database.element.binary.CustomIconPool 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 com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
import java.util.* import java.util.*
class IconsManager(private val binaryCache: BinaryCache) { class IconsManager(binaryCache: BinaryCache) {
private val standardCache = List(NB_ICONS) { private val standardCache = List(NB_ICONS) {
IconImageStandard(it) IconImageStandard(it)
@@ -52,17 +53,15 @@ class IconsManager(private val binaryCache: BinaryCache) {
fun buildNewCustomIcon(key: UUID? = null, fun buildNewCustomIcon(key: UUID? = null,
result: (IconImageCustom, BinaryData?) -> Unit) { result: (IconImageCustom, BinaryData?) -> Unit) {
// Create a binary file for a brand new custom icon // Create a binary file for a brand new custom icon
addCustomIcon(key, false, result) addCustomIcon(key, "", null, false, result)
} }
fun addCustomIcon(key: UUID? = null, fun addCustomIcon(key: UUID? = null,
name: String,
lastModificationTime: DateInstant?,
smallSize: Boolean, smallSize: Boolean,
result: (IconImageCustom, BinaryData?) -> Unit) { result: (IconImageCustom, BinaryData?) -> Unit) {
val keyBinary = customCache.put(key) { uniqueBinaryId -> customCache.put(key, name, lastModificationTime, smallSize, result)
// Create a byte array for better performance with small data
binaryCache.getBinaryData(uniqueBinaryId, smallSize)
}
result.invoke(IconImageCustom(keyBinary.keys.first()), keyBinary.binary)
} }
fun getIcon(iconUuid: UUID): IconImageCustom { fun getIcon(iconUuid: UUID): IconImageCustom {
@@ -88,8 +87,12 @@ class IconsManager(private val binaryCache: BinaryCache) {
} }
fun doForEachCustomIcon(action: (IconImageCustom, BinaryData) -> Unit) { fun doForEachCustomIcon(action: (IconImageCustom, BinaryData) -> Unit) {
customCache.doForEachBinary { key, binary -> customCache.doForEachCustomIcon(action)
action.invoke(IconImageCustom(key), binary) }
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 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.DateInstant
import com.kunzisoft.keepass.database.element.Tags
import com.kunzisoft.keepass.utils.UnsignedLong import com.kunzisoft.keepass.utils.UnsignedLong
import java.util.*
interface NodeKDBXInterface : NodeTimeInterface { interface NodeKDBXInterface : NodeTimeInterface {
var usageCount: UnsignedLong var usageCount: UnsignedLong
var locationChanged: DateInstant var locationChanged: DateInstant
var customData: CustomData
fun putCustomData(key: String, value: String) var tags: Tags
var previousParentGroup: UUID
fun containsCustomData(): Boolean
} }

View File

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

View File

@@ -19,9 +19,9 @@
*/ */
package com.kunzisoft.keepass.database.file package com.kunzisoft.keepass.database.file
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
import com.kunzisoft.encrypt.HashManager import com.kunzisoft.encrypt.HashManager
import com.kunzisoft.keepass.database.action.node.NodeHandler 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.VariantDictionary
import com.kunzisoft.keepass.database.crypto.kdf.AesKdf import com.kunzisoft.keepass.database.crypto.kdf.AesKdf
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
@@ -91,41 +91,64 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
this.masterSeed = ByteArray(32) this.masterSeed = ByteArray(32)
} }
private inner class NodeHasCustomData<T: NodeKDBXInterface> : NodeHandler<T>() { private open class NodeOperationHandler<T: NodeKDBXInterface> : NodeHandler<T>() {
var containsCustomData = false
internal var containsCustomData = false
override fun operate(node: T): Boolean { override fun operate(node: T): Boolean {
if (node.containsCustomData()) { if (node.customData.isNotEmpty()) {
containsCustomData = true containsCustomData = true
return false
} }
return true 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 // 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 // Check each condition to determine version
if (databaseV4.kdfParameters != null return if (containsGroupWithTag
&& databaseV4.kdfParameters!!.uuid != AesKdf.CIPHER_UUID) { || containsEntryWithPasswordQualityEstimationDisabled
return FILE_VERSION_32_4 || containsCustomIconWithNameOrLastModificationTime
} || containsHeaderCustomDataWithLastModificationTime) {
FILE_VERSION_41
if (databaseV4.rootGroup == null) { } else if (kdfIsNotAes
return FILE_VERSION_32_3 || containsHeaderCustomData
} || containsNodeCustomData) {
FILE_VERSION_40
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
} else { } 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 { private fun readHeaderField(dis: InputStream): Boolean {
val fieldID = dis.read().toByte() 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() dis.readBytes2ToUShort()
} else { } else {
dis.readBytes4ToUInt().toKotlinInt() dis.readBytes4ToUInt().toKotlinInt()
@@ -194,20 +217,20 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData
PwDbHeaderV4Fields.TransformSeed -> if (version.isBefore(FILE_VERSION_32_4)) PwDbHeaderV4Fields.TransformSeed -> if (version.isBefore(FILE_VERSION_40))
transformSeed = fieldData transformSeed = fieldData
PwDbHeaderV4Fields.TransformRounds -> if (version.isBefore(FILE_VERSION_32_4)) PwDbHeaderV4Fields.TransformRounds -> if (version.isBefore(FILE_VERSION_40))
setTransformRound(fieldData) setTransformRound(fieldData)
PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.isBefore(FILE_VERSION_32_4)) PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.isBefore(FILE_VERSION_40))
innerRandomStreamKey = fieldData innerRandomStreamKey = fieldData
PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.isBefore(FILE_VERSION_32_4)) PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.isBefore(FILE_VERSION_40))
setRandomStreamID(fieldData) setRandomStreamID(fieldData)
PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(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 { private fun validVersion(version: UnsignedInt): Boolean {
return version.toKotlinInt() and FILE_VERSION_CRITICAL_MASK.toKotlinInt() <= 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 { companion object {
@@ -292,8 +315,9 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
val DBSIG_2 = UnsignedInt(-0x4ab40499) val DBSIG_2 = UnsignedInt(-0x4ab40499)
private val FILE_VERSION_CRITICAL_MASK = UnsignedInt(-0x10000) private val FILE_VERSION_CRITICAL_MASK = UnsignedInt(-0x10000)
val FILE_VERSION_32_3 = UnsignedInt(0x00030001) val FILE_VERSION_31 = UnsignedInt(0x00030001)
val FILE_VERSION_32_4 = UnsignedInt(0x00040000) val FILE_VERSION_40 = UnsignedInt(0x00040000)
val FILE_VERSION_41 = UnsignedInt(0x00040001)
fun getCompressionFromFlag(flag: UnsignedInt): CompressionAlgorithm? { fun getCompressionFromFlag(flag: UnsignedInt): CompressionAlgorithm? {
return when (flag.toKotlinInt()) { return when (flag.toKotlinInt()) {

View File

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

View File

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

View File

@@ -280,8 +280,10 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
stopSelf() stopSelf()
} else { } else {
// Restart the service to open lock notification // Restart the service to open lock notification
startService(Intent(applicationContext, try {
DatabaseTaskNotificationService::class.java)) 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() { abstract class LockNotificationService : NotificationService() {
private var onStart: Boolean = false private var mLockReceiver: LockReceiver = LockReceiver {
private var mLockReceiver: LockReceiver? = null actionOnLock()
}
protected open fun actionOnLock() { protected open fun actionOnLock() {
// Stop the service in all cases // Stop the service in all cases
@@ -36,30 +37,17 @@ abstract class LockNotificationService : NotificationService() {
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
// Register a lock receiver to stop notification service when lock on keyboard is performed // Register a lock receiver to stop notification service when lock on keyboard is performed
mLockReceiver = LockReceiver {
if (onStart)
actionOnLock()
}
registerLockReceiver(mLockReceiver) 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?) { override fun onTaskRemoved(rootIntent: Intent?) {
notificationManager?.cancel(notificationId) stopSelf()
super.onTaskRemoved(rootIntent) super.onTaskRemoved(rootIntent)
} }
override fun onDestroy() { override fun onDestroy() {
unregisterLockReceiver(mLockReceiver) unregisterLockReceiver(mLockReceiver)
mLockReceiver = null
super.onDestroy() super.onDestroy()
} }
} }

View File

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

View File

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

View File

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

View File

@@ -17,17 +17,42 @@
You should have received a copy of the GNU General Public License You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--> -->
<FrameLayout <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/icon_container" android:id="@+id/icon_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="80dp"
android:background="@drawable/background_item_selection"> android:background="@drawable/background_item_selection">
<androidx.appcompat.widget.AppCompatImageView <androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon_image" android:id="@+id/icon_image"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="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_gravity="center"
android:layout_margin="16dp"> android:layout_marginStart="16dp"
</androidx.appcompat.widget.AppCompatImageView> android:layout_marginLeft="16dp"
</FrameLayout> 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 You should have received a copy of the GNU General Public License
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>. along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
--><resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="MissingTranslation"> --><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="accept">قبول</string>
<string name="add_group">إضافة مجموعة</string> <string name="add_group">إضافة مجموعة</string>
<string name="encryption">التعمية</string> <string name="encryption">التعمية</string>
@@ -106,8 +106,8 @@
<string name="education_entry_new_field_title">إضافة حقول مخصصة</string> <string name="education_entry_new_field_title">إضافة حقول مخصصة</string>
<string name="education_field_copy_title">نسخ حقل</string> <string name="education_field_copy_title">نسخ حقل</string>
<string name="education_lock_title">تأمين قاعدة البيانات</string> <string name="education_lock_title">تأمين قاعدة البيانات</string>
<string name="feedback">الأصداء</string> <string name="feedback">التغذية الراجعة</string>
<string name="about_description">تنفيذ أندرويد لمدير كلمات السر «كي‌باس»</string> <string name="about_description">التنفيذ لمُدير كلمات المرور «كي‌ باس» على نظام أندرويد</string>
<string name="add_entry">إضافة مدخلة</string> <string name="add_entry">إضافة مدخلة</string>
<string name="edit_entry">تحرير مدخلة</string> <string name="edit_entry">تحرير مدخلة</string>
<string name="key_derivation_function">وظيفة اشتقاق المفتاح</string> <string name="key_derivation_function">وظيفة اشتقاق المفتاح</string>

View File

@@ -22,4 +22,11 @@
<string name="clipboard_error_title">ক্লিপবোর্ড ত্রুটি</string> <string name="clipboard_error_title">ক্লিপবোর্ড ত্রুটি</string>
<string name="allow">অনুমোদন</string> <string name="allow">অনুমোদন</string>
<string name="file_manager_install_description">ACTION_CREATE_DOCUMENT এবং ACTION_OPEN_DOCUMENT অভিপ্রায় গ্রহণ করে এমন একটি ফাইল ম্যানেজার ডাটাবেস ফাইলগুলো তৈরি করা, খোলা এবং সংরক্ষণ করতে প্রয়োজন।</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> </resources>

View File

@@ -25,7 +25,7 @@
<string name="add_entry">Afegeix entrada</string> <string name="add_entry">Afegeix entrada</string>
<string name="add_group">Afegeix grup</string> <string name="add_group">Afegeix grup</string>
<string name="encryption_algorithm">Algoritme de xifrat</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="app_timeout_summary">Temps d\'inactivitat abans de blocar la base de dades</string>
<string name="application">Aplicació</string> <string name="application">Aplicació</string>
<string name="menu_app_settings">Configuració de l\'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_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_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="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> </resources>

View File

@@ -24,7 +24,7 @@
<string name="add_entry">Přidat záznam</string> <string name="add_entry">Přidat záznam</string>
<string name="add_group">Přidat skupinu</string> <string name="add_group">Přidat skupinu</string>
<string name="encryption_algorithm">Šifrovací algoritmus</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="app_timeout_summary">Doba nečinnosti, po které se aplikace zamkne</string>
<string name="application">Aplikace</string> <string name="application">Aplikace</string>
<string name="menu_app_settings">Nastavení 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_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="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="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> </resources>

View File

@@ -223,7 +223,7 @@
<string name="application_appearance">Brugerflade</string> <string name="application_appearance">Brugerflade</string>
<string name="other">Øvrige</string> <string name="other">Øvrige</string>
<string name="keyboard">Tastatur</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="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_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> <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="style_choose_summary">Tema, der bruges i programmet</string>
<string name="icon_pack_choose_title">Ikonpakke</string> <string name="icon_pack_choose_title">Ikonpakke</string>
<string name="icon_pack_choose_summary">Ikonpakke, der anvendes</string> <string name="icon_pack_choose_summary">Ikonpakke, der anvendes</string>
<string name="keyboard_name">Magikeyboard</string> <string name="keyboard_name">Magi keyboard</string>
<string name="keyboard_label">Magikeyboard (KeePassDX)</string> <string name="keyboard_label">Magi keyboard (KeePassDX)</string>
<string name="keyboard_setting_label">Magikeyboard indstillinger</string> <string name="keyboard_setting_label">Magikeyboard indstillinger</string>
<string name="keyboard_entry_category">Post</string> <string name="keyboard_entry_category">Post</string>
<string name="keyboard_entry_timeout_title">Udløbstid</string> <string name="keyboard_entry_timeout_title">Udløbstid</string>
@@ -300,7 +300,7 @@
<string name="keyboard_keys_category">Taster</string> <string name="keyboard_keys_category">Taster</string>
<string name="keyboard_key_vibrate_title">Vibrerende tastetryk</string> <string name="keyboard_key_vibrate_title">Vibrerende tastetryk</string>
<string name="keyboard_key_sound_title">Hørbare 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="keyboard_entry_timeout_summary">Tidsudløb for at rydde indtastning</string>
<string name="entry_notes">Noter</string> <string name="entry_notes">Noter</string>
<string name="selection_mode">Valgstilstand</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_title">Skjule brudte databaselinks</string>
<string name="hide_broken_locations_summary">Skjul brudte links på listen over seneste databaser</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="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="education_setup_OTP_title">Opsætning af OTP</string>
<string name="error_create_database">Databasefilen kunne ikke oprettes.</string> <string name="error_create_database">Databasefilen kunne ikke oprettes.</string>
<string name="entry_add_attachment">Tilføj vedhæng</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_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_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="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="enter">Enter</string>
<string name="temp_advanced_unlock_timeout_summary">Varigheden af avanceret oplåsning, før indholdet slettes</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> <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="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_summary">Vælg en fil for at importere app-egenskaber</string>
<string name="import_app_properties_title">Importer appegenskaber</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> </resources>

View File

@@ -30,17 +30,17 @@
<string name="add_entry">Eintrag hinzufügen</string> <string name="add_entry">Eintrag hinzufügen</string>
<string name="add_group">Gruppe hinzufügen</string> <string name="add_group">Gruppe hinzufügen</string>
<string name="encryption_algorithm">Verschlüsselungsalgorithmus</string> <string name="encryption_algorithm">Verschlüsselungsalgorithmus</string>
<string name="app_timeout">Zeitüberschreitung</string> <string name="app_timeout">Inaktivitätszeit</string>
<string name="app_timeout_summary">Inaktivitätszeit vor dem Sperren der Datenbank</string> <string name="app_timeout_summary">Zeit bis zum Sperren der Datenbank</string>
<string name="application">App</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="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="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_cleared">Zwischenablage geleert</string>
<string name="clipboard_error_title">Zwischenablage-Fehler</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">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_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="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="select_to_copy">%1$s in die Zwischenablage kopieren</string>
<string name="retrieving_db_key">Datenbank-Schlüsseldatei erzeugen </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_db">Datenbank nicht lesbar.</string>
<string name="error_invalid_path">Sicherstellen, dass der Pfad korrekt ist.</string> <string name="error_invalid_path">Sicherstellen, dass der Pfad korrekt ist.</string>
<string name="error_no_name">Namen eingeben.</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_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_gen_type">Mindestens eine Art der Passwortgenerierung muss ausgewählt sein.</string>
<string name="error_pass_match">Die Passwörter stimmen nicht überein.</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_not_found_content">Datei nicht gefunden. Bitte versuchen, sie über den Dateimanager zu öffnen.</string>
<string name="file_browser">Dateimanager</string> <string name="file_browser">Dateimanager</string>
<string name="generate_password">Passwort generieren</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_generated_password">Generiertes Passwort</string>
<string name="hint_group_name">Name der Gruppe</string> <string name="hint_group_name">Name der Gruppe</string>
<string name="hint_keyfile">Schlüsseldatei</string> <string name="hint_keyfile">Schlüsseldatei</string>
@@ -104,14 +104,14 @@
<string name="about">Über</string> <string name="about">Über</string>
<string name="menu_change_key_settings">Hauptschlüssel ändern</string> <string name="menu_change_key_settings">Hauptschlüssel ändern</string>
<string name="settings">Einstellungen</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_delete">Löschen</string>
<string name="menu_donate">Spenden</string> <string name="menu_donate">Spenden</string>
<string name="menu_edit">Bearbeiten</string> <string name="menu_edit">Bearbeiten</string>
<string name="menu_hide_password">Passwort verstecken</string> <string name="menu_hide_password">Passwort verstecken</string>
<string name="menu_lock">Datenbank sperren</string> <string name="menu_lock">Datenbank sperren</string>
<string name="menu_open">Öffnen</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_showpass">Passwort anzeigen</string>
<string name="menu_url">URL öffnen</string> <string name="menu_url">URL öffnen</string>
<string name="minus">Bindestrich</string> <string name="minus">Bindestrich</string>
@@ -202,14 +202,14 @@
<string name="autofill_service_name">KeePassDX autom. Formularausfüllung</string> <string name="autofill_service_name">KeePassDX autom. Formularausfüllung</string>
<string name="autofill_sign_in_prompt">Mit KeePassDX anmelden</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="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="clipboard">Zwischenablage</string>
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</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_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="unavailable_feature_hardware">Keine entsprechende Hardware.</string>
<string name="recycle_bin_title">Papierkorb-Nutzung</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_title">Feldschriftart</string>
<string name="monospace_font_fields_enable_summary">Schriftart in Feldern ändern, um Lesbarkeit zu verbessern</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> <string name="allow_copy_password_title">Zwischenablage vertrauen</string>
@@ -218,8 +218,8 @@
<string name="database_description_title">Datenbankbeschreibung</string> <string name="database_description_title">Datenbankbeschreibung</string>
<string name="database_version_title">Datenbankversion</string> <string name="database_version_title">Datenbankversion</string>
<string name="text_appearance">Text</string> <string name="text_appearance">Text</string>
<string name="application_appearance">Interface</string> <string name="application_appearance">Benutzeroberfläche</string>
<string name="other">Andere</string> <string name="other">Sonstiges</string>
<string name="keyboard">Tastatur</string> <string name="keyboard">Tastatur</string>
<string name="magic_keyboard_title">Magikeyboard</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> <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_label">Magikeyboard (KeePassDX)</string>
<string name="keyboard_setting_label">Magikeyboard-Einstellungen</string> <string name="keyboard_setting_label">Magikeyboard-Einstellungen</string>
<string name="keyboard_entry_category">Eintrag</string> <string name="keyboard_entry_category">Eintrag</string>
<string name="keyboard_entry_timeout_title">Timeout</string> <string name="keyboard_entry_timeout_title">Inaktivitätszeit</string>
<string name="keyboard_entry_timeout_summary">Timeout zum Löschen der Tastatureingabe</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_title">Benachrichtigung</string>
<string name="keyboard_notification_entry_summary">Benachrichtigung anzeigen, wenn ein Eintrag abrufbar ist</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> <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="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="invalid_db_same_uuid">%1$s mit derselben UUID %2$s existiert bereits.</string>
<string name="creating_database">Datenbank wird erstellt </string> <string name="creating_database">Datenbank wird erstellt </string>
<string name="menu_security_settings">Sicherheits-Einstellungen</string> <string name="menu_security_settings">Sicherheitseinstellungen</string>
<string name="menu_master_key_settings">Hauptschlüssel-Einstellungen</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">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="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> <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_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_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="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_default_username_title">Standard-Benutzername</string>
<string name="database_custom_color_title">Benutzerdefinierte Datenbankfarbe</string> <string name="database_custom_color_title">Eigene Datenbankfarbe</string>
<string name="compression">Kompression</string> <string name="compression">Kompression</string>
<string name="compression_none">Keine</string> <string name="compression_none">Keine</string>
<string name="compression_gzip">Gzip</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_title">Abgelaufene Einträge ausblenden</string>
<string name="hide_expired_entries_summary">Abgelaufene Einträge werden nicht angezeigt</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_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"> <string-array name="list_style_names">
<item>Wald</item> <item>Wald</item>
<item>Göttlich</item> <item>Göttlich</item>
@@ -443,7 +443,7 @@
<string name="discard">Verwerfen</string> <string name="discard">Verwerfen</string>
<string name="discard_changes">Änderungen verwerfen\?</string> <string name="discard_changes">Änderungen verwerfen\?</string>
<string name="validate">Validieren</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="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_summary">Zeigt die Sperrtaste in der Benutzeroberfläche an</string>
<string name="lock_database_show_button_title">Sperrtaste anzeigen</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="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_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_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_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_summary">Liste der Apps, in denen ein automatisches Ausfüllen verhindert wird</string>
<string name="autofill_application_id_blocklist_title">Liste gesperrter Anwendungen</string> <string name="autofill_application_id_blocklist_title">Liste gesperrter Anwendungen</string>
@@ -497,11 +497,11 @@
<string name="save_mode">Speichermodus</string> <string name="save_mode">Speichermodus</string>
<string name="search_mode">Suchmodus</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="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_save_search_info_summary">Suchdaten bei manueller Auswahl einer Eingabe wenn möglich 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_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 anfordern</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_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_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="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> <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="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="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_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="education_advanced_unlock_title">Moderne Entsperrung der Datenbank</string>
<string name="advanced_unlock_timeout">Verfallzeit der erweiterten Entsperrung</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_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_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_summary">Keine zur modernen Entsperrung verwendeten, verschlüsselten Inhalte speichern</string>
<string name="temp_advanced_unlock_enable_title">Temporär moderne Entsperrung</string> <string name="temp_advanced_unlock_enable_title">Befristete moderne Entsperrung</string>
<string name="device_credential_unlock_enable_summary">Erlaubt Ihn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden</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="advanced_unlock_tap_delete">Drücken, um Schlüssel für modernes Entsperren zu löschen</string>
<string name="content">Inhalt</string> <string name="content">Inhalt</string>
<string name="advanced_unlock_prompt_extract_credential_title">Datenbank mit moderner Entsperrerkennung öffnen</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="select_entry">Wähle Eintrag</string>
<string name="back_to_previous_keyboard">Zurück zur vorherigen Tastatur</string> <string name="back_to_previous_keyboard">Zurück zur vorherigen Tastatur</string>
<string name="custom_fields">Benutzerdefinierte Felder</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_unlock_enable_title">Geräteanmeldedaten entsperren</string>
<string name="device_credential">Geräteanmeldedaten</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> <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="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="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_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="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_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="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> <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="success_export_app_properties">App-Eigenschaften exportiert</string>
<string name="error_import_app_properties">Fehler beim Importieren der App-Eigenschaften</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="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="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="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> </resources>

View File

@@ -26,7 +26,7 @@
<string name="add_entry">Añadir entrada</string> <string name="add_entry">Añadir entrada</string>
<string name="add_group">Añadir grupo</string> <string name="add_group">Añadir grupo</string>
<string name="encryption_algorithm">Algoritmo de cifrado</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="app_timeout_summary">Inactividad antes del bloqueo de aplicación</string>
<string name="application">Aplicación</string> <string name="application">Aplicación</string>
<string name="menu_app_settings">Configuración de la 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="database_opened">Base de datos abierta</string>
<string name="education_add_attachment_title">Adjuntar</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="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_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). <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 \n
@@ -492,7 +492,7 @@
<string name="hide_expired_entries_title">Ocultar las entradas expiradas</string> <string name="hide_expired_entries_title">Ocultar las entradas expiradas</string>
<string name="keyboard_search_share_title">Buscar información compartida</string> <string name="keyboard_search_share_title">Buscar información compartida</string>
<string name="upload_attachment">Subir %1$s</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_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_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> <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="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="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="download_canceled">¡Cancelado!</string>
<string name="error_duplicate_file">Los datos de archivo ya existen.</string> <string name="error_duplicate_file">Los datos del archivo ya existen.</string>
<string name="error_upload_file">Ha habido un error al subir el archivo de datos.</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> </resources>

View File

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

View File

@@ -367,7 +367,7 @@
<string name="contribution">Doprinos</string> <string name="contribution">Doprinos</string>
<string name="error_label_exists">Ova oznaka već postoji.</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="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="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="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> <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_summary">Odaberi datoteku za uvoz svojstva aplikacije</string>
<string name="import_app_properties_title">Uvezi 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_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> </resources>

View File

@@ -160,7 +160,7 @@
<string name="extended_ASCII">ASCII Diperluas</string> <string name="extended_ASCII">ASCII Diperluas</string>
<string name="brackets">Tanda Kurung</string> <string name="brackets">Tanda Kurung</string>
<string name="application">Aplikasi</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="key_derivation_function">Fungsi Derivasi Kunci</string>
<string name="encryption_algorithm">Algoritma Enkripsi</string> <string name="encryption_algorithm">Algoritma Enkripsi</string>
<string name="encryption">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_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="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_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> </resources>

View File

@@ -31,7 +31,7 @@
<string name="encryption">暗号化</string> <string name="encryption">暗号化</string>
<string name="encryption_algorithm">暗号化アルゴリズム</string> <string name="encryption_algorithm">暗号化アルゴリズム</string>
<string name="key_derivation_function">鍵導出関数</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="app_timeout_summary">この期間アプリの操作がなかった場合、データベースをロックします</string>
<string name="application">アプリ</string> <string name="application">アプリ</string>
<string name="brackets">かっこ</string> <string name="brackets">かっこ</string>
@@ -559,4 +559,6 @@
<string name="export_app_properties_summary">アプリのプロパティをエクスポートするファイルを作成します</string> <string name="export_app_properties_summary">アプリのプロパティをエクスポートするファイルを作成します</string>
<string name="export_app_properties_title">アプリのプロパティをエクスポートする</string> <string name="export_app_properties_title">アプリのプロパティをエクスポートする</string>
<string name="error_start_database_action">データベースに対するアクションの実行中にエラーが発生しました。</string> <string name="error_start_database_action">データベースに対するアクションの実行中にエラーが発生しました。</string>
<string name="error_word_reserved">この単語は予約語のため使用できません。</string>
<string name="error_move_group_here">グループをここに移動できません。</string>
</resources> </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_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="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_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> </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_summary">Solicitar uma pesquisa quando abrir a base de dados</string>
<string name="auto_focus_search_title">Pesquisa rápida</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_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="about">Sobre</string>
<string name="hide_password_summary">Mascarar palavras-passe (***) por predefinição</string> <string name="hide_password_summary">Mascarar palavras-passe (***) por predefinição</string>
<string name="hide_password_title">Esconder palavras-passe</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_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_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_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_disallow_no_credentials">Ao menos uma credencial deve ser definida.</string>
<string name="error_invalid_OTP">Segredo OTP inválido.</string> <string name="error_invalid_OTP">Segredo OTP inválido.</string>
<string name="entry_otp">OTP</string> <string name="entry_otp">OTP</string>
@@ -400,7 +400,7 @@
<string name="build_label">Compilação %1$s</string> <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="retrieving_db_key">A criar a chave da base de dados…</string>
<string name="clipboard">Área de transferência</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="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_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> <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">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="clipboard_error_title">Erro na área de transferência</string>
<string name="allow">Permitir</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="brackets">Parênteses</string>
<string name="application">App</string> <string name="application">App</string>
<string name="app_timeout_summary">Inatividade antes de bloquear a base de dados</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_group">Adicionar grupo</string>
<string name="add_entry">Adicionar entrada</string> <string name="add_entry">Adicionar entrada</string>
<string name="accept">Aceitar</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> </resources>

View File

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

View File

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

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 @@
* 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