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 childProcess = require("child_process"); var readline = require("readline"); var version = "UNKNOWN"; try { version = JSON.parse(fs.readFileSync(__dirname + "/mod.info")).version; } catch (ex) { // Can't determine version } var configJSONS = {}; try { configJSONS = JSON.parse(fs.readFileSync(__dirname + "/../../../config.json")); // Read configuration JSON } catch(ex) { //RedBrick 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... } var exttointerpreteruser = {}; var scriptExts = []; try { exttointerpreteruser = JSON.parse(fs.readFileSync(__dirname + "/../../../redbrick-interpreters.json")); } catch (ex) { } try { scriptExts = JSON.parse(fs.readFileSync(__dirname + "/../../../redbrick-scriptexts.json")); } catch (ex) { } 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); function Mod() {} 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) { return function () { if (!configJSON) { configJSON = configJSONS; } 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; } 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)); function executeCGI(fname, req, res, dh, nEnv) { // Function to execute CGI 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 exttointerpreter = { ".pl": ["perl"], ".py": ["python"], ".sh": ["bash"], ".ksh": ["ksh"], ".csh": ["csh"], ".rb": ["ruby"], ".php": ["php-cgi"] }; if (os.platform() == "win32") { exttointerpreter[".exe"] = []; exttointerpreter[".bat"] = ["cmd", "/c"]; exttointerpreter[".vbs"] = ["cscript"]; } fs.stat(fname, function (err, stats) { if (err) { if (!callServerError) { res.writeHead(500, { "Content-Type": "text/html", "Server": "RedBrick/" + version }); res.end("
Reason: " + err.message + "
"); } else { callServerError(500, "RedBrick/" + version, err); } return; } if (stats.size == 0) { afterShebangCallback(false); return; } var s = fs.createReadStream(fname); s.on("error", function (err) { if (!callServerError) { res.writeHead(500, { "Content-Type": "text/html", "Server": "RedBrick/" + version }); res.end("Reason: " + err.message + "
"); } else { callServerError(500, "RedBrick/" + version, err); } return; }).on("open", function () { var rl = readline.createInterface({ input: s, }); rl.once("line", function (line) { rl.close(); if (line.substr(0, 2) == "#!") { var args = line.substr(2).match(/(?:[^" ]|\\(?:.|$)|"[^"]*(?:"|$))+/g); if (!args) args = []; args = args.map(function (arg) { return arg.replace(/"/g, ""); }); afterShebangCallback(args); } else if (os.platform() != "win32" && line.substr(0, 4) == "\x7fELF") { afterShebangCallback("binary"); } else { afterShebangCallback(false); } }); }); }); function afterShebangCallback(passedArgs) { var ext = path.extname(fname); var args = []; var buffer = ""; var stderr = ""; var headerendline = -1; var cned = false; args.push(fname); if (passedArgs == "binary") { filename = (process.cwd() + (os.platform() == "win32" ? "\\" + fname.replace(/\//g, "\\") : "/" + fname)).replace(os.platform() == "win32" ? /\\+/ : /\/+/, os.platform() == "win32" ? "\\" : "/"); args = []; } else if (passedArgs) { if (os.platform() == "win32") { args = passedArgs; args.push((process.cwd() + ("\\" + fname.replace(/\//g, "\\"))).replace(/\\+/, "\\")); filename = args.shift(); } else { filename = (process.cwd() + (os.platform() == "win32" ? "\\" + fname.replace(/\//g, "\\") : "/" + fname)).replace(os.platform() == "win32" ? /\\+/ : /\/+/, os.platform() == "win32" ? "\\" : "/"); args = []; } } else { args = exttointerpreteruser[ext]; if (args === null) { elseCallback(); return; } else if (!args) { args = exttointerpreter[ext]; if (!args) { elseCallback(); return; } } args.push((process.cwd() + (os.platform() == "win32" ? "\\" + fname.replace(/\//g, "\\") : "/" + fname)).replace(os.platform() == "win32" ? /\\+/ : /\/+/, os.platform() == "win32" ? "\\" : "/")); filename = args.shift(); } var wd = fname.split("/"); wd[0] = ""; wd[wd.length - 1] = ""; wd = wd.join(os.platform() == "win32" ? "\\" : "/"); var interpreter = childProcess.spawn(filename, args, { cwd: (process.cwd() + wd).replace(os.platform() == "win32" ? /\\+/ : /\/+/, os.platform() == "win32" ? "\\" : "/"), env: env }); interpreter.on("error", (error) => { if (!callServerError) { res.writeHead(500, { "Content-Type": "text/html", "Server": "RedBrick/" + version }); res.end("Reason: " + error.message + "
"); } else { callServerError(500, "RedBrick/" + version, error); } }); var dataHandler = function (data) { if (!cned) buffer += data.toString("latin1"); var m = null; if (!cned) m = buffer.match(/(?:\r\n\r\n|\n\r\n\r|\n\n|\r\r)/); if (!cned && m) { cned = true; eol = m[0]; headerendline = m.index; var bheaders = buffer.substr(0, headerendline).split(/(?:\r\n|\n\r|\n|\r)/); var bheaderso = {}; if (dh) bheaderso = dh; var code = 200; var msg = "OK"; var httpMatch = bheaders[0].match(/^HTTP\/[^ ]+ ([0-9]{3})(?: (.*))?$/); if (httpMatch) { bheaders.shift(); code = parseInt(httpMatch[1]); if (httpMatch[2]) msg = httpMatch[2]; else msg = http.STATUS_CODES[code]; } else if (bheaders[0].indexOf(":") == -1) { var heada = bheaders.shift(); var hso = heada.match(/^([0-9]{3})(?: (.*))?$/); if (hso) { code = parseInt(hso[1]); if (hso[2]) msg = hso[2]; else msg = http.STATUS_CODES[code]; } } var hasLocation = false; for (var i = 0; i < bheaders.length; i++) { var headerp = bheaders[i].match(/^([^:]*)(?:: (.*))?/); if (!headerp) headerp = []; var headern = headerp[1]; var headerv = headerp[2]; if (headern.toLowerCase() == "status") { var httpMatch = headerv.match(/^([0-9]{3})(?: (.*))?$/); if (httpMatch) { code = parseInt(httpMatch[1]); if (httpMatch[2]) msg = httpMatch[2]; else msg = http.STATUS_CODES[code]; } } else if (headern.toLowerCase() == "set-cookie") { if (!bheaderso["Set-Cookie"]) bheaderso["Set-Cookie"] = []; bheaderso["Set-Cookie"].push(headerv); } else { if (headern.toLowerCase() == "location") hasLocation = true; bheaderso[headern] = headerv; } } if ((code < 300 || code > 399) && hasLocation) { code = 302; msg = "Found"; } try { res.writeHead(code, msg, bheaderso); res.write(Buffer.from(buffer.substr(headerendline + eol.length), "latin1")); } catch (ex) { interpreter.removeAllListeners("exit"); interpreter.stdout.removeAllListeners("data"); if (!callServerError) { res.writeHead(500); res.end(ex.stack); } else { callServerError(500, "RedBrick/" + version, ex); } return; } } else { if (cned && !res.finished) { res.write(data); interpreter.stdout.removeListener("data", dataHandler); interpreter.stdout.pipe(res, {end: false}); dataHandler = function () {}; //Prevent event listener memory leaks } } }; res.prependListener("close", function() { if(interpreter.stdout) interpreter.stdout.unpipe(res); //Prevent server crashes with write after the end }); res.on("error", function() {}); //Suppress response stream errors if (interpreter.stdout) { interpreter.stdout.on("data", dataHandler); interpreter.stderr.on("data", function (data) { stderr += data.toString(); }); req.pipe(interpreter.stdin); interpreter.on("exit", (code, signal) => { if (!cned && (signal || code !== 0)) { var ex = new Error("Process execution failed!" + (stderr ? " Reason: " + stderr.trim() : "")); if (!callServerError) { res.writeHead(500); res.end(ex.stack); } else { callServerError(500, "RedBrick/" + version, ex); } } else { var preparedStderr = stderr.trim() if(preparedStderr) { serverconsole.errmessage("There were CGI application errors:"); serverconsole.errmessage(preparedStderr); } interpreter.stdout.removeListener("data", dataHandler); interpreter.stdout.unpipe(res); //Prevent server crashes with write after the end if(!res.finished) res.end(); } }); } } } function executeCGIWithEnv(a, b, req, res, pubip, port, software, dh, user) { // Function to set up environment variables and execute CGI scripts var nEnv = {}; 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())) { 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"] = "redbrick_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_redbrick_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; } nEnv["SERVER_NAME"] = req.headers.host; nEnv["DOCUMENT_ROOT"] = process.cwd(); 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" ? "\\" : "/"))) : ""; nEnv["REQUEST_METHOD"] = req.method; nEnv["GATEWAY_INTERFACE"] = "CGI/1.1"; nEnv["REQUEST_URI"] = (!origHref || origHref == href) ? req.url : (origHref + (uobject.search ? ((uobject.search[0] == "?" ? "" : "?") + uobject.search) : "")); 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, ""); 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; } nEnv["SCRIPT_NAME"] = a; nEnv["SCRIPT_FILENAME"] = (process.cwd() + (require("os").platform == "win32" ? a.replace(/\//g, "\\") : a)).replace((require("os").platform == "win32" ? /\\\\/g : /\/\//g), (require("os").platform == "win32" ? "\\" : "/")); if (req.socket.encrypted) nEnv["HTTPS"] = "ON"; if (req.headers["content-type"]) nEnv["CONTENT_TYPE"] = req.headers["content-type"]; 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]]; } executeCGI("." + a, req, res, dh, nEnv); } var isCgiBin = href.match(new RegExp("^/cgi-bin(?:$|[?#/])", os.platform() == "win32" ? "i" : "")); var isScriptExt = scriptExts.indexOf("." + ext) != -1; if ((href == "/redbrick-interpreters.json" || href == "/redbrick-scriptexts.json" || (os.platform() == "win32" && (href.toLowerCase() == "/redbrick-interpreters.json" || href.toLowerCase() == "/redbrick-scriptexts.json"))) && path.normalize(__dirname + "/../../..") == process.cwd()) { if (!callServerError) { res.writeHead(403, "Forbidden", { "Content-Type": "text/html", "Server": "RedBrick/" + version }); res.write( "You don't have access to specific page.
SVR.JS " + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") RedBrick/" + version + " " + (req.headers.host == undefined ? "" : " on " + req.headers.host) + "
" ); res.end(JSON.stringify(exttointerpreteruser, null, 2)); } else { callServerError(403, "RedBrick/" + version); } } else { fs.stat("." + decodeURIComponent(href), function (err, stats) { if (!err) { if (!stats.isFile()) { if (isCgiBin || scriptExts.indexOf(".php") != -1) { fs.stat("." + decodeURIComponent(href) + "/index.php", function (e2, s2) { if (!e2 && s2.isFile()) { try { executeCGIWithEnv( (decodeURIComponent(href) + "/index.php").replace(/\/+/g, "/"), "", req, res, req.socket.localAddress, req.socket.localPort, getCustomHeaders ? getCustomHeaders()["Server"] + (disableModExposeSupported && (configJSON.exposeModsInErrorPages || configJSON.exposeModsInErrorPages === undefined) ? " RedBrick/" + version : "") : "SVR.JS/" + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") RedBrick/" + version, bheaders, authUser ); } catch (ex) { if (!callServerError) { res.writeHead(500, "Internal Server Error", abheaders); res.write( "A server had unexpected exception. Below, the stack trace of the error is shown:
" +
ex.stack.replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ /g, " ") +
"
Please contact the developer/administrator of the website.
SVR.JS " + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") RedBrick/" + version + " " + (req.headers.host == undefined ? "" : " on " + req.headers.host) + "
" ); res.end(); } else { callServerError(500, "RedBrick/" + version, ex); } } } else if (isCgiBin || scriptExts.indexOf(".cgi") != -1) { fs.stat("." + decodeURIComponent(href) + "/index.cgi", function (e3, s3) { if (!e3 && s3.isFile()) { try { executeCGIWithEnv( (decodeURIComponent(href) + "/index.cgi").replace(/\/+/g, "/"), "", req, res, req.socket.localAddress, req.socket.localPort, getCustomHeaders ? getCustomHeaders()["Server"] + (disableModExposeSupported && (configJSON.exposeModsInErrorPages || configJSON.exposeModsInErrorPages === undefined) ? " RedBrick/" + version : "") : "SVR.JS/" + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") RedBrick/" + version, bheaders, authUser ); } catch (ex) { if (!callServerError) { res.writeHead(500, "Internal Server Error", abheaders); res.write( "A server had unexpected exception. Below, the stack trace of the error is shown:
" +
ex.stack.replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ /g, " ") +
"
Please contact the developer/administrator of the website.
SVR.JS " + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") RedBrick/" + version + " " + (req.headers.host == undefined ? "" : " on " + req.headers.host) + "
" ); res.end(); } else { callServerError(500, "RedBrick/" + version, ex); } } } else { elseCallback(); } }); } else { elseCallback(); } }); } else if (scriptExts.indexOf(".cgi") != -1) { fs.stat("." + decodeURIComponent(href) + "/index.cgi", function (e3, s3) { if (!e3 && s3.isFile()) { try { executeCGIWithEnv( (decodeURIComponent(href) + "/index.cgi").replace(/\/+/g, "/"), "", req, res, req.socket.localAddress, req.socket.localPort, getCustomHeaders ? getCustomHeaders()["Server"] + (disableModExposeSupported && (configJSON.exposeModsInErrorPages || configJSON.exposeModsInErrorPages === undefined) ? " RedBrick/" + version : "") : "SVR.JS/" + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") RedBrick/" + version, bheaders, authUser ); } catch (ex) { if (!callServerError) { res.writeHead(500, "Internal Server Error", abheaders); res.write( "A server had unexpected exception. Below, the stack trace of the error is shown:
" +
ex.stack.replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ /g, " ") +
"
Please contact the developer/administrator of the website.
SVR.JS " + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") RedBrick/" + version + " " + (req.headers.host == undefined ? "" : " on " + req.headers.host) + "
" ); res.end(); } else { callServerError(500, "RedBrick/" + version, ex); } } } else { elseCallback(); } }); } else { elseCallback(); } } else { if (isCgiBin || isScriptExt) { try { executeCGIWithEnv( decodeURIComponent(href), "", req, res, req.socket.localAddress, req.socket.localPort, getCustomHeaders ? getCustomHeaders()["Server"] + (disableModExposeSupported && (configJSON.exposeModsInErrorPages || configJSON.exposeModsInErrorPages === undefined) ? " RedBrick/" + version : "") : "SVR.JS/" + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") RedBrick/" + version, bheaders, authUser ); } catch (ex) { if (!callServerError) { res.writeHead(500, "Internal Server Error", abheaders); res.write( "A server had unexpected exception. Below, the stack trace of the error is shown:
" +
ex.stack.replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ /g, " ") +
"
Please contact the developer/administrator of the website.
SVR.JS " + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") RedBrick/" + version + " " + (req.headers.host == undefined ? "" : " on " + req.headers.host) + "
" ); res.end(); } else { callServerError(500, "RedBrick/" + version, ex); } } } else { elseCallback(); } } } else if (err && err.code == "ENOTDIR") { function checkPath(pth, cb, a) { // Function to check the path of the file and execute CGI script var cpth = pth.split("/"); if (cpth.length < (isCgiBin ? 3 : 2)) { cb(false); return; } if (!a) b = []; else var b = a.split("/"); var isFile = false; fs.stat(pth, function (err, stats) { if (!err && stats.isFile()) { cb({ fpth: pth, rpth: (a !== undefined ? "/" + a : "") }) } else { b.unshift(cpth.pop()); return checkPath(cpth.join("/"), cb, b.join("/")); } }); } checkPath("." + decodeURIComponent(href), function (pathp) { if (!pathp) { elseCallback(); } else { var newext = path.extname(pathp.fpth); if (isCgiBin || scriptExts.indexOf(newext) != -1) { try { executeCGIWithEnv( pathp.fpth.substr(1), pathp.rpth, req, res, req.socket.localAddress, req.socket.localPort, getCustomHeaders ? getCustomHeaders()["Server"] + (disableModExposeSupported && (configJSON.exposeModsInErrorPages || configJSON.exposeModsInErrorPages === undefined) ? " RedBrick/" + version : "") : "SVR.JS/" + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") RedBrick/" + version, bheaders, authUser ); } catch (ex) { if (!callServerError) { res.writeHead(500, "Internal Server Error", abheaders); res.write( "A server had unexpected exception. Below, the stack trace of the error is shown:
" +
ex.stack.replace(/\r\n/g, "
").replace(/\n/g, "
").replace(/\r/g, "
").replace(/ /g, " ") +
"
Please contact the developer/administrator of the website.
SVR.JS " + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") RedBrick/" + version + " " + (req.headers.host == undefined ? "" : " on " + req.headers.host) + "
" ); res.end(); } else { callServerError(500, "RedBrick/" + version, ex); } } } else { elseCallback(); } } }); } else { elseCallback(); //Invoke default error handler } }); } } } module.exports = Mod;