Compare commits

...

2 Commits

Author SHA1 Message Date
02a032023f . 2025-01-23 16:22:26 +03:00
d17b53d049 Cosmetic and functionally fixes for module root API 2025-01-19 05:18:50 +03:00
17 changed files with 571 additions and 155 deletions

View File

@ -7,6 +7,9 @@ module.exports = class Component {
// Run server
run () {}
// Stop server
stop () {}
// Working with domains
domainAction (context, domain) {}

280
components/_http.js Normal file
View File

@ -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 `<html><head><title>Content of dir: ${displayedDir}</title></head><body>
<h3>Content of dir: ${displayedDir}</h3><hr/>
<table>
<tr>
<th scope="col"></th>
<th scope="col">Filename</th>
<th scope="col">Size</th>
<th scope="col">Checksum</th>
</tr>
${renderDirContent.genTRs(files)}
</table>
</body></html>`;
}
renderDirContent.genTRs = function (files) {
const allFiles = files.map(f => `<tr><td>${renderDirContent.getIcon(f)}</td><td><a href="${f.path}">${f.name + (f.lstat.isDirectory() ? "/" : "")}</a></td><td>${f.sizeReadable}</td><td>${f.hash}</td></tr>`);
return [`<tr><td>${renderDirContent.getIcon(null)}</td><td><a href="${path.join(files.folder, "..")}">../</a></td><td></td><td></td></tr>`].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 `<img src=${JSON.stringify(iconUrl)} />`;
}
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 = "<h1>403: Permission denied</h1><hr/><p>Permission denied.</p>";
req.st404 = "<h1>404: Page not finded</h1><hr/><p>Please, recheck the URL address and try again.</p>";
req.st500 = "<head><title>500: Internal Server Error</title></head><body><h1>Error:</h1><hr/><p>Error catched. Check logs</p></body>";
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;

View File

@ -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}`);
});
}
};

View File

@ -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("<head><title>Error</title></head><body><h1>Error:</h1><hr/><p>Error catched. Check logs</p></body>");
});
}
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("<h1>Permission denied</h1><hr/><p>Permission denied.</p>");
}
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}`);
})
});
}
};

41
components/http-simple.js Normal file
View File

@ -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}`);
});
}
};

View File

@ -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"),
};

3
components/tcp.js Normal file
View File

@ -0,0 +1,3 @@
const Component = require("./_component");
class TCPServer extends Component {}

View File

@ -1,8 +1,9 @@
const EventEmitter = require("events");
// Func
class OperatorsStorage extends Array {
constructor (...p) {
super(...p);
class OperatorStorage extends Array {
constructor () {
super();
this._indexObject = {};
this._indexObjectIndexOf = {};
}
@ -15,7 +16,7 @@ class OperatorsStorage extends Array {
}
}
else
throw new TypeError("Into OperatorsStorage you can add only Operator's instances");
throw new TypeError("Into OperatorStorage you can add only Operator's instances");
}
includes (item) {
@ -23,7 +24,7 @@ class OperatorsStorage extends Array {
return this._indexObject[item.operator] !== undefined;
}
else
throw new TypeError("Into OperatorsStorage storage only Operator type");
throw new TypeError("Into OperatorStorage storage only Operator type");
}
}
@ -39,11 +40,18 @@ class Operator {
}
}
module.exports = class NetHelperModule {
class NetHelperModule extends EventEmitter {
constructor () {
this.operators = new OperatorsStorage();
super();
this.operators = new OperatorStorage();
this.statics = new Object();
this.statics.Operator = Operator;
this.statics = { Operator };
this.serverType = null;
}
bind (typeInstance) {
this.serverType = typeInstance;
}
}
module.exports = { NetHelperModule };

33
modules/certbot.js Normal file
View File

@ -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);
}
};

View File

@ -1,4 +1,4 @@
const NetHelperModule = require("./");
const { NetHelperModule } = global.moduleApi;
module.exports = class ExampleMdoule extends NetHelperModule {
constructor () {

View File

@ -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('<form action=\"/auth/'+ Buffer.from(req.originalUrl).toString("hex") +'\"><h4>Needs auth</h4><hr/><p>Логин: <input type="text" size="40" name="login"></p><p>Пароль: <input type="password" size="40" name="password"></p><p><input type="submit"></p></form>');
});
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);
}
};

20
package-lock.json generated
View File

@ -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",

View File

@ -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"
},

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);

37
utils.js Normal file
View File

@ -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;