net-helper/components/_http.js
2025-01-23 19:31:47 +03:00

285 lines
12 KiB
JavaScript

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 {
static type () {
return null;
}
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;