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