const fs = require("fs"); const path = require("path"); const components = require("./components"); const HTTPServer = require("./components/_http"); const { createHash } = require("crypto"); // General regexps // const FIND_ADDRESS_INSTR_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}\s{0,}\{[\s\n\r0-9a-z;:\/\."',]{0,}\}/gmi; const DOMAINER_REGEX = /domain\s{1,}[a-z0-9\.]{1,}\s{1,}\{[\s\n\r0-9a-z;:\/\."',_-]{0,}\}/gmi; const DOMAINER_VALUE_FIND_REGEX = /domain\s{1,}[a-z0-9\.]{1,}\s{1,}\{/gmi; const ADDRESS_WITH_PORT_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}/mi; const FIND_ADDRESS_INSTR_REGEX = /([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]{1,5}\s{0,}\{([\s\n\r0-9a-z;:\/\."',_-]|domain\s{1,}[a-z0-9\._]{1,}\s{1,}\{[\s\n\r0-9a-z;:\/\."',_-]{0,}\}){0,}\}/gmi; const COMMENT_REGEX = /#.{0,}(\n|\r|$)/gmi; const CONTENT_INTO_CURLY_BRACES = /\{[\s\n\r0-9a-z;:\/\."',_-]{0,}\}{0,}\}/gmi; // Instruction regexps function readInstructions (instructions, fullCode, forDomains=false) { const syntaxTree = { firewall: false, encryption: {}, modules: new Set(), showDir: false }; const moduleOpsQueue = new Array(); for (let instruction of instructions) { if (!instruction) { if (instructions.indexOf(instruction) === instructions.length - 1) { break; } throw new SyntaxError("Invalid syntax: `;;`"); } const instructionParts = instruction.split(/\s{1,}/); const mainOperator = instructionParts[0]; switch (mainOperator) { case "type": if (instructionParts.length !== 2) throw new SyntaxError("Invalid syntax of operator `type`: type "); const SelectedComponent = components[instructionParts[1]]; if (SelectedComponent === undefined) throw new SyntaxError(`Unknown component: ${instructionParts[1]}. Valid components: ${Object.keys(components).join(", ")}`); syntaxTree.Type = SelectedComponent; break; case "target": if (instructionParts.length !== 2) throw new SyntaxError("Invalid syntax of operator `target`: target "); syntaxTree.target = instructionParts[1]; break; case "cert": if (instructionParts.length < 3) throw new SyntaxError("Invalid syntax of operator `cert`: cert "); if (!["public", "private"].includes(instructionParts[1])) throw new SyntaxError("Cert type can be only private or public, not " + instructionParts[1]); syntaxTree.encryption[instructionParts[1]] = instructionParts.slice(2,).join(" "); break; case "auth": if (instructionParts.length < 4 || instructionParts.length < 2) throw new SyntaxError("Invalid syntax of operator `auth`: auth OR auth null"); if (!["loopback", "strict", "null"].includes(instructionParts[1])) throw new SyntaxError("Invalid mode: " + instructionParts[1] + ", valid modes: loopback, strict, null"); syntaxTree.auth = instructionParts[1] !== "null" ? { mode: instructionParts[1], login: instructionParts[2], password: instructionParts.slice(3,).join(" ") } : null; break; case "showdir": syntaxTree.showDir = true; break; case "firewall": break; case "import": if (instructionParts.length < 2) throw new SyntaxError("Invalid syntax of operator `import`: import OR import , , ..., "); const modules = instructionParts.slice(1,).join(" ").split(/\s{0,},\s{0,}/gmi); modules.forEach(m => syntaxTree.modules.add(new (require(`./modules/${m}`))())); break; case "#instuction:domain": if (forDomains) throw new SyntaxError("Nested use of domains doesn't not supported"); break; default: let mopFlag = false for (let module of syntaxTree.modules) { for (let op of module.operators) { if (op.operator === mainOperator) { // Нуждается в доработке! if (!op.handlers.argv(instructionParts.slice(1,))) throw new SyntaxError("Custom module's operator error. Invalid argv syntax on operator: " + mainOperator); moduleOpsQueue.push( (st) => op.handlers.exec(instructionParts.slice(1,), module, st) ); mopFlag = true; break; } } if (mopFlag) break; } if (mopFlag) break; throw new SyntaxError(`Unknown routemap's operator: ${mainOperator}`); } } if (Object.keys(syntaxTree.encryption).length === 0) syntaxTree.encryption = null; else if (Object.keys(syntaxTree.encryption).length === 1) throw new SyntaxError("No private or public key specified"); if (!forDomains) { syntaxTree.domains = new Object(); const allDomains = Object.fromEntries((fullCode.match(DOMAINER_REGEX) ?? []) .map(x => [x.match(DOMAINER_VALUE_FIND_REGEX)[0].split(/\s{1,}/gmi)[1], x.match(CONTENT_INTO_CURLY_BRACES)[0].slice(1, -1).split(/\s{0,};\s{0,}/g).map(x => x.trim())])); for (let domain in allDomains) { syntaxTree.domains[domain] = readInstructions(allDomains[domain], "", true); } } syntaxTree.modules = Array.from(syntaxTree.modules); // Object.assign needs replace to object which give only-read access moduleOpsQueue.forEach(h => { const st = Object.assign({}, syntaxTree); return h(st); }); return syntaxTree; } function logger (...p) { return; console.debug("[DEBUG.routemap]:", ...p); } module.exports.parse = function () { const syntaxTree = new Object(); const routemapContent = fs.readFileSync(path.join(__dirname, "./routemap.txt"), { encoding: "utf-8" }) .replace(COMMENT_REGEX, "\n"); const findedAdrInstructions = routemapContent.match(FIND_ADDRESS_INSTR_REGEX); logger("findedAdrInstructions:", findedAdrInstructions); if (findedAdrInstructions === null) throw new SyntaxError("routemap.txt is empty"); for (let itemAdrInst of findedAdrInstructions) { itemAdrInst = itemAdrInst.replace(/[\n\r]{1,}/g, ""); const content = itemAdrInst .replace(ADDRESS_WITH_PORT_REGEX, "") .replace(DOMAINER_REGEX, "#instuction:domain") .trim() .slice(1, -1) .split(/\s{0,};\s{0,}/g).map(x => x.trim()); logger("itemAdrInst:", itemAdrInst, "with content:", content); const localSyntaxTree = readInstructions(content, itemAdrInst); syntaxTree[itemAdrInst.match(ADDRESS_WITH_PORT_REGEX)[0]] = localSyntaxTree; } logger("Full syntax tree:", syntaxTree); return syntaxTree; } module.exports.hash = function () { const content = fs.readFileSync(path.join(__dirname, "./routemap.txt"), { encoding: "utf-8" }) .replace(COMMENT_REGEX, "\n"); const hash = createHash('sha256').update(content, "utf8") .digest().toString("hex"); return hash; }