/* * 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 "BinaryResourceParser.h" #include "Logger.h" #include "ResChunkPullParser.h" #include "Resolver.h" #include "ResourceParser.h" #include "ResourceTable.h" #include "ResourceTypeExtensions.h" #include "ResourceValues.h" #include "Source.h" #include "Util.h" #include <androidfw/ResourceTypes.h> #include <androidfw/TypeWrappers.h> #include <map> #include <string> namespace aapt { using namespace android; /* * Visitor that converts a reference's resource ID to a resource name, * given a mapping from resource ID to resource name. */ struct ReferenceIdToNameVisitor : ValueVisitor { ReferenceIdToNameVisitor(const std::shared_ptr<IResolver>& resolver, std::map<ResourceId, ResourceName>* cache) : mResolver(resolver), mCache(cache) { } void visit(Reference& reference, ValueVisitorArgs&) override { idToName(reference); } void visit(Attribute& attr, ValueVisitorArgs&) override { for (auto& entry : attr.symbols) { idToName(entry.symbol); } } void visit(Style& style, ValueVisitorArgs&) override { if (style.parent.id.isValid()) { idToName(style.parent); } for (auto& entry : style.entries) { idToName(entry.key); entry.value->accept(*this, {}); } } void visit(Styleable& styleable, ValueVisitorArgs&) override { for (auto& attr : styleable.entries) { idToName(attr); } } void visit(Array& array, ValueVisitorArgs&) override { for (auto& item : array.items) { item->accept(*this, {}); } } void visit(Plural& plural, ValueVisitorArgs&) override { for (auto& item : plural.values) { if (item) { item->accept(*this, {}); } } } private: void idToName(Reference& reference) { if (!reference.id.isValid()) { return; } auto cacheIter = mCache->find(reference.id); if (cacheIter != mCache->end()) { reference.name = cacheIter->second; reference.id = 0; } else { Maybe<ResourceName> result = mResolver->findName(reference.id); if (result) { reference.name = result.value(); // Add to cache. mCache->insert({reference.id, reference.name}); reference.id = 0; } } } std::shared_ptr<IResolver> mResolver; std::map<ResourceId, ResourceName>* mCache; }; BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table, const std::shared_ptr<IResolver>& resolver, const Source& source, const std::u16string& defaultPackage, const void* data, size_t len) : mTable(table), mResolver(resolver), mSource(source), mDefaultPackage(defaultPackage), mData(data), mDataLen(len) { } bool BinaryResourceParser::parse() { ResChunkPullParser parser(mData, mDataLen); bool error = false; while(ResChunkPullParser::isGoodEvent(parser.next())) { if (parser.getChunk()->type != android::RES_TABLE_TYPE) { Logger::warn(mSource) << "unknown chunk of type '" << parser.getChunk()->type << "'." << std::endl; continue; } error |= !parseTable(parser.getChunk()); } if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { Logger::error(mSource) << "bad document: " << parser.getLastError() << "." << std::endl; return false; } return !error; } bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) { if (!mSymbolEntries || mSymbolEntryCount == 0) { return false; } if (reinterpret_cast<uintptr_t>(data) < reinterpret_cast<uintptr_t>(mData)) { return false; } // We only support 32 bit offsets right now. const uintptr_t offset = reinterpret_cast<uintptr_t>(data) - reinterpret_cast<uintptr_t>(mData); if (offset > std::numeric_limits<uint32_t>::max()) { return false; } for (size_t i = 0; i < mSymbolEntryCount; i++) { if (mSymbolEntries[i].offset == offset) { // This offset is a symbol! const StringPiece16 str = util::getString(mSymbolPool, mSymbolEntries[i].stringIndex); StringPiece16 typeStr; ResourceParser::extractResourceName(str, &outSymbol->package, &typeStr, &outSymbol->entry); const ResourceType* type = parseResourceType(typeStr); if (!type) { return false; } if (outSymbol->package.empty()) { outSymbol->package = mTable->getPackage(); } outSymbol->type = *type; // Since we scan the symbol table in order, we can start looking for the // next symbol from this point. mSymbolEntryCount -= i + 1; mSymbolEntries += i + 1; return true; } } return false; } bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) { const SymbolTable_header* symbolTableHeader = convertTo<SymbolTable_header>(chunk); if (!symbolTableHeader) { Logger::error(mSource) << "could not parse chunk as SymbolTable_header." << std::endl; return false; } const size_t entrySizeBytes = symbolTableHeader->count * sizeof(SymbolTable_entry); if (entrySizeBytes > getChunkDataLen(symbolTableHeader->header)) { Logger::error(mSource) << "entries extend beyond chunk." << std::endl; return false; } mSymbolEntries = reinterpret_cast<const SymbolTable_entry*>( getChunkData(symbolTableHeader->header)); mSymbolEntryCount = symbolTableHeader->count; ResChunkPullParser parser(getChunkData(symbolTableHeader->header) + entrySizeBytes, getChunkDataLen(symbolTableHeader->header) - entrySizeBytes); if (!ResChunkPullParser::isGoodEvent(parser.next())) { Logger::error(mSource) << "failed to parse chunk: " << parser.getLastError() << "." << std::endl; return false; } if (parser.getChunk()->type != android::RES_STRING_POOL_TYPE) { Logger::error(mSource) << "expected Symbol string pool." << std::endl; return false; } if (mSymbolPool.setTo(parser.getChunk(), parser.getChunk()->size) != NO_ERROR) { Logger::error(mSource) << "failed to parse symbol string pool with code: " << mSymbolPool.getError() << "." << std::endl; return false; } return true; } bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) { const ResTable_header* tableHeader = convertTo<ResTable_header>(chunk); if (!tableHeader) { Logger::error(mSource) << "could not parse chunk as ResTable_header." << std::endl; return false; } ResChunkPullParser parser(getChunkData(tableHeader->header), getChunkDataLen(tableHeader->header)); while (ResChunkPullParser::isGoodEvent(parser.next())) { switch (parser.getChunk()->type) { case android::RES_STRING_POOL_TYPE: if (mValuePool.getError() == NO_INIT) { if (mValuePool.setTo(parser.getChunk(), parser.getChunk()->size) != NO_ERROR) { Logger::error(mSource) << "failed to parse value string pool with code: " << mValuePool.getError() << "." << std::endl; return false; } // Reserve some space for the strings we are going to add. mTable->getValueStringPool().hintWillAdd( mValuePool.size(), mValuePool.styleCount()); } else { Logger::warn(mSource) << "unexpected string pool." << std::endl; } break; case RES_TABLE_SYMBOL_TABLE_TYPE: if (!parseSymbolTable(parser.getChunk())) { return false; } break; case RES_TABLE_SOURCE_POOL_TYPE: { if (mSourcePool.setTo(getChunkData(*parser.getChunk()), getChunkDataLen(*parser.getChunk())) != NO_ERROR) { Logger::error(mSource) << "failed to parse source pool with code: " << mSourcePool.getError() << "." << std::endl; return false; } break; } case android::RES_TABLE_PACKAGE_TYPE: if (!parsePackage(parser.getChunk())) { return false; } break; default: Logger::warn(mSource) << "unexpected chunk of type " << parser.getChunk()->type << "." << std::endl; break; } } if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { Logger::error(mSource) << "bad resource table: " << parser.getLastError() << "." << std::endl; return false; } return true; } bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { if (mValuePool.getError() != NO_ERROR) { Logger::error(mSource) << "no value string pool for ResTable." << std::endl; return false; } const ResTable_package* packageHeader = convertTo<ResTable_package>(chunk); if (!packageHeader) { Logger::error(mSource) << "could not parse chunk as ResTable_header." << std::endl; return false; } if (mTable->getPackageId() == ResourceTable::kUnsetPackageId) { // This is the first time the table has it's package ID set. mTable->setPackageId(packageHeader->id); } else if (mTable->getPackageId() != packageHeader->id) { Logger::error(mSource) << "ResTable_package has package ID " << std::hex << packageHeader->id << std::dec << " but ResourceTable has package ID " << std::hex << mTable->getPackageId() << std::dec << std::endl; return false; } size_t len = strnlen16(reinterpret_cast<const char16_t*>(packageHeader->name), sizeof(packageHeader->name) / sizeof(packageHeader->name[0])); if (mTable->getPackage().empty() && len == 0) { mTable->setPackage(mDefaultPackage); } else if (len > 0) { StringPiece16 thisPackage(reinterpret_cast<const char16_t*>(packageHeader->name), len); if (mTable->getPackage().empty()) { mTable->setPackage(thisPackage); } else if (thisPackage != mTable->getPackage()) { Logger::error(mSource) << "incompatible packages: " << mTable->getPackage() << " vs. " << thisPackage << std::endl; return false; } } ResChunkPullParser parser(getChunkData(packageHeader->header), getChunkDataLen(packageHeader->header)); while (ResChunkPullParser::isGoodEvent(parser.next())) { switch (parser.getChunk()->type) { case android::RES_STRING_POOL_TYPE: if (mTypePool.getError() == NO_INIT) { if (mTypePool.setTo(parser.getChunk(), parser.getChunk()->size) != NO_ERROR) { Logger::error(mSource) << "failed to parse type string pool with code " << mTypePool.getError() << "." << std::endl; return false; } } else if (mKeyPool.getError() == NO_INIT) { if (mKeyPool.setTo(parser.getChunk(), parser.getChunk()->size) != NO_ERROR) { Logger::error(mSource) << "failed to parse key string pool with code " << mKeyPool.getError() << "." << std::endl; return false; } } else { Logger::warn(mSource) << "unexpected string pool." << std::endl; } break; case android::RES_TABLE_TYPE_SPEC_TYPE: if (!parseTypeSpec(parser.getChunk())) { return false; } break; case android::RES_TABLE_TYPE_TYPE: if (!parseType(parser.getChunk())) { return false; } break; case RES_TABLE_PUBLIC_TYPE: if (!parsePublic(parser.getChunk())) { return false; } break; default: Logger::warn(mSource) << "unexpected chunk of type " << parser.getChunk()->type << "." << std::endl; break; } } if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { Logger::error(mSource) << "bad package: " << parser.getLastError() << "." << std::endl; return false; } // Now go through the table and change resource ID references to // symbolic references. ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex); for (auto& type : *mTable) { for (auto& entry : type->entries) { for (auto& configValue : entry->values) { configValue.value->accept(visitor, {}); } } } return true; } bool BinaryResourceParser::parsePublic(const ResChunk_header* chunk) { const Public_header* header = convertTo<Public_header>(chunk); if (header->typeId == 0) { Logger::error(mSource) << "invalid type ID " << header->typeId << std::endl; return false; } const ResourceType* parsedType = parseResourceType(util::getString(mTypePool, header->typeId - 1)); if (!parsedType) { Logger::error(mSource) << "invalid type " << util::getString(mTypePool, header->typeId - 1) << std::endl; return false; } const uintptr_t chunkEnd = reinterpret_cast<uintptr_t>(chunk) + chunk->size; const Public_entry* entry = reinterpret_cast<const Public_entry*>( getChunkData(header->header)); for (uint32_t i = 0; i < header->count; i++) { if (reinterpret_cast<uintptr_t>(entry) + sizeof(*entry) > chunkEnd) { Logger::error(mSource) << "Public_entry extends beyond chunk." << std::endl; return false; } const ResourceId resId = { mTable->getPackageId(), header->typeId, entry->entryId }; const ResourceName name = { mTable->getPackage(), *parsedType, util::getString(mKeyPool, entry->key.index).toString() }; SourceLine source; if (mSourcePool.getError() == NO_ERROR) { source.path = util::utf16ToUtf8(util::getString(mSourcePool, entry->source.index)); source.line = entry->sourceLine; } if (!mTable->markPublicAllowMangled(name, resId, source)) { return false; } // Add this resource name->id mapping to the index so // that we can resolve all ID references to name references. auto cacheIter = mIdIndex.find(resId); if (cacheIter == mIdIndex.end()) { mIdIndex.insert({ resId, name }); } entry++; } return true; } bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) { if (mTypePool.getError() != NO_ERROR) { Logger::error(mSource) << "no type string pool available for ResTable_typeSpec." << std::endl; return false; } const ResTable_typeSpec* typeSpec = convertTo<ResTable_typeSpec>(chunk); if (!typeSpec) { Logger::error(mSource) << "could not parse chunk as ResTable_typeSpec." << std::endl; return false; } if (typeSpec->id == 0) { Logger::error(mSource) << "ResTable_typeSpec has invalid id: " << typeSpec->id << "." << std::endl; return false; } return true; } bool BinaryResourceParser::parseType(const ResChunk_header* chunk) { if (mTypePool.getError() != NO_ERROR) { Logger::error(mSource) << "no type string pool available for ResTable_typeSpec." << std::endl; return false; } if (mKeyPool.getError() != NO_ERROR) { Logger::error(mSource) << "no key string pool available for ResTable_type." << std::endl; return false; } const ResTable_type* type = convertTo<ResTable_type>(chunk); if (!type) { Logger::error(mSource) << "could not parse chunk as ResTable_type." << std::endl; return false; } if (type->id == 0) { Logger::error(mSource) << "ResTable_type has invalid id: " << type->id << "." << std::endl; return false; } const ConfigDescription config(type->config); const StringPiece16 typeName = util::getString(mTypePool, type->id - 1); const ResourceType* parsedType = parseResourceType(typeName); if (!parsedType) { Logger::error(mSource) << "invalid type name '" << typeName << "' for type with ID " << uint32_t(type->id) << "." << std::endl; return false; } android::TypeVariant tv(type); for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) { if (!*it) { continue; } const ResTable_entry* entry = *it; const ResourceName name = { mTable->getPackage(), *parsedType, util::getString(mKeyPool, entry->key.index).toString() }; const ResourceId resId = { mTable->getPackageId(), type->id, it.index() }; std::unique_ptr<Value> resourceValue; const ResTable_entry_source* sourceBlock = nullptr; if (entry->flags & ResTable_entry::FLAG_COMPLEX) { const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry); if (mapEntry->size - sizeof(*mapEntry) == sizeof(*sourceBlock)) { const uint8_t* data = reinterpret_cast<const uint8_t*>(mapEntry); data += mapEntry->size - sizeof(*sourceBlock); sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data); } // TODO(adamlesinski): Check that the entry count is valid. resourceValue = parseMapEntry(name, config, mapEntry); } else { if (entry->size - sizeof(*entry) == sizeof(*sourceBlock)) { const uint8_t* data = reinterpret_cast<const uint8_t*>(entry); data += entry->size - sizeof(*sourceBlock); sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data); } const Res_value* value = reinterpret_cast<const Res_value*>( reinterpret_cast<const uint8_t*>(entry) + entry->size); resourceValue = parseValue(name, config, value, entry->flags); } if (!resourceValue) { // TODO(adamlesinski): For now this is ok, but it really shouldn't be. continue; } SourceLine source = mSource.line(0); if (sourceBlock) { size_t len; const char* str = mSourcePool.string8At(sourceBlock->pathIndex, &len); if (str) { source.path.assign(str, len); } source.line = sourceBlock->line; } if (!mTable->addResourceAllowMangled(name, config, source, std::move(resourceValue))) { return false; } if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) { if (!mTable->markPublicAllowMangled(name, resId, mSource.line(0))) { return false; } } // Add this resource name->id mapping to the index so // that we can resolve all ID references to name references. auto cacheIter = mIdIndex.find(resId); if (cacheIter == mIdIndex.end()) { mIdIndex.insert({ resId, name }); } } return true; } std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& name, const ConfigDescription& config, const Res_value* value, uint16_t flags) { if (name.type == ResourceType::kId) { return util::make_unique<Id>(); } if (value->dataType == Res_value::TYPE_STRING) { StringPiece16 str = util::getString(mValuePool, value->data); const ResStringPool_span* spans = mValuePool.styleAt(value->data); if (spans != nullptr) { StyleString styleStr = { str.toString() }; while (spans->name.index != ResStringPool_span::END) { styleStr.spans.push_back(Span{ util::getString(mValuePool, spans->name.index).toString(), spans->firstChar, spans->lastChar }); spans++; } return util::make_unique<StyledString>( mTable->getValueStringPool().makeRef( styleStr, StringPool::Context{1, config})); } else { if (name.type != ResourceType::kString && util::stringStartsWith<char16_t>(str, u"res/")) { // This must be a FileReference. return util::make_unique<FileReference>(mTable->getValueStringPool().makeRef( str, StringPool::Context{ 0, config })); } // There are no styles associated with this string, so treat it as // a simple string. return util::make_unique<String>( mTable->getValueStringPool().makeRef( str, StringPool::Context{1, config})); } } if (value->dataType == Res_value::TYPE_REFERENCE || value->dataType == Res_value::TYPE_ATTRIBUTE) { const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ? Reference::Type::kResource : Reference::Type::kAttribute; if (value->data != 0) { // This is a normal reference. return util::make_unique<Reference>(value->data, type); } // This reference has an invalid ID. Check if it is an unresolved symbol. ResourceNameRef symbol; if (getSymbol(&value->data, &symbol)) { return util::make_unique<Reference>(symbol, type); } // This is not an unresolved symbol, so it must be the magic @null reference. Res_value nullType = {}; nullType.dataType = Res_value::TYPE_REFERENCE; return util::make_unique<BinaryPrimitive>(nullType); } if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) { return util::make_unique<RawString>( mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data), StringPool::Context{ 1, config })); } // Treat this as a raw binary primitive. return util::make_unique<BinaryPrimitive>(*value); } std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { switch (name.type) { case ResourceType::kStyle: return parseStyle(name, config, map); case ResourceType::kAttr: return parseAttr(name, config, map); case ResourceType::kArray: return parseArray(name, config, map); case ResourceType::kStyleable: return parseStyleable(name, config, map); case ResourceType::kPlurals: return parsePlural(name, config, map); default: break; } return {}; } std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { std::unique_ptr<Style> style = util::make_unique<Style>(); if (map->parent.ident == 0) { // The parent is either not set or it is an unresolved symbol. // Check to see if it is a symbol. ResourceNameRef symbol; if (getSymbol(&map->parent.ident, &symbol)) { style->parent.name = symbol.toResourceName(); } } else { // The parent is a regular reference to a resource. style->parent.id = map->parent.ident; } for (const ResTable_map& mapEntry : map) { style->entries.emplace_back(); Style::Entry& styleEntry = style->entries.back(); if (mapEntry.name.ident == 0) { // The map entry's key (attribute) is not set. This must be // a symbol reference, so resolve it. ResourceNameRef symbol; bool result = getSymbol(&mapEntry.name.ident, &symbol); assert(result); styleEntry.key.name = symbol.toResourceName(); } else { // The map entry's key (attribute) is a regular reference. styleEntry.key.id = mapEntry.name.ident; } // Parse the attribute's value. styleEntry.value = parseValue(name, config, &mapEntry.value, 0); assert(styleEntry.value); } return style; } std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0; std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak); // First we must discover what type of attribute this is. Find the type mask. auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool { return entry.name.ident == ResTable_map::ATTR_TYPE; }); if (typeMaskIter != end(map)) { attr->typeMask = typeMaskIter->value.data; } if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { for (const ResTable_map& mapEntry : map) { if (Res_INTERNALID(mapEntry.name.ident)) { continue; } Attribute::Symbol symbol; symbol.value = mapEntry.value.data; if (mapEntry.name.ident == 0) { // The map entry's key (id) is not set. This must be // a symbol reference, so resolve it. ResourceNameRef symbolName; bool result = getSymbol(&mapEntry.name.ident, &symbolName); assert(result); symbol.symbol.name = symbolName.toResourceName(); } else { // The map entry's key (id) is a regular reference. symbol.symbol.id = mapEntry.name.ident; } attr->symbols.push_back(std::move(symbol)); } } // TODO(adamlesinski): Find min, max, i80n, etc attributes. return attr; } std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { std::unique_ptr<Array> array = util::make_unique<Array>(); for (const ResTable_map& mapEntry : map) { array->items.push_back(parseValue(name, config, &mapEntry.value, 0)); } return array; } std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); for (const ResTable_map& mapEntry : map) { if (mapEntry.name.ident == 0) { // The map entry's key (attribute) is not set. This must be // a symbol reference, so resolve it. ResourceNameRef symbol; bool result = getSymbol(&mapEntry.name.ident, &symbol); assert(result); styleable->entries.emplace_back(symbol); } else { // The map entry's key (attribute) is a regular reference. styleable->entries.emplace_back(mapEntry.name.ident); } } return styleable; } std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name, const ConfigDescription& config, const ResTable_map_entry* map) { std::unique_ptr<Plural> plural = util::make_unique<Plural>(); for (const ResTable_map& mapEntry : map) { std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0); switch (mapEntry.name.ident) { case android::ResTable_map::ATTR_ZERO: plural->values[Plural::Zero] = std::move(item); break; case android::ResTable_map::ATTR_ONE: plural->values[Plural::One] = std::move(item); break; case android::ResTable_map::ATTR_TWO: plural->values[Plural::Two] = std::move(item); break; case android::ResTable_map::ATTR_FEW: plural->values[Plural::Few] = std::move(item); break; case android::ResTable_map::ATTR_MANY: plural->values[Plural::Many] = std::move(item); break; case android::ResTable_map::ATTR_OTHER: plural->values[Plural::Other] = std::move(item); break; } } return plural; } } // namespace aapt