You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
272 lines
7.3 KiB
272 lines
7.3 KiB
/*
|
|
* 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;
|
|
}
|
|
|