Home | History | Annotate | Download | only in flatten
      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 "flatten/XmlFlattener.h"
     18 
     19 #include <algorithm>
     20 #include <map>
     21 #include <vector>
     22 
     23 #include "android-base/logging.h"
     24 #include "android-base/macros.h"
     25 #include "androidfw/ResourceTypes.h"
     26 #include "utils/misc.h"
     27 
     28 #include "SdkConstants.h"
     29 #include "flatten/ChunkWriter.h"
     30 #include "flatten/ResourceTypeExtensions.h"
     31 #include "xml/XmlDom.h"
     32 
     33 using namespace android;
     34 
     35 namespace aapt {
     36 
     37 namespace {
     38 
     39 constexpr uint32_t kLowPriority = 0xffffffffu;
     40 
     41 static bool cmp_xml_attribute_by_id(const xml::Attribute* a,
     42                                     const xml::Attribute* b) {
     43   if (a->compiled_attribute && a->compiled_attribute.value().id) {
     44     if (b->compiled_attribute && b->compiled_attribute.value().id) {
     45       return a->compiled_attribute.value().id.value() <
     46              b->compiled_attribute.value().id.value();
     47     }
     48     return true;
     49   } else if (!b->compiled_attribute) {
     50     int diff = a->namespace_uri.compare(b->namespace_uri);
     51     if (diff < 0) {
     52       return true;
     53     } else if (diff > 0) {
     54       return false;
     55     }
     56     return a->name < b->name;
     57   }
     58   return false;
     59 }
     60 
     61 class XmlFlattenerVisitor : public xml::Visitor {
     62  public:
     63   using xml::Visitor::Visit;
     64 
     65   StringPool pool;
     66   std::map<uint8_t, StringPool> package_pools;
     67 
     68   struct StringFlattenDest {
     69     StringPool::Ref ref;
     70     ResStringPool_ref* dest;
     71   };
     72 
     73   std::vector<StringFlattenDest> string_refs;
     74 
     75   XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options)
     76       : buffer_(buffer), options_(options) {}
     77 
     78   void Visit(xml::Namespace* node) override {
     79     if (node->namespace_uri == xml::kSchemaTools) {
     80       // Skip dedicated tools namespace.
     81       xml::Visitor::Visit(node);
     82     } else {
     83       WriteNamespace(node, android::RES_XML_START_NAMESPACE_TYPE);
     84       xml::Visitor::Visit(node);
     85       WriteNamespace(node, android::RES_XML_END_NAMESPACE_TYPE);
     86     }
     87   }
     88 
     89   void Visit(xml::Text* node) override {
     90     if (util::TrimWhitespace(node->text).empty()) {
     91       // Skip whitespace only text nodes.
     92       return;
     93     }
     94 
     95     ChunkWriter writer(buffer_);
     96     ResXMLTree_node* flat_node =
     97         writer.StartChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE);
     98     flat_node->lineNumber = util::HostToDevice32(node->line_number);
     99     flat_node->comment.index = util::HostToDevice32(-1);
    100 
    101     ResXMLTree_cdataExt* flat_text = writer.NextBlock<ResXMLTree_cdataExt>();
    102 
    103     // Process plain strings to make sure they get properly escaped.
    104     util::StringBuilder builder;
    105     builder.Append(node->text);
    106     AddString(builder.ToString(), kLowPriority, &flat_text->data);
    107 
    108     writer.Finish();
    109   }
    110 
    111   void Visit(xml::Element* node) override {
    112     {
    113       ChunkWriter start_writer(buffer_);
    114       ResXMLTree_node* flat_node =
    115           start_writer.StartChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE);
    116       flat_node->lineNumber = util::HostToDevice32(node->line_number);
    117       flat_node->comment.index = util::HostToDevice32(-1);
    118 
    119       ResXMLTree_attrExt* flat_elem =
    120           start_writer.NextBlock<ResXMLTree_attrExt>();
    121 
    122       // A missing namespace must be null, not an empty string. Otherwise the
    123       // runtime complains.
    124       AddString(node->namespace_uri, kLowPriority, &flat_elem->ns,
    125                 true /* treat_empty_string_as_null */);
    126       AddString(node->name, kLowPriority, &flat_elem->name,
    127                 true /* treat_empty_string_as_null */);
    128 
    129       flat_elem->attributeStart = util::HostToDevice16(sizeof(*flat_elem));
    130       flat_elem->attributeSize =
    131           util::HostToDevice16(sizeof(ResXMLTree_attribute));
    132 
    133       WriteAttributes(node, flat_elem, &start_writer);
    134 
    135       start_writer.Finish();
    136     }
    137 
    138     xml::Visitor::Visit(node);
    139 
    140     {
    141       ChunkWriter end_writer(buffer_);
    142       ResXMLTree_node* flat_end_node =
    143           end_writer.StartChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE);
    144       flat_end_node->lineNumber = util::HostToDevice32(node->line_number);
    145       flat_end_node->comment.index = util::HostToDevice32(-1);
    146 
    147       ResXMLTree_endElementExt* flat_end_elem =
    148           end_writer.NextBlock<ResXMLTree_endElementExt>();
    149       AddString(node->namespace_uri, kLowPriority, &flat_end_elem->ns,
    150                 true /* treat_empty_string_as_null */);
    151       AddString(node->name, kLowPriority, &flat_end_elem->name);
    152 
    153       end_writer.Finish();
    154     }
    155   }
    156 
    157  private:
    158   DISALLOW_COPY_AND_ASSIGN(XmlFlattenerVisitor);
    159 
    160   void AddString(const StringPiece& str, uint32_t priority,
    161                  android::ResStringPool_ref* dest,
    162                  bool treat_empty_string_as_null = false) {
    163     if (str.empty() && treat_empty_string_as_null) {
    164       // Some parts of the runtime treat null differently than empty string.
    165       dest->index = util::DeviceToHost32(-1);
    166     } else {
    167       string_refs.push_back(StringFlattenDest{
    168           pool.MakeRef(str, StringPool::Context(priority)), dest});
    169     }
    170   }
    171 
    172   void AddString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) {
    173     string_refs.push_back(StringFlattenDest{ref, dest});
    174   }
    175 
    176   void WriteNamespace(xml::Namespace* node, uint16_t type) {
    177     ChunkWriter writer(buffer_);
    178 
    179     ResXMLTree_node* flatNode = writer.StartChunk<ResXMLTree_node>(type);
    180     flatNode->lineNumber = util::HostToDevice32(node->line_number);
    181     flatNode->comment.index = util::HostToDevice32(-1);
    182 
    183     ResXMLTree_namespaceExt* flat_ns =
    184         writer.NextBlock<ResXMLTree_namespaceExt>();
    185     AddString(node->namespace_prefix, kLowPriority, &flat_ns->prefix);
    186     AddString(node->namespace_uri, kLowPriority, &flat_ns->uri);
    187 
    188     writer.Finish();
    189   }
    190 
    191   void WriteAttributes(xml::Element* node, ResXMLTree_attrExt* flat_elem, ChunkWriter* writer) {
    192     filtered_attrs_.clear();
    193     filtered_attrs_.reserve(node->attributes.size());
    194 
    195     // Filter the attributes.
    196     for (xml::Attribute& attr : node->attributes) {
    197       if (attr.namespace_uri != xml::kSchemaTools) {
    198         filtered_attrs_.push_back(&attr);
    199       }
    200     }
    201 
    202     if (filtered_attrs_.empty()) {
    203       return;
    204     }
    205 
    206     const ResourceId kIdAttr(0x010100d0);
    207 
    208     std::sort(filtered_attrs_.begin(), filtered_attrs_.end(), cmp_xml_attribute_by_id);
    209 
    210     flat_elem->attributeCount = util::HostToDevice16(filtered_attrs_.size());
    211 
    212     ResXMLTree_attribute* flat_attr =
    213         writer->NextBlock<ResXMLTree_attribute>(filtered_attrs_.size());
    214     uint16_t attribute_index = 1;
    215     for (const xml::Attribute* xml_attr : filtered_attrs_) {
    216       // Assign the indices for specific attributes.
    217       if (xml_attr->compiled_attribute &&
    218           xml_attr->compiled_attribute.value().id &&
    219           xml_attr->compiled_attribute.value().id.value() == kIdAttr) {
    220         flat_elem->idIndex = util::HostToDevice16(attribute_index);
    221       } else if (xml_attr->namespace_uri.empty()) {
    222         if (xml_attr->name == "class") {
    223           flat_elem->classIndex = util::HostToDevice16(attribute_index);
    224         } else if (xml_attr->name == "style") {
    225           flat_elem->styleIndex = util::HostToDevice16(attribute_index);
    226         }
    227       }
    228       attribute_index++;
    229 
    230       // Add the namespaceUri to the list of StringRefs to encode. Use null if the namespace
    231       // is empty (doesn't exist).
    232       AddString(xml_attr->namespace_uri, kLowPriority, &flat_attr->ns,
    233                 true /* treat_empty_string_as_null */);
    234 
    235       flat_attr->rawValue.index = util::HostToDevice32(-1);
    236 
    237       if (!xml_attr->compiled_attribute || !xml_attr->compiled_attribute.value().id) {
    238         // The attribute has no associated ResourceID, so the string order doesn't matter.
    239         AddString(xml_attr->name, kLowPriority, &flat_attr->name);
    240       } else {
    241         // Attribute names are stored without packages, but we use
    242         // their StringPool index to lookup their resource IDs.
    243         // This will cause collisions, so we can't dedupe
    244         // attribute names from different packages. We use separate
    245         // pools that we later combine.
    246         //
    247         // Lookup the StringPool for this package and make the reference there.
    248         const xml::AaptAttribute& aapt_attr = xml_attr->compiled_attribute.value();
    249 
    250         StringPool::Ref name_ref =
    251             package_pools[aapt_attr.id.value().package_id()].MakeRef(
    252                 xml_attr->name, StringPool::Context(aapt_attr.id.value().id));
    253 
    254         // Add it to the list of strings to flatten.
    255         AddString(name_ref, &flat_attr->name);
    256       }
    257 
    258       // Process plain strings to make sure they get properly escaped.
    259       StringPiece raw_value = xml_attr->value;
    260       util::StringBuilder str_builder;
    261       if (!options_.keep_raw_values) {
    262         str_builder.Append(xml_attr->value);
    263         raw_value = str_builder.ToString();
    264       }
    265 
    266       if (options_.keep_raw_values || !xml_attr->compiled_value) {
    267         // Keep raw values if the value is not compiled or
    268         // if we're building a static library (need symbols).
    269         AddString(raw_value, kLowPriority, &flat_attr->rawValue);
    270       }
    271 
    272       if (xml_attr->compiled_value) {
    273         CHECK(xml_attr->compiled_value->Flatten(&flat_attr->typedValue));
    274       } else {
    275         // Flatten as a regular string type.
    276         flat_attr->typedValue.dataType = android::Res_value::TYPE_STRING;
    277 
    278         AddString(str_builder.ToString(), kLowPriority,
    279                   (ResStringPool_ref*) &flat_attr->typedValue.data);
    280       }
    281 
    282       flat_attr->typedValue.size = util::HostToDevice16(sizeof(flat_attr->typedValue));
    283       flat_attr++;
    284     }
    285   }
    286 
    287   BigBuffer* buffer_;
    288   XmlFlattenerOptions options_;
    289 
    290   // Scratch vector to filter attributes. We avoid allocations
    291   // making this a member.
    292   std::vector<xml::Attribute*> filtered_attrs_;
    293 };
    294 
    295 }  // namespace
    296 
    297 bool XmlFlattener::Flatten(IAaptContext* context, xml::Node* node) {
    298   BigBuffer node_buffer(1024);
    299   XmlFlattenerVisitor visitor(&node_buffer, options_);
    300   node->Accept(&visitor);
    301 
    302   // Merge the package pools into the main pool.
    303   for (auto& package_pool_entry : visitor.package_pools) {
    304     visitor.pool.Merge(std::move(package_pool_entry.second));
    305   }
    306 
    307   // Sort the string pool so that attribute resource IDs show up first.
    308   visitor.pool.Sort(
    309       [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
    310         return a.context.priority < b.context.priority;
    311       });
    312 
    313   // Now we flatten the string pool references into the correct places.
    314   for (const auto& ref_entry : visitor.string_refs) {
    315     ref_entry.dest->index = util::HostToDevice32(ref_entry.ref.index());
    316   }
    317 
    318   // Write the XML header.
    319   ChunkWriter xml_header_writer(buffer_);
    320   xml_header_writer.StartChunk<ResXMLTree_header>(RES_XML_TYPE);
    321 
    322   // Flatten the StringPool.
    323   StringPool::FlattenUtf8(buffer_, visitor.pool);
    324 
    325   {
    326     // Write the array of resource IDs, indexed by StringPool order.
    327     ChunkWriter res_id_map_writer(buffer_);
    328     res_id_map_writer.StartChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE);
    329     for (const auto& str : visitor.pool) {
    330       ResourceId id = {str->context.priority};
    331       if (id.id == kLowPriority || !id.is_valid()) {
    332         // When we see the first non-resource ID,
    333         // we're done.
    334         break;
    335       }
    336 
    337       *res_id_map_writer.NextBlock<uint32_t>() = id.id;
    338     }
    339     res_id_map_writer.Finish();
    340   }
    341 
    342   // Move the nodeBuffer and append it to the out buffer.
    343   buffer_->AppendBuffer(std::move(node_buffer));
    344 
    345   // Finish the xml header.
    346   xml_header_writer.Finish();
    347   return true;
    348 }
    349 
    350 bool XmlFlattener::Consume(IAaptContext* context, xml::XmlResource* resource) {
    351   if (!resource->root) {
    352     return false;
    353   }
    354   return Flatten(context, resource->root.get());
    355 }
    356 
    357 }  // namespace aapt
    358