Merge branch 'develop' into feature/Templates

This commit is contained in:
J-Jamet
2021-05-07 17:46:11 +02:00
44 changed files with 834 additions and 403 deletions

View File

@@ -1,4 +1,8 @@
KeePassDX(3.0.0) KeePassDX(3.0.0)
*
KeePassDX(2.10.0)
* Manage new database format 4.1 #956
* Fix show button consistency #980 * Fix show button consistency #980
* Fix persistent notification #979 * Fix persistent notification #979

View File

@@ -11,7 +11,7 @@ android {
applicationId "com.kunzisoft.keepass" applicationId "com.kunzisoft.keepass"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 30 targetSdkVersion 30
versionCode = 76 versionCode = 77
versionName = "3.0.0" versionName = "3.0.0"
multiDexEnabled true multiDexEnabled true

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

@@ -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, CustomData::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
@@ -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,12 @@ 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 * To perform a search in entry custom data
} */
override fun containsCustomData(): Boolean {
return customData.isNotEmpty()
}
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 +608,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

@@ -337,8 +337,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

@@ -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
@@ -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
} }
@@ -322,14 +327,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

@@ -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)) {
@@ -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()) { for ((key, value) in autoType.entrySet()) {
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

@@ -52,7 +52,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)

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

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

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

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

@@ -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>
@@ -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>
@@ -384,8 +384,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>
@@ -403,8 +403,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>
@@ -431,7 +431,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>
@@ -448,7 +448,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>
@@ -502,11 +502,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>
@@ -517,13 +517,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>
@@ -532,7 +532,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>
@@ -548,9 +548,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>
@@ -559,8 +559,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

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

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

@@ -177,7 +177,7 @@
<string name="field_name">Nome do campo</string> <string name="field_name">Nome do campo</string>
<string name="error_autofill_enable_service">Não pôde ser ativado o serviço de preenchimento automático.</string> <string name="error_autofill_enable_service">Não pôde ser ativado o serviço de preenchimento automático.</string>
<string name="error_wrong_length">Digite um número inteiro positivo no campo \"Tamanho\".</string> <string name="error_wrong_length">Digite um número inteiro positivo no campo \"Tamanho\".</string>
<string name="error_string_key">Um nome do campo é necessário para cada cadeia.</string> <string name="error_string_key">Um nome do campo é necessário para cada string.</string>
<string name="error_rounds_too_large">\"Número de rodadas\" é muito grande. Modificado para 2147483648.</string> <string name="error_rounds_too_large">\"Número de rodadas\" é muito grande. Modificado para 2147483648.</string>
<string name="error_pass_match">As palavras-passe não coincidem.</string> <string name="error_pass_match">As palavras-passe não coincidem.</string>
<string name="error_pass_gen_type">Pelo menos um tipo de geração de palavra-chave deve ser selecionado.</string> <string name="error_pass_gen_type">Pelo menos um tipo de geração de palavra-chave deve ser selecionado.</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 dispositivo</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

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

View File

@@ -0,0 +1 @@
*

View File

@@ -1,2 +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 consistance du bouton de visibilité #980
* Correction de la notification persistante #979 * Correction de la notification persistante #979

View File

@@ -0,0 +1 @@
*