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 } 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... } 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; 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)); var socket = {}; 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) { 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"; 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(" "); } } var hasLocation = false; 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 { 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) { socket.removeAllListeners("data"); socket.removeAllListeners("end"); if (!callServerError) { res.writeHead(500); res.end(ex.stack); } else { callServerError(500, "OrangeCircle/" + version, ex); } return; } } else { if (cned && !res.finished) { res.write(data); socket.removeListener("data", dataHandler); socket.pipe(res, {end: false}); } } }; socket = net.createConnection({ 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) { var errorcode = (error.code == "ENOTFOUND" || error.code == "EHOSTUNREACH" || error.code == "ECONNREFUSED") ? 503 : 500; if (!callServerError) { res.writeHead(errorcode, { "Content-Type": "text/html", "Server": "OrangeCircle/" + version }); res.end("
Reason: " + error.message + "
"); } else { callServerError(errorcode, "OrangeCircle/" + version, error); } }); } function executeSCGIWithEnv(a, req, res, pubip, port, software, dh, user) { // Function to set up environment variables and execute sCGI scripts var b = href.replace(scgiConf.path,"") 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"] = "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; } nEnv["SERVER_NAME"] = req.headers.host; nEnv["DOCUMENT_ROOT"] = process.cwd(); nEnv["SCRIPT_NAME"] = scgiConf.path; 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["SCGI"] = "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; } if (req.socket.encrypted) nEnv["HTTPS"] = "ON"; 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( decodeURIComponent(href), req, res, req.socket.localAddress, req.socket.localPort, getCustomHeaders ? getCustomHeaders()["Server"] + (disableModExposeSupported && (configJSON.exposeModsInErrorPages || configJSON.exposeModsInErrorPages === undefined) ? " OrangeCircle/" + version : "") : "SVR.JS/" + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") OrangeCircle/" + 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 + ") OrangeCircle/" + version + " " + (req.headers.host == undefined ? "" : " on " + req.headers.host) + "
" ); 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()) { 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;