diff --git a/components/_component.js b/components/_component.js index ec702c8..6764fd7 100644 --- a/components/_component.js +++ b/components/_component.js @@ -7,6 +7,9 @@ module.exports = class Component { // Run server run () {} + // Stop server + stop () {} + // Working with domains domainAction (context, domain) {} diff --git a/components/_http.js b/components/_http.js new file mode 100644 index 0000000..94d5a00 --- /dev/null +++ b/components/_http.js @@ -0,0 +1,280 @@ +const Component = require("./_component"); +const utils = require("../utils"); + +const https = require("https"); +const http = require("http"); +const express = require("express"); +const bodyParser = require("body-parser"); +const bodyHand = require('body'); +const url = require('url'); +const path = require('path'); +const fs = require('fs'); +const fetch = require('node-fetch'); +const cookieParser = require('cookie-parser'); +const mime = require("mime"); + +function logger (...p) { + //return; + console.log("[DEBUG.http]:", ...p); +} + +function fetchProxy (req, res, reverseTo) { + const params = { method: req.method.toUpperCase(), headers: Object.assign({}, req.headers), redirect: "follow", follow: 20, compress: false }; + delete params.headers.host; + delete params.headers.referer; + if (!["GET", "HEAD"].includes(params.method)) + params.body = req.body; + + const reqAddress = url.resolve(reverseTo, path.join("/", req.originalUrl)); + fetch(reqAddress, params) + .then(async result => { + const content = await result.arrayBuffer(); + //logger("result:", result); + //logger("result.arrayBuffer():", content); + //logger("result.Buffer:", Buffer.from(content).toString()); + + const headers = Object.fromEntries(result.headers); + + //logger("headers Headers", result.headers); + //logger("headers Headers.raw()", result.headers.raw()); + logger("headers object", headers); + + Object.entries(headers).forEach(([header, value]) => { + res.set(header, value); + }); + + const finalContent = Buffer.from(content); + res.status(result.status).send(finalContent); + }) + .catch(err => { + console.error(err); + res.status(500).send(req.st500); + }); +} + +function authPage (self, req, res, next) { + if (self.syntax.auth) { + const ACCESS = "Basic " + Buffer.from(`${self.syntax.auth.login}:${self.syntax.auth.password}`).toString('base64'); + if (req.headers['authorization'] === ACCESS) { + return next(); + } + else { + if (self.syntax.auth.mode === "strict" && req.headers['authorization'] !== undefined) { + return res.status(403).send(req.st403); + } + res.set("WWW-Authenticate", "Basic"); + return res.status(401).send(); + } + } + next(); +} + +function redirectPage (redirTo, req, res) { + redirTo = url.resolve(redirTo, path.join("/", req.originalUrl)); + res.redirect(redirTo); +} + +function toNormalText (httpPath) { + const findedUnicodeTexts = httpPath.match(/(\%[A-F0-9]{2}){1,}/g) ?? []; + for (let matchedQuery of findedUnicodeTexts) { + httpPath = httpPath + .split(matchedQuery) + .join(Buffer.from( + matchedQuery + .replace(/\%/g, ""), + "hex" + ).toString("utf8")) + } + return httpPath; +} + +function renderDirContent (dirpath, displayedDir) { + const files = fs.readdirSync(dirpath).map(f => ({ + name: f, + path: path.join(displayedDir, f), + folder: displayedDir, + lstat: fs.lstatSync(path.join(dirpath, f)), + sizeReadable: utils.getStrSize(fs.lstatSync(path.join(dirpath, f)).size), + hash: "1ac89f5efc", + mime: mime.getType(f) + })); + files.folder = displayedDir; + + const ht = (i) => parseInt(Buffer.from(i ?? "0").toString("hex"), 16); + + files.sort((a, b) => a.name - b.name); + files.sort((a, b) => ht(a.mime) - ht(b.mime)); + files.sort((a, b) => +(b.lstat.isDirectory()) - +(a.lstat.isDirectory())); + + return `Content of dir: ${displayedDir} +

Content of dir: ${displayedDir}


+ + + + + + + + ${renderDirContent.genTRs(files)} +
FilenameSizeChecksum
+ `; +} +renderDirContent.genTRs = function (files) { + const allFiles = files.map(f => `${renderDirContent.getIcon(f)}${f.name + (f.lstat.isDirectory() ? "/" : "")}${f.sizeReadable}${f.hash}`); + return [`${renderDirContent.getIcon(null)}../`].concat(allFiles).join("\n\t\t\t"); +} +renderDirContent.getIcon = function (fileData) { + let iconUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEiSURBVDhPjZHLaoYwFITtK/YJfQlFFAt1VTehvwsR3bgQ8bYQ8b5JnWOi6d+mdODDJOfMJDGGlGmazPd9/oznedxxHG5ZFuZvXde9Hu0vp0sRmvd9/8E4jrxpGg7Zto2wdxHyXbqAYRh4WZYUACEEJxG2W3+doKoqYT+F6wjbLV3Asiy873sKwUlwHfwTYbulC9i2jUJwElwHX/xYYbv1WwDM4HkdvcJ263hGvq7rxTzPtBvAWK2hV9huYXGapgvc+3guAmO1pg1Ao6Sua14UBYGxWtMGtG1L5HlOpGlKyLmsawPwTCBJEuLx+CTkXNa1AXKnKIqIMPwg5FzWtQFZlnHG2EUQBIS6hh5tQBzHx47hheu6hLqGHl0AQ+GfsNNlGF+M+aF9URxgkQAAAABJRU5ErkJggg=="; + if (!fileData) + iconUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAEFSURBVDhPvZI7CsJAFEVDcANuwcrOxsZKd+ASxBi1cTMWWYBgLTb2ugNBsBTBD/5/KYwO+nzvZSaYGExsvHDIZObeO5MhWlC5dhrk8HcVu3lQyKn4agxKEEQu/UFiWrAQCMHS4kig+WH3PqB5JLpEyALYmD7eSoL4SwVOhhWokiDk56CSPcryggo5w0wkMuoqzHCdlX2IpQGwrwJc6rDqp/wFk04SxK4Jt7kB94XB5seqArDDwLHGIYbGWxPIL6Ouxq0EF/AOZ2k+oRnfn2uTC6ncmZcZ8suoK6+Agoca705GOjo91cmI7wV4gWRWQe9T6HLx6ASdKKRAt2gyPnq8PzRamvYCpw6whDcmQQIAAAAASUVORK5CYII="; + else if (fileData.lstat.isDirectory()) { + iconUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADpSURBVDhP1ZLNDYJAEIUJsQFbsAEvFmArIGI9HijABrx5twQTE4/GRDT+4C8H0Q2OM8MucXEjHPUlX4DlvbfDBus3JBbtAAEDgVVFAs1pPPqA1pHyEiELYOdpvJUU0UsFLpoKVEkR8nNQKZ62+IUKJZNmKTKayWS4LR0NsXYBDl2Aaw8244ZeMB/WQUR9uIcuPFYum9NNByDCwMnnEEP3ew/IL6OZZoMaF/AOF2k+oxmfn1uPC6k8CR2G/DKaKS+g4NHn3clIo9NVTUZ8L8ADJLMK5p9Ch4ujEzSRocAOaLE6drU/9B9kWS8Dg5sSqHCbFwAAAABJRU5ErkJggg=="; + } + else { + console.log("fileData:", fileData); + if (fileData.mime?.includes("image/")) { + iconUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHxSURBVDhPpZA9SFtRGIYPgRKHTsJdukXomtHBjEohQwetmEySDpGmODRCf7wGLKUEhSrBUj2kJVZp0WKpdpFaEoz4kzjUItqtHQrSQU00ShJMvPft+T6T6w8UWvvBw3vOufd57+GKcDisK+QJfZWs0HdmzVx4rlwRCoUkqmNWksZQm7N7WtNZda2GXBEMBrng8eIhehb+kuQBF5Ar/H4/F9z7lEHX3N45gp/3LR7EcxhfzbD4anmHk1zh9Xq5oD+5i1gqg83tEidJq2sjML/bOR8lcvi1f8RiNckVbrdbmqaJ6Er23AtDyW2WzfUrnKPrRUTWjjC8eYzhjWOQQ65wuVxc8GbjEONfc3APNSC6vIXBhQzi31ZY/vIjjefpA9ye8WLup4H4lvqPyiFXOJ1OaRgGXqazLHvGmtAcq8eNkWsIz2ctfFMtuD+ro2O6nUvIIVc4HA5ZLpfRNtYI37ub6PjQCv/7FrS9brRKfFO3WH6afIHexDMuIadOuUKr1WSpVELnZCsC6oqBj56TnKH0cEn/UhSDqbcWT+YjIEfTNClq7HZZLBa5QI8HFHfRrdATp/BNFgcwkIpZkEOusNlsslAocMGfeDh7h0u61Qci6QkuIIdcoUbm83k++BfIIZcK9MriMpArriquXxJy/2eE+A1jEWc14ZrA1gAAAABJRU5ErkJggg=="; + } + else if (fileData.mime?.includes("html")) { + iconUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAFZSURBVDhPnZO/L0NRFMf7z0hM/oBGLEwkBmmZTd2EiUTs8sLQxcBAYtBEJBY/BjPVSJlodDA1lHQoIXQ58jnJ97X3ig69ybe3755zPufHuy9jA6xM7+KgVH209dOHVGu75674WQogPOBUf2lZo/Vpb+2PdOdM5xK+c4vFLoQ/5ae2G6lk9jBvE3tZG1q6tFyx7mfY0ftXJ60qhfBDlqSy7MF3zQs3EIDj2EZihZNNO7r/cYA0M7/qfg6gXBxZz99lh2W3Xz0QIBUBkP4AKBUDwfu1xCZLZzZ1ULWV62mHqbp/AfSLgZIXjm88uwAEAh3ZGu4PYA4KFkADjQEMPAAoC6XSNy0AAawhqs2dSscBo+O5LoAhkokZUDbZCEYEaJeoNgDoNQLR0GiHrASz9wVc3dZ8MLpIyh4HSlQcALhVumm6yohMsbCTMADwgQCRmHCvcI4VvIXBl9kvGykjPhc1ZOcAAAAASUVORK5CYII="; + } + else if (fileData.mime?.includes("video")) { + iconUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAF4SURBVDhPfZJXjsJQDEWzQSAQCL2F0EPbEL2GvgTEQmAnnrmWjN9EaCydz3PvsxMLM5lM6H6/03g8ptvtxlyvV+ZyuTDn85npdDp0Op2o2WwSyxgEQB6NRh85KkICrVaL5UajoQEiDofDf+UwDMn3fTocDuR53t8AyEEQML1ej8FzgdkK6vU61Wo1DRgMBtzY7/e/tgK0Asi73Y4qlYoGoBUyWkXGPJ/Pj7zf7xmI5XKZSqWSBogoF4Yk836/WUQrgLzdbqlQKGhAt9v97CpPjg5kiMVikeVcLqcB7XabjscjH0p2lXm9XiyC9XpN+XyeVqsVZbNZDYCIz4MLy66Yx+PB4mazYVlE13Upk8loAES5sOxrtooMIC6XS3IcRwMggmq1ylfGhWVXPNlsTafTLKdSKQ3AT2FeONqKRrBYLFiezWZk27YGmK2mbIpgPp9TMplkOZFIaABkHApP/tYK0CrN0+mU4vG4Bpi7fmsVWUQQi8V+AyzrB2TCUiLZmdTqAAAAAElFTkSuQmCC"; + } + else if (fileData.mime?.includes("audio/")) { + iconUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGkSURBVDhPYxicYNL2aJs5RxKn928Jn9u3NWIeAofNm3M0cXrftsgoqFLsoHSxbl77PrP/5csN/pctRcLLDP637TH9X7xIbwlUKXaQOkM2o2S1/P/k6aIubr0MgjAcPpFNKXOOzP+MmdLzoEqxg4zZ8hlla5X+Z8+TNYQKgUHIFAHu7Lny/zNnyxFnQNZceTOoEBgkz5YSpI8BqXOkYsrWKWIYAAKZc2T/Z86Smw/looKKhSbOOZPU03JnaCwpW60KNEAabEDzGke5/Ck6aVkT1AqKF6v9z54jPxusARlUzDeKalhl/n7iXuf/bRts/5cv1v+ft0BVt3KuvkjlQuOTPTvs//ftcPrftM7sf9FczelQbQiQM0lz2aRD9v/nnvH+P/2o2/+6lcb/E1tlNZO6FbWb1pv+n3Hc/f+c097/+/ba/geqnQvVhgAF09WLq5fp/+3da/G/YzswsczVflI0T0kmb6aibNFs7ScgMZBc9XL9v4UztIqg2hCg5n8NY/Ec7cyCaZrT86ZqTC9ZqGMBlWIAsUFiILnCmdqZILVQqQEHDAwA2QbKhGiFnzoAAAAASUVORK5CYII="; + } + } + return ``; +} + +function simpleHTTP (staticPath, req, res, dirContentFlag = false) { + const curpath = toNormalText(req.originalUrl + .replace(/\?.{0,}/gmi)); + + const pathToFile = path.join(staticPath, curpath); + console.log("curpath:", curpath); + console.log("pathToFile:", pathToFile); + + let finalPathToFile; + let mimetype = "text/html"; + let isDir = false; + try { + const lstat = fs.lstatSync(pathToFile); + if (lstat.isFile()) { + mimetype = mime.getType(pathToFile); + finalPathToFile = pathToFile + } else if (lstat.isDirectory()) { + finalPathToFile = path.join(pathToFile, "index.html"); + isDir = true; + } + fs.readFile(finalPathToFile, (err, data) => { + if (err) { + if (isDir && dirContentFlag) { + try { + return res.send(renderDirContent(pathToFile, curpath)); + } catch (e) { + return res.status(404).send(req.st404); + /*if (e.code === "EACCES") + return res.status(404).send(req.st404); + return res.status(500).send(req.st500);*/ + } + } + return res.status(404).send(req.st404); + } + res.set("Content-Type", mimetype); + res.send(data); + }); + } catch (e) { + return res.status(404).send(req.st404); + } +} + +module.exports = class HTTPServer extends Component { + constructor (adr, syntaxTree) { + super(adr, syntaxTree); + this.routelist = []; + } + + domainAction (context, domain, callCommand, req, res, next) { + logger("DOMAIN ACT:", this.syntax); + if (!(context instanceof HTTPServer)) + throw new SyntaxError("You can't call revHTTP domain from unsupported type. Supported types: revHTTP, simpleHTTP, redirectHTTP"); + switch (callCommand) { + case "proxy": + return fetchProxy(req, res, this.syntax.target); + case "auth": + const authContext = context.syntax.auth === undefined ? this : context; + return authPage(authContext, req, res, next); + case "redirect": + return redirectPage(this.syntax.target, req, res); + case "shttp": + return simpleHTTP(this.syntax.target, req, res, !!this.syntax.showDir); + } + } + + preinit () { + this.app = express(); + + this.app.use('/*', async (req, res, next) => { // Myself body parser :) + bodyHand(req, res, { + limit: 9999999999, + cache: false, + encoding: 'base64' + }, (err, body) => { + if (!err) { + req.body = Buffer.from(body, 'base64'); + } else { + req.body = undefined; + } + next(); + }); + }); + + this.app.use((req, res, next) => { + const currentDomain = req.hostname; + const isNotDomained = !this.syntax.domains[currentDomain]; + if (!isNotDomained) { + return this + .callDomain(currentDomain, this.syntax.domains[currentDomain]) + .argv("auth", req, res, next); + } + authPage(this, req, res, next) + }); + + this.app.use((req, res, next) => { + req.st403 = "

403: Permission denied


Permission denied.

"; + req.st404 = "

404: Page not finded


Please, recheck the URL address and try again.

"; + req.st500 = "500: Internal Server Error

Error:


Error catched. Check logs

"; + next(); + }); + + this.app.use(cookieParser()); + + this.routelist.forEach(route => { + this.app.use(route); + }); + } + + postinit (startHandler) { + if (!this.syntax.encryption) { + this.server = http.createServer(this.app); + } else { + this.server = https.createServer({ + cert : fs.readFileSync(this.syntax.encryption.public, 'utf-8'), + key : fs.readFileSync(this.syntax.encryption.private, 'utf-8') + }, this.app); + } + + this.server.listen(this.adr.port, this.adr.address, startHandler); + } + + stop () { + this.server.close(() => logger("Server stopped")); + } +} + +module.exports.fetchProxy = fetchProxy; +module.exports.authPage = authPage; +module.exports.redirectPage = redirectPage; +module.exports.simpleHTTP = simpleHTTP; diff --git a/components/http-redirector.js b/components/http-redirector.js new file mode 100644 index 0000000..3b92367 --- /dev/null +++ b/components/http-redirector.js @@ -0,0 +1,40 @@ +const HTTPServer = require("./_http"); +const { redirectPage } = HTTPServer; + +function logger (...p) { + return; + console.log("[DEBUG.http.redirector]:", ...p); +} + +module.exports = class HTTPRedirector extends HTTPServer { + constructor (adr, syntaxTree) { + super(adr, syntaxTree); + } + + run () { + this.preinit(); + const redirectTo = this.syntax.target ?? "https://example.org"; + + this.app.use("*", async (req, res) => { + logger("originalUrl:>", req.originalUrl); + const currentDomain = req.hostname; + logger("hostname:", currentDomain); + const isNotDomained = !this.syntax.domains[currentDomain]; + if (!isNotDomained) { + return this + .callDomain(currentDomain, this.syntax.domains[currentDomain]) + .argv("redirect", req, res); + } + return redirectPage(redirectTo, req, res); + }); + + this.postinit((err) => { + if (err) { + throw err; + } + console.log(`HTTP redirect server listened at ${ + this.adr.address + }:${this.adr.port}`); + }); + } +}; diff --git a/components/http-reverse.js b/components/http-reverse.js index d2dc378..d18e63a 100644 --- a/components/http-reverse.js +++ b/components/http-reverse.js @@ -1,127 +1,20 @@ -const Component = require("./_component"); - -const https = require("https"); -const express = require("express"); -const bodyParser = require("body-parser"); -const bodyHand = require('body'); -const url = require('url'); -const path = require('path'); -const fs = require('fs'); -const fetch = require('node-fetch'); -const cookieParser = require('cookie-parser') +const HTTPServer = require("./_http"); +const { fetchProxy } = HTTPServer; function logger (...p) { - return; - console.log("[DEBUG.main]:", ...p); + //return; + console.log("[DEBUG.http.reverse]:", ...p); } -function fetchProxy (req, res, reverseTo) { - const params = { method: req.method.toUpperCase(), headers: Object.assign({}, req.headers), redirect: "follow", follow: 20, compress: false }; - delete params.headers.host; - delete params.headers.referer; - if (!["GET", "HEAD"].includes(params.method)) - params.body = req.body; - - const reqAddress = url.resolve(reverseTo, path.join("/", req.originalUrl)); - fetch(reqAddress, params) - .then(async result => { - const content = await result.arrayBuffer(); - //logger("result:", result); - //logger("result.arrayBuffer():", content); - //logger("result.Buffer:", Buffer.from(content).toString()); - - const headers = Object.fromEntries(result.headers); - - //logger("headers Headers", result.headers); - //logger("headers Headers.raw()", result.headers.raw()); - logger("headers object", headers); - - Object.entries(headers).forEach(([header, value]) => { - res.set(header, value); - }); - - const finalContent = Buffer.from(content); - res.status(result.status).send(finalContent); - }) - .catch(err => { - console.error(err); - res.status(500).send("Error

Error:


Error catched. Check logs

"); - }); -} - -function authPage (self, req, res, next) { - if (self.syntax.auth) { - const ACCESS = "Basic " + Buffer.from(`${self.syntax.auth.login}:${self.syntax.auth.password}`).toString('base64'); - if (req.headers['authorization'] === ACCESS) { - return next(); - } - else { - if (self.syntax.auth.mode === "strict" && req.headers['authorization'] !== undefined) { - return res.status(403).send("

Permission denied


Permission denied.

"); - } - res.set("WWW-Authenticate", "Basic"); - return res.status(401).send(); - } - } - next(); -} - -module.exports = class HTTPReverse extends Component { +module.exports = class HTTPReverse extends HTTPServer { constructor (adr, syntaxTree) { super(adr, syntaxTree); } - domainAction (context, domain, callCommand, req, res, next) { - logger("DOMAIN ACT:", this.syntax); - if (!(context instanceof HTTPReverse)) - throw new SyntaxError("You can't call revHTTP domain from unsupported type. Supported types: revHTTP, simpleHTTP, redirectHTTP"); - switch (callCommand) { - case "proxy": - return fetchProxy(req, res, this.syntax.target); - case "auth": - const authContext = context.syntax.auth === undefined ? this : context; - return authPage(authContext, req, res); - } - } - run () { - this.app = express(); - - const address = this.adr.address ?? "127.0.0.1"; + this.preinit(); const reverseTo = this.syntax.target ?? "https://example.org"; - this.app.use('/*', async (req, res, next) => { // Myself body parser :) - bodyHand(req, res, { - limit: 9999999999, - cache: false, - encoding: 'base64' - }, (err, body) => { - if (!err) { - req.body = Buffer.from(body, 'base64'); - } else { - req.body = undefined; - } - next(); - }); - }); - - this.app.use((req, res, next) => { - const currentDomain = req.hostname; - const isNotDomained = !this.syntax.domains[currentDomain]; - if (!isNotDomained) { - return this - .callDomain(currentDomain, this.syntax.domains[currentDomain]) - .argv("auth", req, res, next); - } - authPage(this, req, res, next) - }); - - this.app.use(cookieParser()); - - this.syntax.routelist.forEach(route => { - this.app.use(route); - }); - this.app.use("*", async (req, res) => { logger("REQ HEADERS:>", req.headers); logger("originalUrl:>", req.originalUrl); @@ -136,18 +29,13 @@ module.exports = class HTTPReverse extends Component { return fetchProxy(req, res, reverseTo); }); - const server = !this.syntax.encryption ? this.app : https.createServer({ - cert : fs.readFileSync(this.syntax.encryption.public, 'utf-8'), - key : fs.readFileSync(this.syntax.encryption.private, 'utf-8') - }, this.app); - - server.listen(this.adr.port, address, (err) => { + this.postinit((err) => { if (err) { throw err; } console.log(`HTTP reverse proxy server listened at ${ - address + this.adr.address }:${this.adr.port}`); - }) + }); } }; diff --git a/components/http-simple.js b/components/http-simple.js new file mode 100644 index 0000000..99a9710 --- /dev/null +++ b/components/http-simple.js @@ -0,0 +1,41 @@ +const HTTPServer = require("./_http"); +const mime = require("mime"); +const { simpleHTTP } = HTTPServer; + +function logger (...p) { + return; + console.log("[DEBUG.http.simple]:", ...p); +} + +module.exports = class HTTPRedirector extends HTTPServer { + constructor (adr, syntaxTree) { + super(adr, syntaxTree); + } + + run () { + this.preinit(); + const redirectTo = this.syntax.target ?? "https://example.org"; + + this.app.use("*", async (req, res) => { + logger("originalUrl:>", req.originalUrl); + const currentDomain = req.hostname; + logger("hostname:", currentDomain); + const isNotDomained = !this.syntax.domains[currentDomain]; + if (!isNotDomained) { + return this + .callDomain(currentDomain, this.syntax.domains[currentDomain]) + .argv("shttp", req, res); + } + return simpleHTTP(redirectTo, req, res, !!this.syntax.showDir); + }); + + this.postinit((err) => { + if (err) { + throw err; + } + console.log(`Simple HTTP server listened at ${ + this.adr.address + }:${this.adr.port}`); + }); + } +}; diff --git a/components/index.js b/components/index.js index 14c0914..470847c 100644 --- a/components/index.js +++ b/components/index.js @@ -1,5 +1,5 @@ module.exports = { "revHTTP": require("./http-reverse"), - "simpleHTTP": require("./http-reverse"), - "redirectHTTP": require("./http-reverse"), + "simpleHTTP": require("./http-simple"), + "redirectHTTP": require("./http-redirector"), }; diff --git a/components/tcp.js b/components/tcp.js new file mode 100644 index 0000000..2563a11 --- /dev/null +++ b/components/tcp.js @@ -0,0 +1,3 @@ +const Component = require("./_component"); + +class TCPServer extends Component {} diff --git a/modules/index.js b/module-api.js similarity index 81% rename from modules/index.js rename to module-api.js index df8a729..2efda91 100644 --- a/modules/index.js +++ b/module-api.js @@ -1,3 +1,4 @@ +const EventEmitter = require("events"); // Func class OperatorStorage extends Array { @@ -39,10 +40,18 @@ class Operator { } } -module.exports = class NetHelperModule { +class NetHelperModule extends EventEmitter { constructor () { + super(); this.operators = new OperatorStorage(); this.statics = { Operator }; + this.serverType = null; + } + + bind (typeInstance) { + this.serverType = typeInstance; } } + +module.exports = { NetHelperModule }; diff --git a/modules/certbot.js b/modules/certbot.js new file mode 100644 index 0000000..acc2bb1 --- /dev/null +++ b/modules/certbot.js @@ -0,0 +1,33 @@ +const express = require("express"); +const { NetHelperModule } = global.moduleApi; + +const HTTPReverse = require("../components/http-reverse"); + +module.exports = class CustomAuthPage extends NetHelperModule { + constructor () { + super(); + this.router = express.Router(); + + this.certbot = new this.statics.Operator("certbot", (argv) => { + return argv.length === 2; + }, (argv, self, syntaxTree) => { + if (![HTTPReverse].includes(syntaxTree.Type)) { + throw new SyntaxError("Unsupported type of server"); + } + const [ pathToFile, secretKey ] = argv; + const router = this.router; + + router.get(pathToFile, (req, res, next) => { + res.send(secretKey); + }); + }); + + this.operators.push(this.certbot); + } + + bind (type) { + super.bind(type); + this.serverType.routelist.push(this.router); + } +}; + diff --git a/modules/example.js b/modules/example.js index c910d84..3773ec1 100644 --- a/modules/example.js +++ b/modules/example.js @@ -1,4 +1,4 @@ -const NetHelperModule = require("./"); +const { NetHelperModule } = global.moduleApi; module.exports = class ExampleMdoule extends NetHelperModule { constructor () { diff --git a/modules/example_custom_auth.js b/modules/example_custom_auth.js index 1aa6723..005fb65 100644 --- a/modules/example_custom_auth.js +++ b/modules/example_custom_auth.js @@ -1,20 +1,21 @@ const express = require("express"); -const NetHelperModule = require("./"); +const { NetHelperModule } = global.moduleApi; const HTTPReverse = require("../components/http-reverse"); module.exports = class CustomAuthPage extends NetHelperModule { constructor () { super(); + this.router = express.Router(); - const cauth = new this.statics.Operator("cauth", (argv) => { + this.cauth = new this.statics.Operator("cauth", (argv) => { return argv.length === 2; }, (argv, self, syntaxTree) => { if (![HTTPReverse].includes(syntaxTree.Type)) { throw new SyntaxError("Unsupported type of server"); } const [ login, password ] = argv; - const router = express.Router(); + const router = this.router; router.get("/auth/:redirectTo", (req, res, next) => { if (req.cookies.auth === login + ":" + password) @@ -34,10 +35,17 @@ module.exports = class CustomAuthPage extends NetHelperModule { return next(); res.send('

Needs auth


Логин:

Пароль:

'); }); - - syntaxTree.routelist.push(router); }); - this.operators.push(cauth); + this.operators.push(this.cauth); + + this.emit("runned", () => { + this.serverType.app.use(router); + }); + } + + bind (type) { + super.bind(type); + this.serverType.routelist.push(this.router); } }; diff --git a/package-lock.json b/package-lock.json index ba008a3..1cccbfa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "body-parser": "^1.20.3", "cookie-parser": "^1.4.7", "express": "^4.21.2", + "mime": "^3.0.0", "node-fetch": "^2.6.7", "sse": "^0.0.8" }, @@ -683,14 +684,14 @@ } }, "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "bin": { "mime": "cli.js" }, "engines": { - "node": ">=4" + "node": ">=10.0.0" } }, "node_modules/mime-db": { @@ -1009,6 +1010,17 @@ "node": ">= 0.8" } }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/send/node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index cd2feb6..ffba990 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "body-parser": "^1.20.3", "cookie-parser": "^1.4.7", "express": "^4.21.2", + "mime": "^3.0.0", "node-fetch": "^2.6.7", "sse": "^0.0.8" }, diff --git a/routemap-parser.js b/routemap-parser.js index b594788..3bc7593 100644 --- a/routemap-parser.js +++ b/routemap-parser.js @@ -1,6 +1,8 @@ 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; @@ -13,7 +15,7 @@ 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(), routelist: [] }; + const syntaxTree = { firewall: false, encryption: {}, modules: new Set(), showDir: false }; const moduleOpsQueue = new Array(); for (let instruction of instructions) { @@ -58,6 +60,9 @@ function readInstructions (instructions, fullCode, forDomains=false) { password: instructionParts.slice(3,).join(" ") } : null; break; + case "showdir": + syntaxTree.showDir = true; + break; case "firewall": break; case "import": @@ -112,10 +117,8 @@ function readInstructions (instructions, fullCode, forDomains=false) { syntaxTree.modules = Array.from(syntaxTree.modules); // Object.assign needs replace to object which give only-read access - // Except: routelist moduleOpsQueue.forEach(h => { const st = Object.assign({}, syntaxTree); - st.routelist = syntaxTree.routelist; return h(st); }); @@ -123,7 +126,7 @@ function readInstructions (instructions, fullCode, forDomains=false) { } function logger (...p) { - //return; + return; console.debug("[DEBUG.routemap]:", ...p); } @@ -150,3 +153,11 @@ module.exports.parse = function () { 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; +} diff --git a/routemap.txt b/routemap.txt index cc19486..162186e 100644 --- a/routemap.txt +++ b/routemap.txt @@ -16,8 +16,23 @@ import example, example_custom_auth; type revHTTP; target https://google.com; - #auth loopback admin qwerty; - cauth admin 123; + auth loopback admin qwerty; + #cauth admin 123; cert public ./tests/pub.crt; cert private ./tests/priv.key; } + +127.0.0.1:9999 { + type redirectHTTP; + target https://duckduckgo.com; + domain localhost { + type redirectHTTP; + target https://google.com; + } +} + +127.0.0.1:8081 { + type simpleHTTP; + target /; + #showdir; +} diff --git a/server.js b/server.js index f2bdebe..37b2311 100644 --- a/server.js +++ b/server.js @@ -1,11 +1,47 @@ 'use strict'; +global.moduleApi = require("./module-api"); +const EventEmitter = require("events"); const rtmap = require("./routemap-parser"); -const syntaxTree = rtmap.parse(); -for (let faddress in syntaxTree) { - const [address, port] = faddress.split(/:/gmi); - const localSyntaxTree = syntaxTree[faddress]; - const instance = new localSyntaxTree.Type({ address, port }, localSyntaxTree); - instance.run(); +class ServersOrchestry extends EventEmitter { + constructor () { + super(); + this.on("update-routemap", () => { + console.log("[Update routemap]: routemap.txt updated, reloading servers..."); + this.stop(); + this.run(); + }); + this.instances = new Array(); + } + + run () { + this.hash = rtmap.hash(); + const syntaxTree = rtmap.parse(); + for (let faddress in syntaxTree) { + const [address, port] = faddress.split(/:/gmi); + const localSyntaxTree = syntaxTree[faddress]; + const instance = new localSyntaxTree.Type({ address, port }, localSyntaxTree); + localSyntaxTree.modules.forEach(module => module.bind(instance)); + instance.run(); + this.instances.push(instance); + localSyntaxTree.modules.forEach(module => module.emit("runned")); + } + } + + stop () { + for (let instance of this.instances) { + instance.stop(); + } + this.instances = []; + } } + +const orchestry = new ServersOrchestry(); + +orchestry.run(); + +setInterval(() => { + if (rtmap.hash() !== orchestry.hash) + orchestry.emit("update-routemap"); +}, 100); diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..47e9b08 --- /dev/null +++ b/utils.js @@ -0,0 +1,37 @@ +function getNormalSizeString (val, sliceNumber=2) { + val = +val; + if (Number.isNaN(val)) + return "NaN"; + if (!Number.isFinite(val)) + return val >= 0 ? "∞" : "-∞"; + if (!(""+val).includes(".")) + return ""+val + `.${"0".repeat(sliceNumber)}`; + let [ + intVal, pointVal + ] = (""+val).split("."); + if (pointVal.length < sliceNumber) + pointVal = pointVal + "0" + .repeat(sliceNumber - pointVal.length); + return `${intVal}.${pointVal.slice(0, sliceNumber)}`; +} + +function getStrSize (sizeBytes) { + if (sizeBytes < 512) + return sizeBytes + " B"; + else { + //const failed = getNormalSizeString(0); + + const kb = getNormalSizeString(sizeBytes / 1024); + const mb = getNormalSizeString(sizeBytes / 1048576); + const gb = getNormalSizeString(sizeBytes / 1073741824); + + if (gb.split(".")[0] !== "0") + return gb + " GB"; + if (mb.split(".")[0] !== "0") + return mb + " MB"; + return kb + " KB"; + } +} + +module.exports.getNormalSizeString = getNormalSizeString; +module.exports.getStrSize = getStrSize;