394 lines
15 KiB
JavaScript
394 lines
15 KiB
JavaScript
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();
|
|
}
|
|
}
|
|
|
|
var normalizedWebrootSupported = process.versions.svrjs && process.versions.svrjs.match(/^(?:Nightly-|(?:[5-9]|[1234][0-9])[0-9]*\.|4\.(?:(?:[1][0-9]|[2-9])+\.))/i);
|
|
|
|
function normalizeWebroot(currentWebroot) {
|
|
if (currentWebroot === undefined) {
|
|
return process.cwd();
|
|
} else if (!path.isAbsolute(currentWebroot)) {
|
|
return (
|
|
process.cwd() + (os.platform() == "win32" ? "\\" : "/") + currentWebroot
|
|
);
|
|
} else {
|
|
return currentWebroot;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
var detectedWwwroot = normalizedWebrootSupported ? normalizeWebroot(configJSON.wwwroot) : process.cwd();
|
|
|
|
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(detectedWwwroot + "/" + 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("<html><head></head><body><h1>YellowSquare Error!</h1><p>Reason: " + error.message + "</p></body></html>");
|
|
} 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 ? ((detectedWwwroot + 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(detectedWwwroot + a, req, res, dh, jsgiRequestObject);
|
|
}
|
|
|
|
if (href.match(new RegExp("^/jsgi-bin(?:$|[?#/])",os.platform() == "win32" ? "i" : ""))) {
|
|
fs.stat(detectedWwwroot + 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(
|
|
"<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, " ") +
|
|
"</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 +
|
|
") YellowSquare/" +
|
|
version +
|
|
" " +
|
|
(req.headers.host == undefined ? "" : " on " + req.headers.host) +
|
|
"</p></body></html>"
|
|
);
|
|
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(detectedWwwroot + "/" + 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(
|
|
"<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, " ") +
|
|
"</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 +
|
|
") YellowSquare/" +
|
|
version +
|
|
" " +
|
|
(req.headers.host == undefined ? "" : " on " + req.headers.host) +
|
|
"</p></body></html>"
|
|
);
|
|
res.end();
|
|
} else {
|
|
callServerError(500, "YellowSquare/" + version, ex);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
elseCallback(); //Invoke default error handler
|
|
}
|
|
});
|
|
} else {
|
|
elseCallback();
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = Mod;
|