mirror of
https://github.com/morluto/rea.git
synced 2026-11-15 11:13:21 +00:00
fix(apktool): distinguish document failures from entry omissions
Return tagged string-document parsing failures instead of fabricating one unparsed entry. Preserve XML failure reasons and publish unknown counts with partial coverage; count only examined unsupported entries in validated documents. Migrate every consumer, remove the superseded throwing XML wrapper, and retain completed failure Evidence through cancellation and failed cleanup. Adapt the document-versus-entry distinction from PR #4804 using the shared Result and XML owner. Multi-table projection and its aggregate bounds remain under review in #4777. Validation before incoming main rebase: 229 focused tests in 15 files, four baseline controls fail, static/docs checks and two source reviews. Twelve tiny actual MCP workflows and four CLI controls; authored launchers only, no real Apktool/JVM/Windows qualification. Fixes #4820 Co-authored-by: Abhay Tiwari <abhaytiwari0821@gmail.com>
This commit is contained in:
1 parent
bec88b9d77
commit
dc2e8117b6
5 files changed
+251
-96
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import type { Document, Element, Node } from "@xmldom/xmldom";
|
||||
import type { Element, Node } from "@xmldom/xmldom";
|
||||
import { parseXmlDocument } from "../domain/xmlDocument.js";
|
||||
import { ok, err, type Result } from "../domain/result.js";
|
||||
import {
|
||||
isMap,
|
||||
isScalar,
|
||||
@@ -53,8 +54,16 @@ export const parseApktoolYml = (yml: string): ApktoolMetadata => {
|
||||
|
||||
/** Read the package attribute from the decoded Android manifest root. */
|
||||
export const parseManifestPackage = (manifest: string): string | null => {
|
||||
const document = parseXml(manifest, "AndroidManifest.xml");
|
||||
const root = document.documentElement;
|
||||
const parsed = parseXmlDocument(manifest, {
|
||||
doctype: "allow",
|
||||
lineEndings: "preserve",
|
||||
});
|
||||
if (!parsed.ok)
|
||||
throw new TypeError(
|
||||
`AndroidManifest.xml is not well-formed XML: ${parsed.error.message}`,
|
||||
{ cause: parsed.error },
|
||||
);
|
||||
const root = parsed.value.documentElement;
|
||||
if (root === null || root.nodeName !== "manifest")
|
||||
throw new TypeError("AndroidManifest.xml root must be <manifest>");
|
||||
return root.getAttribute("package") || null;
|
||||
@@ -65,6 +74,18 @@ export interface DecodedStringEntry {
|
||||
readonly value: string;
|
||||
}
|
||||
|
||||
/** A whole-document failure does not establish an omitted-entry count. */
|
||||
export class ApktoolStringsDocumentError extends Error {
|
||||
readonly _tag = "ApktoolStringsDocumentError";
|
||||
constructor(
|
||||
readonly reason: "malformed" | "wrong_root",
|
||||
message: string,
|
||||
options?: ErrorOptions,
|
||||
) {
|
||||
super(message, options);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Project direct `<string>` children from decoded resources XML. DOM parsing
|
||||
* identifies elements and attributes; text values are sliced from the input
|
||||
@@ -73,19 +94,31 @@ export interface DecodedStringEntry {
|
||||
*/
|
||||
export const parseDecodedStrings = (
|
||||
stringsXml: string,
|
||||
): {
|
||||
entries: readonly DecodedStringEntry[];
|
||||
unparsedCount: number;
|
||||
} => {
|
||||
let document: Document;
|
||||
try {
|
||||
document = parseXml(stringsXml, "strings.xml");
|
||||
} catch {
|
||||
return { entries: [], unparsedCount: 1 };
|
||||
}
|
||||
const root = document.documentElement;
|
||||
): Result<
|
||||
{
|
||||
readonly entries: readonly DecodedStringEntry[];
|
||||
readonly unparsedCount: number;
|
||||
},
|
||||
ApktoolStringsDocumentError
|
||||
> => {
|
||||
const parsed = parseXmlDocument(stringsXml, {
|
||||
doctype: "allow",
|
||||
lineEndings: "preserve",
|
||||
});
|
||||
if (!parsed.ok)
|
||||
return err(
|
||||
new ApktoolStringsDocumentError("malformed", parsed.error.message, {
|
||||
cause: parsed.error,
|
||||
}),
|
||||
);
|
||||
const root = parsed.value.documentElement;
|
||||
if (root === null || root.nodeName !== "resources")
|
||||
return { entries: [], unparsedCount: 1 };
|
||||
return err(
|
||||
new ApktoolStringsDocumentError(
|
||||
"wrong_root",
|
||||
"String resource document root must be <resources>",
|
||||
),
|
||||
);
|
||||
|
||||
const entries: DecodedStringEntry[] = [];
|
||||
let unparsedCount = 0;
|
||||
@@ -125,7 +158,7 @@ export const parseDecodedStrings = (
|
||||
}
|
||||
entries.push({ name, value: value.join("") });
|
||||
}
|
||||
return { entries, unparsedCount };
|
||||
return ok({ entries, unparsedCount });
|
||||
};
|
||||
|
||||
const yamlScalar = (
|
||||
@@ -142,19 +175,6 @@ const yamlScalar = (
|
||||
: source.slice(range[0], range[1]);
|
||||
};
|
||||
|
||||
const parseXml = (xml: string, label: string) => {
|
||||
const parsed = parseXmlDocument(xml, {
|
||||
doctype: "allow",
|
||||
lineEndings: "preserve",
|
||||
});
|
||||
if (!parsed.ok)
|
||||
throw new TypeError(
|
||||
`${label} is not well-formed XML: ${parsed.error.message}`,
|
||||
{ cause: parsed.error },
|
||||
);
|
||||
return parsed.value;
|
||||
};
|
||||
|
||||
const childNodes = (node: Node): Node[] => {
|
||||
const children: Node[] = [];
|
||||
for (let child = node.firstChild; child !== null; child = child.nextSibling)
|
||||
|
||||
Reference in new issue
Block a user