1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "BigBuffer.h"
18 #include "Logger.h"
19 #include "Maybe.h"
20 #include "Resolver.h"
21 #include "Resource.h"
22 #include "ResourceParser.h"
23 #include "ResourceValues.h"
24 #include "SdkConstants.h"
25 #include "Source.h"
26 #include "StringPool.h"
27 #include "Util.h"
28 #include "XmlFlattener.h"
29 
30 #include <androidfw/ResourceTypes.h>
31 #include <limits>
32 #include <map>
33 #include <string>
34 #include <vector>
35 
36 namespace aapt {
37 namespace xml {
38 
39 constexpr uint32_t kLowPriority = 0xffffffffu;
40 
41 // A vector that maps String refs to their final destination in the out buffer.
42 using FlatStringRefList = std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>;
43 
44 struct XmlFlattener : public Visitor {
XmlFlatteneraapt::xml::XmlFlattener45     XmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
46                  const std::u16string& defaultPackage) :
47             mOut(outBuffer), mPool(pool), mStringRefs(stringRefs),
48             mDefaultPackage(defaultPackage) {
49     }
50 
51     // No copying.
52     XmlFlattener(const XmlFlattener&) = delete;
53     XmlFlattener& operator=(const XmlFlattener&) = delete;
54 
writeNamespaceaapt::xml::XmlFlattener55     void writeNamespace(Namespace* node, uint16_t type) {
56         const size_t startIndex = mOut->size();
57         android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
58         android::ResXMLTree_namespaceExt* flatNs =
59                 mOut->nextBlock<android::ResXMLTree_namespaceExt>();
60         mOut->align4();
61 
62         flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
63         flatNode->lineNumber = node->lineNumber;
64         flatNode->comment.index = -1;
65         addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
66         addString(node->namespaceUri, kLowPriority, &flatNs->uri);
67     }
68 
visitaapt::xml::XmlFlattener69     virtual void visit(Namespace* node) override {
70         // Extract the package/prefix from this namespace node.
71         Maybe<std::u16string> package = util::extractPackageFromNamespace(node->namespaceUri);
72         if (package) {
73             mPackageAliases.emplace_back(
74                     node->namespacePrefix,
75                     package.value().empty() ? mDefaultPackage : package.value());
76         }
77 
78         writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
79         for (const auto& child : node->children) {
80             child->accept(this);
81         }
82         writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
83 
84         if (package) {
85             mPackageAliases.pop_back();
86         }
87     }
88 
visitaapt::xml::XmlFlattener89     virtual void visit(Text* node) override {
90         if (util::trimWhitespace(node->text).empty()) {
91             return;
92         }
93 
94         const size_t startIndex = mOut->size();
95         android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
96         android::ResXMLTree_cdataExt* flatText = mOut->nextBlock<android::ResXMLTree_cdataExt>();
97         mOut->align4();
98 
99         const uint16_t type = android::RES_XML_CDATA_TYPE;
100         flatNode->header = { type, sizeof(*flatNode), (uint32_t)(mOut->size() - startIndex) };
101         flatNode->lineNumber = node->lineNumber;
102         flatNode->comment.index = -1;
103         addString(node->text, kLowPriority, &flatText->data);
104     }
105 
visitaapt::xml::XmlFlattener106     virtual void visit(Element* node) override {
107         const size_t startIndex = mOut->size();
108         android::ResXMLTree_node* flatNode = mOut->nextBlock<android::ResXMLTree_node>();
109         android::ResXMLTree_attrExt* flatElem = mOut->nextBlock<android::ResXMLTree_attrExt>();
110 
111         const uint16_t type = android::RES_XML_START_ELEMENT_TYPE;
112         flatNode->header = { type, sizeof(*flatNode), 0 };
113         flatNode->lineNumber = node->lineNumber;
114         flatNode->comment.index = -1;
115 
116         addString(node->namespaceUri, kLowPriority, &flatElem->ns);
117         addString(node->name, kLowPriority, &flatElem->name);
118         flatElem->attributeStart = sizeof(*flatElem);
119         flatElem->attributeSize = sizeof(android::ResXMLTree_attribute);
120         flatElem->attributeCount = node->attributes.size();
121 
122         if (!writeAttributes(mOut, node, flatElem)) {
123             mError = true;
124         }
125 
126         mOut->align4();
127         flatNode->header.size = (uint32_t)(mOut->size() - startIndex);
128 
129         for (const auto& child : node->children) {
130             child->accept(this);
131         }
132 
133         const size_t startEndIndex = mOut->size();
134         android::ResXMLTree_node* flatEndNode = mOut->nextBlock<android::ResXMLTree_node>();
135         android::ResXMLTree_endElementExt* flatEndElem =
136                 mOut->nextBlock<android::ResXMLTree_endElementExt>();
137         mOut->align4();
138 
139         const uint16_t endType = android::RES_XML_END_ELEMENT_TYPE;
140         flatEndNode->header = { endType, sizeof(*flatEndNode),
141                 (uint32_t)(mOut->size() - startEndIndex) };
142         flatEndNode->lineNumber = node->lineNumber;
143         flatEndNode->comment.index = -1;
144 
145         addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
146         addString(node->name, kLowPriority, &flatEndElem->name);
147     }
148 
successaapt::xml::XmlFlattener149     bool success() const {
150         return !mError;
151     }
152 
153 protected:
addStringaapt::xml::XmlFlattener154     void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
155         if (!str.empty()) {
156             mStringRefs->emplace_back(mPool->makeRef(str, StringPool::Context{ priority }), dest);
157         } else {
158             // The device doesn't think a string of size 0 is the same as null.
159             dest->index = -1;
160         }
161     }
162 
addStringaapt::xml::XmlFlattener163     void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
164         mStringRefs->emplace_back(ref, dest);
165     }
166 
getPackageAliasaapt::xml::XmlFlattener167     Maybe<std::u16string> getPackageAlias(const std::u16string& prefix) {
168         const auto endIter = mPackageAliases.rend();
169         for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
170             if (iter->first == prefix) {
171                 return iter->second;
172             }
173         }
174         return {};
175     }
176 
getDefaultPackageaapt::xml::XmlFlattener177     const std::u16string& getDefaultPackage() const {
178         return mDefaultPackage;
179     }
180 
181     /**
182      * Subclasses override this to deal with attributes. Attributes can be flattened as
183      * raw values or as resources.
184      */
185     virtual bool writeAttributes(BigBuffer* out, Element* node,
186                                  android::ResXMLTree_attrExt* flatElem) = 0;
187 
188 private:
189     BigBuffer* mOut;
190     StringPool* mPool;
191     FlatStringRefList* mStringRefs;
192     std::u16string mDefaultPackage;
193     bool mError = false;
194     std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
195 };
196 
197 /**
198  * Flattens XML, encoding the attributes as raw strings. This is used in the compile phase.
199  */
200 struct CompileXmlFlattener : public XmlFlattener {
CompileXmlFlatteneraapt::xml::CompileXmlFlattener201     CompileXmlFlattener(BigBuffer* outBuffer, StringPool* pool, FlatStringRefList* stringRefs,
202                         const std::u16string& defaultPackage) :
203             XmlFlattener(outBuffer, pool, stringRefs, defaultPackage) {
204     }
205 
writeAttributesaapt::xml::CompileXmlFlattener206     virtual bool writeAttributes(BigBuffer* out, Element* node,
207                                  android::ResXMLTree_attrExt* flatElem) override {
208         flatElem->attributeCount = node->attributes.size();
209         if (node->attributes.empty()) {
210             return true;
211         }
212 
213         android::ResXMLTree_attribute* flatAttrs = out->nextBlock<android::ResXMLTree_attribute>(
214                 node->attributes.size());
215         for (const Attribute& attr : node->attributes) {
216             addString(attr.namespaceUri, kLowPriority, &flatAttrs->ns);
217             addString(attr.name, kLowPriority, &flatAttrs->name);
218             addString(attr.value, kLowPriority, &flatAttrs->rawValue);
219             flatAttrs++;
220         }
221         return true;
222     }
223 };
224 
225 struct AttributeToFlatten {
226     uint32_t resourceId = 0;
227     const Attribute* xmlAttr = nullptr;
228     const ::aapt::Attribute* resourceAttr = nullptr;
229 };
230 
lessAttributeId(const AttributeToFlatten & a,uint32_t id)231 static bool lessAttributeId(const AttributeToFlatten& a, uint32_t id) {
232     return a.resourceId < id;
233 }
234 
235 /**
236  * Flattens XML, encoding the attributes as resources.
237  */
238 struct LinkedXmlFlattener : public XmlFlattener {
LinkedXmlFlatteneraapt::xml::LinkedXmlFlattener239     LinkedXmlFlattener(BigBuffer* outBuffer, StringPool* pool,
240                        std::map<std::u16string, StringPool>* packagePools,
241                        FlatStringRefList* stringRefs,
242                        const std::u16string& defaultPackage,
243                        const std::shared_ptr<IResolver>& resolver,
244                        SourceLogger* logger,
245                        const FlattenOptions& options) :
246             XmlFlattener(outBuffer, pool, stringRefs, defaultPackage), mResolver(resolver),
247             mLogger(logger), mPackagePools(packagePools), mOptions(options) {
248     }
249 
writeAttributesaapt::xml::LinkedXmlFlattener250     virtual bool writeAttributes(BigBuffer* out, Element* node,
251                                  android::ResXMLTree_attrExt* flatElem) override {
252         bool error = false;
253         std::vector<AttributeToFlatten> sortedAttributes;
254         uint32_t nextAttributeId = 0x80000000u;
255 
256         // Sort and filter attributes by their resource ID.
257         for (const Attribute& attr : node->attributes) {
258             AttributeToFlatten attrToFlatten;
259             attrToFlatten.xmlAttr = &attr;
260 
261             Maybe<std::u16string> package = util::extractPackageFromNamespace(attr.namespaceUri);
262             if (package) {
263                 // Find the Attribute object via our Resolver.
264                 ResourceName attrName = { package.value(), ResourceType::kAttr, attr.name };
265                 if (attrName.package.empty()) {
266                     attrName.package = getDefaultPackage();
267                 }
268 
269                 Maybe<IResolver::Entry> result = mResolver->findAttribute(attrName);
270                 if (!result || !result.value().id.isValid() || !result.value().attr) {
271                     error = true;
272                     mLogger->error(node->lineNumber)
273                             << "unresolved attribute '" << attrName << "'."
274                             << std::endl;
275                 } else {
276                     attrToFlatten.resourceId = result.value().id.id;
277                     attrToFlatten.resourceAttr = result.value().attr;
278 
279                     size_t sdk = findAttributeSdkLevel(attrToFlatten.resourceId);
280                     if (mOptions.maxSdkAttribute && sdk > mOptions.maxSdkAttribute.value()) {
281                         // We need to filter this attribute out.
282                         mSmallestFilteredSdk = std::min(mSmallestFilteredSdk, sdk);
283                         continue;
284                     }
285                 }
286             }
287 
288             if (attrToFlatten.resourceId == 0) {
289                 // Attributes that have no resource ID (because they don't belong to a
290                 // package) should appear after those that do have resource IDs. Assign
291                 // them some integer value that will appear after.
292                 attrToFlatten.resourceId = nextAttributeId++;
293             }
294 
295             // Insert the attribute into the sorted vector.
296             auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
297                                          attrToFlatten.resourceId, lessAttributeId);
298             sortedAttributes.insert(iter, std::move(attrToFlatten));
299         }
300 
301         flatElem->attributeCount = sortedAttributes.size();
302         if (sortedAttributes.empty()) {
303             return true;
304         }
305 
306         android::ResXMLTree_attribute* flatAttr = out->nextBlock<android::ResXMLTree_attribute>(
307                 sortedAttributes.size());
308 
309         // Now that we have sorted the attributes into their final encoded order, it's time
310         // to actually write them out.
311         uint16_t attributeIndex = 1;
312         for (const AttributeToFlatten& attrToFlatten : sortedAttributes) {
313             Maybe<std::u16string> package = util::extractPackageFromNamespace(
314                     attrToFlatten.xmlAttr->namespaceUri);
315 
316             // Assign the indices for specific attributes.
317             if (package && package.value() == u"android" && attrToFlatten.xmlAttr->name == u"id") {
318                 flatElem->idIndex = attributeIndex;
319             } else if (attrToFlatten.xmlAttr->namespaceUri.empty()) {
320                 if (attrToFlatten.xmlAttr->name == u"class") {
321                     flatElem->classIndex = attributeIndex;
322                 } else if (attrToFlatten.xmlAttr->name == u"style") {
323                     flatElem->styleIndex = attributeIndex;
324                 }
325             }
326             attributeIndex++;
327 
328             // Add the namespaceUri and name to the list of StringRefs to encode.
329             addString(attrToFlatten.xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
330             flatAttr->rawValue.index = -1;
331 
332             if (!attrToFlatten.resourceAttr) {
333                 addString(attrToFlatten.xmlAttr->name, kLowPriority, &flatAttr->name);
334             } else {
335                 // We've already extracted the package successfully before.
336                 assert(package);
337 
338                 // Attribute names are stored without packages, but we use
339                 // their StringPool index to lookup their resource IDs.
340                 // This will cause collisions, so we can't dedupe
341                 // attribute names from different packages. We use separate
342                 // pools that we later combine.
343                 //
344                 // Lookup the StringPool for this package and make the reference there.
345                 StringPool::Ref nameRef = (*mPackagePools)[package.value()].makeRef(
346                         attrToFlatten.xmlAttr->name,
347                         StringPool::Context{ attrToFlatten.resourceId });
348 
349                 // Add it to the list of strings to flatten.
350                 addString(nameRef, &flatAttr->name);
351 
352                 if (mOptions.keepRawValues) {
353                     // Keep raw values (this is for static libraries).
354                     // TODO(with a smarter inflater for binary XML, we can do without this).
355                     addString(attrToFlatten.xmlAttr->value, kLowPriority, &flatAttr->rawValue);
356                 }
357             }
358 
359             error |= !flattenItem(node, attrToFlatten.xmlAttr->value, attrToFlatten.resourceAttr,
360                                   flatAttr);
361             flatAttr->typedValue.size = sizeof(flatAttr->typedValue);
362             flatAttr++;
363         }
364         return !error;
365     }
366 
getSmallestFilteredSdkaapt::xml::LinkedXmlFlattener367     Maybe<size_t> getSmallestFilteredSdk() const {
368         if (mSmallestFilteredSdk == std::numeric_limits<size_t>::max()) {
369             return {};
370         }
371         return mSmallestFilteredSdk;
372     }
373 
374 private:
flattenItemaapt::xml::LinkedXmlFlattener375     bool flattenItem(const Node* el, const std::u16string& value, const ::aapt::Attribute* attr,
376                      android::ResXMLTree_attribute* flatAttr) {
377         std::unique_ptr<Item> item;
378         if (!attr) {
379             bool create = false;
380             item = ResourceParser::tryParseReference(value, &create);
381             if (!item) {
382                 flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
383                 addString(value, kLowPriority, &flatAttr->rawValue);
384                 addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
385                         &flatAttr->typedValue.data));
386                 return true;
387             }
388         } else {
389             item = ResourceParser::parseItemForAttribute(value, *attr);
390             if (!item) {
391                 if (!(attr->typeMask & android::ResTable_map::TYPE_STRING)) {
392                     mLogger->error(el->lineNumber)
393                             << "'"
394                             << value
395                             << "' is not compatible with attribute '"
396                             << *attr
397                             << "'."
398                             << std::endl;
399                     return false;
400                 }
401 
402                 flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
403                 addString(value, kLowPriority, &flatAttr->rawValue);
404                 addString(value, kLowPriority, reinterpret_cast<android::ResStringPool_ref*>(
405                         &flatAttr->typedValue.data));
406                 return true;
407             }
408         }
409 
410         assert(item);
411 
412         bool error = false;
413 
414         // If this is a reference, resolve the name into an ID.
415         visitFunc<Reference>(*item, [&](Reference& reference) {
416             // First see if we can convert the package name from a prefix to a real
417             // package name.
418             ResourceName realName = reference.name;
419             if (!realName.package.empty()) {
420                 Maybe<std::u16string> package = getPackageAlias(realName.package);
421                 if (package) {
422                     realName.package = package.value();
423                 }
424             } else {
425                 realName.package = getDefaultPackage();
426             }
427 
428             Maybe<ResourceId> result = mResolver->findId(realName);
429             if (!result || !result.value().isValid()) {
430                 std::ostream& out = mLogger->error(el->lineNumber)
431                         << "unresolved reference '"
432                         << reference.name
433                         << "'";
434                 if (realName != reference.name) {
435                     out << " (aka '" << realName << "')";
436                 }
437                 out << "'." << std::endl;
438                 error = true;
439             } else {
440                 reference.id = result.value();
441             }
442         });
443 
444         if (error) {
445             return false;
446         }
447 
448         item->flatten(flatAttr->typedValue);
449         return true;
450     }
451 
452     std::shared_ptr<IResolver> mResolver;
453     SourceLogger* mLogger;
454     std::map<std::u16string, StringPool>* mPackagePools;
455     FlattenOptions mOptions;
456     size_t mSmallestFilteredSdk = std::numeric_limits<size_t>::max();
457 };
458 
459 /**
460  * The binary XML file expects the StringPool to appear first, but we haven't collected the
461  * strings yet. We write to a temporary BigBuffer while parsing the input, adding strings
462  * we encounter to the StringPool. At the end, we write the StringPool to the given BigBuffer and
463  * then move the data from the temporary BigBuffer into the given one. This incurs no
464  * copies as the given BigBuffer simply takes ownership of the data.
465  */
flattenXml(StringPool * pool,FlatStringRefList * stringRefs,BigBuffer * outBuffer,BigBuffer && xmlTreeBuffer)466 static void flattenXml(StringPool* pool, FlatStringRefList* stringRefs, BigBuffer* outBuffer,
467                        BigBuffer&& xmlTreeBuffer) {
468     // Sort the string pool so that attribute resource IDs show up first.
469     pool->sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
470         return a.context.priority < b.context.priority;
471     });
472 
473     // Now we flatten the string pool references into the correct places.
474     for (const auto& refEntry : *stringRefs) {
475         refEntry.second->index = refEntry.first.getIndex();
476     }
477 
478     // Write the XML header.
479     const size_t beforeXmlTreeIndex = outBuffer->size();
480     android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
481     header->header.type = android::RES_XML_TYPE;
482     header->header.headerSize = sizeof(*header);
483 
484     // Flatten the StringPool.
485     StringPool::flattenUtf16(outBuffer, *pool);
486 
487     // Write the array of resource IDs, indexed by StringPool order.
488     const size_t beforeResIdMapIndex = outBuffer->size();
489     android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
490     resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
491     resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
492     for (const auto& str : *pool) {
493         ResourceId id { str->context.priority };
494         if (id.id == kLowPriority || !id.isValid()) {
495             // When we see the first non-resource ID,
496             // we're done.
497             break;
498         }
499 
500         *outBuffer->nextBlock<uint32_t>() = id.id;
501     }
502     resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
503 
504     // Move the temporary BigBuffer into outBuffer.
505     outBuffer->appendBuffer(std::move(xmlTreeBuffer));
506     header->header.size = outBuffer->size() - beforeXmlTreeIndex;
507 }
508 
flatten(Node * root,const std::u16string & defaultPackage,BigBuffer * outBuffer)509 bool flatten(Node* root, const std::u16string& defaultPackage, BigBuffer* outBuffer) {
510     StringPool pool;
511 
512     // This will hold the StringRefs and the location in which to write the index.
513     // Once we sort the StringPool, we can assign the updated indices
514     // to the correct data locations.
515     FlatStringRefList stringRefs;
516 
517     // Since we don't know the size of the final StringPool, we write to this
518     // temporary BigBuffer, which we will append to outBuffer later.
519     BigBuffer out(1024);
520 
521     CompileXmlFlattener flattener(&out, &pool, &stringRefs, defaultPackage);
522     root->accept(&flattener);
523 
524     if (!flattener.success()) {
525         return false;
526     }
527 
528     flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
529     return true;
530 };
531 
flattenAndLink(const Source & source,Node * root,const std::u16string & defaultPackage,const std::shared_ptr<IResolver> & resolver,const FlattenOptions & options,BigBuffer * outBuffer)532 Maybe<size_t> flattenAndLink(const Source& source, Node* root,
533                              const std::u16string& defaultPackage,
534                              const std::shared_ptr<IResolver>& resolver,
535                              const FlattenOptions& options, BigBuffer* outBuffer) {
536     SourceLogger logger(source);
537     StringPool pool;
538 
539     // Attribute names are stored without packages, but we use
540     // their StringPool index to lookup their resource IDs.
541     // This will cause collisions, so we can't dedupe
542     // attribute names from different packages. We use separate
543     // pools that we later combine.
544     std::map<std::u16string, StringPool> packagePools;
545 
546     FlatStringRefList stringRefs;
547 
548     // Since we don't know the size of the final StringPool, we write to this
549     // temporary BigBuffer, which we will append to outBuffer later.
550     BigBuffer out(1024);
551 
552     LinkedXmlFlattener flattener(&out, &pool, &packagePools, &stringRefs, defaultPackage, resolver,
553                                  &logger, options);
554     root->accept(&flattener);
555 
556     if (!flattener.success()) {
557         return {};
558     }
559 
560     // Merge the package pools into the main pool.
561     for (auto& packagePoolEntry : packagePools) {
562         pool.merge(std::move(packagePoolEntry.second));
563     }
564 
565     flattenXml(&pool, &stringRefs, outBuffer, std::move(out));
566 
567     if (flattener.getSmallestFilteredSdk()) {
568         return flattener.getSmallestFilteredSdk();
569     }
570     return 0;
571 }
572 
573 } // namespace xml
574 } // namespace aapt
575