128 lines
4.2 KiB
JavaScript
128 lines
4.2 KiB
JavaScript
import { createHash } from "node:crypto";
|
|
import { mkdtemp, readFile, rm, stat } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import { ApktoolResourceAnalysisService } from "../dist/application/apktool/ApktoolResourceAnalysisService.js";
|
|
import { createApktoolResourceAnalysisProvider } from "../dist/composition/apktool.js";
|
|
|
|
const options = parseArguments(process.argv.slice(2));
|
|
if (options.help) {
|
|
process.stdout.write(
|
|
"Usage: npm run verify:apktool [-- --apk PATH]\n" +
|
|
"Requires a caller-selected apktool launcher (REA_APKTOOL_COMMAND or\n" +
|
|
"PATH) and a Java runtime. Without --apk, a small signed probe APK is\n" +
|
|
"expected at the path given by --apk; nothing is downloaded or built.\n",
|
|
);
|
|
process.exit(0);
|
|
}
|
|
|
|
const provider = createApktoolResourceAnalysisProvider(process.env);
|
|
const service = new ApktoolResourceAnalysisService(provider);
|
|
let failures = 0;
|
|
const check = (name, condition, detail = "") => {
|
|
const mark = condition ? "ok" : "FAIL";
|
|
process.stdout.write(
|
|
`[${mark}] ${name}${detail === "" ? "" : ` — ${detail}`}\n`,
|
|
);
|
|
if (!condition) failures += 1;
|
|
};
|
|
|
|
const run = async (operation, input) => {
|
|
const result = await service.execute(operation, input);
|
|
if (!result.ok) {
|
|
process.stdout.write(`[FAIL] ${operation} — ${result.error.message}\n`);
|
|
failures += 1;
|
|
return null;
|
|
}
|
|
return result.value.normalized_result;
|
|
};
|
|
|
|
const client = await run("inspect_apktool_client", {});
|
|
if (client !== null) {
|
|
check(
|
|
"apktool launcher reports a version",
|
|
typeof client.client.apktool_version === "string" &&
|
|
client.client.apktool_version !== "",
|
|
`command=${client.client.command} version=${client.client.apktool_version}`,
|
|
);
|
|
}
|
|
|
|
if (options.apk !== null) {
|
|
const decoded = await run("decode_android_resources", {
|
|
path: options.apk,
|
|
include_strings: true,
|
|
});
|
|
if (decoded !== null) {
|
|
const bytes = await stat(options.apk).then((info) => info.size);
|
|
const digest = createHash("sha256")
|
|
.update(await readFile(options.apk))
|
|
.digest("hex");
|
|
check(
|
|
"decoded target identity matches the file on disk",
|
|
decoded.target.bytes === bytes && decoded.target.sha256 === digest,
|
|
`${bytes} bytes`,
|
|
);
|
|
check(
|
|
"metadata carries version and SDK facts",
|
|
typeof decoded.metadata.version_name === "string" &&
|
|
decoded.metadata.min_sdk_version !== null &&
|
|
decoded.metadata.target_sdk_version !== null,
|
|
`version=${String(decoded.metadata.version_name)} sdk=${String(decoded.metadata.min_sdk_version)}..${String(decoded.metadata.target_sdk_version)}`,
|
|
);
|
|
check(
|
|
"decoded manifest names the package",
|
|
typeof decoded.metadata.package_name === "string" &&
|
|
decoded.manifest !== null &&
|
|
decoded.manifest.includes(decoded.metadata.package_name),
|
|
String(decoded.metadata.package_name),
|
|
);
|
|
check(
|
|
"string resources projected",
|
|
decoded.strings.length > 0,
|
|
`${decoded.strings.length} string(s)`,
|
|
);
|
|
if (decoded.locales.length > 0) {
|
|
const locale = await run("decode_android_resources", {
|
|
path: options.apk,
|
|
include_strings: true,
|
|
locale: decoded.locales[0],
|
|
});
|
|
if (locale !== null)
|
|
check(
|
|
`locale table projected for ${String(decoded.locales[0])}`,
|
|
Array.isArray(locale.strings),
|
|
`${locale.strings.length} string(s)`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
await provider.close();
|
|
} catch {
|
|
// The provider owns per-call workspaces only; closing is best-effort.
|
|
}
|
|
|
|
if (failures > 0) {
|
|
process.stdout.write(`\n${failures} check(s) failed\n`);
|
|
process.exit(1);
|
|
}
|
|
process.stdout.write("\nall apktool checks passed\n");
|
|
|
|
function parseArguments(argv) {
|
|
const parsed = { help: false, apk: null };
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const argument = argv[index];
|
|
if (argument === "--help" || argument === "-h") parsed.help = true;
|
|
else if (argument === "--apk") {
|
|
parsed.apk = argv[index + 1] ?? null;
|
|
index += 1;
|
|
} else {
|
|
process.stderr.write(`unknown argument: ${argument}\n`);
|
|
process.exit(2);
|
|
}
|
|
}
|
|
return parsed;
|
|
}
|