UNPKG

@probelabs/probe

Version:

Node.js wrapper for the probe code search tool

272 lines (232 loc) 8.36 kB
/** * Binary extractor for bundled probe binaries * @module extractor */ import fs from 'fs-extra'; import path from 'path'; import tar from 'tar'; import AdmZip from 'adm-zip'; import os from 'os'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const BINARY_NAME = "probe"; /** * Detects the current OS and architecture * @returns {Object} Object containing OS and architecture information */ function detectPlatform() { const osType = os.platform(); const archType = os.arch(); let platform; let extension; // Map to the same format used in release artifacts if (osType === 'linux') { if (archType === 'x64') { platform = 'x86_64-unknown-linux-musl'; extension = 'tar.gz'; } else if (archType === 'arm64') { platform = 'aarch64-unknown-linux-musl'; extension = 'tar.gz'; } else { throw new Error(`Unsupported Linux architecture: ${archType}`); } } else if (osType === 'darwin') { if (archType === 'x64') { platform = 'x86_64-apple-darwin'; extension = 'tar.gz'; } else if (archType === 'arm64') { platform = 'aarch64-apple-darwin'; extension = 'tar.gz'; } else { throw new Error(`Unsupported macOS architecture: ${archType}`); } } else if (osType === 'win32') { if (archType === 'x64') { platform = 'x86_64-pc-windows-msvc'; extension = 'zip'; } else { throw new Error(`Unsupported Windows architecture: ${archType}`); } } else { throw new Error(`Unsupported operating system: ${osType}`); } if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') { console.log(`Detected platform: ${platform}`); } return { platform, extension, osType, archType }; } /** * Validates that a path is within a base directory (prevents path traversal) * @param {string} filePath - Path to validate * @param {string} baseDir - Base directory that filePath must be within * @returns {boolean} True if path is safe */ function isPathSafe(filePath, baseDir) { const normalizedBase = path.normalize(baseDir); const normalizedPath = path.normalize(filePath); const relativePath = path.relative(normalizedBase, normalizedPath); // Path is safe if it doesn't start with '..' and isn't absolute return !relativePath.startsWith('..') && !path.isAbsolute(relativePath); } /** * Extracts a tar.gz archive using the tar library * @param {string} archivePath - Path to the .tar.gz file * @param {string} extractDir - Directory to extract to */ async function extractTarGz(archivePath, extractDir) { await tar.extract({ file: archivePath, cwd: extractDir, // Security: Prevent path traversal attacks onentry: (entry) => { const fullPath = path.join(extractDir, entry.path); if (!isPathSafe(fullPath, extractDir)) { throw new Error(`Path traversal attempt detected: ${entry.path}`); } } }); } /** * Extracts a zip archive using adm-zip library * @param {string} archivePath - Path to the .zip file * @param {string} extractDir - Directory to extract to */ async function extractZip(archivePath, extractDir) { const zip = new AdmZip(archivePath); const zipEntries = zip.getEntries(); // Extract each entry with path validation for (const entry of zipEntries) { const outputPath = path.join(extractDir, entry.entryName); // Security: Validate path to prevent traversal attacks if (!isPathSafe(outputPath, extractDir)) { throw new Error(`Path traversal attempt detected: ${entry.entryName}`); } if (entry.isDirectory) { await fs.ensureDir(outputPath); } else { await fs.ensureDir(path.dirname(outputPath)); await fs.writeFile(outputPath, entry.getData()); } } } /** * Finds the binary file in the extracted directory * @param {string} dir - Directory to search * @param {string} baseDir - Base directory for path validation * @param {string} binaryName - Name of binary to find * @param {boolean} isWindows - Whether running on Windows * @returns {Promise<string|null>} Path to binary or null */ async function findBinary(dir, baseDir, binaryName, isWindows) { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); // Security: Validate path to prevent traversal if (!isPathSafe(fullPath, baseDir)) { if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') { console.log(`Skipping unsafe path: ${fullPath}`); } continue; } if (entry.isDirectory()) { const result = await findBinary(fullPath, baseDir, binaryName, isWindows); if (result) return result; } else if (entry.isFile()) { // Check if this is the binary we're looking for if (entry.name === binaryName || entry.name === BINARY_NAME || (isWindows && entry.name.endsWith('.exe'))) { return fullPath; } } } return null; } /** * Extracts the bundled binary for the current platform * @param {string} version - Version string (used for archive naming) * @returns {Promise<string>} Path to the extracted binary */ export async function extractBundledBinary(version) { const { platform, extension, osType } = detectPlatform(); // Construct the archive filename const archiveName = `probe-v${version}-${platform}.${extension}`; // Path to the bundled archive const binariesDir = path.resolve(__dirname, '..', 'bin', 'binaries'); const archivePath = path.join(binariesDir, archiveName); if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') { console.log(`Looking for bundled binary at: ${archivePath}`); } // Check if the archive exists if (!(await fs.pathExists(archivePath))) { throw new Error( `Bundled binary not found for platform ${platform}.\n` + `Expected archive: ${archiveName}\n` + `Searched in: ${binariesDir}\n` + `\n` + `Supported platforms:\n` + ` - x86_64-unknown-linux-musl (Linux x64, static)\n` + ` - aarch64-unknown-linux-musl (Linux ARM64, static)\n` + ` - x86_64-apple-darwin (macOS Intel)\n` + ` - aarch64-apple-darwin (macOS Apple Silicon)\n` + ` - x86_64-pc-windows-msvc (Windows x64)\n` + `\n` + `Your platform: ${platform}` ); } // Determine output binary name and path const binDir = path.resolve(__dirname, '..', 'bin'); const isWindows = osType === 'win32'; const binaryName = isWindows ? `${BINARY_NAME}.exe` : `${BINARY_NAME}-binary`; const binaryPath = path.join(binDir, binaryName); if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') { console.log(`Extracting ${archiveName} to ${binDir}...`); } // Create a temporary extraction directory const extractDir = path.join(binDir, 'temp_extract'); await fs.ensureDir(extractDir); try { // Extract based on file type using proper libraries (no shell commands!) if (extension === 'tar.gz') { await extractTarGz(archivePath, extractDir); } else if (extension === 'zip') { await extractZip(archivePath, extractDir); } // Find the binary in the extracted files if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') { console.log(`Searching for binary in extracted files...`); } const binaryFilePath = await findBinary(extractDir, extractDir, binaryName, isWindows); if (!binaryFilePath) { const allFiles = await fs.readdir(extractDir, { recursive: true }); throw new Error( `Binary not found in the archive.\n` + `Expected binary name: ${binaryName}\n` + `Files in archive: ${allFiles.join(', ')}` ); } // Copy the binary to the final location if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') { console.log(`Found binary at ${binaryFilePath}`); console.log(`Installing to ${binaryPath}`); } await fs.copyFile(binaryFilePath, binaryPath); // Make the binary executable on Unix-like systems if (!isWindows) { await fs.chmod(binaryPath, 0o755); } // Clean up the temporary extraction directory await fs.remove(extractDir); if (process.env.DEBUG === '1' || process.env.VERBOSE === '1') { console.log(`Binary successfully extracted to ${binaryPath}`); } return binaryPath; } catch (error) { // Clean up on error try { await fs.remove(extractDir); } catch {} throw new Error(`Failed to extract bundled binary: ${error.message}`); } }