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 "NameMangler.h"
18 #include "ResourceUtils.h"
19 #include "flatten/ResourceTypeExtensions.h"
20 #include "util/Files.h"
21 #include "util/Util.h"
22
23 #include <androidfw/ResourceTypes.h>
24 #include <sstream>
25
26 namespace aapt {
27 namespace ResourceUtils {
28
extractResourceName(const StringPiece16 & str,StringPiece16 * outPackage,StringPiece16 * outType,StringPiece16 * outEntry)29 bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
30 StringPiece16* outType, StringPiece16* outEntry) {
31 bool hasPackageSeparator = false;
32 bool hasTypeSeparator = false;
33 const char16_t* start = str.data();
34 const char16_t* end = start + str.size();
35 const char16_t* current = start;
36 while (current != end) {
37 if (outType->size() == 0 && *current == u'/') {
38 hasTypeSeparator = true;
39 outType->assign(start, current - start);
40 start = current + 1;
41 } else if (outPackage->size() == 0 && *current == u':') {
42 hasPackageSeparator = true;
43 outPackage->assign(start, current - start);
44 start = current + 1;
45 }
46 current++;
47 }
48 outEntry->assign(start, end - start);
49
50 return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
51 }
52
parseResourceName(const StringPiece16 & str,ResourceNameRef * outRef,bool * outPrivate)53 bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
54 if (str.empty()) {
55 return false;
56 }
57
58 size_t offset = 0;
59 bool priv = false;
60 if (str.data()[0] == u'*') {
61 priv = true;
62 offset = 1;
63 }
64
65 StringPiece16 package;
66 StringPiece16 type;
67 StringPiece16 entry;
68 if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
69 return false;
70 }
71
72 const ResourceType* parsedType = parseResourceType(type);
73 if (!parsedType) {
74 return false;
75 }
76
77 if (entry.empty()) {
78 return false;
79 }
80
81 if (outRef) {
82 outRef->package = package;
83 outRef->type = *parsedType;
84 outRef->entry = entry;
85 }
86
87 if (outPrivate) {
88 *outPrivate = priv;
89 }
90 return true;
91 }
92
tryParseReference(const StringPiece16 & str,ResourceNameRef * outRef,bool * outCreate,bool * outPrivate)93 bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
94 bool* outPrivate) {
95 StringPiece16 trimmedStr(util::trimWhitespace(str));
96 if (trimmedStr.empty()) {
97 return false;
98 }
99
100 bool create = false;
101 bool priv = false;
102 if (trimmedStr.data()[0] == u'@') {
103 size_t offset = 1;
104 if (trimmedStr.data()[1] == u'+') {
105 create = true;
106 offset += 1;
107 }
108
109 ResourceNameRef name;
110 if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
111 &name, &priv)) {
112 return false;
113 }
114
115 if (create && priv) {
116 return false;
117 }
118
119 if (create && name.type != ResourceType::kId) {
120 return false;
121 }
122
123 if (outRef) {
124 *outRef = name;
125 }
126
127 if (outCreate) {
128 *outCreate = create;
129 }
130
131 if (outPrivate) {
132 *outPrivate = priv;
133 }
134 return true;
135 }
136 return false;
137 }
138
isReference(const StringPiece16 & str)139 bool isReference(const StringPiece16& str) {
140 return tryParseReference(str, nullptr, nullptr, nullptr);
141 }
142
tryParseAttributeReference(const StringPiece16 & str,ResourceNameRef * outRef)143 bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) {
144 StringPiece16 trimmedStr(util::trimWhitespace(str));
145 if (trimmedStr.empty()) {
146 return false;
147 }
148
149 if (*trimmedStr.data() == u'?') {
150 StringPiece16 package;
151 StringPiece16 type;
152 StringPiece16 entry;
153 if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1),
154 &package, &type, &entry)) {
155 return false;
156 }
157
158 if (!type.empty() && type != u"attr") {
159 return false;
160 }
161
162 if (entry.empty()) {
163 return false;
164 }
165
166 if (outRef) {
167 outRef->package = package;
168 outRef->type = ResourceType::kAttr;
169 outRef->entry = entry;
170 }
171 return true;
172 }
173 return false;
174 }
175
isAttributeReference(const StringPiece16 & str)176 bool isAttributeReference(const StringPiece16& str) {
177 return tryParseAttributeReference(str, nullptr);
178 }
179
180 /*
181 * Style parent's are a bit different. We accept the following formats:
182 *
183 * @[[*]package:][style/]<entry>
184 * ?[[*]package:]style/<entry>
185 * <[*]package>:[style/]<entry>
186 * [[*]package:style/]<entry>
187 */
parseStyleParentReference(const StringPiece16 & str,std::string * outError)188 Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) {
189 if (str.empty()) {
190 return {};
191 }
192
193 StringPiece16 name = str;
194
195 bool hasLeadingIdentifiers = false;
196 bool privateRef = false;
197
198 // Skip over these identifiers. A style's parent is a normal reference.
199 if (name.data()[0] == u'@' || name.data()[0] == u'?') {
200 hasLeadingIdentifiers = true;
201 name = name.substr(1, name.size() - 1);
202 }
203
204 if (name.data()[0] == u'*') {
205 privateRef = true;
206 name = name.substr(1, name.size() - 1);
207 }
208
209 ResourceNameRef ref;
210 ref.type = ResourceType::kStyle;
211
212 StringPiece16 typeStr;
213 extractResourceName(name, &ref.package, &typeStr, &ref.entry);
214 if (!typeStr.empty()) {
215 // If we have a type, make sure it is a Style.
216 const ResourceType* parsedType = parseResourceType(typeStr);
217 if (!parsedType || *parsedType != ResourceType::kStyle) {
218 std::stringstream err;
219 err << "invalid resource type '" << typeStr << "' for parent of style";
220 *outError = err.str();
221 return {};
222 }
223 }
224
225 if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
226 std::stringstream err;
227 err << "invalid parent reference '" << str << "'";
228 *outError = err.str();
229 return {};
230 }
231
232 Reference result(ref);
233 result.privateReference = privateRef;
234 return result;
235 }
236
tryParseReference(const StringPiece16 & str,bool * outCreate)237 std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate) {
238 ResourceNameRef ref;
239 bool privateRef = false;
240 if (tryParseReference(str, &ref, outCreate, &privateRef)) {
241 std::unique_ptr<Reference> value = util::make_unique<Reference>(ref);
242 value->privateReference = privateRef;
243 return value;
244 }
245
246 if (tryParseAttributeReference(str, &ref)) {
247 if (outCreate) {
248 *outCreate = false;
249 }
250 return util::make_unique<Reference>(ref, Reference::Type::kAttribute);
251 }
252 return {};
253 }
254
tryParseNullOrEmpty(const StringPiece16 & str)255 std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str) {
256 StringPiece16 trimmedStr(util::trimWhitespace(str));
257 android::Res_value value = { };
258 if (trimmedStr == u"@null") {
259 // TYPE_NULL with data set to 0 is interpreted by the runtime as an error.
260 // Instead we set the data type to TYPE_REFERENCE with a value of 0.
261 value.dataType = android::Res_value::TYPE_REFERENCE;
262 } else if (trimmedStr == u"@empty") {
263 // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime.
264 value.dataType = android::Res_value::TYPE_NULL;
265 value.data = android::Res_value::DATA_NULL_EMPTY;
266 } else {
267 return {};
268 }
269 return util::make_unique<BinaryPrimitive>(value);
270 }
271
tryParseEnumSymbol(const Attribute * enumAttr,const StringPiece16 & str)272 std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr,
273 const StringPiece16& str) {
274 StringPiece16 trimmedStr(util::trimWhitespace(str));
275 for (const Attribute::Symbol& symbol : enumAttr->symbols) {
276 // Enum symbols are stored as @package:id/symbol resources,
277 // so we need to match against the 'entry' part of the identifier.
278 const ResourceName& enumSymbolResourceName = symbol.symbol.name.value();
279 if (trimmedStr == enumSymbolResourceName.entry) {
280 android::Res_value value = { };
281 value.dataType = android::Res_value::TYPE_INT_DEC;
282 value.data = symbol.value;
283 return util::make_unique<BinaryPrimitive>(value);
284 }
285 }
286 return {};
287 }
288
tryParseFlagSymbol(const Attribute * flagAttr,const StringPiece16 & str)289 std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr,
290 const StringPiece16& str) {
291 android::Res_value flags = { };
292 flags.dataType = android::Res_value::TYPE_INT_DEC;
293 flags.data = 0u;
294
295 if (util::trimWhitespace(str).empty()) {
296 // Empty string is a valid flag (0).
297 return util::make_unique<BinaryPrimitive>(flags);
298 }
299
300 for (StringPiece16 part : util::tokenize(str, u'|')) {
301 StringPiece16 trimmedPart = util::trimWhitespace(part);
302
303 bool flagSet = false;
304 for (const Attribute::Symbol& symbol : flagAttr->symbols) {
305 // Flag symbols are stored as @package:id/symbol resources,
306 // so we need to match against the 'entry' part of the identifier.
307 const ResourceName& flagSymbolResourceName = symbol.symbol.name.value();
308 if (trimmedPart == flagSymbolResourceName.entry) {
309 flags.data |= symbol.value;
310 flagSet = true;
311 break;
312 }
313 }
314
315 if (!flagSet) {
316 return {};
317 }
318 }
319 return util::make_unique<BinaryPrimitive>(flags);
320 }
321
parseHex(char16_t c,bool * outError)322 static uint32_t parseHex(char16_t c, bool* outError) {
323 if (c >= u'0' && c <= u'9') {
324 return c - u'0';
325 } else if (c >= u'a' && c <= u'f') {
326 return c - u'a' + 0xa;
327 } else if (c >= u'A' && c <= u'F') {
328 return c - u'A' + 0xa;
329 } else {
330 *outError = true;
331 return 0xffffffffu;
332 }
333 }
334
tryParseColor(const StringPiece16 & str)335 std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) {
336 StringPiece16 colorStr(util::trimWhitespace(str));
337 const char16_t* start = colorStr.data();
338 const size_t len = colorStr.size();
339 if (len == 0 || start[0] != u'#') {
340 return {};
341 }
342
343 android::Res_value value = { };
344 bool error = false;
345 if (len == 4) {
346 value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4;
347 value.data = 0xff000000u;
348 value.data |= parseHex(start[1], &error) << 20;
349 value.data |= parseHex(start[1], &error) << 16;
350 value.data |= parseHex(start[2], &error) << 12;
351 value.data |= parseHex(start[2], &error) << 8;
352 value.data |= parseHex(start[3], &error) << 4;
353 value.data |= parseHex(start[3], &error);
354 } else if (len == 5) {
355 value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4;
356 value.data |= parseHex(start[1], &error) << 28;
357 value.data |= parseHex(start[1], &error) << 24;
358 value.data |= parseHex(start[2], &error) << 20;
359 value.data |= parseHex(start[2], &error) << 16;
360 value.data |= parseHex(start[3], &error) << 12;
361 value.data |= parseHex(start[3], &error) << 8;
362 value.data |= parseHex(start[4], &error) << 4;
363 value.data |= parseHex(start[4], &error);
364 } else if (len == 7) {
365 value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8;
366 value.data = 0xff000000u;
367 value.data |= parseHex(start[1], &error) << 20;
368 value.data |= parseHex(start[2], &error) << 16;
369 value.data |= parseHex(start[3], &error) << 12;
370 value.data |= parseHex(start[4], &error) << 8;
371 value.data |= parseHex(start[5], &error) << 4;
372 value.data |= parseHex(start[6], &error);
373 } else if (len == 9) {
374 value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8;
375 value.data |= parseHex(start[1], &error) << 28;
376 value.data |= parseHex(start[2], &error) << 24;
377 value.data |= parseHex(start[3], &error) << 20;
378 value.data |= parseHex(start[4], &error) << 16;
379 value.data |= parseHex(start[5], &error) << 12;
380 value.data |= parseHex(start[6], &error) << 8;
381 value.data |= parseHex(start[7], &error) << 4;
382 value.data |= parseHex(start[8], &error);
383 } else {
384 return {};
385 }
386 return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value);
387 }
388
tryParseBool(const StringPiece16 & str,bool * outValue)389 bool tryParseBool(const StringPiece16& str, bool* outValue) {
390 StringPiece16 trimmedStr(util::trimWhitespace(str));
391 if (trimmedStr == u"true" || trimmedStr == u"TRUE" || trimmedStr == u"True") {
392 if (outValue) {
393 *outValue = true;
394 }
395 return true;
396 } else if (trimmedStr == u"false" || trimmedStr == u"FALSE" || trimmedStr == u"False") {
397 if (outValue) {
398 *outValue = false;
399 }
400 return true;
401 }
402 return false;
403 }
404
tryParseBool(const StringPiece16 & str)405 std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) {
406 bool result = false;
407 if (tryParseBool(str, &result)) {
408 android::Res_value value = {};
409 value.dataType = android::Res_value::TYPE_INT_BOOLEAN;
410
411 if (result) {
412 value.data = 0xffffffffu;
413 } else {
414 value.data = 0;
415 }
416 return util::make_unique<BinaryPrimitive>(value);
417 }
418 return {};
419 }
420
tryParseInt(const StringPiece16 & str)421 std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) {
422 android::Res_value value;
423 if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) {
424 return {};
425 }
426 return util::make_unique<BinaryPrimitive>(value);
427 }
428
tryParseFloat(const StringPiece16 & str)429 std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) {
430 android::Res_value value;
431 if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) {
432 return {};
433 }
434 return util::make_unique<BinaryPrimitive>(value);
435 }
436
androidTypeToAttributeTypeMask(uint16_t type)437 uint32_t androidTypeToAttributeTypeMask(uint16_t type) {
438 switch (type) {
439 case android::Res_value::TYPE_NULL:
440 case android::Res_value::TYPE_REFERENCE:
441 case android::Res_value::TYPE_ATTRIBUTE:
442 case android::Res_value::TYPE_DYNAMIC_REFERENCE:
443 return android::ResTable_map::TYPE_REFERENCE;
444
445 case android::Res_value::TYPE_STRING:
446 return android::ResTable_map::TYPE_STRING;
447
448 case android::Res_value::TYPE_FLOAT:
449 return android::ResTable_map::TYPE_FLOAT;
450
451 case android::Res_value::TYPE_DIMENSION:
452 return android::ResTable_map::TYPE_DIMENSION;
453
454 case android::Res_value::TYPE_FRACTION:
455 return android::ResTable_map::TYPE_FRACTION;
456
457 case android::Res_value::TYPE_INT_DEC:
458 case android::Res_value::TYPE_INT_HEX:
459 return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM
460 | android::ResTable_map::TYPE_FLAGS;
461
462 case android::Res_value::TYPE_INT_BOOLEAN:
463 return android::ResTable_map::TYPE_BOOLEAN;
464
465 case android::Res_value::TYPE_INT_COLOR_ARGB8:
466 case android::Res_value::TYPE_INT_COLOR_RGB8:
467 case android::Res_value::TYPE_INT_COLOR_ARGB4:
468 case android::Res_value::TYPE_INT_COLOR_RGB4:
469 return android::ResTable_map::TYPE_COLOR;
470
471 default:
472 return 0;
473 };
474 }
475
parseItemForAttribute(const StringPiece16 & value,uint32_t typeMask,std::function<void (const ResourceName &)> onCreateReference)476 std::unique_ptr<Item> parseItemForAttribute(
477 const StringPiece16& value, uint32_t typeMask,
478 std::function<void(const ResourceName&)> onCreateReference) {
479 std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value);
480 if (nullOrEmpty) {
481 return std::move(nullOrEmpty);
482 }
483
484 bool create = false;
485 std::unique_ptr<Reference> reference = tryParseReference(value, &create);
486 if (reference) {
487 if (create && onCreateReference) {
488 onCreateReference(reference->name.value());
489 }
490 return std::move(reference);
491 }
492
493 if (typeMask & android::ResTable_map::TYPE_COLOR) {
494 // Try parsing this as a color.
495 std::unique_ptr<BinaryPrimitive> color = tryParseColor(value);
496 if (color) {
497 return std::move(color);
498 }
499 }
500
501 if (typeMask & android::ResTable_map::TYPE_BOOLEAN) {
502 // Try parsing this as a boolean.
503 std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value);
504 if (boolean) {
505 return std::move(boolean);
506 }
507 }
508
509 if (typeMask & android::ResTable_map::TYPE_INTEGER) {
510 // Try parsing this as an integer.
511 std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value);
512 if (integer) {
513 return std::move(integer);
514 }
515 }
516
517 const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT
518 | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION;
519 if (typeMask & floatMask) {
520 // Try parsing this as a float.
521 std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value);
522 if (floatingPoint) {
523 if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) {
524 return std::move(floatingPoint);
525 }
526 }
527 }
528 return {};
529 }
530
531 /**
532 * We successively try to parse the string as a resource type that the Attribute
533 * allows.
534 */
parseItemForAttribute(const StringPiece16 & str,const Attribute * attr,std::function<void (const ResourceName &)> onCreateReference)535 std::unique_ptr<Item> parseItemForAttribute(
536 const StringPiece16& str, const Attribute* attr,
537 std::function<void(const ResourceName&)> onCreateReference) {
538 const uint32_t typeMask = attr->typeMask;
539 std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference);
540 if (value) {
541 return value;
542 }
543
544 if (typeMask & android::ResTable_map::TYPE_ENUM) {
545 // Try parsing this as an enum.
546 std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str);
547 if (enumValue) {
548 return std::move(enumValue);
549 }
550 }
551
552 if (typeMask & android::ResTable_map::TYPE_FLAGS) {
553 // Try parsing this as a flag.
554 std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str);
555 if (flagValue) {
556 return std::move(flagValue);
557 }
558 }
559 return {};
560 }
561
buildResourceFileName(const ResourceFile & resFile,const NameMangler * mangler)562 std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler) {
563 std::stringstream out;
564 out << "res/" << resFile.name.type;
565 if (resFile.config != ConfigDescription{}) {
566 out << "-" << resFile.config;
567 }
568 out << "/";
569
570 if (mangler && mangler->shouldMangle(resFile.name.package)) {
571 out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry);
572 } else {
573 out << resFile.name.entry;
574 }
575 out << file::getExtension(resFile.source.path);
576 return out.str();
577 }
578
579 } // namespace ResourceUtils
580 } // namespace aapt
581