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 stream = require("stream"); 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) { //YellowSquare 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... } class RequestBodyStream extends stream.Readable { constructor(sourceStream) { super(); this.sourceStream = sourceStream; // When the source stream emits data, push it to the wrapper stream this.sourceStream.on('data', (chunk) => { this.push(chunk); }); // When the source stream ends, push null to indicate the end of the wrapper stream this.sourceStream.on('end', () => { this.push(null); }); } // Implement the _read method to handle the read operation _read() {} } class ErrorStream extends stream.Writable { constructor() { super(); this.buffer = Buffer.alloc(0); } _write(chunk, encoding, callback) { // Concatenate the incoming chunk to the buffer this.buffer = Buffer.concat([this.buffer, chunk]); // Call the callback to indicate that the write operation is complete callback(); } } 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 executeJSGI(fname, req, res, dh, jsgiRequestObject) { // Function to execute JSGI scripts if (!fname.match(/\.jsgi(?:\.js)?$/)) { elseCallback(); return; } try { var JSGIApp = require(process.cwd() + "/" + fname); var jsgiResponseObject = {}; if (typeof JSGIApp === "object" && JSGIApp.app) { jsgiResponseObject = JSGIApp.app(jsgiRequestObject); } else { throw new Error("JSGI app must have app key in exports objects!!!"); } if (jsgiRequestObject.jsgi.errors.readable) jsgiRequestObject.jsgi.errors.end(); var errors = jsgiRequestObject.jsgi.errors.buffer.toString(); if (errors.trim().length > 0) { serverconsole.errmessage("There were JSGI application errors:"); serverconsole.errmessage(errors); } if (!jsgiResponseObject.status) jsgiResponseObject.status = 200; if (!getCustomHeaders) { var aheaders = JSON.parse(JSON.stringify(configJSON.customHeaders)); } else { var aheaders = getCustomHeaders(); } if (jsgiResponseObject.headers) { var hKeys = Object.keys(jsgiResponseObject.headers); for (var i = 0; i < hKeys.length; i++) { aheaders[hKeys[i]] = jsgiResponseObject.headers[hKeys[i]]; } } if (!jsgiResponseObject.body) jsgiResponseObject.body = [""]; if (typeof jsgiResponseObject.body === "string") { res.writeHead(jsgiResponseObject.status, http.STATUS_CODES[jsgiResponseObject.status], aheaders); res.write(jsgiResponseObject.body); res.end(); } else if (typeof jsgiResponseObject.body.forEach !== "function") { throw new Error("JSGI app must return body, which has forEach function."); } else { res.writeHead(jsgiResponseObject.status, http.STATUS_CODES[jsgiResponseObject.status], aheaders); jsgiResponseObject.body.forEach(function (chunk) { res.write(chunk); }); res.end(); } } catch (error) { if (!callServerError) { res.writeHead(500, { "Content-Type": "text/html", "Server": "YellowSquare/" + version }); res.end("

YellowSquare Error!

Reason: " + error.message + "

"); } else { callServerError(500, "YellowSquare/" + version, error); } } } function executeJSGIWithReqObj(a, b, req, res, pubip, port, software, dh, user) { // Function to set up request object and execute JSGI scripts var inputStream = new RequestBodyStream(req); var errorStream = new ErrorStream(); var jsgiRequestObject = { version: req.httpVersion.split("."), method: req.method, headers: req.headers, input: inputStream, scriptName: a, pathInfo: decodeURIComponent(b), pathTranslated: b ? ((process.cwd() + decodeURIComponent(require("os").platform == "win32" ? b.replace(/\//g, "\\") : b)).replace((require("os").platform == "win32" ? /\\\\/g : /\/\//g), (require("os").platform == "win32" ? "\\" : "/"))) : "", scheme: req.socket.encrypted ? "https" : "http", env: {}, jsgi: { version: [0, 3], errors: errorStream, multithread: false, multiprocess: true, runOnce: false, async: false, cgi: false, ext: {} }, serverSoftware: software, remoteAddr: (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) { jsgiRequestObject.remotePort = req.socket.realRemotePort; } else if(!(req.socket.realRemoteAddress && !req.socket.realRemotePort)) { jsgiRequestObject.remotePort = req.socket.remotePort; } if (typeof user != "undefined") { if (user !== null) { if (req.headers.authorization) jsgiRequestObject.authType = req.headers.authorization.split(" ")[0]; jsgiRequestObject.remoteUser = user; } } else if (req.headers.authorization && (typeof checkIfThereIsA401Rule == "undefined" || checkIfThereIsA401Rule())) { jsgiRequestObject.authType = req.headers.authorization.split(" ")[0]; if (jsgiRequestObject.authType == "Basic") { var remoteCred = req.headers.authorization.split(" ")[1]; if (!remoteCred) { jsgiRequestObject.remoteUser = "yellowsquare_jsgi_invalid_user"; } else { var remoteCredDecoded = Buffer.from(remoteCred, "base64").toString("utf8"); jsgiRequestObject.remoteUser = remoteCredDecoded.split(":")[0]; } } else { jsgiRequestObject.remoteUser = "svrjs_this_property_is_not_yet_supported_by_yellowsquare_jsgi"; } } jsgiRequestObject.queryString = req.url.split("?")[1]; if (jsgiRequestObject.queryString == undefined || jsgiRequestObject.queryString == "undefined") jsgiRequestObject.queryString = ""; if (pubip && (port !== null && port !== undefined)) { jsgiRequestObject.port = port; jsgiRequestObject.host = pubip.replace(/^::ffff:/i, ""); if (jsgiRequestObject.host.indexOf(":") != -1) jsgiRequestObject.host = "[" + jsgiRequestObject.host + "]"; } executeJSGI("." + a, req, res, dh, jsgiRequestObject); } if (href.match(new RegExp("^/jsgi-bin(?:$|[?#/])",os.platform() == "win32" ? "i" : ""))) { fs.stat("." + decodeURIComponent(href), function (err, stats) { if (!err) { if (!stats.isFile()) { elseCallback(); } else { try { executeJSGIWithReqObj( decodeURIComponent(href), "", req, res, req.socket.localAddress, req.socket.localPort, getCustomHeaders ? getCustomHeaders()["Server"] + " YellowSquare/" + version : "SVR.JS/" + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") YellowSquare/" + version, bheaders, authUser ); } catch (ex) { if (!callServerError) { res.writeHead(500, "Internal Server Error", abheaders); res.write( "500 Internal Server Error

500 Internal Server Error

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 + ") YellowSquare/" + version + " " + (req.headers.host == undefined ? "" : " on " + req.headers.host) + "

" ); res.end(); } else { callServerError(500, "YellowSquare/" + version, ex); } } } } 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 < 3) { 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 { try { executeJSGIWithReqObj( pathp.fpth.substr(1), pathp.rpth, req, res, req.socket.localAddress, req.socket.localPort, getCustomHeaders ? getCustomHeaders()["Server"] + " YellowSquare/" + version : "SVR.JS/" + configJSON.version + " (" + os.platform()[0].toUpperCase() + os.platform().slice(1) + "; Node.JS/" + process.version + ") YellowSquare/" + version, bheaders, authUser ); } catch (ex) { if (!callServerError) { res.writeHead(500, "Internal Server Error", abheaders); res.write( "500 Internal Server Error

500 Internal Server Error

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 + ") YellowSquare/" + version + " " + (req.headers.host == undefined ? "" : " on " + req.headers.host) + "

" ); res.end(); } else { callServerError(500, "YellowSquare/" + version, ex); } } } }); } else { elseCallback(); //Invoke default error handler } }); } else { elseCallback(); } } } module.exports = Mod;