orangecircle/index.js

341 lines
14 KiB
JavaScript
Raw Normal View History

2023-08-11 03:23:27 +02:00
var https = require("https");
var os = require("os");
var http = require("http");
var url = require("url");
var fs = require("fs");
var path = require("path");
var net = require("net");
var version = "UNKNOWN";
try {
version = JSON.parse(fs.readFileSync(__dirname + "/mod.info")).version;
} catch (ex) {
// Can't determine version
}
2023-12-31 21:39:21 +01:00
var configJSONS = {};
try {
configJSONS = JSON.parse(fs.readFileSync(__dirname + "/../../../config.json")); // Read configuration JSON
} catch(ex) {
//OrangeCircle will not care about configJSONS in SVR.JS 3.x and newer
//SVR.JS 2.x and older will fail to start with broken configuration file anyway...
}
2023-08-11 03:23:27 +02:00
var scgiConf = {};
try {
scgiConf = JSON.parse(fs.readFileSync(__dirname + "/../../../orangecircle-config.json"));
} catch (ex) {
// Use defaults
}
// Load default configuration
if (scgiConf.path === undefined) scgiConf.path = "/scgi";
scgiConf.path = scgiConf.path.replace(/([^\/])\/+$/, "$1");
if (scgiConf.host === undefined) scgiConf.host = "localhost";
if (scgiConf.port === undefined) scgiConf.port = 4000;
2023-12-31 21:39:21 +01:00
var disableModExposeSupported = process.versions.svrjs && process.versions.svrjs.match(/^(?:Nightly-|(?:[4-9]|[123][0-9])[0-9]*\.|3\.(?:[1-9][0-9]+\.|9\.(?:[1-9])|4\.(?:(?:[3-9]|[12][0-9])[0-9]+|29)))/i);
2023-08-11 03:23:27 +02:00
function Mod() {}
2024-02-07 01:33:31 +01:00
Mod.prototype.callback = function (req, res, serverconsole, responseEnd, href, ext, uobject, search, defaultpage, users, page404, head, foot, fd, elseCallback, configJSON, callServerError, getCustomHeaders, origHref, redirect, parsePostData, authUser) {
2023-08-11 03:23:27 +02:00
return function () {
if (!configJSON) {
configJSON = configJSONS;
}
2024-02-07 01:33:31 +01:00
function checkIfThereIsA401Rule() {
var actually401 = false;
function createRegex(regex) {
var regexObj = regex.split("/");
if (regexObj.length == 0) throw new Error("Invalid regex!");
var modifiers = regexObj.pop();
regexObj.shift();
var searchString = regexObj.join("/");
return new RegExp(searchString, modifiers);
}
if(configJSON.nonStandardCodes) {
configJSON.nonStandardCodes.every(function (nonscode) {
if (nonscode.scode == 401) {
if (nonscode.regex && (req.url.match(createRegex(nonscode.regex)) || href.match(createRegex(nonscode.regex)))) {
actually401 = true;
return true;
} else if (nonscode.url && (nonStandardCodes[i].url == href || (os.platform() == "win32" && nonStandardCodes[i].url.toLowerCase() == href.toLowerCase()))) {
actually401 = true;
return true;
}
}
return false;
});
}
return actually401;
}
2023-08-11 03:23:27 +02:00
if (!getCustomHeaders) {
var bheaders = JSON.parse(JSON.stringify(configJSON.customHeaders));
} else {
var bheaders = getCustomHeaders();
}
bheaders["Content-Type"] = "text/html"; // HTML output
if (!getCustomHeaders) {
bheaders["Server"] =
"SVR.JS/" +
configJSON.version +
" (" +
os.platform()[0].toUpperCase() +
os.platform().slice(1) +
")"; // Add Server header
}
var abheaders = JSON.parse(JSON.stringify(bheaders));
2024-02-16 02:37:49 +01:00
var socket = {};
2023-08-11 03:23:27 +02:00
function executeSCGI(req, res, dh, nEnv) {
// Function to execute SCGI scripts
var env = JSON.parse(JSON.stringify(process.env));
var nEnvKeys = Object.keys(nEnv);
for (var i = 0; i < nEnvKeys.length; i++) {
env[nEnvKeys[i]] = nEnv[nEnvKeys[i]];
}
var buffer = "";
var headerendline = -1;
var cned = false;
var dataHandler = function (data) {
2024-02-16 02:37:49 +01:00
if (!cned) buffer += data.toString("latin1");
2023-08-11 03:23:27 +02:00
var m = null;
2023-08-26 04:35:07 +02:00
if (!cned) m = buffer.match(/(?:\r\n\r\n|\n\r\n\r|\n\n|\r\r)/);
2023-08-11 03:23:27 +02:00
if (!cned && m) {
cned = true;
eol = m[0];
headerendline = m.index;
2023-08-26 04:35:07 +02:00
var bheaders = buffer.substr(0, headerendline).split(/(?:\r\n|\n\r|\n|\r)/);
2023-08-11 03:23:27 +02:00
var bheaderso = {};
if (dh) bheaderso = dh;
var code = 200;
var msg = "OK";
if (bheaders[0].indexOf("HTTP/") == 0) {
var heada = bheaders.shift();
var hso = heada.split(" ");
code = hso[1];
if (hso[2] !== undefined) msg = heada.split(" ").splice(2).join(" ");
} else if (bheaders[0].indexOf(":") == -1) {
var heada = bheaders.shift();
var hso = heada.split(" ");
if (hso[0].match(/^[0-9]{3}$/)) {
code = hso[0];
if (hso[1] !== undefined) msg = heada.split(" ").splice(1).join(" ");
}
}
2024-02-11 15:06:34 +01:00
var hasLocation = false;
2023-08-11 03:23:27 +02:00
for (var i = 0; i < bheaders.length; i++) {
var headerp = bheaders[i].split(": ");
var headern = headerp.shift();
var headerv = headerp.join(": ");
if (headern.toLowerCase() == "status") {
code = headerv.split(" ")[0];
if (headerv.split(" ")[1] !== undefined) msg = headerv.split(" ").splice(1).join(" ");
} else if (headern.toLowerCase() == "set-cookie") {
if (!bheaderso["Set-Cookie"]) bheaderso["Set-Cookie"] = [];
bheaderso["Set-Cookie"].push(headerv);
} else {
2024-02-11 15:06:34 +01:00
if (headern.toLowerCase() == "location") hasLocation = true;
2023-08-11 03:23:27 +02:00
bheaderso[headern] = headerv;
}
}
2024-02-11 15:06:34 +01:00
if ((code < 300 || code > 399) && hasLocation) {
2023-08-11 03:23:27 +02:00
code = 302;
msg = "Found";
}
try {
res.writeHead(code, msg, bheaderso);
2024-05-03 16:10:57 +02:00
res.write(Buffer.from(buffer.substr(headerendline + eol.length), "latin1"));
2023-08-11 03:23:27 +02:00
} catch (ex) {
2024-02-16 02:37:49 +01:00
socket.removeAllListeners("data");
socket.removeAllListeners("end");
2023-08-11 03:23:27 +02:00
if (!callServerError) {
res.writeHead(500);
res.end(ex.stack);
} else {
callServerError(500, "OrangeCircle/" + version, ex);
}
return;
}
} else {
2024-02-16 02:37:49 +01:00
if (cned && !res.finished) {
res.write(data);
socket.removeListener("data", dataHandler);
socket.pipe(res, {end: false});
}
2023-08-11 03:23:27 +02:00
}
};
2024-02-16 02:37:49 +01:00
socket = net.createConnection({
2023-08-11 03:23:27 +02:00
host: scgiConf.host,
port: scgiConf.port,
}, function () {
socket.on("data", dataHandler);
socket.on("end", function () {
res.end();
});
var envEncoded = "";
var envNames = Object.keys(env);
for (var i = 0; i < envNames.length; i++) {
var envName = String(envNames[i]).replace(/\0/g, "");
var envValue = String(env[envNames[i]]).replace(/\0/g, "");
if(envName == "CONTENT_LENGTH") {
// CONTENT_LENGTH environment variable must be first!!!
envEncoded = envName + "\0" + envValue + "\0" + envEncoded;
} else {
envEncoded += envName + "\0" + envValue + "\0";
}
}
socket.write(envEncoded.length + ":" + envEncoded + ",");
req.pipe(socket, {end: false});
}).on("error", function (error) {
2023-08-12 12:18:52 +02:00
var errorcode = (error.code == "ENOTFOUND" || error.code == "EHOSTUNREACH" || error.code == "ECONNREFUSED") ? 503 : 500;
2023-08-11 03:23:27 +02:00
if (!callServerError) {
2023-08-12 12:18:52 +02:00
res.writeHead(errorcode, {
2023-08-11 03:23:27 +02:00
"Content-Type": "text/html",
"Server": "OrangeCircle/" + version
});
res.end("<html><head></head><body><h1>OrangeCircle Error!</h1><p>Reason: " + error.message + "</p></body></html>");
} else {
2023-08-12 12:18:52 +02:00
callServerError(errorcode, "OrangeCircle/" + version, error);
2023-08-11 03:23:27 +02:00
}
});
}
2024-02-07 01:33:31 +01:00
function executeSCGIWithEnv(a, req, res, pubip, port, software, dh, user) {
2023-08-11 03:23:27 +02:00
// Function to set up environment variables and execute sCGI scripts
var b = href.replace(scgiConf.path,"")
var nEnv = {};
2024-02-07 01:33:31 +01:00
if (typeof user != "undefined") {
if (user !== null) {
if (req.headers.authorization) nEnv["AUTH_TYPE"] = req.headers.authorization.split(" ")[0];
nEnv["REMOTE_USER"] = user;
}
} else if (req.headers.authorization && (typeof checkIfThereIsA401Rule == "undefined" || checkIfThereIsA401Rule())) {
2023-08-11 03:23:27 +02:00
nEnv["AUTH_TYPE"] = req.headers.authorization.split(" ")[0];
if (nEnv["AUTH_TYPE"] == "Basic") {
var remoteCred = req.headers.authorization.split(" ")[1];
if (!remoteCred) {
nEnv["REMOTE_USER"] = "orangecircle_cgi_invalid_user";
} else {
var remoteCredDecoded = Buffer.from(remoteCred, "base64").toString("utf8");
nEnv["REMOTE_USER"] = remoteCredDecoded.split(":")[0];
}
} else {
nEnv["REMOTE_USER"] = "svrjs_this_property_is_not_yet_supported_by_orangecircle_cgi";
}
}
nEnv["QUERY_STRING"] = req.url.split("?")[1];
if (nEnv["QUERY_STRING"] == undefined || nEnv["QUERY_STRING"] == "undefined") nEnv["QUERY_STRING"] = "";
nEnv["SERVER_SOFTWARE"] = software;
nEnv["SERVER_PROTOCOL"] = "HTTP/" + req.httpVersion;
if (pubip && (port !== null && port !== undefined)) {
nEnv["SERVER_PORT"] = port;
nEnv["SERVER_ADDR"] = pubip.replace(/^::ffff:/i, "");
if (nEnv["SERVER_ADDR"].indexOf(":") != -1) nEnv["SERVER_ADDR"] = "[" + nEnv["SERVER_ADDR"] + "]";
}
if(configJSON.serverAdministratorEmail && configJSON.serverAdministratorEmail != "[no contact information]") {
nEnv["SERVER_ADMIN"] = configJSON.serverAdministratorEmail;
}
2023-08-11 03:23:27 +02:00
nEnv["SERVER_NAME"] = req.headers.host;
nEnv["DOCUMENT_ROOT"] = process.cwd();
nEnv["SCRIPT_NAME"] = scgiConf.path;
2023-12-31 21:39:21 +01:00
nEnv["PATH_INFO"] = decodeURIComponent(b);
nEnv["PATH_TRANSLATED"] = b ? ((process.cwd() + decodeURIComponent(require("os").platform == "win32" ? b.replace(/\//g, "\\") : b)).replace((require("os").platform == "win32" ? /\\\\/g : /\/\//g), (require("os").platform == "win32" ? "\\" : "/"))) : "";
2023-08-11 03:23:27 +02:00
nEnv["REQUEST_METHOD"] = req.method;
nEnv["SCGI"] = "1";
2024-11-21 00:20:33 +01:00
nEnv["REQUEST_URI"] = (!origHref || origHref == href) ? req.url : (origHref + (uobject.search ? ((uobject.search[0] == "?" ? "" : "?") + uobject.search) : ""));
2023-08-11 03:23:27 +02:00
nEnv["REMOTE_ADDR"] = (req.socket.realRemoteAddress ? req.socket.realRemoteAddress : ((req.headers["x-forwarded-for"] && configJSON.enableIPSpoofing) ? req.headers["x-forwarded-for"].split(",")[0].replace(/ /g, "") : req.socket.remoteAddress)).replace(/^::ffff:/i, "");
2023-09-03 11:34:18 +02:00
if(req.socket.realRemoteAddress && req.socket.realRemotePort) {
nEnv["REMOTE_PORT"] = req.socket.realRemotePort;
} else if(!(req.socket.realRemoteAddress && !req.socket.realRemotePort)) {
nEnv["REMOTE_PORT"] = req.socket.remotePort;
}
2023-08-21 20:21:32 +02:00
if (req.socket.encrypted) nEnv["HTTPS"] = "ON";
2023-08-11 03:23:27 +02:00
if (req.headers["content-type"]) nEnv["CONTENT_TYPE"] = req.headers["content-type"];
nEnv["CONTENT_LENGTH"] = "0";
if (req.headers["content-length"]) nEnv["CONTENT_LENGTH"] = req.headers["content-length"];
var nh = JSON.parse(JSON.stringify(req.headers));
delete nh["content-type"];
delete nh["content-length"];
var nhKeys = Object.keys(nh);
for (var i = 0; i < nhKeys.length; i++) {
nEnv["HTTP_" + nhKeys[i].replace(/[^0-9A-Za-z]+/g, "_").toUpperCase()] = req.headers[nhKeys[i]];
}
executeSCGI(req, res, dh, nEnv);
}
if (href == scgiConf.path || href.indexOf(scgiConf.path + "/") == 0) {
try {
executeSCGIWithEnv(
2023-12-31 21:39:21 +01:00
decodeURIComponent(href),
2023-08-11 03:23:27 +02:00
req,
res,
req.socket.localAddress,
req.socket.localPort,
getCustomHeaders ?
getCustomHeaders()["Server"] +
2023-12-31 21:39:21 +01:00
(disableModExposeSupported && (configJSON.exposeModsInErrorPages || configJSON.exposeModsInErrorPages === undefined) ?
2023-08-11 03:23:27 +02:00
" OrangeCircle/" +
2023-12-31 21:39:21 +01:00
version : "") :
2023-08-11 03:23:27 +02:00
"SVR.JS/" +
configJSON.version +
" (" +
os.platform()[0].toUpperCase() +
os.platform().slice(1) +
"; Node.JS/" +
process.version +
") OrangeCircle/" +
version,
2024-02-07 01:33:31 +01:00
bheaders,
authUser
2023-08-11 03:23:27 +02:00
);
} catch (ex) {
if (!callServerError) {
res.writeHead(500, "Internal Server Error", abheaders);
res.write(
"<html><head><title>500 Internal Server Error</title></head><body><h1>500 Internal Server Error</h1><p>A server had unexpected exception. Below, the stack trace of the error is shown:</p><code>" +
ex.stack.replace(/\r\n/g, "<br/>").replace(/\n/g, "<br/>").replace(/\r/g, "<br/>").replace(/ /g, "&nbsp;") +
"</code><p>Please contact the developer/administrator of the website.</p><p style=\"font-style: italic; font-weight: normal;\">SVR.JS " +
configJSON.version +
" (" +
os.platform()[0].toUpperCase() +
os.platform().slice(1) +
"; Node.JS/" +
process.version +
") OrangeCircle/" +
version +
" " +
(req.headers.host == undefined ? "" : " on " + req.headers.host) +
"</p></body></html>"
);
res.end();
} else {
callServerError(500, "OrangeCircle/" + version, ex);
}
}
} else if ((href == "/orangecircle-config.json" || (os.platform() == "win32" && href.toLowerCase() == "/orangecircle-config.json")) && path.normalize(__dirname + "/../../..") == process.cwd()) {
2023-08-11 03:23:27 +02:00
if (!callServerError) {
res.writeHead(200, "OK", {
"Content-Type": "application/json",
"Server": "OrangeCircle/" + version
});
res.end(JSON.stringify(exttointerpreteruser, null, 2));
} else {
callServerError(200, "OrangeCircle/" + version, exttointerpreteruser);
}
} else {
elseCallback();
}
}
}
module.exports = Mod;