Files
android-dev-ext/src/apk-file-info.js
Dave Holoway 6439e1b8b7 Version 1.1 improvements (#88)
* fix 0 alignment in binary xml decoding

* output reason for APK manifest read failure

* try and match package name against process name
when determining which pid to attach

* make post launch pause user-configurable

* code tidy, jsdocs and types

* more types in expression parse classes

* fix issue with expandable objects not evaluating

* update build task example

* fix package/type evaluation

* improve handling of targetDevice and processID combinations

* show full call stack by default

* implement a queue for evaluations

* improve performance of retrieving single fields

* check root term identifiers against this fields
2020-04-24 19:03:39 +01:00

145 lines
3.8 KiB
JavaScript

const crypto = require('crypto');
const fs = require('fs');
const path = require('path');
const { extractManifestFromAPK, parseManifest } = require('./manifest');
const { D } = require('./utils/print');
class APKFileInfo {
/**
* the full file path to the APK file
*/
fpn = '';
/**
* The APK file data
* @type {Buffer}
*/
file_data = null;
/**
* last modified time of the APK file (in ms)
*/
app_modified = 0;
/**
* SHA-1 (hex) digest of the APK file
*/
content_hash = '';
/**
* Contents of Android Manifest XML file
*/
manifestXml = '';
/**
* Extracted data from the manifest
*/
manifest = {
/**
* Package name of the app
*/
package: '',
/**
* List of all named Activities
* @type {string[]}
*/
activities: [],
/**
* The launcher Activity
*/
launcher: '',
};
constructor(apk_fpn) {
this.fpn = apk_fpn;
}
/**
* Build a new APKFileInfo instance
* @param {*} args
*/
static async from(args) {
const result = new APKFileInfo(args.apkFile);
// read the APK file contents
try {
result.file_data = await readFile(args.apkFile);
} catch(err) {
throw new Error(`APK read error. ${err.message}`);
}
// save the last modification time of the app
result.app_modified = fs.statSync(result.fpn).mtime.getTime();
// create a SHA-1 hash as a simple way to see if we need to install/update the app
const h = crypto.createHash('SHA1');
h.update(result.file_data);
result.content_hash = h.digest('hex');
// read the manifest
try {
result.manifestXml = await getAndroidManifestXml(args);
} catch (err) {
throw new Error(`Manifest read error. ${err.message}`);
}
// extract the parts we need from the manifest
try {
result.manifest = parseManifest(result.manifestXml);
} catch(err) {
throw new Error(`Manifest parse failed. ${err.message}`);
}
return result;
}
}
/**
* Retrieve the AndroidManifest.xml file content
*
* Because of manifest merging and build-injected properties, the manifest compiled inside
* the APK is frequently different from the AndroidManifest.xml source file.
* We try to extract the manifest from 3 sources (in priority order):
* 1. The 'manifestFile' launch configuration property
* 2. The decoded manifest from the APK
* 3. The AndroidManifest.xml file from the root of the source tree.
*/
async function getAndroidManifestXml({manifestFile, apkFile, appSrcRoot}) {
let manifest;
// a value from the manifestFile overrides the default manifest extraction
// note: there's no validation that the file is a valid AndroidManifest.xml file
if (manifestFile) {
D(`Reading manifest from ${manifestFile}`);
manifest = await readFile(manifestFile, 'utf8');
return manifest;
}
try {
D(`Reading APK Manifest`);
manifest = await extractManifestFromAPK(apkFile);
} catch(err) {
// if we fail to get manifest from the APK, revert to the source file version
D(`Reading source manifest from ${appSrcRoot} (${err.message})`);
manifest = await readFile(path.join(appSrcRoot, 'AndroidManifest.xml'), 'utf8');
}
return manifest;
}
/**
* Promisified fs.readFile()
* @param {string} path
* @param {*} [options]
*/
function readFile(path, options) {
return new Promise((res, rej) => {
fs.readFile(path, options || {}, (err, data) => {
err ? rej(err) : res(data);
})
})
}
module.exports = {
APKFileInfo,
}