1
0
Fork 0
forked from svrjs/svrjs

Update to SVR.JS 4.0.0-beta2

This commit is contained in:
Dorian Niemiec 2024-08-29 06:55:08 +02:00
parent 827e1efd4e
commit 920d942016
9 changed files with 91 additions and 94 deletions

View file

@ -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"

View file

@ -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;
} }

View file

@ -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) => {

View file

@ -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;

View file

@ -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)
: Math.log(bytes / prefixIndexTranslated) / Math.log(10),
); );
if (decimalPoints < 0) decimalPoints = 0;
return ( const size =
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;

View file

@ -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
Buffer.from(string, "latin1").forEach((value) => {
if (value > 127) { if (value > 127) {
encoded += result +=
"%" + (value < 16 ? "0" : "") + value.toString(16).toUpperCase(); "%" + (value < 16 ? "0" : "") + value.toString(16).toUpperCase();
} else { } else {
encoded += String.fromCodePoint(value); result += String.fromCharCode(value);
} }
}); return result;
}, "")
//Upper case the URL encodings .replace(/%[0-9a-f]{2}/gi, (match) => match.toUpperCase());
return encoded.replace(/%[0-9a-f-A-F]{2}/g, (match) => {
return match.toUpperCase();
});
} }
module.exports = fixNodeMojibakeURL; module.exports = fixNodeMojibakeURL;

View file

@ -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

View file

@ -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;

View file

@ -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"
] ]
} }