From d3bd4526ee6889daf00a7500d6f7c18d1030b5c1 Mon Sep 17 00:00:00 2001 From: Josh Hou Date: Mon, 14 Feb 2022 10:24:42 +0800 Subject: [PATCH] Verify the locale format within a localeConfig file Leverage the existing mechanism for checking the locale format of the resources to check whether the inputs in the localeConfig file conforms to the BCP47 regular expressions. Bug: 208943132 Test: AAPT2 test Change-Id: If972b8cc89b1e5bab422be16cc13d471e39f036f --- tools/aapt2/cmd/Link.cpp | 81 +++++++++++++++++++++++++++++++++++ tools/aapt2/cmd/Link_test.cpp | 48 +++++++++++++++++++++ 2 files changed, 129 insertions(+) diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 790f2b34c58b2..1efe6c25d2176 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -1057,6 +1057,83 @@ class Linker { return true; } + bool VerifyLocaleFormat(xml::XmlResource* manifest, IDiagnostics* diag) { + // Skip it if the Manifest doesn't declare the localeConfig attribute within the + // element. + const xml::Element* application = manifest->root->FindChild("", "application"); + if (!application) { + return true; + } + const xml::Attribute* localeConfig = + application->FindAttribute(xml::kSchemaAndroid, "localeConfig"); + if (!localeConfig) { + return true; + } + + if (localeConfig->compiled_value) { + const auto localeconfig_reference = ValueCast(localeConfig->compiled_value.get()); + const auto localeconfig_entry = + ResolveTableEntry(context_, &final_table_, localeconfig_reference); + if (!localeconfig_entry) { + return true; + } + + for (const auto& value : localeconfig_entry->values) { + // Load an XML file which is linked from the localeConfig attribute. + const std::string& path = value->value->GetSource().path; + std::unique_ptr localeConfig_xml = LoadXml(path, diag); + if (!localeConfig_xml) { + diag->Error(DiagMessage(path) << "can't load the XML"); + return false; + } + + xml::Element* localeConfig_el = xml::FindRootElement(localeConfig_xml->root.get()); + if (!localeConfig_el) { + diag->Error(DiagMessage(path) << "no root tag defined"); + return false; + } + if (localeConfig_el->name != "locale-config") { + diag->Error(DiagMessage(path) << "invalid element name: " << localeConfig_el->name + << ", expected: locale-config"); + return false; + } + + for (const xml::Element* child_el : localeConfig_el->GetChildElements()) { + if (child_el->name == "locale") { + if (const xml::Attribute* locale_name_attr = + child_el->FindAttribute(xml::kSchemaAndroid, "name")) { + const std::string& locale_name = locale_name_attr->value; + const std::string valid_name = ConvertToBCP47Tag(locale_name); + + // Start to verify the locale format + ConfigDescription config; + if (!ConfigDescription::Parse(valid_name, &config)) { + diag->Error(DiagMessage(path) << "invalid configuration: " << locale_name); + return false; + } + } else { + diag->Error(DiagMessage(path) << "the attribute android:name is not found"); + return false; + } + } else { + diag->Error(DiagMessage(path) + << "invalid element name: " << child_el->name << ", expected: locale"); + return false; + } + } + } + } + return true; + } + + std::string ConvertToBCP47Tag(const std::string& locale) { + std::string bcp47tag = "b+"; + bcp47tag += locale; + std::replace(bcp47tag.begin(), bcp47tag.end(), '-', '+'); + + return bcp47tag; + } + std::unique_ptr MakeArchiveWriter(const StringPiece& out) { if (options_.output_to_directory) { return CreateDirectoryArchiveWriter(context_->GetDiagnostics(), out); @@ -2180,6 +2257,10 @@ class Linker { return 1; } + if (!VerifyLocaleFormat(manifest_xml.get(), context_->GetDiagnostics())) { + return 1; + }; + if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) { return 1; } diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 430c184ef87d6..7b1236ab4a5ec 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -22,6 +22,7 @@ #include "LoadedApk.h" #include "test/Test.h" +using android::ConfigDescription; using testing::Eq; using testing::HasSubstr; using testing::IsNull; @@ -783,4 +784,51 @@ TEST_F(LinkTest, MacroSubstitution) { EXPECT_THAT(xml_attrs[1].value, Eq("Hello World!")); } +TEST_F(LinkTest, ParseLocaleConfig) { + StdErrDiagnostics diag; + const std::string xml_values = + R"( + + + + + + + + )"; + + const std::string res = GetTestPath("test-res"); + ASSERT_TRUE(CompileFile(GetTestPath("res/xml/locale_config.xml"), xml_values, res, &diag)); + + const std::string out_apk = GetTestPath("out.apk"); + auto link_args = LinkCommandBuilder(this) + .SetManifestFile(ManifestBuilder(this).SetPackageName("com.test").Build()) + .AddCompiledResDir(res, &diag) + .AddFlag("--no-auto-version") + .Build(out_apk); + ASSERT_TRUE(Link(link_args, &diag)); + + std::unique_ptr apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + + auto xml = apk->LoadXml("res/xml/locale_config.xml", &diag); + ASSERT_THAT(xml, NotNull()); + EXPECT_THAT(xml->root->name, Eq("locale-config")); + ASSERT_THAT(xml->root->children.size(), Eq(7)); + for (auto& node : xml->root->children) { + const xml::Element* child_el = xml::NodeCast(node.get()); + ASSERT_THAT(child_el, NotNull()); + EXPECT_THAT(child_el->name, Eq("locale")); + + auto& xml_attrs = child_el->attributes; + for (auto& attr : xml_attrs) { + std::string locale = "b+"; + locale += attr.value; + std::replace(locale.begin(), locale.end(), '-', '+'); + ConfigDescription config; + ASSERT_TRUE(ConfigDescription::Parse(locale, &config)); + } + } +} + } // namespace aapt