Merge "Force path parts in intent filter with deeplinks to have leading slash."
This commit is contained in:
committed by
Android (Google) Code Review
commit
669965a8cd
@@ -30,6 +30,91 @@ using android::StringPiece;
|
||||
|
||||
namespace aapt {
|
||||
|
||||
// This is to detect whether an <intent-filter> contains deeplink.
|
||||
// See https://developer.android.com/training/app-links/deep-linking.
|
||||
static bool HasDeepLink(xml::Element* intent_filter_el) {
|
||||
xml::Element* action_el = intent_filter_el->FindChild({}, "action");
|
||||
xml::Element* category_el = intent_filter_el->FindChild({}, "category");
|
||||
xml::Element* data_el = intent_filter_el->FindChild({}, "data");
|
||||
if (action_el == nullptr || category_el == nullptr || data_el == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deeplinks must specify the ACTION_VIEW intent action.
|
||||
constexpr const char* action_view = "android.intent.action.VIEW";
|
||||
if (intent_filter_el->FindChildWithAttribute({}, "action", xml::kSchemaAndroid, "name",
|
||||
action_view) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deeplinks must have scheme included in <data> tag.
|
||||
xml::Attribute* data_scheme_attr = data_el->FindAttribute(xml::kSchemaAndroid, "scheme");
|
||||
if (data_scheme_attr == nullptr || data_scheme_attr->value.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Deeplinks must include BROWSABLE category.
|
||||
constexpr const char* category_browsable = "android.intent.category.BROWSABLE";
|
||||
if (intent_filter_el->FindChildWithAttribute({}, "category", xml::kSchemaAndroid, "name",
|
||||
category_browsable) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VerifyDeeplinkPathAttribute(xml::Element* data_el, android::SourcePathDiagnostics* diag,
|
||||
const std::string& attr_name) {
|
||||
xml::Attribute* attr = data_el->FindAttribute(xml::kSchemaAndroid, attr_name);
|
||||
if (attr != nullptr && !attr->value.empty()) {
|
||||
StringPiece attr_value = attr->value;
|
||||
const char* startChar = attr_value.begin();
|
||||
if (attr_name == "pathPattern") {
|
||||
if (*startChar == '/' || *startChar == '.' || *startChar == '*') {
|
||||
return true;
|
||||
} else {
|
||||
diag->Error(android::DiagMessage(data_el->line_number)
|
||||
<< "attribute 'android:" << attr_name << "' in <" << data_el->name
|
||||
<< "> tag has value of '" << attr_value
|
||||
<< "', it must be in a pattern start with '.' or '*', otherwise must start "
|
||||
"with a leading slash '/'");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (*startChar == '/') {
|
||||
return true;
|
||||
} else {
|
||||
diag->Error(android::DiagMessage(data_el->line_number)
|
||||
<< "attribute 'android:" << attr_name << "' in <" << data_el->name
|
||||
<< "> tag has value of '" << attr_value
|
||||
<< "', it must start with a leading slash '/'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool VerifyDeepLinkIntentAction(xml::Element* intent_filter_el,
|
||||
android::SourcePathDiagnostics* diag) {
|
||||
if (!HasDeepLink(intent_filter_el)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
xml::Element* data_el = intent_filter_el->FindChild({}, "data");
|
||||
if (data_el != nullptr) {
|
||||
if (!VerifyDeeplinkPathAttribute(data_el, diag, "path")) {
|
||||
return false;
|
||||
}
|
||||
if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPrefix")) {
|
||||
return false;
|
||||
}
|
||||
if (!VerifyDeeplinkPathAttribute(data_el, diag, "pathPattern")) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool RequiredNameIsNotEmpty(xml::Element* el, android::SourcePathDiagnostics* diag) {
|
||||
xml::Attribute* attr = el->FindAttribute(xml::kSchemaAndroid, "name");
|
||||
if (attr == nullptr) {
|
||||
@@ -323,6 +408,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, android::IDiagn
|
||||
|
||||
// Common <intent-filter> actions.
|
||||
xml::XmlNodeAction intent_filter_action;
|
||||
intent_filter_action.Action(VerifyDeepLinkIntentAction);
|
||||
intent_filter_action["action"].Action(RequiredNameIsNotEmpty);
|
||||
intent_filter_action["category"].Action(RequiredNameIsNotEmpty);
|
||||
intent_filter_action["data"];
|
||||
|
||||
@@ -1068,4 +1068,345 @@ TEST_F(ManifestFixerTest, ComponentPropertyOnlyOneAttributeDefined) {
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
}
|
||||
|
||||
TEST_F(ManifestFixerTest, IntentFilterActionMustHaveNonEmptyName) {
|
||||
std::string input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), IsNull());
|
||||
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), IsNull());
|
||||
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
}
|
||||
|
||||
TEST_F(ManifestFixerTest, IntentFilterCategoryMustHaveNonEmptyName) {
|
||||
std::string input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<category android:name="" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), IsNull());
|
||||
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<category />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), IsNull());
|
||||
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
}
|
||||
|
||||
TEST_F(ManifestFixerTest, IntentFilterPathMustStartWithLeadingSlashOnDeepLinks) {
|
||||
// No DeepLink.
|
||||
std::string input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<data />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
|
||||
// No DeepLink, missing ACTION_VIEW.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:pathPrefix="pathPattern" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
|
||||
// DeepLink, missing DEFAULT category while DEFAULT is recommended but not required.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:pathPrefix="pathPattern" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), IsNull());
|
||||
|
||||
// No DeepLink, missing BROWSABLE category.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:pathPrefix="pathPattern" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
|
||||
// No DeepLink, missing 'android:scheme' in <data> tag.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:host="www.example.com"
|
||||
android:pathPrefix="pathPattern" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
|
||||
// No DeepLink, <action> is ACTION_MAIN not ACTION_VIEW.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:pathPrefix="pathPattern" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
|
||||
// DeepLink with no leading slash in android:path.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:path="path" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), IsNull());
|
||||
|
||||
// DeepLink with leading slash in android:path.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:path="/path" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
|
||||
// DeepLink with no leading slash in android:pathPrefix.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:pathPrefix="pathPrefix" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), IsNull());
|
||||
|
||||
// DeepLink with leading slash in android:pathPrefix.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:pathPrefix="/pathPrefix" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
|
||||
// DeepLink with no leading slash in android:pathPattern.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:pathPattern="pathPattern" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), IsNull());
|
||||
|
||||
// DeepLink with leading slash in android:pathPattern.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:pathPattern="/pathPattern" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
|
||||
// DeepLink with '.' start in pathPattern.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:pathPattern=".*\\.pathPattern" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
|
||||
// DeepLink with '*' start in pathPattern.
|
||||
input = R"(
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="android">
|
||||
<application>
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data android:scheme="http"
|
||||
android:host="www.example.com"
|
||||
android:pathPattern="*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>)";
|
||||
EXPECT_THAT(Verify(input), NotNull());
|
||||
}
|
||||
} // namespace aapt
|
||||
|
||||
Reference in New Issue
Block a user