UNPKG

node-apk

Version:

A library to parse Android application manifest and signature

111 lines (98 loc) 4.17 kB
/*Copyright (c) 2019 XdevL. All rights reserved. This work is licensed under the terms of the MIT license. For a copy, see <https://opensource.org/licenses/MIT>.*/ import NodeZlib from "zlib"; import Source from "./source"; enum Signature { CENTRAL_HEADER = 0x2014b50, LOCAL_HEADER = 0x4034b50, } enum Algorithm { DEFLATE = 8, NONE = 0, } export type BufferLoader = () => Promise<Buffer>; export class ZipEntry { public static lookup(loader: BufferLoader, key: string): Promise<ZipEntry> { return ZipEntry.index(loader).then((map) => { const entry = map.get(key); if (entry) { return entry; } else { throw Error("Entry not found: " + key); } }); } public static index(loader: BufferLoader): Promise<Map<string, ZipEntry>> { return loader().then((buffer) => { const index = buffer.lastIndexOf("504B0506", undefined, "hex"); if (index < 0) { throw Error("End of central directory not found"); } const source = new Source(buffer.slice(index + 10)); const count = source.readUShort(); const size = source.readUInt(); const offset = source.readUInt(); const directory = new Source(buffer.slice(offset, offset + size)); const map = new Map() as Map<string, ZipEntry>; for (let i = 0; i < count; ++i) { const entry = new ZipEntry(buffer, directory); map.set(entry.name, entry); } return map; }); } public readonly buffer: Buffer; public readonly signature: number; public readonly versionMadeBy: number; public readonly extractVersion: number; public readonly flags: number; public readonly compressionMethod: number; public readonly time: number; public readonly date: number; public readonly crc32: number; public readonly compressedSize: number; public readonly unCompressedSize: number; public readonly nameLength: number; public readonly extraLength: number; public readonly commentLength: number; public readonly diskNumber: number; public readonly internalAttributes: number; public readonly externalAttributes: number; public readonly offset: number; public readonly name: string; private constructor(buffer: Buffer, source: Source, signature: Signature = Signature.CENTRAL_HEADER) { this.buffer = buffer; this.signature = source.readUInt(); if (this.signature !== signature) { throw Error(`Invalid file header signature found: 0x${this.signature.toString(16)}`); } this.versionMadeBy = this.signature === Signature.CENTRAL_HEADER ? source.readUShort() : 0; this.extractVersion = source.readUShort(); this.flags = source.readUShort(); this.compressionMethod = source.readUShort(); this.time = source.readUShort(); this.date = source.readUShort(); this.crc32 = source.readUInt(); this.compressedSize = source.readUInt(); this.unCompressedSize = source.readUInt(); this.nameLength = source.readUShort(); this.extraLength = source.readUShort(); this.commentLength = this.signature === Signature.CENTRAL_HEADER ? source.readUShort() : 0; this.diskNumber = this.signature === Signature.CENTRAL_HEADER ? source.readUShort() : 0; this.internalAttributes = this.signature === Signature.CENTRAL_HEADER ? source.readUShort() : 0; this.externalAttributes = this.signature === Signature.CENTRAL_HEADER ? source.readUInt() : 0; this.offset = this.signature === Signature.CENTRAL_HEADER ? source.readUInt() : 0; this.name = source.readUtf8String(this.nameLength); source.getCursorAndMove(this.extraLength + this.commentLength); } public stream(): NodeJS.ReadableStream { const source = new Source(this.buffer); source.moveAt(this.offset); const local = new ZipEntry(this.buffer, source, Signature.LOCAL_HEADER); const stream = source.stream(this.compressedSize); if (local.compressionMethod === Algorithm.DEFLATE) { return stream.pipe(NodeZlib.createInflateRaw()); } else if (local.compressionMethod === Algorithm.NONE) { return stream; } else { throw Error(`Unsupported compression method: 0x${this.compressionMethod.toString(16)}`); } } }