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