/** * Модуль валидации синтаксиса MikroTik RouterOS конфигурации */ /** * Валидация MikroTik конфигурации * @param {string} config - Текст конфигурации * @returns {object} { valid: boolean, errors: array, warnings: array } */ function validateMikrotikConfig(config) { const errors = []; const warnings = []; if (!config || typeof config !== 'string') { errors.push({ line: 0, message: 'Конфигурация пуста или неверного типа' }); return { valid: false, errors, warnings }; } const lines = config.split('\n'); let inBlock = false; let blockName = ''; let braceBalance = 0; let currentBlockLine = 0; for (let i = 0; i < lines.length; i++) { const lineNum = i + 1; const line = lines[i].trim(); // Пропускаем комментарии и пустые строки if (line.startsWith('//') || line.startsWith('#') || line === '') { continue; } // Проверка начала блока (например, /routing filter bgp-in-tmp {) if (line.match(/^\/\w+(\s+\w+)*\s+\w+(-\w+)*\s*\{/)) { if (inBlock) { errors.push({ line: lineNum, message: `Вложенные блоки не поддерживаются в RouterOS` }); } inBlock = true; blockName = line.match(/^\/\w+(\s+\w+)*/)?.[0] || ''; currentBlockLine = lineNum; // Считаем скобки только один раз для этой строки (чтобы не дублировать подсчёт ниже) const openBraces = (line.match(/\{/g) || []).length; const closeBraces = (line.match(/\}/g) || []).length; braceBalance += openBraces - closeBraces; continue; } // Подсчет фигурных скобок const openBraces = (line.match(/\{/g) || []).length; const closeBraces = (line.match(/\}/g) || []).length; braceBalance += openBraces - closeBraces; // Проверка закрытия блока if (line === '}') { if (braceBalance === 0) { inBlock = false; blockName = ''; } continue; } // Проверка синтаксиса внутри блока if (inBlock) { // Проверка if конструкций if (line.startsWith('if')) { // Проверка наличия условия в скобках if (!line.match(/if\s*\(/)) { errors.push({ line: lineNum, message: `Отсутствует открывающая скобка после if` }); } // Проверка сбалансированности скобок в условии const conditionPart = line.substring(line.indexOf('(')); const openParens = (conditionPart.match(/\(/g) || []).length; const closeParens = (conditionPart.match(/\)/g) || []).length; if (openParens !== closeParens) { errors.push({ line: lineNum, message: `Несбалансированные скобки в условии if` }); } } // Проверка команд set gw if (line.match(/set\s+gw\s+/)) { const match = line.match(/set\s+gw\s+([^;]+)/); if (match) { const gateway = match[1].trim(); // Проверка валидности имени gateway if (!gateway || !/^[a-zA-Z0-9_-]+$/.test(gateway)) { warnings.push({ line: lineNum, message: `Возможно неверное имя gateway: "${gateway}"` }); } } } // Проверка команд accept/reject if (line.match(/;\s*(accept|reject)\s*;/)) { warnings.push({ line: lineNum, message: `Двойная точка с запятой перед ${line.includes('accept') ? 'accept' : 'reject'}` }); } // Проверка bgp-communities if (line.includes('bgp-communities includes')) { const match = line.match(/bgp-communities\s+includes\s+(\S+)/); if (match) { const community = match[1].replace(/[()]/g, ''); // Базовая проверка формата community if (!community.match(/^\d+:\d+$/)) { warnings.push({ line: lineNum, message: `Community "${community}" может иметь неверный формат (ожидается N:N)` }); } } } // Проверка else конструкций if (line.startsWith('else')) { // else должен следовать после закрывающей скобки if const prevNonEmpty = lines.slice(0, i).reverse().find(l => l.trim() !== '' && !l.trim().startsWith('//')); if (prevNonEmpty && !prevNonEmpty.trim().endsWith('}')) { warnings.push({ line: lineNum, message: `else должен следовать после закрывающей скобки блока if` }); } } } // Проверка команд вне блока if (!inBlock && line.startsWith('/')) { // Команды верхнего уровня должны начинаться с / const validTopLevel = ['/routing', '/ip', '/interface', '/system']; const isValidTopLevel = validTopLevel.some(cmd => line.startsWith(cmd)); if (!isValidTopLevel) { warnings.push({ line: lineNum, message: `Неизвестная команда верхнего уровня: ${line.substring(0, 30)}` }); } } } // Проверка сбалансированности фигурных скобок if (braceBalance !== 0) { errors.push({ line: 0, message: `Несбалансированные фигурные скобки (баланс: ${braceBalance})` }); } // Проверка незакрытых блоков if (inBlock) { errors.push({ line: currentBlockLine, message: `Блок "${blockName}" не закрыт` }); } return { valid: errors.length === 0, errors, warnings }; } // Используем общие валидаторы из validators.js const { isValidCommunity, isValidGateway } = require('./validators'); module.exports = { validateMikrotikConfig, isValidCommunity, isValidGateway, };