/* * 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 "BigBuffer.h" #include "Logger.h" #include "Maybe.h" #include "Resolver.h" #include "Resource.h" #include "ResourceParser.h" #include "ResourceValues.h" #include "SdkConstants.h" #include "Source.h" #include "StringPool.h" #include "Util.h" #include "XmlFlattener.h" #include #include #include #include #include namespace aapt { constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android"; struct AttributeValueFlattener : ValueVisitor { AttributeValueFlattener( std::shared_ptr resolver, SourceLogger* logger, android::Res_value* outValue, std::shared_ptr parser, bool* outError, StringPool::Ref rawValue, std::u16string* defaultPackage, std::vector>* outStringRefs) : mResolver(resolver), mLogger(logger), mOutValue(outValue), mParser(parser), mError(outError), mRawValue(rawValue), mDefaultPackage(defaultPackage), mStringRefs(outStringRefs) { } void visit(Reference& reference, ValueVisitorArgs&) override { // First see if we can convert the package name from a prefix to a real // package name. ResourceName aliasedName = reference.name; if (!reference.name.package.empty()) { // Only if we specified a package do we look for its alias. mParser->applyPackageAlias(&reference.name.package, *mDefaultPackage); } else { reference.name.package = *mDefaultPackage; } Maybe result = mResolver->findId(reference.name); if (!result || !result.value().isValid()) { std::ostream& out = mLogger->error(mParser->getLineNumber()) << "unresolved reference '" << aliasedName << "'"; if (aliasedName != reference.name) { out << " (aka '" << reference.name << "')"; } out << "'." << std::endl; *mError = true; } else { reference.id = result.value(); reference.flatten(*mOutValue); } } void visit(String& string, ValueVisitorArgs&) override { mOutValue->dataType = android::Res_value::TYPE_STRING; mStringRefs->emplace_back( mRawValue, reinterpret_cast(mOutValue->data)); } void visitItem(Item& item, ValueVisitorArgs&) override { item.flatten(*mOutValue); } private: std::shared_ptr mResolver; SourceLogger* mLogger; android::Res_value* mOutValue; std::shared_ptr mParser; bool* mError; StringPool::Ref mRawValue; std::u16string* mDefaultPackage; std::vector>* mStringRefs; }; struct XmlAttribute { uint32_t resourceId; const XmlPullParser::Attribute* xmlAttr; const Attribute* attr; StringPool::Ref nameRef; }; static bool lessAttributeId(const XmlAttribute& a, uint32_t id) { return a.resourceId < id; } XmlFlattener::XmlFlattener(const std::shared_ptr& table, const std::shared_ptr& resolver) : mTable(table), mResolver(resolver) { } /** * Reads events from the parser and writes to a BigBuffer. The binary XML file * expects the StringPool to appear first, but we haven't collected the strings yet. We * write to a temporary BigBuffer while parsing the input, adding strings we encounter * to the StringPool. At the end, we write the StringPool to the given BigBuffer and * then move the data from the temporary BigBuffer into the given one. This incurs no * copies as the given BigBuffer simply takes ownership of the data. */ Maybe XmlFlattener::flatten(const Source& source, const std::shared_ptr& parser, BigBuffer* outBuffer, Options options) { SourceLogger logger(source); StringPool pool; bool error = false; size_t smallestStrippedAttributeSdk = std::numeric_limits::max(); // Attribute names are stored without packages, but we use // their StringPool index to lookup their resource IDs. // This will cause collisions, so we can't dedupe // attribute names from different packages. We use separate // pools that we later combine. std::map packagePools; // Attribute resource IDs are stored in the same order // as the attribute names appear in the StringPool. // Since the StringPool contains more than just attribute // names, to maintain a tight packing of resource IDs, // we must ensure that attribute names appear first // in our StringPool. For this, we assign a low priority // (0xffffffff) to non-attribute strings. Attribute // names will be stored along with a priority equal // to their resource ID so that they are ordered. StringPool::Context lowPriority { 0xffffffffu }; // Once we sort the StringPool, we can assign the updated indices // to the correct data locations. std::vector> stringRefs; // Since we don't know the size of the final StringPool, we write to this // temporary BigBuffer, which we will append to outBuffer later. BigBuffer out(1024); while (XmlPullParser::isGoodEvent(parser->next())) { XmlPullParser::Event event = parser->getEvent(); switch (event) { case XmlPullParser::Event::kStartNamespace: case XmlPullParser::Event::kEndNamespace: { const size_t startIndex = out.size(); android::ResXMLTree_node* node = out.nextBlock(); if (event == XmlPullParser::Event::kStartNamespace) { node->header.type = android::RES_XML_START_NAMESPACE_TYPE; } else { node->header.type = android::RES_XML_END_NAMESPACE_TYPE; } node->header.headerSize = sizeof(*node); node->lineNumber = parser->getLineNumber(); node->comment.index = -1; android::ResXMLTree_namespaceExt* ns = out.nextBlock(); stringRefs.emplace_back( pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix); stringRefs.emplace_back( pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri); out.align4(); node->header.size = out.size() - startIndex; break; } case XmlPullParser::Event::kStartElement: { const size_t startIndex = out.size(); android::ResXMLTree_node* node = out.nextBlock(); node->header.type = android::RES_XML_START_ELEMENT_TYPE; node->header.headerSize = sizeof(*node); node->lineNumber = parser->getLineNumber(); node->comment.index = -1; android::ResXMLTree_attrExt* elem = out.nextBlock(); if (!parser->getElementNamespace().empty()) { stringRefs.emplace_back( pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns); } else { // The device doesn't think a string of size 0 is the same as null. elem->ns.index = -1; } stringRefs.emplace_back( pool.makeRef(parser->getElementName(), lowPriority), &elem->name); elem->attributeStart = sizeof(*elem); elem->attributeSize = sizeof(android::ResXMLTree_attribute); // The resource system expects attributes to be sorted by resource ID. std::vector sortedAttributes; uint32_t nextAttributeId = 0; const auto endAttrIter = parser->endAttributes(); for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) { uint32_t id; StringPool::Ref nameRef; const Attribute* attr = nullptr; if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) { size_t sdkVersion = findAttributeSdkLevel(attrIter->name); if (sdkVersion > options.maxSdkAttribute.value()) { // We will silently omit this attribute smallestStrippedAttributeSdk = std::min(smallestStrippedAttributeSdk, sdkVersion); continue; } } ResourceNameRef genIdName; bool create = false; bool privateRef = false; if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName, &create, &privateRef) && create) { mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()), util::make_unique()); } Maybe package = util::extractPackageFromNamespace( attrIter->namespaceUri); if (!package || !mResolver) { // Attributes that have no resource ID (because they don't belong to a // package) should appear after those that do have resource IDs. Assign // them some integer value that will appear after. id = 0x80000000u | nextAttributeId++; nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id }); } else { // Find the Attribute object via our Resolver. ResourceName attrName = { package.value(), ResourceType::kAttr, attrIter->name }; if (attrName.package.empty()) { attrName.package = options.defaultPackage; } Maybe result = mResolver->findAttribute(attrName); if (!result || !result.value().id.isValid()) { logger.error(parser->getLineNumber()) << "unresolved attribute '" << attrName << "'." << std::endl; error = true; continue; } if (!result.value().attr) { logger.error(parser->getLineNumber()) << "not a valid attribute '" << attrName << "'." << std::endl; error = true; continue; } id = result.value().id.id; attr = result.value().attr; // Put the attribute name into a package specific pool, since we don't // want to collapse names from different packages. nameRef = packagePools[package.value()].makeRef( attrIter->name, StringPool::Context{ id }); } // Insert the attribute into the sorted vector. auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(), id, lessAttributeId); sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef }); } if (error) { break; } // Now that we have filtered out some attributes, get the final count. elem->attributeCount = sortedAttributes.size(); // Flatten the sorted attributes. uint16_t attributeIndex = 1; for (auto entry : sortedAttributes) { android::ResXMLTree_attribute* attr = out.nextBlock(); if (!entry.xmlAttr->namespaceUri.empty()) { stringRefs.emplace_back( pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns); } else { attr->ns.index = -1; } StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority); stringRefs.emplace_back(entry.nameRef, &attr->name); if (options.keepRawValues) { stringRefs.emplace_back(rawValueRef, &attr->rawValue); } else { attr->rawValue.index = -1; } // Assign the indices for specific attributes. if (entry.xmlAttr->namespaceUri == kSchemaAndroid && entry.xmlAttr->name == u"id") { elem->idIndex = attributeIndex; } else if (entry.xmlAttr->namespaceUri.empty()) { if (entry.xmlAttr->name == u"class") { elem->classIndex = attributeIndex; } else if (entry.xmlAttr->name == u"style") { elem->styleIndex = attributeIndex; } } attributeIndex++; std::unique_ptr value; if (entry.attr) { value = ResourceParser::parseItemForAttribute(entry.xmlAttr->value, *entry.attr); } else { bool create = false; value = ResourceParser::tryParseReference(entry.xmlAttr->value, &create); } if (mResolver && value) { AttributeValueFlattener flattener( mResolver, &logger, &attr->typedValue, parser, &error, rawValueRef, &options.defaultPackage, &stringRefs); value->accept(flattener, {}); } else if (!value && entry.attr && !(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) { logger.error(parser->getLineNumber()) << "'" << *rawValueRef << "' is not compatible with attribute " << *entry.attr << "." << std::endl; error = true; } else { attr->typedValue.dataType = android::Res_value::TYPE_STRING; if (!options.keepRawValues) { // Don't set the string twice. stringRefs.emplace_back(rawValueRef, &attr->rawValue); } stringRefs.emplace_back(rawValueRef, reinterpret_cast( &attr->typedValue.data)); } attr->typedValue.size = sizeof(attr->typedValue); } out.align4(); node->header.size = out.size() - startIndex; break; } case XmlPullParser::Event::kEndElement: { const size_t startIndex = out.size(); android::ResXMLTree_node* node = out.nextBlock(); node->header.type = android::RES_XML_END_ELEMENT_TYPE; node->header.headerSize = sizeof(*node); node->lineNumber = parser->getLineNumber(); node->comment.index = -1; android::ResXMLTree_endElementExt* elem = out.nextBlock(); stringRefs.emplace_back( pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns); stringRefs.emplace_back( pool.makeRef(parser->getElementName(), lowPriority), &elem->name); out.align4(); node->header.size = out.size() - startIndex; break; } case XmlPullParser::Event::kText: { StringPiece16 text = util::trimWhitespace(parser->getText()); if (text.empty()) { break; } const size_t startIndex = out.size(); android::ResXMLTree_node* node = out.nextBlock(); node->header.type = android::RES_XML_CDATA_TYPE; node->header.headerSize = sizeof(*node); node->lineNumber = parser->getLineNumber(); node->comment.index = -1; android::ResXMLTree_cdataExt* elem = out.nextBlock(); stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data); out.align4(); node->header.size = out.size() - startIndex; break; } default: break; } } out.align4(); if (error) { return {}; } if (parser->getEvent() == XmlPullParser::Event::kBadDocument) { logger.error(parser->getLineNumber()) << parser->getLastError() << std::endl; return {}; } // Merge the package pools into the main pool. for (auto& packagePoolEntry : packagePools) { pool.merge(std::move(packagePoolEntry.second)); } // Sort so that attribute resource IDs show up first. pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { return a.context.priority < b.context.priority; }); // Now we flatten the string pool references into the correct places. for (const auto& refEntry : stringRefs) { refEntry.second->index = refEntry.first.getIndex(); } // Write the XML header. const size_t beforeXmlTreeIndex = outBuffer->size(); android::ResXMLTree_header* header = outBuffer->nextBlock(); header->header.type = android::RES_XML_TYPE; header->header.headerSize = sizeof(*header); // Flatten the StringPool. StringPool::flattenUtf16(outBuffer, pool); // Write the array of resource IDs, indexed by StringPool order. const size_t beforeResIdMapIndex = outBuffer->size(); android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock(); resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE; resIdMapChunk->headerSize = sizeof(*resIdMapChunk); for (const auto& str : pool) { ResourceId id { str->context.priority }; if (!id.isValid()) { // When we see the first non-resource ID, // we're done. break; } uint32_t* flatId = outBuffer->nextBlock(); *flatId = id.id; } resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex; // Move the temporary BigBuffer into outBuffer. outBuffer->appendBuffer(std::move(out)); header->header.size = outBuffer->size() - beforeXmlTreeIndex; if (smallestStrippedAttributeSdk == std::numeric_limits::max()) { // Nothing was stripped return 0u; } return smallestStrippedAttributeSdk; } } // namespace aapt