diff options
4 files changed, 187 insertions, 4 deletions
diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index bf2b3c7f491f..a8a6a723ce74 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -24,6 +24,7 @@ import android.content.IntentFilter; import android.content.pm.parsing.component.ParsedActivity; import android.content.pm.parsing.component.ParsedIntentInfo; import android.os.Build; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Patterns; @@ -32,7 +33,6 @@ import com.android.server.compat.PlatformCompat; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.util.List; -import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -251,6 +251,10 @@ public class DomainVerificationCollector { * improve the reliability of any legacy verifiers. */ private boolean isValidHost(String host) { + if (TextUtils.isEmpty(host)) { + return false; + } + mDomainMatcher.reset(host); return mDomainMatcher.matches(); } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java index 44ff3eb4b942..246810f4d796 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java @@ -22,6 +22,8 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.text.TextUtils; +import android.util.Patterns; import com.android.internal.util.CollectionUtils; import com.android.server.compat.PlatformCompat; @@ -29,9 +31,13 @@ import com.android.server.pm.PackageManagerService; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.util.Set; +import java.util.regex.Matcher; public final class DomainVerificationUtils { + private static final ThreadLocal<Matcher> sCachedMatcher = ThreadLocal.withInitial( + () -> Patterns.DOMAIN_NAME.matcher("")); + /** * Consolidates package exception messages. A generic unavailable message is included since the * caller doesn't bother to check why the package isn't available. @@ -48,6 +54,15 @@ public final class DomainVerificationUtils { return false; } + String host = intent.getData().getHost(); + if (TextUtils.isEmpty(host)) { + return false; + } + + if (!sCachedMatcher.get().reset(host).matches()) { + return false; + } + Set<String> categories = intent.getCategories(); int categoriesSize = CollectionUtils.size(categories); if (categoriesSize > 2) { diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt index dce853afc763..4de8d52a8c2e 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt @@ -235,9 +235,23 @@ class DomainVerificationCollectorTest { <category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.DEFAULT"/> <data android:scheme="https"/> - <data android:path="/sub5"/> - <data android:host="example5.com"/> - <data android:host="invalid5"/> + <data android:path="/sub6"/> + <data android:host="example6.com"/> + <data android:host="invalid6"/> + </intent-filter> + <intent-filter android:autoVerify="$autoVerify"> + <category android:name="android.intent.category.BROWSABLE"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:scheme="example7.com"/> + <intent-filter android:autoVerify="$autoVerify"> + <category android:name="android.intent.category.BROWSABLE"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:scheme="https"/> + </intent-filter> + <intent-filter android:autoVerify="$autoVerify"> + <category android:name="android.intent.category.BROWSABLE"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:path="/sub7"/> </intent-filter> </xml> """.trimIndent() @@ -324,6 +338,30 @@ class DomainVerificationCollectorTest { addDataAuthority("invalid6", null) } ) + addIntent( + ParsedIntentInfo().apply { + setAutoVerify(autoVerify) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataAuthority("example7.com", null) + } + ) + addIntent( + ParsedIntentInfo().apply { + setAutoVerify(autoVerify) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataScheme("https") + } + ) + addIntent( + ParsedIntentInfo().apply { + setAutoVerify(autoVerify) + addCategory(Intent.CATEGORY_BROWSABLE) + addCategory(Intent.CATEGORY_DEFAULT) + addDataPath("/sub7", PatternMatcher.PATTERN_LITERAL) + } + ) }, ) diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt new file mode 100644 index 000000000000..98634b28ea9d --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm.test.verify.domain + +import android.content.Intent +import android.content.pm.PackageManager +import android.net.Uri +import com.android.server.pm.verify.domain.DomainVerificationUtils +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.Parameterized + +@RunWith(Parameterized::class) +class DomainVerificationValidIntentTest { + + companion object { + + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun parameters(): Array<Params> { + val succeeding = mutableListOf<Params>() + val failing = mutableListOf<Params>() + + // Start with the base intent + val base = Params(categorySet = emptySet()).also { succeeding += it } + + // Add all explicit supported categorySet + succeeding += base.copy( + categorySet = setOf(Intent.CATEGORY_BROWSABLE), + matchDefaultOnly = true + ) + + failing += base.copy( + categorySet = setOf(Intent.CATEGORY_BROWSABLE), + matchDefaultOnly = false + ) + + succeeding += listOf(true, false).map { + base.copy( + categorySet = setOf(Intent.CATEGORY_DEFAULT), + matchDefaultOnly = it + ) + } + + succeeding += listOf(true, false).map { + base.copy( + categorySet = setOf(Intent.CATEGORY_BROWSABLE, Intent.CATEGORY_DEFAULT), + matchDefaultOnly = it + ) + } + + // Fail on unsupported category + failing += listOf( + emptySet(), + setOf(Intent.CATEGORY_BROWSABLE), + setOf(Intent.CATEGORY_DEFAULT), + setOf(Intent.CATEGORY_BROWSABLE, Intent.CATEGORY_DEFAULT) + ).map { base.copy(categorySet = it + "invalid.CATEGORY") } + + // Fail on unsupported action + failing += base.copy(action = Intent.ACTION_SEND) + + // Fail on unsupported domain + failing += base.copy(domain = "invalid") + + // Fail on empty domains + failing += base.copy(domain = "") + + // Fail on missing scheme + failing += base.copy( + uriFunction = { Uri.Builder().authority("test.com").build() } + ) + + // Fail on missing host + failing += base.copy( + domain = "", + uriFunction = { Uri.Builder().scheme("https").build() } + ) + + succeeding.forEach { it.expected = true } + failing.forEach { it.expected = false } + return (succeeding + failing).toTypedArray() + } + + data class Params( + val action: String = Intent.ACTION_VIEW, + val categorySet: Set<String> = mutableSetOf(), + val domain: String = "test.com", + val matchDefaultOnly: Boolean = true, + var expected: Boolean? = null, + val uriFunction: (domain: String) -> Uri = { Uri.parse("https://$it") } + ) { + val intent = Intent(action, uriFunction(domain)).apply { + categorySet.forEach(::addCategory) + } + + override fun toString() = intent.toShortString(false, false, false, false) + + ", matchDefaultOnly = $matchDefaultOnly, expected = $expected" + } + } + + @Parameterized.Parameter(0) + lateinit var params: Params + + @Test + fun verify() { + val flags = if (params.matchDefaultOnly) PackageManager.MATCH_DEFAULT_ONLY else 0 + assertThat(DomainVerificationUtils.isDomainVerificationIntent(params.intent, flags)) + .isEqualTo(params.expected) + } +} |