1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17#include "flatten/XmlFlattener.h" 18 19#include "androidfw/ResourceTypes.h" 20 21#include "link/Linkers.h" 22#include "test/Test.h" 23#include "util/BigBuffer.h" 24#include "util/Util.h" 25 26using android::StringPiece16; 27 28namespace aapt { 29 30class XmlFlattenerTest : public ::testing::Test { 31 public: 32 void SetUp() override { 33 context_ = test::ContextBuilder() 34 .SetCompilationPackage("com.app.test") 35 .SetNameManglerPolicy(NameManglerPolicy{"com.app.test"}) 36 .AddSymbolSource( 37 test::StaticSymbolSourceBuilder() 38 .AddPublicSymbol("android:attr/id", ResourceId(0x010100d0), 39 test::AttributeBuilder().Build()) 40 .AddSymbol("com.app.test:id/id", ResourceId(0x7f020000)) 41 .AddPublicSymbol("android:attr/paddingStart", ResourceId(0x010103b3), 42 test::AttributeBuilder().Build()) 43 .AddPublicSymbol("android:attr/colorAccent", ResourceId(0x01010435), 44 test::AttributeBuilder().Build()) 45 .AddSymbol("com.app.test.feature:id/foo", ResourceId(0x80020000)) 46 .AddSymbol("com.app.test.feature:attr/foo", ResourceId(0x80010000), 47 test::AttributeBuilder().Build()) 48 .Build()) 49 .Build(); 50 } 51 52 ::testing::AssertionResult Flatten(xml::XmlResource* doc, 53 android::ResXMLTree* out_tree, 54 const XmlFlattenerOptions& options = {}) { 55 using namespace android; // For NO_ERROR on windows because it is a macro. 56 57 BigBuffer buffer(1024); 58 XmlFlattener flattener(&buffer, options); 59 if (!flattener.Consume(context_.get(), doc)) { 60 return ::testing::AssertionFailure() << "failed to flatten XML Tree"; 61 } 62 63 std::unique_ptr<uint8_t[]> data = util::Copy(buffer); 64 if (out_tree->setTo(data.get(), buffer.size(), true) != NO_ERROR) { 65 return ::testing::AssertionFailure() << "flattened XML is corrupt"; 66 } 67 return ::testing::AssertionSuccess(); 68 } 69 70 protected: 71 std::unique_ptr<test::Context> context_; 72}; 73 74TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) { 75 std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( 76 <View xmlns:test="http://com.test" 77 attr="hey"> 78 <Layout test:hello="hi" /> 79 <Layout>Some text\\</Layout> 80 </View>)EOF"); 81 82 android::ResXMLTree tree; 83 ASSERT_TRUE(Flatten(doc.get(), &tree)); 84 85 ASSERT_EQ(android::ResXMLTree::START_NAMESPACE, tree.next()); 86 87 size_t len; 88 const char16_t* namespace_prefix = tree.getNamespacePrefix(&len); 89 EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len)); 90 91 const char16_t* namespace_uri = tree.getNamespaceUri(&len); 92 ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len)); 93 94 ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); 95 96 ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); 97 const char16_t* tag_name = tree.getElementName(&len); 98 EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len)); 99 100 ASSERT_EQ(1u, tree.getAttributeCount()); 101 ASSERT_EQ(nullptr, tree.getAttributeNamespace(0, &len)); 102 const char16_t* attr_name = tree.getAttributeName(0, &len); 103 EXPECT_EQ(StringPiece16(u"attr"), StringPiece16(attr_name, len)); 104 105 EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size())); 106 107 ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); 108 109 ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); 110 tag_name = tree.getElementName(&len); 111 EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); 112 113 ASSERT_EQ(1u, tree.getAttributeCount()); 114 const char16_t* attr_namespace = tree.getAttributeNamespace(0, &len); 115 EXPECT_EQ(StringPiece16(u"http://com.test"), StringPiece16(attr_namespace, len)); 116 117 attr_name = tree.getAttributeName(0, &len); 118 EXPECT_EQ(StringPiece16(u"hello"), StringPiece16(attr_name, len)); 119 120 ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); 121 ASSERT_EQ(android::ResXMLTree::START_TAG, tree.next()); 122 123 ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); 124 tag_name = tree.getElementName(&len); 125 EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); 126 ASSERT_EQ(0u, tree.getAttributeCount()); 127 128 ASSERT_EQ(android::ResXMLTree::TEXT, tree.next()); 129 const char16_t* text = tree.getText(&len); 130 EXPECT_EQ(StringPiece16(u"Some text\\"), StringPiece16(text, len)); 131 132 ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); 133 ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); 134 tag_name = tree.getElementName(&len); 135 EXPECT_EQ(StringPiece16(u"Layout"), StringPiece16(tag_name, len)); 136 137 ASSERT_EQ(android::ResXMLTree::END_TAG, tree.next()); 138 ASSERT_EQ(nullptr, tree.getElementNamespace(&len)); 139 tag_name = tree.getElementName(&len); 140 EXPECT_EQ(StringPiece16(u"View"), StringPiece16(tag_name, len)); 141 142 ASSERT_EQ(android::ResXMLTree::END_NAMESPACE, tree.next()); 143 namespace_prefix = tree.getNamespacePrefix(&len); 144 EXPECT_EQ(StringPiece16(u"test"), StringPiece16(namespace_prefix, len)); 145 146 namespace_uri = tree.getNamespaceUri(&len); 147 ASSERT_EQ(StringPiece16(u"http://com.test"), StringPiece16(namespace_uri, len)); 148 149 ASSERT_EQ(android::ResXMLTree::END_DOCUMENT, tree.next()); 150} 151 152TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) { 153 std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( 154 <View xmlns:tools="http://schemas.android.com/tools" 155 xmlns:foo="http://schemas.android.com/foo" 156 foo:bar="Foo" 157 tools:ignore="MissingTranslation"/>)EOF"); 158 159 android::ResXMLTree tree; 160 ASSERT_TRUE(Flatten(doc.get(), &tree)); 161 162 ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); 163 164 size_t len; 165 const char16_t* namespace_prefix = tree.getNamespacePrefix(&len); 166 EXPECT_EQ(StringPiece16(namespace_prefix, len), u"foo"); 167 168 const char16_t* namespace_uri = tree.getNamespaceUri(&len); 169 ASSERT_EQ(StringPiece16(namespace_uri, len), 170 u"http://schemas.android.com/foo"); 171 172 ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); 173 174 EXPECT_EQ(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"), 175 android::NAME_NOT_FOUND); 176 EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0); 177} 178 179TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) { 180 std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom(R"EOF( 181 <View xmlns:android="http://schemas.android.com/apk/res/android" 182 android:id="@id/id" 183 class="str" 184 style="@id/id"/>)EOF"); 185 186 android::ResXMLTree tree; 187 ASSERT_TRUE(Flatten(doc.get(), &tree)); 188 189 while (tree.next() != android::ResXMLTree::START_TAG) { 190 ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); 191 ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); 192 } 193 194 EXPECT_EQ(tree.indexOfClass(), 0); 195 EXPECT_EQ(tree.indexOfStyle(), 1); 196} 197 198// The device ResXMLParser in libandroidfw differentiates between empty namespace and null 199// namespace. 200TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { 201 std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"android\"/>"); 202 203 android::ResXMLTree tree; 204 ASSERT_TRUE(Flatten(doc.get(), &tree)); 205 206 while (tree.next() != android::ResXMLTree::START_TAG) { 207 ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); 208 ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); 209 } 210 211 const StringPiece16 kPackage = u"package"; 212 EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); 213} 214 215TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) { 216 std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom("<View package=\"\"/>"); 217 218 android::ResXMLTree tree; 219 ASSERT_TRUE(Flatten(doc.get(), &tree)); 220 221 while (tree.next() != android::ResXMLTree::START_TAG) { 222 ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); 223 ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); 224 } 225 226 const StringPiece16 kPackage = u"package"; 227 ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()); 228 ASSERT_GE(idx, 0); 229 230 size_t len; 231 EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len)); 232} 233 234TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) { 235 context_->SetCompilationPackage("com.app.test.feature"); 236 context_->SetPackageId(0x80); 237 context_->SetNameManglerPolicy({"com.app.test.feature"}); 238 239 std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"EOF( 240 <View xmlns:android="http://schemas.android.com/apk/res/android" 241 xmlns:app="http://schemas.android.com/apk/res-auto" 242 android:id="@id/foo" 243 app:foo="@id/foo" />)EOF"); 244 245 XmlReferenceLinker linker; 246 ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); 247 248 // The tree needs a custom DynamicRefTable since it is not using a standard app ID (0x7f). 249 android::DynamicRefTable dynamic_ref_table; 250 dynamic_ref_table.addMapping(0x80, 0x80); 251 252 android::ResXMLTree tree(&dynamic_ref_table); 253 ASSERT_TRUE(Flatten(doc.get(), &tree)); 254 255 while (tree.next() != android::ResXMLTree::START_TAG) { 256 ASSERT_NE(android::ResXMLTree::BAD_DOCUMENT, tree.getEventType()); 257 ASSERT_NE(android::ResXMLTree::END_DOCUMENT, tree.getEventType()); 258 } 259 260 ssize_t idx; 261 262 idx = tree.indexOfAttribute(xml::kSchemaAndroid, "id"); 263 ASSERT_GE(idx, 0); 264 EXPECT_EQ(idx, tree.indexOfID()); 265 EXPECT_EQ(ResourceId(0x010100d0), ResourceId(tree.getAttributeNameResID(idx))); 266 267 idx = tree.indexOfAttribute(xml::kSchemaAuto, "foo"); 268 ASSERT_GE(idx, 0); 269 EXPECT_EQ(ResourceId(0x80010000), ResourceId(tree.getAttributeNameResID(idx))); 270 EXPECT_EQ(android::Res_value::TYPE_REFERENCE, tree.getAttributeDataType(idx)); 271 EXPECT_EQ(ResourceId(0x80020000), tree.getAttributeData(idx)); 272} 273 274TEST_F(XmlFlattenerTest, ProcessEscapedStrings) { 275 std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDom( 276 R"EOF(<element value="\?hello" pattern="\\d{5}">\\d{5}</element>)EOF"); 277 278 android::ResXMLTree tree; 279 ASSERT_TRUE(Flatten(doc.get(), &tree)); 280 281 while (tree.next() != android::ResXMLTree::START_TAG) { 282 ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); 283 ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); 284 } 285 286 const StringPiece16 kValue = u"value"; 287 const StringPiece16 kPattern = u"pattern"; 288 289 size_t len; 290 ssize_t idx; 291 const char16_t* str16; 292 293 idx = tree.indexOfAttribute(nullptr, 0, kValue.data(), kValue.size()); 294 ASSERT_GE(idx, 0); 295 str16 = tree.getAttributeStringValue(idx, &len); 296 ASSERT_NE(nullptr, str16); 297 EXPECT_EQ(StringPiece16(u"?hello"), StringPiece16(str16, len)); 298 299 idx = tree.indexOfAttribute(nullptr, 0, kPattern.data(), kPattern.size()); 300 ASSERT_GE(idx, 0); 301 str16 = tree.getAttributeStringValue(idx, &len); 302 ASSERT_NE(nullptr, str16); 303 EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len)); 304 305 ASSERT_EQ(android::ResXMLTree::TEXT, tree.next()); 306 str16 = tree.getText(&len); 307 ASSERT_NE(nullptr, str16); 308 EXPECT_EQ(StringPiece16(u"\\d{5}"), StringPiece16(str16, len)); 309} 310 311} // namespace aapt 312