'use strict' import BinaryXmlParser from './binaryxml'; import { attributeNode, docNode, typedType, manifestType, applicationType, activityType } from './binaryxml' // const NS_ANDROID = 'http://schemas.android.com/apk/res/android' const INTENT_MAIN: string = 'android.intent.action.MAIN'; const CATEGORY_LAUNCHER: string = 'android.intent.category.LAUNCHER'; class ManifestParser { private readonly buffer: Buffer; private xmlParser: BinaryXmlParser; constructor(buffer: Buffer) { this.buffer = buffer; this.xmlParser = new BinaryXmlParser(this.buffer) } collapseAttributes(element: docNode) { const collapsed = Object.create(null); for (let attr of Array.from((element.attributes as attributeNode[]))) { collapsed[(attr.name as string)] = (attr.typedValue as typedType).value; } return collapsed; } parseIntents(element: docNode, target: activityType) { target.intentFilters = []; target.metaData = []; return (element.childNodes as docNode[]).forEach(el => { switch (el.nodeName) { case 'intent-filter': { const intentFilter = this.collapseAttributes(el); intentFilter.actions = []; intentFilter.categories = []; intentFilter.data = []; (el.childNodes as docNode[]).forEach(e => { switch (e.nodeName) { case 'action': intentFilter.actions.push(this.collapseAttributes(e)); break; case 'category': intentFilter.categories.push(this.collapseAttributes(e)); break; case 'data': intentFilter.data.push(this.collapseAttributes(e)); break; } }); target.intentFilters.push(intentFilter); break } case 'meta-data': target.metaData.push(this.collapseAttributes(el)); break; } }) } parseApplication(element: docNode) { const app: applicationType = this.collapseAttributes(element); app.activities = []; app.activityAliases = []; app.launcherActivities = []; app.services = []; app.receivers = []; app.providers = []; app.usesLibraries = []; app.metaData = []; (element.childNodes as docNode[]).forEach((el: docNode) => { switch (el.nodeName) { case 'activity': { const activity: activityType = this.collapseAttributes(el); this.parseIntents(el, activity); app.activities.push(activity); if (this.isLauncherActivity(activity)) { app.launcherActivities.push(activity); } break } case 'activity-alias': { const activityAlias = this.collapseAttributes(el); this.parseIntents(el, activityAlias); app.activityAliases.push(activityAlias); if (this.isLauncherActivity(activityAlias)) { app.launcherActivities.push(activityAlias); } break; } case 'service': { const service = this.collapseAttributes(el); this.parseIntents(el, service); app.services.push(service); break; } case 'receiver': { const receiver = this.collapseAttributes(el); this.parseIntents(el, receiver); app.receivers.push(receiver); break; } case 'provider': { const provider = this.collapseAttributes(el); provider.grantUriPermissions = []; provider.metaData = []; provider.pathPermissions = []; (el.childNodes as docNode[]).forEach(e => { switch (e.nodeName) { case 'grant-uri-permission': provider.grantUriPermissions.push(this.collapseAttributes(e)); break; case 'meta-data': provider.metaData.push(this.collapseAttributes(e)); break; case 'path-permission': provider.pathPermissions.push(this.collapseAttributes(e)); break; } }); app.providers.push(provider); break; } case 'uses-library': app.usesLibraries.push(this.collapseAttributes(el)); break; case 'meta-data': app.metaData.push(this.collapseAttributes(el)); } }); return app; } isLauncherActivity(activity: activityType) { return activity.intentFilters.some(function (filter) { const hasMain = filter.actions.some(action => action.name === INTENT_MAIN); if (!hasMain) { return false } return filter.categories.some(category => category.name === CATEGORY_LAUNCHER) }) } parse() { const document: docNode = this.xmlParser.parse(); const manifest: manifestType = this.collapseAttributes(document); manifest.usesPermissions = []; manifest.permissions = []; manifest.permissionTrees = []; manifest.permissionGroups = []; manifest.instrumentation = null; manifest.usesSdk = null; manifest.usesConfiguration = null; manifest.usesFeatures = []; manifest.supportsScreens = null; manifest.compatibleScreens = []; manifest.supportsGlTextures = []; manifest.application = Object.create(null); (document.childNodes as docNode[]).forEach((element: docNode) => { switch (element.nodeName) { case 'uses-permission': manifest.usesPermissions.push(this.collapseAttributes(element)); break; case 'permission': manifest.permissions.push(this.collapseAttributes(element)); break; case 'permission-tree': manifest.permissionTrees.push(this.collapseAttributes(element)); break; case 'permission-group': manifest.permissionGroups.push(this.collapseAttributes(element)); break; case 'instrumentation': manifest.instrumentation = this.collapseAttributes(element); break; case 'uses-sdk': manifest.usesSdk = this.collapseAttributes(element); break; case 'uses-configuration': manifest.usesConfiguration = this.collapseAttributes(element); break; case 'uses-feature': manifest.usesFeatures.push(this.collapseAttributes(element)); break; case 'supports-screens': manifest.supportsScreens = this.collapseAttributes(element); break; case 'compatible-screens': (element.childNodes as docNode[]).forEach(screen => manifest.compatibleScreens.push(this.collapseAttributes(screen))); break case 'supports-gl-texture': manifest.supportsGlTextures.push(this.collapseAttributes(element)); break; case 'application': case 'com.stub.StubApp': // 360 encryption services (adbkit-apkreader#13) manifest.application = this.parseApplication(element); break; } }); return manifest; } } export default ManifestParser