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