/* * 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 "unflatten/BinaryResourceParser.h" #include #include #include #include "android-base/logging.h" #include "android-base/macros.h" #include "androidfw/ResourceTypes.h" #include "androidfw/TypeWrappers.h" #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" #include "Source.h" #include "ValueVisitor.h" #include "unflatten/ResChunkPullParser.h" #include "util/Util.h" namespace aapt { using namespace android; namespace { /* * Visitor that converts a reference's resource ID to a resource name, * given a mapping from resource ID to resource name. */ class ReferenceIdToNameVisitor : public ValueVisitor { public: using ValueVisitor::Visit; explicit ReferenceIdToNameVisitor( const std::map* mapping) : mapping_(mapping) { CHECK(mapping_ != nullptr); } void Visit(Reference* reference) override { if (!reference->id || !reference->id.value().is_valid()) { return; } ResourceId id = reference->id.value(); auto cache_iter = mapping_->find(id); if (cache_iter != mapping_->end()) { reference->name = cache_iter->second; } } private: DISALLOW_COPY_AND_ASSIGN(ReferenceIdToNameVisitor); const std::map* mapping_; }; } // namespace BinaryResourceParser::BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source, const void* data, size_t len) : context_(context), table_(table), source_(source), data_(data), data_len_(len) {} bool BinaryResourceParser::Parse() { ResChunkPullParser parser(data_, data_len_); bool error = false; while (ResChunkPullParser::IsGoodEvent(parser.Next())) { if (parser.chunk()->type != android::RES_TABLE_TYPE) { context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unknown chunk of type '" << (int)parser.chunk()->type << "'"); continue; } if (!ParseTable(parser.chunk())) { error = true; } } if (parser.event() == ResChunkPullParser::Event::kBadDocument) { context_->GetDiagnostics()->Error( DiagMessage(source_) << "corrupt resource table: " << parser.error()); return false; } return !error; } /** * Parses the resource table, which contains all the packages, types, and * entries. */ bool BinaryResourceParser::ParseTable(const ResChunk_header* chunk) { const ResTable_header* table_header = ConvertTo(chunk); if (!table_header) { context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_header chunk"); return false; } ResChunkPullParser parser(GetChunkData(&table_header->header), GetChunkDataLen(&table_header->header)); while (ResChunkPullParser::IsGoodEvent(parser.Next())) { switch (util::DeviceToHost16(parser.chunk()->type)) { case android::RES_STRING_POOL_TYPE: if (value_pool_.getError() == NO_INIT) { status_t err = value_pool_.setTo( parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { context_->GetDiagnostics()->Error( DiagMessage(source_) << "corrupt string pool in ResTable: " << value_pool_.getError()); return false; } // Reserve some space for the strings we are going to add. table_->string_pool.HintWillAdd(value_pool_.size(), value_pool_.styleCount()); } else { context_->GetDiagnostics()->Warn( DiagMessage(source_) << "unexpected string pool in ResTable"); } break; case android::RES_TABLE_PACKAGE_TYPE: if (!ParsePackage(parser.chunk())) { return false; } break; default: context_->GetDiagnostics()->Warn( DiagMessage(source_) << "unexpected chunk type " << (int)util::DeviceToHost16(parser.chunk()->type)); break; } } if (parser.event() == ResChunkPullParser::Event::kBadDocument) { context_->GetDiagnostics()->Error( DiagMessage(source_) << "corrupt resource table: " << parser.error()); return false; } return true; } bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { const ResTable_package* package_header = ConvertTo(chunk); if (!package_header) { context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_package chunk"); return false; } uint32_t package_id = util::DeviceToHost32(package_header->id); if (package_id > std::numeric_limits::max()) { context_->GetDiagnostics()->Error( DiagMessage(source_) << "package ID is too big (" << package_id << ")"); return false; } // Extract the package name. size_t len = strnlen16((const char16_t*)package_header->name, arraysize(package_header->name)); std::u16string package_name; package_name.resize(len); for (size_t i = 0; i < len; i++) { package_name[i] = util::DeviceToHost16(package_header->name[i]); } ResourceTablePackage* package = table_->CreatePackage( util::Utf16ToUtf8(package_name), static_cast(package_id)); if (!package) { context_->GetDiagnostics()->Error( DiagMessage(source_) << "incompatible package '" << package_name << "' with ID " << package_id); return false; } // There can be multiple packages in a table, so // clear the type and key pool in case they were set from a previous package. type_pool_.uninit(); key_pool_.uninit(); ResChunkPullParser parser(GetChunkData(&package_header->header), GetChunkDataLen(&package_header->header)); while (ResChunkPullParser::IsGoodEvent(parser.Next())) { switch (util::DeviceToHost16(parser.chunk()->type)) { case android::RES_STRING_POOL_TYPE: if (type_pool_.getError() == NO_INIT) { status_t err = type_pool_.setTo( parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt type string pool in " << "ResTable_package: " << type_pool_.getError()); return false; } } else if (key_pool_.getError() == NO_INIT) { status_t err = key_pool_.setTo( parser.chunk(), util::DeviceToHost32(parser.chunk()->size)); if (err != NO_ERROR) { context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt key string pool in " << "ResTable_package: " << key_pool_.getError()); return false; } } else { context_->GetDiagnostics()->Warn(DiagMessage(source_) << "unexpected string pool"); } break; case android::RES_TABLE_TYPE_SPEC_TYPE: if (!ParseTypeSpec(parser.chunk())) { return false; } break; case android::RES_TABLE_TYPE_TYPE: if (!ParseType(package, parser.chunk())) { return false; } break; default: context_->GetDiagnostics()->Warn( DiagMessage(source_) << "unexpected chunk type " << (int)util::DeviceToHost16(parser.chunk()->type)); break; } } if (parser.event() == ResChunkPullParser::Event::kBadDocument) { context_->GetDiagnostics()->Error( DiagMessage(source_) << "corrupt ResTable_package: " << parser.error()); return false; } // Now go through the table and change local resource ID references to // symbolic references. ReferenceIdToNameVisitor visitor(&id_index_); VisitAllValuesInTable(table_, &visitor); return true; } bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) { if (type_pool_.getError() != NO_ERROR) { context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool"); return false; } const ResTable_typeSpec* type_spec = ConvertTo(chunk); if (!type_spec) { context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_typeSpec chunk"); return false; } if (type_spec->id == 0) { context_->GetDiagnostics()->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id); return false; } return true; } bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, const ResChunk_header* chunk) { if (type_pool_.getError() != NO_ERROR) { context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing type string pool"); return false; } if (key_pool_.getError() != NO_ERROR) { context_->GetDiagnostics()->Error(DiagMessage(source_) << "missing key string pool"); return false; } const ResTable_type* type = ConvertTo(chunk); if (!type) { context_->GetDiagnostics()->Error(DiagMessage(source_) << "corrupt ResTable_type chunk"); return false; } if (type->id == 0) { context_->GetDiagnostics()->Error(DiagMessage(source_) << "ResTable_type has invalid id: " << (int)type->id); return false; } ConfigDescription config; config.copyFromDtoH(type->config); const std::string type_str = util::GetString(type_pool_, type->id - 1); const ResourceType* parsed_type = ParseResourceType(type_str); if (!parsed_type) { context_->GetDiagnostics()->Error( DiagMessage(source_) << "invalid type name '" << type_str << "' for type with ID " << (int)type->id); return false; } TypeVariant tv(type); for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) { const ResTable_entry* entry = *it; if (!entry) { continue; } const ResourceName name( package->name, *parsed_type, util::GetString(key_pool_, util::DeviceToHost32(entry->key.index))); const ResourceId res_id(package->id.value(), type->id, static_cast(it.index())); std::unique_ptr resource_value; if (entry->flags & ResTable_entry::FLAG_COMPLEX) { const ResTable_map_entry* mapEntry = static_cast(entry); // TODO(adamlesinski): Check that the entry count is valid. resource_value = ParseMapEntry(name, config, mapEntry); } else { const Res_value* value = (const Res_value*)((const uint8_t*)entry + util::DeviceToHost32(entry->size)); resource_value = ParseValue(name, config, value, entry->flags); } if (!resource_value) { context_->GetDiagnostics()->Error( DiagMessage(source_) << "failed to parse value for resource " << name << " (" << res_id << ") with configuration '" << config << "'"); return false; } if (!table_->AddResourceAllowMangled(name, config, {}, std::move(resource_value), context_->GetDiagnostics())) { return false; } if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) { Symbol symbol; symbol.state = SymbolState::kPublic; symbol.source = source_.WithLine(0); if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, context_->GetDiagnostics())) { return false; } } // Add this resource name->id mapping to the index so // that we can resolve all ID references to name references. auto cache_iter = id_index_.find(res_id); if (cache_iter == id_index_.end()) { id_index_.insert({res_id, name}); } } return true; } std::unique_ptr BinaryResourceParser::ParseValue( const ResourceNameRef& name, const ConfigDescription& config, const Res_value* value, uint16_t flags) { if (name.type == ResourceType::kId) { return util::make_unique(); } const uint32_t data = util::DeviceToHost32(value->data); if (value->dataType == Res_value::TYPE_STRING) { const std::string str = util::GetString(value_pool_, data); const ResStringPool_span* spans = value_pool_.styleAt(data); // Check if the string has a valid style associated with it. if (spans != nullptr && spans->name.index != ResStringPool_span::END) { StyleString style_str = {str}; while (spans->name.index != ResStringPool_span::END) { style_str.spans.push_back( Span{util::GetString(value_pool_, spans->name.index), spans->firstChar, spans->lastChar}); spans++; } return util::make_unique(table_->string_pool.MakeRef( style_str, StringPool::Context(StringPool::Context::kStylePriority, config))); } else { if (name.type != ResourceType::kString && util::StartsWith(str, "res/")) { // This must be a FileReference. return util::make_unique(table_->string_pool.MakeRef( str, StringPool::Context(StringPool::Context::kHighPriority, config))); } // There are no styles associated with this string, so treat it as // a simple string. return util::make_unique( table_->string_pool.MakeRef(str, StringPool::Context(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 (data == 0) { // A reference of 0, must be the magic @null reference. Res_value null_type = {}; null_type.dataType = Res_value::TYPE_REFERENCE; return util::make_unique(null_type); } // This is a normal reference. return util::make_unique(data, type); } // Treat this as a raw binary primitive. return util::make_unique(*value); } std::unique_ptr 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::kAttrPrivate: // fallthrough case ResourceType::kAttr: return ParseAttr(name, config, map); case ResourceType::kArray: return ParseArray(name, config, map); case ResourceType::kPlurals: return ParsePlural(name, config, map); default: LOG(FATAL) << "unknown map type"; break; } return {}; } std::unique_ptr