forked from svrjs/svrjs
Update to SVR.JS 4.0.0-beta2
This commit is contained in:
parent
827e1efd4e
commit
920d942016
9 changed files with 91 additions and 94 deletions
|
@ -5,7 +5,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node esbuild.config.js",
|
"build": "node esbuild.config.js",
|
||||||
"dev": "npm run build && npm run start",
|
"dev": "npm run build && npm run start",
|
||||||
"lint": "eslint --no-error-on-unmatched-pattern src/**/*.js src/*.js tests/**/*.test.js tests/**/*.js utils/**/*.js",
|
"lint": "eslint --no-error-on-unmatched-pattern src/**/*.js src/*.js tests/**/*.test.js tests/**/*.js tests/*.test.js tests/*.js utils/**/*.js utils/*.js",
|
||||||
"lint:fix": "npm run lint -- --fix",
|
"lint:fix": "npm run lint -- --fix",
|
||||||
"start": "node dist/svr.js",
|
"start": "node dist/svr.js",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
|
|
|
@ -22,57 +22,50 @@ function getInitializePath(to) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to check if URL path name is a forbidden path.
|
|
||||||
function isForbiddenPath(decodedHref, match) {
|
function isForbiddenPath(decodedHref, match) {
|
||||||
const forbiddenPath = forbiddenPaths[match];
|
const forbiddenPath = forbiddenPaths[match];
|
||||||
if (!forbiddenPath) return false;
|
if (!forbiddenPath) return false;
|
||||||
|
|
||||||
|
const isWin32 = os.platform() === "win32";
|
||||||
|
const decodedHrefLower = isWin32 ? decodedHref.toLowerCase() : null;
|
||||||
|
|
||||||
if (typeof forbiddenPath === "string") {
|
if (typeof forbiddenPath === "string") {
|
||||||
return (
|
return isWin32
|
||||||
decodedHref === forbiddenPath ||
|
? decodedHrefLower === forbiddenPath.toLowerCase()
|
||||||
(os.platform() === "win32" &&
|
: decodedHref === forbiddenPath;
|
||||||
decodedHref.toLowerCase() === forbiddenPath.toLowerCase())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof forbiddenPath === "object") {
|
if (typeof forbiddenPath === "object") {
|
||||||
return forbiddenPath.some((forbiddenPathSingle) => {
|
return isWin32
|
||||||
return (
|
? forbiddenPath.some((path) => decodedHrefLower === path.toLowerCase())
|
||||||
decodedHref === forbiddenPathSingle ||
|
: forbiddenPath.includes(decodedHref);
|
||||||
(os.platform() === "win32" &&
|
|
||||||
decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to check if URL path name is index of one of defined forbidden paths.
|
|
||||||
function isIndexOfForbiddenPath(decodedHref, match) {
|
function isIndexOfForbiddenPath(decodedHref, match) {
|
||||||
const forbiddenPath = forbiddenPaths[match];
|
const forbiddenPath = forbiddenPaths[match];
|
||||||
if (!forbiddenPath) return false;
|
if (!forbiddenPath) return false;
|
||||||
|
|
||||||
|
const isWin32 = os.platform() === "win32";
|
||||||
|
const decodedHrefLower = isWin32 ? decodedHref.toLowerCase() : null;
|
||||||
|
|
||||||
if (typeof forbiddenPath === "string") {
|
if (typeof forbiddenPath === "string") {
|
||||||
return (
|
const forbiddenPathLower = isWin32 ? forbiddenPath.toLowerCase() : null;
|
||||||
decodedHref === forbiddenPath ||
|
return isWin32
|
||||||
decodedHref.indexOf(forbiddenPath + "/") === 0 ||
|
? decodedHrefLower.indexOf(forbiddenPathLower) == 0
|
||||||
(os.platform() === "win32" &&
|
: decodedHref.indexOf(forbiddenPath) == 0;
|
||||||
(decodedHref.toLowerCase() === forbiddenPath.toLowerCase() ||
|
|
||||||
decodedHref
|
|
||||||
.toLowerCase()
|
|
||||||
.indexOf(forbiddenPath.toLowerCase() + "/") === 0))
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof forbiddenPath === "object") {
|
if (typeof forbiddenPath === "object") {
|
||||||
return forbiddenPath.some((forbiddenPathSingle) => {
|
return isWin32
|
||||||
return (
|
? forbiddenPath.some(
|
||||||
decodedHref === forbiddenPathSingle ||
|
(path) => decodedHrefLower.indexOf(path.toLowerCase()) == 0,
|
||||||
decodedHref.indexOf(forbiddenPathSingle + "/") === 0 ||
|
)
|
||||||
(os.platform() === "win32" &&
|
: forbiddenPath.some((path) => decodedHref.indexOf(path) == 0);
|
||||||
(decodedHref.toLowerCase() === forbiddenPathSingle.toLowerCase() ||
|
|
||||||
decodedHref
|
|
||||||
.toLowerCase()
|
|
||||||
.indexOf(forbiddenPathSingle.toLowerCase() + "/") === 0))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ module.exports = (legacyMod) => {
|
||||||
let middleware = (req, res, logFacilities, config, next) => {
|
let middleware = (req, res, logFacilities, config, next) => {
|
||||||
let ext = req.parsedURL.pathname.match(/[^/]\.([^.]+)$/);
|
let ext = req.parsedURL.pathname.match(/[^/]\.([^.]+)$/);
|
||||||
if (!ext) ext = "";
|
if (!ext) ext = "";
|
||||||
|
else ext = ext[1].toLowerCase();
|
||||||
|
|
||||||
// Function to parse incoming POST data from the request
|
// Function to parse incoming POST data from the request
|
||||||
const parsePostData = (options, callback) => {
|
const parsePostData = (options, callback) => {
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
function matchHostname(hostname, reqHostname) {
|
function matchHostname(hostname, reqHostname) {
|
||||||
if (typeof hostname == "undefined" || hostname == "*") {
|
if (typeof hostname === "undefined" || hostname === "*") {
|
||||||
return true;
|
return true;
|
||||||
} else if (reqHostname && hostname.indexOf("*.") == 0 && hostname != "*.") {
|
} else if (reqHostname && hostname.indexOf("*.") == 0 && hostname !== "*.") {
|
||||||
const hostnamesRoot = hostname.substring(2);
|
const hostnamesRoot = hostname.substring(2);
|
||||||
if (
|
if (
|
||||||
reqHostname == hostnamesRoot ||
|
reqHostname === hostnamesRoot ||
|
||||||
(reqHostname.length > hostnamesRoot.length &&
|
(reqHostname.length > hostnamesRoot.length &&
|
||||||
reqHostname.indexOf("." + hostnamesRoot) ==
|
reqHostname.indexOf("." + hostnamesRoot) ===
|
||||||
reqHostname.length - hostnamesRoot.length - 1)
|
reqHostname.length - hostnamesRoot.length - 1)
|
||||||
) {
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (reqHostname && reqHostname == hostname) {
|
} else if (reqHostname && reqHostname === hostname) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
function sizify(bytes, addI) {
|
function sizify(bytes, addI) {
|
||||||
if (bytes == 0) return "0";
|
if (bytes === 0) return "0";
|
||||||
if (bytes < 0) bytes = -bytes;
|
if (bytes < 0) bytes = -bytes;
|
||||||
|
|
||||||
const prefixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"];
|
const prefixes = ["", "K", "M", "G", "T", "P", "E", "Z", "Y", "R", "Q"];
|
||||||
let prefixIndex = Math.floor(
|
const prefixIndex = Math.min(
|
||||||
Math.log2 ? Math.log2(bytes) / 10 : Math.log(bytes) / (Math.log(2) * 10),
|
Math.floor(Math.log2(bytes) / 10),
|
||||||
|
prefixes.length - 1,
|
||||||
);
|
);
|
||||||
if (prefixIndex >= prefixes.length - 1) prefixIndex = prefixes.length - 1;
|
const prefixIndexTranslated = Math.pow(2, 10 * prefixIndex);
|
||||||
let prefixIndexTranslated = Math.pow(2, 10 * prefixIndex);
|
const decimalPoints = Math.max(
|
||||||
let decimalPoints =
|
2 - Math.floor(Math.log10(bytes / prefixIndexTranslated)),
|
||||||
2 -
|
0,
|
||||||
Math.floor(
|
);
|
||||||
Math.log10
|
|
||||||
? Math.log10(bytes / prefixIndexTranslated)
|
const size =
|
||||||
: Math.log(bytes / prefixIndexTranslated) / Math.log(10),
|
|
||||||
);
|
|
||||||
if (decimalPoints < 0) decimalPoints = 0;
|
|
||||||
return (
|
|
||||||
Math.ceil((bytes / prefixIndexTranslated) * Math.pow(10, decimalPoints)) /
|
Math.ceil((bytes / prefixIndexTranslated) * Math.pow(10, decimalPoints)) /
|
||||||
Math.pow(10, decimalPoints) +
|
Math.pow(10, decimalPoints);
|
||||||
prefixes[prefixIndex] +
|
const prefix = prefixes[prefixIndex];
|
||||||
(prefixIndex > 0 && addI ? "i" : "")
|
const suffix = prefixIndex > 0 && addI ? "i" : "";
|
||||||
);
|
|
||||||
|
return size + prefix + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = sizify;
|
module.exports = sizify;
|
||||||
|
|
|
@ -1,21 +1,16 @@
|
||||||
// Node.JS mojibake URL fixing function
|
// Node.JS mojibake URL fixing function
|
||||||
function fixNodeMojibakeURL(string) {
|
function fixNodeMojibakeURL(string) {
|
||||||
var encoded = "";
|
return Buffer.from(string, "latin1")
|
||||||
|
.reduce((result, value) => {
|
||||||
//Encode URLs
|
if (value > 127) {
|
||||||
Buffer.from(string, "latin1").forEach((value) => {
|
result +=
|
||||||
if (value > 127) {
|
"%" + (value < 16 ? "0" : "") + value.toString(16).toUpperCase();
|
||||||
encoded +=
|
} else {
|
||||||
"%" + (value < 16 ? "0" : "") + value.toString(16).toUpperCase();
|
result += String.fromCharCode(value);
|
||||||
} else {
|
}
|
||||||
encoded += String.fromCodePoint(value);
|
return result;
|
||||||
}
|
}, "")
|
||||||
});
|
.replace(/%[0-9a-f]{2}/gi, (match) => match.toUpperCase());
|
||||||
|
|
||||||
//Upper case the URL encodings
|
|
||||||
return encoded.replace(/%[0-9a-f-A-F]{2}/g, (match) => {
|
|
||||||
return match.toUpperCase();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = fixNodeMojibakeURL;
|
module.exports = fixNodeMojibakeURL;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const url = require("url");
|
const url = require("url");
|
||||||
|
|
||||||
// SVR.JS URL parser function (needed only for SVR.JS 2.x and 3.x mods)
|
// SVR.JS URL parser function (compatible with legacy Node.JS URL parsing function)
|
||||||
function parseURL(uri, prepend) {
|
function parseURL(uri, prepend) {
|
||||||
// Replace newline characters with its respective URL encodings
|
// Replace newline characters with its respective URL encodings
|
||||||
uri = uri.replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
uri = uri.replace(/\r/g, "%0D").replace(/\n/g, "%0A");
|
||||||
|
@ -13,7 +13,7 @@ function parseURL(uri, prepend) {
|
||||||
|
|
||||||
// Parse the URL using regular expression
|
// Parse the URL using regular expression
|
||||||
let parsedURI = uri.match(
|
let parsedURI = uri.match(
|
||||||
/^(?:([^:]+:)(\/\/)?)?(?:([^@/?#*]+)@)?([^:/?#*]+|\[[^*]\/]\])?(?::([0-9]+))?(\*|\/[^?#]*)?(\?[^#]*)?(#[\S\s]*)?/,
|
/^(?:([^:]+:)(\/\/)?)?(?:([^@/?#*]+)@)?([^:/?#*]+|\[[^*\]/]\])?(?::([0-9]+))?(\*|\/[^?#]*)?(\?[^#]*)?(#[\S\s]*)?/,
|
||||||
);
|
);
|
||||||
// Match 1: protocol
|
// Match 1: protocol
|
||||||
// Match 2: slashes after protocol
|
// Match 2: slashes after protocol
|
||||||
|
|
|
@ -1,43 +1,50 @@
|
||||||
// SVR.JS path sanitizer function
|
// SVR.JS path sanitizer function
|
||||||
function sanitizeURL(resource, allowDoubleSlashes) {
|
function sanitizeURL(resource, allowDoubleSlashes) {
|
||||||
if (resource == "*" || resource == "") return resource;
|
if (resource === "*" || resource === "") return resource;
|
||||||
|
|
||||||
// Remove null characters
|
// Remove null characters
|
||||||
resource = resource.replace(/%00|\0/g, "");
|
resource = resource.replace(/%00|\0/g, "");
|
||||||
|
|
||||||
// Check if URL is malformed (e.g. %c0%af or %u002f or simply %as)
|
// Check if URL is malformed (e.g. %c0%af or %u002f or simply %as)
|
||||||
if (resource.match(/%(?:c[01]|f[ef]|(?![0-9a-f]{2}).{2}|.{0,1}$)/i))
|
if (resource.match(/%(?:c[01]|f[ef]|(?![0-9a-f]{2}).{2}|.{0,1}$)/i))
|
||||||
throw new URIError("URI malformed");
|
throw new URIError("URI malformed");
|
||||||
|
|
||||||
// Decode URL-encoded characters while preserving certain characters
|
// Decode URL-encoded characters while preserving certain characters
|
||||||
resource = resource.replace(/%([0-9a-f]{2})/gi, (match, hex) => {
|
resource = resource.replace(/%([0-9a-f]{2})/gi, (match, hex) => {
|
||||||
const decodedChar = String.fromCharCode(parseInt(hex, 16));
|
const decodedChar = String.fromCharCode(parseInt(hex, 16));
|
||||||
return /(?!["<>^`{|}?#%])[!-~]/.test(decodedChar) ? decodedChar : "%" + hex;
|
return /[!$&-;=@-\]_a-z~]/.test(decodedChar) ? decodedChar : match;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Encode certain characters
|
// Encode certain characters
|
||||||
resource = resource.replace(/[<>^`{|}]]/g, (character) => {
|
resource = resource.replace(/[<>^`{|}]/g, (character) => {
|
||||||
const charCode = character.charCodeAt(0);
|
const charCode = character.charCodeAt(0);
|
||||||
return (
|
return (
|
||||||
"%" + (charCode < 16 ? "0" : "") + charCode.toString(16).toUpperCase()
|
"%" + (charCode < 16 ? "0" : "") + charCode.toString(16).toUpperCase()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
let sanitizedResource = resource;
|
|
||||||
// Ensure the resource starts with a slash
|
// Ensure the resource starts with a slash
|
||||||
if (resource[0] != "/") sanitizedResource = "/" + sanitizedResource;
|
if (resource[0] !== "/") resource = "/" + resource;
|
||||||
|
|
||||||
// Convert backslashes to slashes and handle duplicate slashes
|
// Convert backslashes to slashes and handle duplicate slashes
|
||||||
sanitizedResource = sanitizedResource
|
resource = resource
|
||||||
.replace(/\\/g, "/")
|
.replace(/\\/g, "/")
|
||||||
.replace(allowDoubleSlashes ? /\/{3,}/g : /\/+/g, "/");
|
.replace(allowDoubleSlashes ? /\/{3,}/g : /\/+/g, "/");
|
||||||
// Handle relative navigation (e.g., "/./", "/../", "../", "./"), also remove trailing dots in paths
|
|
||||||
sanitizedResource = sanitizedResource
|
// Handle relative navigation (e.g., "/./", "/../", "../", "./") and remove trailing dots in paths
|
||||||
|
resource = resource
|
||||||
.replace(/\/\.(?:\.{2,})?(?=\/|$)/g, "")
|
.replace(/\/\.(?:\.{2,})?(?=\/|$)/g, "")
|
||||||
.replace(/([^./])\.+(?=\/|$)/g, "$1");
|
.replace(/([^./])\.+(?=\/|$)/g, "$1");
|
||||||
while (sanitizedResource.match(/\/(?!\.\.\/)[^/]+\/\.\.(?=\/|$)/)) {
|
|
||||||
sanitizedResource = sanitizedResource.replace(
|
// Remove remaining "../"
|
||||||
/\/(?!\.\.\/)[^/]+\/\.\.(?=\/|$)/g,
|
while (resource.match(/\/(?!\.\.\/)[^/]+\/\.\.(?=\/|$)/)) {
|
||||||
"",
|
resource = resource.replace(/\/(?!\.\.\/)[^/]+\/\.\.(?=\/|$)/g, "");
|
||||||
);
|
|
||||||
}
|
}
|
||||||
sanitizedResource = sanitizedResource.replace(/\/\.\.(?=\/|$)/g, "");
|
resource = resource.replace(/\/\.\.(?=\/|$)/g, "");
|
||||||
if (sanitizedResource.length == 0) return "/";
|
|
||||||
else return sanitizedResource;
|
// If the result has length of 0, return "/", else return the sanitized URL
|
||||||
|
if (resource.length == 0) return "/";
|
||||||
|
else return resource;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = sanitizeURL;
|
module.exports = sanitizeURL;
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
{
|
{
|
||||||
"version": "4.0.0-beta1",
|
"version": "4.0.0-beta2",
|
||||||
"name": "SVR.JS",
|
"name": "SVR.JS",
|
||||||
"documentationURL": "https://svrjs.org/docs/tentative",
|
"documentationURL": "https://svrjs.org/docs/tentative",
|
||||||
"statisticsServerCollectEndpoint": "https://statistics.svrjs.org/collect.svr",
|
"statisticsServerCollectEndpoint": "https://statistics.svrjs.org/collect.svr",
|
||||||
"changes": [
|
"changes": [
|
||||||
"First SVR.JS 4.0.0 beta version"
|
"Fixed the bug with \"ext\" variable for .tar.gz mods.",
|
||||||
|
"Fixed the regular expression in the URL parser.",
|
||||||
|
"Optimized many functions"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue