1/*
2 * Copyright (C) 2014 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 "androidfw/ResourceTypes.h"
18
19#include <codecvt>
20#include <locale>
21#include <string>
22
23#include "utils/String16.h"
24#include "utils/String8.h"
25
26#include "TestHelpers.h"
27#include "data/basic/R.h"
28#include "data/lib_one/R.h"
29
30namespace basic = com::android::basic;
31namespace lib = com::android::lib_one;
32
33namespace android {
34
35TEST(ResTableTest, ShouldLoadSuccessfully) {
36  std::string contents;
37  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
38                                      "resources.arsc", &contents));
39
40  ResTable table;
41  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
42}
43
44TEST(ResTableTest, ShouldLoadSparseEntriesSuccessfully) {
45  std::string contents;
46  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/sparse/sparse.apk", "resources.arsc",
47                                      &contents));
48
49  ResTable table;
50  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
51
52  ResTable_config config;
53  memset(&config, 0, sizeof(config));
54  config.sdkVersion = 26;
55  table.setParameters(&config);
56
57  String16 name(u"com.android.sparse:integer/foo_9");
58  uint32_t flags;
59  uint32_t resid =
60      table.identifierForName(name.string(), name.size(), nullptr, 0, nullptr, 0, &flags);
61  ASSERT_NE(0u, resid);
62
63  Res_value val;
64  ResTable_config selected_config;
65  ASSERT_GE(
66      table.getResource(resid, &val, false /*mayBeBag*/, 0u /*density*/, &flags, &selected_config),
67      0);
68  EXPECT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
69  EXPECT_EQ(900u, val.data);
70}
71
72TEST(ResTableTest, SimpleTypeIsRetrievedCorrectly) {
73  std::string contents;
74  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
75                                      "resources.arsc", &contents));
76
77  ResTable table;
78  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
79
80  EXPECT_TRUE(IsStringEqual(table, basic::R::string::test1, "test1"));
81}
82
83TEST(ResTableTest, ResourceNameIsResolved) {
84  std::string contents;
85  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
86                                      "resources.arsc", &contents));
87
88  ResTable table;
89  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
90
91  String16 defPackage("com.android.basic");
92  String16 testName("@string/test1");
93  uint32_t resID =
94      table.identifierForName(testName.string(), testName.size(), 0, 0,
95                              defPackage.string(), defPackage.size());
96  ASSERT_NE(uint32_t(0x00000000), resID);
97  ASSERT_EQ(basic::R::string::test1, resID);
98}
99
100TEST(ResTableTest, NoParentThemeIsAppliedCorrectly) {
101  std::string contents;
102  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
103                                      "resources.arsc", &contents));
104
105  ResTable table;
106  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
107
108  ResTable::Theme theme(table);
109  ASSERT_EQ(NO_ERROR, theme.applyStyle(basic::R::style::Theme1));
110
111  Res_value val;
112  uint32_t specFlags = 0;
113  ssize_t index = theme.getAttribute(basic::R::attr::attr1, &val, &specFlags);
114  ASSERT_GE(index, 0);
115  ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
116  ASSERT_EQ(uint32_t(100), val.data);
117
118  index = theme.getAttribute(basic::R::attr::attr2, &val, &specFlags);
119  ASSERT_GE(index, 0);
120  ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
121  ASSERT_EQ(basic::R::integer::number1, val.data);
122}
123
124TEST(ResTableTest, ParentThemeIsAppliedCorrectly) {
125  std::string contents;
126  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
127                                      "resources.arsc", &contents));
128
129  ResTable table;
130  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
131
132  ResTable::Theme theme(table);
133  ASSERT_EQ(NO_ERROR, theme.applyStyle(basic::R::style::Theme2));
134
135  Res_value val;
136  uint32_t specFlags = 0;
137  ssize_t index = theme.getAttribute(basic::R::attr::attr1, &val, &specFlags);
138  ASSERT_GE(index, 0);
139  ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
140  ASSERT_EQ(uint32_t(300), val.data);
141
142  index = theme.getAttribute(basic::R::attr::attr2, &val, &specFlags);
143  ASSERT_GE(index, 0);
144  ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
145  ASSERT_EQ(basic::R::integer::number1, val.data);
146}
147
148TEST(ResTableTest, LibraryThemeIsAppliedCorrectly) {
149  std::string contents;
150  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/lib_one/lib_one.apk",
151                                      "resources.arsc", &contents));
152
153  ResTable table;
154  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
155
156  ResTable::Theme theme(table);
157  ASSERT_EQ(NO_ERROR, theme.applyStyle(lib::R::style::Theme));
158
159  Res_value val;
160  uint32_t specFlags = 0;
161  ssize_t index = theme.getAttribute(lib::R::attr::attr1, &val, &specFlags);
162  ASSERT_GE(index, 0);
163  ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
164  ASSERT_EQ(uint32_t(700), val.data);
165
166  index = theme.getAttribute(lib::R::attr::attr2, &val, &specFlags);
167  ASSERT_GE(index, 0);
168  ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
169  ASSERT_EQ(uint32_t(700), val.data);
170}
171
172TEST(ResTableTest, ReferenceToBagIsNotResolved) {
173  std::string contents;
174  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
175                                      "resources.arsc", &contents));
176
177  ResTable table;
178  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
179
180  Res_value val;
181  ssize_t block =
182      table.getResource(basic::R::integer::number2, &val, MAY_NOT_BE_BAG);
183  ASSERT_GE(block, 0);
184  ASSERT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
185  ASSERT_EQ(basic::R::array::integerArray1, val.data);
186
187  ssize_t newBlock = table.resolveReference(&val, block);
188  EXPECT_EQ(block, newBlock);
189  EXPECT_EQ(Res_value::TYPE_REFERENCE, val.dataType);
190  EXPECT_EQ(basic::R::array::integerArray1, val.data);
191}
192
193TEST(ResTableTest, ResourcesStillAccessibleAfterParameterChange) {
194  std::string contents;
195  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
196                                      "resources.arsc", &contents));
197
198  ResTable table;
199  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
200
201  Res_value val;
202  ssize_t block =
203      table.getResource(basic::R::integer::number1, &val, MAY_NOT_BE_BAG);
204  ASSERT_GE(block, 0);
205  ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
206
207  const ResTable::bag_entry* entry;
208  ssize_t count = table.lockBag(basic::R::array::integerArray1, &entry);
209  ASSERT_GE(count, 0);
210  table.unlockBag(entry);
211
212  ResTable_config param;
213  memset(&param, 0, sizeof(param));
214  param.density = 320;
215  table.setParameters(&param);
216
217  block = table.getResource(basic::R::integer::number1, &val, MAY_NOT_BE_BAG);
218  ASSERT_GE(block, 0);
219  ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
220
221  count = table.lockBag(basic::R::array::integerArray1, &entry);
222  ASSERT_GE(count, 0);
223  table.unlockBag(entry);
224}
225
226TEST(ResTableTest, ResourceIsOverridenWithBetterConfig) {
227  std::string contents;
228  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
229                                      "resources.arsc", &contents));
230
231  ResTable table;
232  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
233
234  Res_value val;
235  ssize_t block =
236      table.getResource(basic::R::integer::number1, &val, MAY_NOT_BE_BAG);
237  ASSERT_GE(block, 0);
238  ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
239  ASSERT_EQ(uint32_t(200), val.data);
240
241  ResTable_config param;
242  memset(&param, 0, sizeof(param));
243  param.language[0] = 's';
244  param.language[1] = 'v';
245  param.country[0] = 'S';
246  param.country[1] = 'E';
247  table.setParameters(&param);
248
249  block = table.getResource(basic::R::integer::number1, &val, MAY_NOT_BE_BAG);
250  ASSERT_GE(block, 0);
251  ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
252  ASSERT_EQ(uint32_t(400), val.data);
253}
254
255TEST(ResTableTest, emptyTableHasSensibleDefaults) {
256  const int32_t assetCookie = 1;
257
258  ResTable table;
259  ASSERT_EQ(NO_ERROR, table.addEmpty(assetCookie));
260
261  // Adding an empty table gives us one table!
262  ASSERT_EQ(uint32_t(1), table.getTableCount());
263
264  // Adding an empty table doesn't mean we get packages.
265  ASSERT_EQ(uint32_t(0), table.getBasePackageCount());
266
267  Res_value val;
268  ASSERT_LT(table.getResource(basic::R::integer::number1, &val, MAY_NOT_BE_BAG),
269            0);
270}
271
272void testU16StringToInt(const char16_t* str, uint32_t expectedValue,
273                        bool expectSuccess, bool expectHex) {
274  size_t len = std::char_traits<char16_t>::length(str);
275
276  // Gtest can't print UTF-16 strings, so we have to convert to UTF-8 :(
277  std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
278  std::string s = convert.to_bytes(std::u16string(str, len));
279
280  Res_value out = {};
281  ASSERT_EQ(expectSuccess, U16StringToInt(str, len, &out)) << "Failed with "
282                                                           << s;
283
284  if (!expectSuccess) {
285    ASSERT_EQ(out.TYPE_NULL, out.dataType) << "Failed with " << s;
286    return;
287  }
288
289  if (expectHex) {
290    ASSERT_EQ(out.TYPE_INT_HEX, out.dataType) << "Failed with " << s;
291  } else {
292    ASSERT_EQ(out.TYPE_INT_DEC, out.dataType) << "Failed with " << s;
293  }
294
295  ASSERT_EQ(expectedValue, out.data) << "Failed with " << s;
296}
297
298TEST(ResTableTest, U16StringToInt) {
299  testU16StringToInt(u"", 0U, false, false);
300  testU16StringToInt(u"    ", 0U, false, false);
301  testU16StringToInt(u"\t\n", 0U, false, false);
302
303  testU16StringToInt(u"abcd", 0U, false, false);
304  testU16StringToInt(u"10abcd", 0U, false, false);
305  testU16StringToInt(u"42 42", 0U, false, false);
306  testU16StringToInt(u"- 42", 0U, false, false);
307  testU16StringToInt(u"-", 0U, false, false);
308
309  testU16StringToInt(u"0x", 0U, false, true);
310  testU16StringToInt(u"0xnope", 0U, false, true);
311  testU16StringToInt(u"0X42", 0U, false, true);
312  testU16StringToInt(u"0x42 0x42", 0U, false, true);
313  testU16StringToInt(u"-0x0", 0U, false, true);
314  testU16StringToInt(u"-0x42", 0U, false, true);
315  testU16StringToInt(u"- 0x42", 0U, false, true);
316
317  // Note that u" 42" would pass. This preserves the old behavior, but it may
318  // not be desired.
319  testU16StringToInt(u"42 ", 0U, false, false);
320  testU16StringToInt(u"0x42 ", 0U, false, true);
321
322  // Decimal cases.
323  testU16StringToInt(u"0", 0U, true, false);
324  testU16StringToInt(u"-0", 0U, true, false);
325  testU16StringToInt(u"42", 42U, true, false);
326  testU16StringToInt(u" 42", 42U, true, false);
327  testU16StringToInt(u"-42", static_cast<uint32_t>(-42), true, false);
328  testU16StringToInt(u" -42", static_cast<uint32_t>(-42), true, false);
329  testU16StringToInt(u"042", 42U, true, false);
330  testU16StringToInt(u"-042", static_cast<uint32_t>(-42), true, false);
331
332  // Hex cases.
333  testU16StringToInt(u"0x0", 0x0, true, true);
334  testU16StringToInt(u"0x42", 0x42, true, true);
335  testU16StringToInt(u" 0x42", 0x42, true, true);
336
337  // Just before overflow cases:
338  testU16StringToInt(u"2147483647", INT_MAX, true, false);
339  testU16StringToInt(u"-2147483648", static_cast<uint32_t>(INT_MIN), true,
340                     false);
341  testU16StringToInt(u"0xffffffff", UINT_MAX, true, true);
342
343  // Overflow cases:
344  testU16StringToInt(u"2147483648", 0U, false, false);
345  testU16StringToInt(u"-2147483649", 0U, false, false);
346  testU16StringToInt(u"0x1ffffffff", 0U, false, true);
347}
348
349TEST(ResTableTest, ShareButDontModifyResTable) {
350  std::string contents;
351  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
352                                      "resources.arsc", &contents));
353
354  ResTable sharedTable;
355  ASSERT_EQ(NO_ERROR, sharedTable.add(contents.data(), contents.size()));
356
357  ResTable_config param;
358  memset(&param, 0, sizeof(param));
359  param.language[0] = 'v';
360  param.language[1] = 's';
361  sharedTable.setParameters(&param);
362
363  // Check that we get the default value for @integer:number1
364  Res_value val;
365  ssize_t block =
366      sharedTable.getResource(basic::R::integer::number1, &val, MAY_NOT_BE_BAG);
367  ASSERT_GE(block, 0);
368  ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
369  ASSERT_EQ(uint32_t(600), val.data);
370
371  // Create a new table that shares the entries of the shared table.
372  ResTable table;
373  ASSERT_EQ(NO_ERROR, table.add(&sharedTable, false));
374
375  // Set a new configuration on the new table.
376  memset(&param, 0, sizeof(param));
377  param.language[0] = 's';
378  param.language[1] = 'v';
379  param.country[0] = 'S';
380  param.country[1] = 'E';
381  table.setParameters(&param);
382
383  // Check that we get a new value in the new table.
384  block = table.getResource(basic::R::integer::number1, &val, MAY_NOT_BE_BAG);
385  ASSERT_GE(block, 0);
386  ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
387  ASSERT_EQ(uint32_t(400), val.data);
388
389  // Check that we still get the old value in the shared table.
390  block =
391      sharedTable.getResource(basic::R::integer::number1, &val, MAY_NOT_BE_BAG);
392  ASSERT_GE(block, 0);
393  ASSERT_EQ(Res_value::TYPE_INT_DEC, val.dataType);
394  ASSERT_EQ(uint32_t(600), val.data);
395}
396
397TEST(ResTableTest, GetConfigurationsReturnsUniqueList) {
398  std::string contents;
399  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk",
400                                      "resources.arsc", &contents));
401
402  std::string system_contents;
403  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/system/system.apk",
404                                      "resources.arsc", &system_contents));
405
406  ResTable table;
407  ASSERT_EQ(NO_ERROR,
408            table.add(system_contents.data(), system_contents.size()));
409  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
410
411  ResTable_config configSv;
412  memset(&configSv, 0, sizeof(configSv));
413  configSv.language[0] = 's';
414  configSv.language[1] = 'v';
415
416  Vector<ResTable_config> configs;
417  table.getConfigurations(&configs);
418
419  EXPECT_EQ(1, std::count(configs.begin(), configs.end(), configSv));
420
421  Vector<String8> locales;
422  table.getLocales(&locales);
423
424  EXPECT_EQ(1, std::count(locales.begin(), locales.end(), String8("sv")));
425}
426
427TEST(ResTableTest, TruncatedEncodeLength) {
428  std::string contents;
429  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/length_decode/length_decode_valid.apk",
430                                      "resources.arsc", &contents));
431
432  ResTable table;
433  ASSERT_EQ(NO_ERROR, table.add(contents.data(), contents.size()));
434
435  Res_value val;
436  ssize_t block = table.getResource(0x7f010001, &val, MAY_NOT_BE_BAG);
437  ASSERT_GE(block, 0);
438  ASSERT_EQ(Res_value::TYPE_STRING, val.dataType);
439
440  const ResStringPool* pool = table.getTableStringBlock(block);
441  ASSERT_TRUE(pool != NULL);
442  ASSERT_LT(val.data, pool->size());
443
444  // Make sure a string with a truncated length is read to its correct length
445  size_t str_len;
446  const char* target_str8 = pool->string8At(val.data, &str_len);
447  ASSERT_TRUE(target_str8 != NULL);
448  ASSERT_EQ(size_t(40076), String8(target_str8, str_len).size());
449  ASSERT_EQ(target_str8[40075], ']');
450
451  const char16_t* target_str16 = pool->stringAt(val.data, &str_len);
452  ASSERT_TRUE(target_str16 != NULL);
453  ASSERT_EQ(size_t(40076), String16(target_str16, str_len).size());
454  ASSERT_EQ(target_str8[40075], (char16_t) ']');
455
456  // Load an edited apk with the null terminator removed from the end of the
457  // string
458  std::string invalid_contents;
459  ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/length_decode/length_decode_invalid.apk",
460                                      "resources.arsc", &invalid_contents));
461  ResTable invalid_table;
462  ASSERT_EQ(NO_ERROR, invalid_table.add(invalid_contents.data(), invalid_contents.size()));
463
464  Res_value invalid_val;
465  ssize_t invalid_block = invalid_table.getResource(0x7f010001, &invalid_val, MAY_NOT_BE_BAG);
466  ASSERT_GE(invalid_block, 0);
467  ASSERT_EQ(Res_value::TYPE_STRING, invalid_val.dataType);
468
469  const ResStringPool* invalid_pool = invalid_table.getTableStringBlock(invalid_block);
470  ASSERT_TRUE(invalid_pool != NULL);
471  ASSERT_LT(invalid_val.data, invalid_pool->size());
472
473  // Make sure a string with a truncated length that is not null terminated errors
474  // and does not return the string
475  ASSERT_TRUE(invalid_pool->string8At(invalid_val.data, &str_len) == NULL);
476  ASSERT_TRUE(invalid_pool->stringAt(invalid_val.data, &str_len) == NULL);
477}
478
479}  // namespace android
480