diff options
Diffstat (limited to 'autofill-parser')
11 files changed, 203 insertions, 68 deletions
diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt index e51ab69e..85381254 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillFormParser.kt @@ -41,7 +41,8 @@ public sealed class FormOrigin(public open val identifier: String) { when (this) { is Web -> identifier is App -> { - val info = context.packageManager.getApplicationInfo(identifier, PackageManager.GET_META_DATA) + val info = + context.packageManager.getApplicationInfo(identifier, PackageManager.GET_META_DATA) val label = context.packageManager.getApplicationLabel(info) if (untrusted) "“$label”" else "$label" } @@ -174,7 +175,10 @@ private class AutofillFormParser( // the single origin among the detected fillable or saveable fields. If this origin // is null, but we encountered web origins elsewhere in the AssistStructure, the // situation is uncertain and Autofill should not be offered. - webOriginToFormOrigin(context, scenario.allFields.map { it.webOrigin }.toSet().singleOrNull() ?: return null) + webOriginToFormOrigin( + context, + scenario.allFields.map { it.webOrigin }.toSet().singleOrNull() ?: return null + ) } } } @@ -204,7 +208,12 @@ private constructor( ): FillableForm? { val form = AutofillFormParser(context, structure, isManualRequest, customSuffixes) if (form.formOrigin == null || form.scenario == null) return null - return FillableForm(form.formOrigin, form.scenario.map { it.autofillId }, form.ignoredIds, form.saveFlags) + return FillableForm( + form.formOrigin, + form.scenario.map { it.autofillId }, + form.ignoredIds, + form.saveFlags + ) } } diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillHelper.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillHelper.kt index 6c8ae0dd..2de929b9 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillHelper.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillHelper.kt @@ -49,14 +49,19 @@ public fun computeCertificatesHash(context: Context, appPackage: String): String // hashes comparable between versions and hence default to using the deprecated API. @SuppressLint("PackageManagerGetSignatures") @Suppress("DEPRECATION") - val signaturesOld = context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNATURES).signatures + val signaturesOld = + context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNATURES).signatures val stableHashOld = stableHash(signaturesOld.map { it.toByteArray() }) if (Build.VERSION.SDK_INT >= 28) { - val info = context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNING_CERTIFICATES) - val signaturesNew = info.signingInfo.signingCertificateHistory ?: info.signingInfo.apkContentsSigners + val info = + context.packageManager.getPackageInfo(appPackage, PackageManager.GET_SIGNING_CERTIFICATES) + val signaturesNew = + info.signingInfo.signingCertificateHistory ?: info.signingInfo.apkContentsSigners val stableHashNew = stableHash(signaturesNew.map { it.toByteArray() }) if (stableHashNew != stableHashOld) - tag("CertificatesHash").e { "Mismatch between old and new hash: $stableHashNew != $stableHashOld" } + tag("CertificatesHash").e { + "Mismatch between old and new hash: $stableHashNew != $stableHashOld" + } } return stableHashOld } @@ -106,7 +111,10 @@ private fun visitViewNodes(structure: AssistStructure, block: (AssistStructure.V } } -private fun visitViewNode(node: AssistStructure.ViewNode, block: (AssistStructure.ViewNode) -> Unit) { +private fun visitViewNode( + node: AssistStructure.ViewNode, + block: (AssistStructure.ViewNode) -> Unit +) { block(node) for (i in 0 until node.childCount) { visitViewNode(node.getChildAt(i), block) @@ -114,7 +122,9 @@ private fun visitViewNode(node: AssistStructure.ViewNode, block: (AssistStructur } @RequiresApi(Build.VERSION_CODES.O) -internal fun AssistStructure.findNodeByAutofillId(autofillId: AutofillId): AssistStructure.ViewNode? { +internal fun AssistStructure.findNodeByAutofillId( + autofillId: AutofillId +): AssistStructure.ViewNode? { var node: AssistStructure.ViewNode? = null visitViewNodes(this) { if (it.autofillId == autofillId) node = it } return node diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillScenario.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillScenario.kt index 3583d705..11b85b66 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillScenario.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillScenario.kt @@ -56,9 +56,15 @@ public sealed class AutofillScenario<out T : Any> { username = clientState.getParcelable(BUNDLE_KEY_USERNAME_ID) fillUsername = clientState.getBoolean(BUNDLE_KEY_FILL_USERNAME) otp = clientState.getParcelable(BUNDLE_KEY_OTP_ID) - currentPassword.addAll(clientState.getParcelableArrayList(BUNDLE_KEY_CURRENT_PASSWORD_IDS) ?: emptyList()) - newPassword.addAll(clientState.getParcelableArrayList(BUNDLE_KEY_NEW_PASSWORD_IDS) ?: emptyList()) - genericPassword.addAll(clientState.getParcelableArrayList(BUNDLE_KEY_GENERIC_PASSWORD_IDS) ?: emptyList()) + currentPassword.addAll( + clientState.getParcelableArrayList(BUNDLE_KEY_CURRENT_PASSWORD_IDS) ?: emptyList() + ) + newPassword.addAll( + clientState.getParcelableArrayList(BUNDLE_KEY_NEW_PASSWORD_IDS) ?: emptyList() + ) + genericPassword.addAll( + clientState.getParcelableArrayList(BUNDLE_KEY_GENERIC_PASSWORD_IDS) ?: emptyList() + ) } .build() } catch (e: Throwable) { @@ -227,7 +233,9 @@ public fun Dataset.Builder.fillWith( } } -internal inline fun <T : Any, S : Any> AutofillScenario<T>.map(transform: (T) -> S): AutofillScenario<S> { +internal inline fun <T : Any, S : Any> AutofillScenario<T>.map( + transform: (T) -> S +): AutofillScenario<S> { val builder = AutofillScenario.Builder<S>() builder.username = username?.let(transform) builder.fillUsername = fillUsername @@ -253,7 +261,10 @@ internal fun AutofillScenario<AutofillId>.toBundle(): Bundle = putParcelable(AutofillScenario.BUNDLE_KEY_USERNAME_ID, username) putBoolean(AutofillScenario.BUNDLE_KEY_FILL_USERNAME, fillUsername) putParcelable(AutofillScenario.BUNDLE_KEY_OTP_ID, otp) - putParcelableArrayList(AutofillScenario.BUNDLE_KEY_CURRENT_PASSWORD_IDS, ArrayList(currentPassword)) + putParcelableArrayList( + AutofillScenario.BUNDLE_KEY_CURRENT_PASSWORD_IDS, + ArrayList(currentPassword) + ) putParcelableArrayList(AutofillScenario.BUNDLE_KEY_NEW_PASSWORD_IDS, ArrayList(newPassword)) } } @@ -262,7 +273,10 @@ internal fun AutofillScenario<AutofillId>.toBundle(): Bundle = putParcelable(AutofillScenario.BUNDLE_KEY_USERNAME_ID, username) putBoolean(AutofillScenario.BUNDLE_KEY_FILL_USERNAME, fillUsername) putParcelable(AutofillScenario.BUNDLE_KEY_OTP_ID, otp) - putParcelableArrayList(AutofillScenario.BUNDLE_KEY_GENERIC_PASSWORD_IDS, ArrayList(genericPassword)) + putParcelableArrayList( + AutofillScenario.BUNDLE_KEY_GENERIC_PASSWORD_IDS, + ArrayList(genericPassword) + ) } } } diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategy.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategy.kt index ed264233..7303efc5 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategy.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategy.kt @@ -9,11 +9,14 @@ import androidx.annotation.RequiresApi import com.github.androidpasswordstore.autofillparser.CertaintyLevel.Certain import com.github.androidpasswordstore.autofillparser.CertaintyLevel.Likely -private inline fun <T> Pair<T, T>.all(predicate: T.() -> Boolean) = predicate(first) && predicate(second) +private inline fun <T> Pair<T, T>.all(predicate: T.() -> Boolean) = + predicate(first) && predicate(second) -private inline fun <T> Pair<T, T>.any(predicate: T.() -> Boolean) = predicate(first) || predicate(second) +private inline fun <T> Pair<T, T>.any(predicate: T.() -> Boolean) = + predicate(first) || predicate(second) -private inline fun <T> Pair<T, T>.none(predicate: T.() -> Boolean) = !predicate(first) && !predicate(second) +private inline fun <T> Pair<T, T>.none(predicate: T.() -> Boolean) = + !predicate(first) && !predicate(second) /** * The strategy used to detect [AutofillScenario] s; expressed using the DSL implemented in @@ -32,7 +35,8 @@ internal val autofillStrategy = strategy { } currentPassword(optional = true) { takeSingle { alreadyMatched -> - val adjacentToNewPasswords = directlyPrecedes(alreadyMatched) || directlyFollows(alreadyMatched) + val adjacentToNewPasswords = + directlyPrecedes(alreadyMatched) || directlyFollows(alreadyMatched) // The Autofill framework has not hint that applies to current passwords only. // In this scenario, we have already matched fields a pair of fields with a specific // new password hint, so we take a generic Autofill password hint to mean a current @@ -109,7 +113,9 @@ internal val autofillStrategy = strategy { rule(applyInSingleOriginMode = true) { newPassword { takeSingle { hasHintNewPassword && isFocused } } username(optional = true) { - takeSingle { alreadyMatched -> usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) } + takeSingle { alreadyMatched -> + usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) + } } } @@ -119,7 +125,9 @@ internal val autofillStrategy = strategy { rule(applyInSingleOriginMode = true) { currentPassword { takeSingle { hasAutocompleteHintCurrentPassword && isFocused } } username(optional = true) { - takeSingle { alreadyMatched -> usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) } + takeSingle { alreadyMatched -> + usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) + } } } @@ -129,7 +137,9 @@ internal val autofillStrategy = strategy { rule(applyInSingleOriginMode = true) { genericPassword { takeSingle { passwordCertainty >= Likely && isFocused } } username(optional = true) { - takeSingle { alreadyMatched -> usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) } + takeSingle { alreadyMatched -> + usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) + } } } @@ -139,12 +149,16 @@ internal val autofillStrategy = strategy { rule { username { takeSingle { hasHintUsername && isFocused } } currentPassword(matchHidden = true) { - takeSingle { alreadyMatched -> directlyFollows(alreadyMatched.singleOrNull()) && couldBeTwoStepHiddenPassword } + takeSingle { alreadyMatched -> + directlyFollows(alreadyMatched.singleOrNull()) && couldBeTwoStepHiddenPassword + } } } // Match a single focused OTP field. - rule(applyInSingleOriginMode = true) { otp { takeSingle { otpCertainty >= Likely && isFocused } } } + rule(applyInSingleOriginMode = true) { + otp { takeSingle { otpCertainty >= Likely && isFocused } } + } // Match a single focused username field without a password field. rule(applyInSingleOriginMode = true) { @@ -162,7 +176,9 @@ internal val autofillStrategy = strategy { // This rule can apply in single origin mode since even though the password field may not be // focused at the time the rule runs, the fill suggestion will only show if it ever receives // focus. - rule(applyInSingleOriginMode = true) { currentPassword { takeSingle { hasAutocompleteHintCurrentPassword } } } + rule(applyInSingleOriginMode = true) { + currentPassword { takeSingle { hasAutocompleteHintCurrentPassword } } + } // See above. rule(applyInSingleOriginMode = true) { genericPassword { takeSingle { true } } } @@ -171,10 +187,14 @@ internal val autofillStrategy = strategy { rule(applyInSingleOriginMode = true, applyOnManualRequestOnly = true) { genericPassword { takeSingle { isFocused } } username(optional = true) { - takeSingle { alreadyMatched -> usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) } + takeSingle { alreadyMatched -> + usernameCertainty >= Likely && directlyPrecedes(alreadyMatched.singleOrNull()) + } } } // Match any focused username field on manual request. - rule(applyInSingleOriginMode = true, applyOnManualRequestOnly = true) { username { takeSingle { isFocused } } } + rule(applyInSingleOriginMode = true, applyOnManualRequestOnly = true) { + username { takeSingle { isFocused } } + } } diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt index c6886d15..3d7f9849 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt @@ -20,28 +20,41 @@ internal interface FieldMatcher { class Builder { private var takeSingle: (FormField.(List<FormField>) -> Boolean)? = null - private val tieBreakersSingle: MutableList<FormField.(List<FormField>) -> Boolean> = mutableListOf() + private val tieBreakersSingle: MutableList<FormField.(List<FormField>) -> Boolean> = + mutableListOf() private var takePair: (Pair<FormField, FormField>.(List<FormField>) -> Boolean)? = null - private var tieBreakersPair: MutableList<Pair<FormField, FormField>.(List<FormField>) -> Boolean> = mutableListOf() + private var tieBreakersPair: + MutableList<Pair<FormField, FormField>.(List<FormField>) -> Boolean> = + mutableListOf() fun takeSingle(block: FormField.(alreadyMatched: List<FormField>) -> Boolean = { true }) { - check(takeSingle == null && takePair == null) { "Every block can only have at most one take{Single,Pair} block" } + check(takeSingle == null && takePair == null) { + "Every block can only have at most one take{Single,Pair} block" + } takeSingle = block } fun breakTieOnSingle(block: FormField.(alreadyMatched: List<FormField>) -> Boolean) { - check(takeSingle != null) { "Every block needs a takeSingle block before a breakTieOnSingle block" } + check(takeSingle != null) { + "Every block needs a takeSingle block before a breakTieOnSingle block" + } check(takePair == null) { "takePair cannot be mixed with breakTieOnSingle" } tieBreakersSingle.add(block) } - fun takePair(block: Pair<FormField, FormField>.(alreadyMatched: List<FormField>) -> Boolean = { true }) { - check(takeSingle == null && takePair == null) { "Every block can only have at most one take{Single,Pair} block" } + fun takePair( + block: Pair<FormField, FormField>.(alreadyMatched: List<FormField>) -> Boolean = { true } + ) { + check(takeSingle == null && takePair == null) { + "Every block can only have at most one take{Single,Pair} block" + } takePair = block } - fun breakTieOnPair(block: Pair<FormField, FormField>.(alreadyMatched: List<FormField>) -> Boolean) { + fun breakTieOnPair( + block: Pair<FormField, FormField>.(alreadyMatched: List<FormField>) -> Boolean + ) { check(takePair != null) { "Every block needs a takePair block before a breakTieOnPair block" } check(takeSingle == null) { "takeSingle cannot be mixed with breakTieOnPair" } tieBreakersPair.add(block) @@ -69,7 +82,8 @@ internal class SingleFieldMatcher( class Builder { private var takeSingle: (FormField.(List<FormField>) -> Boolean)? = null - private val tieBreakersSingle: MutableList<FormField.(List<FormField>) -> Boolean> = mutableListOf() + private val tieBreakersSingle: MutableList<FormField.(List<FormField>) -> Boolean> = + mutableListOf() fun takeSingle(block: FormField.(alreadyMatched: List<FormField>) -> Boolean = { true }) { check(takeSingle == null) { "Every block can only have at most one takeSingle block" } @@ -77,7 +91,9 @@ internal class SingleFieldMatcher( } fun breakTieOnSingle(block: FormField.(alreadyMatched: List<FormField>) -> Boolean) { - check(takeSingle != null) { "Every block needs a takeSingle block before a breakTieOnSingle block" } + check(takeSingle != null) { + "Every block needs a takeSingle block before a breakTieOnSingle block" + } tieBreakersSingle.add(block) } @@ -180,7 +196,10 @@ private constructor( } @AutofillDsl - class Builder(private val applyInSingleOriginMode: Boolean, private val applyOnManualRequestOnly: Boolean) { + class Builder( + private val applyInSingleOriginMode: Boolean, + private val applyOnManualRequestOnly: Boolean + ) { companion object { @@ -286,9 +305,13 @@ private constructor( "Rules with applyInSingleOriginMode set to true must not fill into hidden fields" } } - return AutofillRule(matchers, applyInSingleOriginMode, applyOnManualRequestOnly, name ?: "Rule #$ruleId").also { - ruleId++ - } + return AutofillRule( + matchers, + applyInSingleOriginMode, + applyOnManualRequestOnly, + name ?: "Rule #$ruleId" + ) + .also { ruleId++ } } } @@ -409,4 +432,5 @@ internal class AutofillStrategy private constructor(private val rules: List<Auto } } -internal fun strategy(block: AutofillStrategy.Builder.() -> Unit) = AutofillStrategy.Builder().apply(block).build() +internal fun strategy(block: AutofillStrategy.Builder.() -> Unit) = + AutofillStrategy.Builder().apply(block).build() diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt index c418df79..6e3e129a 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetection.kt @@ -63,7 +63,10 @@ private val TRUSTED_BROWSER_CERTIFICATE_HASH = "com.chrome.canary" to arrayOf("IBnfofsj779wxbzRRDxb6rBPPy/0Nm6aweNFdjmiTPw="), "com.chrome.dev" to arrayOf("kETuX+5LvF4h3URmVDHE6x8fcaMnFqC8knvLs5Izyr8="), "com.duckduckgo.mobile.android" to - arrayOf("u3uzHFc8RqHaf8XFKKas9DIQhFb+7FCBDH8zaU6z0tQ=", "8HB9AhwL8+b43MEbo/VwBCXVl9yjAaMeIQVWk067Gwo="), + arrayOf( + "u3uzHFc8RqHaf8XFKKas9DIQhFb+7FCBDH8zaU6z0tQ=", + "8HB9AhwL8+b43MEbo/VwBCXVl9yjAaMeIQVWk067Gwo=" + ), "com.microsoft.emmx" to arrayOf("AeGZlxCoLCdJtNUMRF3IXWcLYTYInQp2anOCfIKh6sk="), "com.opera.mini.native" to arrayOf("V6y8Ul8bLr0ZGWzW8BQ5fMkQ/RiEHgroUP68Ph5ZP/I="), "com.opera.mini.native.beta" to arrayOf("V6y8Ul8bLr0ZGWzW8BQ5fMkQ/RiEHgroUP68Ph5ZP/I="), @@ -80,7 +83,8 @@ private val TRUSTED_BROWSER_CERTIFICATE_HASH = "org.mozilla.klar" to arrayOf("YgOkc7421k7jf4f6UA7bx56rkwYQq5ufpMp9XB8bT/w="), "org.torproject.torbrowser" to arrayOf("IAYfBF5zfGc3XBd5TP7bQ2oDzsa6y3y5+WZCIFyizsg="), "org.ungoogled.chromium.stable" to arrayOf("29UOO5cXoxO/e/hH3hOu6bbtg1My4tK6Eik2Ym5Krtk="), - "org.ungoogled.chromium.extensions.stable" to arrayOf("29UOO5cXoxO/e/hH3hOu6bbtg1My4tK6Eik2Ym5Krtk="), + "org.ungoogled.chromium.extensions.stable" to + arrayOf("29UOO5cXoxO/e/hH3hOu6bbtg1My4tK6Eik2Ym5Krtk="), "com.kiwibrowser.browser" to arrayOf("wGnqlmMy6R4KDDzFd+b1Cf49ndr3AVrQxcXvj9o/hig="), ) @@ -162,19 +166,30 @@ private val BROWSER_SAVE_FLAG_IF_NO_ACCESSIBILITY = private fun isNoAccessibilityServiceEnabled(context: Context): Boolean { // See https://chromium.googlesource.com/chromium/src/+/447a31e977a65e2eb78804e4a09633699b4ede33 - return Settings.Secure.getString(context.contentResolver, Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES) + return Settings.Secure.getString( + context.contentResolver, + Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES + ) .isNullOrEmpty() } @RequiresApi(Build.VERSION_CODES.O) private fun getBrowserSaveFlag(context: Context, appPackage: String): Int? = BROWSER_SAVE_FLAG[appPackage] - ?: BROWSER_SAVE_FLAG_IF_NO_ACCESSIBILITY[appPackage]?.takeIf { isNoAccessibilityServiceEnabled(context) } + ?: BROWSER_SAVE_FLAG_IF_NO_ACCESSIBILITY[appPackage]?.takeIf { + isNoAccessibilityServiceEnabled(context) + } -internal data class BrowserAutofillSupportInfo(val multiOriginMethod: BrowserMultiOriginMethod, val saveFlags: Int?) +internal data class BrowserAutofillSupportInfo( + val multiOriginMethod: BrowserMultiOriginMethod, + val saveFlags: Int? +) @RequiresApi(Build.VERSION_CODES.O) -internal fun getBrowserAutofillSupportInfoIfTrusted(context: Context, appPackage: String): BrowserAutofillSupportInfo? { +internal fun getBrowserAutofillSupportInfoIfTrusted( + context: Context, + appPackage: String +): BrowserAutofillSupportInfo? { if (!isTrustedBrowser(context, appPackage)) return null return BrowserAutofillSupportInfo( multiOriginMethod = getBrowserMultiOriginMethod(appPackage), @@ -197,14 +212,18 @@ public enum class BrowserAutofillSupportLevel { } @RequiresApi(Build.VERSION_CODES.O) -private fun getBrowserAutofillSupportLevel(context: Context, appPackage: String): BrowserAutofillSupportLevel { +private fun getBrowserAutofillSupportLevel( + context: Context, + appPackage: String +): BrowserAutofillSupportLevel { val browserInfo = getBrowserAutofillSupportInfoIfTrusted(context, appPackage) return when { browserInfo == null -> BrowserAutofillSupportLevel.None appPackage in FLAKY_BROWSERS -> BrowserAutofillSupportLevel.FlakyFill appPackage in BROWSER_SAVE_FLAG_IF_NO_ACCESSIBILITY -> BrowserAutofillSupportLevel.PasswordFillAndSaveIfNoAccessibility - browserInfo.multiOriginMethod == BrowserMultiOriginMethod.None -> BrowserAutofillSupportLevel.PasswordFill + browserInfo.multiOriginMethod == BrowserMultiOriginMethod.None -> + BrowserAutofillSupportLevel.PasswordFill browserInfo.saveFlags == null -> BrowserAutofillSupportLevel.GeneralFill else -> BrowserAutofillSupportLevel.GeneralFillAndSave }.takeUnless { supportLevel -> @@ -212,7 +231,8 @@ private fun getBrowserAutofillSupportLevel(context: Context, appPackage: String) // (compatibility mode is only available on Android Pie and higher). Since all known browsers // with native Autofill support offer full save support as well, we reuse the list of those // browsers here. - supportLevel != BrowserAutofillSupportLevel.GeneralFillAndSave && Build.VERSION.SDK_INT < Build.VERSION_CODES.P + supportLevel != BrowserAutofillSupportLevel.GeneralFillAndSave && + Build.VERSION.SDK_INT < Build.VERSION_CODES.P } ?: BrowserAutofillSupportLevel.None } @@ -222,9 +242,15 @@ public fun getInstalledBrowsersWithAutofillSupportLevel( context: Context ): List<Pair<String, BrowserAutofillSupportLevel>> { val testWebIntent = Intent(Intent.ACTION_VIEW).apply { data = Uri.parse("http://example.org") } - val installedBrowsers = context.packageManager.queryIntentActivities(testWebIntent, PackageManager.MATCH_ALL) + val installedBrowsers = + context.packageManager.queryIntentActivities(testWebIntent, PackageManager.MATCH_ALL) return installedBrowsers .map { it to getBrowserAutofillSupportLevel(context, it.activityInfo.packageName) } .filter { it.first.isDefault || it.second != BrowserAutofillSupportLevel.None } - .map { context.packageManager.getApplicationLabel(it.first.activityInfo.applicationInfo).toString() to it.second } + .map { + context + .packageManager + .getApplicationLabel(it.first.activityInfo.applicationInfo) + .toString() to it.second + } } diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt index 0bd3d404..719af425 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt @@ -108,9 +108,14 @@ internal class FormField( "text", ) private val HTML_INPUT_FIELD_TYPES_FILLABLE = - (HTML_INPUT_FIELD_TYPES_USERNAME + HTML_INPUT_FIELD_TYPES_PASSWORD + HTML_INPUT_FIELD_TYPES_OTP).toSet().toList() - - @RequiresApi(Build.VERSION_CODES.O) private fun isSupportedHint(hint: String) = hint in HINTS_FILLABLE + (HTML_INPUT_FIELD_TYPES_USERNAME + + HTML_INPUT_FIELD_TYPES_PASSWORD + + HTML_INPUT_FIELD_TYPES_OTP) + .toSet() + .toList() + + @RequiresApi(Build.VERSION_CODES.O) + private fun isSupportedHint(hint: String) = hint in HINTS_FILLABLE private val EXCLUDED_TERMS = listOf( "url_bar", // Chrome/Edge/Firefox address bar @@ -214,7 +219,8 @@ internal class FormField( private val hasAutocompleteHintUsername = htmlAutocomplete == "username" val hasAutocompleteHintCurrentPassword = htmlAutocomplete == "current-password" private val hasAutocompleteHintNewPassword = htmlAutocomplete == "new-password" - private val hasAutocompleteHintPassword = hasAutocompleteHintCurrentPassword || hasAutocompleteHintNewPassword + private val hasAutocompleteHintPassword = + hasAutocompleteHintCurrentPassword || hasAutocompleteHintNewPassword private val hasAutocompleteHintOtp = htmlAutocomplete == "one-time-code" // Results of hint-based field type detection @@ -238,7 +244,9 @@ internal class FormField( // fields to the fill rules and only exclude those fields that have incompatible autocomplete // hint. val couldBeTwoStepHiddenPassword = - !isVisible && isHtmlPasswordField && (hasAutocompleteHintCurrentPassword || htmlAutocomplete == null) + !isVisible && + isHtmlPasswordField && + (hasAutocompleteHintCurrentPassword || htmlAutocomplete == null) // Since many site put autocomplete=off on login forms for compliance reasons or since they are // worried of the user's browser automatically (i.e., without any user interaction) filling @@ -247,7 +255,8 @@ internal class FormField( private val excludedByHints = excludedByAutofillHints // Only offer to fill into custom views if they explicitly opted into Autofill. - val relevantField = hasAutofillTypeText && (isTextField || autofillHints.isNotEmpty()) && !excludedByHints + val relevantField = + hasAutofillTypeText && (isTextField || autofillHints.isNotEmpty()) && !excludedByHints // Exclude fields based on hint, resource ID or HTML name. // Note: We still report excluded fields as relevant since they count for adjacency heuristics, @@ -260,7 +269,8 @@ internal class FormField( notExcluded && (isAndroidPasswordField || isHtmlPasswordField || hasHintPassword) private val isCertainPasswordField = isPossiblePasswordField && hasHintPassword private val isLikelyPasswordField = - isPossiblePasswordField && (isCertainPasswordField || PASSWORD_HEURISTIC_TERMS.anyMatchesFieldInfo) + isPossiblePasswordField && + (isCertainPasswordField || PASSWORD_HEURISTIC_TERMS.anyMatchesFieldInfo) val passwordCertainty = if (isCertainPasswordField) CertaintyLevel.Certain else if (isLikelyPasswordField) CertaintyLevel.Likely @@ -273,17 +283,20 @@ internal class FormField( isPossibleOtpField && (isCertainOtpField || OTP_HEURISTIC_TERMS.anyMatchesFieldInfo || - ((htmlMaxLength == null || htmlMaxLength in 6..8) && OTP_WEAK_HEURISTIC_TERMS.anyMatchesFieldInfo)) + ((htmlMaxLength == null || htmlMaxLength in 6..8) && + OTP_WEAK_HEURISTIC_TERMS.anyMatchesFieldInfo)) val otpCertainty = if (isCertainOtpField) CertaintyLevel.Certain else if (isLikelyOtpField) CertaintyLevel.Likely else if (isPossibleOtpField) CertaintyLevel.Possible else CertaintyLevel.Impossible // Username field heuristics (based only on the current field) - private val isPossibleUsernameField = notExcluded && !isPossiblePasswordField && !isCertainOtpField + private val isPossibleUsernameField = + notExcluded && !isPossiblePasswordField && !isCertainOtpField private val isCertainUsernameField = isPossibleUsernameField && hasHintUsername private val isLikelyUsernameField = - isPossibleUsernameField && (isCertainUsernameField || (USERNAME_HEURISTIC_TERMS.anyMatchesFieldInfo)) + isPossibleUsernameField && + (isCertainUsernameField || (USERNAME_HEURISTIC_TERMS.anyMatchesFieldInfo)) val usernameCertainty = if (isCertainUsernameField) CertaintyLevel.Certain else if (isLikelyUsernameField) CertaintyLevel.Likely diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/PublicSuffixListCache.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/PublicSuffixListCache.kt index c62e74ab..be3cbe66 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/PublicSuffixListCache.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/PublicSuffixListCache.kt @@ -34,12 +34,18 @@ public fun cachePublicSuffixList(context: Context) { * Note: Invalid domains, such as IP addresses, are returned unchanged and thus never collide with * the return value for valid domains. */ -internal fun getPublicSuffixPlusOne(context: Context, domain: String, customSuffixes: Sequence<String>) = runBlocking { +internal fun getPublicSuffixPlusOne( + context: Context, + domain: String, + customSuffixes: Sequence<String> +) = runBlocking { // We only feed valid domain names which are not IP addresses into getPublicSuffixPlusOne. // We do not check whether the domain actually exists (actually, not even whether its TLD // exists). As long as we restrict ourselves to syntactically valid domain names, // getPublicSuffixPlusOne will return non-colliding results. - if (!Patterns.DOMAIN_NAME.matcher(domain).matches() || Patterns.IP_ADDRESS.matcher(domain).matches()) { + if (!Patterns.DOMAIN_NAME.matcher(domain).matches() || + Patterns.IP_ADDRESS.matcher(domain).matches() + ) { domain } else { getCanonicalSuffix(context, domain, customSuffixes) @@ -60,7 +66,11 @@ private fun getSuffixPlusUpToOne(domain: String, suffix: String): String? { return "$lastPrefixPart.$suffix" } -private suspend fun getCanonicalSuffix(context: Context, domain: String, customSuffixes: Sequence<String>): String { +private suspend fun getCanonicalSuffix( + context: Context, + domain: String, + customSuffixes: Sequence<String> +): String { val publicSuffixList = PublicSuffixListCache.getOrCachePublicSuffixList(context) val publicSuffixPlusOne = publicSuffixList.getPublicSuffixPlusOne(domain).await() ?: return domain var longestSuffix = publicSuffixPlusOne diff --git a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt index 8976242e..6f8c6d80 100644 --- a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt +++ b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt @@ -58,7 +58,8 @@ internal class PublicSuffixList( fun getPublicSuffixPlusOne(domain: String): Deferred<String?> = scope.async { when (val offset = data.getPublicSuffixOffset(domain)) { - is PublicSuffixOffset.Offset -> domain.split('.').drop(offset.value).joinToString(separator = ".") + is PublicSuffixOffset.Offset -> + domain.split('.').drop(offset.value).joinToString(separator = ".") else -> null } } diff --git a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt index 7a17a80f..2bdf5f70 100644 --- a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt +++ b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListData.kt @@ -12,7 +12,10 @@ import java.net.IDN import mozilla.components.lib.publicsuffixlist.ext.binarySearch /** Class wrapping the public suffix list data and offering methods for accessing rules in it. */ -internal class PublicSuffixListData(private val rules: ByteArray, private val exceptions: ByteArray) { +internal class PublicSuffixListData( + private val rules: ByteArray, + private val exceptions: ByteArray +) { private fun binarySearchRules(labels: List<ByteArray>, labelIndex: Int): String? { return rules.binarySearch(labels, labelIndex) diff --git a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt index 0cf4c918..5f3fc296 100644 --- a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt +++ b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixListLoader.kt @@ -30,7 +30,12 @@ internal object PublicSuffixListLoader { @Suppress("MagicNumber") private fun BufferedInputStream.readInt(): Int { - return (read() and 0xff shl 24 or (read() and 0xff shl 16) or (read() and 0xff shl 8) or (read() and 0xff)) + return (read() and + 0xff shl + 24 or + (read() and 0xff shl 16) or + (read() and 0xff shl 8) or + (read() and 0xff)) } private fun BufferedInputStream.readFully(size: Int): ByteArray { |