Publish Fast Tabler Docker image / build-and-push-fast (push) Successful in 2m0s
157 lines
6.2 KiB
JavaScript
157 lines
6.2 KiB
JavaScript
/**
|
|
* Модуль валидации синтаксиса 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,
|
|
};
|
|
|