/* * 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