|  | /* | 
|  | * 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; | 
|  | } | 
|  |  |