mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Compare commits
155 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d560c3e8de | ||
|
|
4f8e8e6669 | ||
|
|
7cdc2e0915 | ||
|
|
74b236b317 | ||
|
|
89ffeaf03b | ||
|
|
8b779a0fca | ||
|
|
4b71dc8445 | ||
|
|
780875d5f2 | ||
|
|
7356d4b0e2 | ||
|
|
654dea6b7e | ||
|
|
1204374637 | ||
|
|
880fde2148 | ||
|
|
d776d76100 | ||
|
|
2a7af826a8 | ||
|
|
9d2fd53073 | ||
|
|
edc5985a7e | ||
|
|
96fc79103b | ||
|
|
76879f3a73 | ||
|
|
328629fe88 | ||
|
|
9754535055 | ||
|
|
26f701d890 | ||
|
|
f3df8024e6 | ||
|
|
bc9fdfc7a4 | ||
|
|
e1e37989e4 | ||
|
|
dc67cb1807 | ||
|
|
2e8a2457bc | ||
|
|
6d4398c6fd | ||
|
|
9f67ad872d | ||
|
|
aba396f274 | ||
|
|
4315f34398 | ||
|
|
47b456e1ee | ||
|
|
529810b2dc | ||
|
|
6f67b8e788 | ||
|
|
a9c9d12444 | ||
|
|
f8428cec61 | ||
|
|
ac46bce807 | ||
|
|
23e899042d | ||
|
|
80c4a3c06d | ||
|
|
d2a31601ba | ||
|
|
7ddb4f3486 | ||
|
|
e56da87e0e | ||
|
|
2e4ebecf67 | ||
|
|
1b4ccaed91 | ||
|
|
1e2d41c7fb | ||
|
|
1b2ead054a | ||
|
|
468c1b95b7 | ||
|
|
60d8eff71f | ||
|
|
8baae8b801 | ||
|
|
9f16f26347 | ||
|
|
69b4cacab4 | ||
|
|
b74b5040b1 | ||
|
|
a28decc854 | ||
|
|
cb59cef1b8 | ||
|
|
d9b600466c | ||
|
|
4edf2f8cd1 | ||
|
|
c60dfdf0d7 | ||
|
|
006afc6841 | ||
|
|
f70879581d | ||
|
|
7a2d2b0376 | ||
|
|
b5368fa239 | ||
|
|
db1d71af9f | ||
|
|
6b03ef35a6 | ||
|
|
2afd02d86f | ||
|
|
b32c00f455 | ||
|
|
fbe9fb41ed | ||
|
|
cc0a7f7d76 | ||
|
|
db33fc60b9 | ||
|
|
6feaee4f86 | ||
|
|
1a27a31a32 | ||
|
|
5b611e71d5 | ||
|
|
6de88bfe11 | ||
|
|
6d7236249f | ||
|
|
69fbaba8a6 | ||
|
|
6d88737505 | ||
|
|
9869cfc736 | ||
|
|
8505326a68 | ||
|
|
3a4af88384 | ||
|
|
5b2e7d0f70 | ||
|
|
ddeea6bee3 | ||
|
|
0cfe3a7634 | ||
|
|
727463e4d1 | ||
|
|
d42abfdc56 | ||
|
|
e01ea1df4c | ||
|
|
078bfac5f5 | ||
|
|
111b07b9e6 | ||
|
|
dfbc89addc | ||
|
|
bf44da9a14 | ||
|
|
d75d13965b | ||
|
|
8aedebdc94 | ||
|
|
9388c4bb0d | ||
|
|
77d4f601af | ||
|
|
7fae590848 | ||
|
|
bc41558a26 | ||
|
|
f6651face4 | ||
|
|
345f00f7f2 | ||
|
|
e876d02118 | ||
|
|
5b7018f71b | ||
|
|
f45b3fc50a | ||
|
|
01196be30d | ||
|
|
0a2999bffb | ||
|
|
8f097096e7 | ||
|
|
cd97fc046a | ||
|
|
eeb10f31a6 | ||
|
|
9df5e116e8 | ||
|
|
1228a03d39 | ||
|
|
a5e1b3096e | ||
|
|
b41ae67128 | ||
|
|
ddfbe20125 | ||
|
|
0bfe9291dd | ||
|
|
622b2e1edc | ||
|
|
4a40719534 | ||
|
|
384993d363 | ||
|
|
01b7d28154 | ||
|
|
d7c4f5577f | ||
|
|
a69d23ca64 | ||
|
|
e2f8b7a6e3 | ||
|
|
171a0b012f | ||
|
|
5c04b15433 | ||
|
|
6397feffff | ||
|
|
e73b9b7f1c | ||
|
|
0d82e40c67 | ||
|
|
b75d6d02fa | ||
|
|
76d4542716 | ||
|
|
87955de849 | ||
|
|
6df60cf5da | ||
|
|
3c23a314f0 | ||
|
|
8fda6b04a4 | ||
|
|
9fa98e6b76 | ||
|
|
deb685f39b | ||
|
|
d7851d3a18 | ||
|
|
44946fc54a | ||
|
|
a033d10adc | ||
|
|
5a3e599fe0 | ||
|
|
a9f645f389 | ||
|
|
d662f0903a | ||
|
|
beaa947eb7 | ||
|
|
48006b64d6 | ||
|
|
8f195ba66f | ||
|
|
5866e95d49 | ||
|
|
e79f395424 | ||
|
|
999ca87fec | ||
|
|
1217266d88 | ||
|
|
bb262198be | ||
|
|
6d860c5cb7 | ||
|
|
d8be832858 | ||
|
|
afcb9fcf41 | ||
|
|
3c7ae0aaf0 | ||
|
|
6b7f93dbfe | ||
|
|
3240e0bcae | ||
|
|
80f00aba0a | ||
|
|
232682f4a8 | ||
|
|
de3b690d60 | ||
|
|
a5cd6d5ac0 | ||
|
|
a6803bf0e3 | ||
|
|
df9a71a63d |
26
CHANGELOG
26
CHANGELOG
@@ -1,3 +1,29 @@
|
||||
KeePassDX(2.10.1)
|
||||
* Fix parcelable with custom data #986
|
||||
|
||||
KeePassDX(2.10.0)
|
||||
* Manage new database format 4.1 #956
|
||||
* Fix show button consistency #980
|
||||
* Fix persistent notification #979
|
||||
|
||||
KeePassDX(2.9.20)
|
||||
* Fix search with non-latin chars #971
|
||||
* Fix action mode with search #972 (rollback ignore accents #945)
|
||||
* Fix timeout with 0s #974
|
||||
|
||||
KeePassDX(2.9.19)
|
||||
* Fix search slowdown #964
|
||||
* Fix closing notification after lock request #965
|
||||
* Better temp advanced unlocking code implementation #965
|
||||
* Fix OTP token generation #967
|
||||
|
||||
KeePassDX(2.9.18)
|
||||
* Move groups #658
|
||||
* Improve autofill recognition #960
|
||||
* Remove diacritical marks in search string #945
|
||||
* Fix search in references #962
|
||||
* Fix themes in Libre version
|
||||
|
||||
KeePassDX(2.9.17)
|
||||
* Import / Export app properties #839
|
||||
* Force twofish padding compatibility #955
|
||||
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 15
|
||||
targetSdkVersion 30
|
||||
versionCode = 71
|
||||
versionName = "2.9.17"
|
||||
versionCode = 78
|
||||
versionName = "2.10.1"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
@@ -109,7 +109,7 @@ dependencies {
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'androidx.viewpager2:viewpager2:1.1.0-alpha01'
|
||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||
implementation 'androidx.biometric:biometric:1.1.0-rc01'
|
||||
implementation 'androidx.biometric:biometric:1.1.0'
|
||||
// Lifecycle - LiveData - ViewModel - Coroutines
|
||||
implementation "androidx.core:core-ktx:1.3.2"
|
||||
implementation 'androidx.fragment:fragment-ktx:1.2.5'
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.kunzisoft.keepass.tests.utils
|
||||
|
||||
import com.kunzisoft.keepass.utils.UuidUtil
|
||||
import junit.framework.TestCase
|
||||
import java.util.*
|
||||
|
||||
class UUIDTest: TestCase() {
|
||||
|
||||
fun testUUID() {
|
||||
val randomUUID = UUID.randomUUID()
|
||||
val hexStringUUID = UuidUtil.toHexString(randomUUID)
|
||||
val retrievedUUID = UuidUtil.fromHexString(hexStringUUID)
|
||||
assertEquals(randomUUID, retrievedUUID)
|
||||
}
|
||||
}
|
||||
@@ -382,6 +382,7 @@ class GroupActivity : LockingActivity(),
|
||||
Log.d(TAG, "setNewIntent: $intentNotNull")
|
||||
setIntent(intentNotNull)
|
||||
if (Intent.ACTION_SEARCH == intentNotNull.action) {
|
||||
finishNodeAction()
|
||||
// only one instance of search in backstack
|
||||
deletePreviousSearchGroup()
|
||||
openGroup(retrieveCurrentGroup(intentNotNull, null), true)
|
||||
@@ -812,74 +813,75 @@ class GroupActivity : LockingActivity(),
|
||||
|
||||
override fun onPasteMenuClick(pasteMode: ListNodesFragment.PasteMode?,
|
||||
nodes: List<Node>): Boolean {
|
||||
// Move or copy only if allowed (in root if allowed)
|
||||
if (mCurrentGroup != mDatabase?.rootGroup
|
||||
|| mDatabase?.rootCanContainsEntry() == true) {
|
||||
|
||||
when (pasteMode) {
|
||||
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
|
||||
// Copy
|
||||
mCurrentGroup?.let { newParent ->
|
||||
mProgressDatabaseTaskProvider?.startDatabaseCopyNodes(
|
||||
nodes,
|
||||
newParent,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
|
||||
// Move
|
||||
mCurrentGroup?.let { newParent ->
|
||||
mProgressDatabaseTaskProvider?.startDatabaseMoveNodes(
|
||||
nodes,
|
||||
newParent,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
when (pasteMode) {
|
||||
ListNodesFragment.PasteMode.PASTE_FROM_COPY -> {
|
||||
// Copy
|
||||
mCurrentGroup?.let { newParent ->
|
||||
mProgressDatabaseTaskProvider?.startDatabaseCopyNodes(
|
||||
nodes,
|
||||
newParent,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
coordinatorLayout?.let { coordinatorLayout ->
|
||||
Snackbar.make(coordinatorLayout,
|
||||
R.string.error_copy_entry_here,
|
||||
Snackbar.LENGTH_LONG).asError().show()
|
||||
ListNodesFragment.PasteMode.PASTE_FROM_MOVE -> {
|
||||
// Move
|
||||
mCurrentGroup?.let { newParent ->
|
||||
mProgressDatabaseTaskProvider?.startDatabaseMoveNodes(
|
||||
nodes,
|
||||
newParent,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
finishNodeAction()
|
||||
return true
|
||||
}
|
||||
|
||||
private fun eachNodeRecyclable(nodes: List<Node>): Boolean {
|
||||
mDatabase?.let { database ->
|
||||
return nodes.find { node ->
|
||||
var cannotRecycle = true
|
||||
if (node is Entry) {
|
||||
cannotRecycle = !database.canRecycle(node)
|
||||
} else if (node is Group) {
|
||||
cannotRecycle = !database.canRecycle(node)
|
||||
}
|
||||
cannotRecycle
|
||||
} == null
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun deleteNodes(nodes: List<Node>, recycleBin: Boolean = false): Boolean {
|
||||
val database = mDatabase
|
||||
mDatabase?.let { database ->
|
||||
|
||||
// If recycle bin enabled, ensure it exists
|
||||
if (database != null && database.isRecycleBinEnabled) {
|
||||
database.ensureRecycleBinExists(resources)
|
||||
}
|
||||
|
||||
// If recycle bin enabled and not in recycle bin, move in recycle bin
|
||||
if (database != null
|
||||
&& database.isRecycleBinEnabled
|
||||
&& database.recycleBin != mCurrentGroup) {
|
||||
|
||||
mProgressDatabaseTaskProvider?.startDatabaseDeleteNodes(
|
||||
nodes,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
// else open the dialog to confirm deletion
|
||||
else {
|
||||
val deleteNodesDialogFragment: DeleteNodesDialogFragment =
|
||||
if (recycleBin) {
|
||||
EmptyRecycleBinDialogFragment.getInstance(nodes)
|
||||
} else {
|
||||
DeleteNodesDialogFragment.getInstance(nodes)
|
||||
// If recycle bin enabled, ensure it exists
|
||||
if (database.isRecycleBinEnabled) {
|
||||
database.ensureRecycleBinExists(resources)
|
||||
}
|
||||
deleteNodesDialogFragment.show(supportFragmentManager, "deleteNodesDialogFragment")
|
||||
|
||||
// If recycle bin enabled and not in recycle bin, move in recycle bin
|
||||
if (eachNodeRecyclable(nodes)) {
|
||||
mProgressDatabaseTaskProvider?.startDatabaseDeleteNodes(
|
||||
nodes,
|
||||
!mReadOnly && mAutoSaveEnable
|
||||
)
|
||||
}
|
||||
// else open the dialog to confirm deletion
|
||||
else {
|
||||
val deleteNodesDialogFragment: DeleteNodesDialogFragment =
|
||||
if (recycleBin) {
|
||||
EmptyRecycleBinDialogFragment.getInstance(nodes)
|
||||
} else {
|
||||
DeleteNodesDialogFragment.getInstance(nodes)
|
||||
}
|
||||
deleteNodesDialogFragment.show(supportFragmentManager, "deleteNodesDialogFragment")
|
||||
}
|
||||
finishNodeAction()
|
||||
}
|
||||
finishNodeAction()
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -1076,6 +1078,16 @@ class GroupActivity : LockingActivity(),
|
||||
}
|
||||
}
|
||||
|
||||
override fun isValidGroupName(name: String): GroupEditDialogFragment.Error {
|
||||
if (name.isEmpty()) {
|
||||
return GroupEditDialogFragment.Error(true, R.string.error_no_name)
|
||||
}
|
||||
if (mDatabase?.groupNamesNotAllowed?.find { it.equals(name, ignoreCase = true) } != null) {
|
||||
return GroupEditDialogFragment.Error(true, R.string.error_word_reserved)
|
||||
}
|
||||
return GroupEditDialogFragment.Error(false, null)
|
||||
}
|
||||
|
||||
override fun approveEditGroup(action: GroupEditDialogFragment.EditGroupDialogAction,
|
||||
groupInfo: GroupInfo) {
|
||||
|
||||
|
||||
@@ -219,14 +219,19 @@ class GroupEditDialogFragment : DialogFragment() {
|
||||
}
|
||||
|
||||
private fun isValid(): Boolean {
|
||||
if (nameTextView.text.toString().isEmpty()) {
|
||||
nameTextLayoutView.error = getString(R.string.error_no_name)
|
||||
return false
|
||||
val error = mEditGroupListener?.isValidGroupName(nameTextView.text.toString()) ?: Error(false, null)
|
||||
error.messageId?.let { messageId ->
|
||||
nameTextLayoutView.error = getString(messageId)
|
||||
} ?: kotlin.run {
|
||||
nameTextLayoutView.error = null
|
||||
}
|
||||
return true
|
||||
return !error.isError
|
||||
}
|
||||
|
||||
data class Error(val isError: Boolean, val messageId: Int?)
|
||||
|
||||
interface EditGroupListener {
|
||||
fun isValidGroupName(name: String): Error
|
||||
fun approveEditGroup(action: EditGroupDialogAction,
|
||||
groupInfo: GroupInfo)
|
||||
fun cancelEditGroup(action: EditGroupDialogAction,
|
||||
|
||||
@@ -311,13 +311,17 @@ class ListNodesFragment : StylishFragment(), SortDialogFragment.SortSelectionLis
|
||||
menu?.removeItem(R.id.menu_edit)
|
||||
}
|
||||
|
||||
// Copy and Move (not for groups)
|
||||
// Move
|
||||
if (readOnly
|
||||
|| isASearchResult) {
|
||||
menu?.removeItem(R.id.menu_move)
|
||||
}
|
||||
|
||||
// Copy (not allowed for group)
|
||||
if (readOnly
|
||||
|| isASearchResult
|
||||
|| nodes.any { it.type == Type.GROUP }) {
|
||||
// TODO Copy For Group
|
||||
menu?.removeItem(R.id.menu_copy)
|
||||
menu?.removeItem(R.id.menu_move)
|
||||
}
|
||||
|
||||
// Deletion
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageDraw
|
||||
@@ -95,6 +96,12 @@ class IconPickerAdapter<I: IconImageDraw>(val context: Context, private val tint
|
||||
override fun onBindViewHolder(holder: CustomIconViewHolder, position: Int) {
|
||||
val icon = iconList[position]
|
||||
iconDrawableFactory?.assignDatabaseIcon(holder.iconImageView, icon, tintIcon)
|
||||
icon.getIconImageToDraw().custom.name.let { iconName ->
|
||||
holder.iconTextView.apply {
|
||||
text = iconName
|
||||
visibility = if (iconName.isNotEmpty()) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
holder.iconContainerView.isSelected = icon.selected
|
||||
holder.itemView.setOnClickListener {
|
||||
iconPickerListener?.onIconClickListener(icon)
|
||||
@@ -117,5 +124,6 @@ class IconPickerAdapter<I: IconImageDraw>(val context: Context, private val tint
|
||||
inner class CustomIconViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
var iconContainerView: ViewGroup = itemView.findViewById(R.id.icon_container)
|
||||
var iconImageView: ImageView = itemView.findViewById(R.id.icon_image)
|
||||
var iconTextView: TextView = itemView.findViewById(R.id.icon_name)
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,6 @@ class SearchEntryCursorAdapter(private val context: Context,
|
||||
|
||||
private fun getEntryFrom(cursor: Cursor): Entry? {
|
||||
return database.createEntry()?.apply {
|
||||
database.startManageEntry(this)
|
||||
entryKDB?.let { entryKDB ->
|
||||
(cursor as EntryCursorKDB).populateEntry(entryKDB,
|
||||
{ standardIconId ->
|
||||
@@ -127,7 +126,6 @@ class SearchEntryCursorAdapter(private val context: Context,
|
||||
}
|
||||
)
|
||||
}
|
||||
database.stopManageEntry(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,12 +148,14 @@ class SearchEntryCursorAdapter(private val context: Context,
|
||||
if (searchGroup != null) {
|
||||
// Search in hide entries but not meta-stream
|
||||
for (entry in searchGroup.getFilteredChildEntries(Group.ChildFilter.getDefaults(context))) {
|
||||
database.startManageEntry(entry)
|
||||
entry.entryKDB?.let {
|
||||
cursorKDB?.addEntry(it)
|
||||
}
|
||||
entry.entryKDBX?.let {
|
||||
cursorKDBX?.addEntry(it)
|
||||
}
|
||||
database.stopManageEntry(entry)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,10 +19,7 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.app.database
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.*
|
||||
import android.net.Uri
|
||||
import android.os.IBinder
|
||||
import android.util.Log
|
||||
@@ -42,66 +39,95 @@ class CipherDatabaseAction(context: Context) {
|
||||
// Temp DAO to easily remove content if object no longer in memory
|
||||
private var useTempDao = PreferencesUtil.isTempAdvancedUnlockEnable(applicationContext)
|
||||
|
||||
private val mIntentAdvancedUnlockService = Intent(applicationContext,
|
||||
AdvancedUnlockNotificationService::class.java)
|
||||
private var mBinder: AdvancedUnlockNotificationService.AdvancedUnlockBinder? = null
|
||||
private var mServiceConnection: ServiceConnection? = null
|
||||
|
||||
private var mDatabaseListeners = LinkedList<DatabaseListener>()
|
||||
private var mDatabaseListeners = LinkedList<CipherDatabaseListener>()
|
||||
private var mAdvancedUnlockBroadcastReceiver = AdvancedUnlockNotificationService.AdvancedUnlockReceiver {
|
||||
deleteAll()
|
||||
removeAllDataAndDetach()
|
||||
}
|
||||
|
||||
fun reloadPreferences() {
|
||||
useTempDao = PreferencesUtil.isTempAdvancedUnlockEnable(applicationContext)
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun attachService(performedAction: () -> Unit) {
|
||||
// Check if a service is currently running else do nothing
|
||||
if (mBinder != null) {
|
||||
private fun serviceActionTask(startService: Boolean = false, performedAction: () -> Unit) {
|
||||
// Check if a service is currently running else call action without info
|
||||
if (startService && mServiceConnection == null) {
|
||||
attachService(performedAction)
|
||||
} else {
|
||||
performedAction.invoke()
|
||||
} else if (mServiceConnection == null) {
|
||||
mServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
|
||||
mBinder = (serviceBinder as AdvancedUnlockNotificationService.AdvancedUnlockBinder)
|
||||
performedAction.invoke()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
mBinder = null
|
||||
mServiceConnection = null
|
||||
mDatabaseListeners.forEach {
|
||||
it.onDatabaseCleared()
|
||||
}
|
||||
}
|
||||
}
|
||||
applicationContext.bindService(mIntentAdvancedUnlockService,
|
||||
mServiceConnection!!,
|
||||
Context.BIND_ABOVE_CLIENT)
|
||||
if (mBinder == null) {
|
||||
try {
|
||||
applicationContext.startService(mIntentAdvancedUnlockService)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to start cipher action", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun registerDatabaseListener(listener: DatabaseListener) {
|
||||
mDatabaseListeners.add(listener)
|
||||
@Synchronized
|
||||
private fun attachService(performedAction: () -> Unit) {
|
||||
applicationContext.registerReceiver(mAdvancedUnlockBroadcastReceiver, IntentFilter().apply {
|
||||
addAction(AdvancedUnlockNotificationService.REMOVE_ADVANCED_UNLOCK_KEY_ACTION)
|
||||
})
|
||||
|
||||
mServiceConnection = object : ServiceConnection {
|
||||
override fun onServiceConnected(name: ComponentName?, serviceBinder: IBinder?) {
|
||||
mBinder = (serviceBinder as AdvancedUnlockNotificationService.AdvancedUnlockBinder)
|
||||
performedAction.invoke()
|
||||
}
|
||||
|
||||
override fun onServiceDisconnected(name: ComponentName?) {
|
||||
onClear()
|
||||
}
|
||||
}
|
||||
try {
|
||||
AdvancedUnlockNotificationService.bindService(applicationContext,
|
||||
mServiceConnection!!,
|
||||
Context.BIND_AUTO_CREATE)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to start cipher action", e)
|
||||
performedAction.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
fun unregisterDatabaseListener(listener: DatabaseListener) {
|
||||
mDatabaseListeners.remove(listener)
|
||||
@Synchronized
|
||||
private fun detachService() {
|
||||
try {
|
||||
applicationContext.unregisterReceiver(mAdvancedUnlockBroadcastReceiver)
|
||||
} catch (e: Exception) {}
|
||||
|
||||
mServiceConnection?.let {
|
||||
AdvancedUnlockNotificationService.unbindService(applicationContext, it)
|
||||
}
|
||||
}
|
||||
|
||||
interface DatabaseListener {
|
||||
fun onDatabaseCleared()
|
||||
private fun removeAllDataAndDetach() {
|
||||
detachService()
|
||||
onClear()
|
||||
}
|
||||
|
||||
fun registerDatabaseListener(listenerCipher: CipherDatabaseListener) {
|
||||
mDatabaseListeners.add(listenerCipher)
|
||||
}
|
||||
|
||||
fun unregisterDatabaseListener(listenerCipher: CipherDatabaseListener) {
|
||||
mDatabaseListeners.remove(listenerCipher)
|
||||
}
|
||||
|
||||
private fun onClear() {
|
||||
mBinder = null
|
||||
mServiceConnection = null
|
||||
mDatabaseListeners.forEach {
|
||||
it.onCipherDatabaseCleared()
|
||||
}
|
||||
}
|
||||
|
||||
interface CipherDatabaseListener {
|
||||
fun onCipherDatabaseCleared()
|
||||
}
|
||||
|
||||
fun getCipherDatabase(databaseUri: Uri,
|
||||
cipherDatabaseResultListener: (CipherDatabaseEntity?) -> Unit) {
|
||||
if (useTempDao) {
|
||||
attachService {
|
||||
serviceActionTask {
|
||||
cipherDatabaseResultListener.invoke(mBinder?.getCipherDatabase(databaseUri))
|
||||
}
|
||||
} else {
|
||||
@@ -126,7 +152,8 @@ class CipherDatabaseAction(context: Context) {
|
||||
fun addOrUpdateCipherDatabase(cipherDatabaseEntity: CipherDatabaseEntity,
|
||||
cipherDatabaseResultListener: (() -> Unit)? = null) {
|
||||
if (useTempDao) {
|
||||
attachService {
|
||||
// The only case to create service (not needed to get an info)
|
||||
serviceActionTask(true) {
|
||||
mBinder?.addOrUpdateCipherDatabase(cipherDatabaseEntity)
|
||||
cipherDatabaseResultListener?.invoke()
|
||||
}
|
||||
@@ -151,7 +178,7 @@ class CipherDatabaseAction(context: Context) {
|
||||
fun deleteByDatabaseUri(databaseUri: Uri,
|
||||
cipherDatabaseResultListener: (() -> Unit)? = null) {
|
||||
if (useTempDao) {
|
||||
attachService {
|
||||
serviceActionTask {
|
||||
mBinder?.deleteByDatabaseUri(databaseUri)
|
||||
cipherDatabaseResultListener?.invoke()
|
||||
}
|
||||
@@ -168,14 +195,19 @@ class CipherDatabaseAction(context: Context) {
|
||||
}
|
||||
|
||||
fun deleteAll() {
|
||||
attachService {
|
||||
mBinder?.deleteAll()
|
||||
if (useTempDao) {
|
||||
serviceActionTask {
|
||||
mBinder?.deleteAll()
|
||||
}
|
||||
}
|
||||
// To erase the residues
|
||||
IOActionTask(
|
||||
{
|
||||
cipherDatabaseDao.deleteAll()
|
||||
}
|
||||
).execute()
|
||||
// Unbind
|
||||
removeAllDataAndDetach()
|
||||
}
|
||||
|
||||
companion object : SingletonHolderParameter<CipherDatabaseAction, Context>(::CipherDatabaseAction) {
|
||||
|
||||
@@ -223,9 +223,22 @@ class StructureParser(private val structure: AssistStructure) {
|
||||
usernameValueCandidate = node.autofillValue
|
||||
Log.d(TAG, "Autofill username candidate android text type: ${showHexInputType(inputType)}")
|
||||
}
|
||||
inputIsVariationType(inputType,
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) -> {
|
||||
// Some forms used visible password as username
|
||||
if (usernameCandidate == null && usernameValueCandidate == null) {
|
||||
usernameCandidate = autofillId
|
||||
usernameValueCandidate = node.autofillValue
|
||||
Log.d(TAG, "Autofill visible password android text type (as username): ${showHexInputType(inputType)}")
|
||||
} else if (result?.passwordId == null && result?.passwordValue == null) {
|
||||
result?.passwordId = autofillId
|
||||
result?.passwordValue = node.autofillValue
|
||||
Log.d(TAG, "Autofill visible password android text type (as password): ${showHexInputType(inputType)}")
|
||||
usernameNeeded = false
|
||||
}
|
||||
}
|
||||
inputIsVariationType(inputType,
|
||||
InputType.TYPE_TEXT_VARIATION_PASSWORD,
|
||||
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
|
||||
InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD) -> {
|
||||
result?.passwordId = autofillId
|
||||
result?.passwordValue = node.autofillValue
|
||||
|
||||
@@ -36,7 +36,6 @@ import com.kunzisoft.keepass.activities.stylish.StylishFragment
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.database.exception.IODatabaseException
|
||||
import com.kunzisoft.keepass.education.PasswordActivityEducation
|
||||
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.view.AdvancedUnlockInfoView
|
||||
|
||||
@@ -68,7 +67,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
|
||||
private lateinit var cipherDatabaseAction : CipherDatabaseAction
|
||||
|
||||
private var cipherDatabaseListener: CipherDatabaseAction.DatabaseListener? = null
|
||||
private var cipherDatabaseListener: CipherDatabaseAction.CipherDatabaseListener? = null
|
||||
|
||||
// Only to fix multiple fingerprint menu #332
|
||||
private var mAllowAdvancedUnlockMenu = false
|
||||
@@ -125,8 +124,10 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|
||||
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(requireContext())
|
||||
context?.let {
|
||||
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(it)
|
||||
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(it)
|
||||
}
|
||||
keepConnection = false
|
||||
}
|
||||
|
||||
@@ -176,34 +177,36 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
* Check unlock availability and change the current mode depending of device's state
|
||||
*/
|
||||
fun checkUnlockAvailability() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
allowOpenBiometricPrompt = true
|
||||
if (PreferencesUtil.isBiometricUnlockEnable(requireContext())) {
|
||||
mAdvancedUnlockInfoView?.setIconResource(R.drawable.fingerprint)
|
||||
context?.let { context ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
allowOpenBiometricPrompt = true
|
||||
if (PreferencesUtil.isBiometricUnlockEnable(context)) {
|
||||
mAdvancedUnlockInfoView?.setIconResource(R.drawable.fingerprint)
|
||||
|
||||
// biometric not supported (by API level or hardware) so keep option hidden
|
||||
// or manually disable
|
||||
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(requireContext())
|
||||
if (!PreferencesUtil.isAdvancedUnlockEnable(requireContext())
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
|
||||
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
|
||||
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_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)
|
||||
// biometric not supported (by API level or hardware) so keep option hidden
|
||||
// or manually disable
|
||||
val biometricCanAuthenticate = AdvancedUnlockManager.canAuthenticate(context)
|
||||
if (!PreferencesUtil.isAdvancedUnlockEnable(context)
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE
|
||||
|| biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
|
||||
toggleMode(Mode.BIOMETRIC_UNAVAILABLE)
|
||||
} else if (biometricCanAuthenticate == BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED) {
|
||||
toggleMode(Mode.BIOMETRIC_SECURITY_UPDATE_REQUIRED)
|
||||
} 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -261,7 +264,7 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
private fun openBiometricSetting() {
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
// ACTION_SECURITY_SETTINGS does not contain fingerprint enrollment on some devices...
|
||||
requireContext().startActivity(Intent(Settings.ACTION_SETTINGS))
|
||||
context?.startActivity(Intent(Settings.ACTION_SETTINGS))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,9 +299,11 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
setAdvancedUnlockedTitleView(R.string.no_credentials_stored)
|
||||
setAdvancedUnlockedMessageView("")
|
||||
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
|
||||
requireContext().getString(R.string.credential_before_click_advanced_unlock_button))
|
||||
context?.let { context ->
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener(false) {
|
||||
onAuthenticationError(BiometricPrompt.ERROR_UNABLE_TO_PROCESS,
|
||||
context.getString(R.string.credential_before_click_advanced_unlock_button))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -402,9 +407,10 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
fun connect(databaseUri: Uri) {
|
||||
showViews(true)
|
||||
this.databaseFileUri = databaseUri
|
||||
cipherDatabaseListener = object: CipherDatabaseAction.DatabaseListener {
|
||||
override fun onDatabaseCleared() {
|
||||
deleteEncryptedDatabaseKey()
|
||||
cipherDatabaseListener = object: CipherDatabaseAction.CipherDatabaseListener {
|
||||
override fun onCipherDatabaseCleared() {
|
||||
advancedUnlockManager?.closeBiometricPrompt()
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
}
|
||||
cipherDatabaseAction.apply {
|
||||
@@ -435,14 +441,12 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun deleteEncryptedDatabaseKey() {
|
||||
allowOpenBiometricPrompt = false
|
||||
mAdvancedUnlockInfoView?.setIconViewClickListener(false, null)
|
||||
advancedUnlockManager?.closeBiometricPrompt()
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
cipherDatabaseAction.deleteByDatabaseUri(databaseUri) {
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
}
|
||||
} ?: checkUnlockAvailability()
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
|
||||
@@ -479,7 +483,6 @@ class AdvancedUnlockFragment: StylishFragment(), AdvancedUnlockManager.AdvancedU
|
||||
mBuilderListener?.retrieveCredentialForEncryption()?.let { credential ->
|
||||
advancedUnlockManager?.encryptData(credential)
|
||||
}
|
||||
AdvancedUnlockNotificationService.startServiceForTimeout(requireContext())
|
||||
}
|
||||
Mode.EXTRACT_CREDENTIAL -> {
|
||||
// retrieve the encrypted value from preferences
|
||||
|
||||
@@ -31,41 +31,47 @@ class DeleteNodesRunnable(context: Context,
|
||||
afterActionNodesFinish: AfterActionNodesFinish)
|
||||
: ActionNodeDatabaseRunnable(context, database, afterActionNodesFinish, save) {
|
||||
|
||||
private var mParent: Group? = null
|
||||
private var mOldParent: Group? = null
|
||||
private var mCanRecycle: Boolean = false
|
||||
|
||||
private var mNodesToDeleteBackup = ArrayList<Node>()
|
||||
|
||||
override fun nodeAction() {
|
||||
|
||||
foreachNode@ for(currentNode in mNodesToDelete) {
|
||||
mParent = currentNode.parent
|
||||
mParent?.touch(modified = false, touchParents = true)
|
||||
foreachNode@ for(nodeToDelete in mNodesToDelete) {
|
||||
mOldParent = nodeToDelete.parent
|
||||
mOldParent?.touch(modified = false, touchParents = true)
|
||||
|
||||
when (currentNode.type) {
|
||||
when (nodeToDelete.type) {
|
||||
Type.GROUP -> {
|
||||
val groupToDelete = nodeToDelete as Group
|
||||
// Create a copy to keep the old ref and remove it visually
|
||||
mNodesToDeleteBackup.add(Group(currentNode as Group))
|
||||
mNodesToDeleteBackup.add(Group(groupToDelete))
|
||||
// Remove Node from parent
|
||||
mCanRecycle = database.canRecycle(currentNode)
|
||||
mCanRecycle = database.canRecycle(groupToDelete)
|
||||
if (mCanRecycle) {
|
||||
database.recycle(currentNode, context.resources)
|
||||
groupToDelete.touch(modified = false, touchParents = true)
|
||||
database.recycle(groupToDelete, context.resources)
|
||||
groupToDelete.setPreviousParentGroup(mOldParent)
|
||||
} else {
|
||||
database.deleteGroup(currentNode)
|
||||
database.deleteGroup(groupToDelete)
|
||||
}
|
||||
}
|
||||
Type.ENTRY -> {
|
||||
val entryToDelete = nodeToDelete as Entry
|
||||
// Create a copy to keep the old ref and remove it visually
|
||||
mNodesToDeleteBackup.add(Entry(currentNode as Entry))
|
||||
mNodesToDeleteBackup.add(Entry(entryToDelete))
|
||||
// Remove Node from parent
|
||||
mCanRecycle = database.canRecycle(currentNode)
|
||||
mCanRecycle = database.canRecycle(entryToDelete)
|
||||
if (mCanRecycle) {
|
||||
database.recycle(currentNode, context.resources)
|
||||
entryToDelete.touch(modified = false, touchParents = true)
|
||||
database.recycle(entryToDelete, context.resources)
|
||||
entryToDelete.setPreviousParentGroup(mOldParent)
|
||||
} else {
|
||||
database.deleteEntry(currentNode)
|
||||
database.deleteEntry(entryToDelete)
|
||||
}
|
||||
// Remove the oldest attachments
|
||||
currentNode.getAttachments(database.attachmentPool).forEach {
|
||||
entryToDelete.getAttachments(database.attachmentPool).forEach {
|
||||
database.removeAttachmentIfNotUsed(it)
|
||||
}
|
||||
}
|
||||
@@ -76,7 +82,7 @@ class DeleteNodesRunnable(context: Context,
|
||||
override fun nodeFinish(): ActionNodesValues {
|
||||
if (!result.isSuccess) {
|
||||
if (mCanRecycle) {
|
||||
mParent?.let {
|
||||
mOldParent?.let {
|
||||
mNodesToDeleteBackup.forEach { backupNode ->
|
||||
when (backupNode.type) {
|
||||
Type.GROUP -> {
|
||||
|
||||
@@ -24,7 +24,7 @@ import android.util.Log
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.database.exception.EntryDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.MoveEntryDatabaseException
|
||||
import com.kunzisoft.keepass.database.exception.MoveGroupDatabaseException
|
||||
|
||||
class MoveNodesRunnable constructor(
|
||||
@@ -47,11 +47,14 @@ class MoveNodesRunnable constructor(
|
||||
when (nodeToMove.type) {
|
||||
Type.GROUP -> {
|
||||
val groupToMove = nodeToMove as Group
|
||||
// Move group in new parent if not in the current group
|
||||
if (groupToMove != mNewParent
|
||||
// Move group if the parent change
|
||||
if (mOldParent != mNewParent
|
||||
// and if not in the current group
|
||||
&& groupToMove != mNewParent
|
||||
&& !mNewParent.isContainedIn(groupToMove)) {
|
||||
nodeToMove.touch(modified = true, touchParents = true)
|
||||
groupToMove.touch(modified = true, touchParents = true)
|
||||
database.moveGroupTo(groupToMove, mNewParent)
|
||||
groupToMove.setPreviousParentGroup(mOldParent)
|
||||
} else {
|
||||
// Only finish thread
|
||||
setError(MoveGroupDatabaseException())
|
||||
@@ -64,11 +67,12 @@ class MoveNodesRunnable constructor(
|
||||
if (mOldParent != mNewParent
|
||||
// and root can contains entry
|
||||
&& (mNewParent != database.rootGroup || database.rootCanContainsEntry())) {
|
||||
nodeToMove.touch(modified = true, touchParents = true)
|
||||
entryToMove.touch(modified = true, touchParents = true)
|
||||
database.moveEntryTo(entryToMove, mNewParent)
|
||||
entryToMove.setPreviousParentGroup(mOldParent)
|
||||
} else {
|
||||
// Only finish thread
|
||||
setError(EntryDatabaseException())
|
||||
setError(MoveEntryDatabaseException())
|
||||
break@foreachNode
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.kunzisoft.keepass.database.element
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.utils.ParcelableUtil
|
||||
import java.util.*
|
||||
|
||||
class CustomData : Parcelable {
|
||||
|
||||
private val mCustomDataItems = HashMap<String, CustomDataItem>()
|
||||
|
||||
constructor()
|
||||
|
||||
constructor(toCopy: CustomData) {
|
||||
mCustomDataItems.clear()
|
||||
mCustomDataItems.putAll(toCopy.mCustomDataItems)
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) {
|
||||
ParcelableUtil.readStringParcelableMap(parcel, CustomDataItem::class.java)
|
||||
}
|
||||
|
||||
fun get(key: String): CustomDataItem? {
|
||||
return mCustomDataItems[key]
|
||||
}
|
||||
|
||||
fun put(customDataItem: CustomDataItem) {
|
||||
mCustomDataItems[customDataItem.key] = customDataItem
|
||||
}
|
||||
|
||||
fun containsItemWithValue(value: String): Boolean {
|
||||
return mCustomDataItems.any { mapEntry -> mapEntry.value.value.equals(value, true) }
|
||||
}
|
||||
|
||||
fun containsItemWithLastModificationTime(): Boolean {
|
||||
return mCustomDataItems.any { mapEntry -> mapEntry.value.lastModificationTime != null }
|
||||
}
|
||||
|
||||
fun isNotEmpty(): Boolean {
|
||||
return mCustomDataItems.isNotEmpty()
|
||||
}
|
||||
|
||||
fun doForEachItems(action: (CustomDataItem) -> Unit) {
|
||||
for ((_, value) in mCustomDataItems) {
|
||||
action.invoke(value)
|
||||
}
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
ParcelableUtil.writeStringParcelableMap(parcel, flags, mCustomDataItems)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<CustomData> {
|
||||
override fun createFromParcel(parcel: Parcel): CustomData {
|
||||
return CustomData(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<CustomData?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,6 @@ import android.content.ContentResolver
|
||||
import android.content.res.Resources
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.utils.readBytes4ToUInt
|
||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
|
||||
@@ -43,7 +42,7 @@ import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.database.exception.*
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
|
||||
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDB
|
||||
import com.kunzisoft.keepass.database.file.input.DatabaseInputKDBX
|
||||
import com.kunzisoft.keepass.database.file.output.DatabaseOutputKDB
|
||||
@@ -55,6 +54,7 @@ import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskUpdater
|
||||
import com.kunzisoft.keepass.utils.SingletonHolder
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import com.kunzisoft.keepass.utils.readBytes4ToUInt
|
||||
import java.io.*
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
@@ -222,7 +222,7 @@ class Database {
|
||||
// Default compression not necessary if stored in header
|
||||
mDatabaseKDBX?.let {
|
||||
return it.compressionAlgorithm == CompressionAlgorithm.GZip
|
||||
&& it.kdbxVersion.isBefore(FILE_VERSION_32_4)
|
||||
&& it.kdbxVersion.isBefore(FILE_VERSION_40)
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -359,15 +359,10 @@ class Database {
|
||||
return null
|
||||
}
|
||||
|
||||
fun ensureRecycleBinExists(resources: Resources) {
|
||||
mDatabaseKDB?.ensureBackupExists()
|
||||
mDatabaseKDBX?.ensureRecycleBinExists(resources)
|
||||
}
|
||||
|
||||
fun removeRecycleBin() {
|
||||
// Don't allow remove backup in KDB
|
||||
mDatabaseKDBX?.removeRecycleBin()
|
||||
}
|
||||
val groupNamesNotAllowed: List<String>
|
||||
get() {
|
||||
return mDatabaseKDB?.groupNamesNotAllowed ?: ArrayList()
|
||||
}
|
||||
|
||||
private fun setDatabaseKDB(databaseKDB: DatabaseKDB) {
|
||||
this.mDatabaseKDB = databaseKDB
|
||||
@@ -542,25 +537,27 @@ class Database {
|
||||
omitBackup: Boolean,
|
||||
max: Int = Integer.MAX_VALUE): Group? {
|
||||
return mSearchHelper?.createVirtualGroupWithSearchResult(this,
|
||||
searchQuery, SearchParameters(), omitBackup, max)
|
||||
SearchParameters().apply {
|
||||
this.searchQuery = searchQuery
|
||||
}, omitBackup, max)
|
||||
}
|
||||
|
||||
fun createVirtualGroupFromSearchInfo(searchInfoString: String,
|
||||
omitBackup: Boolean,
|
||||
max: Int = Integer.MAX_VALUE): Group? {
|
||||
return mSearchHelper?.createVirtualGroupWithSearchResult(this,
|
||||
searchInfoString, SearchParameters().apply {
|
||||
searchInTitles = true
|
||||
searchInUserNames = false
|
||||
searchInPasswords = false
|
||||
searchInUrls = true
|
||||
searchInNotes = true
|
||||
searchInOTP = false
|
||||
searchInOther = true
|
||||
searchInUUIDs = false
|
||||
searchInTags = false
|
||||
ignoreCase = true
|
||||
}, omitBackup, max)
|
||||
SearchParameters().apply {
|
||||
searchQuery = searchInfoString
|
||||
searchInTitles = true
|
||||
searchInUserNames = false
|
||||
searchInPasswords = false
|
||||
searchInUrls = true
|
||||
searchInNotes = true
|
||||
searchInOTP = false
|
||||
searchInOther = true
|
||||
searchInUUIDs = false
|
||||
searchInTags = false
|
||||
}, omitBackup, max)
|
||||
}
|
||||
|
||||
val attachmentPool: AttachmentPool
|
||||
@@ -790,11 +787,11 @@ class Database {
|
||||
}
|
||||
|
||||
fun addGroupTo(group: Group, parent: Group) {
|
||||
group.groupKDB?.let { entryKDB ->
|
||||
mDatabaseKDB?.addGroupTo(entryKDB, parent.groupKDB)
|
||||
group.groupKDB?.let { groupKDB ->
|
||||
mDatabaseKDB?.addGroupTo(groupKDB, parent.groupKDB)
|
||||
}
|
||||
group.groupKDBX?.let { entryKDBX ->
|
||||
mDatabaseKDBX?.addGroupTo(entryKDBX, parent.groupKDBX)
|
||||
group.groupKDBX?.let { groupKDBX ->
|
||||
mDatabaseKDBX?.addGroupTo(groupKDBX, parent.groupKDBX)
|
||||
}
|
||||
group.afterAssignNewParent()
|
||||
}
|
||||
@@ -809,11 +806,11 @@ class Database {
|
||||
}
|
||||
|
||||
fun removeGroupFrom(group: Group, parent: Group) {
|
||||
group.groupKDB?.let { entryKDB ->
|
||||
mDatabaseKDB?.removeGroupFrom(entryKDB, parent.groupKDB)
|
||||
group.groupKDB?.let { groupKDB ->
|
||||
mDatabaseKDB?.removeGroupFrom(groupKDB, parent.groupKDB)
|
||||
}
|
||||
group.groupKDBX?.let { entryKDBX ->
|
||||
mDatabaseKDBX?.removeGroupFrom(entryKDBX, parent.groupKDBX)
|
||||
group.groupKDBX?.let { groupKDBX ->
|
||||
mDatabaseKDBX?.removeGroupFrom(groupKDBX, parent.groupKDBX)
|
||||
}
|
||||
group.afterAssignNewParent()
|
||||
}
|
||||
@@ -888,6 +885,16 @@ class Database {
|
||||
}
|
||||
}
|
||||
|
||||
fun ensureRecycleBinExists(resources: Resources) {
|
||||
mDatabaseKDB?.ensureBackupExists()
|
||||
mDatabaseKDBX?.ensureRecycleBinExists(resources)
|
||||
}
|
||||
|
||||
fun removeRecycleBin() {
|
||||
// Don't allow remove backup in KDB
|
||||
mDatabaseKDBX?.removeRecycleBin()
|
||||
}
|
||||
|
||||
fun canRecycle(entry: Entry): Boolean {
|
||||
var canRecycle: Boolean? = null
|
||||
entry.entryKDB?.let {
|
||||
|
||||
@@ -55,7 +55,7 @@ class DateInstant : Parcelable {
|
||||
jDate = Date()
|
||||
}
|
||||
|
||||
protected constructor(parcel: Parcel) {
|
||||
constructor(parcel: Parcel) {
|
||||
jDate = parcel.readSerializable() as Date
|
||||
}
|
||||
|
||||
|
||||
@@ -19,30 +19,37 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.element
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.ParcelUuid
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
|
||||
import java.util.Date
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
|
||||
class DeletedObject {
|
||||
class DeletedObject : Parcelable {
|
||||
|
||||
var uuid: UUID = DatabaseVersioned.UUID_ZERO
|
||||
private var mDeletionTime: Date? = null
|
||||
private var mDeletionTime: DateInstant? = null
|
||||
|
||||
fun getDeletionTime(): Date {
|
||||
constructor()
|
||||
|
||||
constructor(uuid: UUID, deletionTime: DateInstant = DateInstant()) {
|
||||
this.uuid = uuid
|
||||
this.mDeletionTime = deletionTime
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) {
|
||||
uuid = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
|
||||
mDeletionTime = parcel.readParcelable(DateInstant::class.java.classLoader)
|
||||
}
|
||||
|
||||
fun getDeletionTime(): DateInstant {
|
||||
if (mDeletionTime == null) {
|
||||
mDeletionTime = Date(System.currentTimeMillis())
|
||||
mDeletionTime = DateInstant(System.currentTimeMillis())
|
||||
}
|
||||
return mDeletionTime!!
|
||||
}
|
||||
|
||||
fun setDeletionTime(deletionTime: Date) {
|
||||
this.mDeletionTime = deletionTime
|
||||
}
|
||||
|
||||
constructor()
|
||||
|
||||
constructor(uuid: UUID, deletionTime: Date = Date()) {
|
||||
this.uuid = uuid
|
||||
fun setDeletionTime(deletionTime: DateInstant) {
|
||||
this.mDeletionTime = deletionTime
|
||||
}
|
||||
|
||||
@@ -59,4 +66,23 @@ class DeletedObject {
|
||||
override fun hashCode(): Int {
|
||||
return uuid.hashCode()
|
||||
}
|
||||
|
||||
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||
parcel.writeParcelable(ParcelUuid(uuid), flags)
|
||||
parcel.writeParcelable(mDeletionTime, flags)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<DeletedObject> {
|
||||
override fun createFromParcel(parcel: Parcel): DeletedObject {
|
||||
return DeletedObject(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<DeletedObject?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryVersionedInterface
|
||||
@@ -114,6 +115,20 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
entryKDBX?.icon = value
|
||||
}
|
||||
|
||||
var tags: Tags
|
||||
get() = entryKDBX?.tags ?: Tags()
|
||||
set(value) {
|
||||
entryKDBX?.tags = value
|
||||
}
|
||||
|
||||
var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
|
||||
get() = entryKDBX?.previousParentGroup ?: DatabaseVersioned.UUID_ZERO
|
||||
private set
|
||||
|
||||
fun setPreviousParentGroup(previousParent: Group?) {
|
||||
entryKDBX?.previousParentGroup = previousParent?.groupKDBX?.id ?: DatabaseVersioned.UUID_ZERO
|
||||
}
|
||||
|
||||
override val type: Type
|
||||
get() = Type.ENTRY
|
||||
|
||||
@@ -373,10 +388,6 @@ class Entry : Node, EntryVersionedInterface<Group> {
|
||||
return entryKDBX?.getSize(attachmentPool) ?: 0L
|
||||
}
|
||||
|
||||
fun containsCustomData(): Boolean {
|
||||
return entryKDBX?.containsCustomData() ?: false
|
||||
}
|
||||
|
||||
/*
|
||||
------------
|
||||
Converter
|
||||
|
||||
@@ -22,6 +22,7 @@ package com.kunzisoft.keepass.database.element
|
||||
import android.content.Context
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||
import com.kunzisoft.keepass.database.element.group.GroupVersionedInterface
|
||||
@@ -134,6 +135,20 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
groupKDBX?.icon = value
|
||||
}
|
||||
|
||||
var tags: Tags
|
||||
get() = groupKDBX?.tags ?: Tags()
|
||||
set(value) {
|
||||
groupKDBX?.tags = value
|
||||
}
|
||||
|
||||
var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
|
||||
get() = groupKDBX?.previousParentGroup ?: DatabaseVersioned.UUID_ZERO
|
||||
private set
|
||||
|
||||
fun setPreviousParentGroup(previousParent: Group?) {
|
||||
groupKDBX?.previousParentGroup = previousParent?.groupKDBX?.id ?: DatabaseVersioned.UUID_ZERO
|
||||
}
|
||||
|
||||
override val type: Type
|
||||
get() = Type.GROUP
|
||||
|
||||
@@ -368,14 +383,6 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
groupKDB?.nodeId = id
|
||||
}
|
||||
|
||||
fun getLevel(): Int {
|
||||
return groupKDB?.level ?: -1
|
||||
}
|
||||
|
||||
fun setLevel(level: Int) {
|
||||
groupKDB?.level = level
|
||||
}
|
||||
|
||||
/*
|
||||
------------
|
||||
KDBX Methods
|
||||
@@ -402,10 +409,6 @@ class Group : Node, GroupVersionedInterface<Group, Entry> {
|
||||
groupKDBX?.isExpanded = expanded
|
||||
}
|
||||
|
||||
fun containsCustomData(): Boolean {
|
||||
return groupKDBX?.containsCustomData() ?: false
|
||||
}
|
||||
|
||||
/*
|
||||
------------
|
||||
Converter
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,27 @@
|
||||
package com.kunzisoft.keepass.database.element.binary
|
||||
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||
import java.util.*
|
||||
|
||||
class CustomIconPool(binaryCache: BinaryCache) : BinaryPool<UUID>(binaryCache) {
|
||||
class CustomIconPool(private val binaryCache: BinaryCache) : BinaryPool<UUID>(binaryCache) {
|
||||
|
||||
private val customIcons = HashMap<UUID, IconImageCustom>()
|
||||
|
||||
fun put(key: UUID? = null,
|
||||
name: String,
|
||||
lastModificationTime: DateInstant?,
|
||||
smallSize: Boolean,
|
||||
result: (IconImageCustom, BinaryData?) -> Unit) {
|
||||
val keyBinary = super.put(key) { uniqueBinaryId ->
|
||||
// Create a byte array for better performance with small data
|
||||
binaryCache.getBinaryData(uniqueBinaryId, smallSize)
|
||||
}
|
||||
val uuid = keyBinary.keys.first()
|
||||
val customIcon = IconImageCustom(uuid, name, lastModificationTime)
|
||||
customIcons[uuid] = customIcon
|
||||
result.invoke(customIcon, keyBinary.binary)
|
||||
}
|
||||
|
||||
override fun findUnusedKey(): UUID {
|
||||
var newUUID = UUID.randomUUID()
|
||||
@@ -11,4 +30,14 @@ class CustomIconPool(binaryCache: BinaryCache) : BinaryPool<UUID>(binaryCache) {
|
||||
}
|
||||
return newUUID
|
||||
}
|
||||
|
||||
fun any(predicate: (IconImageCustom)-> Boolean): Boolean {
|
||||
return customIcons.any { predicate(it.value) }
|
||||
}
|
||||
|
||||
fun doForEachCustomIcon(action: (customIcon: IconImageCustom, binary: BinaryData) -> Unit) {
|
||||
doForEachBinary { key, binary ->
|
||||
action.invoke(customIcons[key] ?: IconImageCustom(key), binary)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,8 +38,6 @@ import kotlin.collections.ArrayList
|
||||
|
||||
class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
|
||||
private var backupGroupId: Int = BACKUP_FOLDER_UNDEFINED_ID
|
||||
|
||||
private var kdfListV3: MutableList<KdfEngine> = ArrayList()
|
||||
|
||||
override val version: String
|
||||
@@ -55,13 +53,14 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
return getGroupById(NodeIdInt(groupId))
|
||||
}
|
||||
|
||||
// Retrieve backup group in index
|
||||
val backupGroup: GroupKDB?
|
||||
get() {
|
||||
return if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
|
||||
null
|
||||
else
|
||||
getGroupById(backupGroupId)
|
||||
return retrieveBackup()
|
||||
}
|
||||
|
||||
val groupNamesNotAllowed: List<String>
|
||||
get() {
|
||||
return listOf(BACKUP_FOLDER_TITLE)
|
||||
}
|
||||
|
||||
override val kdfEngine: KdfEngine
|
||||
@@ -80,12 +79,7 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
|
||||
val rootGroups: List<GroupKDB>
|
||||
get() {
|
||||
val kids = ArrayList<GroupKDB>()
|
||||
doForEachGroupInIndex { group ->
|
||||
if (group.level == 0)
|
||||
kids.add(group)
|
||||
}
|
||||
return kids
|
||||
return rootGroup?.getChildGroups() ?: ArrayList()
|
||||
}
|
||||
|
||||
override val passwordEncoding: String
|
||||
@@ -163,27 +157,16 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
return this.iconsManager.getIcon(iconId)
|
||||
}
|
||||
|
||||
override fun containsCustomData(): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun isInRecycleBin(group: GroupKDB): Boolean {
|
||||
var currentGroup: GroupKDB? = group
|
||||
val currentBackupGroup = backupGroup ?: return false
|
||||
|
||||
// Init backup group variable
|
||||
if (backupGroupId == BACKUP_FOLDER_UNDEFINED_ID)
|
||||
findBackupGroupId()
|
||||
|
||||
if (backupGroup == null)
|
||||
return false
|
||||
|
||||
if (currentGroup == backupGroup)
|
||||
if (currentGroup == currentBackupGroup)
|
||||
return true
|
||||
|
||||
val backupGroupId = currentBackupGroup.id
|
||||
while (currentGroup != null) {
|
||||
if (currentGroup.level == 0
|
||||
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) {
|
||||
backupGroupId = currentGroup.id
|
||||
if (backupGroupId == currentGroup.id) {
|
||||
return true
|
||||
}
|
||||
currentGroup = currentGroup.parent
|
||||
@@ -191,12 +174,12 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun findBackupGroupId() {
|
||||
rootGroups.forEach { currentGroup ->
|
||||
if (currentGroup.level == 0
|
||||
&& currentGroup.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)) {
|
||||
backupGroupId = currentGroup.id
|
||||
}
|
||||
/**
|
||||
* Retrieve backup group with his name
|
||||
*/
|
||||
private fun retrieveBackup(): GroupKDB? {
|
||||
return rootGroup?.searchChildGroup {
|
||||
it.title.equals(BACKUP_FOLDER_TITLE, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,8 +188,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
* if it doesn't exist
|
||||
*/
|
||||
fun ensureBackupExists() {
|
||||
findBackupGroupId()
|
||||
|
||||
if (backupGroup == null) {
|
||||
// Create recycle bin
|
||||
val recycleBinGroup = createGroup().apply {
|
||||
@@ -214,7 +195,6 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
icon.standard = getStandardIcon(IconImageStandard.TRASH_ID)
|
||||
}
|
||||
addGroupTo(recycleBinGroup, rootGroup)
|
||||
backupGroupId = recycleBinGroup.id
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,6 +248,5 @@ class DatabaseKDB : DatabaseVersioned<Int, UUID, GroupKDB, EntryKDB>() {
|
||||
val TYPE = DatabaseKDB::class.java
|
||||
|
||||
const val BACKUP_FOLDER_TITLE = "Backup"
|
||||
private const val BACKUP_FOLDER_UNDEFINED_ID = -1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,6 @@ import android.content.res.Resources
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import com.kunzisoft.encrypt.HashManager
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import com.kunzisoft.keepass.utils.longTo8Bytes
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||
import com.kunzisoft.keepass.database.crypto.AesEngine
|
||||
@@ -34,11 +32,13 @@ import com.kunzisoft.keepass.database.crypto.VariantDictionary
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfEngine
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfParameters
|
||||
import com.kunzisoft.keepass.database.element.CustomData
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.DeletedObject
|
||||
import com.kunzisoft.keepass.database.element.binary.BinaryData
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB.Companion.BACKUP_FOLDER_TITLE
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.element.entry.FieldReferencesEngine
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageCustom
|
||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||
@@ -46,10 +46,13 @@ import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.database.element.node.NodeVersioned
|
||||
import com.kunzisoft.keepass.database.element.security.MemoryProtectionConfig
|
||||
import com.kunzisoft.keepass.database.exception.UnknownKDF
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_3
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_31
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_41
|
||||
import com.kunzisoft.keepass.utils.StringUtil.removeSpaceChars
|
||||
import com.kunzisoft.keepass.utils.StringUtil.toHexString
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import com.kunzisoft.keepass.utils.longTo8Bytes
|
||||
import org.apache.commons.codec.binary.Hex
|
||||
import org.w3c.dom.Node
|
||||
import java.io.IOException
|
||||
@@ -75,6 +78,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
private var kdfList: MutableList<KdfEngine> = ArrayList()
|
||||
private var numKeyEncRounds: Long = 0
|
||||
var publicCustomData = VariantDictionary()
|
||||
private val mFieldReferenceEngine = FieldReferencesEngine(this)
|
||||
|
||||
var kdbxVersion = UnsignedInt(0)
|
||||
var name = ""
|
||||
@@ -100,7 +104,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
*/
|
||||
var isRecycleBinEnabled = true
|
||||
var recycleBinUUID: UUID = UUID_ZERO
|
||||
var recycleBinChanged = Date()
|
||||
var recycleBinChanged = DateInstant()
|
||||
var entryTemplatesGroup = UUID_ZERO
|
||||
var entryTemplatesGroupChanged = DateInstant()
|
||||
var historyMaxItems = DEFAULT_HISTORY_MAX_ITEMS
|
||||
@@ -109,7 +113,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
var lastTopVisibleGroupUUID = UUID_ZERO
|
||||
var memoryProtection = MemoryProtectionConfig()
|
||||
val deletedObjects = ArrayList<DeletedObject>()
|
||||
val customData = HashMap<String, String>()
|
||||
val customData = CustomData()
|
||||
|
||||
var localizedAppName = "KeePassDX"
|
||||
|
||||
@@ -126,20 +130,20 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
*/
|
||||
constructor(databaseName: String, rootName: String) {
|
||||
name = databaseName
|
||||
kdbxVersion = FILE_VERSION_32_3
|
||||
kdbxVersion = FILE_VERSION_31
|
||||
val group = createGroup().apply {
|
||||
title = rootName
|
||||
icon.standard = getStandardIcon(IconImageStandard.FOLDER_ID)
|
||||
}
|
||||
rootGroup = group
|
||||
addGroupIndex(group)
|
||||
}
|
||||
|
||||
override val version: String
|
||||
get() {
|
||||
val kdbxStringVersion = when(kdbxVersion) {
|
||||
FILE_VERSION_32_3 -> "3.1"
|
||||
FILE_VERSION_32_4 -> "4.0"
|
||||
FILE_VERSION_31 -> "3.1"
|
||||
FILE_VERSION_40 -> "4.0"
|
||||
FILE_VERSION_41 -> "4.1"
|
||||
else -> "UNKNOWN"
|
||||
}
|
||||
return "KeePass 2 - KDBX$kdbxStringVersion"
|
||||
@@ -187,7 +191,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
}
|
||||
CompressionAlgorithm.GZip -> {
|
||||
// Only in databaseV3.1, in databaseV4 the header is zipped during the save
|
||||
if (kdbxVersion.isBefore(FILE_VERSION_32_4)) {
|
||||
if (kdbxVersion.isBefore(FILE_VERSION_40)) {
|
||||
compressAllBinaries()
|
||||
}
|
||||
}
|
||||
@@ -195,7 +199,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
}
|
||||
CompressionAlgorithm.GZip -> {
|
||||
// In databaseV4 the header is zipped during the save, so not necessary here
|
||||
if (kdbxVersion.isBefore(FILE_VERSION_32_4)) {
|
||||
if (kdbxVersion.isBefore(FILE_VERSION_40)) {
|
||||
when (newCompression) {
|
||||
CompressionAlgorithm.None -> {
|
||||
decompressAllBinaries()
|
||||
@@ -313,9 +317,11 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
}
|
||||
|
||||
fun addCustomIcon(customIconId: UUID? = null,
|
||||
name: String,
|
||||
lastModificationTime: DateInstant?,
|
||||
smallSize: Boolean,
|
||||
result: (IconImageCustom, BinaryData?) -> Unit) {
|
||||
iconsManager.addCustomIcon(customIconId, smallSize, result)
|
||||
iconsManager.addCustomIcon(customIconId, name, lastModificationTime, smallSize, result)
|
||||
}
|
||||
|
||||
fun isCustomIconBinaryDuplicate(binary: BinaryData): Boolean {
|
||||
@@ -326,12 +332,20 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
return this.iconsManager.getIcon(iconUuid)
|
||||
}
|
||||
|
||||
fun putCustomData(label: String, value: String) {
|
||||
this.customData[label] = value
|
||||
/**
|
||||
* To perform a search in entry custom data
|
||||
*/
|
||||
fun getEntryByCustomData(customDataValue: String): EntryKDBX? {
|
||||
return entryIndexes.values.find { entry ->
|
||||
entry.customData.containsItemWithValue(customDataValue)
|
||||
}
|
||||
}
|
||||
|
||||
override fun containsCustomData(): Boolean {
|
||||
return customData.isNotEmpty()
|
||||
/**
|
||||
* Retrieve the value of a field reference
|
||||
*/
|
||||
fun getFieldReferenceValue(textReference: String, recursionLevel: Int): String {
|
||||
return mFieldReferenceEngine.compile(textReference, recursionLevel)
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
@@ -594,14 +608,14 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
}
|
||||
addGroupTo(recycleBinGroup, rootGroup)
|
||||
recycleBinUUID = recycleBinGroup.id
|
||||
recycleBinChanged = recycleBinGroup.lastModificationTime.date
|
||||
recycleBinChanged = recycleBinGroup.lastModificationTime
|
||||
}
|
||||
}
|
||||
|
||||
fun removeRecycleBin() {
|
||||
if (recycleBin != null) {
|
||||
recycleBinUUID = UUID_ZERO
|
||||
recycleBinChanged = DateInstant().date
|
||||
recycleBinChanged = DateInstant()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -615,6 +629,9 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
return false
|
||||
if (recycleBin == null)
|
||||
return false
|
||||
if (node is GroupKDBX
|
||||
&& recycleBin!!.isContainedIn(node))
|
||||
return false
|
||||
if (!node.isContainedIn(recycleBin!!))
|
||||
return true
|
||||
return false
|
||||
@@ -652,9 +669,20 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
this.deletedObjects.add(deletedObject)
|
||||
}
|
||||
|
||||
override fun addEntryTo(newEntry: EntryKDBX, parent: GroupKDBX?) {
|
||||
super.addEntryTo(newEntry, parent)
|
||||
mFieldReferenceEngine.clear()
|
||||
}
|
||||
|
||||
override fun updateEntry(entry: EntryKDBX) {
|
||||
super.updateEntry(entry)
|
||||
mFieldReferenceEngine.clear()
|
||||
}
|
||||
|
||||
override fun removeEntryFrom(entryToRemove: EntryKDBX, parent: GroupKDBX?) {
|
||||
super.removeEntryFrom(entryToRemove, parent)
|
||||
deletedObjects.add(DeletedObject(entryToRemove.id))
|
||||
mFieldReferenceEngine.clear()
|
||||
}
|
||||
|
||||
override fun undoDeleteEntryFrom(entry: EntryKDBX, origParent: GroupKDBX?) {
|
||||
@@ -725,6 +753,7 @@ class DatabaseKDBX : DatabaseVersioned<UUID, UUID, GroupKDBX, EntryKDBX> {
|
||||
override fun clearCache() {
|
||||
try {
|
||||
super.clearCache()
|
||||
mFieldReferenceEngine.clear()
|
||||
attachmentPool.clear()
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to clear cache", e)
|
||||
|
||||
@@ -35,7 +35,6 @@ import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.UnsupportedEncodingException
|
||||
import java.security.MessageDigest
|
||||
import java.util.*
|
||||
|
||||
abstract class DatabaseVersioned<
|
||||
@@ -68,7 +67,7 @@ abstract class DatabaseVersioned<
|
||||
var changeDuplicateId = false
|
||||
|
||||
private var groupIndexes = LinkedHashMap<NodeId<GroupId>, Group>()
|
||||
private var entryIndexes = LinkedHashMap<NodeId<EntryId>, Entry>()
|
||||
protected var entryIndexes = LinkedHashMap<NodeId<EntryId>, Entry>()
|
||||
|
||||
abstract val version: String
|
||||
|
||||
@@ -87,6 +86,12 @@ abstract class DatabaseVersioned<
|
||||
abstract val availableEncryptionAlgorithms: List<EncryptionAlgorithm>
|
||||
|
||||
var rootGroup: Group? = null
|
||||
set(value) {
|
||||
field = value
|
||||
value?.let {
|
||||
addGroupIndex(it)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class)
|
||||
protected abstract fun getMasterKey(key: String?, keyInputStream: InputStream?): ByteArray
|
||||
@@ -266,6 +271,26 @@ abstract class DatabaseVersioned<
|
||||
return this.entryIndexes[id]
|
||||
}
|
||||
|
||||
fun getEntryByTitle(title: String): Entry? {
|
||||
return this.entryIndexes.values.find { entry -> entry.title.equals(title, true) }
|
||||
}
|
||||
|
||||
fun getEntryByUsername(username: String): Entry? {
|
||||
return this.entryIndexes.values.find { entry -> entry.username.equals(username, true) }
|
||||
}
|
||||
|
||||
fun getEntryByURL(url: String): Entry? {
|
||||
return this.entryIndexes.values.find { entry -> entry.url.equals(url, true) }
|
||||
}
|
||||
|
||||
fun getEntryByPassword(password: String): Entry? {
|
||||
return this.entryIndexes.values.find { entry -> entry.password.equals(password, true) }
|
||||
}
|
||||
|
||||
fun getEntryByNotes(notes: String): Entry? {
|
||||
return this.entryIndexes.values.find { entry -> entry.notes.equals(notes, true) }
|
||||
}
|
||||
|
||||
fun addEntryIndex(entry: Entry) {
|
||||
val entryId = entry.nodeId
|
||||
if (entryIndexes.containsKey(entryId)) {
|
||||
@@ -312,8 +337,6 @@ abstract class DatabaseVersioned<
|
||||
|
||||
abstract fun getStandardIcon(iconId: Int): IconImageStandard
|
||||
|
||||
abstract fun containsCustomData(): Boolean
|
||||
|
||||
fun addGroupTo(newGroup: Group, parent: Group?) {
|
||||
// Add tree to parent tree
|
||||
parent?.addChildGroup(newGroup)
|
||||
@@ -331,14 +354,14 @@ abstract class DatabaseVersioned<
|
||||
removeGroupIndex(groupToRemove)
|
||||
}
|
||||
|
||||
fun addEntryTo(newEntry: Entry, parent: Group?) {
|
||||
open fun addEntryTo(newEntry: Entry, parent: Group?) {
|
||||
// Add entry to parent
|
||||
parent?.addChildEntry(newEntry)
|
||||
newEntry.parent = parent
|
||||
addEntryIndex(newEntry)
|
||||
}
|
||||
|
||||
fun updateEntry(entry: Entry) {
|
||||
open fun updateEntry(entry: Entry) {
|
||||
updateEntryIndex(entry)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,12 +20,15 @@
|
||||
package com.kunzisoft.keepass.database.element.entry
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.ParcelUuid
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.CustomData
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.Tags
|
||||
import com.kunzisoft.keepass.database.element.binary.AttachmentPool
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
@@ -33,6 +36,7 @@ import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.utils.ParcelableUtil
|
||||
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.LinkedHashMap
|
||||
@@ -45,16 +49,20 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
@Transient
|
||||
private var mDecodeRef = false
|
||||
|
||||
var customData = LinkedHashMap<String, String>()
|
||||
override var usageCount = UnsignedLong(0)
|
||||
override var locationChanged = DateInstant()
|
||||
override var customData = CustomData()
|
||||
var fields = LinkedHashMap<String, ProtectedString>()
|
||||
var binaries = LinkedHashMap<String, Int>() // Map<Label, PoolId>
|
||||
var foregroundColor = ""
|
||||
var backgroundColor = ""
|
||||
var overrideURL = ""
|
||||
override var tags = Tags()
|
||||
override var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
|
||||
var qualityCheck = true
|
||||
var autoType = AutoType()
|
||||
var history = ArrayList<EntryKDBX>()
|
||||
var additional = ""
|
||||
var tags = ""
|
||||
|
||||
override var expires: Boolean = false
|
||||
|
||||
@@ -63,17 +71,18 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
constructor(parcel: Parcel) : super(parcel) {
|
||||
usageCount = UnsignedLong(parcel.readLong())
|
||||
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
|
||||
customData = ParcelableUtil.readStringParcelableMap(parcel)
|
||||
customData = parcel.readParcelable(CustomData::class.java.classLoader) ?: CustomData()
|
||||
fields = ParcelableUtil.readStringParcelableMap(parcel, ProtectedString::class.java)
|
||||
binaries = ParcelableUtil.readStringIntMap(parcel)
|
||||
foregroundColor = parcel.readString() ?: foregroundColor
|
||||
backgroundColor = parcel.readString() ?: backgroundColor
|
||||
overrideURL = parcel.readString() ?: overrideURL
|
||||
tags = parcel.readParcelable(Tags::class.java.classLoader) ?: tags
|
||||
previousParentGroup = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
|
||||
autoType = parcel.readParcelable(AutoType::class.java.classLoader) ?: autoType
|
||||
parcel.readTypedList(history, CREATOR)
|
||||
url = parcel.readString() ?: url
|
||||
additional = parcel.readString() ?: additional
|
||||
tags = parcel.readString() ?: tags
|
||||
}
|
||||
|
||||
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
|
||||
@@ -88,17 +97,18 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
super.writeToParcel(dest, flags)
|
||||
dest.writeLong(usageCount.toKotlinLong())
|
||||
dest.writeParcelable(locationChanged, flags)
|
||||
ParcelableUtil.writeStringParcelableMap(dest, customData)
|
||||
dest.writeParcelable(customData, flags)
|
||||
ParcelableUtil.writeStringParcelableMap(dest, flags, fields)
|
||||
ParcelableUtil.writeStringIntMap(dest, binaries)
|
||||
dest.writeString(foregroundColor)
|
||||
dest.writeString(backgroundColor)
|
||||
dest.writeString(overrideURL)
|
||||
dest.writeParcelable(tags, flags)
|
||||
dest.writeParcelable(ParcelUuid(previousParentGroup), flags)
|
||||
dest.writeParcelable(autoType, flags)
|
||||
dest.writeTypedList(history)
|
||||
dest.writeString(url)
|
||||
dest.writeString(additional)
|
||||
dest.writeString(tags)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -109,9 +119,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
super.updateWith(source)
|
||||
usageCount = source.usageCount
|
||||
locationChanged = DateInstant(source.locationChanged)
|
||||
// Add all custom elements in map
|
||||
customData.clear()
|
||||
customData.putAll(source.customData)
|
||||
customData = CustomData(source.customData)
|
||||
fields.clear()
|
||||
fields.putAll(source.fields)
|
||||
binaries.clear()
|
||||
@@ -119,13 +127,14 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
foregroundColor = source.foregroundColor
|
||||
backgroundColor = source.backgroundColor
|
||||
overrideURL = source.overrideURL
|
||||
tags = source.tags
|
||||
previousParentGroup = source.previousParentGroup
|
||||
autoType = AutoType(source.autoType)
|
||||
history.clear()
|
||||
if (copyHistory)
|
||||
history.addAll(source.history)
|
||||
url = source.url
|
||||
additional = source.additional
|
||||
tags = source.tags
|
||||
}
|
||||
|
||||
fun startToManageFieldReferences(database: DatabaseKDBX) {
|
||||
@@ -146,62 +155,78 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
return NodeIdUUID(nodeId.id)
|
||||
}
|
||||
|
||||
override val type: Type
|
||||
get() = Type.ENTRY
|
||||
|
||||
/**
|
||||
* Decode a reference key with the FieldReferencesEngine
|
||||
* @param decodeRef
|
||||
* @param key
|
||||
* @return
|
||||
*/
|
||||
private fun decodeRefKey(decodeRef: Boolean, key: String): String {
|
||||
private fun decodeRefKey(decodeRef: Boolean, key: String, recursionLevel: Int): String {
|
||||
return fields[key]?.toString()?.let { text ->
|
||||
return if (decodeRef) {
|
||||
if (mDatabase == null) text else FieldReferencesEngine().compile(text, this, mDatabase!!)
|
||||
mDatabase?.getFieldReferenceValue(text, recursionLevel) ?: text
|
||||
} else text
|
||||
} ?: ""
|
||||
}
|
||||
|
||||
fun decodeTitleKey(recursionLevel: Int): String {
|
||||
return decodeRefKey(mDecodeRef, STR_TITLE, recursionLevel)
|
||||
}
|
||||
|
||||
override var title: String
|
||||
get() = decodeRefKey(mDecodeRef, STR_TITLE)
|
||||
get() = decodeTitleKey(0)
|
||||
set(value) {
|
||||
val protect = mDatabase != null && mDatabase!!.memoryProtection.protectTitle
|
||||
fields[STR_TITLE] = ProtectedString(protect, value)
|
||||
}
|
||||
|
||||
override val type: Type
|
||||
get() = Type.ENTRY
|
||||
fun decodeUsernameKey(recursionLevel: Int): String {
|
||||
return decodeRefKey(mDecodeRef, STR_USERNAME, recursionLevel)
|
||||
}
|
||||
|
||||
override var username: String
|
||||
get() = decodeRefKey(mDecodeRef, STR_USERNAME)
|
||||
get() = decodeUsernameKey(0)
|
||||
set(value) {
|
||||
val protect = mDatabase != null && mDatabase!!.memoryProtection.protectUserName
|
||||
fields[STR_USERNAME] = ProtectedString(protect, value)
|
||||
}
|
||||
|
||||
fun decodePasswordKey(recursionLevel: Int): String {
|
||||
return decodeRefKey(mDecodeRef, STR_PASSWORD, recursionLevel)
|
||||
}
|
||||
|
||||
override var password: String
|
||||
get() = decodeRefKey(mDecodeRef, STR_PASSWORD)
|
||||
get() = decodePasswordKey(0)
|
||||
set(value) {
|
||||
val protect = mDatabase != null && mDatabase!!.memoryProtection.protectPassword
|
||||
fields[STR_PASSWORD] = ProtectedString(protect, value)
|
||||
}
|
||||
|
||||
fun decodeUrlKey(recursionLevel: Int): String {
|
||||
return decodeRefKey(mDecodeRef, STR_URL, recursionLevel)
|
||||
}
|
||||
|
||||
override var url
|
||||
get() = decodeRefKey(mDecodeRef, STR_URL)
|
||||
get() = decodeUrlKey(0)
|
||||
set(value) {
|
||||
val protect = mDatabase != null && mDatabase!!.memoryProtection.protectUrl
|
||||
fields[STR_URL] = ProtectedString(protect, value)
|
||||
}
|
||||
|
||||
fun decodeNotesKey(recursionLevel: Int): String {
|
||||
return decodeRefKey(mDecodeRef, STR_NOTES, recursionLevel)
|
||||
}
|
||||
|
||||
override var notes: String
|
||||
get() = decodeRefKey(mDecodeRef, STR_NOTES)
|
||||
get() = decodeNotesKey(0)
|
||||
set(value) {
|
||||
val protect = mDatabase != null && mDatabase!!.memoryProtection.protectNotes
|
||||
fields[STR_NOTES] = ProtectedString(protect, value)
|
||||
}
|
||||
|
||||
override var usageCount = UnsignedLong(0)
|
||||
|
||||
override var locationChanged = DateInstant()
|
||||
|
||||
fun getSize(attachmentPool: AttachmentPool): Long {
|
||||
var size = FIXED_LENGTH_SIZE
|
||||
|
||||
@@ -223,7 +248,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
}
|
||||
|
||||
size += overrideURL.length.toLong()
|
||||
size += tags.length.toLong()
|
||||
size += tags.toString().length
|
||||
|
||||
return size
|
||||
}
|
||||
@@ -245,7 +270,7 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
field.clear()
|
||||
for ((key, value) in fields) {
|
||||
if (!isStandardField(key)) {
|
||||
field[key] = ProtectedString(value.isProtected, decodeRefKey(mDecodeRef, key))
|
||||
field[key] = ProtectedString(value.isProtected, decodeRefKey(mDecodeRef, key, 0))
|
||||
}
|
||||
}
|
||||
return field
|
||||
@@ -302,14 +327,6 @@ class EntryKDBX : EntryVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
return size
|
||||
}
|
||||
|
||||
override fun putCustomData(key: String, value: String) {
|
||||
customData[key] = value
|
||||
}
|
||||
|
||||
override fun containsCustomData(): Boolean {
|
||||
return customData.isNotEmpty()
|
||||
}
|
||||
|
||||
fun addEntryToHistory(entry: EntryKDBX) {
|
||||
history.add(entry)
|
||||
}
|
||||
|
||||
@@ -20,267 +20,123 @@
|
||||
package com.kunzisoft.keepass.database.element.entry
|
||||
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||
import com.kunzisoft.keepass.database.search.EntryKDBXSearchHandler
|
||||
import com.kunzisoft.keepass.database.search.SearchParameters
|
||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||
import com.kunzisoft.keepass.utils.UuidUtil
|
||||
import java.util.*
|
||||
|
||||
class FieldReferencesEngine {
|
||||
class FieldReferencesEngine(private val mDatabase: DatabaseKDBX) {
|
||||
|
||||
inner class TargetResult(var entry: EntryKDBX?, var wanted: Char)
|
||||
// Key : <WantedField>@<SearchIn>:<Text>
|
||||
// Value : content
|
||||
private var refsCache: MutableMap<String, String?> = HashMap()
|
||||
|
||||
private inner class SprContextV4 {
|
||||
|
||||
var databaseV4: DatabaseKDBX? = null
|
||||
var entry: EntryKDBX
|
||||
var refsCache: MutableMap<String, String> = HashMap()
|
||||
|
||||
internal constructor(db: DatabaseKDBX, entry: EntryKDBX) {
|
||||
this.databaseV4 = db
|
||||
this.entry = entry
|
||||
}
|
||||
|
||||
internal constructor(source: SprContextV4) {
|
||||
this.databaseV4 = source.databaseV4
|
||||
this.entry = source.entry
|
||||
this.refsCache = source.refsCache
|
||||
}
|
||||
fun clear() {
|
||||
refsCache.clear()
|
||||
}
|
||||
|
||||
fun compile(text: String, entry: EntryKDBX, database: DatabaseKDBX): String {
|
||||
return compileInternal(text, SprContextV4(database, entry), 0)
|
||||
}
|
||||
|
||||
private fun compileInternal(text: String?, sprContextV4: SprContextV4?, recursionLevel: Int): String {
|
||||
if (text == null) {
|
||||
return ""
|
||||
}
|
||||
if (sprContextV4 == null) {
|
||||
return ""
|
||||
}
|
||||
fun compile(textReference: String, recursionLevel: Int): String {
|
||||
return if (recursionLevel >= MAX_RECURSION_DEPTH) {
|
||||
""
|
||||
} else fillRefPlaceholders(text, sprContextV4, recursionLevel)
|
||||
|
||||
} else
|
||||
fillReferencesPlaceholders(textReference, recursionLevel)
|
||||
}
|
||||
|
||||
private fun fillRefPlaceholders(textReference: String, contextV4: SprContextV4, recursionLevel: Int): String {
|
||||
var text = textReference
|
||||
|
||||
if (contextV4.databaseV4 == null) {
|
||||
return text
|
||||
}
|
||||
/**
|
||||
* Manage placeholders with {REF:<WantedField>@<SearchIn>:<Text>}
|
||||
*/
|
||||
private fun fillReferencesPlaceholders(textReference: String, recursionLevel: Int): String {
|
||||
var textValue = textReference
|
||||
|
||||
var offset = 0
|
||||
for (i in 0..19) {
|
||||
text = fillRefsUsingCache(text, contextV4)
|
||||
var numberInlineRef = 0
|
||||
while (textValue.contains(STR_REF_START)
|
||||
&& numberInlineRef <= MAX_INLINE_REF) {
|
||||
numberInlineRef++
|
||||
|
||||
val start = text.indexOf(STR_REF_START, offset, true)
|
||||
textValue = fillReferencesUsingCache(textValue)
|
||||
|
||||
val start = textValue.indexOf(STR_REF_START, offset, true)
|
||||
if (start < 0) {
|
||||
break
|
||||
}
|
||||
val end = text.indexOf(STR_REF_END, start + 1, true)
|
||||
val end = textValue.indexOf(STR_REF_END, offset, true)
|
||||
if (end <= start) {
|
||||
break
|
||||
}
|
||||
|
||||
val fullRef = text.substring(start, end + 1)
|
||||
val result = findRefTarget(fullRef, contextV4)
|
||||
val reference = textValue.substring(start + STR_REF_START.length, end)
|
||||
val fullReference = "$STR_REF_START$reference$STR_REF_END"
|
||||
|
||||
if (result != null) {
|
||||
val found = result.entry
|
||||
found?.stopToManageFieldReferences()
|
||||
val wanted = result.wanted
|
||||
|
||||
var data: String? = null
|
||||
when (wanted) {
|
||||
'T' -> data = found?.title
|
||||
'U' -> data = found?.username
|
||||
'A' -> data = found?.url
|
||||
'P' -> data = found?.password
|
||||
'N' -> data = found?.notes
|
||||
'I' -> data = found?.nodeId.toString()
|
||||
}
|
||||
|
||||
if (data != null && found != null) {
|
||||
val subCtx = SprContextV4(contextV4)
|
||||
subCtx.entry = found
|
||||
|
||||
val innerContent = compileInternal(data, subCtx, recursionLevel + 1)
|
||||
addRefsToCache(fullRef, innerContent, contextV4)
|
||||
text = fillRefsUsingCache(text, contextV4)
|
||||
} else {
|
||||
offset = start + 1
|
||||
if (!refsCache.containsKey(fullReference)) {
|
||||
val result = findReferenceTarget(reference)
|
||||
val entryFound = result.entry
|
||||
val newRecursionLevel = recursionLevel + 1
|
||||
val data: String? = when (result.wanted) {
|
||||
'T' -> entryFound?.decodeTitleKey(newRecursionLevel)
|
||||
'U' -> entryFound?.decodeUsernameKey(newRecursionLevel)
|
||||
'A' -> entryFound?.decodeUrlKey(newRecursionLevel)
|
||||
'P' -> entryFound?.decodePasswordKey(newRecursionLevel)
|
||||
'N' -> entryFound?.decodeNotesKey(newRecursionLevel)
|
||||
'I' -> UuidUtil.toHexString(entryFound?.nodeId?.id)
|
||||
else -> null
|
||||
}
|
||||
refsCache[fullReference] = data
|
||||
textValue = fillReferencesUsingCache(textValue)
|
||||
}
|
||||
|
||||
offset = end
|
||||
}
|
||||
|
||||
return text
|
||||
return textValue
|
||||
}
|
||||
|
||||
private fun findRefTarget(fullReference: String?, contextV4: SprContextV4): TargetResult? {
|
||||
var fullRef: String? = fullReference ?: return null
|
||||
|
||||
fullRef = fullRef!!.toUpperCase(Locale.ENGLISH)
|
||||
if (!fullRef.startsWith(STR_REF_START) || !fullRef.endsWith(STR_REF_END)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val ref = fullRef.substring(STR_REF_START.length, fullRef.length - STR_REF_END.length)
|
||||
if (ref.length <= 4) {
|
||||
return null
|
||||
}
|
||||
if (ref[1] != '@') {
|
||||
return null
|
||||
}
|
||||
if (ref[3] != ':') {
|
||||
return null
|
||||
}
|
||||
|
||||
val scan = Character.toUpperCase(ref[2])
|
||||
val wanted = Character.toUpperCase(ref[0])
|
||||
|
||||
val searchParameters = SearchParameters()
|
||||
searchParameters.setupNone()
|
||||
|
||||
searchParameters.searchString = ref.substring(4)
|
||||
when (scan) {
|
||||
'T' -> searchParameters.searchInTitles = true
|
||||
'U' -> searchParameters.searchInUserNames = true
|
||||
'A' -> searchParameters.searchInUrls = true
|
||||
'P' -> searchParameters.searchInPasswords = true
|
||||
'N' -> searchParameters.searchInNotes = true
|
||||
'I' -> searchParameters.searchInUUIDs = true
|
||||
'O' -> searchParameters.searchInOther = true
|
||||
else -> return null
|
||||
}
|
||||
|
||||
val list = ArrayList<EntryKDBX>()
|
||||
searchEntries(contextV4.databaseV4?.rootGroup, searchParameters, list)
|
||||
|
||||
return if (list.size > 0) {
|
||||
TargetResult(list[0], wanted)
|
||||
} else null
|
||||
|
||||
}
|
||||
|
||||
private fun addRefsToCache(ref: String?, value: String?, ctx: SprContextV4?) {
|
||||
if (ref == null) {
|
||||
return
|
||||
}
|
||||
if (value == null) {
|
||||
return
|
||||
}
|
||||
if (ctx == null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!ctx.refsCache.containsKey(ref)) {
|
||||
ctx.refsCache[ref] = value
|
||||
}
|
||||
}
|
||||
|
||||
private fun fillRefsUsingCache(text: String, sprContextV4: SprContextV4): String {
|
||||
private fun fillReferencesUsingCache(text: String): String {
|
||||
var newText = text
|
||||
for ((key, value) in sprContextV4.refsCache) {
|
||||
newText = text.replace(key, value, true)
|
||||
for ((key, value) in refsCache) {
|
||||
// Replace by key if value not found
|
||||
newText = newText.replace(key, value ?: key, true)
|
||||
}
|
||||
return newText
|
||||
}
|
||||
|
||||
private fun searchEntries(root: GroupKDBX?, searchParameters: SearchParameters?, listStorage: MutableList<EntryKDBX>?) {
|
||||
if (searchParameters == null) {
|
||||
return
|
||||
private fun findReferenceTarget(reference: String): TargetResult {
|
||||
|
||||
val targetResult = TargetResult(null, 'J')
|
||||
|
||||
if (reference.length <= 4) {
|
||||
return targetResult
|
||||
}
|
||||
if (listStorage == null) {
|
||||
return
|
||||
if (reference[1] != '@') {
|
||||
return targetResult
|
||||
}
|
||||
if (reference[3] != ':') {
|
||||
return targetResult
|
||||
}
|
||||
|
||||
val terms = splitStringTerms(searchParameters.searchString)
|
||||
if (terms.size <= 1 || searchParameters.regularExpression) {
|
||||
root!!.doForEachChild(EntryKDBXSearchHandler(searchParameters, listStorage), null)
|
||||
return
|
||||
}
|
||||
|
||||
// Search longest term first
|
||||
val stringLengthComparator = Comparator<String> { lhs, rhs -> lhs.length - rhs.length }
|
||||
Collections.sort(terms, stringLengthComparator)
|
||||
|
||||
val fullSearch = searchParameters.searchString
|
||||
var childEntries: List<EntryKDBX>? = root!!.getChildEntries()
|
||||
for (i in terms.indices) {
|
||||
val pgNew = ArrayList<EntryKDBX>()
|
||||
|
||||
searchParameters.searchString = terms[i]
|
||||
|
||||
var negate = false
|
||||
if (searchParameters.searchString.startsWith("-")) {
|
||||
searchParameters.searchString = searchParameters.searchString.substring(1)
|
||||
negate = searchParameters.searchString.isNotEmpty()
|
||||
}
|
||||
|
||||
if (!root.doForEachChild(EntryKDBXSearchHandler(searchParameters, pgNew), null)) {
|
||||
childEntries = null
|
||||
break
|
||||
}
|
||||
|
||||
childEntries = if (negate) {
|
||||
val complement = ArrayList<EntryKDBX>()
|
||||
for (entry in childEntries!!) {
|
||||
if (!pgNew.contains(entry)) {
|
||||
complement.add(entry)
|
||||
}
|
||||
}
|
||||
complement
|
||||
} else {
|
||||
pgNew
|
||||
}
|
||||
}
|
||||
|
||||
if (childEntries != null) {
|
||||
listStorage.addAll(childEntries)
|
||||
}
|
||||
searchParameters.searchString = fullSearch
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a list of String by split text when ' ', '\t', '\r' or '\n' is found
|
||||
*/
|
||||
private fun splitStringTerms(text: String?): List<String> {
|
||||
val list = ArrayList<String>()
|
||||
if (text == null) {
|
||||
return list
|
||||
}
|
||||
|
||||
val stringBuilder = StringBuilder()
|
||||
var quoted = false
|
||||
|
||||
for (element in text) {
|
||||
|
||||
if ((element == ' ' || element == '\t' || element == '\r' || element == '\n') && !quoted) {
|
||||
|
||||
val len = stringBuilder.length
|
||||
when {
|
||||
len > 0 -> {
|
||||
list.add(stringBuilder.toString())
|
||||
stringBuilder.delete(0, len)
|
||||
}
|
||||
element == '\"' -> quoted = !quoted
|
||||
else -> stringBuilder.append(element)
|
||||
targetResult.wanted = Character.toUpperCase(reference[0])
|
||||
val searchIn = Character.toUpperCase(reference[2])
|
||||
val searchQuery = reference.substring(4)
|
||||
targetResult.entry = when (searchIn) {
|
||||
'T' -> mDatabase.getEntryByTitle(searchQuery)
|
||||
'U' -> mDatabase.getEntryByUsername(searchQuery)
|
||||
'A' -> mDatabase.getEntryByURL(searchQuery)
|
||||
'P' -> mDatabase.getEntryByPassword(searchQuery)
|
||||
'N' -> mDatabase.getEntryByNotes(searchQuery)
|
||||
'I' -> {
|
||||
UuidUtil.fromHexString(searchQuery)?.let { uuid ->
|
||||
mDatabase.getEntryById(NodeIdUUID(uuid))
|
||||
}
|
||||
}
|
||||
'O' -> mDatabase.getEntryByCustomData(searchQuery)
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (stringBuilder.isNotEmpty()) {
|
||||
list.add(stringBuilder.toString())
|
||||
}
|
||||
|
||||
return list
|
||||
return targetResult
|
||||
}
|
||||
|
||||
private data class TargetResult(var entry: EntryKDBX?, var wanted: Char)
|
||||
|
||||
companion object {
|
||||
private const val MAX_RECURSION_DEPTH = 12
|
||||
private const val MAX_RECURSION_DEPTH = 10
|
||||
private const val MAX_INLINE_REF = 10
|
||||
private const val STR_REF_START = "{REF:"
|
||||
private const val STR_REF_END = "}"
|
||||
}
|
||||
|
||||
@@ -31,14 +31,12 @@ import java.util.*
|
||||
|
||||
class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface {
|
||||
|
||||
var level = 0 // short
|
||||
// Used by KeePass internally, don't use
|
||||
var groupFlags = 0
|
||||
|
||||
constructor() : super()
|
||||
|
||||
constructor(parcel: Parcel) : super(parcel) {
|
||||
level = parcel.readInt()
|
||||
groupFlags = parcel.readInt()
|
||||
}
|
||||
|
||||
@@ -52,13 +50,11 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
super.writeToParcel(dest, flags)
|
||||
dest.writeInt(level)
|
||||
dest.writeInt(groupFlags)
|
||||
}
|
||||
|
||||
fun updateWith(source: GroupKDB) {
|
||||
super.updateWith(source)
|
||||
level = source.level
|
||||
groupFlags = source.groupFlags
|
||||
}
|
||||
|
||||
@@ -73,15 +69,12 @@ class GroupKDB : GroupVersioned<Int, UUID, GroupKDB, EntryKDB>, NodeKDBInterface
|
||||
return NodeIdInt(nodeId.id)
|
||||
}
|
||||
|
||||
override fun afterAssignNewParent() {
|
||||
if (parent != null)
|
||||
level = parent!!.level + 1
|
||||
}
|
||||
|
||||
fun setGroupId(groupId: Int) {
|
||||
this.nodeId = NodeIdInt(groupId)
|
||||
}
|
||||
|
||||
override fun afterAssignNewParent() {}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmField
|
||||
|
||||
@@ -20,8 +20,11 @@
|
||||
package com.kunzisoft.keepass.database.element.group
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.ParcelUuid
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.CustomData
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.Tags
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
@@ -33,14 +36,17 @@ import java.util.*
|
||||
|
||||
class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInterface {
|
||||
|
||||
private val customData = HashMap<String, String>()
|
||||
override var usageCount = UnsignedLong(0)
|
||||
override var locationChanged = DateInstant()
|
||||
override var customData = CustomData()
|
||||
var notes = ""
|
||||
|
||||
var isExpanded = true
|
||||
var defaultAutoTypeSequence = ""
|
||||
var enableAutoType: Boolean? = null
|
||||
var enableSearching: Boolean? = null
|
||||
var lastTopVisibleEntry: UUID = DatabaseVersioned.UUID_ZERO
|
||||
override var tags = Tags()
|
||||
override var previousParentGroup: UUID = DatabaseVersioned.UUID_ZERO
|
||||
|
||||
override var expires: Boolean = false
|
||||
|
||||
@@ -60,7 +66,7 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
constructor(parcel: Parcel) : super(parcel) {
|
||||
usageCount = UnsignedLong(parcel.readLong())
|
||||
locationChanged = parcel.readParcelable(DateInstant::class.java.classLoader) ?: locationChanged
|
||||
// TODO customData = ParcelableUtil.readStringParcelableMap(parcel);
|
||||
customData = parcel.readParcelable(CustomData::class.java.classLoader) ?: CustomData()
|
||||
notes = parcel.readString() ?: notes
|
||||
isExpanded = parcel.readByte().toInt() != 0
|
||||
defaultAutoTypeSequence = parcel.readString() ?: defaultAutoTypeSequence
|
||||
@@ -69,6 +75,8 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
val isSearchingEnabled = parcel.readInt()
|
||||
enableSearching = if (isSearchingEnabled == -1) null else isSearchingEnabled == 1
|
||||
lastTopVisibleEntry = parcel.readSerializable() as UUID
|
||||
tags = parcel.readParcelable(Tags::class.java.classLoader) ?: tags
|
||||
previousParentGroup = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
|
||||
}
|
||||
|
||||
override fun readParentParcelable(parcel: Parcel): GroupKDBX? {
|
||||
@@ -83,13 +91,15 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
super.writeToParcel(dest, flags)
|
||||
dest.writeLong(usageCount.toKotlinLong())
|
||||
dest.writeParcelable(locationChanged, flags)
|
||||
// TODO ParcelableUtil.writeStringParcelableMap(dest, customData);
|
||||
dest.writeParcelable(customData, flags)
|
||||
dest.writeString(notes)
|
||||
dest.writeByte((if (isExpanded) 1 else 0).toByte())
|
||||
dest.writeString(defaultAutoTypeSequence)
|
||||
dest.writeInt(if (enableAutoType == null) -1 else if (enableAutoType!!) 1 else 0)
|
||||
dest.writeInt(if (enableSearching == null) -1 else if (enableSearching!!) 1 else 0)
|
||||
dest.writeSerializable(lastTopVisibleEntry)
|
||||
dest.writeParcelable(tags, flags)
|
||||
dest.writeParcelable(ParcelUuid(previousParentGroup), flags)
|
||||
}
|
||||
|
||||
fun updateWith(source: GroupKDBX) {
|
||||
@@ -97,34 +107,21 @@ class GroupKDBX : GroupVersioned<UUID, UUID, GroupKDBX, EntryKDBX>, NodeKDBXInte
|
||||
usageCount = source.usageCount
|
||||
locationChanged = DateInstant(source.locationChanged)
|
||||
// Add all custom elements in map
|
||||
customData.clear()
|
||||
for ((key, value) in source.customData) {
|
||||
customData[key] = value
|
||||
}
|
||||
customData = CustomData(source.customData)
|
||||
notes = source.notes
|
||||
isExpanded = source.isExpanded
|
||||
defaultAutoTypeSequence = source.defaultAutoTypeSequence
|
||||
enableAutoType = source.enableAutoType
|
||||
enableSearching = source.enableSearching
|
||||
lastTopVisibleEntry = source.lastTopVisibleEntry
|
||||
tags = source.tags
|
||||
previousParentGroup = source.previousParentGroup
|
||||
}
|
||||
|
||||
override var usageCount = UnsignedLong(0)
|
||||
|
||||
override var locationChanged = DateInstant()
|
||||
|
||||
override fun afterAssignNewParent() {
|
||||
locationChanged = DateInstant()
|
||||
}
|
||||
|
||||
override fun putCustomData(key: String, value: String) {
|
||||
customData[key] = value
|
||||
}
|
||||
|
||||
override fun containsCustomData(): Boolean {
|
||||
return customData.isNotEmpty()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmField
|
||||
|
||||
@@ -63,6 +63,17 @@ abstract class GroupVersioned
|
||||
get() = titleGroup
|
||||
set(value) { titleGroup = value }
|
||||
|
||||
/**
|
||||
* To determine the level from the root group (root group level is -1)
|
||||
*/
|
||||
fun getLevel(): Int {
|
||||
var level = -1
|
||||
parent?.let { parent ->
|
||||
level = parent.getLevel() + 1
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
override fun getChildGroups(): List<Group> {
|
||||
return childGroups
|
||||
}
|
||||
|
||||
@@ -45,23 +45,64 @@ interface GroupVersionedInterface<Group: GroupVersionedInterface<Group, Entry>,
|
||||
groupHandler.operate(this as Group)
|
||||
}
|
||||
|
||||
fun doForEachChild(entryHandler: NodeHandler<Entry>,
|
||||
fun doForEachChild(entryHandler: NodeHandler<Entry>?,
|
||||
groupHandler: NodeHandler<Group>?,
|
||||
stopIterationWhenGroupHandlerFails: Boolean = true): Boolean {
|
||||
for (entry in this.getChildEntries()) {
|
||||
if (!entryHandler.operate(entry))
|
||||
return false
|
||||
stopIterationWhenGroupHandlerOperateFalse: Boolean = true): Boolean {
|
||||
if (entryHandler != null) {
|
||||
for (entry in this.getChildEntries()) {
|
||||
if (!entryHandler.operate(entry))
|
||||
return false
|
||||
}
|
||||
}
|
||||
for (group in this.getChildGroups()) {
|
||||
var doActionForChild = true
|
||||
if (groupHandler != null && !groupHandler.operate(group)) {
|
||||
doActionForChild = false
|
||||
if (stopIterationWhenGroupHandlerFails)
|
||||
if (stopIterationWhenGroupHandlerOperateFalse)
|
||||
return false
|
||||
}
|
||||
if (doActionForChild)
|
||||
group.doForEachChild(entryHandler, groupHandler)
|
||||
group.doForEachChild(entryHandler, groupHandler, stopIterationWhenGroupHandlerOperateFalse)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun searchChildEntry(criteria: (entry: Entry) -> Boolean): Entry? {
|
||||
return searchChildEntry(this, criteria)
|
||||
}
|
||||
|
||||
private fun searchChildEntry(rootGroup: GroupVersionedInterface<Group, Entry>,
|
||||
criteria: (entry: Entry) -> Boolean): Entry? {
|
||||
for (childEntry in rootGroup.getChildEntries()) {
|
||||
if (criteria.invoke(childEntry)) {
|
||||
return childEntry
|
||||
}
|
||||
}
|
||||
for (group in rootGroup.getChildGroups()) {
|
||||
val searchChildEntry = searchChildEntry(group, criteria)
|
||||
if (searchChildEntry != null) {
|
||||
return searchChildEntry
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun searchChildGroup(criteria: (group: Group) -> Boolean): Group? {
|
||||
return searchChildGroup(this, criteria)
|
||||
}
|
||||
|
||||
private fun searchChildGroup(rootGroup: GroupVersionedInterface<Group, Entry>,
|
||||
criteria: (group: Group) -> Boolean): Group? {
|
||||
for (childGroup in rootGroup.getChildGroups()) {
|
||||
if (criteria.invoke(childGroup)) {
|
||||
return childGroup
|
||||
} else {
|
||||
val subGroup = searchChildGroup(childGroup, criteria)
|
||||
if (subGroup != null) {
|
||||
return subGroup
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,27 +22,38 @@ package com.kunzisoft.keepass.database.element.icon
|
||||
import android.os.Parcel
|
||||
import android.os.ParcelUuid
|
||||
import android.os.Parcelable
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
|
||||
import java.util.*
|
||||
|
||||
class IconImageCustom : IconImageDraw {
|
||||
|
||||
var uuid: UUID
|
||||
val uuid: UUID
|
||||
var name: String = ""
|
||||
var lastModificationTime: DateInstant? = null
|
||||
|
||||
constructor() {
|
||||
uuid = DatabaseVersioned.UUID_ZERO
|
||||
constructor(name: String = "", lastModificationTime: DateInstant? = null) {
|
||||
this.uuid = DatabaseVersioned.UUID_ZERO
|
||||
this.name = name
|
||||
this.lastModificationTime = lastModificationTime
|
||||
}
|
||||
|
||||
constructor(uuid: UUID) {
|
||||
constructor(uuid: UUID, name: String = "", lastModificationTime: DateInstant? = null) {
|
||||
this.uuid = uuid
|
||||
this.name = name
|
||||
this.lastModificationTime = lastModificationTime
|
||||
}
|
||||
|
||||
constructor(parcel: Parcel) {
|
||||
uuid = parcel.readParcelable<ParcelUuid>(ParcelUuid::class.java.classLoader)?.uuid ?: DatabaseVersioned.UUID_ZERO
|
||||
name = parcel.readString() ?: name
|
||||
lastModificationTime = parcel.readParcelable(DateInstant::class.java.classLoader)
|
||||
}
|
||||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeParcelable(ParcelUuid(uuid), flags)
|
||||
dest.writeString(name)
|
||||
dest.writeParcelable(lastModificationTime, flags)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
package com.kunzisoft.keepass.database.element.icon
|
||||
|
||||
import android.util.Log
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.binary.BinaryCache
|
||||
import com.kunzisoft.keepass.database.element.binary.BinaryData
|
||||
import com.kunzisoft.keepass.database.element.binary.CustomIconPool
|
||||
@@ -27,7 +28,7 @@ import com.kunzisoft.keepass.database.element.icon.IconImageStandard.Companion.K
|
||||
import com.kunzisoft.keepass.icons.IconPack.Companion.NB_ICONS
|
||||
import java.util.*
|
||||
|
||||
class IconsManager(private val binaryCache: BinaryCache) {
|
||||
class IconsManager(binaryCache: BinaryCache) {
|
||||
|
||||
private val standardCache = List(NB_ICONS) {
|
||||
IconImageStandard(it)
|
||||
@@ -52,17 +53,15 @@ class IconsManager(private val binaryCache: BinaryCache) {
|
||||
fun buildNewCustomIcon(key: UUID? = null,
|
||||
result: (IconImageCustom, BinaryData?) -> Unit) {
|
||||
// Create a binary file for a brand new custom icon
|
||||
addCustomIcon(key, false, result)
|
||||
addCustomIcon(key, "", null, false, result)
|
||||
}
|
||||
|
||||
fun addCustomIcon(key: UUID? = null,
|
||||
name: String,
|
||||
lastModificationTime: DateInstant?,
|
||||
smallSize: Boolean,
|
||||
result: (IconImageCustom, BinaryData?) -> Unit) {
|
||||
val keyBinary = customCache.put(key) { uniqueBinaryId ->
|
||||
// Create a byte array for better performance with small data
|
||||
binaryCache.getBinaryData(uniqueBinaryId, smallSize)
|
||||
}
|
||||
result.invoke(IconImageCustom(keyBinary.keys.first()), keyBinary.binary)
|
||||
customCache.put(key, name, lastModificationTime, smallSize, result)
|
||||
}
|
||||
|
||||
fun getIcon(iconUuid: UUID): IconImageCustom {
|
||||
@@ -88,8 +87,12 @@ class IconsManager(private val binaryCache: BinaryCache) {
|
||||
}
|
||||
|
||||
fun doForEachCustomIcon(action: (IconImageCustom, BinaryData) -> Unit) {
|
||||
customCache.doForEachBinary { key, binary ->
|
||||
action.invoke(IconImageCustom(key), binary)
|
||||
customCache.doForEachCustomIcon(action)
|
||||
}
|
||||
|
||||
fun containsCustomIconWithNameOrLastModificationTime(): Boolean {
|
||||
return customCache.any { customIcon ->
|
||||
customIcon.name.isNotEmpty() || customIcon.lastModificationTime != null
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -19,17 +19,17 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.element.node
|
||||
|
||||
import com.kunzisoft.keepass.database.element.CustomData
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.Tags
|
||||
import com.kunzisoft.keepass.utils.UnsignedLong
|
||||
import java.util.*
|
||||
|
||||
interface NodeKDBXInterface : NodeTimeInterface {
|
||||
|
||||
var usageCount: UnsignedLong
|
||||
|
||||
var locationChanged: DateInstant
|
||||
|
||||
fun putCustomData(key: String, value: String)
|
||||
|
||||
fun containsCustomData(): Boolean
|
||||
|
||||
var customData: CustomData
|
||||
var tags: Tags
|
||||
var previousParentGroup: UUID
|
||||
}
|
||||
|
||||
@@ -25,15 +25,7 @@ import com.kunzisoft.keepass.database.element.icon.IconImage
|
||||
interface NodeVersionedInterface<ParentGroup> : NodeTimeInterface, Parcelable {
|
||||
|
||||
var title: String
|
||||
|
||||
/**
|
||||
* @return Visual icon
|
||||
*/
|
||||
var icon: IconImage
|
||||
|
||||
/**
|
||||
* @return Type of Node
|
||||
*/
|
||||
val type: Type
|
||||
|
||||
/**
|
||||
|
||||
@@ -114,7 +114,7 @@ class NoMemoryDatabaseException: LoadDatabaseException {
|
||||
constructor(exception: Throwable) : super(exception)
|
||||
}
|
||||
|
||||
class EntryDatabaseException: LoadDatabaseException {
|
||||
class MoveEntryDatabaseException: LoadDatabaseException {
|
||||
@StringRes
|
||||
override var errorId: Int = R.string.error_move_entry_here
|
||||
constructor() : super()
|
||||
@@ -123,7 +123,7 @@ class EntryDatabaseException: LoadDatabaseException {
|
||||
|
||||
class MoveGroupDatabaseException: LoadDatabaseException {
|
||||
@StringRes
|
||||
override var errorId: Int = R.string.error_move_folder_in_itself
|
||||
override var errorId: Int = R.string.error_move_group_here
|
||||
constructor() : super()
|
||||
constructor(exception: Throwable) : super(exception)
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.file
|
||||
|
||||
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
|
||||
import com.kunzisoft.encrypt.HashManager
|
||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
|
||||
import com.kunzisoft.keepass.database.crypto.VariantDictionary
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.AesKdf
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
|
||||
@@ -91,41 +91,64 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
||||
this.masterSeed = ByteArray(32)
|
||||
}
|
||||
|
||||
private inner class NodeHasCustomData<T: NodeKDBXInterface> : NodeHandler<T>() {
|
||||
|
||||
internal var containsCustomData = false
|
||||
|
||||
private open class NodeOperationHandler<T: NodeKDBXInterface> : NodeHandler<T>() {
|
||||
var containsCustomData = false
|
||||
override fun operate(node: T): Boolean {
|
||||
if (node.containsCustomData()) {
|
||||
if (node.customData.isNotEmpty()) {
|
||||
containsCustomData = true
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMinKdbxVersion(databaseV4: DatabaseKDBX): UnsignedInt {
|
||||
private inner class EntryOperationHandler: NodeOperationHandler<EntryKDBX>() {
|
||||
var passwordQualityEstimationDisabled = false
|
||||
override fun operate(node: EntryKDBX): Boolean {
|
||||
if (!node.qualityCheck) {
|
||||
passwordQualityEstimationDisabled = true
|
||||
}
|
||||
return super.operate(node)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class GroupOperationHandler: NodeOperationHandler<GroupKDBX>() {
|
||||
var containsTags = false
|
||||
override fun operate(node: GroupKDBX): Boolean {
|
||||
if (!node.tags.isEmpty())
|
||||
containsTags = true
|
||||
return super.operate(node)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMinKdbxVersion(databaseKDBX: DatabaseKDBX): UnsignedInt {
|
||||
val entryHandler = EntryOperationHandler()
|
||||
val groupHandler = GroupOperationHandler()
|
||||
databaseKDBX.rootGroup?.doForEachChildAndForIt(entryHandler, groupHandler)
|
||||
|
||||
// https://keepass.info/help/kb/kdbx_4.1.html
|
||||
val containsGroupWithTag = groupHandler.containsTags
|
||||
val containsEntryWithPasswordQualityEstimationDisabled = entryHandler.passwordQualityEstimationDisabled
|
||||
val containsCustomIconWithNameOrLastModificationTime = databaseKDBX.iconsManager.containsCustomIconWithNameOrLastModificationTime()
|
||||
val containsHeaderCustomDataWithLastModificationTime = databaseKDBX.customData.containsItemWithLastModificationTime()
|
||||
|
||||
// https://keepass.info/help/kb/kdbx_4.html
|
||||
// If AES is not use, it's at least 4.0
|
||||
val kdfIsNotAes = databaseKDBX.kdfParameters?.uuid != AesKdf.CIPHER_UUID
|
||||
val containsHeaderCustomData = databaseKDBX.customData.isNotEmpty()
|
||||
val containsNodeCustomData = entryHandler.containsCustomData || groupHandler.containsCustomData
|
||||
|
||||
// Return v4 if AES is not use
|
||||
if (databaseV4.kdfParameters != null
|
||||
&& databaseV4.kdfParameters!!.uuid != AesKdf.CIPHER_UUID) {
|
||||
return FILE_VERSION_32_4
|
||||
}
|
||||
|
||||
if (databaseV4.rootGroup == null) {
|
||||
return FILE_VERSION_32_3
|
||||
}
|
||||
|
||||
val entryHandler = NodeHasCustomData<EntryKDBX>()
|
||||
val groupHandler = NodeHasCustomData<GroupKDBX>()
|
||||
databaseV4.rootGroup?.doForEachChildAndForIt(entryHandler, groupHandler)
|
||||
return if (databaseV4.containsCustomData()
|
||||
|| entryHandler.containsCustomData
|
||||
|| groupHandler.containsCustomData) {
|
||||
FILE_VERSION_32_4
|
||||
// Check each condition to determine version
|
||||
return if (containsGroupWithTag
|
||||
|| containsEntryWithPasswordQualityEstimationDisabled
|
||||
|| containsCustomIconWithNameOrLastModificationTime
|
||||
|| containsHeaderCustomDataWithLastModificationTime) {
|
||||
FILE_VERSION_41
|
||||
} else if (kdfIsNotAes
|
||||
|| containsHeaderCustomData
|
||||
|| containsNodeCustomData) {
|
||||
FILE_VERSION_40
|
||||
} else {
|
||||
FILE_VERSION_32_3
|
||||
FILE_VERSION_31
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +190,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
||||
private fun readHeaderField(dis: InputStream): Boolean {
|
||||
val fieldID = dis.read().toByte()
|
||||
|
||||
val fieldSize: Int = if (version.isBefore(FILE_VERSION_32_4)) {
|
||||
val fieldSize: Int = if (version.isBefore(FILE_VERSION_40)) {
|
||||
dis.readBytes2ToUShort()
|
||||
} else {
|
||||
dis.readBytes4ToUInt().toKotlinInt()
|
||||
@@ -194,20 +217,20 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
||||
|
||||
PwDbHeaderV4Fields.MasterSeed -> masterSeed = fieldData
|
||||
|
||||
PwDbHeaderV4Fields.TransformSeed -> if (version.isBefore(FILE_VERSION_32_4))
|
||||
PwDbHeaderV4Fields.TransformSeed -> if (version.isBefore(FILE_VERSION_40))
|
||||
transformSeed = fieldData
|
||||
|
||||
PwDbHeaderV4Fields.TransformRounds -> if (version.isBefore(FILE_VERSION_32_4))
|
||||
PwDbHeaderV4Fields.TransformRounds -> if (version.isBefore(FILE_VERSION_40))
|
||||
setTransformRound(fieldData)
|
||||
|
||||
PwDbHeaderV4Fields.EncryptionIV -> encryptionIV = fieldData
|
||||
|
||||
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.isBefore(FILE_VERSION_32_4))
|
||||
PwDbHeaderV4Fields.InnerRandomstreamKey -> if (version.isBefore(FILE_VERSION_40))
|
||||
innerRandomStreamKey = fieldData
|
||||
|
||||
PwDbHeaderV4Fields.StreamStartBytes -> streamStartBytes = fieldData
|
||||
|
||||
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.isBefore(FILE_VERSION_32_4))
|
||||
PwDbHeaderV4Fields.InnerRandomStreamID -> if (version.isBefore(FILE_VERSION_40))
|
||||
setRandomStreamID(fieldData)
|
||||
|
||||
PwDbHeaderV4Fields.KdfParameters -> databaseV4.kdfParameters = KdfParameters.deserialize(fieldData)
|
||||
@@ -283,7 +306,7 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
||||
*/
|
||||
private fun validVersion(version: UnsignedInt): Boolean {
|
||||
return version.toKotlinInt() and FILE_VERSION_CRITICAL_MASK.toKotlinInt() <=
|
||||
FILE_VERSION_32_4.toKotlinInt() and FILE_VERSION_CRITICAL_MASK.toKotlinInt()
|
||||
FILE_VERSION_40.toKotlinInt() and FILE_VERSION_CRITICAL_MASK.toKotlinInt()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@@ -292,8 +315,9 @@ class DatabaseHeaderKDBX(private val databaseV4: DatabaseKDBX) : DatabaseHeader(
|
||||
val DBSIG_2 = UnsignedInt(-0x4ab40499)
|
||||
|
||||
private val FILE_VERSION_CRITICAL_MASK = UnsignedInt(-0x10000)
|
||||
val FILE_VERSION_32_3 = UnsignedInt(0x00030001)
|
||||
val FILE_VERSION_32_4 = UnsignedInt(0x00040000)
|
||||
val FILE_VERSION_31 = UnsignedInt(0x00030001)
|
||||
val FILE_VERSION_40 = UnsignedInt(0x00040000)
|
||||
val FILE_VERSION_41 = UnsignedInt(0x00040001)
|
||||
|
||||
fun getCompressionFromFlag(flag: UnsignedInt): CompressionAlgorithm? {
|
||||
return when (flag.toKotlinInt()) {
|
||||
|
||||
@@ -79,8 +79,10 @@ object DatabaseKDBXXML {
|
||||
const val ElemFgColor = "ForegroundColor"
|
||||
const val ElemBgColor = "BackgroundColor"
|
||||
const val ElemOverrideUrl = "OverrideURL"
|
||||
const val ElemQualityCheck = "QualityCheck"
|
||||
const val ElemTimes = "Times"
|
||||
const val ElemTags = "Tags"
|
||||
const val ElemPreviousParentGroup = "PreviousParentGroup"
|
||||
|
||||
const val ElemCreationTime = "CreationTime"
|
||||
const val ElemLastModTime = "LastModificationTime"
|
||||
|
||||
@@ -40,6 +40,7 @@ import java.security.MessageDigest
|
||||
import java.util.*
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.CipherInputStream
|
||||
import kotlin.collections.HashMap
|
||||
|
||||
|
||||
/**
|
||||
@@ -154,11 +155,10 @@ class DatabaseInputKDB(cacheDirectory: File,
|
||||
|
||||
// New manual root because KDB contains multiple root groups (here available with getRootGroups())
|
||||
val newRoot = mDatabase.createGroup()
|
||||
newRoot.level = -1
|
||||
mDatabase.rootGroup = newRoot
|
||||
mDatabase.addGroupIndex(newRoot)
|
||||
|
||||
// Import all nodes
|
||||
val groupLevelList = HashMap<GroupKDB, Int>()
|
||||
var newGroup: GroupKDB? = null
|
||||
var newEntry: EntryKDB? = null
|
||||
var currentGroupNumber = 0
|
||||
@@ -248,7 +248,7 @@ class DatabaseInputKDB(cacheDirectory: File,
|
||||
}
|
||||
0x0008 -> {
|
||||
newGroup?.let { group ->
|
||||
group.level = cipherInputStream.readBytes2ToUShort()
|
||||
groupLevelList.put(group, cipherInputStream.readBytes2ToUShort())
|
||||
} ?:
|
||||
newEntry?.let { entry ->
|
||||
entry.notes = cipherInputStream.readBytesToString(fieldSize)
|
||||
@@ -318,7 +318,7 @@ class DatabaseInputKDB(cacheDirectory: File,
|
||||
if (!Arrays.equals(messageDigest.digest(), header.contentsHash)) {
|
||||
throw InvalidCredentialsDatabaseException()
|
||||
}
|
||||
constructTreeFromIndex()
|
||||
constructTreeFromIndex(groupLevelList)
|
||||
|
||||
stopContentTimer()
|
||||
|
||||
@@ -339,34 +339,40 @@ class DatabaseInputKDB(cacheDirectory: File,
|
||||
return mDatabase
|
||||
}
|
||||
|
||||
private fun buildTreeGroups(previousGroup: GroupKDB, currentGroup: GroupKDB, groupIterator: Iterator<GroupKDB>) {
|
||||
private fun buildTreeGroups(groupLevelList: HashMap<GroupKDB, Int>,
|
||||
previousGroup: GroupKDB,
|
||||
currentGroup: GroupKDB,
|
||||
groupIterator: Iterator<GroupKDB>) {
|
||||
|
||||
if (currentGroup.parent == null && (previousGroup.level + 1) == currentGroup.level) {
|
||||
val previousGroupLevel = groupLevelList[previousGroup] ?: -1
|
||||
val currentGroupLevel = groupLevelList[currentGroup] ?: -1
|
||||
|
||||
if (currentGroup.parent == null && (previousGroupLevel + 1) == currentGroupLevel) {
|
||||
// Current group has an increment level compare to the previous, current group is a child
|
||||
previousGroup.addChildGroup(currentGroup)
|
||||
currentGroup.parent = previousGroup
|
||||
} else if (previousGroup.parent != null && previousGroup.level == currentGroup.level) {
|
||||
} else if (previousGroup.parent != null && previousGroupLevel == currentGroupLevel) {
|
||||
// In the same level, previous parent is the same as previous group
|
||||
previousGroup.parent!!.addChildGroup(currentGroup)
|
||||
currentGroup.parent = previousGroup.parent
|
||||
} else if (previousGroup.parent != null) {
|
||||
// Previous group has a higher level than the current group, check it's parent
|
||||
buildTreeGroups(previousGroup.parent!!, currentGroup, groupIterator)
|
||||
buildTreeGroups(groupLevelList, previousGroup.parent!!, currentGroup, groupIterator)
|
||||
}
|
||||
|
||||
// Next current group
|
||||
if (groupIterator.hasNext()){
|
||||
buildTreeGroups(currentGroup, groupIterator.next(), groupIterator)
|
||||
buildTreeGroups(groupLevelList, currentGroup, groupIterator.next(), groupIterator)
|
||||
}
|
||||
}
|
||||
|
||||
private fun constructTreeFromIndex() {
|
||||
mDatabase.rootGroup?.let {
|
||||
private fun constructTreeFromIndex(groupLevelList: HashMap<GroupKDB, Int>) {
|
||||
mDatabase.rootGroup?.let { root ->
|
||||
|
||||
// add each group
|
||||
val groupIterator = mDatabase.getGroupIndexes().iterator()
|
||||
if (groupIterator.hasNext())
|
||||
buildTreeGroups(it, groupIterator.next(), groupIterator)
|
||||
buildTreeGroups(groupLevelList, root, groupIterator.next(), groupIterator)
|
||||
|
||||
// add each child
|
||||
for (currentEntry in mDatabase.getEntryIndexes()) {
|
||||
|
||||
@@ -26,9 +26,7 @@ import com.kunzisoft.keepass.database.crypto.CipherEngine
|
||||
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
|
||||
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.crypto.HmacBlock
|
||||
import com.kunzisoft.keepass.database.element.Attachment
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.DeletedObject
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.binary.BinaryData
|
||||
import com.kunzisoft.keepass.database.element.binary.LoadedKey
|
||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||
@@ -42,7 +40,7 @@ import com.kunzisoft.keepass.database.element.node.NodeKDBXInterface
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.database.exception.*
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
|
||||
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
|
||||
import com.kunzisoft.keepass.database.file.DateKDBXUtil
|
||||
import com.kunzisoft.keepass.stream.HashedBlockInputStream
|
||||
@@ -88,9 +86,12 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
private var ctxHistoryBase: EntryKDBX? = null
|
||||
private var ctxDeletedObject: DeletedObject? = null
|
||||
private var customIconID = DatabaseVersioned.UUID_ZERO
|
||||
private var customIconName: String = ""
|
||||
private var customIconLastModificationTime: DateInstant? = null
|
||||
private var customIconData: ByteArray? = null
|
||||
private var customDataKey: String? = null
|
||||
private var customDataValue: String? = null
|
||||
private var customDataLastModificationTime: DateInstant? = null
|
||||
private var groupCustomDataKey: String? = null
|
||||
private var groupCustomDataValue: String? = null
|
||||
private var entryCustomDataKey: String? = null
|
||||
@@ -161,7 +162,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
}
|
||||
|
||||
val plainInputStream: InputStream
|
||||
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
|
||||
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_40)) {
|
||||
|
||||
val dataDecrypted = CipherInputStream(databaseInputStream, cipher)
|
||||
val storedStartBytes: ByteArray?
|
||||
@@ -210,7 +211,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
else -> plainInputStream
|
||||
}
|
||||
|
||||
if (!mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
|
||||
if (!mDatabase.kdbxVersion.isBefore(FILE_VERSION_40)) {
|
||||
readInnerHeader(inputStreamXml, header)
|
||||
}
|
||||
|
||||
@@ -386,25 +387,25 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
}
|
||||
}
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemSettingsChanged, ignoreCase = true)) {
|
||||
mDatabase.settingsChanged = readPwTime(xpp)
|
||||
mDatabase.settingsChanged = readDateInstant(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDbName, ignoreCase = true)) {
|
||||
mDatabase.name = readString(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDbNameChanged, ignoreCase = true)) {
|
||||
mDatabase.nameChanged = readPwTime(xpp)
|
||||
mDatabase.nameChanged = readDateInstant(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDbDesc, ignoreCase = true)) {
|
||||
mDatabase.description = readString(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDbDescChanged, ignoreCase = true)) {
|
||||
mDatabase.descriptionChanged = readPwTime(xpp)
|
||||
mDatabase.descriptionChanged = readDateInstant(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDbDefaultUser, ignoreCase = true)) {
|
||||
mDatabase.defaultUserName = readString(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDbDefaultUserChanged, ignoreCase = true)) {
|
||||
mDatabase.defaultUserNameChanged = readPwTime(xpp)
|
||||
mDatabase.defaultUserNameChanged = readDateInstant(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDbColor, ignoreCase = true)) {
|
||||
mDatabase.color = readString(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDbMntncHistoryDays, ignoreCase = true)) {
|
||||
mDatabase.maintenanceHistoryDays = readUInt(xpp, DEFAULT_HISTORY_DAYS)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDbKeyChanged, ignoreCase = true)) {
|
||||
mDatabase.keyLastChanged = readPwTime(xpp)
|
||||
mDatabase.keyLastChanged = readDateInstant(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDbKeyChangeRec, ignoreCase = true)) {
|
||||
mDatabase.keyChangeRecDays = readLong(xpp, -1)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDbKeyChangeForce, ignoreCase = true)) {
|
||||
@@ -420,11 +421,11 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemRecycleBinUuid, ignoreCase = true)) {
|
||||
mDatabase.recycleBinUUID = readUuid(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemRecycleBinChanged, ignoreCase = true)) {
|
||||
mDatabase.recycleBinChanged = readTime(xpp)
|
||||
mDatabase.recycleBinChanged = readDateInstant(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemEntryTemplatesGroup, ignoreCase = true)) {
|
||||
mDatabase.entryTemplatesGroup = readUuid(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemEntryTemplatesGroupChanged, ignoreCase = true)) {
|
||||
mDatabase.entryTemplatesGroupChanged = readPwTime(xpp)
|
||||
mDatabase.entryTemplatesGroupChanged = readDateInstant(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemHistoryMaxItems, ignoreCase = true)) {
|
||||
mDatabase.historyMaxItems = readInt(xpp, -1)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemHistoryMaxSize, ignoreCase = true)) {
|
||||
@@ -468,6 +469,10 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
if (strData.isNotEmpty()) {
|
||||
customIconData = Base64.decode(strData, BASE_64_FLAG)
|
||||
}
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemName, ignoreCase = true)) {
|
||||
customIconName = readString(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemLastModTime, ignoreCase = true)) {
|
||||
customIconLastModificationTime = readDateInstant(xpp)
|
||||
} else {
|
||||
readUnknown(xpp)
|
||||
}
|
||||
@@ -488,6 +493,8 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
customDataKey = readString(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemValue, ignoreCase = true)) {
|
||||
customDataValue = readString(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemLastModTime, ignoreCase = true)) {
|
||||
customDataLastModificationTime = readDateInstant(xpp)
|
||||
} else {
|
||||
readUnknown(xpp)
|
||||
}
|
||||
@@ -518,6 +525,10 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
ctxGroup?.icon?.standard = mDatabase.getStandardIcon(readUInt(xpp, UnsignedInt(0)).toKotlinInt())
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemCustomIconID, ignoreCase = true)) {
|
||||
ctxGroup?.icon?.custom = mDatabase.getCustomIcon(readUuid(xpp))
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemTags, ignoreCase = true)) {
|
||||
ctxGroup?.tags = readTags(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemPreviousParentGroup, ignoreCase = true)) {
|
||||
ctxGroup?.previousParentGroup = readUuid(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) {
|
||||
return switchContext(ctx, KdbContext.GroupTimes, xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemIsExpanded, ignoreCase = true)) {
|
||||
@@ -562,6 +573,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
KdbContext.GroupCustomDataItem -> when {
|
||||
name.equals(DatabaseKDBXXML.ElemKey, ignoreCase = true) -> groupCustomDataKey = readString(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemValue, ignoreCase = true) -> groupCustomDataValue = readString(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemLastModTime, ignoreCase = true) -> readDateInstant(xpp) // Ignore
|
||||
else -> readUnknown(xpp)
|
||||
}
|
||||
|
||||
@@ -578,8 +590,12 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
ctxEntry?.backgroundColor = readString(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemOverrideUrl, ignoreCase = true)) {
|
||||
ctxEntry?.overrideURL = readString(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemQualityCheck, ignoreCase = true)) {
|
||||
ctxEntry?.qualityCheck = readBool(xpp, true)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemTags, ignoreCase = true)) {
|
||||
ctxEntry?.tags = readString(xpp)
|
||||
ctxEntry?.tags = readTags(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemPreviousParentGroup, ignoreCase = true)) {
|
||||
ctxEntry?.previousParentGroup = readUuid(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemTimes, ignoreCase = true)) {
|
||||
return switchContext(ctx, KdbContext.EntryTimes, xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemString, ignoreCase = true)) {
|
||||
@@ -608,6 +624,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
KdbContext.EntryCustomDataItem -> when {
|
||||
name.equals(DatabaseKDBXXML.ElemKey, ignoreCase = true) -> entryCustomDataKey = readString(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemValue, ignoreCase = true) -> entryCustomDataValue = readString(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemLastModTime, ignoreCase = true) -> readDateInstant(xpp) // Ignore
|
||||
else -> readUnknown(xpp)
|
||||
}
|
||||
|
||||
@@ -620,13 +637,13 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
}
|
||||
|
||||
when {
|
||||
name.equals(DatabaseKDBXXML.ElemLastModTime, ignoreCase = true) -> tl?.lastModificationTime = readPwTime(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemCreationTime, ignoreCase = true) -> tl?.creationTime = readPwTime(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemLastAccessTime, ignoreCase = true) -> tl?.lastAccessTime = readPwTime(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemExpiryTime, ignoreCase = true) -> tl?.expiryTime = readPwTime(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemLastModTime, ignoreCase = true) -> tl?.lastModificationTime = readDateInstant(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemCreationTime, ignoreCase = true) -> tl?.creationTime = readDateInstant(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemLastAccessTime, ignoreCase = true) -> tl?.lastAccessTime = readDateInstant(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemExpiryTime, ignoreCase = true) -> tl?.expiryTime = readDateInstant(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemExpires, ignoreCase = true) -> tl?.expires = readBool(xpp, false)
|
||||
name.equals(DatabaseKDBXXML.ElemUsageCount, ignoreCase = true) -> tl?.usageCount = readULong(xpp, UnsignedLong(0))
|
||||
name.equals(DatabaseKDBXXML.ElemLocationChanged, ignoreCase = true) -> tl?.locationChanged = readPwTime(xpp)
|
||||
name.equals(DatabaseKDBXXML.ElemLocationChanged, ignoreCase = true) -> tl?.locationChanged = readDateInstant(xpp)
|
||||
else -> readUnknown(xpp)
|
||||
}
|
||||
}
|
||||
@@ -687,7 +704,7 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
KdbContext.DeletedObject -> if (name.equals(DatabaseKDBXXML.ElemUuid, ignoreCase = true)) {
|
||||
ctxDeletedObject?.uuid = readUuid(xpp)
|
||||
} else if (name.equals(DatabaseKDBXXML.ElemDeletionTime, ignoreCase = true)) {
|
||||
ctxDeletedObject?.setDeletionTime(readTime(xpp))
|
||||
ctxDeletedObject?.setDeletionTime(readDateInstant(xpp))
|
||||
} else {
|
||||
readUnknown(xpp)
|
||||
}
|
||||
@@ -714,29 +731,34 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
} else if (ctx == KdbContext.CustomIcon && name.equals(DatabaseKDBXXML.ElemCustomIconItem, ignoreCase = true)) {
|
||||
val iconData = customIconData
|
||||
if (customIconID != DatabaseVersioned.UUID_ZERO && iconData != null) {
|
||||
mDatabase.addCustomIcon(customIconID, isRAMSufficient.invoke(iconData.size.toLong())) { _, binary ->
|
||||
mDatabase.addCustomIcon(customIconID,
|
||||
customIconName,
|
||||
customIconLastModificationTime,
|
||||
isRAMSufficient.invoke(iconData.size.toLong())) { _, binary ->
|
||||
binary?.getOutputDataStream(mDatabase.binaryCache)?.use { outputStream ->
|
||||
outputStream.write(iconData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
customIconID = DatabaseVersioned.UUID_ZERO
|
||||
customIconName = ""
|
||||
customIconLastModificationTime = null
|
||||
customIconData = null
|
||||
|
||||
return KdbContext.CustomIcons
|
||||
} else if (ctx == KdbContext.Binaries && name.equals(DatabaseKDBXXML.ElemBinaries, ignoreCase = true)) {
|
||||
return KdbContext.Meta
|
||||
} else if (ctx == KdbContext.CustomData && name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) {
|
||||
return KdbContext.Meta
|
||||
} else if (ctx == KdbContext.CustomDataItem && name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) {
|
||||
if (customDataKey != null && customDataValue != null) {
|
||||
mDatabase.putCustomData(customDataKey!!, customDataValue!!)
|
||||
customDataKey?.let { dataKey ->
|
||||
customDataValue?.let { dataValue ->
|
||||
mDatabase.customData.put(CustomDataItem(dataKey,
|
||||
dataValue, customDataLastModificationTime))
|
||||
}
|
||||
}
|
||||
|
||||
customDataKey = null
|
||||
customDataValue = null
|
||||
|
||||
customDataLastModificationTime = null
|
||||
return KdbContext.CustomData
|
||||
} else if (ctx == KdbContext.Group && name.equals(DatabaseKDBXXML.ElemGroup, ignoreCase = true)) {
|
||||
if (ctxGroup != null && ctxGroup?.id == DatabaseVersioned.UUID_ZERO) {
|
||||
@@ -758,13 +780,13 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
} else if (ctx == KdbContext.GroupCustomData && name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) {
|
||||
return KdbContext.Group
|
||||
} else if (ctx == KdbContext.GroupCustomDataItem && name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) {
|
||||
if (groupCustomDataKey != null && groupCustomDataValue != null) {
|
||||
ctxGroup?.putCustomData(groupCustomDataKey!!, groupCustomDataValue!!)
|
||||
groupCustomDataKey?.let { customDataKey ->
|
||||
groupCustomDataValue?.let { customDataValue ->
|
||||
ctxGroup?.customData?.put(CustomDataItem(customDataKey, customDataValue))
|
||||
}
|
||||
}
|
||||
|
||||
groupCustomDataKey = null
|
||||
groupCustomDataValue = null
|
||||
|
||||
return KdbContext.GroupCustomData
|
||||
|
||||
} else if (ctx == KdbContext.Entry && name.equals(DatabaseKDBXXML.ElemEntry, ignoreCase = true)) {
|
||||
@@ -810,13 +832,13 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
} else if (ctx == KdbContext.EntryCustomData && name.equals(DatabaseKDBXXML.ElemCustomData, ignoreCase = true)) {
|
||||
return KdbContext.Entry
|
||||
} else if (ctx == KdbContext.EntryCustomDataItem && name.equals(DatabaseKDBXXML.ElemStringDictExItem, ignoreCase = true)) {
|
||||
if (entryCustomDataKey != null && entryCustomDataValue != null) {
|
||||
ctxEntry?.putCustomData(entryCustomDataKey!!, entryCustomDataValue!!)
|
||||
entryCustomDataKey?.let { customDataKey ->
|
||||
entryCustomDataValue?.let { customDataValue ->
|
||||
ctxEntry?.customData?.put(CustomDataItem(customDataKey, customDataValue))
|
||||
}
|
||||
}
|
||||
|
||||
entryCustomDataKey = null
|
||||
entryCustomDataValue = null
|
||||
|
||||
return KdbContext.EntryCustomData
|
||||
} else if (ctx == KdbContext.EntryHistory && name.equals(DatabaseKDBXXML.ElemHistory, ignoreCase = true)) {
|
||||
entryInHistory = false
|
||||
@@ -836,16 +858,11 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
}
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun readPwTime(xpp: XmlPullParser): DateInstant {
|
||||
return DateInstant(readTime(xpp))
|
||||
}
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun readTime(xpp: XmlPullParser): Date {
|
||||
private fun readDateInstant(xpp: XmlPullParser): DateInstant {
|
||||
val sDate = readString(xpp)
|
||||
var utcDate: Date? = null
|
||||
|
||||
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_32_4)) {
|
||||
if (mDatabase.kdbxVersion.isBefore(FILE_VERSION_40)) {
|
||||
try {
|
||||
utcDate = DatabaseKDBXXML.DateFormatter.parse(sDate)
|
||||
} catch (e: ParseException) {
|
||||
@@ -863,7 +880,12 @@ class DatabaseInputKDBX(cacheDirectory: File,
|
||||
utcDate = DateKDBXUtil.convertKDBX4Time(seconds)
|
||||
}
|
||||
|
||||
return utcDate ?: Date(0L)
|
||||
return DateInstant(utcDate ?: Date(0L))
|
||||
}
|
||||
|
||||
@Throws(IOException::class, XmlPullParserException::class)
|
||||
private fun readTags(xpp: XmlPullParser): Tags {
|
||||
return Tags(readString(xpp))
|
||||
}
|
||||
|
||||
@Throws(XmlPullParserException::class, IOException::class)
|
||||
|
||||
@@ -27,7 +27,7 @@ import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeader
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
|
||||
import com.kunzisoft.keepass.stream.MacOutputStream
|
||||
import com.kunzisoft.keepass.utils.*
|
||||
import java.io.ByteArrayOutputStream
|
||||
@@ -76,7 +76,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.CompressionFlags, uIntTo4Bytes(DatabaseHeaderKDBX.getFlagFromCompression(databaseKDBX.compressionAlgorithm)))
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.MasterSeed, header.masterSeed)
|
||||
|
||||
if (header.version.isBefore(FILE_VERSION_32_4)) {
|
||||
if (header.version.isBefore(FILE_VERSION_40)) {
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformSeed, header.transformSeed)
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.TransformRounds, longTo8Bytes(databaseKDBX.numberKeyEncryptionRounds))
|
||||
} else {
|
||||
@@ -87,7 +87,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.EncryptionIV, header.encryptionIV)
|
||||
}
|
||||
|
||||
if (header.version.isBefore(FILE_VERSION_32_4)) {
|
||||
if (header.version.isBefore(FILE_VERSION_40)) {
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomstreamKey, header.innerRandomStreamKey)
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.StreamStartBytes, header.streamStartBytes)
|
||||
writeHeaderField(DatabaseHeaderKDBX.PwDbHeaderV4Fields.InnerRandomStreamID, uIntTo4Bytes(header.innerRandomStream!!.id))
|
||||
@@ -121,7 +121,7 @@ constructor(private val databaseKDBX: DatabaseKDBX,
|
||||
|
||||
@Throws(IOException::class)
|
||||
private fun writeHeaderFieldSize(size: Int) {
|
||||
if (header.version.isBefore(FILE_VERSION_32_4)) {
|
||||
if (header.version.isBefore(FILE_VERSION_40)) {
|
||||
mos.write2BytesUShort(size)
|
||||
} else {
|
||||
mos.write4BytesUInt(UnsignedInt(size))
|
||||
|
||||
@@ -20,15 +20,15 @@
|
||||
package com.kunzisoft.keepass.database.file.output
|
||||
|
||||
import com.kunzisoft.encrypt.HashManager
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import com.kunzisoft.keepass.utils.write2BytesUShort
|
||||
import com.kunzisoft.keepass.utils.write4BytesUInt
|
||||
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDB
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeader
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDB
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import com.kunzisoft.keepass.utils.write2BytesUShort
|
||||
import com.kunzisoft.keepass.utils.write4BytesUInt
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.IOException
|
||||
@@ -59,6 +59,8 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
|
||||
override fun output() {
|
||||
// Before we output the header, we should sort our list of groups
|
||||
// and remove any orphaned nodes that are no longer part of the tree hierarchy
|
||||
// also remove the virtual root not present in kdb
|
||||
val rootGroup = mDatabaseKDB.rootGroup
|
||||
sortGroupsForOutput()
|
||||
|
||||
val header = outputHeader(mOutputStream)
|
||||
@@ -86,8 +88,10 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
|
||||
throw DatabaseOutputException("Invalid algorithm parameter.", e)
|
||||
} catch (e: IOException) {
|
||||
throw DatabaseOutputException("Failed to output final encrypted part.", e)
|
||||
} finally {
|
||||
// Add again the virtual root group for better management
|
||||
mDatabaseKDB.rootGroup = rootGroup
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(DatabaseOutputException::class)
|
||||
@@ -201,7 +205,7 @@ class DatabaseOutputKDB(private val mDatabaseKDB: DatabaseKDB,
|
||||
|
||||
private fun sortGroupsForOutput() {
|
||||
val groupList = ArrayList<GroupKDB>()
|
||||
// Rebuild list according to coalation sorting order removing any orphaned groups
|
||||
// Rebuild list according to sorting order removing any orphaned groups
|
||||
for (rootGroup in mDatabaseKDB.rootGroups) {
|
||||
sortGroup(rootGroup, groupList)
|
||||
}
|
||||
|
||||
@@ -23,15 +23,16 @@ import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.util.Xml
|
||||
import com.kunzisoft.encrypt.StreamCipher
|
||||
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
|
||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||
import com.kunzisoft.keepass.database.crypto.CipherEngine
|
||||
import com.kunzisoft.keepass.database.crypto.CrsAlgorithm
|
||||
import com.kunzisoft.keepass.database.crypto.EncryptionAlgorithm
|
||||
import com.kunzisoft.keepass.database.crypto.kdf.KdfFactory
|
||||
import com.kunzisoft.keepass.database.element.DeletedObject
|
||||
import com.kunzisoft.keepass.database.element.*
|
||||
import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseKDBX.Companion.BASE_64_FLAG
|
||||
import com.kunzisoft.keepass.database.element.database.DatabaseVersioned
|
||||
import com.kunzisoft.keepass.database.element.entry.AutoType
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDBX
|
||||
@@ -41,7 +42,8 @@ import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||
import com.kunzisoft.keepass.database.exception.UnknownKDF
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_32_4
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_40
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX.Companion.FILE_VERSION_41
|
||||
import com.kunzisoft.keepass.database.file.DatabaseKDBXXML
|
||||
import com.kunzisoft.keepass.database.file.DateKDBXUtil
|
||||
import com.kunzisoft.keepass.stream.HashedBlockOutputStream
|
||||
@@ -83,7 +85,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
|
||||
header = outputHeader(mOutputStream)
|
||||
|
||||
val osPlain: OutputStream = if (header!!.version.isBefore(FILE_VERSION_32_4)) {
|
||||
val osPlain: OutputStream = if (header!!.version.isBefore(FILE_VERSION_40)) {
|
||||
val cos = attachStreamEncryptor(header!!, mOutputStream)
|
||||
cos.write(header!!.streamStartBytes)
|
||||
|
||||
@@ -102,7 +104,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
else -> osPlain
|
||||
}
|
||||
|
||||
if (!header!!.version.isBefore(FILE_VERSION_32_4)) {
|
||||
if (!header!!.version.isBefore(FILE_VERSION_40)) {
|
||||
outputInnerHeader(mDatabaseKDBX, header!!, xmlOutputStream)
|
||||
}
|
||||
|
||||
@@ -234,40 +236,40 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
private fun writeMeta() {
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemMeta)
|
||||
|
||||
writeObject(DatabaseKDBXXML.ElemGenerator, mDatabaseKDBX.localizedAppName)
|
||||
writeString(DatabaseKDBXXML.ElemGenerator, mDatabaseKDBX.localizedAppName)
|
||||
|
||||
if (hashOfHeader != null) {
|
||||
writeObject(DatabaseKDBXXML.ElemHeaderHash, String(Base64.encode(hashOfHeader!!, BASE_64_FLAG)))
|
||||
writeString(DatabaseKDBXXML.ElemHeaderHash, String(Base64.encode(hashOfHeader!!, BASE_64_FLAG)))
|
||||
}
|
||||
|
||||
writeObject(DatabaseKDBXXML.ElemDbName, mDatabaseKDBX.name, true)
|
||||
writeObject(DatabaseKDBXXML.ElemDbNameChanged, mDatabaseKDBX.nameChanged.date)
|
||||
writeObject(DatabaseKDBXXML.ElemDbDesc, mDatabaseKDBX.description, true)
|
||||
writeObject(DatabaseKDBXXML.ElemDbDescChanged, mDatabaseKDBX.descriptionChanged.date)
|
||||
writeObject(DatabaseKDBXXML.ElemDbDefaultUser, mDatabaseKDBX.defaultUserName, true)
|
||||
writeObject(DatabaseKDBXXML.ElemDbDefaultUserChanged, mDatabaseKDBX.defaultUserNameChanged.date)
|
||||
writeObject(DatabaseKDBXXML.ElemDbMntncHistoryDays, mDatabaseKDBX.maintenanceHistoryDays.toKotlinLong())
|
||||
writeObject(DatabaseKDBXXML.ElemDbColor, mDatabaseKDBX.color)
|
||||
writeObject(DatabaseKDBXXML.ElemDbKeyChanged, mDatabaseKDBX.keyLastChanged.date)
|
||||
writeObject(DatabaseKDBXXML.ElemDbKeyChangeRec, mDatabaseKDBX.keyChangeRecDays)
|
||||
writeObject(DatabaseKDBXXML.ElemDbKeyChangeForce, mDatabaseKDBX.keyChangeForceDays)
|
||||
writeString(DatabaseKDBXXML.ElemDbName, mDatabaseKDBX.name, true)
|
||||
writeDateInstant(DatabaseKDBXXML.ElemDbNameChanged, mDatabaseKDBX.nameChanged)
|
||||
writeString(DatabaseKDBXXML.ElemDbDesc, mDatabaseKDBX.description, true)
|
||||
writeDateInstant(DatabaseKDBXXML.ElemDbDescChanged, mDatabaseKDBX.descriptionChanged)
|
||||
writeString(DatabaseKDBXXML.ElemDbDefaultUser, mDatabaseKDBX.defaultUserName, true)
|
||||
writeDateInstant(DatabaseKDBXXML.ElemDbDefaultUserChanged, mDatabaseKDBX.defaultUserNameChanged)
|
||||
writeLong(DatabaseKDBXXML.ElemDbMntncHistoryDays, mDatabaseKDBX.maintenanceHistoryDays.toKotlinLong())
|
||||
writeString(DatabaseKDBXXML.ElemDbColor, mDatabaseKDBX.color)
|
||||
writeDateInstant(DatabaseKDBXXML.ElemDbKeyChanged, mDatabaseKDBX.keyLastChanged)
|
||||
writeLong(DatabaseKDBXXML.ElemDbKeyChangeRec, mDatabaseKDBX.keyChangeRecDays)
|
||||
writeLong(DatabaseKDBXXML.ElemDbKeyChangeForce, mDatabaseKDBX.keyChangeForceDays)
|
||||
|
||||
writeMemoryProtection(mDatabaseKDBX.memoryProtection)
|
||||
|
||||
writeCustomIconList()
|
||||
|
||||
writeObject(DatabaseKDBXXML.ElemRecycleBinEnabled, mDatabaseKDBX.isRecycleBinEnabled)
|
||||
writeBoolean(DatabaseKDBXXML.ElemRecycleBinEnabled, mDatabaseKDBX.isRecycleBinEnabled)
|
||||
writeUuid(DatabaseKDBXXML.ElemRecycleBinUuid, mDatabaseKDBX.recycleBinUUID)
|
||||
writeObject(DatabaseKDBXXML.ElemRecycleBinChanged, mDatabaseKDBX.recycleBinChanged)
|
||||
writeDateInstant(DatabaseKDBXXML.ElemRecycleBinChanged, mDatabaseKDBX.recycleBinChanged)
|
||||
writeUuid(DatabaseKDBXXML.ElemEntryTemplatesGroup, mDatabaseKDBX.entryTemplatesGroup)
|
||||
writeObject(DatabaseKDBXXML.ElemEntryTemplatesGroupChanged, mDatabaseKDBX.entryTemplatesGroupChanged.date)
|
||||
writeObject(DatabaseKDBXXML.ElemHistoryMaxItems, mDatabaseKDBX.historyMaxItems.toLong())
|
||||
writeObject(DatabaseKDBXXML.ElemHistoryMaxSize, mDatabaseKDBX.historyMaxSize)
|
||||
writeDateInstant(DatabaseKDBXXML.ElemEntryTemplatesGroupChanged, mDatabaseKDBX.entryTemplatesGroupChanged)
|
||||
writeLong(DatabaseKDBXXML.ElemHistoryMaxItems, mDatabaseKDBX.historyMaxItems.toLong())
|
||||
writeLong(DatabaseKDBXXML.ElemHistoryMaxSize, mDatabaseKDBX.historyMaxSize)
|
||||
writeUuid(DatabaseKDBXXML.ElemLastSelectedGroup, mDatabaseKDBX.lastSelectedGroupUUID)
|
||||
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleGroup, mDatabaseKDBX.lastTopVisibleGroupUUID)
|
||||
|
||||
// Seem to work properly if always in meta
|
||||
if (header!!.version.isBefore(FILE_VERSION_32_4))
|
||||
if (header!!.version.isBefore(FILE_VERSION_40))
|
||||
writeMetaBinaries()
|
||||
|
||||
writeCustomData(mDatabaseKDBX.customData)
|
||||
@@ -309,7 +311,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
Log.e(TAG, "Unable to retrieve header", unknownKDF)
|
||||
}
|
||||
|
||||
if (header.version.isBefore(FILE_VERSION_32_4)) {
|
||||
if (header.version.isBefore(FILE_VERSION_40)) {
|
||||
header.innerRandomStream = CrsAlgorithm.Salsa20
|
||||
header.innerRandomStreamKey = ByteArray(32)
|
||||
} else {
|
||||
@@ -324,7 +326,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
throw DatabaseOutputException(e)
|
||||
}
|
||||
|
||||
if (header.version.isBefore(FILE_VERSION_32_4)) {
|
||||
if (header.version.isBefore(FILE_VERSION_40)) {
|
||||
random.nextBytes(header.streamStartBytes)
|
||||
}
|
||||
|
||||
@@ -353,19 +355,21 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
private fun startGroup(group: GroupKDBX) {
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemGroup)
|
||||
writeUuid(DatabaseKDBXXML.ElemUuid, group.id)
|
||||
writeObject(DatabaseKDBXXML.ElemName, group.title)
|
||||
writeObject(DatabaseKDBXXML.ElemNotes, group.notes)
|
||||
writeObject(DatabaseKDBXXML.ElemIcon, group.icon.standard.id.toLong())
|
||||
writeString(DatabaseKDBXXML.ElemName, group.title)
|
||||
writeString(DatabaseKDBXXML.ElemNotes, group.notes)
|
||||
writeLong(DatabaseKDBXXML.ElemIcon, group.icon.standard.id.toLong())
|
||||
|
||||
if (!group.icon.custom.isUnknown) {
|
||||
writeUuid(DatabaseKDBXXML.ElemCustomIconID, group.icon.custom.uuid)
|
||||
}
|
||||
|
||||
writeTags(group.tags)
|
||||
writePreviousParentGroup(group.previousParentGroup)
|
||||
writeTimes(group)
|
||||
writeObject(DatabaseKDBXXML.ElemIsExpanded, group.isExpanded)
|
||||
writeObject(DatabaseKDBXXML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence)
|
||||
writeObject(DatabaseKDBXXML.ElemEnableAutoType, group.enableAutoType)
|
||||
writeObject(DatabaseKDBXXML.ElemEnableSearching, group.enableSearching)
|
||||
writeBoolean(DatabaseKDBXXML.ElemIsExpanded, group.isExpanded)
|
||||
writeString(DatabaseKDBXXML.ElemGroupDefaultAutoTypeSeq, group.defaultAutoTypeSequence)
|
||||
writeBoolean(DatabaseKDBXXML.ElemEnableAutoType, group.enableAutoType)
|
||||
writeBoolean(DatabaseKDBXXML.ElemEnableSearching, group.enableSearching)
|
||||
writeUuid(DatabaseKDBXXML.ElemLastTopVisibleEntry, group.lastTopVisibleEntry)
|
||||
}
|
||||
|
||||
@@ -380,24 +384,26 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemEntry)
|
||||
|
||||
writeUuid(DatabaseKDBXXML.ElemUuid, entry.id)
|
||||
writeObject(DatabaseKDBXXML.ElemIcon, entry.icon.standard.id.toLong())
|
||||
writeLong(DatabaseKDBXXML.ElemIcon, entry.icon.standard.id.toLong())
|
||||
|
||||
if (!entry.icon.custom.isUnknown) {
|
||||
writeUuid(DatabaseKDBXXML.ElemCustomIconID, entry.icon.custom.uuid)
|
||||
}
|
||||
|
||||
writeObject(DatabaseKDBXXML.ElemFgColor, entry.foregroundColor)
|
||||
writeObject(DatabaseKDBXXML.ElemBgColor, entry.backgroundColor)
|
||||
writeObject(DatabaseKDBXXML.ElemOverrideUrl, entry.overrideURL)
|
||||
writeObject(DatabaseKDBXXML.ElemTags, entry.tags)
|
||||
writeString(DatabaseKDBXXML.ElemFgColor, entry.foregroundColor)
|
||||
writeString(DatabaseKDBXXML.ElemBgColor, entry.backgroundColor)
|
||||
writeString(DatabaseKDBXXML.ElemOverrideUrl, entry.overrideURL)
|
||||
|
||||
// Write quality check only if false
|
||||
if (!entry.qualityCheck) {
|
||||
writeBoolean(DatabaseKDBXXML.ElemQualityCheck, entry.qualityCheck)
|
||||
}
|
||||
writeTags(entry.tags)
|
||||
writePreviousParentGroup(entry.previousParentGroup)
|
||||
writeTimes(entry)
|
||||
|
||||
writeFields(entry.fields)
|
||||
writeEntryBinaries(entry.binaries)
|
||||
if (entry.containsCustomData()) {
|
||||
writeCustomData(entry.customData)
|
||||
}
|
||||
writeCustomData(entry.customData)
|
||||
writeAutoType(entry.autoType)
|
||||
|
||||
if (!isHistory) {
|
||||
@@ -408,7 +414,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeObject(name: String, value: String, filterXmlChars: Boolean = false) {
|
||||
private fun writeString(name: String, value: String, filterXmlChars: Boolean = false) {
|
||||
var xmlString = value
|
||||
|
||||
xml.startTag(null, name)
|
||||
@@ -422,38 +428,37 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeObject(name: String, value: Date) {
|
||||
if (header!!.version.isBefore(FILE_VERSION_32_4)) {
|
||||
writeObject(name, DatabaseKDBXXML.DateFormatter.format(value))
|
||||
private fun writeDateInstant(name: String, value: DateInstant) {
|
||||
val date = value.date
|
||||
if (header!!.version.isBefore(FILE_VERSION_40)) {
|
||||
writeString(name, DatabaseKDBXXML.DateFormatter.format(date))
|
||||
} else {
|
||||
val dt = DateTime(value)
|
||||
val seconds = DateKDBXUtil.convertDateToKDBX4Time(dt)
|
||||
val buf = longTo8Bytes(seconds)
|
||||
val buf = longTo8Bytes(DateKDBXUtil.convertDateToKDBX4Time(DateTime(date)))
|
||||
val b64 = String(Base64.encode(buf, BASE_64_FLAG))
|
||||
writeObject(name, b64)
|
||||
writeString(name, b64)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeObject(name: String, value: Long) {
|
||||
writeObject(name, value.toString())
|
||||
private fun writeLong(name: String, value: Long) {
|
||||
writeString(name, value.toString())
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeObject(name: String, value: Boolean?) {
|
||||
private fun writeBoolean(name: String, value: Boolean?) {
|
||||
val text: String = when {
|
||||
value == null -> DatabaseKDBXXML.ValNull
|
||||
value -> DatabaseKDBXXML.ValTrue
|
||||
else -> DatabaseKDBXXML.ValFalse
|
||||
}
|
||||
|
||||
writeObject(name, text)
|
||||
writeString(name, text)
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeUuid(name: String, uuid: UUID) {
|
||||
val data = uuidTo16Bytes(uuid)
|
||||
writeObject(name, String(Base64.encode(data, BASE_64_FLAG)))
|
||||
writeString(name, String(Base64.encode(data, BASE_64_FLAG)))
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -514,34 +519,29 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemBinaries)
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeObject(name: String, keyName: String, keyValue: String, valueName: String, valueValue: String) {
|
||||
xml.startTag(null, name)
|
||||
|
||||
xml.startTag(null, keyName)
|
||||
xml.text(safeXmlString(keyValue))
|
||||
xml.endTag(null, keyName)
|
||||
|
||||
xml.startTag(null, valueName)
|
||||
xml.text(safeXmlString(valueValue))
|
||||
xml.endTag(null, valueName)
|
||||
|
||||
xml.endTag(null, name)
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeAutoType(autoType: AutoType) {
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemAutoType)
|
||||
|
||||
writeObject(DatabaseKDBXXML.ElemAutoTypeEnabled, autoType.enabled)
|
||||
writeObject(DatabaseKDBXXML.ElemAutoTypeObfuscation, autoType.obfuscationOptions.toKotlinLong())
|
||||
writeBoolean(DatabaseKDBXXML.ElemAutoTypeEnabled, autoType.enabled)
|
||||
writeLong(DatabaseKDBXXML.ElemAutoTypeObfuscation, autoType.obfuscationOptions.toKotlinLong())
|
||||
|
||||
if (autoType.defaultSequence.isNotEmpty()) {
|
||||
writeObject(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true)
|
||||
writeString(DatabaseKDBXXML.ElemAutoTypeDefaultSeq, autoType.defaultSequence, true)
|
||||
}
|
||||
|
||||
for ((key, value) in autoType.entrySet()) {
|
||||
writeObject(DatabaseKDBXXML.ElemAutoTypeItem, DatabaseKDBXXML.ElemWindow, key, DatabaseKDBXXML.ElemKeystrokeSequence, value)
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemAutoTypeItem)
|
||||
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemWindow)
|
||||
xml.text(safeXmlString(key))
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemWindow)
|
||||
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemKeystrokeSequence)
|
||||
xml.text(safeXmlString(value))
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemKeystrokeSequence)
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemAutoTypeItem)
|
||||
}
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemAutoType)
|
||||
@@ -592,7 +592,7 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemDeletedObject)
|
||||
|
||||
writeUuid(DatabaseKDBXXML.ElemUuid, value.uuid)
|
||||
writeObject(DatabaseKDBXXML.ElemDeletionTime, value.getDeletionTime())
|
||||
writeDateInstant(DatabaseKDBXXML.ElemDeletionTime, value.getDeletionTime())
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemDeletedObject)
|
||||
}
|
||||
@@ -632,43 +632,72 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
private fun writeMemoryProtection(value: MemoryProtectionConfig) {
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemMemoryProt)
|
||||
|
||||
writeObject(DatabaseKDBXXML.ElemProtTitle, value.protectTitle)
|
||||
writeObject(DatabaseKDBXXML.ElemProtUserName, value.protectUserName)
|
||||
writeObject(DatabaseKDBXXML.ElemProtPassword, value.protectPassword)
|
||||
writeObject(DatabaseKDBXXML.ElemProtURL, value.protectUrl)
|
||||
writeObject(DatabaseKDBXXML.ElemProtNotes, value.protectNotes)
|
||||
writeBoolean(DatabaseKDBXXML.ElemProtTitle, value.protectTitle)
|
||||
writeBoolean(DatabaseKDBXXML.ElemProtUserName, value.protectUserName)
|
||||
writeBoolean(DatabaseKDBXXML.ElemProtPassword, value.protectPassword)
|
||||
writeBoolean(DatabaseKDBXXML.ElemProtURL, value.protectUrl)
|
||||
writeBoolean(DatabaseKDBXXML.ElemProtNotes, value.protectNotes)
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemMemoryProt)
|
||||
}
|
||||
|
||||
@Throws(IllegalArgumentException::class, IllegalStateException::class, IOException::class)
|
||||
private fun writeCustomData(customData: Map<String, String>) {
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemCustomData)
|
||||
private fun writeCustomData(customData: CustomData) {
|
||||
if (customData.isNotEmpty()) {
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemCustomData)
|
||||
|
||||
for ((key, value) in customData) {
|
||||
writeObject(
|
||||
DatabaseKDBXXML.ElemStringDictExItem,
|
||||
DatabaseKDBXXML.ElemKey,
|
||||
key,
|
||||
DatabaseKDBXXML.ElemValue,
|
||||
value
|
||||
)
|
||||
customData.doForEachItems { customDataItem ->
|
||||
writeCustomDataItem(customDataItem)
|
||||
}
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemCustomData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeCustomDataItem(customDataItem: CustomDataItem) {
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemStringDictExItem)
|
||||
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemKey)
|
||||
xml.text(safeXmlString(customDataItem.key))
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemKey)
|
||||
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemValue)
|
||||
xml.text(safeXmlString(customDataItem.value))
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemValue)
|
||||
|
||||
customDataItem.lastModificationTime?.let { lastModificationTime ->
|
||||
writeDateInstant(DatabaseKDBXXML.ElemLastModTime, lastModificationTime)
|
||||
}
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.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)
|
||||
private fun writeTimes(node: NodeKDBXInterface) {
|
||||
xml.startTag(null, DatabaseKDBXXML.ElemTimes)
|
||||
|
||||
writeObject(DatabaseKDBXXML.ElemLastModTime, node.lastModificationTime.date)
|
||||
writeObject(DatabaseKDBXXML.ElemCreationTime, node.creationTime.date)
|
||||
writeObject(DatabaseKDBXXML.ElemLastAccessTime, node.lastAccessTime.date)
|
||||
writeObject(DatabaseKDBXXML.ElemExpiryTime, node.expiryTime.date)
|
||||
writeObject(DatabaseKDBXXML.ElemExpires, node.expires)
|
||||
writeObject(DatabaseKDBXXML.ElemUsageCount, node.usageCount.toKotlinLong())
|
||||
writeObject(DatabaseKDBXXML.ElemLocationChanged, node.locationChanged.date)
|
||||
writeDateInstant(DatabaseKDBXXML.ElemLastModTime, node.lastModificationTime)
|
||||
writeDateInstant(DatabaseKDBXXML.ElemCreationTime, node.creationTime)
|
||||
writeDateInstant(DatabaseKDBXXML.ElemLastAccessTime, node.lastAccessTime)
|
||||
writeDateInstant(DatabaseKDBXXML.ElemExpiryTime, node.expiryTime)
|
||||
writeBoolean(DatabaseKDBXXML.ElemExpires, node.expires)
|
||||
writeLong(DatabaseKDBXXML.ElemUsageCount, node.usageCount.toKotlinLong())
|
||||
writeDateInstant(DatabaseKDBXXML.ElemLocationChanged, node.locationChanged)
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemTimes)
|
||||
}
|
||||
@@ -709,9 +738,15 @@ class DatabaseOutputKDBX(private val mDatabaseKDBX: DatabaseKDBX,
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Unable to write custom icon", e)
|
||||
} finally {
|
||||
writeObject(DatabaseKDBXXML.ElemCustomIconItemData,
|
||||
writeString(DatabaseKDBXXML.ElemCustomIconItemData,
|
||||
String(Base64.encode(customImageData, BASE_64_FLAG)))
|
||||
}
|
||||
if (iconCustom.name.isNotEmpty()) {
|
||||
writeString(DatabaseKDBXXML.ElemName, iconCustom.name)
|
||||
}
|
||||
iconCustom.lastModificationTime?.let { lastModificationTime ->
|
||||
writeDateInstant(DatabaseKDBXXML.ElemLastModTime, lastModificationTime)
|
||||
}
|
||||
|
||||
xml.endTag(null, DatabaseKDBXXML.ElemCustomIconItem)
|
||||
}
|
||||
|
||||
@@ -19,13 +19,9 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.file.output
|
||||
|
||||
import com.kunzisoft.keepass.utils.UnsignedInt
|
||||
import com.kunzisoft.keepass.utils.dateTo5Bytes
|
||||
import com.kunzisoft.keepass.utils.uIntTo4Bytes
|
||||
import com.kunzisoft.keepass.utils.uShortTo2Bytes
|
||||
import com.kunzisoft.keepass.utils.writeStringToStream
|
||||
import com.kunzisoft.keepass.database.element.group.GroupKDB
|
||||
import com.kunzisoft.keepass.database.exception.DatabaseOutputException
|
||||
import com.kunzisoft.keepass.utils.*
|
||||
import java.io.IOException
|
||||
import java.io.OutputStream
|
||||
|
||||
@@ -77,7 +73,7 @@ class GroupOutputKDB(private val mGroup: GroupKDB,
|
||||
// Level
|
||||
mOutputStream.write(LEVEL_FIELD_TYPE)
|
||||
mOutputStream.write(LEVEL_FIELD_SIZE)
|
||||
mOutputStream.write(uShortTo2Bytes(mGroup.level))
|
||||
mOutputStream.write(uShortTo2Bytes(mGroup.getLevel()))
|
||||
|
||||
// Flags
|
||||
mOutputStream.write(FLAGS_FIELD_TYPE)
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.search
|
||||
|
||||
import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
|
||||
|
||||
class EntryKDBXSearchHandler(private val mSearchParametersKDBX: SearchParameters,
|
||||
private val mListStorage: MutableList<EntryKDBX>)
|
||||
: NodeHandler<EntryKDBX>() {
|
||||
|
||||
override fun operate(node: EntryKDBX): Boolean {
|
||||
|
||||
if (mSearchParametersKDBX.excludeExpired
|
||||
&& node.isCurrentlyExpires) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (searchStrings(node)) {
|
||||
mListStorage.add(node)
|
||||
return true
|
||||
}
|
||||
|
||||
if (searchInGroupNames(node)) {
|
||||
mListStorage.add(node)
|
||||
return true
|
||||
}
|
||||
|
||||
if (searchInUUID(node)) {
|
||||
mListStorage.add(node)
|
||||
return true
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun searchInGroupNames(entry: EntryKDBX): Boolean {
|
||||
if (mSearchParametersKDBX.searchInGroupNames) {
|
||||
val parent = entry.parent
|
||||
if (parent != null) {
|
||||
return parent.title
|
||||
.contains(mSearchParametersKDBX.searchString, mSearchParametersKDBX.ignoreCase)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun searchInUUID(entry: EntryKDBX): Boolean {
|
||||
if (mSearchParametersKDBX.searchInUUIDs) {
|
||||
return UuidUtil.toHexString(entry.id)
|
||||
.contains(mSearchParametersKDBX.searchString, true)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun searchStrings(entry: EntryKDBX): Boolean {
|
||||
val iterator = EntrySearchStringIteratorKDBX(entry, mSearchParametersKDBX)
|
||||
while (iterator.hasNext()) {
|
||||
val stringValue = iterator.next()
|
||||
if (stringValue.isNotEmpty()) {
|
||||
if (stringValue.contains(mSearchParametersKDBX.searchString, mSearchParametersKDBX.ignoreCase)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -24,15 +24,67 @@ import com.kunzisoft.keepass.database.action.node.NodeHandler
|
||||
import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
import com.kunzisoft.keepass.database.element.Group
|
||||
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDB
|
||||
import com.kunzisoft.keepass.database.search.iterator.EntrySearchStringIteratorKDBX
|
||||
import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_FIELD
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import com.kunzisoft.keepass.utils.UuidUtil
|
||||
|
||||
class SearchHelper {
|
||||
|
||||
private var incrementEntry = 0
|
||||
|
||||
fun createVirtualGroupWithSearchResult(database: Database,
|
||||
searchParameters: SearchParameters,
|
||||
omitBackup: Boolean,
|
||||
max: Int): Group? {
|
||||
|
||||
val searchGroup = database.createGroup()
|
||||
searchGroup?.isVirtual = true
|
||||
searchGroup?.title = "\"" + searchParameters.searchQuery + "\""
|
||||
|
||||
// Search all entries
|
||||
incrementEntry = 0
|
||||
database.rootGroup?.doForEachChild(
|
||||
object : NodeHandler<Entry>() {
|
||||
override fun operate(node: Entry): Boolean {
|
||||
if (incrementEntry >= max)
|
||||
return false
|
||||
if (entryContainsString(database, node, searchParameters)) {
|
||||
searchGroup?.addChildEntry(node)
|
||||
incrementEntry++
|
||||
}
|
||||
// Stop searching when we have max entries
|
||||
return incrementEntry < max
|
||||
}
|
||||
},
|
||||
object : NodeHandler<Group>() {
|
||||
override fun operate(node: Group): Boolean {
|
||||
return when {
|
||||
incrementEntry >= max -> false
|
||||
database.isGroupSearchable(node, omitBackup) -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
},
|
||||
false)
|
||||
|
||||
return searchGroup
|
||||
}
|
||||
|
||||
private fun entryContainsString(database: Database,
|
||||
entry: Entry,
|
||||
searchParameters: SearchParameters): Boolean {
|
||||
// To search in field references
|
||||
database.startManageEntry(entry)
|
||||
// Search all strings in the entry
|
||||
val searchFound = searchInEntry(entry, searchParameters)
|
||||
database.stopManageEntry(entry)
|
||||
|
||||
return searchFound
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_SEARCH_ENTRY = 10
|
||||
|
||||
@@ -70,75 +122,67 @@ class SearchHelper {
|
||||
onDatabaseClosed.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var incrementEntry = 0
|
||||
/**
|
||||
* Return true if the search query in search parameters is found in available parameters
|
||||
*/
|
||||
fun searchInEntry(entry: Entry,
|
||||
searchParameters: SearchParameters): Boolean {
|
||||
val searchQuery = searchParameters.searchQuery
|
||||
// Entry don't contains string if the search string is empty
|
||||
if (searchQuery.isEmpty())
|
||||
return false
|
||||
|
||||
fun createVirtualGroupWithSearchResult(database: Database,
|
||||
searchQuery: String,
|
||||
searchParameters: SearchParameters,
|
||||
omitBackup: Boolean,
|
||||
max: Int): Group? {
|
||||
|
||||
val searchGroup = database.createGroup()
|
||||
searchGroup?.isVirtual = true
|
||||
searchGroup?.title = "\"" + searchQuery + "\""
|
||||
|
||||
// Search all entries
|
||||
incrementEntry = 0
|
||||
database.rootGroup?.doForEachChild(
|
||||
object : NodeHandler<Entry>() {
|
||||
override fun operate(node: Entry): Boolean {
|
||||
if (incrementEntry >= max)
|
||||
return false
|
||||
if (entryContainsString(node, searchQuery, searchParameters)) {
|
||||
searchGroup?.addChildEntry(node)
|
||||
incrementEntry++
|
||||
}
|
||||
// Stop searching when we have max entries
|
||||
return incrementEntry < max
|
||||
// Search all strings in the KDBX entry
|
||||
if (searchParameters.searchInTitles) {
|
||||
if (checkSearchQuery(entry.title, searchParameters))
|
||||
return true
|
||||
}
|
||||
if (searchParameters.searchInUserNames) {
|
||||
if (checkSearchQuery(entry.username, searchParameters))
|
||||
return true
|
||||
}
|
||||
if (searchParameters.searchInPasswords) {
|
||||
if (checkSearchQuery(entry.password, searchParameters))
|
||||
return true
|
||||
}
|
||||
if (searchParameters.searchInUrls) {
|
||||
if (checkSearchQuery(entry.url, searchParameters))
|
||||
return true
|
||||
}
|
||||
if (searchParameters.searchInNotes) {
|
||||
if (checkSearchQuery(entry.notes, searchParameters))
|
||||
return true
|
||||
}
|
||||
if (searchParameters.searchInUUIDs) {
|
||||
val hexString = UuidUtil.toHexString(entry.nodeId.id)
|
||||
if (hexString != null && hexString.contains(searchQuery, true))
|
||||
return true
|
||||
}
|
||||
if (searchParameters.searchInOther) {
|
||||
entry.getExtraFields().forEach { field ->
|
||||
if (field.name != OTP_FIELD
|
||||
|| (field.name == OTP_FIELD && searchParameters.searchInOTP)) {
|
||||
if (checkSearchQuery(field.protectedValue.toString(), searchParameters))
|
||||
return true
|
||||
}
|
||||
},
|
||||
object : NodeHandler<Group>() {
|
||||
override fun operate(node: Group): Boolean {
|
||||
return when {
|
||||
incrementEntry >= max -> false
|
||||
database.isGroupSearchable(node, omitBackup) -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
},
|
||||
false)
|
||||
|
||||
return searchGroup
|
||||
}
|
||||
|
||||
private fun entryContainsString(entry: Entry,
|
||||
searchQuery: String,
|
||||
searchParameters: SearchParameters): Boolean {
|
||||
|
||||
// Entry don't contains string if the search string is empty
|
||||
if (searchQuery.isEmpty())
|
||||
return false
|
||||
|
||||
// Search all strings in the entry
|
||||
var iterator: Iterator<String>? = null
|
||||
entry.entryKDB?.let {
|
||||
iterator = EntrySearchStringIteratorKDB(it, searchParameters)
|
||||
}
|
||||
entry.entryKDBX?.let {
|
||||
iterator = EntrySearchStringIteratorKDBX(it, searchParameters)
|
||||
}
|
||||
|
||||
iterator?.let {
|
||||
while (it.hasNext()) {
|
||||
val currentString = it.next()
|
||||
if (currentString.isNotEmpty()
|
||||
&& currentString.contains(searchQuery, true)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun checkSearchQuery(stringToCheck: String, searchParameters: SearchParameters): Boolean {
|
||||
/*
|
||||
// TODO Search settings
|
||||
var regularExpression = false
|
||||
var ignoreCase = true
|
||||
var removeAccents = true <- Too much time, to study
|
||||
var excludeExpired = false
|
||||
var searchOnlyInCurrentGroup = false
|
||||
*/
|
||||
return stringToCheck.isNotEmpty()
|
||||
&& stringToCheck.contains(
|
||||
searchParameters.searchQuery, true)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,57 +23,15 @@ package com.kunzisoft.keepass.database.search
|
||||
* Parameters for searching strings in the database.
|
||||
*/
|
||||
class SearchParameters {
|
||||
var searchQuery: String = ""
|
||||
|
||||
var searchString: String = ""
|
||||
|
||||
var regularExpression = false
|
||||
var searchInTitles = true
|
||||
var searchInUserNames = true
|
||||
var searchInPasswords = false
|
||||
var searchInUrls = true
|
||||
var searchInGroupNames = false
|
||||
var searchInNotes = true
|
||||
var searchInOTP = false
|
||||
var searchInOther = true
|
||||
var searchInUUIDs = false
|
||||
var searchInTags = true
|
||||
var ignoreCase = true
|
||||
var ignoreExpired = false
|
||||
var excludeExpired = false
|
||||
|
||||
constructor()
|
||||
|
||||
/**
|
||||
* Copy search parameters
|
||||
* @param source
|
||||
*/
|
||||
constructor(source: SearchParameters) {
|
||||
this.regularExpression = source.regularExpression
|
||||
this.searchInTitles = source.searchInTitles
|
||||
this.searchInUserNames = source.searchInUserNames
|
||||
this.searchInPasswords = source.searchInPasswords
|
||||
this.searchInUrls = source.searchInUrls
|
||||
this.searchInGroupNames = source.searchInGroupNames
|
||||
this.searchInNotes = source.searchInNotes
|
||||
this.searchInOTP = source.searchInOTP
|
||||
this.searchInOther = source.searchInOther
|
||||
this.searchInUUIDs = source.searchInUUIDs
|
||||
this.searchInTags = source.searchInTags
|
||||
this.ignoreCase = source.ignoreCase
|
||||
this.ignoreExpired = source.ignoreExpired
|
||||
this.excludeExpired = source.excludeExpired
|
||||
}
|
||||
|
||||
fun setupNone() {
|
||||
searchInTitles = false
|
||||
searchInUserNames = false
|
||||
searchInPasswords = false
|
||||
searchInUrls = false
|
||||
searchInGroupNames = false
|
||||
searchInNotes = false
|
||||
searchInOTP = false
|
||||
searchInOther = false
|
||||
searchInUUIDs = false
|
||||
searchInTags = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.search;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.kunzisoft.keepass.utils.StreamBytesUtilsKt.uuidTo16Bytes;
|
||||
|
||||
public class UuidUtil {
|
||||
|
||||
public static String toHexString(UUID uuid) {
|
||||
if (uuid == null) { return null; }
|
||||
|
||||
byte[] buf = uuidTo16Bytes(uuid);
|
||||
|
||||
int len = buf.length;
|
||||
if (len == 0) { return ""; }
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
short bt;
|
||||
char high, low;
|
||||
for (byte b : buf) {
|
||||
bt = (short) (b & 0xFF);
|
||||
high = (char) (bt >>> 4);
|
||||
low = (char) (bt & 0x0F);
|
||||
sb.append(byteToChar(high));
|
||||
sb.append(byteToChar(low));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
// Use short to represent unsigned byte
|
||||
private static char byteToChar(char bt) {
|
||||
if (bt >= 10) {
|
||||
return (char)('A' + bt - 10);
|
||||
}
|
||||
else {
|
||||
return (char)('0' + bt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.search.iterator
|
||||
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDB
|
||||
import com.kunzisoft.keepass.database.search.SearchParameters
|
||||
|
||||
import java.util.NoSuchElementException
|
||||
|
||||
class EntrySearchStringIteratorKDB(
|
||||
private val mEntry: EntryKDB,
|
||||
private val mSearchParameters: SearchParameters)
|
||||
: Iterator<String> {
|
||||
|
||||
private var current = 0
|
||||
|
||||
private val currentString: String
|
||||
get() {
|
||||
return when (current) {
|
||||
title -> mEntry.title
|
||||
url -> mEntry.url
|
||||
username -> mEntry.username
|
||||
notes -> mEntry.notes
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return current < maxEntries
|
||||
}
|
||||
|
||||
override fun next(): String {
|
||||
// Past the end of the list
|
||||
if (current == maxEntries) {
|
||||
throw NoSuchElementException("Past final string")
|
||||
}
|
||||
|
||||
useSearchParameters()
|
||||
|
||||
val str = currentString
|
||||
current++
|
||||
return str
|
||||
}
|
||||
|
||||
private fun useSearchParameters() {
|
||||
var found = false
|
||||
while (!found) {
|
||||
found = when (current) {
|
||||
title -> mSearchParameters.searchInTitles
|
||||
url -> mSearchParameters.searchInUrls
|
||||
username -> mSearchParameters.searchInUserNames
|
||||
notes -> mSearchParameters.searchInNotes
|
||||
else -> true
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
current++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val title = 0
|
||||
private const val url = 1
|
||||
private const val username = 2
|
||||
private const val notes = 3
|
||||
private const val maxEntries = 4
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.database.search.iterator
|
||||
|
||||
import com.kunzisoft.keepass.database.element.entry.EntryKDBX
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.database.search.SearchParameters
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import java.util.*
|
||||
import kotlin.collections.Map.Entry
|
||||
|
||||
class EntrySearchStringIteratorKDBX(
|
||||
entry: EntryKDBX,
|
||||
private val mSearchParameters: SearchParameters)
|
||||
: Iterator<String> {
|
||||
|
||||
private var mCurrent: String? = null
|
||||
private var mSetIterator: Iterator<Entry<String, ProtectedString>>? = null
|
||||
|
||||
init {
|
||||
mSetIterator = entry.fields.entries.iterator()
|
||||
advance()
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return mCurrent != null
|
||||
}
|
||||
|
||||
override fun next(): String {
|
||||
if (mCurrent == null) {
|
||||
throw NoSuchElementException("Past the end of the list.")
|
||||
}
|
||||
|
||||
val next:String = mCurrent!!
|
||||
advance()
|
||||
return next
|
||||
}
|
||||
|
||||
private fun advance() {
|
||||
mSetIterator?.let {
|
||||
while (it.hasNext()) {
|
||||
val entry = it.next()
|
||||
val key = entry.key
|
||||
|
||||
if (searchInField(key)) {
|
||||
mCurrent = entry.value.toString()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mCurrent = null
|
||||
}
|
||||
|
||||
private fun searchInField(key: String): Boolean {
|
||||
return when (key) {
|
||||
EntryKDBX.STR_TITLE -> mSearchParameters.searchInTitles
|
||||
EntryKDBX.STR_USERNAME -> mSearchParameters.searchInUserNames
|
||||
EntryKDBX.STR_PASSWORD -> mSearchParameters.searchInPasswords
|
||||
EntryKDBX.STR_URL -> mSearchParameters.searchInUrls
|
||||
EntryKDBX.STR_NOTES -> mSearchParameters.searchInNotes
|
||||
OtpEntryFields.OTP_FIELD -> mSearchParameters.searchInOTP
|
||||
else -> mSearchParameters.searchInOther
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -93,7 +93,7 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
|
||||
value
|
||||
} else {
|
||||
TokenCalculator.HOTP_INITIAL_COUNTER
|
||||
throw IllegalArgumentException()
|
||||
throw NumberFormatException()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ data class OtpElement(var otpModel: OtpModel = OtpModel()) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MIN_HOTP_COUNTER = 1
|
||||
const val MIN_HOTP_COUNTER = 0
|
||||
const val MAX_HOTP_COUNTER = Long.MAX_VALUE
|
||||
|
||||
const val MIN_TOTP_PERIOD = 1
|
||||
|
||||
@@ -295,22 +295,30 @@ object OtpEntryFields {
|
||||
secretHexField != null -> otpElement.setHexSecret(secretHexField)
|
||||
secretBase32Field != null -> otpElement.setBase32Secret(secretBase32Field)
|
||||
secretBase64Field != null -> otpElement.setBase64Secret(secretBase64Field)
|
||||
lengthField != null -> otpElement.digits = lengthField.toIntOrNull() ?: OTP_DEFAULT_DIGITS
|
||||
periodField != null -> otpElement.period = periodField.toIntOrNull() ?: TOTP_DEFAULT_PERIOD
|
||||
algorithmField != null -> otpElement.algorithm =
|
||||
when (algorithmField.toUpperCase(Locale.ENGLISH)) {
|
||||
TIMEOTP_ALGORITHM_SHA1_VALUE -> HashAlgorithm.SHA1
|
||||
TIMEOTP_ALGORITHM_SHA256_VALUE -> HashAlgorithm.SHA256
|
||||
TIMEOTP_ALGORITHM_SHA512_VALUE -> HashAlgorithm.SHA512
|
||||
else -> HashAlgorithm.SHA1
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
otpElement.type = OtpType.TOTP
|
||||
if (lengthField != null) {
|
||||
otpElement.digits = lengthField.toIntOrNull() ?: OTP_DEFAULT_DIGITS
|
||||
}
|
||||
if (lengthField != null) {
|
||||
otpElement.digits = lengthField.toIntOrNull() ?: OTP_DEFAULT_DIGITS
|
||||
}
|
||||
if (periodField != null) {
|
||||
otpElement.period = periodField.toIntOrNull() ?: TOTP_DEFAULT_PERIOD
|
||||
}
|
||||
if (algorithmField != null) {
|
||||
otpElement.algorithm =
|
||||
when (algorithmField.toUpperCase(Locale.ENGLISH)) {
|
||||
TIMEOTP_ALGORITHM_SHA1_VALUE -> HashAlgorithm.SHA1
|
||||
TIMEOTP_ALGORITHM_SHA256_VALUE -> HashAlgorithm.SHA256
|
||||
TIMEOTP_ALGORITHM_SHA512_VALUE -> HashAlgorithm.SHA512
|
||||
else -> HashAlgorithm.SHA1
|
||||
}
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
return false
|
||||
}
|
||||
|
||||
otpElement.type = OtpType.TOTP
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -321,10 +329,10 @@ object OtpEntryFields {
|
||||
return try {
|
||||
// KeeOtp string format
|
||||
val query = breakDownKeyValuePairs(plainText)
|
||||
otpElement.type = OtpType.TOTP
|
||||
otpElement.setBase32Secret(query[SEED_KEY] ?: "")
|
||||
otpElement.digits = query[DIGITS_KEY]?.toIntOrNull() ?: OTP_DEFAULT_DIGITS
|
||||
otpElement.period = query[STEP_KEY]?.toIntOrNull() ?: TOTP_DEFAULT_PERIOD
|
||||
otpElement.type = OtpType.TOTP
|
||||
true
|
||||
} catch (exception: Exception) {
|
||||
false
|
||||
@@ -351,6 +359,7 @@ object OtpEntryFields {
|
||||
// malformed
|
||||
return false
|
||||
}
|
||||
otpElement.type = OtpType.TOTP
|
||||
otpElement.period = matcher.group(1)?.toIntOrNull() ?: TOTP_DEFAULT_PERIOD
|
||||
matcher.group(2)?.let { secondMatcher ->
|
||||
try {
|
||||
@@ -365,7 +374,6 @@ object OtpEntryFields {
|
||||
} catch (exception: Exception) {
|
||||
return false
|
||||
}
|
||||
otpElement.type = OtpType.TOTP
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -374,6 +382,7 @@ object OtpEntryFields {
|
||||
val secretHexField = getField(HMACOTP_SECRET_HEX_FIELD)
|
||||
val secretBase32Field = getField(HMACOTP_SECRET_BASE32_FIELD)
|
||||
val secretBase64Field = getField(HMACOTP_SECRET_BASE64_FIELD)
|
||||
val secretCounterField = getField(HMACOTP_SECRET_COUNTER_FIELD)
|
||||
try {
|
||||
when {
|
||||
secretField != null -> otpElement.setUTF8Secret(secretField)
|
||||
@@ -382,16 +391,13 @@ object OtpEntryFields {
|
||||
secretBase64Field != null -> otpElement.setBase64Secret(secretBase64Field)
|
||||
else -> return false
|
||||
}
|
||||
|
||||
val secretCounterField = getField(HMACOTP_SECRET_COUNTER_FIELD)
|
||||
otpElement.type = OtpType.HOTP
|
||||
if (secretCounterField != null) {
|
||||
otpElement.counter = secretCounterField.toLongOrNull() ?: HOTP_INITIAL_COUNTER
|
||||
}
|
||||
} catch (exception: Exception) {
|
||||
return false
|
||||
}
|
||||
|
||||
otpElement.type = OtpType.HOTP
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package com.kunzisoft.keepass.services
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.*
|
||||
import android.net.Uri
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
@@ -46,58 +45,46 @@ class AdvancedUnlockNotificationService : NotificationService() {
|
||||
return getString(R.string.advanced_unlock)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? {
|
||||
super.onBind(intent)
|
||||
return mActionTaskBinder
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
mTempCipherDao = ArrayList()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
override fun onBind(intent: Intent): IBinder {
|
||||
super.onBind(intent)
|
||||
|
||||
val deleteIntent = Intent(this, AdvancedUnlockNotificationService::class.java).apply {
|
||||
action = ACTION_REMOVE_KEYS
|
||||
}
|
||||
val pendingDeleteIntent = PendingIntent.getService(this, 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val pendingDeleteIntent = PendingIntent.getBroadcast(this,
|
||||
4577, Intent(REMOVE_ADVANCED_UNLOCK_KEY_ACTION), 0)
|
||||
val biometricUnlockEnabled = PreferencesUtil.isBiometricUnlockEnable(this)
|
||||
val notificationBuilder = buildNewNotification().apply {
|
||||
val notificationBuilder = buildNewNotification().apply {
|
||||
setSmallIcon(if (biometricUnlockEnabled) {
|
||||
R.drawable.notification_ic_fingerprint_unlock_24dp
|
||||
} else {
|
||||
R.drawable.notification_ic_device_unlock_24dp
|
||||
})
|
||||
intent?.let {
|
||||
setContentTitle(getString(R.string.advanced_unlock))
|
||||
}
|
||||
setContentTitle(getString(R.string.advanced_unlock))
|
||||
setContentText(getString(R.string.advanced_unlock_tap_delete))
|
||||
setContentIntent(pendingDeleteIntent)
|
||||
// Unfortunately swipe is disabled in lollipop+
|
||||
setDeleteIntent(pendingDeleteIntent)
|
||||
}
|
||||
|
||||
when (intent?.action) {
|
||||
ACTION_TIMEOUT -> {
|
||||
val notificationTimeoutMilliSecs = PreferencesUtil.getAdvancedUnlockTimeout(this)
|
||||
// Not necessarily a foreground service
|
||||
if (mTimerJob == null && notificationTimeoutMilliSecs != TimeoutHelper.NEVER) {
|
||||
defineTimerJob(notificationBuilder, notificationTimeoutMilliSecs) {
|
||||
stopSelf()
|
||||
}
|
||||
} else {
|
||||
startForeground(notificationId, notificationBuilder.build())
|
||||
}
|
||||
val notificationTimeoutMilliSecs = PreferencesUtil.getAdvancedUnlockTimeout(this)
|
||||
// Not necessarily a foreground service
|
||||
if (mTimerJob == null && notificationTimeoutMilliSecs != TimeoutHelper.NEVER) {
|
||||
defineTimerJob(notificationBuilder, notificationTimeoutMilliSecs) {
|
||||
sendBroadcast(Intent(REMOVE_ADVANCED_UNLOCK_KEY_ACTION))
|
||||
}
|
||||
ACTION_REMOVE_KEYS -> {
|
||||
stopSelf()
|
||||
}
|
||||
else -> {}
|
||||
} else {
|
||||
startForeground(notificationId, notificationBuilder.build())
|
||||
}
|
||||
|
||||
return START_STICKY
|
||||
return mActionTaskBinder
|
||||
}
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
mTempCipherDao = ArrayList()
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
stopSelf()
|
||||
return super.onUnbind(intent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
@@ -105,22 +92,32 @@ class AdvancedUnlockNotificationService : NotificationService() {
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CHANNEL_ADVANCED_UNLOCK_ID = "com.kunzisoft.keepass.notification.channel.unlock"
|
||||
|
||||
private const val ACTION_TIMEOUT = "ACTION_TIMEOUT"
|
||||
private const val ACTION_REMOVE_KEYS = "ACTION_REMOVE_KEYS"
|
||||
|
||||
fun startServiceForTimeout(context: Context) {
|
||||
if (PreferencesUtil.isTempAdvancedUnlockEnable(context)) {
|
||||
context.startService(Intent(context, AdvancedUnlockNotificationService::class.java).apply {
|
||||
action = ACTION_TIMEOUT
|
||||
})
|
||||
class AdvancedUnlockReceiver(var removeKeyAction: () -> Unit): BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
intent.action?.let {
|
||||
when (it) {
|
||||
REMOVE_ADVANCED_UNLOCK_KEY_ACTION -> {
|
||||
removeKeyAction.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun stopService(context: Context) {
|
||||
context.stopService(Intent(context, AdvancedUnlockNotificationService::class.java))
|
||||
companion object {
|
||||
private const val CHANNEL_ADVANCED_UNLOCK_ID = "com.kunzisoft.keepass.notification.channel.unlock"
|
||||
const val REMOVE_ADVANCED_UNLOCK_KEY_ACTION = "com.kunzisoft.keepass.REMOVE_ADVANCED_UNLOCK_KEY"
|
||||
|
||||
// Only one service connection
|
||||
fun bindService(context: Context, serviceConnection: ServiceConnection, flags: Int) {
|
||||
context.bindService(Intent(context,
|
||||
AdvancedUnlockNotificationService::class.java),
|
||||
serviceConnection,
|
||||
flags)
|
||||
}
|
||||
|
||||
fun unbindService(context: Context, serviceConnection: ServiceConnection) {
|
||||
context.unbindService(serviceConnection)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,6 @@ class ClipboardEntryNotificationService : LockNotificationService() {
|
||||
override val notificationId = 485
|
||||
private var mEntryInfo: EntryInfo? = null
|
||||
private var clipboardHelper: ClipboardHelper? = null
|
||||
private var mNotificationTimeoutMilliSecs: Long = 0
|
||||
|
||||
override fun retrieveChannelId(): String {
|
||||
return CHANNEL_CLIPBOARD_ID
|
||||
@@ -68,9 +67,6 @@ class ClipboardEntryNotificationService : LockNotificationService() {
|
||||
// Get entry info from intent
|
||||
mEntryInfo = intent?.getParcelableExtra(EXTRA_ENTRY_INFO)
|
||||
|
||||
//Get settings
|
||||
mNotificationTimeoutMilliSecs = PreferencesUtil.getClipboardTimeout(this)
|
||||
|
||||
when {
|
||||
intent == null -> Log.w(TAG, "null intent")
|
||||
ACTION_NEW_NOTIFICATION == intent.action -> {
|
||||
@@ -169,8 +165,10 @@ class ClipboardEntryNotificationService : LockNotificationService() {
|
||||
this, 0, cleanIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
builder.setDeleteIntent(cleanPendingIntent)
|
||||
|
||||
if (mNotificationTimeoutMilliSecs != NEVER) {
|
||||
defineTimerJob(builder, mNotificationTimeoutMilliSecs, {
|
||||
//Get settings
|
||||
val notificationTimeoutMilliSecs = PreferencesUtil.getClipboardTimeout(this)
|
||||
if (notificationTimeoutMilliSecs != NEVER) {
|
||||
defineTimerJob(builder, notificationTimeoutMilliSecs, {
|
||||
val newGeneratedValue = fieldToCopy.getGeneratedValue(mEntryInfo)
|
||||
// New auto generated value
|
||||
if (generatedValue != newGeneratedValue) {
|
||||
|
||||
@@ -200,10 +200,6 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
if (intentAction == null && !mDatabase.loaded) {
|
||||
stopSelf()
|
||||
}
|
||||
if (intentAction == ACTION_DATABASE_CLOSE) {
|
||||
// Send lock action
|
||||
sendBroadcast(Intent(LOCK_ACTION))
|
||||
}
|
||||
|
||||
val actionRunnable: ActionRunnable? = when (intentAction) {
|
||||
ACTION_DATABASE_CREATE_TASK -> buildDatabaseCreateActionTask(intent)
|
||||
@@ -378,10 +374,8 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
ReadOnlyHelper.putReadOnlyInIntent(this, mDatabase.isReadOnly)
|
||||
},
|
||||
PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val deleteIntent = Intent(this, DatabaseTaskNotificationService::class.java).apply {
|
||||
action = ACTION_DATABASE_CLOSE
|
||||
}
|
||||
val pendingDeleteIntent = PendingIntent.getService(this, 0, deleteIntent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||
val pendingDeleteIntent = PendingIntent.getBroadcast(this,
|
||||
4576, Intent(LOCK_ACTION), 0)
|
||||
// Add actions in notifications
|
||||
notificationBuilder.apply {
|
||||
setContentText(mDatabase.name + " (" + mDatabase.version + ")")
|
||||
@@ -877,7 +871,6 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
const val ACTION_DATABASE_UPDATE_PARALLELISM_TASK = "ACTION_DATABASE_UPDATE_PARALLELISM_TASK"
|
||||
const val ACTION_DATABASE_UPDATE_ITERATIONS_TASK = "ACTION_DATABASE_UPDATE_ITERATIONS_TASK"
|
||||
const val ACTION_DATABASE_SAVE = "ACTION_DATABASE_SAVE"
|
||||
const val ACTION_DATABASE_CLOSE = "ACTION_DATABASE_CLOSE"
|
||||
|
||||
const val DATABASE_TASK_TITLE_KEY = "DATABASE_TASK_TITLE_KEY"
|
||||
const val DATABASE_TASK_MESSAGE_KEY = "DATABASE_TASK_MESSAGE_KEY"
|
||||
|
||||
@@ -26,8 +26,9 @@ import com.kunzisoft.keepass.utils.unregisterLockReceiver
|
||||
|
||||
abstract class LockNotificationService : NotificationService() {
|
||||
|
||||
private var onStart: Boolean = false
|
||||
private var mLockReceiver: LockReceiver? = null
|
||||
private var mLockReceiver: LockReceiver = LockReceiver {
|
||||
actionOnLock()
|
||||
}
|
||||
|
||||
protected open fun actionOnLock() {
|
||||
// Stop the service in all cases
|
||||
@@ -36,30 +37,17 @@ abstract class LockNotificationService : NotificationService() {
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
|
||||
// Register a lock receiver to stop notification service when lock on keyboard is performed
|
||||
mLockReceiver = LockReceiver {
|
||||
if (onStart)
|
||||
actionOnLock()
|
||||
}
|
||||
registerLockReceiver(mLockReceiver)
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
onStart = true
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
notificationManager?.cancel(notificationId)
|
||||
|
||||
stopSelf()
|
||||
super.onTaskRemoved(rootIntent)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
||||
unregisterLockReceiver(mLockReceiver)
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
}
|
||||
@@ -80,17 +80,22 @@ abstract class NotificationService : Service() {
|
||||
actionEnd: () -> Unit) {
|
||||
mTimerJob?.cancel()
|
||||
mTimerJob = CoroutineScope(Dispatchers.Main).launch {
|
||||
val timeoutInSeconds = timeoutMilliseconds / 1000L
|
||||
for (currentTime in timeoutInSeconds downTo 0) {
|
||||
actionAfterASecond?.invoke()
|
||||
builder.setProgress(100,
|
||||
(currentTime * 100 / timeoutInSeconds).toInt(),
|
||||
false)
|
||||
startForeground(notificationId, builder.build())
|
||||
delay(1000)
|
||||
if (currentTime <= 0) {
|
||||
actionEnd()
|
||||
if (timeoutMilliseconds > 0) {
|
||||
val timeoutInSeconds = timeoutMilliseconds / 1000L
|
||||
for (currentTime in timeoutInSeconds downTo 0) {
|
||||
actionAfterASecond?.invoke()
|
||||
builder.setProgress(100,
|
||||
(currentTime * 100 / timeoutInSeconds).toInt(),
|
||||
false)
|
||||
startForeground(notificationId, builder.build())
|
||||
delay(1000)
|
||||
if (currentTime <= 0) {
|
||||
actionEnd()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// If timeout is 0, run action once
|
||||
actionEnd()
|
||||
}
|
||||
notificationManager?.cancel(notificationId)
|
||||
mTimerJob = null
|
||||
|
||||
@@ -45,7 +45,6 @@ import com.kunzisoft.keepass.app.database.FileDatabaseHistoryAction
|
||||
import com.kunzisoft.keepass.biometric.AdvancedUnlockManager
|
||||
import com.kunzisoft.keepass.education.Education
|
||||
import com.kunzisoft.keepass.icons.IconPackChooser
|
||||
import com.kunzisoft.keepass.services.AdvancedUnlockNotificationService
|
||||
import com.kunzisoft.keepass.settings.preference.IconPackListPreference
|
||||
import com.kunzisoft.keepass.settings.preferencedialogfragment.DurationDialogFragmentCompat
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
@@ -374,7 +373,6 @@ class NestedAppSettingsFragment : NestedSettingsFragment() {
|
||||
}
|
||||
})
|
||||
}
|
||||
AdvancedUnlockNotificationService.stopService(activity.applicationContext)
|
||||
CipherDatabaseAction.getInstance(activity.applicationContext).deleteAll()
|
||||
}
|
||||
.setNegativeButton(resources.getString(android.R.string.cancel)
|
||||
|
||||
@@ -147,8 +147,10 @@ object PreferencesUtil {
|
||||
|
||||
fun setStyle(context: Context, styleString: String) {
|
||||
var tempThemeString = styleString
|
||||
if (tempThemeString in BuildConfig.STYLES_DISABLED) {
|
||||
tempThemeString = Stylish.defaultStyle(context)
|
||||
if (BuildConfig.CLOSED_STORE || !Education.isEducationScreenReclickedPerformed(context)) {
|
||||
if (tempThemeString in BuildConfig.STYLES_DISABLED) {
|
||||
tempThemeString = Stylish.defaultStyle(context)
|
||||
}
|
||||
}
|
||||
// Store light style to show selection in array list
|
||||
tempThemeString = Stylish.retrieveEquivalentLightStyle(context, tempThemeString)
|
||||
|
||||
@@ -42,8 +42,12 @@ object ParcelableUtil {
|
||||
val size = parcel.readInt()
|
||||
val map = HashMap<K, V>(size)
|
||||
for (i in 0 until size) {
|
||||
val key: K? = kClass.cast(parcel.readParcelable(kClass.classLoader))
|
||||
val value: V? = vClass.cast(parcel.readParcelable(vClass.classLoader))
|
||||
val key: K? = try {
|
||||
parcel.readParcelable(kClass.classLoader)
|
||||
} catch (e: Exception) { null }
|
||||
val value: V? = try {
|
||||
parcel.readParcelable(vClass.classLoader)
|
||||
} catch (e: Exception) { null }
|
||||
if (key != null && value != null)
|
||||
map[key] = value
|
||||
}
|
||||
@@ -52,7 +56,7 @@ object ParcelableUtil {
|
||||
|
||||
// For writing map with string key to a Parcel
|
||||
fun <V : Parcelable> writeStringParcelableMap(
|
||||
parcel: Parcel, flags: Int, map: LinkedHashMap<String, V>) {
|
||||
parcel: Parcel, flags: Int, map: HashMap<String, V>) {
|
||||
parcel.writeInt(map.size)
|
||||
for ((key, value) in map) {
|
||||
parcel.writeString(key)
|
||||
@@ -76,7 +80,9 @@ object ParcelableUtil {
|
||||
val map = LinkedHashMap<String, V>(size)
|
||||
for (i in 0 until size) {
|
||||
val key: String? = parcel.readString()
|
||||
val value: V? = vClass.cast(parcel.readParcelable(vClass.classLoader))
|
||||
val value: V? = try {
|
||||
parcel.readParcelable(vClass.classLoader)
|
||||
} catch (e: Exception) { null }
|
||||
if (key != null && value != null)
|
||||
map[key] = value
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.kunzisoft.keepass.utils
|
||||
|
||||
import java.text.Normalizer
|
||||
|
||||
object StringUtil {
|
||||
|
||||
fun String.removeLineChars(): String {
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.utils
|
||||
|
||||
import com.kunzisoft.keepass.database.file.DatabaseHeaderKDBX
|
||||
|
||||
class UnsignedInt(private var unsignedValue: Int) {
|
||||
|
||||
constructor(unsignedValue: UnsignedInt) : this(unsignedValue.toKotlinInt())
|
||||
|
||||
101
app/src/main/java/com/kunzisoft/keepass/utils/UuidUtil.java
Normal file
101
app/src/main/java/com/kunzisoft/keepass/utils/UuidUtil.java
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright 2019 Jeremy Jamet / Kunzisoft.
|
||||
*
|
||||
* This file is part of KeePassDX.
|
||||
*
|
||||
* KeePassDX is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* KeePassDX is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package com.kunzisoft.keepass.utils;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static com.kunzisoft.keepass.utils.StreamBytesUtilsKt.uuidTo16Bytes;
|
||||
|
||||
public class UuidUtil {
|
||||
|
||||
public static @Nullable String toHexString(@Nullable UUID uuid) {
|
||||
if (uuid == null) { return null; }
|
||||
try {
|
||||
byte[] buf = uuidTo16Bytes(uuid);
|
||||
|
||||
int len = buf.length;
|
||||
if (len == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
short bt;
|
||||
char high, low;
|
||||
for (byte b : buf) {
|
||||
bt = (short) (b & 0xFF);
|
||||
high = (char) (bt >>> 4);
|
||||
low = (char) (bt & 0x0F);
|
||||
sb.append(byteToChar(high));
|
||||
sb.append(byteToChar(low));
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static @Nullable UUID fromHexString(@Nullable String hexString) {
|
||||
if (hexString == null)
|
||||
return null;
|
||||
|
||||
if (hexString.length() != 32)
|
||||
return null;
|
||||
|
||||
char[] charArray = hexString.toLowerCase().toCharArray();
|
||||
char[] leastSignificantChars = new char[16];
|
||||
char[] mostSignificantChars = new char[16];
|
||||
|
||||
for (int i = 31; i >= 0; i = i-2) {
|
||||
if (i >= 16) {
|
||||
mostSignificantChars[32-i] = charArray[i];
|
||||
mostSignificantChars[31-i] = charArray[i-1];
|
||||
} else {
|
||||
leastSignificantChars[16-i] = charArray[i];
|
||||
leastSignificantChars[15-i] = charArray[i-1];
|
||||
}
|
||||
}
|
||||
StringBuilder standardUUIDString = new StringBuilder();
|
||||
standardUUIDString.append(leastSignificantChars);
|
||||
standardUUIDString.append(mostSignificantChars);
|
||||
standardUUIDString.insert(8, '-');
|
||||
standardUUIDString.insert(13, '-');
|
||||
standardUUIDString.insert(18, '-');
|
||||
standardUUIDString.insert(23, '-');
|
||||
try {
|
||||
return UUID.fromString(standardUUIDString.toString());
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Use short to represent unsigned byte
|
||||
private static char byteToChar(char bt) {
|
||||
if (bt >= 10) {
|
||||
return (char)('A' + bt - 10);
|
||||
}
|
||||
else {
|
||||
return (char)('0' + bt);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ import com.kunzisoft.keepass.database.element.Database
|
||||
import com.kunzisoft.keepass.database.element.DateInstant
|
||||
import com.kunzisoft.keepass.database.element.Entry
|
||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||
import com.kunzisoft.keepass.database.search.UuidUtil
|
||||
import com.kunzisoft.keepass.utils.UuidUtil
|
||||
import com.kunzisoft.keepass.model.EntryAttachmentState
|
||||
import com.kunzisoft.keepass.model.StreamDirection
|
||||
import com.kunzisoft.keepass.otp.OtpElement
|
||||
|
||||
@@ -46,10 +46,10 @@ class EntryField @JvmOverloads constructor(context: Context,
|
||||
|
||||
var hiddenProtectedValue: Boolean
|
||||
get() {
|
||||
return showButtonView.isSelected
|
||||
return !showButtonView.isSelected
|
||||
}
|
||||
set(value) {
|
||||
showButtonView.isSelected = !value
|
||||
showButtonView.isSelected = value
|
||||
changeProtectedValueParameters()
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ class EntryField @JvmOverloads constructor(context: Context,
|
||||
} else {
|
||||
setTextIsSelectable(true)
|
||||
}
|
||||
applyHiddenStyle(isProtected && !showButtonView.isSelected)
|
||||
applyHiddenStyle(isProtected && showButtonView.isSelected)
|
||||
if (!isProtected) linkify()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,17 +17,42 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with KeePassDX. If not, see <http://www.gnu.org/licenses/>.
|
||||
-->
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/icon_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="80dp"
|
||||
android:background="@drawable/background_item_selection">
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/icon_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/icon_name"
|
||||
android:layout_gravity="center"
|
||||
android:layout_margin="16dp">
|
||||
</androidx.appcompat.widget.AppCompatImageView>
|
||||
</FrameLayout>
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginRight="16dp" />
|
||||
<androidx.appcompat.widget.AppCompatTextView
|
||||
android:id="@+id/icon_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@+id/icon_image"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:maxLines="3"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingRight="4dp" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
@@ -137,7 +137,6 @@
|
||||
<string name="error_string_key">يجب أن يكون لكل سلسلة اسم حقل.</string>
|
||||
<string name="error_wrong_length">أدخل عددًا صحيحًا موجبًا في حقل «الطول».</string>
|
||||
<string name="error_autofill_enable_service">تعذر تمكين خدمة الملء التلقائي.</string>
|
||||
<string name="error_move_folder_in_itself">لا يمكن نقل مجموعة إلى نفسها.</string>
|
||||
<string name="file_not_found_content">تعذر إيجاد الملف. جرِّب فتحه من متصفح ملفات.</string>
|
||||
<string name="file_browser">مدير الملفات</string>
|
||||
<string name="invalid_credentials">تعذر قراءة الإعتمادات.</string>
|
||||
|
||||
@@ -85,7 +85,6 @@
|
||||
<string name="error_copy_group_here">Ovde ne možete kopirati grupu.</string>
|
||||
<string name="error_copy_entry_here">Ovde ne možete kopirati unos.</string>
|
||||
<string name="error_move_entry_here">Ovde ne možete premestiti unos.</string>
|
||||
<string name="error_move_folder_in_itself">Ne možete premestiti grupu u samu sebe.</string>
|
||||
<string name="error_autofill_enable_service">Nije moguće omogućiti uslugu automatskog popunjavanja.</string>
|
||||
<string name="error_wrong_length">Unesite pozitivan ceo broj u polje \"Dužina\".</string>
|
||||
<string name="error_label_exists">Ova oznaka već postoji.</string>
|
||||
|
||||
@@ -22,4 +22,11 @@
|
||||
<string name="clipboard_error_title">ক্লিপবোর্ড ত্রুটি</string>
|
||||
<string name="allow">অনুমোদন</string>
|
||||
<string name="file_manager_install_description">ACTION_CREATE_DOCUMENT এবং ACTION_OPEN_DOCUMENT অভিপ্রায় গ্রহণ করে এমন একটি ফাইল ম্যানেজার ডাটাবেস ফাইলগুলো তৈরি করা, খোলা এবং সংরক্ষণ করতে প্রয়োজন।</string>
|
||||
<string name="digits">ডিজিট</string>
|
||||
<string name="database">তথ্যভিত্তি</string>
|
||||
<string name="content_description_remove_from_list">সরাও</string>
|
||||
<string name="content_description_update_from_list">হালনাগাদ</string>
|
||||
<string name="discard">বাতিল</string>
|
||||
<string name="validate">সত্যায়ন</string>
|
||||
<string name="content_description_background">পটভূমি</string>
|
||||
</resources>
|
||||
@@ -288,6 +288,5 @@
|
||||
<string name="error_string_type">Aquest text no coincideix amb l\'element sol·licitat.</string>
|
||||
<string name="error_otp_type">L\'OTP existent no està reconegut per aquest formulari, la seva validació ja no pot generar correctament el token.</string>
|
||||
<string name="error_create_database_file">No s\'ha pogut crear una base de dades amb aquesta contrasenya i arxiu de clau.</string>
|
||||
<string name="error_move_folder_in_itself">No pots moure un grup dintre d\'ell mateix.</string>
|
||||
<string name="error_autofill_enable_service">No s\'ha pogut habilitar el servei d\'autocompletat.</string>
|
||||
</resources>
|
||||
@@ -24,7 +24,7 @@
|
||||
<string name="add_entry">Přidat záznam</string>
|
||||
<string name="add_group">Přidat skupinu</string>
|
||||
<string name="encryption_algorithm">Šifrovací algoritmus</string>
|
||||
<string name="app_timeout">Časový limit</string>
|
||||
<string name="app_timeout">Časový limit překročen</string>
|
||||
<string name="app_timeout_summary">Doba nečinnosti, po které se aplikace zamkne</string>
|
||||
<string name="application">Aplikace</string>
|
||||
<string name="menu_app_settings">Nastavení aplikace</string>
|
||||
@@ -146,7 +146,6 @@
|
||||
<string name="error_load_database">Databázi se nepodařilo načíst.</string>
|
||||
<string name="error_load_database_KDF_memory">Klíč se nepodařilo načíst, zkuste snížit \"využití paměti\" pro KDF.</string>
|
||||
<string name="error_autofill_enable_service">Službu automatického vyplňování se nepodařilo zapnout.</string>
|
||||
<string name="error_move_folder_in_itself">Není možné přesunout skupinu do ní samotné.</string>
|
||||
<string name="file_not_found_content">Soubor nenalezen. Zkuste jej otevřít ze správce souborů.</string>
|
||||
<string name="list_entries_show_username_title">Zobrazit uživatelská jména</string>
|
||||
<string name="list_entries_show_username_summary">V seznamech záznamů zobrazit uživatelská jména</string>
|
||||
@@ -549,4 +548,19 @@
|
||||
<string name="error_upload_file">Během nahrávání souboru došlo k chybě.</string>
|
||||
<string name="error_file_to_big">Soubor, který se pokoušíte nahrát, je příliš velký.</string>
|
||||
<string name="content_description_otp_information">Info o jednorázovém hesle</string>
|
||||
<string name="properties">Vlastnosti</string>
|
||||
<string name="error_export_app_properties">Během exportu vlastností aplikace došlo k chybě</string>
|
||||
<string name="success_export_app_properties">Vlastnosti aplikace byly exportovány</string>
|
||||
<string name="error_import_app_properties">Během importu vlastností aplikace došlo k chybě</string>
|
||||
<string name="success_import_app_properties">Vlastnosti aplikace byly importovány</string>
|
||||
<string name="description_app_properties">Vlastnosti KeePassDX pro správu aplikačních nastavení</string>
|
||||
<string name="export_app_properties_summary">Pro export vlastností aplikace založte soubor</string>
|
||||
<string name="export_app_properties_title">Exportovat vlastnosti aplikace</string>
|
||||
<string name="import_app_properties_summary">Pro import vlastostí aplikace zvolte soubor</string>
|
||||
<string name="import_app_properties_title">Importovat vlastnosti aplikace</string>
|
||||
<string name="error_start_database_action">Během akce v databázi došlo k chybě.</string>
|
||||
<string name="error_remove_file">Při odstraňování dat soboru došlo k chybě.</string>
|
||||
<string name="error_duplicate_file">Datový soubor již existuje.</string>
|
||||
<string name="error_move_group_here">Sem skupinu přesunout nemůžete.</string>
|
||||
<string name="error_word_reserved">Toto slovo je rezervováno a nelze je použít.</string>
|
||||
</resources>
|
||||
@@ -24,7 +24,7 @@
|
||||
<string name="add_entry">Tilføj post</string>
|
||||
<string name="add_group">Tilføj gruppe</string>
|
||||
<string name="encryption_algorithm">Krypteringsalgoritme</string>
|
||||
<string name="app_timeout">Timeout</string>
|
||||
<string name="app_timeout">Tid udløbet</string>
|
||||
<string name="app_timeout_summary">Inaktiv tid, før databasen låses</string>
|
||||
<string name="application">Program</string>
|
||||
<string name="menu_app_settings">Indstillinger</string>
|
||||
@@ -145,7 +145,6 @@
|
||||
<string name="error_load_database">Databasen kunne ikke indlæses.</string>
|
||||
<string name="error_load_database_KDF_memory">Kunne ikke indlæse nøglen. Prøv at reducere KDF \"hukommelsesforbrug\".</string>
|
||||
<string name="error_autofill_enable_service">Kunne ikke aktivere autofyld tjenesten.</string>
|
||||
<string name="error_move_folder_in_itself">Kan ikke flytte en gruppe til sig selv.</string>
|
||||
<string name="file_not_found_content">Kunne ikke finde filen. Prøv at åbne den fra filhåndtering.</string>
|
||||
<string name="list_entries_show_username_title">Vis brugernavne</string>
|
||||
<string name="list_entries_show_username_summary">Vis brugernavne i postlister</string>
|
||||
@@ -221,7 +220,7 @@
|
||||
<string name="database_description_title">Database beskrivelse</string>
|
||||
<string name="database_version_title">Databaseversion</string>
|
||||
<string name="text_appearance">Tekst</string>
|
||||
<string name="application_appearance">Program</string>
|
||||
<string name="application_appearance">Brugerflade</string>
|
||||
<string name="other">Øvrige</string>
|
||||
<string name="keyboard">Tastatur</string>
|
||||
<string name="magic_keyboard_title">Magikeyboard</string>
|
||||
@@ -253,9 +252,9 @@
|
||||
<string name="education_entry_new_field_summary">Registrer et ekstra felt, tilføj en værdi og beskyt det eventuelt.</string>
|
||||
<string name="education_unlock_title">Lås databasen op</string>
|
||||
<string name="education_read_only_title">Skrivebeskyt databasen</string>
|
||||
<string name="education_read_only_summary">Skift åbningstilstanden for sessionen.
|
||||
\n
|
||||
\nI \"skrivebeskyttet\" tilstand forhindres utilsigtede ændringer i databasen.
|
||||
<string name="education_read_only_summary">Skift åbningstilstanden for sessionen.
|
||||
\n
|
||||
\nI \"skrivebeskyttet\" tilstand forhindres utilsigtede ændringer i databasen.
|
||||
\nI \"skrivetilstand\" kan man tilføje, slette eller redigere alle elementerne, som det ønskes.</string>
|
||||
<string name="education_field_copy_title">Kopier et felt</string>
|
||||
<string name="education_field_copy_summary">Kopier nemt et felt og indsæt det hvor det ønskes
|
||||
@@ -270,14 +269,14 @@
|
||||
<string name="html_text_ad_free">I modsætning til andre programmer til adgangskodeadministration er denne <strong>annoncefri</strong>, <strong>copyleft fri software</strong>, og indsamler ikke personlige data, uanset hvilken version der bruges.</string>
|
||||
<string name="html_text_buy_pro">Ved at købe pro-versionen, er der adgang til <strong>visuel stil</strong>, og det vil især hjælpe <strong>gennemførelsen af lokale projekter.</strong></string>
|
||||
<string name="html_text_feature_generosity">Denne <strong>visuelle stil</strong> er tilgængelige takket være bidrag.</string>
|
||||
<string name="html_text_donation">For at bevare uafhængighed og altid at være aktiv, regner vi med <strong>bidrag.</strong></string>
|
||||
<string name="html_text_donation">For at bevare uafhængighed og altid at være aktiv, håber vi på dit <strong>bidrag.</strong></string>
|
||||
<string name="html_text_dev_feature">Funktionen er <strong>under udvikling</strong>, og det kræver <strong>bidrag</strong>, for snart at være tilgængelig.</string>
|
||||
<string name="html_text_dev_feature_buy_pro">Ved at købe <strong>pro</strong> versionen,</string>
|
||||
<string name="html_text_dev_feature_contibute">Ved at <strong>bidrage</strong>,</string>
|
||||
<string name="html_text_dev_feature_encourage">tilskyndes udviklerne til at lave <strong>nye funktioner</strong> og <strong>rette fejl</strong> i henhold bemærkninger.</string>
|
||||
<string name="html_text_dev_feature_thanks">Tak for bidrag.</string>
|
||||
<string name="html_text_dev_feature_work_hard">Vi arbejder hårdt på hurtigt at frigive denne funktion.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Glem ikke at holde appen opdateret ved at installere nye versioner.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Husk at holde din app opdateret ved at installere den nyeste version.</string>
|
||||
<string name="download">Hent</string>
|
||||
<string name="contribute">Bidrag</string>
|
||||
<string name="style_choose_title">Tema</string>
|
||||
@@ -341,7 +340,7 @@
|
||||
<string name="menu_advanced_unlock_settings">Avanceret oplåsning</string>
|
||||
<string name="biometric">Biometrisk</string>
|
||||
<string name="biometric_auto_open_prompt_title">Åbn automatisk biometrisk prompt</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Spørg automatisk efter biometri, hvis databasen er konfigureret til at bruge den</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Spørg automatisk efter biometrisk oplåsning, hvis databasen er konfigureret til at bruge det</string>
|
||||
<string name="enable">Aktiver</string>
|
||||
<string name="disable">Deaktiver</string>
|
||||
<string name="master_key">Hovednøgle</string>
|
||||
@@ -427,7 +426,7 @@
|
||||
<string name="hide_broken_locations_title">Skjule brudte databaselinks</string>
|
||||
<string name="hide_broken_locations_summary">Skjul brudte links på listen over seneste databaser</string>
|
||||
<string name="warning_database_read_only">Giv fil skriveadgang for at gemme databasændringer</string>
|
||||
<string name="education_setup_OTP_summary">Opsætning af engangsadgangskodestyring (HOTP / TOTP) for at generere et token anmodet om tofaktorautentisering (2FA).</string>
|
||||
<string name="education_setup_OTP_summary">Opsætning af engangs-adgangskode-styring (HOTP / TOTP) for at generere et token anmodet af tofaktor-autentisering (2FA).</string>
|
||||
<string name="education_setup_OTP_title">Opsætning af OTP</string>
|
||||
<string name="error_create_database">Databasefilen kunne ikke oprettes.</string>
|
||||
<string name="entry_add_attachment">Tilføj vedhæng</string>
|
||||
@@ -531,4 +530,35 @@
|
||||
<string name="error_rebuild_list">Listen kan ikke genopbygges korrekt.</string>
|
||||
<string name="error_database_uri_null">Database-URI kan ikke hentes.</string>
|
||||
<string name="content_description_otp_information">Oplysninger om engangsadgangskode</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Uddrag databasens legitimationsoplysninger med biometriske data</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Advarsel: hovedadgangskoden skal stadig huskes, hvis der bruges biometrisk genkendelse.</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Åbn biometriske forespørgsel for at låse databasen op</string>
|
||||
<string name="warning_database_revoked">Adgang til filen tilbagekaldt af filhåndteringsprogrammet, luk databasen og genåbn den fra dens placering.</string>
|
||||
<string name="error_start_database_action">Der opstod en fejl under udførelsen af en handling på databasen.</string>
|
||||
<string name="error_remove_file">Der opstod en fejl med at fjerne fildata.</string>
|
||||
<string name="error_otp_type">Den existerende OTP type kunne ikke genkendes, den kan være tiden er udløbet for at lave dette token.</string>
|
||||
<string name="education_advanced_unlock_summary">Sammenkæd din adgangskode, med din scannede biometriske oplysninger eller enheds legitimationsoplysninger for, hurtigt at låse din database op.</string>
|
||||
<string name="enter">Enter</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Varigheden af avanceret oplåsning, før indholdet slettes</string>
|
||||
<string name="device_credential_unlock_enable_summary">Giver dig mulighed for at bruge dine enhedsoplysninger for at åbne databasen</string>
|
||||
<string name="device_credential_unlock_enable_title">Oplåsning via enhedsoplysninger</string>
|
||||
<string name="autofill_inline_suggestions_summary">Forsøg på at vise forslag til automatisk udfyldning direkte fra et kompatibelt tastatur</string>
|
||||
<string name="autofill_inline_suggestions_title">Indbyggede forslag</string>
|
||||
<string name="backspace">Tilbagetast</string>
|
||||
<string name="advanced_unlock_timeout">Avanceret oplåsningstimeout</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Avanceret oplåsning udløbsdato</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Gem ikke krypteret indhold for at bruge avanceret oplåsning</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Midlertidig biometrisk oplåsning</string>
|
||||
<string name="device_credential">Enhedsoplysninger</string>
|
||||
<string name="properties">Egenskaber</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Åbn den biometrisk genkendelse for at gemme legitimationsoplysninger</string>
|
||||
<string name="error_export_app_properties">Fejl under eksport af app-egenskaber</string>
|
||||
<string name="success_export_app_properties">App-egenskaber eksporteret</string>
|
||||
<string name="error_import_app_properties">Fejl under importering af app-egenskaber</string>
|
||||
<string name="success_import_app_properties">App-egenskaber importeret</string>
|
||||
<string name="description_app_properties">KeePassDX-egenskaber til at administrere app-indstillinger</string>
|
||||
<string name="export_app_properties_summary">Opret en fil til at eksportere app-egenskaber</string>
|
||||
<string name="export_app_properties_title">Eksporter app-egenskaber</string>
|
||||
<string name="import_app_properties_summary">Vælg en fil for at importere app-egenskaber</string>
|
||||
<string name="import_app_properties_title">Importer appegenskaber</string>
|
||||
</resources>
|
||||
@@ -30,17 +30,17 @@
|
||||
<string name="add_entry">Eintrag hinzufügen</string>
|
||||
<string name="add_group">Gruppe hinzufügen</string>
|
||||
<string name="encryption_algorithm">Verschlüsselungsalgorithmus</string>
|
||||
<string name="app_timeout">App-Timeout</string>
|
||||
<string name="app_timeout_summary">Inaktivitätszeit vor dem Sperren der Datenbank</string>
|
||||
<string name="app_timeout">Inaktivitätszeit</string>
|
||||
<string name="app_timeout_summary">Zeit bis zum Sperren der Datenbank</string>
|
||||
<string name="application">App</string>
|
||||
<string name="menu_app_settings">App-Einstellungen</string>
|
||||
<string name="menu_app_settings">Einstellungen</string>
|
||||
<string name="brackets">Klammern</string>
|
||||
<string name="file_manager_install_description">Zum Erstellen, Öffnen und Speichern von Datenbankdateien wird ein Dateimanager benötigt, der die beabsichtigte Aktion ACTION_CREATE_DOCUMENT und ACTION_OPEN_DOCUMENT akzeptiert.</string>
|
||||
<string name="clipboard_cleared">Zwischenablage geleert</string>
|
||||
<string name="clipboard_error_title">Zwischenablage-Fehler</string>
|
||||
<string name="clipboard_error">Einige Geräte lassen keine Nutzung der Zwischenablage durch Apps zu.</string>
|
||||
<string name="clipboard_error_clear">Leeren der Zwischenablage fehlgeschlagen</string>
|
||||
<string name="clipboard_timeout">Zwischenablage-Timeout</string>
|
||||
<string name="clipboard_timeout">Zwischenablage-Inaktivitätszeit</string>
|
||||
<string name="clipboard_timeout_summary">Dauer der Speicherung in der Zwischenablage (falls vom Gerät unterstützt)</string>
|
||||
<string name="select_to_copy">%1$s in die Zwischenablage kopieren</string>
|
||||
<string name="retrieving_db_key">Datenbank-Schlüsseldatei erzeugen …</string>
|
||||
@@ -67,7 +67,7 @@
|
||||
<string name="entry_user_name">Benutzername</string>
|
||||
<string name="error_arc4">Die RC4/Arcfour-Stromverschlüsselung wird nicht unterstützt.</string>
|
||||
<string name="error_can_not_handle_uri">KeePassDX kann diese URI-Adresse nicht verarbeiten.</string>
|
||||
<string name="error_file_not_create">Konnte Datei nicht erstellen</string>
|
||||
<string name="error_file_not_create">Datei konnte nicht erstellt werden</string>
|
||||
<string name="error_invalid_db">Datenbank nicht lesbar.</string>
|
||||
<string name="error_invalid_path">Sicherstellen, dass der Pfad korrekt ist.</string>
|
||||
<string name="error_no_name">Namen eingeben.</string>
|
||||
@@ -83,7 +83,7 @@
|
||||
<string name="file_not_found_content">Datei nicht gefunden. Bitte versuchen, sie über den Dateimanager zu öffnen.</string>
|
||||
<string name="file_browser">Dateimanager</string>
|
||||
<string name="generate_password">Passwort generieren</string>
|
||||
<string name="hint_conf_pass">Passwort wiederholen</string>
|
||||
<string name="hint_conf_pass">Passwort bestätigen</string>
|
||||
<string name="hint_generated_password">Generiertes Passwort</string>
|
||||
<string name="hint_group_name">Name der Gruppe</string>
|
||||
<string name="hint_keyfile">Schlüsseldatei</string>
|
||||
@@ -104,22 +104,22 @@
|
||||
<string name="about">Über</string>
|
||||
<string name="menu_change_key_settings">Hauptschlüssel ändern</string>
|
||||
<string name="settings">Einstellungen</string>
|
||||
<string name="menu_database_settings">Datenbank-Einstellungen</string>
|
||||
<string name="menu_database_settings">Datenbankeinstellungen</string>
|
||||
<string name="menu_delete">Löschen</string>
|
||||
<string name="menu_donate">Spenden</string>
|
||||
<string name="menu_edit">Bearbeiten</string>
|
||||
<string name="menu_hide_password">Passwort verstecken</string>
|
||||
<string name="menu_lock">Datenbank sperren</string>
|
||||
<string name="menu_open">Öffnen</string>
|
||||
<string name="menu_search">Suchen</string>
|
||||
<string name="menu_search">Suche</string>
|
||||
<string name="menu_showpass">Passwort anzeigen</string>
|
||||
<string name="menu_url">URL öffnen</string>
|
||||
<string name="minus">Bindestrich</string>
|
||||
<string name="never">Nie</string>
|
||||
<string name="no_results">Keine Suchergebnisse</string>
|
||||
<string name="no_url_handler">Bitte einen Webbrowser installieren, um diese URL zu öffnen.</string>
|
||||
<string name="omit_backup_search_title">Recycle bin und Backup nicht durchsuchen</string>
|
||||
<string name="omit_backup_search_summary">Die Gruppen „Backup“ und „Recycle bin“ werden bei der Suche nicht berücksichtigt</string>
|
||||
<string name="omit_backup_search_title">Papierkorb und Backup nicht durchsuchen</string>
|
||||
<string name="omit_backup_search_summary">Die Gruppen „Backup“ und „Papierkorb“ werden bei der Suche nicht berücksichtigt</string>
|
||||
<string name="auto_focus_search_title">Schnellsuche</string>
|
||||
<string name="auto_focus_search_summary">Beim Öffnen einer Datenbank eine Suche veranlassen</string>
|
||||
<string name="progress_create">Neue Datenbank anlegen …</string>
|
||||
@@ -167,7 +167,7 @@
|
||||
<string name="file_name">Dateiname</string>
|
||||
<string name="unavailable_feature_text">Dieses Feature konnte nicht gestartet werden.</string>
|
||||
<string name="biometric_unlock_enable_summary">Ermöglicht Ihre Biometrie zu scannen, um die Datenbank zu öffnen.</string>
|
||||
<string name="advanced_unlock">Erweiterte Entsperrung</string>
|
||||
<string name="advanced_unlock">Moderne Entsperrung</string>
|
||||
<string name="biometric_unlock_enable_title">Biometrische Entsperrung</string>
|
||||
<string name="lock">Sperren</string>
|
||||
<string name="list_password_generator_options_summary">Erlaubte Zeichen für Passwortgenerator festlegen</string>
|
||||
@@ -186,7 +186,7 @@
|
||||
<string name="encryption_explanation">Verschlüsselungsalgorithmus der Datenbank wird für sämtliche Daten verwendet.</string>
|
||||
<string name="kdf_explanation">Um den Schlüssel für den Verschlüsselungsalgorithmus zu generieren, wird der Hauptschlüssel umgewandelt, wobei ein zufälliger Salt in der Schlüsselberechnung verwendet wird.</string>
|
||||
<string name="memory_usage">Speichernutzung</string>
|
||||
<string name="memory_usage_explanation">Größe des Speichers der für die Schlüsselableitung genutzt wird.</string>
|
||||
<string name="memory_usage_explanation">Größe des Speichers, der für die Schlüsselableitung genutzt wird.</string>
|
||||
<string name="parallelism">Parallelismus</string>
|
||||
<string name="parallelism_explanation">Grad des Parallelismus (d. h. Anzahl der Threads), der für die Schlüsselableitung genutzt wird.</string>
|
||||
<string name="sort_menu">Sortieren</string>
|
||||
@@ -202,14 +202,14 @@
|
||||
<string name="autofill_service_name">KeePassDX autom. Formularausfüllung</string>
|
||||
<string name="autofill_sign_in_prompt">Mit KeePassDX anmelden</string>
|
||||
<string name="set_autofill_service_title">Standarddienst für automatisches Ausfüllen festlegen</string>
|
||||
<string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um schnell Formulare in anderen Apps auszufüllen</string>
|
||||
<string name="autofill_explanation_summary">Automatisches Ausfüllen aktivieren, um Formulare schnell in anderen Apps auszufüllen</string>
|
||||
<string name="clipboard">Zwischenablage</string>
|
||||
<string name="biometric_delete_all_key_title">Verschlüsselungsschlüssel löschen</string>
|
||||
<string name="biometric_delete_all_key_summary">Lösche alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen</string>
|
||||
<string name="biometric_delete_all_key_summary">Alle Verschlüsselungsschlüssel löschen, die mit der modernen Entsperrerkennung zusammenhängen</string>
|
||||
<string name="unavailable_feature_version">Auf dem Gerät läuft Android %1$s, eine Version ab %2$s wäre aber nötig.</string>
|
||||
<string name="unavailable_feature_hardware">Keine entsprechende Hardware.</string>
|
||||
<string name="recycle_bin_title">Papierkorb-Nutzung</string>
|
||||
<string name="recycle_bin_summary">Verschiebt Gruppen oder Einträge in den Papierkorb, bevor sie gelöscht werden.</string>
|
||||
<string name="recycle_bin_summary">Verschiebt Gruppen oder Einträge in den Papierkorb, bevor sie gelöscht werden</string>
|
||||
<string name="monospace_font_fields_enable_title">Feldschriftart</string>
|
||||
<string name="monospace_font_fields_enable_summary">Schriftart in Feldern ändern, um Lesbarkeit zu verbessern</string>
|
||||
<string name="allow_copy_password_title">Zwischenablage vertrauen</string>
|
||||
@@ -218,8 +218,8 @@
|
||||
<string name="database_description_title">Datenbankbeschreibung</string>
|
||||
<string name="database_version_title">Datenbankversion</string>
|
||||
<string name="text_appearance">Text</string>
|
||||
<string name="application_appearance">Interface</string>
|
||||
<string name="other">Andere</string>
|
||||
<string name="application_appearance">Benutzeroberfläche</string>
|
||||
<string name="other">Sonstiges</string>
|
||||
<string name="keyboard">Tastatur</string>
|
||||
<string name="magic_keyboard_title">Magikeyboard</string>
|
||||
<string name="magic_keyboard_explanation_summary">Eine eigene Tastatur zum einfachen Ausfüllen aller Passwort- und Identitätsfelder aktivieren</string>
|
||||
@@ -268,7 +268,6 @@
|
||||
<string name="contribute">Unterstützen</string>
|
||||
<string name="icon_pack_choose_title">Symbolpaket</string>
|
||||
<string name="icon_pack_choose_summary">In der App verwendetes Symbolpaket</string>
|
||||
<string name="error_move_folder_in_itself">Eine Gruppe kann nicht in sich selbst verschoben werden.</string>
|
||||
<string name="menu_copy">Kopieren</string>
|
||||
<string name="menu_move">Verschieben</string>
|
||||
<string name="menu_paste">Einfügen</string>
|
||||
@@ -298,8 +297,8 @@
|
||||
<string name="keyboard_label">Magikeyboard (KeePassDX)</string>
|
||||
<string name="keyboard_setting_label">Magikeyboard-Einstellungen</string>
|
||||
<string name="keyboard_entry_category">Eintrag</string>
|
||||
<string name="keyboard_entry_timeout_title">Timeout</string>
|
||||
<string name="keyboard_entry_timeout_summary">Timeout zum Löschen der Tastatureingabe</string>
|
||||
<string name="keyboard_entry_timeout_title">Inaktivitätszeit</string>
|
||||
<string name="keyboard_entry_timeout_summary">Zeit bis zum Löschen der Tastatureingabe</string>
|
||||
<string name="keyboard_notification_entry_title">Benachrichtigung</string>
|
||||
<string name="keyboard_notification_entry_summary">Benachrichtigung anzeigen, wenn ein Eintrag abrufbar ist</string>
|
||||
<string name="keyboard_notification_entry_content_title_text">Eintrag</string>
|
||||
@@ -354,12 +353,12 @@
|
||||
<string name="content_description_update_from_list">Aktualisieren</string>
|
||||
<string name="content_description_keyboard_close_fields">Felder schließen</string>
|
||||
<string name="error_create_database_file">Es ist nicht möglich, eine Datenbank mit diesem Passwort und dieser Schlüsseldatei zu erstellen.</string>
|
||||
<string name="menu_advanced_unlock_settings">Erweitertes Entsperren</string>
|
||||
<string name="menu_advanced_unlock_settings">Modernes Entsperren</string>
|
||||
<string name="biometric">Biometrisch</string>
|
||||
<string name="enable">Aktivieren</string>
|
||||
<string name="disable">Deaktivieren</string>
|
||||
<string name="biometric_auto_open_prompt_title">Abfrage automatisch öffnen</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automatisch nach der erweiterten Entsperrung fragen, wenn die Datenbank dafür eingerichtet ist</string>
|
||||
<string name="biometric_auto_open_prompt_summary">Automatisch moderne Entsperrung abfragen, wenn die Datenbank dafür eingerichtet ist</string>
|
||||
<string name="master_key">Hauptschlüssel</string>
|
||||
<string name="security">Sicherheit</string>
|
||||
<string name="entry_history">Verlauf</string>
|
||||
@@ -373,20 +372,20 @@
|
||||
<string name="entry_otp">OTP</string>
|
||||
<string name="error_invalid_OTP">Ungültiges OTP-Geheimnis.</string>
|
||||
<string name="error_disallow_no_credentials">Mindestens eine Anmeldeinformation muss festgelegt sein.</string>
|
||||
<string name="error_copy_group_here">Eine Gruppe kann nicht hierher kopiert werden.</string>
|
||||
<string name="error_copy_group_here">Hierher kann keine Gruppe kopiert werden.</string>
|
||||
<string name="error_otp_secret_key">Geheimschlüssel muss im Base32-Format vorliegen.</string>
|
||||
<string name="error_otp_counter">Zähler muss zwischen %1$d und %2$d eingestellt sein.</string>
|
||||
<string name="error_otp_period">Zeitraum muss zwischen %1$d und %2$d Sekunden liegen.</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="creating_database">Datenbank wird erstellt …</string>
|
||||
<string name="menu_security_settings">Sicherheits-Einstellungen</string>
|
||||
<string name="menu_master_key_settings">Hauptschlüssel-Einstellungen</string>
|
||||
<string name="menu_security_settings">Sicherheitseinstellungen</string>
|
||||
<string name="menu_master_key_settings">Hauptschlüsseleinstellungen</string>
|
||||
<string name="contains_duplicate_uuid">Die Datenbank enthält UUID-Duplikate.</string>
|
||||
<string name="contains_duplicate_uuid_procedure">Problem lösen, indem neue UUIDs für Duplikate generiert werden und danach fortfahren\?</string>
|
||||
<string name="database_opened">Datenbank geöffnet</string>
|
||||
<string name="clipboard_explanation_summary">Eintragsfelder mithilfe der Zwischenablage des Geräts kopieren</string>
|
||||
<string name="advanced_unlock_explanation_summary">Erweitertes Entsperren verwenden, um eine Datenbank einfacher zu öffnen.</string>
|
||||
<string name="advanced_unlock_explanation_summary">Modernes Entsperren verwenden, um eine Datenbank einfacher zu öffnen.</string>
|
||||
<string name="database_data_compression_title">Datenkompression</string>
|
||||
<string name="database_data_compression_summary">Datenkompression reduziert die Datenbankgröße</string>
|
||||
<string name="max_history_items_title">Maximale Anzahl</string>
|
||||
@@ -399,8 +398,8 @@
|
||||
<string name="settings_database_force_changing_master_key_summary">Ändern des Hauptschlüssels erforderlich (Tage)</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_title">Erneuerung beim nächsten Mal erzwingen</string>
|
||||
<string name="settings_database_force_changing_master_key_next_time_summary">Änderung des Hauptschlüssels beim nächsten Mal erfordern (einmalig)</string>
|
||||
<string name="database_default_username_title">Vorgegebener Benutzername</string>
|
||||
<string name="database_custom_color_title">Benutzerdefinierte Datenbankfarbe</string>
|
||||
<string name="database_default_username_title">Standard-Benutzername</string>
|
||||
<string name="database_custom_color_title">Eigene Datenbankfarbe</string>
|
||||
<string name="compression">Kompression</string>
|
||||
<string name="compression_none">Keine</string>
|
||||
<string name="compression_gzip">Gzip</string>
|
||||
@@ -427,7 +426,7 @@
|
||||
<string name="hide_expired_entries_title">Abgelaufene Einträge ausblenden</string>
|
||||
<string name="hide_expired_entries_summary">Abgelaufene Einträge werden nicht angezeigt</string>
|
||||
<string name="style_choose_title">App-Design</string>
|
||||
<string name="style_choose_summary">Design, das in der App genutzt wird</string>
|
||||
<string name="style_choose_summary">In der App verwendetes Design</string>
|
||||
<string-array name="list_style_names">
|
||||
<item>Wald</item>
|
||||
<item>Göttlich</item>
|
||||
@@ -444,7 +443,7 @@
|
||||
<string name="discard">Verwerfen</string>
|
||||
<string name="discard_changes">Änderungen verwerfen\?</string>
|
||||
<string name="validate">Validieren</string>
|
||||
<string name="autofill_auto_search_summary">Automatisch Suchergebnisse nach Web-Domain oder Anwendungs-ID vorschlagen</string>
|
||||
<string name="autofill_auto_search_summary">Suchergebnisse automatisch nach Web-Domain oder Anwendungs-ID vorschlagen</string>
|
||||
<string name="autofill_auto_search_title">Automatische Suche</string>
|
||||
<string name="lock_database_show_button_summary">Zeigt die Sperrtaste in der Benutzeroberfläche an</string>
|
||||
<string name="lock_database_show_button_title">Sperrtaste anzeigen</string>
|
||||
@@ -459,7 +458,7 @@
|
||||
<string name="autofill_web_domain_blocklist_title">Liste gesperrter Web-Domains</string>
|
||||
<string name="autofill_application_id_blocklist_summary">Liste der Apps, in denen ein automatisches Ausfüllen verhindert wird</string>
|
||||
<string name="autofill_application_id_blocklist_title">Liste gesperrter Anwendungen</string>
|
||||
<string name="subdomain_search_summary">Suche Web-Domains mit Subdomain-Beschränkungen</string>
|
||||
<string name="subdomain_search_summary">Web-Domains mit Subdomain-Beschränkungen durchsuchen</string>
|
||||
<string name="subdomain_search_title">Subdomain-Suche</string>
|
||||
<string name="error_string_type">Dieser Text stimmt nicht mit dem angeforderten Element überein.</string>
|
||||
<string name="content_description_add_item">Element hinzufügen</string>
|
||||
@@ -476,7 +475,7 @@
|
||||
<string name="warning_empty_keyfile">Es wird nicht empfohlen, eine leere Schlüsseldatei hinzuzufügen.</string>
|
||||
<string name="warning_empty_keyfile_explanation">Der Inhalt der Key-Datei sollte nie geändert werden und im besten Fall zufällig generierte Daten enthalten.</string>
|
||||
<string name="warning_sure_remove_data">Sollen diese Daten trotzdem entfernt werden\?</string>
|
||||
<string name="warning_remove_unlinked_attachment">Das Entfernen ungekoppelter Daten könnte die Größe Ihrer Datenbank reduzieren, jedoch auch Daten, die von KeePass-Plugins genutzt werden, entfernen.</string>
|
||||
<string name="warning_remove_unlinked_attachment">Das Entfernen nicht verknüpfter Daten kann die Größe Ihrer Datenbank reduzieren, allerdings auch Daten löschen, die von KeePass-Plugins genutzt werden.</string>
|
||||
<string name="warning_replace_file">Das Hochladen dieser Datei wird die bereits Existierende ersetzen.</string>
|
||||
<string name="warning_file_too_big">Eine KeePass-Datenbank sollte nur kleine Dienstprogrammdateien beinhalten (zum Beispiel PGP-Schlüsseldateien).
|
||||
\n
|
||||
@@ -498,46 +497,80 @@
|
||||
<string name="save_mode">Speichermodus</string>
|
||||
<string name="search_mode">Suchmodus</string>
|
||||
<string name="error_registration_read_only">Das Speichern eines neuen Elements in einer schreibgeschützten Datenbank ist nicht zulässig</string>
|
||||
<string name="autofill_save_search_info_summary">Versuche bei manueller Eingabeauswahl gemeinsam genutzte Informationen zu speichern</string>
|
||||
<string name="autofill_ask_to_save_data_summary">Speichern von Daten anfordern, wenn ein Formular überprüft wird</string>
|
||||
<string name="autofill_ask_to_save_data_title">Speichern von Daten anfordern</string>
|
||||
<string name="autofill_save_search_info_summary">Suchdaten bei manueller Auswahl einer Eingabe wenn möglich speichern</string>
|
||||
<string name="autofill_ask_to_save_data_summary">Nachfragen, ob die Daten gespeichert werden sollen, wenn ein Formular ausgefüllt ist</string>
|
||||
<string name="autofill_ask_to_save_data_title">Speichern von Daten abfragen</string>
|
||||
<string name="autofill_save_search_info_title">Suchinformationen speichern</string>
|
||||
<string name="autofill_close_database_summary">Datenbank nach der Auswahl des automatischen Ausfüllens schließen</string>
|
||||
<string name="autofill_close_database_summary">Datenbank nach Auswahl eines Eintrags zum automatischen Ausfüllen schließen</string>
|
||||
<string name="keyboard_save_search_info_summary">Versuche bei manueller Eingabeauswahl gemeinsam genutzte Informationen zu speichern</string>
|
||||
<string name="keyboard_save_search_info_title">Gemeinsam genutzte Informationen speichern</string>
|
||||
<string name="warning_empty_recycle_bin">Sollen alle ausgewählten Knoten wirklich aus dem Papierkorb gelöscht werden\?</string>
|
||||
<string name="error_field_name_already_exists">Der Feldname existiert bereits.</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Warnung: Sie müssen sich immer noch an ihr Masterpasswort erinnern, wenn sie die erweiterte Entsperrerkennung verwenden.</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Öffne den erweiterten Entsperrdialog zum Speichern von Anmeldeinformationen</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Öffnen des erweiterten Entsperrdialogs zum Entsperren der Datenbank</string>
|
||||
<string name="menu_keystore_remove_key">Löschen des Schlüssels zum erweiterten Entsperren</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_title">Erweiterte Entsperrerkennung</string>
|
||||
<string name="education_advanced_unlock_summary">Ihr Passwort verbinden mit Ihrem gescannten biometrischen oder berechtigen Gerät um schnell Ihre Datenbank zu entsperren.</string>
|
||||
<string name="education_advanced_unlock_title">Erweiterte Entsperrung der Datenbank</string>
|
||||
<string name="advanced_unlock_timeout">Verfallzeit der erweiterten Entsperrung</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Dauer der erweiterten Entsperrung bevor sein Inhalt gelöscht wird</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Verfall der erweiterten Entsperrung</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Keinen verschlüsselten Inhalt speichern, um erweiterte Entsperrung zu benutzen</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Temporäre erweiterte Entsperrung</string>
|
||||
<string name="device_credential_unlock_enable_summary">Erlaubt Ihn die Geräteanmeldedaten zum Öffnen der Datenbank zu verwenden</string>
|
||||
<string name="advanced_unlock_tap_delete">Drücken um erweiterte Entsperrschlüssel zu löschen</string>
|
||||
<string name="advanced_unlock_prompt_store_credential_message">Warnung: Sie müssen sich immer noch an Ihr Masterpasswort erinnern, wenn Sie die moderne Entsperrerkennung verwenden.</string>
|
||||
<string name="open_advanced_unlock_prompt_store_credential">Zum Speichern der Anmeldeinformationen Dialog zum modernen Entsperren öffnen</string>
|
||||
<string name="open_advanced_unlock_prompt_unlock_database">Dialog zum modernen Entsperren der Datenbank öffnen</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="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">Moderne Entsperrung der Datenbank</string>
|
||||
<string name="advanced_unlock_timeout">Verfallzeit der modernen Entsperrung</string>
|
||||
<string name="temp_advanced_unlock_timeout_summary">Laufzeit der modernen Entsperrung bevor ihr Inhalt gelöscht wird</string>
|
||||
<string name="temp_advanced_unlock_timeout_title">Verfall der modernen Entsperrung</string>
|
||||
<string name="temp_advanced_unlock_enable_summary">Keine zur modernen Entsperrung verwendeten, verschlüsselten Inhalte speichern</string>
|
||||
<string name="temp_advanced_unlock_enable_title">Befristete moderne Entsperrung</string>
|
||||
<string name="device_credential_unlock_enable_summary">Ermöglicht das Öffnen der Datenbank mit Ihren Geräte-Anmeldeinformationen</string>
|
||||
<string name="advanced_unlock_tap_delete">Drücken, um Schlüssel für modernes Entsperren zu löschen</string>
|
||||
<string name="content">Inhalt</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Öffne Datenbank mit erweiterter Entsperrerkennung</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_title">Datenbank mit moderner Entsperrerkennung öffnen</string>
|
||||
<string name="enter">Eingabetaste</string>
|
||||
<string name="backspace">Rücktaste</string>
|
||||
<string name="select_entry">Wähle Eintrag</string>
|
||||
<string name="back_to_previous_keyboard">Zurück zur vorherigen Tastatur</string>
|
||||
<string name="custom_fields">Benutzerdefinierte Felder</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Alle Verschlüsselungsschlüssel, die mit der erweiterten Entsperrerkennung zusammenhängen, löschen\?</string>
|
||||
<string name="advanced_unlock_delete_all_key_warning">Alle Verschlüsselungsschlüssel, die mit der modernen Entsperrerkennung zusammenhängen, löschen\?</string>
|
||||
<string name="device_credential_unlock_enable_title">Geräteanmeldedaten entsperren</string>
|
||||
<string name="device_credential">Geräteanmeldedaten</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Geben sie das Passwort ein, und dann klicken sie den \"Erweitertes Entsperren\" Knopf.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Initialisieren des erweitertes Entsperren Dialogs fehlgeschlagen.</string>
|
||||
<string name="advanced_unlock_scanning_error">Erweitertes Entsperren Fehler: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Konnte den Abdruck des erweiterten Entsperrens nicht erkennen</string>
|
||||
<string name="advanced_unlock_invalid_key">Kann den Schlüssel zum erweiterten Entsperren nicht lesen. Bitte löschen sie ihn und wiederholen sie Prozedur zum Erkennen des Entsperrens.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Extrahiere Datenbankanmeldedaten mit Daten aus erweitertem Entsperren</string>
|
||||
<string name="credential_before_click_advanced_unlock_button">Geben Sie das Passwort ein und klicken Sie dann auf diesen Knopf.</string>
|
||||
<string name="advanced_unlock_prompt_not_initialized">Dialog für modernes Entsperren konnte nicht gestartet werden.</string>
|
||||
<string name="advanced_unlock_scanning_error">Fehler beim modernen Entsperren: %1$s</string>
|
||||
<string name="advanced_unlock_not_recognized">Abdruck zum modernen Entsperren nicht erkannt</string>
|
||||
<string name="advanced_unlock_invalid_key">Schlüssel zum modernen Entsperren nicht lesbar. Bitte löschen Sie ihn und wiederholen Sie die Prozedur zur Entsperrerkennung.</string>
|
||||
<string name="advanced_unlock_prompt_extract_credential_message">Datenbankanmeldedaten mit Daten aus moderner Entsperrung extrahieren</string>
|
||||
<string name="error_rebuild_list">Die Liste kann nicht ordnungsgemäß neu erstellt werden.</string>
|
||||
<string name="error_database_uri_null">Datenbank-URI kann nicht abgerufen werden.</string>
|
||||
<string name="menu_reload_database">Datenbank neu laden</string>
|
||||
<string name="warning_database_info_changed_options">Überschreiben Sie die externen Änderungen, indem Sie die Datenbank speichern, oder laden Sie sie mit den neuesten Änderungen neu.</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="error_remove_file">Beim Löschen der Datei trat ein Fehler auf.</string>
|
||||
<string name="error_duplicate_file">Die Datei existiert bereits.</string>
|
||||
<string name="error_upload_file">Beim Hochladen der Datei trat ein Fehler auf.</string>
|
||||
<string name="import_app_properties_title">App-Eigenschaften importieren</string>
|
||||
<string name="error_start_database_action">Beim Ausführen einer Aktion in der Datenbank ist ein Fehler aufgetreten.</string>
|
||||
<string name="error_otp_type">Der vorhandene Einmalpassworttyp wird von diesem Formular nicht erkannt, seine Validierung erzeugt das Token möglicherweise nicht mehr korrekt.</string>
|
||||
<string name="content_description_otp_information">Informationen zu Einmalpasswörtern</string>
|
||||
<string name="warning_database_revoked">Auf die Datei kann nicht zugegriffen werden. Schließen Sie die Datenbank und öffnen Sie die Datei erneut.</string>
|
||||
<string name="error_export_app_properties">Fehler beim Exportieren der App-Eigenschaften</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="success_import_app_properties">App-Eigenschaften importiert</string>
|
||||
<string name="export_app_properties_summary">Datei zum Exportieren von App-Eigenschaften erstellen</string>
|
||||
<string name="export_app_properties_title">App-Eigenschaften exportieren</string>
|
||||
<string name="import_app_properties_summary">Datei zum Importieren von App-Eigenschaften auswählen</string>
|
||||
<string name="error_move_group_here">Sie können hier keine Gruppe verschieben.</string>
|
||||
<string name="autofill_inline_suggestions_title">Ausfüllvorschläge</string>
|
||||
<string name="error_word_reserved">Dieses Wort ist reserviert und kann nicht verwendet werden.</string>
|
||||
<string name="icon_section_custom">Benutzerdefiniert</string>
|
||||
<string name="icon_section_standard">Standard</string>
|
||||
<string name="style_brightness_summary">Helles oder dunkles Design auswählen</string>
|
||||
<string name="style_brightness_title">Designhelligkeit</string>
|
||||
<string name="unit_gibibyte">GiB</string>
|
||||
<string name="unit_mebibyte">MiB</string>
|
||||
<string name="unit_kibibyte">KiB</string>
|
||||
<string name="unit_byte">B</string>
|
||||
<string name="download_canceled">Abgebrochen!</string>
|
||||
<string name="autofill_inline_suggestions_keyboard">Vorschläge für automatisches Ausfüllen hinzugefügt.</string>
|
||||
<string name="autofill_inline_suggestions_summary">Wenn möglich unmittelbar Vorschläge zum automatischen Ausfüllen auf einer kompatiblen Tastatur anzeigen</string>
|
||||
<string name="properties">Eigenschaften</string>
|
||||
<string name="description_app_properties">KeePassDX-Eigenschaften zur Verwaltung der App-Einstellungen</string>
|
||||
</resources>
|
||||
@@ -24,7 +24,7 @@
|
||||
<string name="add_entry">Προσθήκη καταχώρησης</string>
|
||||
<string name="add_group">Προσθήκη ομάδας</string>
|
||||
<string name="encryption_algorithm">Αλγόριθμος κρυπτογράφησης</string>
|
||||
<string name="app_timeout">Χρονικό όριο εφαρμογής</string>
|
||||
<string name="app_timeout">Χρονικό όριο</string>
|
||||
<string name="app_timeout_summary">Χρόνος αδράνειας πριν από το κλείδωμα της βάσης δεδομένων</string>
|
||||
<string name="application">Εφαρμογή</string>
|
||||
<string name="menu_app_settings">Ρυθμίσεις εφαρμογής</string>
|
||||
@@ -251,7 +251,6 @@
|
||||
<string name="style_choose_summary">Θέμα που χρησιμοποιείται στην εφαρμογή</string>
|
||||
<string name="icon_pack_choose_title">Πακέτο Εικονιδίων</string>
|
||||
<string name="icon_pack_choose_summary">Πακέτο εικονιδίων που χρησιμοποιείται στην εφαρμογή</string>
|
||||
<string name="error_move_folder_in_itself">Δεν μπορείτε να μετακινήσετε μια ομάδα μέσα στον εαυτό της.</string>
|
||||
<string name="menu_copy">Αντιγραφή</string>
|
||||
<string name="menu_move">Μετακίνηση</string>
|
||||
<string name="menu_paste">Επικόλληση</string>
|
||||
@@ -550,4 +549,17 @@
|
||||
<string name="content_description_otp_information">Πληροφορίες One-time κωδικού πρόσβασης</string>
|
||||
<string name="error_remove_file">Παρουσιάστηκε σφάλμα κατά την κατάργηση των δεδομένων αρχείου.</string>
|
||||
<string name="error_duplicate_file">Τα δεδομένα αρχείου υπάρχουν ήδη.</string>
|
||||
<string name="properties">Ιδιότητες</string>
|
||||
<string name="error_export_app_properties">Σφάλμα κατά την εξαγωγή ιδιοτήτων εφαρμογής</string>
|
||||
<string name="success_export_app_properties">Έγινε εξαγωγή ιδιοτήτων εφαρμογής</string>
|
||||
<string name="error_import_app_properties">Σφάλμα κατά την εισαγωγή ιδιοτήτων εφαρμογής</string>
|
||||
<string name="success_import_app_properties">Έγινε εισαγωγή ιδιοτήτων εφαρμογής</string>
|
||||
<string name="description_app_properties">Ιδιότητες KeePassDX για διαχείριση ρυθμίσεων εφαρμογής</string>
|
||||
<string name="export_app_properties_summary">Δημιουργήστε ένα αρχείο για εξαγωγή ιδιοτήτων εφαρμογής</string>
|
||||
<string name="export_app_properties_title">Εξαγωγή ιδιοτήτων εφαρμογής</string>
|
||||
<string name="import_app_properties_summary">Επιλέξτε ένα αρχείο για εισαγωγή ιδιοτήτων εφαρμογής</string>
|
||||
<string name="import_app_properties_title">Εισαγωγή ιδιοτήτων εφαρμογής</string>
|
||||
<string name="error_start_database_action">Παρουσιάστηκε σφάλμα κατά την εκτέλεση μιας ενέργειας στη βάση δεδομένων.</string>
|
||||
<string name="error_move_group_here">Δεν μπορείτε να μετακινήσετε μια ομάδα εδώ.</string>
|
||||
<string name="error_word_reserved">Αυτή η λέξη είναι δεσμευμένη και δεν μπορεί να χρησιμοποιηθεί.</string>
|
||||
</resources>
|
||||
@@ -267,7 +267,6 @@
|
||||
<string name="edit_entry">Editar entrada</string>
|
||||
<string name="error_load_database">No se pudo cargar la base de datos.</string>
|
||||
<string name="error_load_database_KDF_memory">No se pudo cargar la clave. Intente disminuir el uso de memoria de KDF.</string>
|
||||
<string name="error_move_folder_in_itself">No puede mover un grupo dentro de sí mismo.</string>
|
||||
<string name="list_entries_show_username_title">Enseña nombres de usuario</string>
|
||||
<string name="list_entries_show_username_summary">Enseña nombres de usuador en las listras de entradas</string>
|
||||
<string name="menu_copy">Copiar</string>
|
||||
|
||||
@@ -141,7 +141,6 @@
|
||||
<string name="error_copy_group_here">شما نمی توانید یک گروه را در اینجا کپی کنید.</string>
|
||||
<string name="error_copy_entry_here">شما نمی توانید یک ورودی را در اینجا کپی کنید.</string>
|
||||
<string name="error_move_entry_here">شما نمی توانید یک ورودی را به اینجا منتقل کنید.</string>
|
||||
<string name="error_move_folder_in_itself">شما نمی توانید یک گروه را به خود منتقل کنید.</string>
|
||||
<string name="error_autofill_enable_service">قادر به فعال کردن سرویس پر کردن خودکار نبود.</string>
|
||||
<string name="error_wrong_length">یک عدد کامل مثبت را در زمینه \"طول\" وارد کنید.</string>
|
||||
<string name="error_label_exists">این برچسب در حال حاضر وجود دارد.</string>
|
||||
|
||||
@@ -308,7 +308,6 @@
|
||||
<string name="error_copy_group_here">Et voi kopioida ryhmää tänne.</string>
|
||||
<string name="error_copy_entry_here">Et voi kopioida tietuetta tänne.</string>
|
||||
<string name="error_move_entry_here">Et voi siirtää tietuetta tänne.</string>
|
||||
<string name="error_move_folder_in_itself">Et voi siirtää ryhmää itsensä sisälle.</string>
|
||||
<string name="error_autofill_enable_service">Automaattista täyttöä ei voitu ottaa käyttöön.</string>
|
||||
<string name="content_description_node_children">Solmun lapset</string>
|
||||
</resources>
|
||||
@@ -26,7 +26,7 @@
|
||||
<string name="encryption">Chiffrement</string>
|
||||
<string name="encryption_algorithm">Algorithme de chiffrement</string>
|
||||
<string name="key_derivation_function">Fonction de dérivation de clé</string>
|
||||
<string name="app_timeout">Délai d’expiration de l’application</string>
|
||||
<string name="app_timeout">Délai d’expiration</string>
|
||||
<string name="app_timeout_summary">Durée d’inactivité avant le verrouillage de la base de données</string>
|
||||
<string name="application">Application</string>
|
||||
<string name="menu_app_settings">Paramètres de l’application</string>
|
||||
@@ -275,7 +275,6 @@
|
||||
</string-array>
|
||||
<string name="icon_pack_choose_title">Collection d’icônes</string>
|
||||
<string name="icon_pack_choose_summary">Collection d’icônes utilisées dans l’application</string>
|
||||
<string name="error_move_folder_in_itself">Vous ne pouvez pas déplacer un groupe dans lui-même.</string>
|
||||
<string name="menu_copy">Copier</string>
|
||||
<string name="menu_move">Déplacer</string>
|
||||
<string name="menu_paste">Coller</string>
|
||||
@@ -555,7 +554,20 @@
|
||||
<string name="style_brightness_title">Luminosité de thème</string>
|
||||
<string name="error_remove_file">Une erreur s\'est produite lors de la suppression des données du fichier.</string>
|
||||
<string name="error_duplicate_file">Les données du fichier existent déjà.</string>
|
||||
<string name="error_upload_file">Une erreur s\'est produite lors du téléchargement des données du fichier.</string>
|
||||
<string name="error_file_to_big">Le fichier que vous essayez de téléverser est trop gros.</string>
|
||||
<string name="error_upload_file">Une erreur est survenue lors du téléversement des données du fichier.</string>
|
||||
<string name="error_file_to_big">Le fichier que vous essayez de téléverser est trop volumineux.</string>
|
||||
<string name="content_description_otp_information">Information sur le mot de passe à usage unique</string>
|
||||
<string name="properties">Propriétés</string>
|
||||
<string name="error_export_app_properties">Erreur lors de l\'exportation des propriétés de l\'application</string>
|
||||
<string name="success_export_app_properties">Propriétés de l\'application exportées</string>
|
||||
<string name="error_import_app_properties">Erreur lors de l\'importation des propriétés de l\'application</string>
|
||||
<string name="success_import_app_properties">Propriétés de l\'application importées</string>
|
||||
<string name="description_app_properties">Propriétés KeePassDX pour gérer les paramètres de l\'application</string>
|
||||
<string name="export_app_properties_summary">Créer un fichier pour exporter les propriétés de l\'application</string>
|
||||
<string name="export_app_properties_title">Export des propriétés de l\'app</string>
|
||||
<string name="import_app_properties_summary">Sélectionner un fichier pour importer les propriétés de l\'application</string>
|
||||
<string name="import_app_properties_title">Importation des propriétés de l\'appli</string>
|
||||
<string name="error_start_database_action">Une erreur s\'est produite lors de l\'exécution d\'une action sur la base de données.</string>
|
||||
<string name="error_move_group_here">Vous ne pouvez pas déplacer un groupe ici.</string>
|
||||
<string name="error_word_reserved">Ce mot est réservé et ne peut pas être utilisé.</string>
|
||||
</resources>
|
||||
@@ -242,7 +242,6 @@
|
||||
<string name="error_disallow_no_credentials">Barem jedan skup podataka za prijavu mora biti postavljen.</string>
|
||||
<string name="error_string_key">Svaki niz mora imati ime polja.</string>
|
||||
<string name="error_autofill_enable_service">Nije moguće aktivirati uslugu automatskog ispunjavanja.</string>
|
||||
<string name="error_move_folder_in_itself">Nije moguće premjestiti grupu u samu sebe.</string>
|
||||
<string name="error_move_entry_here">Unos se ne može ovdje premijestiti.</string>
|
||||
<string name="error_copy_entry_here">Unos se ne može ovdje kopirati.</string>
|
||||
<string name="error_copy_group_here">Grupa se ne može ovjde kopirati.</string>
|
||||
@@ -368,7 +367,7 @@
|
||||
<string name="contribution">Doprinos</string>
|
||||
<string name="error_label_exists">Ova oznaka već postoji.</string>
|
||||
<string name="warning_database_read_only">Za spremanje promjena u bazi podataka, datoteci dozvoli pisanje</string>
|
||||
<string name="app_timeout">Istek vremena aplikacije</string>
|
||||
<string name="app_timeout">Istek vremena</string>
|
||||
<string name="content_description_repeat_toggle_password_visibility">Ponovo uklj/isklj vidljivosti lozinke</string>
|
||||
<string name="warning_password_encoding">Izbjegni u lozinkama koristiti znakove koji su izvan formata kodiranja teksta u datoteci baze podataka (neprepoznati znakovi pretvaraju se u isto slovo).</string>
|
||||
<string name="rounds_explanation">Dodatni prolazi šifriranja pružaju veću zaštitu od brutalnih napada, ali stvarno mogu usporiti učitavanje i spremanje.</string>
|
||||
@@ -545,4 +544,17 @@
|
||||
<string name="content_description_otp_information">Podaci jednokratne lozinke</string>
|
||||
<string name="error_remove_file">Tijekom uklanjanja podataka datoteke došlo je do greške.</string>
|
||||
<string name="error_duplicate_file">Podaci datoteke već postoje.</string>
|
||||
<string name="properties">Svojstva</string>
|
||||
<string name="error_export_app_properties">Greška tijekom izvoza svojstava aplikacije</string>
|
||||
<string name="success_export_app_properties">Svojstva aplikacije su izvezena</string>
|
||||
<string name="error_import_app_properties">Greška tijekom uvoza svojstava aplikacije</string>
|
||||
<string name="success_import_app_properties">Svojstva aplikacije su uvezena</string>
|
||||
<string name="description_app_properties">KeePassDX svojstva za upravljanje postavkama aplikacije</string>
|
||||
<string name="export_app_properties_summary">Stvori datoteku za izvoz svojstva aplikacije</string>
|
||||
<string name="export_app_properties_title">Izvezi 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="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>
|
||||
@@ -154,7 +154,6 @@
|
||||
<string name="error_load_database">Az adatbázis betöltése meghiúsult.</string>
|
||||
<string name="error_load_database_KDF_memory">A kulcs nem tölthető be. Próbálja meg csökkenteni a KDF „Memóriahasználatot”.</string>
|
||||
<string name="error_autofill_enable_service">Az automatikus kitöltési szolgáltatás nem engedélyezhető.</string>
|
||||
<string name="error_move_folder_in_itself">Nem helyezheti át a csoportot saját magába.</string>
|
||||
<string name="list_entries_show_username_title">Felhasználónevek megjelenítése</string>
|
||||
<string name="list_entries_show_username_summary">Felhasználónevek megjelenítése a bejegyzéslistákban</string>
|
||||
<string name="copy_field">%1$s másolata</string>
|
||||
|
||||
@@ -72,7 +72,6 @@
|
||||
<string name="error_copy_group_here">Anda tidak bisa menyalin grup di sini.</string>
|
||||
<string name="error_copy_entry_here">Anda tidak bisa menyalin entri di sini.</string>
|
||||
<string name="error_move_entry_here">Anda tidak bisa memindahkan entri ke sini.</string>
|
||||
<string name="error_move_folder_in_itself">Anda tidak bisa memindahkan grup ke grup itu sendiri.</string>
|
||||
<string name="error_autofill_enable_service">Tidak bisa mengaktifkan layanan IsiOtomatis.</string>
|
||||
<string name="error_wrong_length">Masukkan bilangan bulat di bidang \"Panjang\".</string>
|
||||
<string name="error_label_exists">Label ini sudah ada.</string>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<string name="add_entry">Aggiungi elemento</string>
|
||||
<string name="add_group">Aggiungi gruppo</string>
|
||||
<string name="encryption_algorithm">Algoritmo di cifratura</string>
|
||||
<string name="app_timeout">Scadenza app</string>
|
||||
<string name="app_timeout">Timeout</string>
|
||||
<string name="app_timeout_summary">Tempo di inattività prima del blocco del database</string>
|
||||
<string name="application">App</string>
|
||||
<string name="menu_app_settings">Impostazioni app</string>
|
||||
@@ -68,7 +68,7 @@
|
||||
<string name="error_invalid_path">Assicurati che il percorso sia corretto.</string>
|
||||
<string name="error_no_name">Inserisci un nome.</string>
|
||||
<string name="error_out_of_memory">Memoria insufficiente per caricare l\'intero database.</string>
|
||||
<string name="error_pass_gen_type">Deve essere selezionato almeno un tipo di generazione password.</string>
|
||||
<string name="error_pass_gen_type">Selezionare almeno un tipo di generazione della password.</string>
|
||||
<string name="error_pass_match">Le password non corrispondono.</string>
|
||||
<string name="error_rounds_too_large">«Livello» troppo alto. Impostato a 2147483648.</string>
|
||||
<string name="error_string_key">Ogni stringa deve avere un nome.</string>
|
||||
@@ -154,7 +154,6 @@
|
||||
<string name="extended_ASCII">ASCII esteso</string>
|
||||
<string name="error_nokeyfile">Seleziona un file chiave.</string>
|
||||
<string name="error_autofill_enable_service">Attivazione del servizio di auto-completamento fallita.</string>
|
||||
<string name="error_move_folder_in_itself">Non puoi spostare un gruppo in se stesso.</string>
|
||||
<string name="menu_form_filling_settings">Riempimento campi</string>
|
||||
<string name="menu_copy">Copia</string>
|
||||
<string name="menu_move">Sposta</string>
|
||||
@@ -535,7 +534,7 @@
|
||||
<string name="autofill_inline_suggestions_summary">Mostra i suggerimenti di riempimento campi in una tastiera compatibile</string>
|
||||
<string name="autofill_inline_suggestions_title">Suggerimenti in linea</string>
|
||||
<string name="warning_database_revoked">L\'accesso al file è stato revocato dal file manager, chiudi il database e riaprilo dalla sua posizione originale.</string>
|
||||
<string name="warning_database_info_changed_options">Sovrascrivi le modifiche esterne salvano il database o ricaricalo con gli ultimi cambiamenti.</string>
|
||||
<string name="warning_database_info_changed_options">Sovrascrivi le modifiche esterne salvando il database o ricaricalo con gli ultimi cambiamenti.</string>
|
||||
<string name="warning_database_info_changed">I dati nel tuo database sono stati modificati al di fuori di questa app.</string>
|
||||
<string name="menu_reload_database">Ricarica database</string>
|
||||
<string name="error_otp_type">Il tipo di OTP esistente non è riconosciuto da questo modulo, la sua convalida potrebbe non generare più correttamente il token.</string>
|
||||
@@ -553,4 +552,17 @@
|
||||
<string name="error_duplicate_file">Il file esiste già.</string>
|
||||
<string name="error_upload_file">Si è verificato un errore durante il caricamento del file.</string>
|
||||
<string name="error_file_to_big">Il file che stai cercando di caricare è troppo grande.</string>
|
||||
<string name="error_start_database_action">Si è verificato un errore durante l\'esecuzione di una azione sul database.</string>
|
||||
<string name="properties">Proprietà</string>
|
||||
<string name="error_export_app_properties">Errore durante l\'esportazione delle proprietà dell\'app</string>
|
||||
<string name="success_export_app_properties">Proprietà dell\'app esportate</string>
|
||||
<string name="error_import_app_properties">Errore durante l\'importazione delle proprietà dell\'app</string>
|
||||
<string name="success_import_app_properties">Proprietà dell\'app importate</string>
|
||||
<string name="description_app_properties">Proprietà di KeePassDX per gestire le impostazioni dell\'app</string>
|
||||
<string name="export_app_properties_summary">Crea un file in cui esportare le proprietà dell\'app</string>
|
||||
<string name="export_app_properties_title">Esporta 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="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>
|
||||
@@ -31,7 +31,7 @@
|
||||
<string name="encryption">暗号化</string>
|
||||
<string name="encryption_algorithm">暗号化アルゴリズム</string>
|
||||
<string name="key_derivation_function">鍵導出関数</string>
|
||||
<string name="app_timeout">アプリのタイムアウト</string>
|
||||
<string name="app_timeout">タイムアウト</string>
|
||||
<string name="app_timeout_summary">この期間アプリの操作がなかった場合、データベースをロックします</string>
|
||||
<string name="application">アプリ</string>
|
||||
<string name="brackets">かっこ</string>
|
||||
@@ -120,7 +120,6 @@
|
||||
<string name="error_label_exists">このラベルはすでに存在します。</string>
|
||||
<string name="error_wrong_length">[長さ] フィールドには正の整数を入力してください。</string>
|
||||
<string name="error_autofill_enable_service">自動入力サービスを有効にできませんでした。</string>
|
||||
<string name="error_move_folder_in_itself">グループを自分自身の中に移動することはできません。</string>
|
||||
<string name="error_move_entry_here">ここではエントリーを移動することはできません。</string>
|
||||
<string name="error_copy_entry_here">ここではエントリーをコピーすることはできません。</string>
|
||||
<string name="error_copy_group_here">ここではグループをコピーすることはできません。</string>
|
||||
@@ -243,7 +242,7 @@
|
||||
<string name="sort_title">タイトル</string>
|
||||
<string name="sort_username">ユーザー名</string>
|
||||
<string name="sort_creation_time">作成日</string>
|
||||
<string name="sort_last_modify_time">変更日</string>
|
||||
<string name="sort_last_modify_time">変更日時</string>
|
||||
<string name="sort_last_access_time">最終アクセス</string>
|
||||
<string name="special">特殊文字</string>
|
||||
<string name="search">検索</string>
|
||||
@@ -452,7 +451,7 @@
|
||||
<string name="education_donation_title">参加</string>
|
||||
<string name="education_donation_summary">安定性やセキュリティを向上させ、機能を追加することを支援します。</string>
|
||||
<string name="html_text_ad_free">多くのパスワード管理アプリとは異なり、このアプリは<strong>広告なし</strong>かつ<strong>コピーレフトの自由ソフトウェア</strong>です。どのバージョンを使っても、サーバー上で個人情報が収集されることはありません。</string>
|
||||
<string name="html_text_buy_pro">pro バージョンを購入すると、この<strong>ビジュアル スタイル</strong>にアクセスできるようになり、また<strong>コミュニティ プロジェクトの実現</strong>を特に支援できます。</string>
|
||||
<string name="html_text_buy_pro">Pro バージョンを購入すると、この<strong>ビジュアル スタイル</strong>にアクセスできるようになり、また<strong>コミュニティ プロジェクトの実現</strong>を特に支援できます。</string>
|
||||
<string name="html_text_feature_generosity">この<strong>ビジュアル スタイル</strong>はあなたの厚意により利用可能となります。</string>
|
||||
<string name="html_text_donation">自由を維持し活発に開発し続けるために、私たちはあなたの<strong>貢献</strong>に期待しています。</string>
|
||||
<string name="html_text_dev_feature">この機能は<strong>開発中</strong>であり、早期に提供するにはあなたの<strong>貢献</strong>が必要です。</string>
|
||||
@@ -542,11 +541,24 @@
|
||||
<string name="error_otp_type">既存の OTP の種類がこのフォームでは認識されていないため、フォームの検証によってトークンが正しく生成されなくなる可能性があります。</string>
|
||||
<string name="icon_section_custom">カスタム</string>
|
||||
<string name="icon_section_standard">標準</string>
|
||||
<string name="style_brightness_summary">ライトテーマとダークテーマのどちらかを選択します</string>
|
||||
<string name="style_brightness_summary">ライト / ダークテーマを選択します</string>
|
||||
<string name="style_brightness_title">テーマの明るさ</string>
|
||||
<string name="error_remove_file">ファイルデータの削除中にエラーが発生しました。</string>
|
||||
<string name="error_duplicate_file">ファイルデータはすでに存在します。</string>
|
||||
<string name="error_upload_file">ファイルデータのアップロード中にエラーが発生しました。</string>
|
||||
<string name="error_file_to_big">アップロードしようとしているファイルが大きすぎます。</string>
|
||||
<string name="content_description_otp_information">ワンタイムパスワードについて</string>
|
||||
<string name="import_app_properties_summary">アプリのプロパティをインポートするファイルを選択します</string>
|
||||
<string name="import_app_properties_title">アプリのプロパティをインポートする</string>
|
||||
<string name="properties">プロパティ</string>
|
||||
<string name="error_export_app_properties">アプリのプロパティのエクスポート時にエラーが発生しました</string>
|
||||
<string name="success_export_app_properties">アプリのプロパティをエクスポートしました</string>
|
||||
<string name="error_import_app_properties">アプリのプロパティのインポート時にエラーが発生しました</string>
|
||||
<string name="success_import_app_properties">アプリのプロパティをインポートしました</string>
|
||||
<string name="description_app_properties">アプリの設定を管理する KeePassDX のプロパティ</string>
|
||||
<string name="export_app_properties_summary">アプリのプロパティをエクスポートするファイルを作成します</string>
|
||||
<string name="export_app_properties_title">アプリのプロパティをエクスポートする</string>
|
||||
<string name="error_start_database_action">データベースに対するアクションの実行中にエラーが発生しました。</string>
|
||||
<string name="error_word_reserved">この単語は予約語のため使用できません。</string>
|
||||
<string name="error_move_group_here">グループをここに移動できません。</string>
|
||||
</resources>
|
||||
@@ -78,7 +78,6 @@
|
||||
<string name="error_string_key">각 항목은 필드 이름을 가져야 합니다.</string>
|
||||
<string name="error_wrong_length">\"길이\" 필드에는 양수를 입력하십시오.</string>
|
||||
<string name="error_autofill_enable_service">자동 채우기 서비스를 활성화할 수 없습니다.</string>
|
||||
<string name="error_move_folder_in_itself">그룹을 자신에게 옮길 수 없습니다.</string>
|
||||
<string name="field_name">필드 이름</string>
|
||||
<string name="field_value">필드 값</string>
|
||||
<string name="file_not_found_content">파일을 찾을 수 없습니다. 파일 탐색기에서 열리는지 확인해 주세요.</string>
|
||||
|
||||
@@ -128,7 +128,6 @@
|
||||
<string name="error_create_database_file">ഈ പാസ്വേഡും കീഫയലും ഉപയോഗിച്ച് ഡാറ്റാബേസ് സൃഷ്ടിക്കാൻ കഴിയില്ല.</string>
|
||||
<string name="error_copy_group_here">നിങ്ങൾക്ക് ഇവിടെ ഒരു ഗ്രൂപ്പ് പകർത്താൻ കഴിയില്ല.</string>
|
||||
<string name="error_copy_entry_here">നിങ്ങൾക്ക് ഇവിടെ ഒരു എൻട്രി പകർത്താൻ കഴിയില്ല.</string>
|
||||
<string name="error_move_folder_in_itself">നിങ്ങൾക്ക് ഒരു ഗ്രൂപ്പിനെ അതിലേക്ക് നീക്കാൻ കഴിയില്ല.</string>
|
||||
<string name="error_label_exists">ഈ ലേബൽ ഇതിനകം നിലവിലുണ്ട്.</string>
|
||||
<string name="error_string_key">ഓരോ സ്ട്രിംഗിനും ഒരു ഫീൽഡ് നാമം ഉണ്ടായിരിക്കണം.</string>
|
||||
<string name="error_rounds_too_large">\"Transformation rounds\" too high. Setting to 2147483648.</string>
|
||||
|
||||
@@ -78,7 +78,6 @@
|
||||
<string name="error_string_key">Hver streng må ha et feltnavn.</string>
|
||||
<string name="error_wrong_length">Skriv inn et positivt heltall i \"Lengde\" feltet.</string>
|
||||
<string name="error_autofill_enable_service">Autofyll-tjenesten kan ikke skrus på.</string>
|
||||
<string name="error_move_folder_in_itself">Kan ikke flytte gruppe inn i seg selv.</string>
|
||||
<string name="field_name">Feltnavn</string>
|
||||
<string name="field_value">Feltverdi</string>
|
||||
<string name="file_not_found_content">Fant ikke filen. Prøv å åpne den fra din innholdsleverandør.</string>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<string name="add_entry">Item toevoegen</string>
|
||||
<string name="add_group">Groep toevoegen</string>
|
||||
<string name="encryption_algorithm">Algoritme</string>
|
||||
<string name="app_timeout">App-time-out</string>
|
||||
<string name="app_timeout">Time-out</string>
|
||||
<string name="app_timeout_summary">Tijd tot vergrendeling bij inactiviteit</string>
|
||||
<string name="application">App</string>
|
||||
<string name="menu_app_settings">App-instellingen</string>
|
||||
@@ -143,7 +143,6 @@
|
||||
<string name="error_load_database_KDF_memory">De sleutel kan niet worden geladen. Probeer om het \"geheugengebruik\" van KDF te verminderen.</string>
|
||||
<string name="error_string_key">Elke zin moet een veldnaam bevatten.</string>
|
||||
<string name="error_autofill_enable_service">De dienst automatisch aanvullen kan niet worden ingeschakeld.</string>
|
||||
<string name="error_move_folder_in_itself">Een groep kan niet naar zichzelf worden verplaatst.</string>
|
||||
<string name="field_name">Veldnaam</string>
|
||||
<string name="field_value">Veldwaarde</string>
|
||||
<string name="file_not_found_content">Bestand niet gevonden. Probeer opnieuw te openen via bestandsbeheer.</string>
|
||||
@@ -552,4 +551,17 @@
|
||||
<string name="error_upload_file">Er is een fout opgetreden bij het uploaden van de bestandsgegevens.</string>
|
||||
<string name="error_file_to_big">Het bestand dat je probeert te uploaden, is te groot.</string>
|
||||
<string name="content_description_otp_information">Eenmalig wachtwoord-informatie</string>
|
||||
<string name="properties">Eigenschappen</string>
|
||||
<string name="error_export_app_properties">Fout tijdens het exporteren van app-eigenschappen</string>
|
||||
<string name="success_export_app_properties">App-eigenschappen geëxporteerd</string>
|
||||
<string name="error_import_app_properties">Fout tijdens het importeren van app-eigenschappen</string>
|
||||
<string name="success_import_app_properties">App-eigenschappen geïmporteerd</string>
|
||||
<string name="description_app_properties">KeePassDX-eigenschappen om app-instellingen te beheren</string>
|
||||
<string name="export_app_properties_summary">Maak een bestand om app-eigenschappen te exporteren</string>
|
||||
<string name="export_app_properties_title">App-eigenschappen exporteren</string>
|
||||
<string name="import_app_properties_summary">Selecteer een bestand om app-eigenschappen te importeren</string>
|
||||
<string name="import_app_properties_title">App-eigenschappen importeren</string>
|
||||
<string name="error_start_database_action">Er is een fout opgetreden bij het uitvoeren van een actie op de database.</string>
|
||||
<string name="error_move_group_here">Je kunt hier geen groep verplaatsen.</string>
|
||||
<string name="error_word_reserved">Dit woord is gereserveerd en kan niet worden gebruikt.</string>
|
||||
</resources>
|
||||
@@ -244,7 +244,6 @@
|
||||
<string name="error_create_database">ਡਾਟਾਬੇਸ ਫਾਈਲ ਬਣਾਉਣ ਲਈ ਅਸਮਰੱਥ।</string>
|
||||
<string name="error_copy_group_here">ਤੁਸੀਂ ਗਰੁੱਪ ਨੂੰ ਇੱਥੇ ਕਾਪੀ ਨਹੀਂ ਕਰ ਸਕਦੇ ਹੋ।</string>
|
||||
<string name="error_copy_entry_here">ਤੁਸੀੰ ਇਸ ਐੰਟਰੀ ਨੂੰ ਇੱਥੇ ਕਾਪੀ ਨਹੀਂ ਕਰ ਸਕਦੇ ਹੋ।</string>
|
||||
<string name="error_move_folder_in_itself">ਤੁਸੀੰ ਗਰੁੱਪ ਨੂੰ ਖੁਦ ਵਿੱਚ ਨਹੀਂ ਭੇਜ ਸਕਦੇ ਹੋ।</string>
|
||||
<string name="list_password_generator_options_title">ਪਾਸਵਰਡ ਅੱਖਰ</string>
|
||||
<string name="password_size_summary">ਤਿਆਰ ਕੀਤੇ ਪਾਸਵਰਡਾਂ ਲਈ ਮੂਲ ਆਕਾਰ ਸੈੱਟ ਕਰਦਾ ਹੈ</string>
|
||||
<string name="password_size_title">ਤਿਆਰ ਕੀਤੇ ਪਾਸਵਰਡ ਦਾ ਆਕਾਰ</string>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<string name="add_entry">Dodaj wpis</string>
|
||||
<string name="add_group">Dodaj grupę</string>
|
||||
<string name="encryption_algorithm">Algorytm szyfrowania</string>
|
||||
<string name="app_timeout">Limit czasu aplikacji</string>
|
||||
<string name="app_timeout">Koniec czasu</string>
|
||||
<string name="app_timeout_summary">Czas bezczynności przed zablokowaniem bazy danych</string>
|
||||
<string name="application">Aplikacja</string>
|
||||
<string name="menu_app_settings">Ustawienia aplikacji</string>
|
||||
@@ -140,7 +140,6 @@
|
||||
<string name="error_load_database_KDF_memory">Nie można załadować klucza. Spróbuj zmniejszyć użycie pamięć KDF.</string>
|
||||
<string name="error_string_key">Każdy ciąg musi mieć nazwę pola.</string>
|
||||
<string name="error_autofill_enable_service">Nie można włączyć usługi autouzupełniania.</string>
|
||||
<string name="error_move_folder_in_itself">Nie można przenieść grupy do samej siebie.</string>
|
||||
<string name="field_name">Nazwa pola</string>
|
||||
<string name="field_value">Wartość pola</string>
|
||||
<string name="file_not_found_content">Nie znaleziono pliku. Spróbuj ponownie otworzyć go w przeglądarce plików.</string>
|
||||
@@ -550,4 +549,17 @@
|
||||
<string name="content_description_otp_information">Informacje o hasłach jednorazowych</string>
|
||||
<string name="error_remove_file">Wystąpił błąd podczas usuwania danych z pliku.</string>
|
||||
<string name="error_duplicate_file">Dane pliku już istnieją.</string>
|
||||
<string name="properties">Właściwości</string>
|
||||
<string name="error_export_app_properties">Błąd podczas eksportowania właściwości aplikacji</string>
|
||||
<string name="success_export_app_properties">Wyeksportowano właściwości aplikacji</string>
|
||||
<string name="error_import_app_properties">Błąd podczas importowania właściwości aplikacji</string>
|
||||
<string name="success_import_app_properties">Zaimportowano właściwości aplikacji</string>
|
||||
<string name="description_app_properties">Właściwości KeePassDX do zarządzania ustawieniami aplikacji</string>
|
||||
<string name="export_app_properties_summary">Utwórz plik, aby wyeksportować właściwości aplikacji</string>
|
||||
<string name="export_app_properties_title">Eksportuj 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="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>
|
||||
@@ -138,7 +138,6 @@
|
||||
<string name="entry_not_found">Não pôde encontrar dado de entrada.</string>
|
||||
<string name="error_string_key">Um nome do campo é necessário para cada string.</string>
|
||||
<string name="error_autofill_enable_service">Não pôde ser habilitado o serviço de preenchimento automático.</string>
|
||||
<string name="error_move_folder_in_itself">Você não pode mover um grupo para dentro de si mesmo.</string>
|
||||
<string name="field_name">Nome do campo</string>
|
||||
<string name="field_value">Valor do campo</string>
|
||||
<string name="file_not_found_content">Arquivo não encontrado. Tente reabri-lo de seu buscador de arquivos.</string>
|
||||
@@ -237,7 +236,7 @@
|
||||
<string name="education_create_database_title">Crie um arquivo de banco de dados</string>
|
||||
<string name="education_create_database_summary">Crie seu primeiro arquivo de gerenciamento de senhas.</string>
|
||||
<string name="education_select_database_title">Abra um banco de dados existente</string>
|
||||
<string name="education_select_database_summary">Abra seu banco de dados de mais cedo pelo seu navegador de arquivos.</string>
|
||||
<string name="education_select_database_summary">Abra seu arquivo de banco de dados existente a partir do navegador de arquivos.</string>
|
||||
<string name="education_new_node_title">Adicione itens ao seu banco</string>
|
||||
<string name="education_new_node_summary">Entradas ajudam a gerenciar suas identidades digitais.
|
||||
\n
|
||||
|
||||
@@ -148,7 +148,6 @@
|
||||
<string name="allow">Permitir</string>
|
||||
<string name="error_load_database">Não foi possível abrir a sua base de dados.</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_move_folder_in_itself">Não pode mover um grupo para si mesmo.</string>
|
||||
<string name="list_entries_show_username_title">Mostrar nomes de utilizador</string>
|
||||
<string name="copy_field">Cópia de %1$s</string>
|
||||
<string name="menu_form_filling_settings">Preenchimento de formulário</string>
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<string name="menu_paste">Colar</string>
|
||||
<string name="menu_move">Mover</string>
|
||||
<string name="menu_copy">Copiar</string>
|
||||
<string name="error_move_folder_in_itself">Não pode mover um grupo para si mesmo.</string>
|
||||
<string name="clipboard_cleared">Área de transferência limpa</string>
|
||||
<string name="about_description">Uma implementação do gestor de palavras-chave KeePass para Android</string>
|
||||
<string name="icon_pack_choose_summary">Pacote de ícones usado na app</string>
|
||||
@@ -178,7 +177,7 @@
|
||||
<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_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_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>
|
||||
@@ -319,7 +318,7 @@
|
||||
<string name="error_otp_period">O período deve estar entre %1$d e %2$d segundos.</string>
|
||||
<string name="error_otp_counter">O contador deve estar entre %1$d e %2$d.</string>
|
||||
<string name="error_otp_secret_key">A chave secreta deve estar em formato Base32.</string>
|
||||
<string name="error_copy_group_here">Mão pode copiar um grupo aqui.</string>
|
||||
<string name="error_copy_group_here">Não pode copiar um grupo aqui.</string>
|
||||
<string name="error_disallow_no_credentials">Ao menos uma credencial deve ser definida.</string>
|
||||
<string name="error_invalid_OTP">Segredo OTP inválido.</string>
|
||||
<string name="entry_otp">OTP</string>
|
||||
@@ -401,7 +400,7 @@
|
||||
<string name="build_label">Compilação %1$s</string>
|
||||
<string name="retrieving_db_key">A criar a chave da base de dados…</string>
|
||||
<string name="clipboard">Área de transferência</string>
|
||||
<string name="list_entries_show_username_summary">Mostrar nomes de utilizador em listas de entrada</string>
|
||||
<string name="list_entries_show_username_summary">Mostrar nomes de utilizador na lista entradas</string>
|
||||
<string name="list_entries_show_username_title">Mostrar nomes de utilizador</string>
|
||||
<string name="error_load_database_KDF_memory">Não foi possível carregar a chave. Tente descarregar o \"Uso de Memória\" do KDF.</string>
|
||||
<string name="error_load_database">Não foi possível abrir a sua base de dados.</string>
|
||||
@@ -442,7 +441,7 @@
|
||||
<string name="clipboard_error">Alguns aparelhos não deixam as apps usarem a área de transferência.</string>
|
||||
<string name="clipboard_error_title">Erro na área de transferência</string>
|
||||
<string name="allow">Permitir</string>
|
||||
<string name="extended_ASCII">ASCII Estendido</string>
|
||||
<string name="extended_ASCII">ASCII Extendido</string>
|
||||
<string name="brackets">Parênteses</string>
|
||||
<string name="application">App</string>
|
||||
<string name="app_timeout_summary">Inatividade antes de bloquear a base de dados</string>
|
||||
@@ -453,4 +452,30 @@
|
||||
<string name="add_group">Adicionar grupo</string>
|
||||
<string name="add_entry">Adicionar entrada</string>
|
||||
<string name="accept">Aceitar</string>
|
||||
<string name="device_credential">Credencial do 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>
|
||||
@@ -111,7 +111,6 @@
|
||||
<string name="error_string_key">Fiecare șir trebuie să aibă un nume de câmp.</string>
|
||||
<string name="error_wrong_length">Introduceți un număr întreg pozitiv în câmpul \"Lungime\".</string>
|
||||
<string name="error_autofill_enable_service">Nu s-a putut activa serviciul de completare automată.</string>
|
||||
<string name="error_move_folder_in_itself">Nu puteți muta un grup în sine.</string>
|
||||
<string name="error_move_entry_here">Nu puteți muta o intrare aici.</string>
|
||||
<string name="error_copy_entry_here">Nu puteți copia o intrare aici.</string>
|
||||
<string name="error_copy_group_here">Nu puteți copia un grup aici.</string>
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
<string name="encryption">Шифрование</string>
|
||||
<string name="key_derivation_function">Функция формирования ключа</string>
|
||||
<string name="extended_ASCII">Расширенный набор ASCII</string>
|
||||
<string name="error_autofill_enable_service">Сервис автозаполнения не может быть включён.</string>
|
||||
<string name="error_autofill_enable_service">Служба автозаполнения не может быть включена.</string>
|
||||
<string name="copy_field">%1$s скопировано</string>
|
||||
<string name="menu_form_filling_settings">Заполнение форм</string>
|
||||
<string name="encryption_explanation">Алгоритм шифрования базы для всех данных.</string>
|
||||
@@ -168,7 +168,6 @@
|
||||
<string name="allow">Разрешить</string>
|
||||
<string name="error_load_database">Невозможно загрузить базу.</string>
|
||||
<string name="error_load_database_KDF_memory">Невозможно загрузить ключ. Попробуйте уменьшить размер памяти, используемой функцией формирования ключа (KDF).</string>
|
||||
<string name="error_move_folder_in_itself">Нельзя переместить группу в саму себя.</string>
|
||||
<string name="list_entries_show_username_title">Показывать имя</string>
|
||||
<string name="list_entries_show_username_summary">Показывать имя пользователя в списке записей</string>
|
||||
<string name="menu_copy">Копировать</string>
|
||||
@@ -186,10 +185,10 @@
|
||||
<string name="menu_appearance_settings">Внешний вид</string>
|
||||
<string name="general">Общие</string>
|
||||
<string name="autofill">Автозаполнение</string>
|
||||
<string name="autofill_service_name">Сервис автозаполнения KeePassDX</string>
|
||||
<string name="autofill_service_name">Служба автозаполнения KeePassDX</string>
|
||||
<string name="autofill_sign_in_prompt">Войти с помощью KeePassDX</string>
|
||||
<string name="autofill_explanation_summary">Включить сервис для быстрого заполнения форм в других приложениях</string>
|
||||
<string name="set_autofill_service_title">Сервис автозаполнения по умолчанию</string>
|
||||
<string name="autofill_explanation_summary">Включите службу для быстрого заполнения форм в других приложениях</string>
|
||||
<string name="set_autofill_service_title">Использовать службу автозаполнения</string>
|
||||
<string name="password_size_title">Длина создаваемого пароля</string>
|
||||
<string name="password_size_summary">Настройка длины создаваемых паролей по умолчанию</string>
|
||||
<string name="list_password_generator_options_title">Символы пароля</string>
|
||||
@@ -273,12 +272,12 @@
|
||||
<string name="html_text_ad_free">В отличие от многих приложений управления паролями, это <strong>без рекламы</strong> и <strong>свободно от лицензирования</strong>. Оно не собирает ваши личные данные на своих серверах независимо от того, какую версию вы используете.</string>
|
||||
<string name="html_text_buy_pro">При покупке Pro-версии вы будете иметь доступ к этим <strong>визуальным стилям</strong> и особенно поможете <strong>реализации общественных проектов</strong>.</string>
|
||||
<string name="html_text_feature_generosity">Эти <strong>визуальные стили</strong> доступны благодаря вашей щедрости.</string>
|
||||
<string name="html_text_donation">Для того, чтобы сохранить нашу независимость и быть всегда активными, мы рассчитываем на ваше <strong>содействие</strong>.</string>
|
||||
<string name="html_text_donation">Для того, чтобы сохранить нашу независимость и быть всегда активными, мы рассчитываем на вашу <strong>поддержку</strong>.</string>
|
||||
<string name="html_text_dev_feature">Эта функция находится <strong>в разработке</strong> и требует вашего <strong>участия</strong>, чтобы стать доступной в ближайшее время.</string>
|
||||
<string name="html_text_dev_feature_buy_pro">Покупая <strong>Pro</strong>-версию,</string>
|
||||
<string name="html_text_dev_feature_contibute"><strong>Участвуя в проекте</strong>,</string>
|
||||
<string name="html_text_dev_feature_encourage">вы поощряете разработчиков добавлять <strong>новые возможности</strong> и <strong>исправлять ошибки</strong> в соответствии с вашими замечаниями.</string>
|
||||
<string name="html_text_dev_feature_thanks">Спасибо большое за содействие.</string>
|
||||
<string name="html_text_dev_feature_thanks">Большое спасибо за поддержку.</string>
|
||||
<string name="html_text_dev_feature_work_hard">Мы прилагаем все усилия, чтобы быстро выпустить эту функцию.</string>
|
||||
<string name="html_text_dev_feature_upgrade">Не забывайте обновлять приложение.</string>
|
||||
<string name="download">Скачать</string>
|
||||
@@ -414,7 +413,7 @@
|
||||
<string name="hide_expired_entries_summary">Не показывать записи с истёкшим сроком</string>
|
||||
<string name="contact">Контактная информация</string>
|
||||
<string name="contribution">Помощь проекту</string>
|
||||
<string name="html_about_contribution">Для <strong>сохранения нашей независимости</strong>, <strong>исправления ошибок</strong>, <strong>добавления новых функций</strong> и <strong>поддержания разработки в активном состоянии</strong>, мы рассчитываем на ваше <strong>содействие</strong>.</string>
|
||||
<string name="html_about_contribution">Для <strong>сохранения нашей независимости</strong>, <strong>исправления ошибок</strong>, <strong>добавления новых функций</strong> и <strong>поддержания разработки в активном состоянии</strong>, мы рассчитываем на вашу <strong>поддержку</strong>.</string>
|
||||
<string name="auto_focus_search_title">Быстрый поиск</string>
|
||||
<string name="auto_focus_search_summary">Открывать поисковый запрос при открытии базы</string>
|
||||
<string name="remember_database_locations_title">Помнить расположение баз</string>
|
||||
@@ -550,4 +549,17 @@
|
||||
<string name="content_description_otp_information">Информация об одноразовом пароле</string>
|
||||
<string name="error_duplicate_file">Данные файла уже существует.</string>
|
||||
<string name="error_remove_file">Ошибка при удалении данных файла.</string>
|
||||
<string name="properties">Свойства</string>
|
||||
<string name="error_export_app_properties">Ошибка при экспорте настроек приложения</string>
|
||||
<string name="success_export_app_properties">Настройки приложения экспортированы</string>
|
||||
<string name="error_import_app_properties">Ошибка при импорте настроек приложения</string>
|
||||
<string name="success_import_app_properties">Настройки приложения импортированы</string>
|
||||
<string name="description_app_properties">Управление настройками приложения KeePassDX</string>
|
||||
<string name="export_app_properties_summary">Создать файл настроек приложения</string>
|
||||
<string name="export_app_properties_title">Экспорт настроек</string>
|
||||
<string name="import_app_properties_summary">Импортировать настройки приложения из файла</string>
|
||||
<string name="import_app_properties_title">Импорт настроек</string>
|
||||
<string name="error_start_database_action">Произошла ошибка при выполнении действия с базой.</string>
|
||||
<string name="error_move_group_here">Сюда группу переместить невозможно.</string>
|
||||
<string name="error_word_reserved">Это слово зарезервировано и не может быть использовано.</string>
|
||||
</resources>
|
||||
@@ -270,7 +270,6 @@
|
||||
<string name="style_choose_summary">Tema som används i appen</string>
|
||||
<string name="icon_pack_choose_title">Ikonpaket</string>
|
||||
<string name="icon_pack_choose_summary">Ikonpaket som används i appen</string>
|
||||
<string name="error_move_folder_in_itself">Du kan inte lägga en grupp i sig själv.</string>
|
||||
<string name="menu_copy">Kopia</string>
|
||||
<string name="menu_move">Flytta</string>
|
||||
<string name="menu_paste">Klistra in</string>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<string name="encryption">Şifreleme</string>
|
||||
<string name="encryption_algorithm">Şifreleme algoritması</string>
|
||||
<string name="key_derivation_function">Anahtar üretme fonksiyonu</string>
|
||||
<string name="app_timeout">Uygulama zaman aşımı</string>
|
||||
<string name="app_timeout">Zaman aşımı</string>
|
||||
<string name="app_timeout_summary">Veri tabanını kilitlemeden önceki boşta kalma süresi</string>
|
||||
<string name="application">Uygulama</string>
|
||||
<string name="brackets">Parantez</string>
|
||||
@@ -78,7 +78,6 @@
|
||||
<string name="error_string_key">Her dizginin bir alan adı olmalıdır.</string>
|
||||
<string name="error_wrong_length">\"Uzunluk\" alanına pozitif bir tam sayı girin.</string>
|
||||
<string name="error_autofill_enable_service">Otomatik doldurma hizmeti etkinleştirilemedi.</string>
|
||||
<string name="error_move_folder_in_itself">Bir grubu kendine taşıyamazsın.</string>
|
||||
<string name="field_name">Alan adı</string>
|
||||
<string name="field_value">Alan değeri</string>
|
||||
<string name="file_not_found_content">Dosya bulunamadı. Dosya tarayıcınızda yeniden açmayı deneyin.</string>
|
||||
@@ -545,4 +544,17 @@
|
||||
<string name="content_description_otp_information">Tek seferlik parola bilgileri</string>
|
||||
<string name="error_remove_file">Dosya verilerini kaldırırken bir hata oluştu.</string>
|
||||
<string name="error_duplicate_file">Dosya verileri zaten var.</string>
|
||||
<string name="properties">Özellikler</string>
|
||||
<string name="error_export_app_properties">Uygulama özelliklerini dışa aktarma sırasında hata oluştu</string>
|
||||
<string name="error_import_app_properties">Uygulama özelliklerini içe aktarma sırasında hata oluştu</string>
|
||||
<string name="success_export_app_properties">Uygulama özellikleri dışa aktarıldı</string>
|
||||
<string name="success_import_app_properties">Uygulama özellikleri içe aktarıldı</string>
|
||||
<string name="description_app_properties">Uygulama ayarlarını yönetmek için KeePassDX özellikleri</string>
|
||||
<string name="export_app_properties_summary">Uygulama özelliklerini dışa aktarmak için bir dosya oluşturun</string>
|
||||
<string name="export_app_properties_title">Uygulama özelliklerini dışa aktar</string>
|
||||
<string name="import_app_properties_summary">Uygulama özelliklerini içe aktarmak için bir dosya seçin</string>
|
||||
<string name="import_app_properties_title">Uygulama özelliklerini içe aktar</string>
|
||||
<string name="error_start_database_action">Veri tabanında bir eylem gerçekleştirilirken bir hata oluştu.</string>
|
||||
<string name="error_move_group_here">Bir grubu buraya taşıyamazsınız.</string>
|
||||
<string name="error_word_reserved">Bu sözcük ayrılmıştır ve kullanılamaz.</string>
|
||||
</resources>
|
||||
47
app/src/main/res/values-tt/strings.xml
Normal file
47
app/src/main/res/values-tt/strings.xml
Normal file
@@ -0,0 +1,47 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="keyboard_notification_entry_content_text">%1$s</string>
|
||||
<string name="compression_gzip">Gzip</string>
|
||||
<string name="other">Башка</string>
|
||||
<string name="text_appearance">Текст</string>
|
||||
<string name="disable">Сүндерү</string>
|
||||
<string name="data">Мәгълүмат</string>
|
||||
<string name="file_name">Файл исеме</string>
|
||||
<string name="database_history">Тарих</string>
|
||||
<string name="version_label">Версия %1$s</string>
|
||||
<string name="search">Эзләү</string>
|
||||
<string name="sort_username">Кулланучы исеме</string>
|
||||
<string name="sort_title">Исем</string>
|
||||
<string name="sort_ascending">Иң түбән беренче ↓</string>
|
||||
<string name="search_label">Эзләү</string>
|
||||
<string name="auto_focus_search_title">Тиз эзләү</string>
|
||||
<string name="never">Беркайчан да</string>
|
||||
<string name="menu_showpass">Серсүзне күрсәтү</string>
|
||||
<string name="menu_search">Эзләү</string>
|
||||
<string name="menu_open">Ачу</string>
|
||||
<string name="menu_hide_password">Серсүзне яшерү</string>
|
||||
<string name="menu_move">Күчү</string>
|
||||
<string name="menu_cancel">Баш тарту</string>
|
||||
<string name="menu_delete">Бетерү</string>
|
||||
<string name="menu_donate">Бүләк итү</string>
|
||||
<string name="menu_app_settings">Кушымта көйләүләре</string>
|
||||
<string name="settings">Көйләүләр</string>
|
||||
<string name="menu_change_key_settings">Мастер ачкычны үзгәртү</string>
|
||||
<string name="about">Турында</string>
|
||||
<string name="hide_password_title">Серсүзләрне яшерү</string>
|
||||
<string name="list_entries_show_username_title">Кулланучы исемнәрен күрсәтү</string>
|
||||
<string name="password">Серсүз</string>
|
||||
<string name="hint_pass">Серсүз</string>
|
||||
<string name="entry_user_name">Кулланучы исеме</string>
|
||||
<string name="entry_url">URL</string>
|
||||
<string name="entry_otp">OTP</string>
|
||||
<string name="otp_algorithm">Алгоритм</string>
|
||||
<string name="entry_title">Исем</string>
|
||||
<string name="save">Саклау</string>
|
||||
<string name="entry_password">Серсүз</string>
|
||||
<string name="entry_history">Тарих</string>
|
||||
<string name="entry_UUID">UUID</string>
|
||||
<string name="entry_cancel">Баш тарту</string>
|
||||
<string name="content_description_update_from_list">Яңарту</string>
|
||||
<string name="application">Кушымта</string>
|
||||
</resources>
|
||||
@@ -24,7 +24,7 @@
|
||||
<string name="add_entry">Додати запис</string>
|
||||
<string name="add_group">Додати групу</string>
|
||||
<string name="encryption_algorithm">Алгоритм шифрування</string>
|
||||
<string name="app_timeout">Час очікування застосунку</string>
|
||||
<string name="app_timeout">Час очікування</string>
|
||||
<string name="app_timeout_summary">Час бездіяльности до блокування бази даних</string>
|
||||
<string name="application">Застосунок</string>
|
||||
<string name="menu_app_settings">Параметри застосунку</string>
|
||||
@@ -217,7 +217,6 @@
|
||||
<string name="error_copy_group_here">Ви не можете копіювати групу сюди.</string>
|
||||
<string name="error_copy_entry_here">Ви не можете копіювати записи сюди.</string>
|
||||
<string name="error_move_entry_here">Ви не можете перемістити запис сюди.</string>
|
||||
<string name="error_move_folder_in_itself">Ви не можете перемістити групу в себе саму.</string>
|
||||
<string name="error_autofill_enable_service">Не вдалось ввімкнути службу автозаповнення.</string>
|
||||
<string name="error_label_exists">Ця мітка вже існує.</string>
|
||||
<string name="error_string_key">Кожен рядок повинен мати назву поля.</string>
|
||||
@@ -550,4 +549,17 @@
|
||||
<string name="content_description_otp_information">Відомості про одноразовий пароль</string>
|
||||
<string name="error_remove_file">Сталася помилка під час вилучення даних файлу.</string>
|
||||
<string name="error_duplicate_file">Дані файлу вже існують.</string>
|
||||
<string name="properties">Властивості</string>
|
||||
<string name="error_export_app_properties">Помилка під час експортування властивостей застосунку</string>
|
||||
<string name="success_export_app_properties">Властивості застосунку експортовано</string>
|
||||
<string name="error_import_app_properties">Помилка під час імпортування властивостей застосунку</string>
|
||||
<string name="success_import_app_properties">Властивості застосунку імпортовано</string>
|
||||
<string name="description_app_properties">Властивості KeePassDX для керування параметрами застосунку</string>
|
||||
<string name="export_app_properties_summary">Створити файл для експорту властивостей застосунку</string>
|
||||
<string name="export_app_properties_title">Експорт властивостей застосунку</string>
|
||||
<string name="import_app_properties_summary">Виберіть файл для імпорту властивостей застосунку</string>
|
||||
<string name="import_app_properties_title">Імпорт властивостей застосунку</string>
|
||||
<string name="error_start_database_action">Під час виконання дії з базою даних сталася помилка.</string>
|
||||
<string name="error_move_group_here">Ви не можете перемістити групу сюди.</string>
|
||||
<string name="error_word_reserved">Це слово зарезервоване, його не можна використовувати.</string>
|
||||
</resources>
|
||||
@@ -24,7 +24,7 @@
|
||||
<string name="add_entry">添加条目</string>
|
||||
<string name="add_group">添加群组</string>
|
||||
<string name="encryption_algorithm">加密算法</string>
|
||||
<string name="app_timeout">离开程序锁定延时</string>
|
||||
<string name="app_timeout">延时</string>
|
||||
<string name="app_timeout_summary">在锁定数据库前处于非活动状态的时长</string>
|
||||
<string name="application">应用</string>
|
||||
<string name="menu_app_settings">程序设置</string>
|
||||
@@ -139,7 +139,6 @@
|
||||
<string name="error_load_database">无法加载数据库。</string>
|
||||
<string name="error_load_database_KDF_memory">无法加载密钥。尝试降低KDF的“内存使用”值。</string>
|
||||
<string name="error_autofill_enable_service">无法启用自动填充服务。</string>
|
||||
<string name="error_move_folder_in_itself">无法将群组移至它自身之中。</string>
|
||||
<string name="file_not_found_content">找不到文件。请重新打开文件。</string>
|
||||
<string name="invalid_algorithm">算法无效。</string>
|
||||
<string name="keyfile_is_empty">密钥文件为空。</string>
|
||||
@@ -550,4 +549,17 @@
|
||||
<string name="content_description_otp_information">一次性密码信息</string>
|
||||
<string name="error_remove_file">删除文件数据时发生了一个错误。</string>
|
||||
<string name="error_duplicate_file">文件数据已存在。</string>
|
||||
<string name="properties">属性</string>
|
||||
<string name="error_export_app_properties">应用属性导出期间出错</string>
|
||||
<string name="success_export_app_properties">已导出应用属性</string>
|
||||
<string name="error_import_app_properties">导入应用属性期间出错</string>
|
||||
<string name="success_import_app_properties">已导入应用属性</string>
|
||||
<string name="description_app_properties">管理应用设置的 KeePassDX 属性</string>
|
||||
<string name="export_app_properties_summary">创建一个文件来导出应用属性</string>
|
||||
<string name="export_app_properties_title">导出应用属性</string>
|
||||
<string name="import_app_properties_summary">选择一个文件来导入应用属性</string>
|
||||
<string name="import_app_properties_title">导入应用属性</string>
|
||||
<string name="error_start_database_action">对数据库执行操作时发生了一个错误。</string>
|
||||
<string name="error_move_group_here">你不能把一个组移动到此处。</string>
|
||||
<string name="error_word_reserved">这个单词是保留的,不能使用。</string>
|
||||
</resources>
|
||||
@@ -128,7 +128,6 @@
|
||||
<string name="clipboard_error">部份設備不容許其他程式使用剪貼簿。</string>
|
||||
<string name="clipboard_error_clear">無法清除剪貼簿</string>
|
||||
<string name="error_autofill_enable_service">無法啟用自動填入服務。</string>
|
||||
<string name="error_move_folder_in_itself">無法移動一個群組至自己本身。</string>
|
||||
<string name="invalid_algorithm">無效的演算法。</string>
|
||||
<string name="keyfile_is_empty">金鑰檔案是空白的。</string>
|
||||
<string name="menu_form_filling_settings">表格填入</string>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user