/*
* Copyright (C) 2015 The Pennsylvania State University and the University of Wisconsin
* Systems and Internet Infrastructure Security Laboratory
*
* Author: Damien Octeau
*
* 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.
*/
package edu.psu.cse.siis.ic3.manifest;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
import edu.psu.cse.siis.ic3.Ic3Data;
import edu.psu.cse.siis.ic3.Ic3Data.Application.Builder;
import edu.psu.cse.siis.ic3.Ic3Data.Application.Component;
import edu.psu.cse.siis.ic3.Ic3Data.Application.Component.ComponentKind;
import edu.psu.cse.siis.ic3.Ic3Data.Application.Component.IntentFilter;
import edu.psu.cse.siis.ic3.Ic3Data.Attribute;
import edu.psu.cse.siis.ic3.Ic3Data.AttributeKind;
import edu.psu.cse.siis.ic3.db.Constants;
import edu.psu.cse.siis.ic3.db.SQLConnection;
import edu.psu.cse.siis.ic3.manifest.binary.AXmlResourceParser;
public class ManifestPullParser {
private static final String MANIFEST = "manifest";
private static final String MANIFEST_FILE_NAME = "AndroidManifest.xml";
private static final String ACTIVITY = "activity";
private static final String ACTIVITY_ALIAS = "activity-alias";
private static final String SERVICE = "service";
private static final String PROVIDER = "provider";
private static final String AUTHORITIES = "authorities";
private static final String READ_PERMISSION = "readPermissions";
private static final String WRITE_PERMISSION = "writePermission";
private static final String RECEIVER = "receiver";
private static final String APPLICATION = "application";
private static final String NAMESPACE = "http://schemas.android.com/apk/res/android";
// private static final String ENABLED = "android:enabled";
private static final String NAME = "name";
private static final String INTENT_FILTER = "intent-filter";
private static final String ACTION = "action";
private static final String CATEGORY = "category";
private static final String MIME_TYPE = "mimeType";
private static final String PACKAGE = "package";
private static final String DATA = "data";
private static final String FALSE = "false";
private static final String VERSION = "versionCode";
private static final String PERMISSION = "permission";
private static final String EXPORTED = "exported";
private static final String TRUE = "true";
private static final String USES_PERMISSION = "uses-permission";
private static final String PROTECTION_LEVEL = "protectionLevel";
private static final String TARGET_ACTIVITY = "targetActivity";
private static final String SCHEME = "scheme";
private static final String HOST = "host";
private static final String PORT = "port";
private static final String PATH = "path";
private static final String PATH_PATTERN = "pathPattern";
private static final String PATH_PREFIX = "pathPrefix";
private static final String GRANT_URI_PERMISSIONS = "grantUriPermissions";
private static final String PRIORITY = "priority";
private static final String NORMAL = "normal";
private static final String DANGEROUS = "dangerous";
private static final String SIGNATURE = "signature";
private static final String SIGNATURE_OR_SYSTEM = "signatureOrSytem";
private String applicationName;
private String packageName;
private static final String[] levelValueToShortString = { Constants.PermissionLevel.NORMAL_SHORT,
Constants.PermissionLevel.DANGEROUS_SHORT, Constants.PermissionLevel.SIGNATURE_SHORT,
Constants.PermissionLevel.SIGNATURE_OR_SYSTEM_SHORT };
private static Map<String, Integer> tagDepthMap = null;
// map a content provider to one or more authorities
// private final Map<String, Set<String>> providersAuthorities = new HashMap<String,
// Set<String>>();
// private final Map<String, Set<String>> providersRPermission = new HashMap<String,
// Set<String>>();
// private final Map<String, Set<String>> providersWPermission = new HashMap<String,
// Set<String>>();
private final List<ManifestComponent> activities = new ArrayList<ManifestComponent>();
private final List<ManifestComponent> activityAliases = new ArrayList<ManifestComponent>();
private final List<ManifestComponent> services = new ArrayList<ManifestComponent>();
private final List<ManifestComponent> receivers = new ArrayList<ManifestComponent>();
private final List<ManifestComponent> providers = new ArrayList<ManifestComponent>();
private int version = -1;
private ManifestComponent currentComponent = null;
private Set<ManifestIntentFilter> currentIntentFilters = null;
private ManifestIntentFilter currentIntentFilter = null;
private String skipToEndTag = null;
private String applicationPermission = null;
private final Set<String> usesPermissions = new HashSet<>();
private Map<String, String> permissions = new HashMap<String, String>();
private final Set<String> entryPointClasses = new HashSet<>();
public List<ManifestComponent> getActivities() {
return activities;
}
public List<ManifestComponent> getActivityAliases() {
return activityAliases;
}
public List<ManifestComponent> getServices() {
return services;
}
public List<ManifestComponent> getReceivers() {
return receivers;
}
public List<ManifestComponent> getProviders() {
return providers;
}
public List<String> getComponents() {
List<String> componentNames = new ArrayList<>();
addComponentNamesToList(activities, componentNames);
addComponentNamesToList(services, componentNames);
addComponentNamesToList(receivers, componentNames);
addComponentNamesToList(providers, componentNames);
return componentNames;
}
private void setApplicationName(String applicationName) {
this.applicationName = applicationName;
}
public String getApplicationName() {
return applicationName;
}
private void setPackageName(String packageName) {
this.packageName = packageName;
}
public String getPackageName() {
return packageName;
}
public Set<String> getEntryPointClasses() {
return entryPointClasses;
}
private void addComponentNamesToList(List<ManifestComponent> components, List<String> output) {
for (ManifestComponent manifestComponent : components) {
output.add(manifestComponent.getName());
}
}
public void loadManifestFile(String manifest) {
try {
if (manifest.endsWith(".xml")) {
loadClassesFromTextManifest(new FileInputStream(manifest));
} else {
handleBinaryManifestFile(manifest);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
private void handleBinaryManifestFile(String apk) {
try {
ZipFile archive = new ZipFile(apk);
ZipEntry manifestEntry = archive.getEntry(MANIFEST_FILE_NAME);
if (manifestEntry == null) {
archive.close();
throw new RuntimeException("No manifest file found in apk");
}
loadClassesFromBinaryManifest(archive.getInputStream(manifestEntry));
archive.close();
} catch (IOException e) {
throw new RuntimeException("Error while processing apk " + apk + ": " + e);
}
}
protected void loadClassesFromBinaryManifest(InputStream manifestIS) {
AXmlResourceParser aXmlResourceParser = new AXmlResourceParser();
aXmlResourceParser.open(manifestIS);
try {
parse(aXmlResourceParser);
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
throw new RuntimeException("Could not parse manifest file.");
}
}
protected void loadClassesFromTextManifest(InputStream manifestIS) {
try {
XmlPullParserFactory xmlPullParserFactory = XmlPullParserFactory.newInstance();
xmlPullParserFactory.setNamespaceAware(true);
XmlPullParser parser = xmlPullParserFactory.newPullParser();
parser.setInput(manifestIS, null);
parse(parser);
} catch (XmlPullParserException | IOException e) {
e.printStackTrace();
throw new RuntimeException("Could not parse manifest file.");
}
}
/**
* Parse the manifest file.
*
* @param reader Input if text XML.
* @param is Input if binary XML.
* @throws IOException
* @throws XmlPullParserException
*/
public void parse(XmlPullParser parser) throws XmlPullParserException, IOException {
initializeTagDepthMap();
int depth = 0;
int eventType = 0;
eventType = parser.next();
boolean okayToContinue = true;
while (okayToContinue && eventType != XmlPullParser.END_DOCUMENT) {
switch (eventType) {
case XmlPullParser.START_TAG:
okayToContinue = handleStartTag(parser, depth);
++depth;
break;
case XmlPullParser.END_TAG:
okayToContinue = handleEndTag(parser);
--depth;
break;
}
eventType = parser.next();
}
}
public Map<String, Integer> writeToDb(boolean skipEntryPoints) {
Map<String, Integer> componentIds = new HashMap<String, Integer>();
componentIds.putAll(SQLConnection.insert(getPackageName(), version, activities,
usesPermissions, permissions, skipEntryPoints));
componentIds.putAll(SQLConnection.insert(getPackageName(), version, activityAliases, null,
null, skipEntryPoints));
componentIds.putAll(SQLConnection.insert(getPackageName(), version, services, null, null,
skipEntryPoints));
componentIds.putAll(SQLConnection.insert(getPackageName(), version, receivers, null, null,
skipEntryPoints));
componentIds.putAll(SQLConnection.insert(getPackageName(), version, providers, null, null,
skipEntryPoints));
return componentIds;
}
public boolean isComponent(String name) {
return entryPointClasses.contains(name);
}
public Map<String, Ic3Data.Application.Component.Builder> populateProtobuf(Builder ic3Builder) {
ic3Builder.setName(getPackageName());
ic3Builder.setVersion(version);
for (Map.Entry<String, String> permission : permissions.entrySet()) {
Ic3Data.Application.Permission protobufPermission =
Ic3Data.Application.Permission.newBuilder().setName(permission.getKey())
.setLevel(stringToLevel(permission.getValue())).build();
ic3Builder.addPermissions(protobufPermission);
}
ic3Builder.addAllUsedPermissions(usesPermissions);
Map<String, Ic3Data.Application.Component.Builder> componentNameToBuilderMap = new HashMap<>();
componentNameToBuilderMap.putAll(populateProtobufComponentBuilders(activities,
ComponentKind.ACTIVITY));
componentNameToBuilderMap.putAll(populateProtobufComponentBuilders(services,
ComponentKind.SERVICE));
componentNameToBuilderMap.putAll(populateProtobufComponentBuilders(receivers,
ComponentKind.RECEIVER));
componentNameToBuilderMap.putAll(populateProtobufComponentBuilders(providers,
ComponentKind.PROVIDER));
return componentNameToBuilderMap;
}
private Map<String, Component.Builder> populateProtobufComponentBuilders(
List<ManifestComponent> components, ComponentKind componentKind) {
Map<String, Component.Builder> componentNameToBuilderMap = new HashMap<>();
for (ManifestComponent manifestComponent : components) {
componentNameToBuilderMap.put(manifestComponent.getName(),
makeProtobufComponentBuilder(manifestComponent, componentKind));
}
return componentNameToBuilderMap;
}
public static Component.Builder makeProtobufComponentBuilder(ManifestComponent manifestComponent,
ComponentKind componentKind) {
Component.Builder componentBuilder = Component.newBuilder();
componentBuilder.setName(manifestComponent.getName());
componentBuilder.setKind(componentKind);
componentBuilder.setExported(manifestComponent.isExported());
if (manifestComponent.getPermission() != null) {
componentBuilder.setPermission(manifestComponent.getPermission());
}
if (manifestComponent.missingIntentFilters() != null) {
componentBuilder.setMissing(manifestComponent.missingIntentFilters());
}
if (manifestComponent.getIntentFilters() != null) {
for (ManifestIntentFilter filter : manifestComponent.getIntentFilters()) {
IntentFilter.Builder filterBuilder = IntentFilter.newBuilder();
if (filter.getPriority() != null) {
filterBuilder.addAttributes(Attribute.newBuilder().setKind(AttributeKind.PRIORITY)
.addIntValue(filter.getPriority()));
}
Set<String> value = filter.getActions();
if (value != null) {
if (value.contains(null)) {
value.remove(null);
value.add(edu.psu.cse.siis.coal.Constants.NULL_STRING);
}
filterBuilder.addAttributes(Attribute.newBuilder().setKind(AttributeKind.ACTION)
.addAllValue(value));
}
value = filter.getCategories();
if (value != null) {
if (value.contains(null)) {
value.remove(null);
value.add(edu.psu.cse.siis.coal.Constants.NULL_STRING);
}
filterBuilder.addAttributes(Attribute.newBuilder().setKind(AttributeKind.CATEGORY)
.addAllValue(value));
}
if (filter.getData() != null) {
for (ManifestData data : filter.getData()) {
if (data.getHost() != null) {
filterBuilder.addAttributes(Attribute.newBuilder().setKind(AttributeKind.HOST)
.addValue(data.getHost()));
}
if (data.getMimeType() != null) {
// String[] typeParts = data.getMimeType().split("/");
// String type;
// String subtype;
// if (typeParts.length == 2) {
// type = typeParts[0];
// subtype = typeParts[1];
// } else {
// type = Constants.ANY_STRING;
// subtype = Constants.ANY_STRING;
// }
filterBuilder.addAttributes(Attribute.newBuilder().setKind(AttributeKind.TYPE)
.addValue(data.getMimeType()));
// filterBuilder.addAttributes(Attribute.newBuilder().setKind(AttributeKind.SUBTYPE)
// .addValue(subtype));
}
if (data.getPath() != null) {
filterBuilder.addAttributes(Attribute.newBuilder().setKind(AttributeKind.PATH)
.addValue(data.getPath()));
}
if (data.getPort() != null) {
filterBuilder.addAttributes(Attribute.newBuilder().setKind(AttributeKind.PORT)
.addValue(data.getPort()));
}
if (data.getScheme() != null) {
filterBuilder.addAttributes(Attribute.newBuilder().setKind(AttributeKind.SCHEME)
.addValue(data.getScheme()));
}
}
}
componentBuilder.addIntentFilters(filterBuilder);
}
}
return componentBuilder;
}
private Ic3Data.Application.Permission.Level stringToLevel(String levelString) {
if (levelString.equalsIgnoreCase(Constants.PermissionLevel.NORMAL_SHORT)) {
return Ic3Data.Application.Permission.Level.NORMAL;
} else if (levelString.equalsIgnoreCase(Constants.PermissionLevel.DANGEROUS_SHORT)) {
return Ic3Data.Application.Permission.Level.DANGEROUS;
} else if (levelString.equalsIgnoreCase(Constants.PermissionLevel.SIGNATURE_SHORT)) {
return Ic3Data.Application.Permission.Level.SIGNATURE;
} else if (levelString.equalsIgnoreCase(Constants.PermissionLevel.SIGNATURE_OR_SYSTEM_SHORT)) {
return Ic3Data.Application.Permission.Level.SIGNATURE_OR_SYSTEM;
} else {
throw new RuntimeException("Unknown permission level: " + levelString);
}
}
@Override
public String toString() {
StringBuilder result = new StringBuilder("Manifest file for ");
result.append(getPackageName());
if (version != -1) {
result.append(" version ").append(version);
}
result.append("\n Activities:\n");
componentMapToString(activities, result);
result.append("\n Activity Aliases:\n");
componentMapToString(activityAliases, result);
result.append(" Services:\n");
componentMapToString(services, result);
result.append(" Receivers:\n");
componentMapToString(receivers, result);
result.append(" Providers:\n");
componentMapToString(providers, result);
return result.toString();
}
private void componentMapToString(List<ManifestComponent> components, StringBuilder out) {
for (ManifestComponent component : components) {
out.append(" ").append(component.getName()).append("\n");
if (component.getIntentFilters() != null) {
for (ManifestIntentFilter manifestIntentFilter : component.getIntentFilters()) {
out.append(manifestIntentFilter.toString(" "));
}
}
if (component instanceof ManifestProviderComponent) {
ManifestProviderComponent provider = (ManifestProviderComponent) component;
for (String authority : provider.getAuthorities()) {
out.append(" authority: " + authority + "\n");
}
String readPermission = provider.getReadPermission();
String writePermission = provider.getWritePermission();
if (readPermission != null && !readPermission.equals("")) {
out.append(" read permission: " + readPermission + "\n");
}
if (writePermission != null && !writePermission.equals("")) {
out.append(" write permission: " + writePermission + "\n");
}
}
}
}
private boolean handleEndTag(XmlPullParser parser) {
String tagName = parser.getName();
if (skipToEndTag != null) {
if (skipToEndTag.equals(tagName)) {
skipToEndTag = null;
}
return true;
}
if (tagName.equals(ACTIVITY)) {
return handleActivityEnd(parser);
}
if (tagName.equals(ACTIVITY_ALIAS)) {
return handleActivityAliasEnd(parser);
}
if (tagName.equals(SERVICE)) {
return handleServiceEnd(parser);
}
if (tagName.equals(RECEIVER)) {
return handleReceiverEnd(parser);
}
if (tagName.equals(INTENT_FILTER)) {
return handleIntentFilterEnd(parser);
}
if (tagName.equals(PROVIDER)) {
return handleProviderEnd(parser);
}
return true;
}
private void initializeTagDepthMap() {
if (tagDepthMap == null) {
tagDepthMap = new HashMap<String, Integer>();
tagDepthMap.put(MANIFEST, 0);
tagDepthMap.put(USES_PERMISSION, 1);
tagDepthMap.put(PERMISSION, 1);
tagDepthMap.put(APPLICATION, 1);
tagDepthMap.put(ACTIVITY, 2);
tagDepthMap.put(SERVICE, 2);
tagDepthMap.put(RECEIVER, 2);
tagDepthMap.put(INTENT_FILTER, 3);
tagDepthMap.put(ACTION, 4);
tagDepthMap.put(CATEGORY, 4);
tagDepthMap.put(DATA, 4);
tagDepthMap = Collections.unmodifiableMap(tagDepthMap);
}
}
private boolean handleStartTag(XmlPullParser parser, int depth) {
if (skipToEndTag != null) {
return true;
}
String tagName = parser.getName();
if (!checkDepth(tagName, depth)) {
return true;
}
if (tagName.equals(ACTIVITY)) {
return handleActivityStart(parser);
}
if (tagName.equals(ACTIVITY_ALIAS)) {
return handleActivityAliasStart(parser);
}
if (tagName.equals(SERVICE)) {
return handleServiceStart(parser);
}
if (tagName.equals(RECEIVER)) {
return handleReceiverStart(parser);
}
if (tagName.equals(INTENT_FILTER)) {
return handleIntentFilterStart(parser);
}
if (tagName.equals(ACTION)) {
return handleActionStart(parser);
}
if (tagName.equals(CATEGORY)) {
return handleCategoryStart(parser);
}
if (tagName.equals(MANIFEST)) {
return handleManifestStart(parser);
}
if (tagName.equals(APPLICATION)) {
return handleApplicationStart(parser);
}
if (tagName.equals(USES_PERMISSION)) {
return handleUsesPermissionStart(parser);
}
if (tagName.equals(PERMISSION)) {
return handlePermissionStart(parser);
}
if (tagName.equals(DATA)) {
return handleDataStart(parser);
}
if (tagName.equals(PROVIDER)) {
return handleProviderStart(parser);
}
return true;
}
private boolean handleIntentFilterStart(XmlPullParser parser) {
Integer priority = null;
String priorityString = parser.getAttributeValue(NAMESPACE, PRIORITY);
if (priorityString != null && priorityString.length() != 0) {
try {
priority = Integer.valueOf(priorityString);
} catch (NumberFormatException exception) {
System.err.println("Bad priority: " + priorityString);
}
}
currentIntentFilter = new ManifestIntentFilter(false, priority);
return true;
}
private boolean handleIntentFilterEnd(XmlPullParser parser) {
if (currentIntentFilters == null) {
currentIntentFilters = new HashSet<ManifestIntentFilter>();
}
currentIntentFilters.add(currentIntentFilter);
currentIntentFilter = null;
return true;
}
private boolean handleActivityStart(XmlPullParser parser) {
return handleComponentStart(parser, ACTIVITY, Constants.ComponentShortType.ACTIVITY);
}
private boolean handleActivityEnd(XmlPullParser parser) {
return handleComponentEnd(parser, activities);
}
private boolean handleActivityAliasStart(XmlPullParser parser) {
return handleComponentStart(parser, ACTIVITY_ALIAS, Constants.ComponentShortType.ACTIVITY);
}
private boolean handleActivityAliasEnd(XmlPullParser parser) {
return handleComponentEnd(parser, activityAliases);
}
private boolean handleServiceStart(XmlPullParser parser) {
return handleComponentStart(parser, SERVICE, Constants.ComponentShortType.SERVICE);
}
private boolean handleServiceEnd(XmlPullParser parser) {
return handleComponentEnd(parser, services);
}
private boolean handleReceiverStart(XmlPullParser parser) {
return handleComponentStart(parser, RECEIVER, Constants.ComponentShortType.RECEIVER);
}
private boolean handleReceiverEnd(XmlPullParser parser) {
return handleComponentEnd(parser, receivers);
}
private boolean handleComponentStart(XmlPullParser parser, String endTag, String componentType) {
String name = null;
String targetActivity = null;
boolean isExported = false;
boolean foundExported = false;
String permission = null;
for (int i = 0; i < parser.getAttributeCount(); ++i) {
if (!parser.getAttributeNamespace(i).equals(NAMESPACE)) {
continue;
}
String attributeName = parser.getAttributeName(i);
if (attributeName.equals(NAME)) {
name = parser.getAttributeValue(i);
} else if (attributeName.equals(EXPORTED)) {
String value = parser.getAttributeValue(i);
if (value.equals(TRUE)) {
isExported = true;
foundExported = true;
} else if (value.equals(FALSE)) {
foundExported = true;
}
} else if (attributeName.equals(PERMISSION)) {
permission = parser.getAttributeValue(i);
} else if (attributeName.equals(TARGET_ACTIVITY)) {
targetActivity = parser.getAttributeValue(i);
}
}
if (name == null
|| (targetActivity != null && !entryPointClasses
.contains(canonicalizeComponentName(targetActivity)))) {
skipToEndTag = endTag;
return true;
}
if (permission == null) {
permission = applicationPermission;
}
currentComponent =
new ManifestComponent(componentType, canonicalizeComponentName(name), isExported,
foundExported, permission, targetActivity, null, null, null);
return true;
}
private boolean handleComponentEnd(XmlPullParser parser, List<ManifestComponent> componentSet) {
currentComponent.setIntentFiltersAndExported(currentIntentFilters);
entryPointClasses.add(currentComponent.getName());
componentSet.add(currentComponent);
currentComponent = null;
currentIntentFilters = null;
return true;
}
private boolean handleApplicationStart(XmlPullParser parser) {
for (int i = 0; i < parser.getAttributeCount(); ++i) {
if (!parser.getAttributeNamespace(i).equals(NAMESPACE)) {
continue;
}
// The enabled setting can be changed in the code using the PackageManager class.
if (parser.getAttributeName(i).equals(NAME)) {
setApplicationName(parser.getAttributeValue(i));
} else if (parser.getAttributeName(i).equals(PERMISSION)) {
applicationPermission = parser.getAttributeValue(i);
}
}
return true;
}
private boolean handleUsesPermissionStart(XmlPullParser parser) {
String permission = parser.getAttributeValue(NAMESPACE, NAME);
if (permission != null) {
usesPermissions.add(permission);
}
return true;
}
private boolean handlePermissionStart(XmlPullParser parser) {
String permission = parser.getAttributeValue(NAMESPACE, NAME);
String protectionLevel = parser.getAttributeValue(NAMESPACE, PROTECTION_LEVEL);
protectionLevel = transformProtectionLevel(protectionLevel);
if (permission != null) {
if (permissions == null) {
permissions = new HashMap<String, String>();
}
permissions.put(permission, protectionLevel);
}
return true;
}
/**
* Transform the protection level to something appropriate for the database.
*
* The protection level is stored in two different ways. In a binary manifest, it is an integer.
* In a text manifest, it is the string designation of the protection level.
*
* @param protectionLevel The protection level found in the manifest.
* @return A string representation for the protection level appropriate for the database.
*/
private String transformProtectionLevel(String protectionLevel) {
if (protectionLevel == null || protectionLevel.equals("")
|| NORMAL.equalsIgnoreCase(protectionLevel)) {
return Constants.PermissionLevel.NORMAL_SHORT;
} else if (DANGEROUS.equalsIgnoreCase(protectionLevel)) {
return Constants.PermissionLevel.DANGEROUS_SHORT;
} else if (SIGNATURE.equalsIgnoreCase(protectionLevel)) {
return Constants.PermissionLevel.SIGNATURE_SHORT;
} else if (SIGNATURE_OR_SYSTEM.equalsIgnoreCase(protectionLevel)) {
return Constants.PermissionLevel.SIGNATURE_OR_SYSTEM_SHORT;
} else {
// We are dealing with a binary manifest file.
if (protectionLevel.startsWith("0x")) {
// Even if hexadecimal, we still don't care about the radix, since these only go up
// to 3.
protectionLevel = protectionLevel.substring(2);
}
int level = Integer.parseInt(protectionLevel);
return levelValueToShortString[level];
}
}
private boolean handleActionStart(XmlPullParser parser) {
if (currentIntentFilter != null) {
currentIntentFilter.addAction(parser.getAttributeValue(NAMESPACE, NAME));
}
return true;
}
private boolean handleCategoryStart(XmlPullParser parser) {
if (currentIntentFilter != null) {
currentIntentFilter.addCategory(parser.getAttributeValue(NAMESPACE, NAME));
}
return true;
}
private boolean handleDataStart(XmlPullParser parser) {
if (currentIntentFilter != null) {
ManifestData manifestData = new ManifestData();
for (int i = 0; i < parser.getAttributeCount(); ++i) {
if (!parser.getAttributeNamespace(i).equals(NAMESPACE)) {
continue;
}
String attributeName = parser.getAttributeName(i);
String attributeValue = parser.getAttributeValue(i);
if (attributeName.equals(MIME_TYPE)) {
manifestData.setMimeType(attributeValue);
} else if (attributeName.equals(SCHEME)) {
manifestData.setScheme(attributeValue);
} else if (attributeName.equals(HOST)) {
manifestData.setHost(attributeValue);
} else if (attributeName.equals(PORT)) {
manifestData.setPort(attributeValue);
} else if (attributeName.equals(PATH)) {
manifestData.setPath(attributeValue);
} else if (attributeName.equals(PATH_PATTERN)) {
manifestData.setPath(attributeValue);
} else if (attributeName.equals(PATH_PREFIX)) {
manifestData.setPath(String.format("%s(.*)", attributeValue));
}
}
currentIntentFilter.addData(manifestData);
}
return true;
}
private String canonicalizeComponentName(String name) {
if (name.length() == 0) {
throw new RuntimeException("Component should have non-empty name.");
} else if (!name.contains(".")) {
// The non-official rule is that we also prefix the name with the package when
// the component name does not contain any dot.
// (http://stackoverflow.com/questions/3608017/activity-name-in-androidmanifest-xml)
name = getPackageName() + "." + name;
} else if (name.charAt(0) == '.') {
// The official rule is to prefix the name with the package when the component
// name starts with a dot.
// (http://developer.android.com/guide/topics/manifest/activity-element.html#nm)
name = getPackageName() + name;
}
// Found a case where there was a '/' instead of a '.'.
return name.replace('/', '.');
}
private boolean handleManifestStart(XmlPullParser parser) {
for (int i = 0; i < parser.getAttributeCount(); ++i) {
String attributeName = parser.getAttributeName(i);
if (attributeName.equals(PACKAGE)) {
// No namespace requirement.
setPackageName(parser.getAttributeValue(i));
} else if (parser.getAttributeNamespace(i).equals(NAMESPACE) && attributeName.equals(VERSION)) {
version = Integer.parseInt(parser.getAttributeValue(i));
}
}
if (getPackageName() != null) {
return true;
} else {
return false;
}
}
private boolean handleProviderStart(XmlPullParser parser) {
boolean r = handleComponentStart(parser, PROVIDER, Constants.ComponentShortType.PROVIDER);
String readPermission = currentComponent.getPermission();
String writePermission = currentComponent.getPermission();
Set<String> authorities = new HashSet<String>();
boolean grantUriPermissions = false;
for (int i = 0; i < parser.getAttributeCount(); ++i) {
if (!parser.getAttributeNamespace(i).equals(NAMESPACE)) {
continue;
}
String attributeName = parser.getAttributeName(i);
// permissions
// Note: readPermission and writePermission attributes take precedence over
// permission attribute
// (http://developer.android.com/guide/topics/manifest/provider-element.html).
if (attributeName.equals(READ_PERMISSION)) {
readPermission = parser.getAttributeValue(i);
} else if (attributeName.equals(WRITE_PERMISSION)) {
writePermission = parser.getAttributeValue(i);
} else if (attributeName.equals(AUTHORITIES)) {
// the "AUTHORITIES" attribute contains a list of authorities separated by semicolons.
String s = parser.getAttributeValue(i);
for (String a : s.split(";")) {
authorities.add(a);
}
} else if (attributeName.equals(GRANT_URI_PERMISSIONS)) {
grantUriPermissions = !parser.getAttributeValue(i).equals("false");
}
}
currentComponent =
new ManifestProviderComponent(currentComponent.getType(), currentComponent.getName(),
currentComponent.isExported(), currentComponent.isFoundExported(), readPermission,
writePermission, authorities, grantUriPermissions);
return r;
}
private boolean handleProviderEnd(XmlPullParser parser) {
return handleComponentEnd(parser, providers);
}
private boolean checkDepth(String tagName, int depth) {
Integer expectedDepth = tagDepthMap.get(tagName);
if (expectedDepth != null && expectedDepth != depth) {
skipToEndTag = tagName;
System.err.println("Warning: malformed Manifest file: " + tagName + " at depth " + depth);
}
return true;
}
}