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 
26 using android::StringPiece16;
27 
28 namespace aapt {
29 
30 class XmlFlattenerTest : public ::testing::Test {
31  public:
SetUp()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 
Flatten(xml::XmlResource * doc,android::ResXMLTree * out_tree,const XmlFlattenerOptions & options={})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 
TEST_F(XmlFlattenerTest,FlattenXmlWithNoCompiledAttributes)74 TEST_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 
TEST_F(XmlFlattenerTest,FlattenCompiledXmlAndStripOnlyTools)152 TEST_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 
TEST_F(XmlFlattenerTest,AssignSpecialAttributeIndices)179 TEST_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.
200 TEST_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 
TEST_F(XmlFlattenerTest,EmptyStringValueInAttributeIsNotNull)215 TEST_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 
TEST_F(XmlFlattenerTest,FlattenNonStandardPackageId)234 TEST_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 
274 TEST_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