fix(admin-service): 改用 yauzl.fromBuffer 解析 APK,支持 V2/V3 签名格式
yauzl.open() 在 APK V2/V3 签名包上报 'end of central directory record signature not found',因为签名块会挤移 EOCD 位置。改为直接调用 yauzl.fromBuffer() + adbkit-apkreader 内部 BinaryXmlParser/ManifestParser 直接从内存解析 AndroidManifest.xml,兼容所有签名格式,且无需临时文件。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
e00cbb71c5
commit
276eda2a84
|
|
@ -1,6 +1,3 @@
|
||||||
import * as fs from 'fs';
|
|
||||||
import * as os from 'os';
|
|
||||||
import * as path from 'path';
|
|
||||||
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
|
import { Injectable, Logger, BadRequestException } from '@nestjs/common';
|
||||||
import { IPackageParser, ParsedPackageInfo } from '../../domain/ports/package-parser.interface';
|
import { IPackageParser, ParsedPackageInfo } from '../../domain/ports/package-parser.interface';
|
||||||
|
|
||||||
|
|
@ -16,14 +13,47 @@ export class PackageParserService implements IPackageParser {
|
||||||
}
|
}
|
||||||
|
|
||||||
private async parseApk(buffer: Buffer): Promise<ParsedPackageInfo> {
|
private async parseApk(buffer: Buffer): Promise<ParsedPackageInfo> {
|
||||||
// adbkit-apkreader v3 底层用 yauzl.open(),只接受文件路径而非 Buffer
|
// yauzl.open() 不支持 APK V2/V3 签名格式(EOCD 被签名块挤移),
|
||||||
// 先写入临时文件,解析后删除
|
// 改用 yauzl.fromBuffer() + adbkit-apkreader 内部 ManifestParser 直接解析
|
||||||
const tmpFile = path.join(os.tmpdir(), `apk-parse-${Date.now()}.apk`);
|
|
||||||
try {
|
try {
|
||||||
fs.writeFileSync(tmpFile, buffer);
|
const yauzl = await import('yauzl');
|
||||||
const ApkReader = await import('adbkit-apkreader').then(m => m.default || m);
|
const { ManifestParser } = await import('adbkit-apkreader/lib/apkreader/parser/manifest' as any).then(
|
||||||
const reader = await ApkReader.open(tmpFile);
|
(m: any) => ({ ManifestParser: m.default || m }),
|
||||||
const manifest = await reader.readManifest();
|
);
|
||||||
|
const { BinaryXmlParser } = await import('adbkit-apkreader/lib/apkreader/parser/binaryxml' as any).then(
|
||||||
|
(m: any) => ({ BinaryXmlParser: m.default || m }),
|
||||||
|
);
|
||||||
|
|
||||||
|
const zipfile = await new Promise<any>((resolve, reject) =>
|
||||||
|
yauzl.fromBuffer(buffer, { lazyEntries: true }, (err: any, zf: any) =>
|
||||||
|
err ? reject(err) : resolve(zf),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const manifestBuffer = await new Promise<Buffer>((resolve, reject) => {
|
||||||
|
zipfile.readEntry();
|
||||||
|
zipfile.on('entry', (entry: any) => {
|
||||||
|
if (entry.fileName === 'AndroidManifest.xml') {
|
||||||
|
zipfile.openReadStream(entry, (err: any, stream: any) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
const chunks: Buffer[] = [];
|
||||||
|
stream.on('data', (c: Buffer) => chunks.push(c));
|
||||||
|
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
||||||
|
stream.on('error', reject);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
zipfile.readEntry();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
zipfile.on('end', () => reject(new Error('AndroidManifest.xml not found')));
|
||||||
|
zipfile.on('error', reject);
|
||||||
|
});
|
||||||
|
|
||||||
|
const binaryXml = new BinaryXmlParser(manifestBuffer);
|
||||||
|
const parsed = binaryXml.parse();
|
||||||
|
const manifestParser = new ManifestParser(parsed);
|
||||||
|
const manifest = manifestParser.parse();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
packageName: manifest.package || 'unknown',
|
packageName: manifest.package || 'unknown',
|
||||||
versionCode: manifest.versionCode || 0,
|
versionCode: manifest.versionCode || 0,
|
||||||
|
|
@ -39,8 +69,6 @@ export class PackageParserService implements IPackageParser {
|
||||||
versionName: '0.0.0',
|
versionName: '0.0.0',
|
||||||
platform: 'ANDROID',
|
platform: 'ANDROID',
|
||||||
};
|
};
|
||||||
} finally {
|
|
||||||
try { fs.unlinkSync(tmpFile); } catch { /* ignore */ }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue