1package com.peachpasswords.autofill23import android.content.Context4import com.peachpasswords.passkeys.PasskeyCallerOrigin5import com.peachpasswords.shared.getDisplayDomain6import com.peachpasswords.vault.VaultEntry7import com.peachpasswords.vault.isLogin89private fun nativeSignerDigests(context: Context, packageName: String?): Set<String>? =10 packageName?.let { PasskeyCallerOrigin.signerFingerprints(context, it) }1112internal data class AutofillSaveDraft(13 val origin: String,14 val isWebOrigin: Boolean,15 val identity: String?,16 val identityIsEmail: Boolean,17 val password: String,18 val nativeSignerDigests: Set<String>? = null,19)2021internal sealed interface AutofillSavePlan {22 data class Add(val entry: VaultEntry) : AutofillSavePlan23 data class Update(val entry: VaultEntry) : AutofillSavePlan24}2526internal fun autofillSaveDraft(parsed: ParsedStructure, nativeSignerDigests: Set<String>? = null): AutofillSaveDraft? {27 if (!parsed.credentialReleaseAllowed) return null28 val password = parsed.passwordValue?.trim().orEmpty()29 if (password.isBlank()) return null30 val origin = parsed.origin?.trim().orEmpty()31 if (origin.isBlank()) return null32 val identity = (parsed.emailValue ?: parsed.usernameValue)?.trim()?.takeIf { it.isNotBlank() }33 return AutofillSaveDraft(34 origin = origin,35 isWebOrigin = parsed.webDomain != null,36 identity = identity,37 identityIsEmail = parsed.emailValue?.isNotBlank() == true || identity?.contains("@") == true,38 password = password,39 nativeSignerDigests = nativeSignerDigests,40 )41}4243internal fun planAutofillSave(entries: List<VaultEntry>, draft: AutofillSaveDraft): AutofillSavePlan {44 val target = findAutofillUpdateTarget(entries, draft)45 return if (target == null) {46 AutofillSavePlan.Add(draft.toEntry())47 } else {48 AutofillSavePlan.Update(49 target.copy(50 username = if (!draft.identityIsEmail) draft.identity ?: target.username else target.username,51 email = if (draft.identityIsEmail) draft.identity ?: target.email else target.email,52 password = draft.password,53 ),54 )55 }56}5758private fun findAutofillUpdateTarget(entries: List<VaultEntry>, draft: AutofillSaveDraft): VaultEntry? {59 val candidates = if (draft.isWebOrigin) {60 AutofillResponseBuilder.matchEntries(entries, draft.origin).hits61 } else {62 entries.filter { it.url == draft.origin }63 }.filter { it.deletedAt == null && it.isLogin() }6465 val identity = draft.identity?.lowercase()66 if (identity != null) {67 candidates.firstOrNull { entry ->68 entry.username?.lowercase() == identity || entry.email?.lowercase() == identity69 }?.let { return it }70 }71 return candidates.singleOrNull()72}7374private fun AutofillSaveDraft.toEntry(): VaultEntry {75 val title = if (isWebOrigin) getDisplayDomain(origin) else origin76 return VaultEntry(77 id = "",78 type = "login",79 title = title,80 username = if (!identityIsEmail) identity else null,81 email = if (identityIsEmail) identity else null,82 password = password,83 url = if (isWebOrigin && !origin.contains("://")) "https://$origin" else origin,84 nativeSignerDigests = nativeSignerDigests,85 createdAt = 0,86 modifiedAt = 0,87 )88}