forked from svrjs/svrjs
feat: add SVR.JS Core
This commit is contained in:
parent
1174a348b6
commit
d34d4bcc2d
10 changed files with 755 additions and 5 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,7 @@
|
|||
# Build output
|
||||
/dist/
|
||||
/out/
|
||||
/core/
|
||||
|
||||
# Temporary files used by build script
|
||||
/generatedAssets/
|
||||
|
|
21
coreAssets/LICENSE
Normal file
21
coreAssets/LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018-2024 SVR.JS
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
3
coreAssets/README.md
Normal file
3
coreAssets/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# SVR.JS Core
|
||||
|
||||
TODO
|
|
@ -7,6 +7,10 @@ const archiver = require("archiver");
|
|||
const chokidar = require("chokidar");
|
||||
const svrjsInfo = JSON.parse(fs.readFileSync(__dirname + "/svrjs.json"));
|
||||
const { version } = svrjsInfo;
|
||||
const svrjsCoreInfo = JSON.parse(fs.readFileSync(__dirname + "/svrjs.core.json"));
|
||||
const { externalPackages } = svrjsCoreInfo;
|
||||
const coreVersion = svrjsCoreInfo.version;
|
||||
const corePackageJSON = svrjsCoreInfo.packageJSON;
|
||||
const isDev = process.env.NODE_ENV == "development";
|
||||
|
||||
// Create the dist directory if it doesn't exist
|
||||
|
@ -21,6 +25,9 @@ if (!fs.existsSync(__dirname + "/dist/temp"))
|
|||
// Create the out directory if it doesn't exist and if not building for development
|
||||
if (!isDev && !fs.existsSync(__dirname + "/out")) fs.mkdirSync(__dirname + "/out");
|
||||
|
||||
// Create the core directory if it doesn't exist and if not building for development
|
||||
if (!isDev && !fs.existsSync(__dirname + "/core")) fs.mkdirSync(__dirname + "/core");
|
||||
|
||||
function generateAssets() {
|
||||
// Variables from "svrjs.json" file
|
||||
const svrjsInfo = JSON.parse(fs.readFileSync(__dirname + "/svrjs.json"));
|
||||
|
@ -242,6 +249,44 @@ if (!isDev) {
|
|||
platform: "node",
|
||||
target: "es2017"
|
||||
})
|
||||
.then(() => {
|
||||
const dependencies = JSON.parse(fs.readFileSync(__dirname + "/package.json")).dependencies || {};
|
||||
const coreDependencyNames = Object.keys(dependencies).filter((dependency) => externalPackages.indexOf(dependency) != -1);
|
||||
const packageJSON = Object.assign({}, corePackageJSON);
|
||||
|
||||
// Add package.json properties
|
||||
packageJSON.version = coreVersion;
|
||||
packageJSON.main = "./svr.core.js";
|
||||
packageJSON.dependencies = coreDependencyNames.reduce((previousDependencies, dependency) => {
|
||||
previousDependencies[dependency] = dependencies[dependency];
|
||||
return previousDependencies;
|
||||
}, {});
|
||||
|
||||
// Write package.json
|
||||
fs.writeFileSync(__dirname + "/core/package.json", JSON.stringify(packageJSON, null, 2));
|
||||
|
||||
// Build SVR.JS Core
|
||||
esbuild
|
||||
.build({
|
||||
entryPoints: ["src/core.js"],
|
||||
bundle: true,
|
||||
outfile: "core/svr.core.js",
|
||||
platform: "node",
|
||||
target: "es2017",
|
||||
external: coreDependencyNames,
|
||||
plugins: [
|
||||
esbuildCopyPlugin.copy({
|
||||
resolveFrom: __dirname,
|
||||
assets: {
|
||||
from: ["./coreAssets/**/*"],
|
||||
to: ["./core"]
|
||||
},
|
||||
globbyOptions: {
|
||||
dot: true
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
.then(() => {
|
||||
const archiveName =
|
||||
"svr.js." +
|
||||
|
@ -284,6 +329,10 @@ if (!isDev) {
|
|||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
})
|
||||
.catch((err) => {
|
||||
throw err;
|
||||
});
|
||||
} else {
|
||||
// Bundle the source and copy the assets using esbuild and esbuild-plugin-copy with watching
|
||||
esbuild
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
"scripts": {
|
||||
"build": "npm run clean && NODE_ENV=production node esbuild.config.js",
|
||||
"cz": "cz",
|
||||
"clean": "rimraf dist && rimraf out && rimraf generatedAssets",
|
||||
"clean": "rimraf dist && rimraf out && rimraf generatedAssets && rimraf core",
|
||||
"dev": "npm run clean && concurrently \"NODE_ENV=development node esbuild.config.js\" \"wait-on dist/svr.js && nodemon dist/svr.js --stdout-notty --no-save-config\"",
|
||||
"lint": "eslint --no-error-on-unmatched-pattern src/**/*.js src/*.js tests/**/*.test.js tests/**/*.js tests/*.test.js tests/*.js",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
|
|
633
src/core.js
Normal file
633
src/core.js
Normal file
|
@ -0,0 +1,633 @@
|
|||
const fs = require("fs");
|
||||
const net = require("net");
|
||||
const defaultPageCSS = require("./res/defaultPageCSS.js");
|
||||
const generateErrorStack = require("./utils/generateErrorStack.js");
|
||||
const serverHTTPErrorDescs = require("./res/httpErrorDescriptions.js");
|
||||
const fixNodeMojibakeURL = require("./utils/urlMojibakeFixer.js");
|
||||
const ipMatch = require("./utils/ipMatch.js");
|
||||
const matchHostname = require("./utils/matchHostname.js");
|
||||
const generateServerStringCore = require("./utils/generateServerStringCore.js");
|
||||
const parseURL = require("./utils/urlParser.js");
|
||||
const deepClone = require("./utils/deepClone.js");
|
||||
const statusCodes = require("./res/statusCodes.js");
|
||||
|
||||
const middleware = [
|
||||
require("./middleware/urlSanitizer.js"),
|
||||
require("./middleware/rewriteURL.js"),
|
||||
require("./middleware/redirectTrailingSlashes.js"),
|
||||
require("./middleware/defaultHandlerChecks.js"),
|
||||
require("./middleware/staticFileServingAndDirectoryListings.js")
|
||||
];
|
||||
let coreConfig = {};
|
||||
|
||||
function requestHandler(req, res, next) {
|
||||
// SVR.JS log facilities (stubs in SVR.JS core)
|
||||
const logFacilities = {
|
||||
climessage: () => {},
|
||||
reqmessage: () => {},
|
||||
resmessage: () => {},
|
||||
errmessage: () => {},
|
||||
locerrmessage: () => {},
|
||||
locwarnmessage: () => {},
|
||||
locmessage: () => {}
|
||||
};
|
||||
|
||||
// SVR.JS configuration object (modified)
|
||||
const config = deepClone(coreConfig);
|
||||
|
||||
config.generateServerString = () =>
|
||||
generateServerStringCore(config.exposeServerVersion);
|
||||
|
||||
// Determine the webroot from the current working directory if it is not configured
|
||||
if (config.wwwroot === undefined) config.wwwroot = process.cwd();
|
||||
|
||||
// getCustomHeaders() in SVR.JS 3.x
|
||||
config.getCustomHeaders = () => {
|
||||
let ph = Object.assign({}, config.customHeaders);
|
||||
if (config.customHeadersVHost) {
|
||||
let vhostP = null;
|
||||
config.customHeadersVHost.every((vhost) => {
|
||||
if (
|
||||
matchHostname(vhost.host, req.headers.host) &&
|
||||
ipMatch(vhost.ip, req.socket ? req.socket.localAddress : undefined)
|
||||
) {
|
||||
vhostP = vhost;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
if (vhostP && vhostP.headers) ph = { ...ph, ...vhostP.headers };
|
||||
}
|
||||
Object.keys(ph).forEach((phk) => {
|
||||
if (typeof ph[phk] == "string")
|
||||
ph[phk] = ph[phk].replace(/\{path\}/g, req.url);
|
||||
});
|
||||
return ph;
|
||||
};
|
||||
|
||||
// Make HTTP/1.x API-based scripts compatible with HTTP/2.0 API
|
||||
if (config.enableHTTP2 == true && req.httpVersion == "2.0") {
|
||||
// Set HTTP/1.x methods (to prevent process warnings)
|
||||
res.writeHeadNodeApi = res.writeHead;
|
||||
res.setHeaderNodeApi = res.setHeader;
|
||||
|
||||
res.writeHead = (a, b, c) => {
|
||||
let table = c;
|
||||
if (typeof b == "object") table = b;
|
||||
if (table == undefined) table = this.tHeaders;
|
||||
if (table == undefined) table = {};
|
||||
table = Object.assign({}, table);
|
||||
Object.keys(table).forEach((key) => {
|
||||
const al = key.toLowerCase();
|
||||
if (
|
||||
al == "transfer-encoding" ||
|
||||
al == "connection" ||
|
||||
al == "keep-alive" ||
|
||||
al == "upgrade"
|
||||
)
|
||||
delete table[key];
|
||||
});
|
||||
if (res.stream && res.stream.destroyed) {
|
||||
return false;
|
||||
} else {
|
||||
return res.writeHeadNodeApi(a, table);
|
||||
}
|
||||
};
|
||||
res.setHeader = (headerName, headerValue) => {
|
||||
const al = headerName.toLowerCase();
|
||||
if (
|
||||
al != "transfer-encoding" &&
|
||||
al != "connection" &&
|
||||
al != "keep-alive" &&
|
||||
al != "upgrade"
|
||||
)
|
||||
return res.setHeaderNodeApi(headerName, headerValue);
|
||||
return false;
|
||||
};
|
||||
|
||||
// Set HTTP/1.x headers
|
||||
if (!req.headers.host) req.headers.host = req.headers[":authority"];
|
||||
if (!req.url) req.url = req.headers[":path"];
|
||||
if (!req.protocol) req.protocol = req.headers[":scheme"];
|
||||
if (!req.method) req.method = req.headers[":method"];
|
||||
if (
|
||||
req.headers[":path"] == undefined ||
|
||||
req.headers[":method"] == undefined
|
||||
) {
|
||||
let err = new Error(
|
||||
'Either ":path" or ":method" pseudoheader is missing.'
|
||||
);
|
||||
if (Buffer.alloc) err.rawPacket = Buffer.alloc(0);
|
||||
if (req.socket && req.socket.server)
|
||||
req.socket.server.emit("clientError", err, req.socket);
|
||||
}
|
||||
}
|
||||
|
||||
req.url = fixNodeMojibakeURL(req.url);
|
||||
|
||||
req.isProxy = false;
|
||||
|
||||
if (req.socket == null) return;
|
||||
|
||||
// Set up X-Forwarded-For
|
||||
let reqip = req.socket.remoteAddress;
|
||||
let reqport = req.socket.remotePort;
|
||||
let oldip = "";
|
||||
let oldport = "";
|
||||
let isForwardedValid = true;
|
||||
if (config.enableIPSpoofing) {
|
||||
if (req.headers["x-forwarded-for"] != undefined) {
|
||||
let preparedReqIP = req.headers["x-forwarded-for"]
|
||||
.split(",")[0]
|
||||
.replace(/ /g, "");
|
||||
let preparedReqIPvalid = net.isIP(preparedReqIP);
|
||||
if (preparedReqIPvalid) {
|
||||
if (
|
||||
preparedReqIPvalid == 4 &&
|
||||
req.socket.remoteAddress &&
|
||||
req.socket.remoteAddress.indexOf(":") > -1
|
||||
)
|
||||
preparedReqIP = "::ffff:" + preparedReqIP;
|
||||
reqip = preparedReqIP;
|
||||
reqport = null;
|
||||
try {
|
||||
oldport = req.socket.remotePort;
|
||||
oldip = req.socket.remoteAddress;
|
||||
req.socket.realRemotePort = reqport;
|
||||
req.socket.realRemoteAddress = reqip;
|
||||
req.socket.originalRemotePort = oldport;
|
||||
req.socket.originalRemoteAddress = oldip;
|
||||
res.socket.realRemotePort = reqport;
|
||||
res.socket.realRemoteAddress = reqip;
|
||||
res.socket.originalRemotePort = oldport;
|
||||
res.socket.originalRemoteAddress = oldip;
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
} catch (err) {
|
||||
// Address setting failed
|
||||
}
|
||||
} else {
|
||||
isForwardedValid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process the Host header
|
||||
if (typeof req.headers.host == "string") {
|
||||
req.headers.host = req.headers.host.toLowerCase();
|
||||
if (!req.headers.host.match(/^\.+$/))
|
||||
req.headers.host = req.headers.host.replace(/\.$/, "");
|
||||
}
|
||||
|
||||
// Header and footer placeholders
|
||||
res.head = "";
|
||||
res.foot = "";
|
||||
|
||||
res.responseEnd = (body) => {
|
||||
// If body is Buffer, then it is converted to String anyway.
|
||||
res.write(res.head + body + res.foot);
|
||||
res.end();
|
||||
};
|
||||
|
||||
const defaultServerError = (errorCode, extName, stack, ch) => {
|
||||
// Determine error file
|
||||
const getErrorFileName = (list, callback, _i) => {
|
||||
const medCallback = (p) => {
|
||||
if (p) callback(p);
|
||||
else {
|
||||
if (errorCode == 404) {
|
||||
fs.access(config.page404, fs.constants.F_OK, (err) => {
|
||||
if (err) {
|
||||
fs.access(
|
||||
config.wwwroot + "/." + errorCode.toString(),
|
||||
fs.constants.F_OK,
|
||||
(err) => {
|
||||
try {
|
||||
if (err) {
|
||||
callback(errorCode.toString() + ".html");
|
||||
} else {
|
||||
callback(config.wwwroot + "/." + errorCode.toString());
|
||||
}
|
||||
} catch (err2) {
|
||||
res.error(500, err2);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
try {
|
||||
callback(config.page404);
|
||||
} catch (err2) {
|
||||
res.error(500, err2);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fs.access(
|
||||
config.wwwroot + "/." + errorCode.toString(),
|
||||
fs.constants.F_OK,
|
||||
(err) => {
|
||||
try {
|
||||
if (err) {
|
||||
callback(errorCode.toString() + ".html");
|
||||
} else {
|
||||
callback(config.wwwroot + "/." + errorCode.toString());
|
||||
}
|
||||
} catch (err2) {
|
||||
res.error(500, err2);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!_i) _i = 0;
|
||||
if (_i >= list.length) {
|
||||
medCallback(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
list[_i].scode != errorCode ||
|
||||
!(
|
||||
matchHostname(list[_i].host, req.headers.host) &&
|
||||
ipMatch(list[_i].ip, req.socket ? req.socket.localAddress : undefined)
|
||||
)
|
||||
) {
|
||||
getErrorFileName(list, callback, _i + 1);
|
||||
return;
|
||||
} else {
|
||||
fs.access(list[_i].path, fs.constants.F_OK, (err) => {
|
||||
if (err) {
|
||||
getErrorFileName(list, callback, _i + 1);
|
||||
} else {
|
||||
medCallback(list[_i].path);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getErrorFileName(config.errorPages, (errorFile) => {
|
||||
// Generate error stack if not provided
|
||||
if (Object.prototype.toString.call(stack) === "[object Error]")
|
||||
stack = generateErrorStack(stack);
|
||||
if (stack === undefined)
|
||||
stack = generateErrorStack(new Error("Unknown error"));
|
||||
|
||||
// Hide the error stack if specified
|
||||
if (config.stackHidden) stack = "[error stack hidden]";
|
||||
|
||||
// Validate the error code and handle unknown codes
|
||||
if (serverHTTPErrorDescs[errorCode] === undefined) {
|
||||
res.error(501, extName, stack);
|
||||
} else {
|
||||
// Process custom headers if provided
|
||||
let cheaders = { ...config.getCustomHeaders(), ...ch };
|
||||
|
||||
cheaders["Content-Type"] = "text/html; charset=utf-8";
|
||||
|
||||
// Set default Allow header for 405 error if not provided
|
||||
if (errorCode == 405 && !cheaders["Allow"])
|
||||
cheaders["Allow"] = "GET, POST, HEAD, OPTIONS";
|
||||
|
||||
// Read the error file and replace placeholders with error information
|
||||
fs.readFile(errorFile, (err, data) => {
|
||||
try {
|
||||
if (err) throw err;
|
||||
res.writeHead(errorCode, statusCodes[errorCode], cheaders);
|
||||
res.responseEnd(
|
||||
data
|
||||
.toString()
|
||||
.replace(
|
||||
/{errorMessage}/g,
|
||||
errorCode.toString() +
|
||||
" " +
|
||||
statusCodes[errorCode]
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
)
|
||||
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
|
||||
.replace(
|
||||
/{stack}/g,
|
||||
stack
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\r\n/g, "<br/>")
|
||||
.replace(/\n/g, "<br/>")
|
||||
.replace(/\r/g, "<br/>")
|
||||
.replace(/ {2}/g, " ")
|
||||
)
|
||||
.replace(
|
||||
/{path}/g,
|
||||
req.url
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
)
|
||||
.replace(
|
||||
/{server}/g,
|
||||
"" +
|
||||
(
|
||||
config.generateServerString() +
|
||||
(!config.exposeModsInErrorPages || extName == undefined
|
||||
? ""
|
||||
: " " + extName)
|
||||
)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">") +
|
||||
(req.headers.host == undefined || req.isProxy
|
||||
? ""
|
||||
: " on " +
|
||||
String(req.headers.host)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">"))
|
||||
)
|
||||
.replace(
|
||||
/{contact}/g,
|
||||
config.serverAdministratorEmail
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\./g, "[dot]")
|
||||
.replace(/@/g, "[at]")
|
||||
)
|
||||
); // Replace placeholders in error response
|
||||
} catch (err) {
|
||||
let additionalError = 500;
|
||||
// Handle additional error cases
|
||||
if (err.code == "ENOENT") {
|
||||
additionalError = 404;
|
||||
} else if (err.code == "ENOTDIR") {
|
||||
additionalError = 404; // Assume that file doesn't exist
|
||||
} else if (err.code == "EACCES") {
|
||||
additionalError = 403;
|
||||
} else if (err.code == "ENAMETOOLONG") {
|
||||
additionalError = 414;
|
||||
} else if (err.code == "EMFILE") {
|
||||
additionalError = 503;
|
||||
} else if (err.code == "ELOOP") {
|
||||
additionalError = 508;
|
||||
}
|
||||
|
||||
res.writeHead(errorCode, statusCodes[errorCode], cheaders);
|
||||
res.write(
|
||||
`<!DOCTYPE html><html><head><title>{errorMessage}</title><meta name="viewport" content="width=device-width, initial-scale=1.0" /><style>${defaultPageCSS}</style></head><body><h1>{errorMessage}</h1><p>{errorDesc}</p>${
|
||||
additionalError == 404
|
||||
? ""
|
||||
: "<p>Additionally, a {additionalError} error occurred while loading an error page.</p>"
|
||||
}<p><i>{server}</i></p></body></html>`
|
||||
.replace(
|
||||
/{errorMessage}/g,
|
||||
errorCode.toString() +
|
||||
" " +
|
||||
statusCodes[errorCode]
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
)
|
||||
.replace(/{errorDesc}/g, serverHTTPErrorDescs[errorCode])
|
||||
.replace(
|
||||
/{stack}/g,
|
||||
stack
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\r\n/g, "<br/>")
|
||||
.replace(/\n/g, "<br/>")
|
||||
.replace(/\r/g, "<br/>")
|
||||
.replace(/ {2}/g, " ")
|
||||
)
|
||||
.replace(
|
||||
/{path}/g,
|
||||
req.url
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
)
|
||||
.replace(
|
||||
/{server}/g,
|
||||
"" +
|
||||
(
|
||||
config.generateServerString() +
|
||||
(!config.exposeModsInErrorPages || extName == undefined
|
||||
? ""
|
||||
: " " + extName)
|
||||
)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">") +
|
||||
(req.headers.host == undefined || req.isProxy
|
||||
? ""
|
||||
: " on " +
|
||||
String(req.headers.host)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">"))
|
||||
)
|
||||
.replace(
|
||||
/{contact}/g,
|
||||
config.serverAdministratorEmail
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/\./g, "[dot]")
|
||||
.replace(/@/g, "[at]")
|
||||
)
|
||||
.replace(/{additionalError}/g, additionalError.toString())
|
||||
); // Replace placeholders in error response
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Server error calling method
|
||||
res.error = (errorCode, extName, stack, ch) => {
|
||||
if (typeof errorCode !== "number") {
|
||||
throw new TypeError("HTTP error code parameter needs to be an integer.");
|
||||
}
|
||||
|
||||
// Handle optional parameters
|
||||
if (extName && typeof extName === "object") {
|
||||
ch = stack;
|
||||
stack = extName;
|
||||
extName = undefined;
|
||||
} else if (
|
||||
typeof extName !== "string" &&
|
||||
extName !== null &&
|
||||
extName !== undefined
|
||||
) {
|
||||
throw new TypeError("Extension name parameter needs to be a string.");
|
||||
}
|
||||
|
||||
if (
|
||||
stack &&
|
||||
typeof stack === "object" &&
|
||||
Object.prototype.toString.call(stack) !== "[object Error]"
|
||||
) {
|
||||
ch = stack;
|
||||
stack = undefined;
|
||||
} else if (
|
||||
typeof stack !== "object" &&
|
||||
typeof stack !== "string" &&
|
||||
stack
|
||||
) {
|
||||
throw new TypeError(
|
||||
"Error stack parameter needs to be either a string or an instance of Error object."
|
||||
);
|
||||
}
|
||||
|
||||
if (next) {
|
||||
// Invoke next() handler, like when it is used in Express
|
||||
if (errorCode == 500) {
|
||||
next(new Error("Internal SVR.JS core error"));
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
} else {
|
||||
// Invoke default server error handler
|
||||
defaultServerError(errorCode, extName, stack, ch);
|
||||
}
|
||||
};
|
||||
|
||||
// Function to perform HTTP redirection to a specified destination URL
|
||||
res.redirect = (destination, isTemporary, keepMethod, customHeaders) => {
|
||||
// If keepMethod is a object, then save it to customHeaders
|
||||
if (typeof keepMethod == "object") customHeaders = keepMethod;
|
||||
|
||||
// If isTemporary is a object, then save it to customHeaders
|
||||
if (typeof isTemporary == "object") customHeaders = isTemporary;
|
||||
|
||||
// If customHeaders are not provided, get the default custom headers
|
||||
if (customHeaders === undefined) customHeaders = config.getCustomHeaders();
|
||||
|
||||
// Set the "Location" header to the destination URL
|
||||
customHeaders["Location"] = destination;
|
||||
|
||||
// Determine the status code for redirection based on the isTemporary and keepMethod flags
|
||||
const statusCode = keepMethod
|
||||
? isTemporary
|
||||
? 307
|
||||
: 308
|
||||
: isTemporary
|
||||
? 302
|
||||
: 301;
|
||||
|
||||
// Write the response header with the appropriate status code and message
|
||||
res.writeHead(statusCode, statusCodes[statusCode], customHeaders);
|
||||
|
||||
// End the response
|
||||
res.end();
|
||||
|
||||
// Return from the function
|
||||
return;
|
||||
};
|
||||
|
||||
try {
|
||||
res.head = fs.existsSync(`${config.wwwroot}/.head`)
|
||||
? fs.readFileSync(`${config.wwwroot}/.head`).toString()
|
||||
: fs.existsSync(`${config.wwwroot}/head.html`)
|
||||
? fs.readFileSync(`${config.wwwroot}/head.html`).toString()
|
||||
: ""; // header
|
||||
res.foot = fs.existsSync(`${config.wwwroot}/.foot`)
|
||||
? fs.readFileSync(`${config.wwwroot}/.foot`).toString()
|
||||
: fs.existsSync(`${config.wwwroot}/foot.html`)
|
||||
? fs.readFileSync(`${config.wwwroot}/foot.html`).toString()
|
||||
: ""; // footer
|
||||
} catch (err) {
|
||||
res.error(500, err);
|
||||
}
|
||||
|
||||
// Authenticated user variable
|
||||
req.authUser = null;
|
||||
|
||||
if (req.url == "*") {
|
||||
// Handle "*" URL
|
||||
if (req.method == "OPTIONS") {
|
||||
// Respond with list of methods
|
||||
let hdss = config.getCustomHeaders();
|
||||
hdss["Allow"] = "GET, POST, HEAD, OPTIONS";
|
||||
res.writeHead(204, statusCodes[204], hdss);
|
||||
res.end();
|
||||
return;
|
||||
} else {
|
||||
// SVR.JS doesn't understand that request, so throw an 400 error
|
||||
res.error(400);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (req.headers["expect"] && req.headers["expect"] != "100-continue") {
|
||||
// Expectations not met.
|
||||
res.error(417);
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.method == "CONNECT") {
|
||||
// CONNECT requests should be handled in "connect" event.
|
||||
res.error(501);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isForwardedValid) {
|
||||
res.error(400);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
req.parsedURL = parseURL(
|
||||
req.url,
|
||||
"http" +
|
||||
(req.socket.encrypted ? "s" : "") +
|
||||
"://" +
|
||||
(req.headers.host
|
||||
? req.headers.host
|
||||
: config.domain
|
||||
? config.domain
|
||||
: "unknown.invalid")
|
||||
);
|
||||
} catch (err) {
|
||||
res.error(400, err);
|
||||
return;
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
|
||||
// Call the next middleware function
|
||||
const nextMiddleware = () => {
|
||||
let currentMiddleware = middleware[index++];
|
||||
while (
|
||||
req.isProxy &&
|
||||
currentMiddleware &&
|
||||
currentMiddleware.proxySafe !== false &&
|
||||
!(currentMiddleware.proxySafe || currentMiddleware.proxy)
|
||||
) {
|
||||
currentMiddleware = middleware[index++];
|
||||
}
|
||||
if (currentMiddleware) {
|
||||
try {
|
||||
currentMiddleware(req, res, logFacilities, config, nextMiddleware);
|
||||
} catch (err) {
|
||||
res.error(500, err);
|
||||
}
|
||||
} else {
|
||||
res.error(404);
|
||||
}
|
||||
};
|
||||
|
||||
// Handle middleware
|
||||
nextMiddleware();
|
||||
}
|
||||
|
||||
function init(config) {
|
||||
coreConfig = config;
|
||||
return requestHandler;
|
||||
}
|
||||
|
||||
module.exports = init;
|
||||
module.exports.init = init;
|
|
@ -714,7 +714,7 @@ function requestHandler(req, res) {
|
|||
let index = 0;
|
||||
|
||||
// Call the next middleware function
|
||||
const next = () => {
|
||||
const nextMiddleware = () => {
|
||||
let currentMiddleware = middleware[index++];
|
||||
while (
|
||||
req.isProxy &&
|
||||
|
@ -726,7 +726,7 @@ function requestHandler(req, res) {
|
|||
}
|
||||
if (currentMiddleware) {
|
||||
try {
|
||||
currentMiddleware(req, res, logFacilities, config, next);
|
||||
currentMiddleware(req, res, logFacilities, config, nextMiddleware);
|
||||
} catch (err) {
|
||||
res.error(500, err);
|
||||
}
|
||||
|
@ -736,7 +736,7 @@ function requestHandler(req, res) {
|
|||
};
|
||||
|
||||
// Handle middleware
|
||||
next();
|
||||
nextMiddleware();
|
||||
}
|
||||
|
||||
module.exports = (serverconsoleO, middlewareO) => {
|
||||
|
|
|
@ -854,7 +854,7 @@ if (!disableMods) {
|
|||
}
|
||||
|
||||
// Middleware
|
||||
let middleware = [
|
||||
const middleware = [
|
||||
require("./middleware/urlSanitizer.js"),
|
||||
require("./middleware/redirects.js"),
|
||||
require("./middleware/blocklist.js"),
|
||||
|
|
20
src/utils/generateServerStringCore.js
Normal file
20
src/utils/generateServerStringCore.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
const svrjsInfo = require("../../svrjs.core.json");
|
||||
const { version, name } = svrjsInfo;
|
||||
const getOS = require("./getOS.js");
|
||||
|
||||
function generateServerString(exposeServerVersion) {
|
||||
return exposeServerVersion
|
||||
? `${name.replace(/ /g, "-")}/${version} (${getOS()}; ${
|
||||
process.isBun
|
||||
? "Bun/v" + process.versions.bun + "; like Node.JS/" + process.version
|
||||
: process.versions && process.versions.deno
|
||||
? "Deno/v" +
|
||||
process.versions.deno +
|
||||
"; like Node.JS/" +
|
||||
process.version
|
||||
: "Node.JS/" + process.version
|
||||
})`
|
||||
: name.replace(/ /g, "-");
|
||||
}
|
||||
|
||||
module.exports = generateServerString;
|
23
svrjs.core.json
Normal file
23
svrjs.core.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"version": "Nightly-GitNext",
|
||||
"name": "SVR.JS Core",
|
||||
"externalPackages": ["mime-types"],
|
||||
"packageJSON": {
|
||||
"name": "svrjs-core",
|
||||
"description": "A library for static file serving, built from SVR.JS source code.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.svrjs.org/svrjs/svrjs.git"
|
||||
},
|
||||
"keywords": [
|
||||
"static",
|
||||
"web",
|
||||
"server",
|
||||
"files",
|
||||
"mime",
|
||||
"middleware"
|
||||
],
|
||||
"homepage": "https://svrjs.org",
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
Reference in a new issue