| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include "NameMangler.h" |
| #include "ResourceUtils.h" |
| #include "flatten/ResourceTypeExtensions.h" |
| #include "util/Files.h" |
| #include "util/Util.h" |
| |
| #include <androidfw/ResourceTypes.h> |
| #include <sstream> |
| |
| namespace aapt { |
| namespace ResourceUtils { |
| |
| bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage, |
| StringPiece16* outType, StringPiece16* outEntry) { |
| bool hasPackageSeparator = false; |
| bool hasTypeSeparator = false; |
| const char16_t* start = str.data(); |
| const char16_t* end = start + str.size(); |
| const char16_t* current = start; |
| while (current != end) { |
| if (outType->size() == 0 && *current == u'/') { |
| hasTypeSeparator = true; |
| outType->assign(start, current - start); |
| start = current + 1; |
| } else if (outPackage->size() == 0 && *current == u':') { |
| hasPackageSeparator = true; |
| outPackage->assign(start, current - start); |
| start = current + 1; |
| } |
| current++; |
| } |
| outEntry->assign(start, end - start); |
| |
| return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty()); |
| } |
| |
| bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) { |
| if (str.empty()) { |
| return false; |
| } |
| |
| size_t offset = 0; |
| bool priv = false; |
| if (str.data()[0] == u'*') { |
| priv = true; |
| offset = 1; |
| } |
| |
| StringPiece16 package; |
| StringPiece16 type; |
| StringPiece16 entry; |
| if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) { |
| return false; |
| } |
| |
| const ResourceType* parsedType = parseResourceType(type); |
| if (!parsedType) { |
| return false; |
| } |
| |
| if (entry.empty()) { |
| return false; |
| } |
| |
| if (outRef) { |
| outRef->package = package; |
| outRef->type = *parsedType; |
| outRef->entry = entry; |
| } |
| |
| if (outPrivate) { |
| *outPrivate = priv; |
| } |
| return true; |
| } |
| |
| bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate, |
| bool* outPrivate) { |
| StringPiece16 trimmedStr(util::trimWhitespace(str)); |
| if (trimmedStr.empty()) { |
| return false; |
| } |
| |
| bool create = false; |
| bool priv = false; |
| if (trimmedStr.data()[0] == u'@') { |
| size_t offset = 1; |
| if (trimmedStr.data()[1] == u'+') { |
| create = true; |
| offset += 1; |
| } |
| |
| ResourceNameRef name; |
| if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), |
| &name, &priv)) { |
| return false; |
| } |
| |
| if (create && priv) { |
| return false; |
| } |
| |
| if (create && name.type != ResourceType::kId) { |
| return false; |
| } |
| |
| if (outRef) { |
| *outRef = name; |
| } |
| |
| if (outCreate) { |
| *outCreate = create; |
| } |
| |
| if (outPrivate) { |
| *outPrivate = priv; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| bool isReference(const StringPiece16& str) { |
| return tryParseReference(str, nullptr, nullptr, nullptr); |
| } |
| |
| bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRef) { |
| StringPiece16 trimmedStr(util::trimWhitespace(str)); |
| if (trimmedStr.empty()) { |
| return false; |
| } |
| |
| if (*trimmedStr.data() == u'?') { |
| StringPiece16 package; |
| StringPiece16 type; |
| StringPiece16 entry; |
| if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), |
| &package, &type, &entry)) { |
| return false; |
| } |
| |
| if (!type.empty() && type != u"attr") { |
| return false; |
| } |
| |
| if (entry.empty()) { |
| return false; |
| } |
| |
| if (outRef) { |
| outRef->package = package; |
| outRef->type = ResourceType::kAttr; |
| outRef->entry = entry; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| bool isAttributeReference(const StringPiece16& str) { |
| return tryParseAttributeReference(str, nullptr); |
| } |
| |
| /* |
| * Style parent's are a bit different. We accept the following formats: |
| * |
| * @[[*]package:][style/]<entry> |
| * ?[[*]package:]style/<entry> |
| * <[*]package>:[style/]<entry> |
| * [[*]package:style/]<entry> |
| */ |
| Maybe<Reference> parseStyleParentReference(const StringPiece16& str, std::string* outError) { |
| if (str.empty()) { |
| return {}; |
| } |
| |
| StringPiece16 name = str; |
| |
| bool hasLeadingIdentifiers = false; |
| bool privateRef = false; |
| |
| // Skip over these identifiers. A style's parent is a normal reference. |
| if (name.data()[0] == u'@' || name.data()[0] == u'?') { |
| hasLeadingIdentifiers = true; |
| name = name.substr(1, name.size() - 1); |
| } |
| |
| if (name.data()[0] == u'*') { |
| privateRef = true; |
| name = name.substr(1, name.size() - 1); |
| } |
| |
| ResourceNameRef ref; |
| ref.type = ResourceType::kStyle; |
| |
| StringPiece16 typeStr; |
| extractResourceName(name, &ref.package, &typeStr, &ref.entry); |
| if (!typeStr.empty()) { |
| // If we have a type, make sure it is a Style. |
| const ResourceType* parsedType = parseResourceType(typeStr); |
| if (!parsedType || *parsedType != ResourceType::kStyle) { |
| std::stringstream err; |
| err << "invalid resource type '" << typeStr << "' for parent of style"; |
| *outError = err.str(); |
| return {}; |
| } |
| } |
| |
| if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) { |
| std::stringstream err; |
| err << "invalid parent reference '" << str << "'"; |
| *outError = err.str(); |
| return {}; |
| } |
| |
| Reference result(ref); |
| result.privateReference = privateRef; |
| return result; |
| } |
| |
| std::unique_ptr<Reference> tryParseReference(const StringPiece16& str, bool* outCreate) { |
| ResourceNameRef ref; |
| bool privateRef = false; |
| if (tryParseReference(str, &ref, outCreate, &privateRef)) { |
| std::unique_ptr<Reference> value = util::make_unique<Reference>(ref); |
| value->privateReference = privateRef; |
| return value; |
| } |
| |
| if (tryParseAttributeReference(str, &ref)) { |
| if (outCreate) { |
| *outCreate = false; |
| } |
| return util::make_unique<Reference>(ref, Reference::Type::kAttribute); |
| } |
| return {}; |
| } |
| |
| std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str) { |
| StringPiece16 trimmedStr(util::trimWhitespace(str)); |
| android::Res_value value = { }; |
| if (trimmedStr == u"@null") { |
| // TYPE_NULL with data set to 0 is interpreted by the runtime as an error. |
| // Instead we set the data type to TYPE_REFERENCE with a value of 0. |
| value.dataType = android::Res_value::TYPE_REFERENCE; |
| } else if (trimmedStr == u"@empty") { |
| // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime. |
| value.dataType = android::Res_value::TYPE_NULL; |
| value.data = android::Res_value::DATA_NULL_EMPTY; |
| } else { |
| return {}; |
| } |
| return util::make_unique<BinaryPrimitive>(value); |
| } |
| |
| std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr, |
| const StringPiece16& str) { |
| StringPiece16 trimmedStr(util::trimWhitespace(str)); |
| for (const Attribute::Symbol& symbol : enumAttr->symbols) { |
| // Enum symbols are stored as @package:id/symbol resources, |
| // so we need to match against the 'entry' part of the identifier. |
| const ResourceName& enumSymbolResourceName = symbol.symbol.name.value(); |
| if (trimmedStr == enumSymbolResourceName.entry) { |
| android::Res_value value = { }; |
| value.dataType = android::Res_value::TYPE_INT_DEC; |
| value.data = symbol.value; |
| return util::make_unique<BinaryPrimitive>(value); |
| } |
| } |
| return {}; |
| } |
| |
| std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr, |
| const StringPiece16& str) { |
| android::Res_value flags = { }; |
| flags.dataType = android::Res_value::TYPE_INT_DEC; |
| flags.data = 0u; |
| |
| if (util::trimWhitespace(str).empty()) { |
| // Empty string is a valid flag (0). |
| return util::make_unique<BinaryPrimitive>(flags); |
| } |
| |
| for (StringPiece16 part : util::tokenize(str, u'|')) { |
| StringPiece16 trimmedPart = util::trimWhitespace(part); |
| |
| bool flagSet = false; |
| for (const Attribute::Symbol& symbol : flagAttr->symbols) { |
| // Flag symbols are stored as @package:id/symbol resources, |
| // so we need to match against the 'entry' part of the identifier. |
| const ResourceName& flagSymbolResourceName = symbol.symbol.name.value(); |
| if (trimmedPart == flagSymbolResourceName.entry) { |
| flags.data |= symbol.value; |
| flagSet = true; |
| break; |
| } |
| } |
| |
| if (!flagSet) { |
| return {}; |
| } |
| } |
| return util::make_unique<BinaryPrimitive>(flags); |
| } |
| |
| static uint32_t parseHex(char16_t c, bool* outError) { |
| if (c >= u'0' && c <= u'9') { |
| return c - u'0'; |
| } else if (c >= u'a' && c <= u'f') { |
| return c - u'a' + 0xa; |
| } else if (c >= u'A' && c <= u'F') { |
| return c - u'A' + 0xa; |
| } else { |
| *outError = true; |
| return 0xffffffffu; |
| } |
| } |
| |
| std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str) { |
| StringPiece16 colorStr(util::trimWhitespace(str)); |
| const char16_t* start = colorStr.data(); |
| const size_t len = colorStr.size(); |
| if (len == 0 || start[0] != u'#') { |
| return {}; |
| } |
| |
| android::Res_value value = { }; |
| bool error = false; |
| if (len == 4) { |
| value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4; |
| value.data = 0xff000000u; |
| value.data |= parseHex(start[1], &error) << 20; |
| value.data |= parseHex(start[1], &error) << 16; |
| value.data |= parseHex(start[2], &error) << 12; |
| value.data |= parseHex(start[2], &error) << 8; |
| value.data |= parseHex(start[3], &error) << 4; |
| value.data |= parseHex(start[3], &error); |
| } else if (len == 5) { |
| value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4; |
| value.data |= parseHex(start[1], &error) << 28; |
| value.data |= parseHex(start[1], &error) << 24; |
| value.data |= parseHex(start[2], &error) << 20; |
| value.data |= parseHex(start[2], &error) << 16; |
| value.data |= parseHex(start[3], &error) << 12; |
| value.data |= parseHex(start[3], &error) << 8; |
| value.data |= parseHex(start[4], &error) << 4; |
| value.data |= parseHex(start[4], &error); |
| } else if (len == 7) { |
| value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8; |
| value.data = 0xff000000u; |
| value.data |= parseHex(start[1], &error) << 20; |
| value.data |= parseHex(start[2], &error) << 16; |
| value.data |= parseHex(start[3], &error) << 12; |
| value.data |= parseHex(start[4], &error) << 8; |
| value.data |= parseHex(start[5], &error) << 4; |
| value.data |= parseHex(start[6], &error); |
| } else if (len == 9) { |
| value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8; |
| value.data |= parseHex(start[1], &error) << 28; |
| value.data |= parseHex(start[2], &error) << 24; |
| value.data |= parseHex(start[3], &error) << 20; |
| value.data |= parseHex(start[4], &error) << 16; |
| value.data |= parseHex(start[5], &error) << 12; |
| value.data |= parseHex(start[6], &error) << 8; |
| value.data |= parseHex(start[7], &error) << 4; |
| value.data |= parseHex(start[8], &error); |
| } else { |
| return {}; |
| } |
| return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value); |
| } |
| |
| bool tryParseBool(const StringPiece16& str, bool* outValue) { |
| StringPiece16 trimmedStr(util::trimWhitespace(str)); |
| if (trimmedStr == u"true" || trimmedStr == u"TRUE" || trimmedStr == u"True") { |
| if (outValue) { |
| *outValue = true; |
| } |
| return true; |
| } else if (trimmedStr == u"false" || trimmedStr == u"FALSE" || trimmedStr == u"False") { |
| if (outValue) { |
| *outValue = false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str) { |
| bool result = false; |
| if (tryParseBool(str, &result)) { |
| android::Res_value value = {}; |
| value.dataType = android::Res_value::TYPE_INT_BOOLEAN; |
| |
| if (result) { |
| value.data = 0xffffffffu; |
| } else { |
| value.data = 0; |
| } |
| return util::make_unique<BinaryPrimitive>(value); |
| } |
| return {}; |
| } |
| |
| std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str) { |
| android::Res_value value; |
| if (!android::ResTable::stringToInt(str.data(), str.size(), &value)) { |
| return {}; |
| } |
| return util::make_unique<BinaryPrimitive>(value); |
| } |
| |
| std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str) { |
| android::Res_value value; |
| if (!android::ResTable::stringToFloat(str.data(), str.size(), &value)) { |
| return {}; |
| } |
| return util::make_unique<BinaryPrimitive>(value); |
| } |
| |
| uint32_t androidTypeToAttributeTypeMask(uint16_t type) { |
| switch (type) { |
| case android::Res_value::TYPE_NULL: |
| case android::Res_value::TYPE_REFERENCE: |
| case android::Res_value::TYPE_ATTRIBUTE: |
| case android::Res_value::TYPE_DYNAMIC_REFERENCE: |
| return android::ResTable_map::TYPE_REFERENCE; |
| |
| case android::Res_value::TYPE_STRING: |
| return android::ResTable_map::TYPE_STRING; |
| |
| case android::Res_value::TYPE_FLOAT: |
| return android::ResTable_map::TYPE_FLOAT; |
| |
| case android::Res_value::TYPE_DIMENSION: |
| return android::ResTable_map::TYPE_DIMENSION; |
| |
| case android::Res_value::TYPE_FRACTION: |
| return android::ResTable_map::TYPE_FRACTION; |
| |
| case android::Res_value::TYPE_INT_DEC: |
| case android::Res_value::TYPE_INT_HEX: |
| return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM |
| | android::ResTable_map::TYPE_FLAGS; |
| |
| case android::Res_value::TYPE_INT_BOOLEAN: |
| return android::ResTable_map::TYPE_BOOLEAN; |
| |
| case android::Res_value::TYPE_INT_COLOR_ARGB8: |
| case android::Res_value::TYPE_INT_COLOR_RGB8: |
| case android::Res_value::TYPE_INT_COLOR_ARGB4: |
| case android::Res_value::TYPE_INT_COLOR_RGB4: |
| return android::ResTable_map::TYPE_COLOR; |
| |
| default: |
| return 0; |
| }; |
| } |
| |
| std::unique_ptr<Item> parseItemForAttribute( |
| const StringPiece16& value, uint32_t typeMask, |
| std::function<void(const ResourceName&)> onCreateReference) { |
| std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value); |
| if (nullOrEmpty) { |
| return std::move(nullOrEmpty); |
| } |
| |
| bool create = false; |
| std::unique_ptr<Reference> reference = tryParseReference(value, &create); |
| if (reference) { |
| if (create && onCreateReference) { |
| onCreateReference(reference->name.value()); |
| } |
| return std::move(reference); |
| } |
| |
| if (typeMask & android::ResTable_map::TYPE_COLOR) { |
| // Try parsing this as a color. |
| std::unique_ptr<BinaryPrimitive> color = tryParseColor(value); |
| if (color) { |
| return std::move(color); |
| } |
| } |
| |
| if (typeMask & android::ResTable_map::TYPE_BOOLEAN) { |
| // Try parsing this as a boolean. |
| std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value); |
| if (boolean) { |
| return std::move(boolean); |
| } |
| } |
| |
| if (typeMask & android::ResTable_map::TYPE_INTEGER) { |
| // Try parsing this as an integer. |
| std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value); |
| if (integer) { |
| return std::move(integer); |
| } |
| } |
| |
| const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT |
| | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION; |
| if (typeMask & floatMask) { |
| // Try parsing this as a float. |
| std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value); |
| if (floatingPoint) { |
| if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) { |
| return std::move(floatingPoint); |
| } |
| } |
| } |
| return {}; |
| } |
| |
| /** |
| * We successively try to parse the string as a resource type that the Attribute |
| * allows. |
| */ |
| std::unique_ptr<Item> parseItemForAttribute( |
| const StringPiece16& str, const Attribute* attr, |
| std::function<void(const ResourceName&)> onCreateReference) { |
| const uint32_t typeMask = attr->typeMask; |
| std::unique_ptr<Item> value = parseItemForAttribute(str, typeMask, onCreateReference); |
| if (value) { |
| return value; |
| } |
| |
| if (typeMask & android::ResTable_map::TYPE_ENUM) { |
| // Try parsing this as an enum. |
| std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str); |
| if (enumValue) { |
| return std::move(enumValue); |
| } |
| } |
| |
| if (typeMask & android::ResTable_map::TYPE_FLAGS) { |
| // Try parsing this as a flag. |
| std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str); |
| if (flagValue) { |
| return std::move(flagValue); |
| } |
| } |
| return {}; |
| } |
| |
| std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler) { |
| std::stringstream out; |
| out << "res/" << resFile.name.type; |
| if (resFile.config != ConfigDescription{}) { |
| out << "-" << resFile.config; |
| } |
| out << "/"; |
| |
| if (mangler && mangler->shouldMangle(resFile.name.package)) { |
| out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry); |
| } else { |
| out << resFile.name.entry; |
| } |
| out << file::getExtension(resFile.source.path); |
| return out.str(); |
| } |
| |
| } // namespace ResourceUtils |
| } // namespace aapt |