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 "ResourceParser.h"
18 
19 #include <functional>
20 #include <limits>
21 #include <sstream>
22 
23 #include "android-base/logging.h"
24 
25 #include "ResourceTable.h"
26 #include "ResourceUtils.h"
27 #include "ResourceValues.h"
28 #include "ValueVisitor.h"
29 #include "text/Utf8Iterator.h"
30 #include "util/ImmutableMap.h"
31 #include "util/Maybe.h"
32 #include "util/Util.h"
33 #include "xml/XmlPullParser.h"
34 
35 using ::aapt::ResourceUtils::StringBuilder;
36 using ::aapt::text::Utf8Iterator;
37 using ::android::ConfigDescription;
38 using ::android::StringPiece;
39 
40 namespace aapt {
41 
42 constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
43 
44 // Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
ShouldIgnoreElement(const StringPiece & ns,const StringPiece & name)45 static bool ShouldIgnoreElement(const StringPiece& ns, const StringPiece& name) {
46   return ns.empty() && (name == "skip" || name == "eat-comment");
47 }
48 
ParseFormatTypeNoEnumsOrFlags(const StringPiece & piece)49 static uint32_t ParseFormatTypeNoEnumsOrFlags(const StringPiece& piece) {
50   if (piece == "reference") {
51     return android::ResTable_map::TYPE_REFERENCE;
52   } else if (piece == "string") {
53     return android::ResTable_map::TYPE_STRING;
54   } else if (piece == "integer") {
55     return android::ResTable_map::TYPE_INTEGER;
56   } else if (piece == "boolean") {
57     return android::ResTable_map::TYPE_BOOLEAN;
58   } else if (piece == "color") {
59     return android::ResTable_map::TYPE_COLOR;
60   } else if (piece == "float") {
61     return android::ResTable_map::TYPE_FLOAT;
62   } else if (piece == "dimension") {
63     return android::ResTable_map::TYPE_DIMENSION;
64   } else if (piece == "fraction") {
65     return android::ResTable_map::TYPE_FRACTION;
66   }
67   return 0;
68 }
69 
ParseFormatType(const StringPiece & piece)70 static uint32_t ParseFormatType(const StringPiece& piece) {
71   if (piece == "enum") {
72     return android::ResTable_map::TYPE_ENUM;
73   } else if (piece == "flags") {
74     return android::ResTable_map::TYPE_FLAGS;
75   }
76   return ParseFormatTypeNoEnumsOrFlags(piece);
77 }
78 
ParseFormatAttribute(const StringPiece & str)79 static uint32_t ParseFormatAttribute(const StringPiece& str) {
80   uint32_t mask = 0;
81   for (const StringPiece& part : util::Tokenize(str, '|')) {
82     StringPiece trimmed_part = util::TrimWhitespace(part);
83     uint32_t type = ParseFormatType(trimmed_part);
84     if (type == 0) {
85       return 0;
86     }
87     mask |= type;
88   }
89   return mask;
90 }
91 
92 // A parsed resource ready to be added to the ResourceTable.
93 struct ParsedResource {
94   ResourceName name;
95   ConfigDescription config;
96   std::string product;
97   Source source;
98 
99   ResourceId id;
100   Visibility::Level visibility_level = Visibility::Level::kUndefined;
101   bool allow_new = false;
102   Maybe<OverlayableItem> overlayable_item;
103 
104   std::string comment;
105   std::unique_ptr<Value> value;
106   std::list<ParsedResource> child_resources;
107 };
108 
109 // Recursively adds resources to the ResourceTable.
AddResourcesToTable(ResourceTable * table,IDiagnostics * diag,ParsedResource * res)110 static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) {
111   StringPiece trimmed_comment = util::TrimWhitespace(res->comment);
112   if (trimmed_comment.size() != res->comment.size()) {
113     // Only if there was a change do we re-assign.
114     res->comment = trimmed_comment.to_string();
115   }
116 
117   if (res->visibility_level != Visibility::Level::kUndefined) {
118     Visibility visibility;
119     visibility.level = res->visibility_level;
120     visibility.source = res->source;
121     visibility.comment = res->comment;
122     if (!table->SetVisibilityWithId(res->name, visibility, res->id, diag)) {
123       return false;
124     }
125   }
126 
127   if (res->allow_new) {
128     AllowNew allow_new;
129     allow_new.source = res->source;
130     allow_new.comment = res->comment;
131     if (!table->SetAllowNew(res->name, allow_new, diag)) {
132       return false;
133     }
134   }
135 
136   if (res->overlayable_item) {
137     if (!table->SetOverlayable(res->name, res->overlayable_item.value(), diag)) {
138       return false;
139     }
140   }
141 
142   if (res->value != nullptr) {
143     // Attach the comment, source and config to the value.
144     res->value->SetComment(std::move(res->comment));
145     res->value->SetSource(std::move(res->source));
146 
147     if (!table->AddResourceWithId(res->name, res->id, res->config, res->product,
148                                   std::move(res->value), diag)) {
149       return false;
150     }
151   }
152 
153   bool error = false;
154   for (ParsedResource& child : res->child_resources) {
155     error |= !AddResourcesToTable(table, diag, &child);
156   }
157   return !error;
158 }
159 
160 // Convenient aliases for more readable function calls.
161 enum { kAllowRawString = true, kNoRawString = false };
162 
ResourceParser(IDiagnostics * diag,ResourceTable * table,const Source & source,const ConfigDescription & config,const ResourceParserOptions & options)163 ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table,
164                                const Source& source,
165                                const ConfigDescription& config,
166                                const ResourceParserOptions& options)
167     : diag_(diag),
168       table_(table),
169       source_(source),
170       config_(config),
171       options_(options) {}
172 
173 // Base class Node for representing the various Spans and UntranslatableSections of an XML string.
174 // This will be used to traverse and flatten the XML string into a single std::string, with all
175 // Span and Untranslatable data maintained in parallel, as indices into the string.
176 class Node {
177  public:
178   virtual ~Node() = default;
179 
180   // Adds the given child node to this parent node's set of child nodes, moving ownership to the
181   // parent node as well.
182   // Returns a pointer to the child node that was added as a convenience.
183   template <typename T>
AddChild(std::unique_ptr<T> node)184   T* AddChild(std::unique_ptr<T> node) {
185     T* raw_ptr = node.get();
186     children.push_back(std::move(node));
187     return raw_ptr;
188   }
189 
Build(StringBuilder * builder) const190   virtual void Build(StringBuilder* builder) const {
191     for (const auto& child : children) {
192       child->Build(builder);
193     }
194   }
195 
196   std::vector<std::unique_ptr<Node>> children;
197 };
198 
199 // A chunk of text in the XML string. This lives between other tags, such as XLIFF tags and Spans.
200 class SegmentNode : public Node {
201  public:
202   std::string data;
203 
Build(StringBuilder * builder) const204   void Build(StringBuilder* builder) const override {
205     builder->AppendText(data);
206   }
207 };
208 
209 // A tag that will be encoded into the final flattened string. Tags like <b> or <i>.
210 class SpanNode : public Node {
211  public:
212   std::string name;
213 
Build(StringBuilder * builder) const214   void Build(StringBuilder* builder) const override {
215     StringBuilder::SpanHandle span_handle = builder->StartSpan(name);
216     Node::Build(builder);
217     builder->EndSpan(span_handle);
218   }
219 };
220 
221 // An XLIFF 'g' tag, which marks a section of the string as untranslatable.
222 class UntranslatableNode : public Node {
223  public:
Build(StringBuilder * builder) const224   void Build(StringBuilder* builder) const override {
225     StringBuilder::UntranslatableHandle handle = builder->StartUntranslatable();
226     Node::Build(builder);
227     builder->EndUntranslatable(handle);
228   }
229 };
230 
231 // Build a string from XML that converts nested elements into Span objects.
FlattenXmlSubtree(xml::XmlPullParser * parser,std::string * out_raw_string,StyleString * out_style_string,std::vector<UntranslatableSection> * out_untranslatable_sections)232 bool ResourceParser::FlattenXmlSubtree(
233     xml::XmlPullParser* parser, std::string* out_raw_string, StyleString* out_style_string,
234     std::vector<UntranslatableSection>* out_untranslatable_sections) {
235   std::string raw_string;
236   std::string current_text;
237 
238   // The first occurrence of a <xliff:g> tag. Nested <xliff:g> tags are illegal.
239   Maybe<size_t> untranslatable_start_depth;
240 
241   Node root;
242   std::vector<Node*> node_stack;
243   node_stack.push_back(&root);
244 
245   bool saw_span_node = false;
246   SegmentNode* first_segment = nullptr;
247   SegmentNode* last_segment = nullptr;
248 
249   size_t depth = 1;
250   while (depth > 0 && xml::XmlPullParser::IsGoodEvent(parser->Next())) {
251     const xml::XmlPullParser::Event event = parser->event();
252 
253     // First take care of any SegmentNodes that should be created.
254     if (event == xml::XmlPullParser::Event::kStartElement
255         || event == xml::XmlPullParser::Event::kEndElement) {
256       if (!current_text.empty()) {
257         auto segment_node = util::make_unique<SegmentNode>();
258         segment_node->data = std::move(current_text);
259 
260         last_segment = node_stack.back()->AddChild(std::move(segment_node));
261         if (first_segment == nullptr) {
262           first_segment = last_segment;
263         }
264         current_text = {};
265       }
266     }
267 
268     switch (event) {
269       case xml::XmlPullParser::Event::kText: {
270         current_text += parser->text();
271         raw_string += parser->text();
272       } break;
273 
274       case xml::XmlPullParser::Event::kStartElement: {
275         if (parser->element_namespace().empty()) {
276           // This is an HTML tag which we encode as a span. Add it to the span stack.
277           std::unique_ptr<SpanNode> span_node = util::make_unique<SpanNode>();
278           span_node->name = parser->element_name();
279           const auto end_attr_iter = parser->end_attributes();
280           for (auto attr_iter = parser->begin_attributes(); attr_iter != end_attr_iter;
281                ++attr_iter) {
282             span_node->name += ";";
283             span_node->name += attr_iter->name;
284             span_node->name += "=";
285             span_node->name += attr_iter->value;
286           }
287 
288           node_stack.push_back(node_stack.back()->AddChild(std::move(span_node)));
289           saw_span_node = true;
290         } else if (parser->element_namespace() == sXliffNamespaceUri) {
291           // This is an XLIFF tag, which is not encoded as a span.
292           if (parser->element_name() == "g") {
293             // Check that an 'untranslatable' tag is not already being processed. Nested
294             // <xliff:g> tags are illegal.
295             if (untranslatable_start_depth) {
296               diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
297                            << "illegal nested XLIFF 'g' tag");
298               return false;
299             } else {
300               // Mark the beginning of an 'untranslatable' section.
301               untranslatable_start_depth = depth;
302               node_stack.push_back(
303                   node_stack.back()->AddChild(util::make_unique<UntranslatableNode>()));
304             }
305           } else {
306             // Ignore unknown XLIFF tags, but don't warn.
307             node_stack.push_back(node_stack.back()->AddChild(util::make_unique<Node>()));
308           }
309         } else {
310           // Besides XLIFF, any other namespaced tag is unsupported and ignored.
311           diag_->Warn(DiagMessage(source_.WithLine(parser->line_number()))
312                       << "ignoring element '" << parser->element_name()
313                       << "' with unknown namespace '" << parser->element_namespace() << "'");
314           node_stack.push_back(node_stack.back()->AddChild(util::make_unique<Node>()));
315         }
316 
317         // Enter one level inside the element.
318         depth++;
319       } break;
320 
321       case xml::XmlPullParser::Event::kEndElement: {
322         // Return one level from within the element.
323         depth--;
324         if (depth == 0) {
325           break;
326         }
327 
328         node_stack.pop_back();
329         if (untranslatable_start_depth == make_value(depth)) {
330           // This is the end of an untranslatable section.
331           untranslatable_start_depth = {};
332         }
333       } break;
334 
335       default:
336         // ignore.
337         break;
338     }
339   }
340 
341   // Sanity check to make sure we processed all the nodes.
342   CHECK(node_stack.size() == 1u);
343   CHECK(node_stack.back() == &root);
344 
345   if (!saw_span_node) {
346     // If there were no spans, we must treat this string a little differently (according to AAPT).
347     // Find and strip the leading whitespace from the first segment, and the trailing whitespace
348     // from the last segment.
349     if (first_segment != nullptr) {
350       // Trim leading whitespace.
351       StringPiece trimmed = util::TrimLeadingWhitespace(first_segment->data);
352       if (trimmed.size() != first_segment->data.size()) {
353         first_segment->data = trimmed.to_string();
354       }
355     }
356 
357     if (last_segment != nullptr) {
358       // Trim trailing whitespace.
359       StringPiece trimmed = util::TrimTrailingWhitespace(last_segment->data);
360       if (trimmed.size() != last_segment->data.size()) {
361         last_segment->data = trimmed.to_string();
362       }
363     }
364   }
365 
366   // Have the XML structure flatten itself into the StringBuilder. The StringBuilder will take
367   // care of recording the correctly adjusted Spans and UntranslatableSections.
368   StringBuilder builder;
369   root.Build(&builder);
370   if (!builder) {
371     diag_->Error(DiagMessage(source_.WithLine(parser->line_number())) << builder.GetError());
372     return false;
373   }
374 
375   ResourceUtils::FlattenedXmlString flattened_string = builder.GetFlattenedString();
376   *out_raw_string = std::move(raw_string);
377   *out_untranslatable_sections = std::move(flattened_string.untranslatable_sections);
378   out_style_string->str = std::move(flattened_string.text);
379   out_style_string->spans = std::move(flattened_string.spans);
380   return true;
381 }
382 
Parse(xml::XmlPullParser * parser)383 bool ResourceParser::Parse(xml::XmlPullParser* parser) {
384   bool error = false;
385   const size_t depth = parser->depth();
386   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
387     if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
388       // Skip comments and text.
389       continue;
390     }
391 
392     if (!parser->element_namespace().empty() || parser->element_name() != "resources") {
393       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
394                    << "root element must be <resources>");
395       return false;
396     }
397 
398     error |= !ParseResources(parser);
399     break;
400   };
401 
402   if (parser->event() == xml::XmlPullParser::Event::kBadDocument) {
403     diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
404                  << "xml parser error: " << parser->error());
405     return false;
406   }
407   return !error;
408 }
409 
ParseResources(xml::XmlPullParser * parser)410 bool ResourceParser::ParseResources(xml::XmlPullParser* parser) {
411   std::set<ResourceName> stripped_resources;
412 
413   bool error = false;
414   std::string comment;
415   const size_t depth = parser->depth();
416   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
417     const xml::XmlPullParser::Event event = parser->event();
418     if (event == xml::XmlPullParser::Event::kComment) {
419       comment = parser->comment();
420       continue;
421     }
422 
423     if (event == xml::XmlPullParser::Event::kText) {
424       if (!util::TrimWhitespace(parser->text()).empty()) {
425         diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
426                      << "plain text not allowed here");
427         error = true;
428       }
429       continue;
430     }
431 
432     CHECK(event == xml::XmlPullParser::Event::kStartElement);
433 
434     if (!parser->element_namespace().empty()) {
435       // Skip unknown namespace.
436       continue;
437     }
438 
439     std::string element_name = parser->element_name();
440     if (element_name == "skip" || element_name == "eat-comment") {
441       comment = "";
442       continue;
443     }
444 
445     ParsedResource parsed_resource;
446     parsed_resource.config = config_;
447     parsed_resource.source = source_.WithLine(parser->line_number());
448     // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment
449     parsed_resource.comment = std::move(comment);
450     if (options_.visibility) {
451       parsed_resource.visibility_level = options_.visibility.value();
452     }
453 
454     // Extract the product name if it exists.
455     if (Maybe<StringPiece> maybe_product = xml::FindNonEmptyAttribute(parser, "product")) {
456       parsed_resource.product = maybe_product.value().to_string();
457     }
458 
459     // Parse the resource regardless of product.
460     if (!ParseResource(parser, &parsed_resource)) {
461       error = true;
462       continue;
463     }
464 
465     if (!AddResourcesToTable(table_, diag_, &parsed_resource)) {
466       error = true;
467     }
468   }
469 
470   // Check that we included at least one variant of each stripped resource.
471   for (const ResourceName& stripped_resource : stripped_resources) {
472     if (!table_->FindResource(stripped_resource)) {
473       // Failed to find the resource.
474       diag_->Error(DiagMessage(source_) << "resource '" << stripped_resource
475                                         << "' was filtered out but no product variant remains");
476       error = true;
477     }
478   }
479 
480   return !error;
481 }
482 
ParseResource(xml::XmlPullParser * parser,ParsedResource * out_resource)483 bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
484                                    ParsedResource* out_resource) {
485   struct ItemTypeFormat {
486     ResourceType type;
487     uint32_t format;
488   };
489 
490   using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*,
491                                           ParsedResource*)>;
492 
493   static const auto elToItemMap = ImmutableMap<std::string, ItemTypeFormat>::CreatePreSorted({
494       {"bool", {ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN}},
495       {"color", {ResourceType::kColor, android::ResTable_map::TYPE_COLOR}},
496       {"configVarying", {ResourceType::kConfigVarying, android::ResTable_map::TYPE_ANY}},
497       {"dimen",
498        {ResourceType::kDimen,
499         android::ResTable_map::TYPE_FLOAT | android::ResTable_map::TYPE_FRACTION |
500             android::ResTable_map::TYPE_DIMENSION}},
501       {"drawable", {ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR}},
502       {"fraction",
503        {ResourceType::kFraction,
504         android::ResTable_map::TYPE_FLOAT | android::ResTable_map::TYPE_FRACTION |
505             android::ResTable_map::TYPE_DIMENSION}},
506       {"integer", {ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER}},
507       {"string", {ResourceType::kString, android::ResTable_map::TYPE_STRING}},
508   });
509 
510   static const auto elToBagMap = ImmutableMap<std::string, BagParseFunc>::CreatePreSorted({
511       {"add-resource", std::mem_fn(&ResourceParser::ParseAddResource)},
512       {"array", std::mem_fn(&ResourceParser::ParseArray)},
513       {"attr", std::mem_fn(&ResourceParser::ParseAttr)},
514       {"configVarying",
515        std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kConfigVarying,
516                  std::placeholders::_2, std::placeholders::_3)},
517       {"declare-styleable", std::mem_fn(&ResourceParser::ParseDeclareStyleable)},
518       {"integer-array", std::mem_fn(&ResourceParser::ParseIntegerArray)},
519       {"java-symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
520       {"overlayable", std::mem_fn(&ResourceParser::ParseOverlayable)},
521       {"plurals", std::mem_fn(&ResourceParser::ParsePlural)},
522       {"public", std::mem_fn(&ResourceParser::ParsePublic)},
523       {"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)},
524       {"string-array", std::mem_fn(&ResourceParser::ParseStringArray)},
525       {"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle,
526                           std::placeholders::_2, std::placeholders::_3)},
527       {"symbol", std::mem_fn(&ResourceParser::ParseSymbol)},
528   });
529 
530   std::string resource_type = parser->element_name();
531 
532   // The value format accepted for this resource.
533   uint32_t resource_format = 0u;
534 
535   bool can_be_item = true;
536   bool can_be_bag = true;
537   if (resource_type == "item") {
538     can_be_bag = false;
539 
540     // The default format for <item> is any. If a format attribute is present, that one will
541     // override the default.
542     resource_format = android::ResTable_map::TYPE_ANY;
543 
544     // Items have their type encoded in the type attribute.
545     if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
546       resource_type = maybe_type.value().to_string();
547     } else {
548       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
549                    << "<item> must have a 'type' attribute");
550       return false;
551     }
552 
553     if (Maybe<StringPiece> maybe_format = xml::FindNonEmptyAttribute(parser, "format")) {
554       // An explicit format for this resource was specified. The resource will
555       // retain its type in its name, but the accepted value for this type is
556       // overridden.
557       resource_format = ParseFormatTypeNoEnumsOrFlags(maybe_format.value());
558       if (!resource_format) {
559         diag_->Error(DiagMessage(out_resource->source)
560                      << "'" << maybe_format.value()
561                      << "' is an invalid format");
562         return false;
563       }
564     }
565   } else if (resource_type == "bag") {
566     can_be_item = false;
567 
568     // Bags have their type encoded in the type attribute.
569     if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
570       resource_type = maybe_type.value().to_string();
571     } else {
572       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
573                    << "<bag> must have a 'type' attribute");
574       return false;
575     }
576   }
577 
578   // Get the name of the resource. This will be checked later, because not all
579   // XML elements require a name.
580   Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
581 
582   if (resource_type == "id") {
583     if (!maybe_name) {
584       diag_->Error(DiagMessage(out_resource->source)
585                    << "<" << parser->element_name()
586                    << "> missing 'name' attribute");
587       return false;
588     }
589 
590     out_resource->name.type = ResourceType::kId;
591     out_resource->name.entry = maybe_name.value().to_string();
592 
593     // Ids either represent a unique resource id or reference another resource id
594     auto item = ParseItem(parser, out_resource, resource_format);
595     if (!item) {
596       return false;
597     }
598 
599     String* empty = ValueCast<String>(out_resource->value.get());
600     if (empty && *empty->value == "") {
601       // If no inner element exists, represent a unique identifier
602       out_resource->value = util::make_unique<Id>();
603     } else {
604       Reference* ref = ValueCast<Reference>(out_resource->value.get());
605       if (ref && !ref->name && !ref->id) {
606         // A null reference also means there is no inner element when ids are in the form:
607         //    <id name="name"/>
608         out_resource->value = util::make_unique<Id>();
609       } else if (!ref || ref->name.value().type != ResourceType::kId) {
610         // If an inner element exists, the inner element must be a reference to another resource id
611         diag_->Error(DiagMessage(out_resource->source)
612                          << "<" << parser->element_name()
613                          << "> inner element must either be a resource reference or empty");
614         return false;
615       }
616     }
617 
618     return true;
619   }
620 
621   if (can_be_item) {
622     const auto item_iter = elToItemMap.find(resource_type);
623     if (item_iter != elToItemMap.end()) {
624       // This is an item, record its type and format and start parsing.
625 
626       if (!maybe_name) {
627         diag_->Error(DiagMessage(out_resource->source)
628                      << "<" << parser->element_name() << "> missing 'name' attribute");
629         return false;
630       }
631 
632       out_resource->name.type = item_iter->second.type;
633       out_resource->name.entry = maybe_name.value().to_string();
634 
635       // Only use the implied format of the type when there is no explicit format.
636       if (resource_format == 0u) {
637         resource_format = item_iter->second.format;
638       }
639 
640       if (!ParseItem(parser, out_resource, resource_format)) {
641         return false;
642       }
643       return true;
644     }
645   }
646 
647   // This might be a bag or something.
648   if (can_be_bag) {
649     const auto bag_iter = elToBagMap.find(resource_type);
650     if (bag_iter != elToBagMap.end()) {
651       // Ensure we have a name (unless this is a <public-group> or <overlayable>).
652       if (resource_type != "public-group" && resource_type != "overlayable") {
653         if (!maybe_name) {
654           diag_->Error(DiagMessage(out_resource->source)
655                        << "<" << parser->element_name() << "> missing 'name' attribute");
656           return false;
657         }
658 
659         out_resource->name.entry = maybe_name.value().to_string();
660       }
661 
662       // Call the associated parse method. The type will be filled in by the
663       // parse func.
664       if (!bag_iter->second(this, parser, out_resource)) {
665         return false;
666       }
667       return true;
668     }
669   }
670 
671   if (can_be_item) {
672     // Try parsing the elementName (or type) as a resource. These shall only be
673     // resources like 'layout' or 'xml' and they can only be references.
674     const ResourceType* parsed_type = ParseResourceType(resource_type);
675     if (parsed_type) {
676       if (!maybe_name) {
677         diag_->Error(DiagMessage(out_resource->source)
678                      << "<" << parser->element_name()
679                      << "> missing 'name' attribute");
680         return false;
681       }
682 
683       out_resource->name.type = *parsed_type;
684       out_resource->name.entry = maybe_name.value().to_string();
685       out_resource->value = ParseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString);
686       if (!out_resource->value) {
687         diag_->Error(DiagMessage(out_resource->source)
688                      << "invalid value for type '" << *parsed_type << "'. Expected a reference");
689         return false;
690       }
691       return true;
692     }
693   }
694 
695   // If the resource type was not recognized, write the error and return false.
696   diag_->Error(DiagMessage(out_resource->source)
697               << "unknown resource type '" << resource_type << "'");
698   return false;
699 }
700 
ParseItem(xml::XmlPullParser * parser,ParsedResource * out_resource,const uint32_t format)701 bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
702                                ParsedResource* out_resource,
703                                const uint32_t format) {
704   if (format == android::ResTable_map::TYPE_STRING) {
705     return ParseString(parser, out_resource);
706   }
707 
708   out_resource->value = ParseXml(parser, format, kNoRawString);
709   if (!out_resource->value) {
710     diag_->Error(DiagMessage(out_resource->source) << "invalid "
711                                                    << out_resource->name.type);
712     return false;
713   }
714   return true;
715 }
716 
717 /**
718  * Reads the entire XML subtree and attempts to parse it as some Item,
719  * with typeMask denoting which items it can be. If allowRawValue is
720  * true, a RawString is returned if the XML couldn't be parsed as
721  * an Item. If allowRawValue is false, nullptr is returned in this
722  * case.
723  */
ParseXml(xml::XmlPullParser * parser,const uint32_t type_mask,const bool allow_raw_value)724 std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser,
725                                                const uint32_t type_mask,
726                                                const bool allow_raw_value) {
727   const size_t begin_xml_line = parser->line_number();
728 
729   std::string raw_value;
730   StyleString style_string;
731   std::vector<UntranslatableSection> untranslatable_sections;
732   if (!FlattenXmlSubtree(parser, &raw_value, &style_string, &untranslatable_sections)) {
733     return {};
734   }
735 
736   if (!style_string.spans.empty()) {
737     // This can only be a StyledString.
738     std::unique_ptr<StyledString> styled_string =
739         util::make_unique<StyledString>(table_->string_pool.MakeRef(
740             style_string, StringPool::Context(StringPool::Context::kNormalPriority, config_)));
741     styled_string->untranslatable_sections = std::move(untranslatable_sections);
742     return std::move(styled_string);
743   }
744 
745   auto on_create_reference = [&](const ResourceName& name) {
746     // name.package can be empty here, as it will assume the package name of the
747     // table.
748     std::unique_ptr<Id> id = util::make_unique<Id>();
749     id->SetSource(source_.WithLine(begin_xml_line));
750     table_->AddResource(name, {}, {}, std::move(id), diag_);
751   };
752 
753   // Process the raw value.
754   std::unique_ptr<Item> processed_item =
755       ResourceUtils::TryParseItemForAttribute(raw_value, type_mask, on_create_reference);
756   if (processed_item) {
757     // Fix up the reference.
758     if (Reference* ref = ValueCast<Reference>(processed_item.get())) {
759       ResolvePackage(parser, ref);
760     }
761     return processed_item;
762   }
763 
764   // Try making a regular string.
765   if (type_mask & android::ResTable_map::TYPE_STRING) {
766     // Use the trimmed, escaped string.
767     std::unique_ptr<String> string = util::make_unique<String>(
768         table_->string_pool.MakeRef(style_string.str, StringPool::Context(config_)));
769     string->untranslatable_sections = std::move(untranslatable_sections);
770     return std::move(string);
771   }
772 
773   // If the text is empty, and the value is not allowed to be a string, encode it as a @null.
774   if (util::TrimWhitespace(raw_value).empty()) {
775     return ResourceUtils::MakeNull();
776   }
777 
778   if (allow_raw_value) {
779     // We can't parse this so return a RawString if we are allowed.
780     return util::make_unique<RawString>(
781         table_->string_pool.MakeRef(util::TrimWhitespace(raw_value),
782                                     StringPool::Context(config_)));
783   }
784   return {};
785 }
786 
ParseString(xml::XmlPullParser * parser,ParsedResource * out_resource)787 bool ResourceParser::ParseString(xml::XmlPullParser* parser,
788                                  ParsedResource* out_resource) {
789   bool formatted = true;
790   if (Maybe<StringPiece> formatted_attr =
791           xml::FindAttribute(parser, "formatted")) {
792     Maybe<bool> maybe_formatted =
793         ResourceUtils::ParseBool(formatted_attr.value());
794     if (!maybe_formatted) {
795       diag_->Error(DiagMessage(out_resource->source)
796                    << "invalid value for 'formatted'. Must be a boolean");
797       return false;
798     }
799     formatted = maybe_formatted.value();
800   }
801 
802   bool translatable = options_.translatable;
803   if (Maybe<StringPiece> translatable_attr = xml::FindAttribute(parser, "translatable")) {
804     Maybe<bool> maybe_translatable = ResourceUtils::ParseBool(translatable_attr.value());
805     if (!maybe_translatable) {
806       diag_->Error(DiagMessage(out_resource->source)
807                    << "invalid value for 'translatable'. Must be a boolean");
808       return false;
809     }
810     translatable = maybe_translatable.value();
811   }
812 
813   out_resource->value =
814       ParseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString);
815   if (!out_resource->value) {
816     diag_->Error(DiagMessage(out_resource->source) << "not a valid string");
817     return false;
818   }
819 
820   if (String* string_value = ValueCast<String>(out_resource->value.get())) {
821     string_value->SetTranslatable(translatable);
822 
823     if (formatted && translatable) {
824       if (!util::VerifyJavaStringFormat(*string_value->value)) {
825         DiagMessage msg(out_resource->source);
826         msg << "multiple substitutions specified in non-positional format; "
827                "did you mean to add the formatted=\"false\" attribute?";
828         if (options_.error_on_positional_arguments) {
829           diag_->Error(msg);
830           return false;
831         }
832 
833         diag_->Warn(msg);
834       }
835     }
836 
837   } else if (StyledString* string_value = ValueCast<StyledString>(out_resource->value.get())) {
838     string_value->SetTranslatable(translatable);
839   }
840   return true;
841 }
842 
ParsePublic(xml::XmlPullParser * parser,ParsedResource * out_resource)843 bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) {
844   if (options_.visibility) {
845     diag_->Error(DiagMessage(out_resource->source)
846                  << "<public> tag not allowed with --visibility flag");
847     return false;
848   }
849 
850   if (out_resource->config != ConfigDescription::DefaultConfig()) {
851     diag_->Warn(DiagMessage(out_resource->source)
852                 << "ignoring configuration '" << out_resource->config << "' for <public> tag");
853   }
854 
855   Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
856   if (!maybe_type) {
857     diag_->Error(DiagMessage(out_resource->source)
858                  << "<public> must have a 'type' attribute");
859     return false;
860   }
861 
862   const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
863   if (!parsed_type) {
864     diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '"
865                                                    << maybe_type.value()
866                                                    << "' in <public>");
867     return false;
868   }
869 
870   out_resource->name.type = *parsed_type;
871 
872   if (Maybe<StringPiece> maybe_id_str = xml::FindNonEmptyAttribute(parser, "id")) {
873     Maybe<ResourceId> maybe_id = ResourceUtils::ParseResourceId(maybe_id_str.value());
874     if (!maybe_id) {
875       diag_->Error(DiagMessage(out_resource->source)
876                    << "invalid resource ID '" << maybe_id_str.value() << "' in <public>");
877       return false;
878     }
879     out_resource->id = maybe_id.value();
880   }
881 
882   if (*parsed_type == ResourceType::kId) {
883     // An ID marked as public is also the definition of an ID.
884     out_resource->value = util::make_unique<Id>();
885   }
886 
887   out_resource->visibility_level = Visibility::Level::kPublic;
888   return true;
889 }
890 
ParsePublicGroup(xml::XmlPullParser * parser,ParsedResource * out_resource)891 bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
892   if (options_.visibility) {
893     diag_->Error(DiagMessage(out_resource->source)
894                  << "<public-group> tag not allowed with --visibility flag");
895     return false;
896   }
897 
898   if (out_resource->config != ConfigDescription::DefaultConfig()) {
899     diag_->Warn(DiagMessage(out_resource->source)
900                 << "ignoring configuration '" << out_resource->config
901                 << "' for <public-group> tag");
902   }
903 
904   Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
905   if (!maybe_type) {
906     diag_->Error(DiagMessage(out_resource->source)
907                  << "<public-group> must have a 'type' attribute");
908     return false;
909   }
910 
911   const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
912   if (!parsed_type) {
913     diag_->Error(DiagMessage(out_resource->source) << "invalid resource type '"
914                                                    << maybe_type.value()
915                                                    << "' in <public-group>");
916     return false;
917   }
918 
919   Maybe<StringPiece> maybe_id_str =
920       xml::FindNonEmptyAttribute(parser, "first-id");
921   if (!maybe_id_str) {
922     diag_->Error(DiagMessage(out_resource->source)
923                  << "<public-group> must have a 'first-id' attribute");
924     return false;
925   }
926 
927   Maybe<ResourceId> maybe_id =
928       ResourceUtils::ParseResourceId(maybe_id_str.value());
929   if (!maybe_id) {
930     diag_->Error(DiagMessage(out_resource->source) << "invalid resource ID '"
931                                                    << maybe_id_str.value()
932                                                    << "' in <public-group>");
933     return false;
934   }
935 
936   ResourceId next_id = maybe_id.value();
937 
938   std::string comment;
939   bool error = false;
940   const size_t depth = parser->depth();
941   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
942     if (parser->event() == xml::XmlPullParser::Event::kComment) {
943       comment = util::TrimWhitespace(parser->comment()).to_string();
944       continue;
945     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
946       // Skip text.
947       continue;
948     }
949 
950     const Source item_source = source_.WithLine(parser->line_number());
951     const std::string& element_namespace = parser->element_namespace();
952     const std::string& element_name = parser->element_name();
953     if (element_namespace.empty() && element_name == "public") {
954       Maybe<StringPiece> maybe_name =
955           xml::FindNonEmptyAttribute(parser, "name");
956       if (!maybe_name) {
957         diag_->Error(DiagMessage(item_source)
958                      << "<public> must have a 'name' attribute");
959         error = true;
960         continue;
961       }
962 
963       if (xml::FindNonEmptyAttribute(parser, "id")) {
964         diag_->Error(DiagMessage(item_source)
965                      << "'id' is ignored within <public-group>");
966         error = true;
967         continue;
968       }
969 
970       if (xml::FindNonEmptyAttribute(parser, "type")) {
971         diag_->Error(DiagMessage(item_source)
972                      << "'type' is ignored within <public-group>");
973         error = true;
974         continue;
975       }
976 
977       ParsedResource child_resource;
978       child_resource.name.type = *parsed_type;
979       child_resource.name.entry = maybe_name.value().to_string();
980       child_resource.id = next_id;
981       // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment
982       child_resource.comment = std::move(comment);
983       child_resource.source = item_source;
984       child_resource.visibility_level = Visibility::Level::kPublic;
985       out_resource->child_resources.push_back(std::move(child_resource));
986 
987       next_id.id += 1;
988 
989     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
990       diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
991       error = true;
992     }
993   }
994   return !error;
995 }
996 
ParseSymbolImpl(xml::XmlPullParser * parser,ParsedResource * out_resource)997 bool ResourceParser::ParseSymbolImpl(xml::XmlPullParser* parser,
998                                      ParsedResource* out_resource) {
999   Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
1000   if (!maybe_type) {
1001     diag_->Error(DiagMessage(out_resource->source)
1002                  << "<" << parser->element_name()
1003                  << "> must have a 'type' attribute");
1004     return false;
1005   }
1006 
1007   const ResourceType* parsed_type = ParseResourceType(maybe_type.value());
1008   if (!parsed_type) {
1009     diag_->Error(DiagMessage(out_resource->source)
1010                  << "invalid resource type '" << maybe_type.value() << "' in <"
1011                  << parser->element_name() << ">");
1012     return false;
1013   }
1014 
1015   out_resource->name.type = *parsed_type;
1016   return true;
1017 }
1018 
ParseSymbol(xml::XmlPullParser * parser,ParsedResource * out_resource)1019 bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1020   if (options_.visibility) {
1021     diag_->Error(DiagMessage(out_resource->source)
1022                  << "<java-symbol> and <symbol> tags not allowed with --visibility flag");
1023     return false;
1024   }
1025   if (out_resource->config != ConfigDescription::DefaultConfig()) {
1026     diag_->Warn(DiagMessage(out_resource->source)
1027                 << "ignoring configuration '" << out_resource->config << "' for <"
1028                 << parser->element_name() << "> tag");
1029   }
1030 
1031   if (!ParseSymbolImpl(parser, out_resource)) {
1032     return false;
1033   }
1034 
1035   out_resource->visibility_level = Visibility::Level::kPrivate;
1036   return true;
1037 }
1038 
ParseOverlayable(xml::XmlPullParser * parser,ParsedResource * out_resource)1039 bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1040   if (out_resource->config != ConfigDescription::DefaultConfig()) {
1041     diag_->Warn(DiagMessage(out_resource->source)
1042                 << "ignoring configuration '" << out_resource->config
1043                 << "' for <overlayable> tag");
1044   }
1045 
1046   Maybe<StringPiece> overlayable_name = xml::FindNonEmptyAttribute(parser, "name");
1047   if (!overlayable_name) {
1048     diag_->Error(DiagMessage(out_resource->source)
1049                   << "<overlayable> tag must have a 'name' attribute");
1050     return false;
1051   }
1052 
1053   const std::string kActorUriScheme =
1054       android::base::StringPrintf("%s://", Overlayable::kActorScheme);
1055   Maybe<StringPiece> overlayable_actor = xml::FindNonEmptyAttribute(parser, "actor");
1056   if (overlayable_actor && !util::StartsWith(overlayable_actor.value(), kActorUriScheme)) {
1057     diag_->Error(DiagMessage(out_resource->source)
1058                  << "specified <overlayable> tag 'actor' attribute must use the scheme '"
1059                  << Overlayable::kActorScheme << "'");
1060     return false;
1061   }
1062 
1063   // Create a overlayable entry grouping that represents this <overlayable>
1064   auto overlayable = std::make_shared<Overlayable>(
1065       overlayable_name.value(), (overlayable_actor) ? overlayable_actor.value() : "",
1066       source_);
1067 
1068   bool error = false;
1069   std::string comment;
1070   OverlayableItem::PolicyFlags current_policies = OverlayableItem::Policy::kNone;
1071   const size_t start_depth = parser->depth();
1072   while (xml::XmlPullParser::IsGoodEvent(parser->Next())) {
1073     xml::XmlPullParser::Event event = parser->event();
1074     if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth) {
1075       // Break the loop when exiting the <overlayable>
1076       break;
1077     } else if (event == xml::XmlPullParser::Event::kEndElement
1078                && parser->depth() == start_depth + 1) {
1079       // Clear the current policies when exiting the <policy> tags
1080       current_policies = OverlayableItem::Policy::kNone;
1081       continue;
1082     } else if (event == xml::XmlPullParser::Event::kComment) {
1083       // Retrieve the comment of individual <item> tags
1084       comment = parser->comment();
1085       continue;
1086     } else if (event != xml::XmlPullParser::Event::kStartElement) {
1087       // Skip to the start of the next element
1088       continue;
1089     }
1090 
1091     const Source element_source = source_.WithLine(parser->line_number());
1092     const std::string& element_name = parser->element_name();
1093     const std::string& element_namespace = parser->element_namespace();
1094     if (element_namespace.empty() && element_name == "item") {
1095       if (current_policies == OverlayableItem::Policy::kNone) {
1096         diag_->Error(DiagMessage(element_source)
1097                          << "<item> within an <overlayable> must be inside a <policy> block");
1098         error = true;
1099         continue;
1100       }
1101 
1102       // Items specify the name and type of resource that should be overlayable
1103       Maybe<StringPiece> item_name = xml::FindNonEmptyAttribute(parser, "name");
1104       if (!item_name) {
1105         diag_->Error(DiagMessage(element_source)
1106                      << "<item> within an <overlayable> must have a 'name' attribute");
1107         error = true;
1108         continue;
1109       }
1110 
1111       Maybe<StringPiece> item_type = xml::FindNonEmptyAttribute(parser, "type");
1112       if (!item_type) {
1113         diag_->Error(DiagMessage(element_source)
1114                      << "<item> within an <overlayable> must have a 'type' attribute");
1115         error = true;
1116         continue;
1117       }
1118 
1119       const ResourceType* type = ParseResourceType(item_type.value());
1120       if (type == nullptr) {
1121         diag_->Error(DiagMessage(element_source)
1122                      << "invalid resource type '" << item_type.value()
1123                      << "' in <item> within an <overlayable>");
1124         error = true;
1125         continue;
1126       }
1127 
1128       OverlayableItem overlayable_item(overlayable);
1129       overlayable_item.policies = current_policies;
1130       overlayable_item.comment = comment;
1131       overlayable_item.source = element_source;
1132 
1133       ParsedResource child_resource{};
1134       child_resource.name.type = *type;
1135       child_resource.name.entry = item_name.value().to_string();
1136       child_resource.overlayable_item = overlayable_item;
1137       out_resource->child_resources.push_back(std::move(child_resource));
1138 
1139     } else if (element_namespace.empty() && element_name == "policy") {
1140       if (current_policies != OverlayableItem::Policy::kNone) {
1141         // If the policy list is not empty, then we are currently inside a policy element
1142         diag_->Error(DiagMessage(element_source) << "<policy> blocks cannot be recursively nested");
1143         error = true;
1144         break;
1145       } else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
1146         // Parse the polices separated by vertical bar characters to allow for specifying multiple
1147         // policies. Items within the policy tag will have the specified policy.
1148         static const auto kPolicyMap =
1149             ImmutableMap<StringPiece, OverlayableItem::Policy>::CreatePreSorted({
1150                 {"odm", OverlayableItem::Policy::kOdm},
1151                 {"oem", OverlayableItem::Policy::kOem},
1152                 {"product", OverlayableItem::Policy::kProduct},
1153                 {"public", OverlayableItem::Policy::kPublic},
1154                 {"signature", OverlayableItem::Policy::kSignature},
1155                 {"system", OverlayableItem::Policy::kSystem},
1156                 {"vendor", OverlayableItem::Policy::kVendor},
1157             });
1158 
1159         for (const StringPiece& part : util::Tokenize(maybe_type.value(), '|')) {
1160           StringPiece trimmed_part = util::TrimWhitespace(part);
1161           const auto policy = kPolicyMap.find(trimmed_part);
1162           if (policy == kPolicyMap.end()) {
1163             diag_->Error(DiagMessage(element_source)
1164                          << "<policy> has unsupported type '" << trimmed_part << "'");
1165             error = true;
1166             continue;
1167           }
1168 
1169           current_policies |= policy->second;
1170         }
1171       } else {
1172         diag_->Error(DiagMessage(element_source)
1173                      << "<policy> must have a 'type' attribute");
1174         error = true;
1175         continue;
1176       }
1177     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1178       diag_->Error(DiagMessage(element_source) << "invalid element <" << element_name << "> "
1179                                                << " in <overlayable>");
1180       error = true;
1181       break;
1182     }
1183 
1184     comment.clear();
1185   }
1186 
1187   return !error;
1188 }
1189 
ParseAddResource(xml::XmlPullParser * parser,ParsedResource * out_resource)1190 bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1191   if (ParseSymbolImpl(parser, out_resource)) {
1192     out_resource->visibility_level = Visibility::Level::kUndefined;
1193     out_resource->allow_new = true;
1194     return true;
1195   }
1196   return false;
1197 }
1198 
ParseAttr(xml::XmlPullParser * parser,ParsedResource * out_resource)1199 bool ResourceParser::ParseAttr(xml::XmlPullParser* parser,
1200                                ParsedResource* out_resource) {
1201   return ParseAttrImpl(parser, out_resource, false);
1202 }
1203 
ParseAttrImpl(xml::XmlPullParser * parser,ParsedResource * out_resource,bool weak)1204 bool ResourceParser::ParseAttrImpl(xml::XmlPullParser* parser,
1205                                    ParsedResource* out_resource, bool weak) {
1206   out_resource->name.type = ResourceType::kAttr;
1207 
1208   // Attributes only end up in default configuration.
1209   if (out_resource->config != ConfigDescription::DefaultConfig()) {
1210     diag_->Warn(DiagMessage(out_resource->source)
1211                 << "ignoring configuration '" << out_resource->config
1212                 << "' for attribute " << out_resource->name);
1213     out_resource->config = ConfigDescription::DefaultConfig();
1214   }
1215 
1216   uint32_t type_mask = 0;
1217 
1218   Maybe<StringPiece> maybe_format = xml::FindAttribute(parser, "format");
1219   if (maybe_format) {
1220     type_mask = ParseFormatAttribute(maybe_format.value());
1221     if (type_mask == 0) {
1222       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1223                    << "invalid attribute format '" << maybe_format.value() << "'");
1224       return false;
1225     }
1226   }
1227 
1228   Maybe<int32_t> maybe_min, maybe_max;
1229 
1230   if (Maybe<StringPiece> maybe_min_str = xml::FindAttribute(parser, "min")) {
1231     StringPiece min_str = util::TrimWhitespace(maybe_min_str.value());
1232     if (!min_str.empty()) {
1233       std::u16string min_str16 = util::Utf8ToUtf16(min_str);
1234       android::Res_value value;
1235       if (android::ResTable::stringToInt(min_str16.data(), min_str16.size(), &value)) {
1236         maybe_min = static_cast<int32_t>(value.data);
1237       }
1238     }
1239 
1240     if (!maybe_min) {
1241       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1242                    << "invalid 'min' value '" << min_str << "'");
1243       return false;
1244     }
1245   }
1246 
1247   if (Maybe<StringPiece> maybe_max_str = xml::FindAttribute(parser, "max")) {
1248     StringPiece max_str = util::TrimWhitespace(maybe_max_str.value());
1249     if (!max_str.empty()) {
1250       std::u16string max_str16 = util::Utf8ToUtf16(max_str);
1251       android::Res_value value;
1252       if (android::ResTable::stringToInt(max_str16.data(), max_str16.size(), &value)) {
1253         maybe_max = static_cast<int32_t>(value.data);
1254       }
1255     }
1256 
1257     if (!maybe_max) {
1258       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1259                    << "invalid 'max' value '" << max_str << "'");
1260       return false;
1261     }
1262   }
1263 
1264   if ((maybe_min || maybe_max) &&
1265       (type_mask & android::ResTable_map::TYPE_INTEGER) == 0) {
1266     diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1267                  << "'min' and 'max' can only be used when format='integer'");
1268     return false;
1269   }
1270 
1271   struct SymbolComparator {
1272     bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) const {
1273       return a.symbol.name.value() < b.symbol.name.value();
1274     }
1275   };
1276 
1277   std::set<Attribute::Symbol, SymbolComparator> items;
1278 
1279   std::string comment;
1280   bool error = false;
1281   const size_t depth = parser->depth();
1282   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1283     if (parser->event() == xml::XmlPullParser::Event::kComment) {
1284       comment = util::TrimWhitespace(parser->comment()).to_string();
1285       continue;
1286     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1287       // Skip text.
1288       continue;
1289     }
1290 
1291     const Source item_source = source_.WithLine(parser->line_number());
1292     const std::string& element_namespace = parser->element_namespace();
1293     const std::string& element_name = parser->element_name();
1294     if (element_namespace.empty() && (element_name == "flag" || element_name == "enum")) {
1295       if (element_name == "enum") {
1296         if (type_mask & android::ResTable_map::TYPE_FLAGS) {
1297           diag_->Error(DiagMessage(item_source)
1298                        << "can not define an <enum>; already defined a <flag>");
1299           error = true;
1300           continue;
1301         }
1302         type_mask |= android::ResTable_map::TYPE_ENUM;
1303 
1304       } else if (element_name == "flag") {
1305         if (type_mask & android::ResTable_map::TYPE_ENUM) {
1306           diag_->Error(DiagMessage(item_source)
1307                        << "can not define a <flag>; already defined an <enum>");
1308           error = true;
1309           continue;
1310         }
1311         type_mask |= android::ResTable_map::TYPE_FLAGS;
1312       }
1313 
1314       if (Maybe<Attribute::Symbol> s =
1315               ParseEnumOrFlagItem(parser, element_name)) {
1316         Attribute::Symbol& symbol = s.value();
1317         ParsedResource child_resource;
1318         child_resource.name = symbol.symbol.name.value();
1319         child_resource.source = item_source;
1320         child_resource.value = util::make_unique<Id>();
1321         if (options_.visibility) {
1322           child_resource.visibility_level = options_.visibility.value();
1323         }
1324         out_resource->child_resources.push_back(std::move(child_resource));
1325 
1326         symbol.symbol.SetComment(std::move(comment));
1327         symbol.symbol.SetSource(item_source);
1328 
1329         auto insert_result = items.insert(std::move(symbol));
1330         if (!insert_result.second) {
1331           const Attribute::Symbol& existing_symbol = *insert_result.first;
1332           diag_->Error(DiagMessage(item_source)
1333                        << "duplicate symbol '"
1334                        << existing_symbol.symbol.name.value().entry << "'");
1335 
1336           diag_->Note(DiagMessage(existing_symbol.symbol.GetSource())
1337                       << "first defined here");
1338           error = true;
1339         }
1340       } else {
1341         error = true;
1342       }
1343     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1344       diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
1345       error = true;
1346     }
1347 
1348     comment = {};
1349   }
1350 
1351   if (error) {
1352     return false;
1353   }
1354 
1355   std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(
1356       type_mask ? type_mask : uint32_t{android::ResTable_map::TYPE_ANY});
1357   attr->SetWeak(weak);
1358   attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
1359   attr->min_int = maybe_min.value_or_default(std::numeric_limits<int32_t>::min());
1360   attr->max_int = maybe_max.value_or_default(std::numeric_limits<int32_t>::max());
1361   out_resource->value = std::move(attr);
1362   return true;
1363 }
1364 
ParseEnumOrFlagItem(xml::XmlPullParser * parser,const StringPiece & tag)1365 Maybe<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem(
1366     xml::XmlPullParser* parser, const StringPiece& tag) {
1367   const Source source = source_.WithLine(parser->line_number());
1368 
1369   Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
1370   if (!maybe_name) {
1371     diag_->Error(DiagMessage(source) << "no attribute 'name' found for tag <"
1372                                      << tag << ">");
1373     return {};
1374   }
1375 
1376   Maybe<StringPiece> maybe_value = xml::FindNonEmptyAttribute(parser, "value");
1377   if (!maybe_value) {
1378     diag_->Error(DiagMessage(source) << "no attribute 'value' found for tag <"
1379                                      << tag << ">");
1380     return {};
1381   }
1382 
1383   std::u16string value16 = util::Utf8ToUtf16(maybe_value.value());
1384   android::Res_value val;
1385   if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) {
1386     diag_->Error(DiagMessage(source) << "invalid value '" << maybe_value.value()
1387                                      << "' for <" << tag
1388                                      << ">; must be an integer");
1389     return {};
1390   }
1391 
1392   return Attribute::Symbol{
1393       Reference(ResourceNameRef({}, ResourceType::kId, maybe_name.value())),
1394       val.data};
1395 }
1396 
ParseStyleItem(xml::XmlPullParser * parser,Style * style)1397 bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) {
1398   const Source source = source_.WithLine(parser->line_number());
1399 
1400   Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
1401   if (!maybe_name) {
1402     diag_->Error(DiagMessage(source) << "<item> must have a 'name' attribute");
1403     return false;
1404   }
1405 
1406   Maybe<Reference> maybe_key = ResourceUtils::ParseXmlAttributeName(maybe_name.value());
1407   if (!maybe_key) {
1408     diag_->Error(DiagMessage(source) << "invalid attribute name '" << maybe_name.value() << "'");
1409     return false;
1410   }
1411 
1412   ResolvePackage(parser, &maybe_key.value());
1413   maybe_key.value().SetSource(source);
1414 
1415   std::unique_ptr<Item> value = ParseXml(parser, 0, kAllowRawString);
1416   if (!value) {
1417     diag_->Error(DiagMessage(source) << "could not parse style item");
1418     return false;
1419   }
1420 
1421   style->entries.push_back(Style::Entry{std::move(maybe_key.value()), std::move(value)});
1422   return true;
1423 }
1424 
ParseStyle(const ResourceType type,xml::XmlPullParser * parser,ParsedResource * out_resource)1425 bool ResourceParser::ParseStyle(const ResourceType type, xml::XmlPullParser* parser,
1426                                 ParsedResource* out_resource) {
1427   out_resource->name.type = type;
1428 
1429   std::unique_ptr<Style> style = util::make_unique<Style>();
1430 
1431   Maybe<StringPiece> maybe_parent = xml::FindAttribute(parser, "parent");
1432   if (maybe_parent) {
1433     // If the parent is empty, we don't have a parent, but we also don't infer either.
1434     if (!maybe_parent.value().empty()) {
1435       std::string err_str;
1436       style->parent = ResourceUtils::ParseStyleParentReference(maybe_parent.value(), &err_str);
1437       if (!style->parent) {
1438         diag_->Error(DiagMessage(out_resource->source) << err_str);
1439         return false;
1440       }
1441 
1442       // Transform the namespace prefix to the actual package name, and mark the reference as
1443       // private if appropriate.
1444       ResolvePackage(parser, &style->parent.value());
1445     }
1446 
1447   } else {
1448     // No parent was specified, so try inferring it from the style name.
1449     std::string style_name = out_resource->name.entry;
1450     size_t pos = style_name.find_last_of(u'.');
1451     if (pos != std::string::npos) {
1452       style->parent_inferred = true;
1453       style->parent = Reference(ResourceName({}, ResourceType::kStyle, style_name.substr(0, pos)));
1454     }
1455   }
1456 
1457   bool error = false;
1458   const size_t depth = parser->depth();
1459   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1460     if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1461       // Skip text and comments.
1462       continue;
1463     }
1464 
1465     const std::string& element_namespace = parser->element_namespace();
1466     const std::string& element_name = parser->element_name();
1467     if (element_namespace == "" && element_name == "item") {
1468       error |= !ParseStyleItem(parser, style.get());
1469 
1470     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1471       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1472                    << ":" << element_name << ">");
1473       error = true;
1474     }
1475   }
1476 
1477   if (error) {
1478     return false;
1479   }
1480 
1481   out_resource->value = std::move(style);
1482   return true;
1483 }
1484 
ParseArray(xml::XmlPullParser * parser,ParsedResource * out_resource)1485 bool ResourceParser::ParseArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1486   uint32_t resource_format = android::ResTable_map::TYPE_ANY;
1487   if (Maybe<StringPiece> format_attr = xml::FindNonEmptyAttribute(parser, "format")) {
1488     resource_format = ParseFormatTypeNoEnumsOrFlags(format_attr.value());
1489     if (resource_format == 0u) {
1490       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1491                    << "'" << format_attr.value() << "' is an invalid format");
1492       return false;
1493     }
1494   }
1495   return ParseArrayImpl(parser, out_resource, resource_format);
1496 }
1497 
ParseIntegerArray(xml::XmlPullParser * parser,ParsedResource * out_resource)1498 bool ResourceParser::ParseIntegerArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1499   return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_INTEGER);
1500 }
1501 
ParseStringArray(xml::XmlPullParser * parser,ParsedResource * out_resource)1502 bool ResourceParser::ParseStringArray(xml::XmlPullParser* parser, ParsedResource* out_resource) {
1503   return ParseArrayImpl(parser, out_resource, android::ResTable_map::TYPE_STRING);
1504 }
1505 
ParseArrayImpl(xml::XmlPullParser * parser,ParsedResource * out_resource,const uint32_t typeMask)1506 bool ResourceParser::ParseArrayImpl(xml::XmlPullParser* parser,
1507                                     ParsedResource* out_resource,
1508                                     const uint32_t typeMask) {
1509   out_resource->name.type = ResourceType::kArray;
1510 
1511   std::unique_ptr<Array> array = util::make_unique<Array>();
1512 
1513   bool translatable = options_.translatable;
1514   if (Maybe<StringPiece> translatable_attr = xml::FindAttribute(parser, "translatable")) {
1515     Maybe<bool> maybe_translatable = ResourceUtils::ParseBool(translatable_attr.value());
1516     if (!maybe_translatable) {
1517       diag_->Error(DiagMessage(out_resource->source)
1518                    << "invalid value for 'translatable'. Must be a boolean");
1519       return false;
1520     }
1521     translatable = maybe_translatable.value();
1522   }
1523   array->SetTranslatable(translatable);
1524 
1525   bool error = false;
1526   const size_t depth = parser->depth();
1527   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1528     if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1529       // Skip text and comments.
1530       continue;
1531     }
1532 
1533     const Source item_source = source_.WithLine(parser->line_number());
1534     const std::string& element_namespace = parser->element_namespace();
1535     const std::string& element_name = parser->element_name();
1536     if (element_namespace.empty() && element_name == "item") {
1537       std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString);
1538       if (!item) {
1539         diag_->Error(DiagMessage(item_source) << "could not parse array item");
1540         error = true;
1541         continue;
1542       }
1543       item->SetSource(item_source);
1544       array->elements.emplace_back(std::move(item));
1545 
1546     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1547       diag_->Error(DiagMessage(source_.WithLine(parser->line_number()))
1548                    << "unknown tag <" << element_namespace << ":"
1549                    << element_name << ">");
1550       error = true;
1551     }
1552   }
1553 
1554   if (error) {
1555     return false;
1556   }
1557 
1558   out_resource->value = std::move(array);
1559   return true;
1560 }
1561 
ParsePlural(xml::XmlPullParser * parser,ParsedResource * out_resource)1562 bool ResourceParser::ParsePlural(xml::XmlPullParser* parser,
1563                                  ParsedResource* out_resource) {
1564   out_resource->name.type = ResourceType::kPlurals;
1565 
1566   std::unique_ptr<Plural> plural = util::make_unique<Plural>();
1567 
1568   bool error = false;
1569   const size_t depth = parser->depth();
1570   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1571     if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1572       // Skip text and comments.
1573       continue;
1574     }
1575 
1576     const Source item_source = source_.WithLine(parser->line_number());
1577     const std::string& element_namespace = parser->element_namespace();
1578     const std::string& element_name = parser->element_name();
1579     if (element_namespace.empty() && element_name == "item") {
1580       Maybe<StringPiece> maybe_quantity =
1581           xml::FindNonEmptyAttribute(parser, "quantity");
1582       if (!maybe_quantity) {
1583         diag_->Error(DiagMessage(item_source)
1584                      << "<item> in <plurals> requires attribute "
1585                      << "'quantity'");
1586         error = true;
1587         continue;
1588       }
1589 
1590       StringPiece trimmed_quantity =
1591           util::TrimWhitespace(maybe_quantity.value());
1592       size_t index = 0;
1593       if (trimmed_quantity == "zero") {
1594         index = Plural::Zero;
1595       } else if (trimmed_quantity == "one") {
1596         index = Plural::One;
1597       } else if (trimmed_quantity == "two") {
1598         index = Plural::Two;
1599       } else if (trimmed_quantity == "few") {
1600         index = Plural::Few;
1601       } else if (trimmed_quantity == "many") {
1602         index = Plural::Many;
1603       } else if (trimmed_quantity == "other") {
1604         index = Plural::Other;
1605       } else {
1606         diag_->Error(DiagMessage(item_source)
1607                      << "<item> in <plural> has invalid value '"
1608                      << trimmed_quantity << "' for attribute 'quantity'");
1609         error = true;
1610         continue;
1611       }
1612 
1613       if (plural->values[index]) {
1614         diag_->Error(DiagMessage(item_source) << "duplicate quantity '"
1615                                               << trimmed_quantity << "'");
1616         error = true;
1617         continue;
1618       }
1619 
1620       if (!(plural->values[index] = ParseXml(
1621                 parser, android::ResTable_map::TYPE_STRING, kNoRawString))) {
1622         error = true;
1623         continue;
1624       }
1625 
1626       plural->values[index]->SetSource(item_source);
1627 
1628     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1629       diag_->Error(DiagMessage(item_source) << "unknown tag <"
1630                                             << element_namespace << ":"
1631                                             << element_name << ">");
1632       error = true;
1633     }
1634   }
1635 
1636   if (error) {
1637     return false;
1638   }
1639 
1640   out_resource->value = std::move(plural);
1641   return true;
1642 }
1643 
ParseDeclareStyleable(xml::XmlPullParser * parser,ParsedResource * out_resource)1644 bool ResourceParser::ParseDeclareStyleable(xml::XmlPullParser* parser,
1645                                            ParsedResource* out_resource) {
1646   out_resource->name.type = ResourceType::kStyleable;
1647 
1648   if (!options_.preserve_visibility_of_styleables) {
1649     // This was added in change Idd21b5de4d20be06c6f8c8eb5a22ccd68afc4927 to mimic aapt1, but no one
1650     // knows exactly what for.
1651     //
1652     // FWIW, styleables only appear in generated R classes.  For custom views these should always be
1653     // package-private (to be used only by the view class); themes are a different story.
1654     out_resource->visibility_level = Visibility::Level::kPublic;
1655   }
1656 
1657   // Declare-styleable only ends up in default config;
1658   if (out_resource->config != ConfigDescription::DefaultConfig()) {
1659     diag_->Warn(DiagMessage(out_resource->source)
1660                 << "ignoring configuration '" << out_resource->config
1661                 << "' for styleable " << out_resource->name.entry);
1662     out_resource->config = ConfigDescription::DefaultConfig();
1663   }
1664 
1665   std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
1666 
1667   std::string comment;
1668   bool error = false;
1669   const size_t depth = parser->depth();
1670   while (xml::XmlPullParser::NextChildNode(parser, depth)) {
1671     if (parser->event() == xml::XmlPullParser::Event::kComment) {
1672       comment = util::TrimWhitespace(parser->comment()).to_string();
1673       continue;
1674     } else if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
1675       // Ignore text.
1676       continue;
1677     }
1678 
1679     const Source item_source = source_.WithLine(parser->line_number());
1680     const std::string& element_namespace = parser->element_namespace();
1681     const std::string& element_name = parser->element_name();
1682     if (element_namespace.empty() && element_name == "attr") {
1683       Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
1684       if (!maybe_name) {
1685         diag_->Error(DiagMessage(item_source) << "<attr> tag must have a 'name' attribute");
1686         error = true;
1687         continue;
1688       }
1689 
1690       // If this is a declaration, the package name may be in the name. Separate
1691       // these out.
1692       // Eg. <attr name="android:text" />
1693       Maybe<Reference> maybe_ref = ResourceUtils::ParseXmlAttributeName(maybe_name.value());
1694       if (!maybe_ref) {
1695         diag_->Error(DiagMessage(item_source) << "<attr> tag has invalid name '"
1696                                               << maybe_name.value() << "'");
1697         error = true;
1698         continue;
1699       }
1700 
1701       Reference& child_ref = maybe_ref.value();
1702       xml::ResolvePackage(parser, &child_ref);
1703 
1704       // Create the ParsedResource that will add the attribute to the table.
1705       ParsedResource child_resource;
1706       child_resource.name = child_ref.name.value();
1707       child_resource.source = item_source;
1708       // NOLINTNEXTLINE(bugprone-use-after-move) move+reset comment
1709       child_resource.comment = std::move(comment);
1710       if (options_.visibility) {
1711         child_resource.visibility_level = options_.visibility.value();
1712       }
1713 
1714       if (!ParseAttrImpl(parser, &child_resource, true)) {
1715         error = true;
1716         continue;
1717       }
1718 
1719       // Create the reference to this attribute.
1720       child_ref.SetComment(child_resource.comment);
1721       child_ref.SetSource(item_source);
1722       styleable->entries.push_back(std::move(child_ref));
1723 
1724       // Do not add referenced attributes that do not define a format to the table.
1725       CHECK(child_resource.value != nullptr);
1726       Attribute* attr = ValueCast<Attribute>(child_resource.value.get());
1727 
1728       CHECK(attr != nullptr);
1729       if (attr->type_mask != android::ResTable_map::TYPE_ANY) {
1730         out_resource->child_resources.push_back(std::move(child_resource));
1731       }
1732 
1733     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
1734       diag_->Error(DiagMessage(item_source) << "unknown tag <"
1735                                             << element_namespace << ":"
1736                                             << element_name << ">");
1737       error = true;
1738     }
1739 
1740     comment = {};
1741   }
1742 
1743   if (error) {
1744     return false;
1745   }
1746 
1747   out_resource->value = std::move(styleable);
1748   return true;
1749 }
1750 
1751 }  // namespace aapt
1752