[794-5][@ant] fix(spa): пакет — восстановление UTF-8 имён записей zip — иначе «в пакете нет файлов»
CI / build (push) Failing after 7s
CI / build (push) Failing after 7s
Десктоп-упаковщик пишет имена в UTF-8, но не ставит bit-11 (UTF-8) в zip → fflate декодирует кириллицу как latin1 (мохибейк), и пути из manifest.json (UTF-8) не находят записи. parseZip строит индекс с восстановлением latin1→utf8 и резолвит манифест устойчиво; fallback-ветка без дублей (имя восстанавливается только для показа).
This commit is contained in:
@@ -25,24 +25,58 @@ export async function parseSingle(pdf: File, sig: File): Promise<ParseResult> {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Восстанавливает UTF-8-имя записи ZIP, если оно пришло как latin1-мохибейк.
|
||||
*
|
||||
* Десктоп-упаковщик пишет имена файлов в UTF-8, но НЕ ставит флаг bit-11 (UTF-8) в zip —
|
||||
* читатели (fflate) тогда декодируют байты как latin1, и кириллица превращается в мусор
|
||||
* («ÐÑиложение…»). Это ломает поиск файлов по путям из manifest.json (там корректный
|
||||
* UTF-8). Если имя целиком в диапазоне latin1 — пробуем перечитать его байты как UTF-8.
|
||||
* Если строка не является валидным UTF-8 (настоящее latin1-имя) — оставляем как есть.
|
||||
*/
|
||||
function recoverUtf8Name(name: string): string {
|
||||
for (let i = 0; i < name.length; i++) if (name.charCodeAt(i) > 0xff) return name;
|
||||
try {
|
||||
const bytes = Uint8Array.from(name, (c) => c.charCodeAt(0));
|
||||
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
||||
} catch {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
/** ZIP-пакет: распознаётся по manifest.json; парсит пары <doc>.pdf/<doc>.coopsig. */
|
||||
export async function parseZip(zip: File): Promise<ParseResult> {
|
||||
const files = unzipSync(new Uint8Array(await zip.arrayBuffer()));
|
||||
const dec = new TextDecoder();
|
||||
const manifestRaw = files['manifest.json'];
|
||||
|
||||
// Индекс по имени: храним и сырой ключ, и восстановленный из мохибейка UTF-8 —
|
||||
// чтобы пути из manifest (UTF-8) находили записи, у которых имена побиты latin1.
|
||||
const byName = new Map<string, Uint8Array>();
|
||||
for (const [raw, data] of Object.entries(files)) {
|
||||
byName.set(raw, data);
|
||||
const fixed = recoverUtf8Name(raw);
|
||||
if (fixed !== raw && !byName.has(fixed)) byName.set(fixed, data);
|
||||
}
|
||||
const pick = (p: string): Uint8Array | undefined =>
|
||||
byName.get(p) ?? byName.get(recoverUtf8Name(p));
|
||||
|
||||
const manifestRaw = byName.get('manifest.json');
|
||||
|
||||
if (manifestRaw) {
|
||||
const manifest = JSON.parse(dec.decode(manifestRaw)) as PackageManifest;
|
||||
const documents: ParsedDocument[] = manifest.documents.map((entry) => {
|
||||
const pdf = files[entry.document];
|
||||
const sigRaw = files[entry.signature];
|
||||
const pdf = pick(entry.document);
|
||||
const sigRaw = pick(entry.signature);
|
||||
if (!pdf || !sigRaw) throw new Error(`В пакете нет файлов для ${entry.name}`);
|
||||
return { name: entry.name, pdf, sig: JSON.parse(dec.decode(sigRaw)) as SigFile, legacy: false };
|
||||
});
|
||||
return { documents, isPackage: true };
|
||||
}
|
||||
|
||||
// нет manifest.json — ищем пары *.pdf + *.coopsig напрямую
|
||||
// нет manifest.json — ищем пары *.pdf + *.coopsig напрямую.
|
||||
// Итерируем исходные ключи (а не byName с алиасами) — иначе один документ задвоится
|
||||
// через сырое и восстановленное имя. Поиск пары самосогласован (оба имени бьются
|
||||
// одинаково), а UTF-8 восстанавливаем только для отображаемого имени.
|
||||
const documents: ParsedDocument[] = [];
|
||||
for (const path of Object.keys(files)) {
|
||||
if (!path.endsWith('.pdf')) continue;
|
||||
@@ -50,7 +84,7 @@ export async function parseZip(zip: File): Promise<ParseResult> {
|
||||
const sigRaw = files[`${base}.coopsig`];
|
||||
if (!sigRaw) continue;
|
||||
documents.push({
|
||||
name: base,
|
||||
name: recoverUtf8Name(base),
|
||||
pdf: files[path]!,
|
||||
sig: JSON.parse(dec.decode(sigRaw)) as SigFile,
|
||||
legacy: !!files[`${base}.signature.txt`],
|
||||
|
||||
Reference in New Issue
Block a user