1package com.peachpasswords.autofill23import com.peachpasswords.shared.getDisplayDomain4import com.peachpasswords.vault.VaultEntry5import com.peachpasswords.vault.isLogin67internal data class AutofillSaveDraft(8 val origin: String,9 val isWebOrigin: Boolean,10 val identity: String?,11 val identityIsEmail: Boolean,12 val password: String,13 val totp: String?,14)1516internal sealed interface AutofillSavePlan {17 data class Add(val entry: VaultEntry) : AutofillSavePlan18 data class Update(val entry: VaultEntry) : AutofillSavePlan19}2021internal fun autofillSaveDraft(parsed: ParsedStructure): AutofillSaveDraft? {22 if (!parsed.credentialReleaseAllowed) return null23 val password = parsed.passwordValue?.trim().orEmpty()24 if (password.isBlank()) return null25 val origin = parsed.origin?.trim().orEmpty()26 if (origin.isBlank()) return null27 val identity = (parsed.emailValue ?: parsed.usernameValue)?.trim()?.takeIf { it.isNotBlank() }28 return AutofillSaveDraft(29 origin = origin,30 isWebOrigin = parsed.webDomain != null,31 identity = identity,32 identityIsEmail = parsed.emailValue?.isNotBlank() == true || identity?.contains("@") == true,33 password = password,34 totp = parsed.otpValue?.trim()?.takeIf { it.isNotBlank() },35 )36}3738internal fun planAutofillSave(entries: List<VaultEntry>, draft: AutofillSaveDraft): AutofillSavePlan {39 val target = findAutofillUpdateTarget(entries, draft)40 return if (target == null) {41 AutofillSavePlan.Add(draft.toEntry())42 } else {43 AutofillSavePlan.Update(44 target.copy(45 username = if (!draft.identityIsEmail) draft.identity ?: target.username else target.username,46 email = if (draft.identityIsEmail) draft.identity ?: target.email else target.email,47 password = draft.password,48 totp = draft.totp ?: target.totp,49 ),50 )51 }52}5354private fun findAutofillUpdateTarget(entries: List<VaultEntry>, draft: AutofillSaveDraft): VaultEntry? {55 val candidates = if (draft.isWebOrigin) {56 AutofillResponseBuilder.matchEntries(entries, draft.origin).hits57 } else {58 entries.filter { it.url == draft.origin }59 }.filter { it.deletedAt == null && it.isLogin() }6061 val identity = draft.identity?.lowercase()62 if (identity != null) {63 candidates.firstOrNull { entry ->64 entry.username?.lowercase() == identity || entry.email?.lowercase() == identity65 }?.let { return it }66 }67 return candidates.singleOrNull()68}6970private fun AutofillSaveDraft.toEntry(): VaultEntry {71 val title = if (isWebOrigin) getDisplayDomain(origin) else origin72 return VaultEntry(73 id = "",74 type = "login",75 title = title,76 username = if (!identityIsEmail) identity else null,77 email = if (identityIsEmail) identity else null,78 password = password,79 url = if (isWebOrigin && !origin.contains("://")) "https://$origin" else origin,80 totp = totp,81 createdAt = 0,82 modifiedAt = 0,83 )84}