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 "SdkConstants.h"
18#include "flatten/ChunkWriter.h"
19#include "flatten/ResourceTypeExtensions.h"
20#include "flatten/XmlFlattener.h"
21#include "xml/XmlDom.h"
22
23#include <androidfw/ResourceTypes.h>
24#include <algorithm>
25#include <utils/misc.h>
26#include <vector>
27
28using namespace android;
29
30namespace aapt {
31
32namespace {
33
34constexpr uint32_t kLowPriority = 0xffffffffu;
35
36struct XmlFlattenerVisitor : public xml::Visitor {
37    using xml::Visitor::visit;
38
39    BigBuffer* mBuffer;
40    XmlFlattenerOptions mOptions;
41    StringPool mPool;
42    std::map<uint8_t, StringPool> mPackagePools;
43
44    struct StringFlattenDest {
45        StringPool::Ref ref;
46        ResStringPool_ref* dest;
47    };
48    std::vector<StringFlattenDest> mStringRefs;
49
50    // Scratch vector to filter attributes. We avoid allocations
51    // making this a member.
52    std::vector<xml::Attribute*> mFilteredAttrs;
53
54
55    XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) :
56            mBuffer(buffer), mOptions(options) {
57    }
58
59    void addString(const StringPiece16& str, uint32_t priority, android::ResStringPool_ref* dest) {
60        if (!str.empty()) {
61            mStringRefs.push_back(StringFlattenDest{
62                    mPool.makeRef(str, StringPool::Context{ priority }),
63                    dest });
64        } else {
65            // The device doesn't think a string of size 0 is the same as null.
66            dest->index = util::deviceToHost32(-1);
67        }
68    }
69
70    void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
71        mStringRefs.push_back(StringFlattenDest{ ref, dest });
72    }
73
74    void writeNamespace(xml::Namespace* node, uint16_t type) {
75        ChunkWriter writer(mBuffer);
76
77        ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type);
78        flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
79        flatNode->comment.index = util::hostToDevice32(-1);
80
81        ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>();
82        addString(node->namespacePrefix, kLowPriority, &flatNs->prefix);
83        addString(node->namespaceUri, kLowPriority, &flatNs->uri);
84
85        writer.finish();
86    }
87
88    void visit(xml::Namespace* node) override {
89        writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
90        xml::Visitor::visit(node);
91        writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
92    }
93
94    void visit(xml::Text* node) override {
95        if (util::trimWhitespace(node->text).empty()) {
96            // Skip whitespace only text nodes.
97            return;
98        }
99
100        ChunkWriter writer(mBuffer);
101        ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
102        flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
103        flatNode->comment.index = util::hostToDevice32(-1);
104
105        ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>();
106        addString(node->text, kLowPriority, &flatText->data);
107
108        writer.finish();
109    }
110
111    void visit(xml::Element* node) override {
112        {
113            ChunkWriter startWriter(mBuffer);
114            ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>(
115                    RES_XML_START_ELEMENT_TYPE);
116            flatNode->lineNumber = util::hostToDevice32(node->lineNumber);
117            flatNode->comment.index = util::hostToDevice32(-1);
118
119            ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>();
120            addString(node->namespaceUri, kLowPriority, &flatElem->ns);
121            addString(node->name, kLowPriority, &flatElem->name);
122            flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem));
123            flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute));
124
125            writeAttributes(node, flatElem, &startWriter);
126
127            startWriter.finish();
128        }
129
130        xml::Visitor::visit(node);
131
132        {
133            ChunkWriter endWriter(mBuffer);
134            ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>(
135                    RES_XML_END_ELEMENT_TYPE);
136            flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber);
137            flatEndNode->comment.index = util::hostToDevice32(-1);
138
139            ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>();
140            addString(node->namespaceUri, kLowPriority, &flatEndElem->ns);
141            addString(node->name, kLowPriority, &flatEndElem->name);
142
143            endWriter.finish();
144        }
145    }
146
147    static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) {
148        if (a->compiledAttribute && a->compiledAttribute.value().id) {
149            if (b->compiledAttribute && b->compiledAttribute.value().id) {
150                return a->compiledAttribute.value().id.value() < b->compiledAttribute.value().id.value();
151            }
152            return true;
153        } else if (!b->compiledAttribute) {
154            int diff = a->namespaceUri.compare(b->namespaceUri);
155            if (diff < 0) {
156                return true;
157            } else if (diff > 0) {
158                return false;
159            }
160            return a->name < b->name;
161        }
162        return false;
163    }
164
165    void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) {
166        mFilteredAttrs.clear();
167        mFilteredAttrs.reserve(node->attributes.size());
168
169        // Filter the attributes.
170        for (xml::Attribute& attr : node->attributes) {
171            if (mOptions.maxSdkLevel && attr.compiledAttribute && attr.compiledAttribute.value().id) {
172                size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id.value());
173                if (sdkLevel > mOptions.maxSdkLevel.value()) {
174                    continue;
175                }
176            }
177            mFilteredAttrs.push_back(&attr);
178        }
179
180        if (mFilteredAttrs.empty()) {
181            return;
182        }
183
184        const ResourceId kIdAttr(0x010100d0);
185
186        std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById);
187
188        flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size());
189
190        ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>(
191                mFilteredAttrs.size());
192        uint16_t attributeIndex = 1;
193        for (const xml::Attribute* xmlAttr : mFilteredAttrs) {
194            // Assign the indices for specific attributes.
195            if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id &&
196                    xmlAttr->compiledAttribute.value().id.value() == kIdAttr) {
197                flatElem->idIndex = util::hostToDevice16(attributeIndex);
198            } else if (xmlAttr->namespaceUri.empty()) {
199                if (xmlAttr->name == u"class") {
200                    flatElem->classIndex = util::hostToDevice16(attributeIndex);
201                } else if (xmlAttr->name == u"style") {
202                    flatElem->styleIndex = util::hostToDevice16(attributeIndex);
203                }
204            }
205            attributeIndex++;
206
207            // Add the namespaceUri to the list of StringRefs to encode.
208            addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns);
209
210            flatAttr->rawValue.index = util::hostToDevice32(-1);
211
212            if (!xmlAttr->compiledAttribute || !xmlAttr->compiledAttribute.value().id) {
213                // The attribute has no associated ResourceID, so the string order doesn't matter.
214                addString(xmlAttr->name, kLowPriority, &flatAttr->name);
215            } else {
216                // Attribute names are stored without packages, but we use
217                // their StringPool index to lookup their resource IDs.
218                // This will cause collisions, so we can't dedupe
219                // attribute names from different packages. We use separate
220                // pools that we later combine.
221                //
222                // Lookup the StringPool for this package and make the reference there.
223                const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value();
224
225                StringPool::Ref nameRef = mPackagePools[aaptAttr.id.value().packageId()].makeRef(
226                        xmlAttr->name, StringPool::Context{ aaptAttr.id.value().id });
227
228                // Add it to the list of strings to flatten.
229                addString(nameRef, &flatAttr->name);
230            }
231
232            if (mOptions.keepRawValues || !xmlAttr->compiledValue) {
233                // Keep raw values if the value is not compiled or
234                // if we're building a static library (need symbols).
235                addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue);
236            }
237
238            if (xmlAttr->compiledValue) {
239                bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue);
240                assert(result);
241            } else {
242                // Flatten as a regular string type.
243                flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING;
244                addString(xmlAttr->value, kLowPriority,
245                          (ResStringPool_ref*) &flatAttr->typedValue.data);
246            }
247
248            flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue));
249            flatAttr++;
250        }
251    }
252};
253
254} // namespace
255
256bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
257    BigBuffer nodeBuffer(1024);
258    XmlFlattenerVisitor visitor(&nodeBuffer, mOptions);
259    node->accept(&visitor);
260
261    // Merge the package pools into the main pool.
262    for (auto& packagePoolEntry : visitor.mPackagePools) {
263        visitor.mPool.merge(std::move(packagePoolEntry.second));
264    }
265
266    // Sort the string pool so that attribute resource IDs show up first.
267    visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
268        return a.context.priority < b.context.priority;
269    });
270
271    // Now we flatten the string pool references into the correct places.
272    for (const auto& refEntry : visitor.mStringRefs) {
273        refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex());
274    }
275
276    // Write the XML header.
277    ChunkWriter xmlHeaderWriter(mBuffer);
278    xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE);
279
280    // Flatten the StringPool.
281    StringPool::flattenUtf16(mBuffer, visitor.mPool);
282
283    {
284        // Write the array of resource IDs, indexed by StringPool order.
285        ChunkWriter resIdMapWriter(mBuffer);
286        resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
287        for (const auto& str : visitor.mPool) {
288            ResourceId id = { str->context.priority };
289            if (id.id == kLowPriority || !id.isValid()) {
290                // When we see the first non-resource ID,
291                // we're done.
292                break;
293            }
294
295            *resIdMapWriter.nextBlock<uint32_t>() = id.id;
296        }
297        resIdMapWriter.finish();
298    }
299
300    // Move the nodeBuffer and append it to the out buffer.
301    mBuffer->appendBuffer(std::move(nodeBuffer));
302
303    // Finish the xml header.
304    xmlHeaderWriter.finish();
305    return true;
306}
307
308bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
309    if (!resource->root) {
310        return false;
311    }
312    return flatten(context, resource->root.get());
313}
314
315} // namespace aapt
316