UNPKG

i18ntk

Version:

i18n Tool Kit - Zero-dependency internationalization toolkit for setup, scanning, analysis, validation, auto translation, fixing, reporting, and runtime translation loading.

141 lines (122 loc) 4.4 kB
const fs = require('fs'); const path = require('path'); const SecurityUtils = require('./security'); const { logger } = require('./logger'); const settingsManager = require('../settings/settings-manager'); const CONFIG_FILE = 'i18ntk-config.json'; /** * Validates if the resolved path is within the allowed directory * @param {string} filePath - Path to validate * @param {string} allowedDir - Base directory that the path must be within * @returns {string} Normalized path if valid * @throws {Error} If path traversal is detected */ function validatePath(filePath, allowedDir) { const resolvedPath = path.resolve(filePath); const resolvedAllowed = path.resolve(allowedDir); // Check if the resolved path is within the allowed directory if (!resolvedPath.startsWith(resolvedAllowed)) { throw new Error(`Path traversal attempt detected: ${filePath}`); } return resolvedPath; } /** * Gets the configuration file path with security validation * @param {string} cwd - Current working directory (defaults to settingsManager.configDir) * @returns {string} Validated configuration file path */ function getConfigPath(cwd = settingsManager.configDir) { const configDir = path.normalize(cwd).replace(/(\.\.(\/|\\|$))+/g, ''); return validatePath( path.join(settingsManager.configDir, configDir, CONFIG_FILE), settingsManager.configDir ); } /** * Loads and parses the configuration file * @param {string} cwd - Current working directory * @returns {Object|null} Parsed configuration or null if not found/invalid */ function loadConfig(cwd = settingsManager.configDir) { try { const configPath = getConfigPath(cwd); // Check if file exists and is accessible if (!SecurityUtils.safeExistsSync(configPath)) { return null; } // Read file with explicit encoding const raw = SecurityUtils.safeReadFileSync(configPath, settingsManager.configDir, 'utf8'); // Basic validation of file content if (!raw || typeof raw !== 'string') { throw new Error('Invalid configuration file content'); } const config = JSON.parse(raw); if (config && typeof config === 'object' && !Array.isArray(config)) { return config; } throw new Error('Invalid configuration format'); } catch (error) { logger.debug(`Error loading config: ${error.message}`, { stack: error.stack }); return null; } } /** * Saves configuration to file * @param {Object} config - Configuration object to save * @param {string} cwd - Current working directory * @returns {boolean} True if successful, false otherwise */ function saveConfig(config, cwd = settingsManager.configDir) { try { const configPath = getConfigPath(cwd); const dir = path.dirname(configPath); // Ensure directory exists if (!SecurityUtils.safeExistsSync(dir, settingsManager.configDir)) { SecurityUtils.safeMkdirSync(dir, settingsManager.configDir, { recursive: true, mode: 0o700 }); } // Write file with secure permissions (read/write for owner only) const written = SecurityUtils.safeWriteFileSync( configPath, JSON.stringify(config, null, 2), dir, 'utf8' ); if (!written) { throw new Error('Failed to write configuration file'); } try { fs.chmodSync(configPath, 0o600); } catch (_) { // Best-effort: permissions are a hardening measure, not required } return true; } catch (error) { logger.error(`Error saving config: ${error.message}`); return false; } } /** * Ensures default values for configuration * @param {Object} cfg - Configuration object * @returns {Object} Configuration with defaults */ function ensureConfigDefaults(cfg = {}) { return { initialized: cfg.initialized ?? false, i18nDir: cfg.i18nDir ?? null, sourceDir: cfg.sourceDir ?? null, framework: { detected: cfg.framework?.detected ?? null, preference: cfg.framework?.preference ?? 'none', prompt: cfg.framework?.prompt ?? 'always', lastPromptedVersion: cfg.framework?.lastPromptedVersion ?? null } }; } module.exports = { getConfigPath, loadConfig, saveConfig, ensureConfigDefaults, validatePath // Exported for testing };