| /* | 
 |  * Copyright (C) 2016 The Android Open Source Project | 
 |  * | 
 |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
 |  * you may not use this file except in compliance with the License. | 
 |  * You may obtain a copy of the License at | 
 |  * | 
 |  *      http://www.apache.org/licenses/LICENSE-2.0 | 
 |  * | 
 |  * Unless required by applicable law or agreed to in writing, software | 
 |  * distributed under the License is distributed on an "AS IS" BASIS, | 
 |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 |  * See the License for the specific language governing permissions and | 
 |  * limitations under the License. | 
 |  */ | 
 |  | 
 | #include "aapt.h" | 
 |  | 
 | #include "command.h" | 
 | #include "print.h" | 
 | #include "util.h" | 
 |  | 
 | #include <regex> | 
 |  | 
 | const regex NS_REGEX("( *)N: ([^=]+)=(.*)"); | 
 | const regex ELEMENT_REGEX("( *)E: ([^ ]+) \\(line=(\\d+)\\)"); | 
 | const regex ATTR_REGEX("( *)A: ([^\\(=]+)[^=]*=\"([^\"]+)\".*"); | 
 |  | 
 | const string ANDROID_NS("http://schemas.android.com/apk/res/android"); | 
 |  | 
 | bool | 
 | Apk::HasActivity(const string& className) | 
 | { | 
 |     string fullClassName = full_class_name(package, className); | 
 |     const size_t N = activities.size(); | 
 |     for (size_t i=0; i<N; i++) { | 
 |         if (activities[i] == fullClassName) { | 
 |             return true; | 
 |         } | 
 |     } | 
 |     return false; | 
 | } | 
 |  | 
 | struct Attribute { | 
 |     string ns; | 
 |     string name; | 
 |     string value; | 
 | }; | 
 |  | 
 | struct Element { | 
 |     Element* parent; | 
 |     string ns; | 
 |     string name; | 
 |     int lineno; | 
 |     vector<Attribute> attributes; | 
 |     vector<Element*> children; | 
 |  | 
 |     /** | 
 |      * Indentation in the xmltree dump. Might not be equal to the distance | 
 |      * from the root because namespace rows (scopes) have their own indentation. | 
 |      */ | 
 |     int depth; | 
 |  | 
 |     Element(); | 
 |     ~Element(); | 
 |  | 
 |     string GetAttr(const string& ns, const string& name) const; | 
 |     void FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse); | 
 |      | 
 | }; | 
 |  | 
 | Element::Element() | 
 | { | 
 | } | 
 |  | 
 | Element::~Element() | 
 | { | 
 |     const size_t N = children.size(); | 
 |     for (size_t i=0; i<N; i++) { | 
 |         delete children[i]; | 
 |     } | 
 | } | 
 |  | 
 | string | 
 | Element::GetAttr(const string& ns, const string& name) const | 
 | { | 
 |     const size_t N = attributes.size(); | 
 |     for (size_t i=0; i<N; i++) { | 
 |         const Attribute& attr = attributes[i]; | 
 |         if (attr.ns == ns && attr.name == name) { | 
 |             return attr.value; | 
 |         } | 
 |     } | 
 |     return string(); | 
 | } | 
 |  | 
 | void | 
 | Element::FindElements(const string& ns, const string& name, vector<Element*>* result, bool recurse) | 
 | { | 
 |     const size_t N = children.size(); | 
 |     for (size_t i=0; i<N; i++) { | 
 |         Element* child = children[i]; | 
 |         if (child->ns == ns && child->name == name) { | 
 |             result->push_back(child); | 
 |         } | 
 |         if (recurse) { | 
 |             child->FindElements(ns, name, result, recurse); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | struct Scope { | 
 |     Scope* parent; | 
 |     int depth; | 
 |     map<string,string> namespaces; | 
 |  | 
 |     Scope(Scope* parent, int depth); | 
 | }; | 
 |  | 
 | Scope::Scope(Scope* p, int d) | 
 |     :parent(p), | 
 |      depth(d) | 
 | { | 
 |      if (p != NULL) { | 
 |          namespaces = p->namespaces; | 
 |      } | 
 | } | 
 |  | 
 |  | 
 | string | 
 | full_class_name(const string& packageName, const string& className) | 
 | { | 
 |     if (className.length() == 0) { | 
 |         return ""; | 
 |     } | 
 |     if (className[0] == '.') { | 
 |         return packageName + className; | 
 |     } | 
 |     if (className.find('.') == string::npos) { | 
 |         return packageName + "." + className; | 
 |     } | 
 |     return className; | 
 | } | 
 |  | 
 | string | 
 | pretty_component_name(const string& packageName, const string& className) | 
 | { | 
 |     if (starts_with(packageName, className)) { | 
 |         size_t pn = packageName.length(); | 
 |         size_t cn = className.length(); | 
 |         if (cn > pn && className[pn] == '.') { | 
 |             return packageName + "/" + string(className, pn, string::npos); | 
 |         } | 
 |     } | 
 |     return packageName + "/" + className; | 
 | } | 
 |  | 
 | int | 
 | inspect_apk(Apk* apk, const string& filename) | 
 | { | 
 |     // Load the manifest xml | 
 |     Command cmd("aapt2"); | 
 |     cmd.AddArg("dump"); | 
 |     cmd.AddArg("xmltree"); | 
 |     cmd.AddArg(filename); | 
 |     cmd.AddArg("--file"); | 
 |     cmd.AddArg("AndroidManifest.xml"); | 
 |  | 
 |     int err; | 
 |  | 
 |     string output = get_command_output(cmd, &err, false); | 
 |     check_error(err); | 
 |  | 
 |     // Parse the manifest xml | 
 |     Scope* scope = new Scope(NULL, -1); | 
 |     Element* root = NULL; | 
 |     Element* current = NULL; | 
 |     vector<string> lines; | 
 |     split_lines(&lines, output); | 
 |     for (size_t i=0; i<lines.size(); i++) { | 
 |         const string& line = lines[i]; | 
 |         smatch match; | 
 |         if (regex_match(line, match, NS_REGEX)) { | 
 |             int depth = match[1].length() / 2; | 
 |             while (depth < scope->depth) { | 
 |                 Scope* tmp = scope; | 
 |                 scope = scope->parent; | 
 |                 delete tmp; | 
 |             } | 
 |             scope = new Scope(scope, depth); | 
 |             scope->namespaces[match[2]] = match[3]; | 
 |         } else if (regex_match(line, match, ELEMENT_REGEX)) { | 
 |             Element* element = new Element(); | 
 |  | 
 |             string str = match[2]; | 
 |             size_t colon = str.find(':'); | 
 |             if (colon == string::npos) { | 
 |                 element->name = str; | 
 |             } else { | 
 |                 element->ns = scope->namespaces[string(str, 0, colon)]; | 
 |                 element->name.assign(str, colon+1, string::npos); | 
 |             } | 
 |             element->lineno = atoi(match[3].str().c_str()); | 
 |             element->depth = match[1].length() / 2; | 
 |  | 
 |             if (root == NULL) { | 
 |                 current = element; | 
 |                 root = element; | 
 |             } else { | 
 |                 while (element->depth <= current->depth && current->parent != NULL) { | 
 |                     current = current->parent; | 
 |                 } | 
 |                 element->parent = current; | 
 |                 current->children.push_back(element); | 
 |                 current = element; | 
 |             } | 
 |         } else if (regex_match(line, match, ATTR_REGEX)) { | 
 |             if (current != NULL) { | 
 |                 Attribute attr; | 
 |                 string str = match[2]; | 
 |                 size_t colon = str.rfind(':'); | 
 |                 if (colon == string::npos) { | 
 |                     attr.name = str; | 
 |                 } else { | 
 |                     attr.ns.assign(str, 0, colon); | 
 |                     attr.name.assign(str, colon+1, string::npos); | 
 |                 } | 
 |                 attr.value = match[3]; | 
 |                 current->attributes.push_back(attr); | 
 |             } | 
 |         } | 
 |     } | 
 |     while (scope != NULL) { | 
 |         Scope* tmp = scope; | 
 |         scope = scope->parent; | 
 |         delete tmp; | 
 |     } | 
 |  | 
 |     // Package name | 
 |     apk->package = root->GetAttr("", "package"); | 
 |     if (apk->package.size() == 0) { | 
 |         print_error("%s:%d: Manifest root element doesn't contain a package attribute", | 
 |                 filename.c_str(), root->lineno); | 
 |         delete root; | 
 |         return 1; | 
 |     } | 
 |  | 
 |     // Instrumentation runner | 
 |     vector<Element*> instrumentation; | 
 |     root->FindElements("", "instrumentation", &instrumentation, true); | 
 |     if (instrumentation.size() > 0) { | 
 |         // TODO: How could we deal with multiple instrumentation tags? | 
 |         // We'll just pick the first one. | 
 |         apk->runner = instrumentation[0]->GetAttr(ANDROID_NS, "name"); | 
 |     } | 
 |  | 
 |     // Activities | 
 |     vector<Element*> activities; | 
 |     root->FindElements("", "activity", &activities, true); | 
 |     for (size_t i=0; i<activities.size(); i++) { | 
 |         string name = activities[i]->GetAttr(ANDROID_NS, "name"); | 
 |         if (name.size() == 0) { | 
 |             continue; | 
 |         } | 
 |         apk->activities.push_back(full_class_name(apk->package, name)); | 
 |     } | 
 |  | 
 |     delete root; | 
 |     return 0; | 
 | } | 
 |  |