Files
ccs/src/cliproxy/binary/zip-extractor.ts
T
Tam Nhu Tran c3c8cb7a82 fix(cliproxy): exclude colocated tests from tsconfig and eslint
- Add src/**/__tests__/** to tsconfig.json exclude list
- Add ignores pattern for __tests__ in eslint.config.mjs
- Fix ui/src/lib/api-client.ts import path for provider-entitlement-types
- Remove stale build artifacts from ui/src/lib/ and tests/mocks/
2026-04-29 17:18:23 -04:00

106 lines
3.4 KiB
TypeScript

/**
* Zip Archive Extractor
* Handles extraction of zip archives using Node.js built-in modules.
*/
import * as fs from 'fs';
import * as path from 'path';
import * as zlib from 'zlib';
import {
getExecutableName,
getArchiveBinaryName,
DEFAULT_BACKEND,
} from '../binary/platform-detector';
import type { CLIProxyBackend } from '../types';
/**
* Extract zip archive using Node.js (simple implementation)
*/
export function extractZip(
archivePath: string,
destDir: string,
verbose = false,
backend: CLIProxyBackend = DEFAULT_BACKEND
): Promise<void> {
return new Promise((resolve, reject) => {
const execName = getExecutableName(backend);
const archiveBinaryName = getArchiveBinaryName(backend);
const buffer = fs.readFileSync(archivePath);
// Find End of Central Directory record (EOCD)
let eocdOffset = buffer.length - 22;
while (eocdOffset >= 0) {
if (buffer.readUInt32LE(eocdOffset) === 0x06054b50) break;
eocdOffset--;
}
if (eocdOffset < 0) {
reject(new Error('Invalid ZIP file: EOCD not found'));
return;
}
const centralDirOffset = buffer.readUInt32LE(eocdOffset + 16);
let offset = centralDirOffset;
while (offset < eocdOffset) {
const sig = buffer.readUInt32LE(offset);
if (sig !== 0x02014b50) break;
const compressionMethod = buffer.readUInt16LE(offset + 10);
const compressedSize = buffer.readUInt32LE(offset + 20);
const uncompressedSize = buffer.readUInt32LE(offset + 24);
const fileNameLength = buffer.readUInt16LE(offset + 28);
const extraFieldLength = buffer.readUInt16LE(offset + 30);
const commentLength = buffer.readUInt16LE(offset + 32);
const localHeaderOffset = buffer.readUInt32LE(offset + 42);
const fileName = buffer.toString('utf8', offset + 46, offset + 46 + fileNameLength);
const baseName = path.basename(fileName);
if (
baseName === execName ||
baseName === archiveBinaryName ||
baseName === 'cli-proxy-api.exe'
) {
const localOffset = localHeaderOffset;
const localSig = buffer.readUInt32LE(localOffset);
if (localSig !== 0x04034b50) {
reject(new Error('Invalid local file header'));
return;
}
const localFileNameLength = buffer.readUInt16LE(localOffset + 26);
const localExtraLength = buffer.readUInt16LE(localOffset + 28);
const dataOffset = localOffset + 30 + localFileNameLength + localExtraLength;
let fileData: Buffer;
if (compressionMethod === 0) {
fileData = buffer.subarray(dataOffset, dataOffset + compressedSize);
} else if (compressionMethod === 8) {
const compressed = buffer.subarray(dataOffset, dataOffset + compressedSize);
fileData = zlib.inflateRawSync(compressed);
} else {
reject(new Error(`Unsupported compression method: ${compressionMethod}`));
return;
}
if (fileData.length !== uncompressedSize) {
reject(new Error('Decompression size mismatch'));
return;
}
const destPath = path.join(destDir, execName);
fs.writeFileSync(destPath, fileData);
if (verbose) console.error(`[cliproxy] Extracted: ${fileName} -> ${destPath}`);
resolve();
return;
}
offset += 46 + fileNameLength + extraFieldLength + commentLength;
}
reject(new Error(`Executable not found in archive: ${execName}`));
});
}