Loading services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +5 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); } Loading services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java +15 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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) { Loading services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt +41 −3 Original line number Diff line number Diff line Loading @@ -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() Loading Loading @@ -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) } ) }, ) Loading services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt 0 → 100644 +126 −0 Original line number Diff line number Diff line /* * 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) } } Loading
services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +5 −1 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -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(); } Loading
services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java +15 −0 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading @@ -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) { Loading
services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt +41 −3 Original line number Diff line number Diff line Loading @@ -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() Loading Loading @@ -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) } ) }, ) Loading
services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationValidIntentTest.kt 0 → 100644 +126 −0 Original line number Diff line number Diff line /* * 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) } }