1
0
Fork 0
forked from svrjs/svrjs

Update to SVR.JS 3.14.11

This commit is contained in:
Dorian Niemiec 2024-04-07 15:53:24 +02:00
parent 40f100db94
commit 67680fc97f
4 changed files with 256 additions and 274 deletions

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>SVR.JS 3.14.10</title>
<title>SVR.JS 3.14.11</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="UTF-8" />
<style>
@ -12,7 +12,7 @@
</style>
</head>
<body>
<h1>Welcome to SVR.JS 3.14.10</h1>
<h1>Welcome to SVR.JS 3.14.11</h1>
<br/>
<img src="/logo.png" style="width: 256px; max-width: 100%;" />
<br/>
@ -84,7 +84,9 @@
</code>
<p>Changes:</p>
<ul style="display: inline-block; margin: 0;">
<li>Disabled trailing slash removal for proxy requests.</li>
<li>Added CVE-2024-27982 Node.JS vulnerability warning.</li>
<li>Fixed bug with Brotli compression not working, when SVR.JS is running on Bun.</li>
<li>Improved the performance of the server.</li>
</ul>
<p>
<a href="/tests.html">Tests</a><br/>

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>SVR.JS 3.14.10 Licenses</title>
<title>SVR.JS 3.14.11 Licenses</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="UTF-8" />
<style>
@ -12,8 +12,8 @@
</style>
</head>
<body>
<h1>SVR.JS 3.14.10 Licenses</h1>
<h2>SVR.JS 3.14.10</h2>
<h1>SVR.JS 3.14.11 Licenses</h1>
<h2>SVR.JS 3.14.11</h2>
<div style="display: inline-block; text-align: left; border-width: 2px; border-style: solid; border-color: gray; padding: 8px;">
MIT License<br/>
<br/>
@ -37,7 +37,7 @@
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE<br/>
SOFTWARE.<br/>
</div>
<h2>Packages used by SVR.JS 3.14.10</h2>
<h2>Packages used by SVR.JS 3.14.11</h2>
<div style="width: 100%; max-width: 1280px; margin: auto">
<div style="width: 100%; background-color: #ccc; border: 1px solid green; text-align: left; margin: 10px 0;">
<div style="float: right;">License: MIT</div>

510
svr.js
View file

@ -69,7 +69,7 @@ function deleteFolderRecursive(path) {
}
var os = require("os");
var version = "3.14.10";
var version = "3.14.11";
var singlethreaded = false;
if (process.versions) process.versions.svrjs = version; // Inject SVR.JS into process.versions
@ -3571,7 +3571,246 @@ if (!cluster.isPrimary) {
}
function properDirectoryListingAndStaticFileServe() {
if (stats.isDirectory()) {
if (stats.isFile()) {
var acceptEncoding = req.headers["accept-encoding"];
if (!acceptEncoding) acceptEncoding = "";
var filelen = stats.size;
// ETag code
var fileETag = undefined;
if (configJSON.enableETag == undefined || configJSON.enableETag) {
fileETag = generateETag(href, stats);
// Check if the client's request matches the ETag value (If-None-Match)
var clientETag = req.headers["if-none-match"];
if (clientETag === fileETag) {
var headers = getCustomHeaders();
headers.ETag = clientETag;
res.writeHead(304, http.STATUS_CODES[304], headers);
res.end();
return;
}
// Check if the client's request doesn't match the ETag value (If-Match)
var ifMatchETag = req.headers["if-match"];
if (ifMatchETag && ifMatchETag !== "*" && ifMatchETag !== fileETag) {
var headers = getCustomHeaders();
headers.ETag = clientETag;
callServerError(412, headers);
return;
}
}
// Helper function to check if compression is allowed for the file
function canCompress(path, list) {
var canCompress = true;
for (var i = 0; i < list.length; i++) {
if (createRegex(list[i], true).test(path)) {
canCompress = false;
break;
}
}
return canCompress;
}
var isCompressable = true;
try {
// Check for files not to compressed and compression enabling setting. Also check for browser quirks and adjust compression accordingly
if(configJSON.enableCompression !== true || !canCompress(href, dontCompress)) {
isCompressable = false; // Compression is disabled
} else if (ext != "html" && ext != "htm" && ext != "xhtml" && ext != "xht" && ext != "shtml") {
if (/^Mozilla\/4\.[0-9]+(( *\[[^)]*\] *| *)\([^)\]]*\))? *$/.test(req.headers["user-agent"]) && !(/https?:\/\/|[bB][oO][tT]|[sS][pP][iI][dD][eE][rR]|[sS][uU][rR][vV][eE][yY]|MSIE/.test(req.headers["user-agent"]))) {
isCompressable = false; // Netscape 4.x doesn't handle compressed data properly outside of HTML documents.
} else if (/^w3m\/[^ ]*$/.test(req.headers["user-agent"])) {
isCompressable = false; // w3m doesn't handle compressed data properly outside of HTML documents.
}
} else {
if (/^Mozilla\/4\.0[6-8](( *\[[^)]*\] *| *)\([^)\]]*\))? *$/.test(req.headers["user-agent"]) && !(/https?:\/\/|[bB][oO][tT]|[sS][pP][iI][dD][eE][rR]|[sS][uU][rR][vV][eE][yY]|MSIE/.test(req.headers["user-agent"]))) {
isCompressable = false; // Netscape 4.06-4.08 doesn't handle compressed data properly.
}
}
} catch (err) {
callServerError(500, err);
return;
}
// Handle partial content request
if (ext != "html" && req.headers["range"]) {
try {
var rhd = getCustomHeaders();
rhd["Accept-Ranges"] = "bytes";
rhd["Content-Range"] = "bytes */" + filelen;
var regexmatch = req.headers["range"].match(/bytes=([0-9]*)-([0-9]*)/);
if (!regexmatch) {
callServerError(416, rhd);
} else {
// Process the partial content request
var beginOrig = regexmatch[1];
var endOrig = regexmatch[2];
var begin = 0;
var end = filelen - 1;
if (beginOrig == "" && endOrig == "") {
callServerError(416, rhd);
return;
} else if (beginOrig == "") {
begin = end - parseInt(endOrig) + 1;
} else {
begin = parseInt(beginOrig);
if (endOrig != "") end = parseInt(endOrig);
}
if (begin > end || begin < 0 || begin > filelen - 1) {
callServerError(416, rhd);
return;
}
if (end > filelen - 1) end = filelen - 1;
rhd["Content-Range"] = "bytes " + begin + "-" + end + "/" + filelen;
rhd["Content-Length"] = end - begin + 1;
if (!(mime.contentType(ext) == false) && ext != "") rhd["Content-Type"] = mime.contentType(ext);
if (fileETag) rhd["ETag"] = fileETag;
if (req.method != "HEAD") {
var readStream = fs.createReadStream(readFrom, {
start: begin,
end: end
});
readStream.on("error", function (err) {
if (err.code == "ENOENT") {
callServerError(404);
serverconsole.errmessage("Resource not found.");
} else if (err.code == "ENOTDIR") {
callServerError(404); // Assume that file doesn't exist.
serverconsole.errmessage("Resource not found.");
} else if (err.code == "EACCES") {
callServerError(403);
serverconsole.errmessage("Access denied.");
} else if (err.code == "ENAMETOOLONG") {
callServerError(414);
} else if (err.code == "EMFILE") {
callServerError(503);
} else if (err.code == "ELOOP") {
callServerError(508); // The symbolic link loop is detected during file system operations.
serverconsole.errmessage("Symbolic link loop detected.");
} else {
callServerError(500, err);
}
}).on("open", function () {
try {
res.writeHead(206, http.STATUS_CODES[206], rhd);
readStream.pipe(res);
serverconsole.resmessage("Client successfully received content.");
} catch (err) {
callServerError(500, err);
}
});
} else {
res.writeHead(206, http.STATUS_CODES[206], rhd);
res.end();
}
}
} catch (err) {
callServerError(500, err);
}
} else {
try {
var hdhds = getCustomHeaders();
var brNotImplementedBun = false;
// Bun 1.1 has definition for zlib.createBrotliCompress, but throws an error while invoking the function.
if (process.isBun && configJSON.enableCompression === true) {
try {
zlib.createBrotliCompress();
} catch (err) {
brNotImplementedBun = true;
}
}
if (configJSON.enableCompression === true && ext != "br" && filelen > 256 && isCompressable && !brNotImplementedBun && zlib.createBrotliCompress && acceptEncoding.match(/\bbr\b/)) {
hdhds["Content-Encoding"] = "br";
} else if (configJSON.enableCompression === true && ext != "zip" && filelen > 256 && isCompressable && acceptEncoding.match(/\bdeflate\b/)) {
hdhds["Content-Encoding"] = "deflate";
} else if (configJSON.enableCompression === true && ext != "gz" && filelen > 256 && isCompressable && acceptEncoding.match(/\bgzip\b/)) {
hdhds["Content-Encoding"] = "gzip";
} else {
if (ext == "html") {
hdhds["Content-Length"] = head.length + filelen + foot.length;
} else {
hdhds["Content-Length"] = filelen;
}
}
if (ext != "html") hdhds["Accept-Ranges"] = "bytes";
delete hdhds["Content-Type"];
if (!(mime.contentType(ext) == false) && ext != "") hdhds["Content-Type"] = mime.contentType(ext);
if (fileETag) hdhds["ETag"] = fileETag;
if (req.method != "HEAD") {
var readStream = fs.createReadStream(readFrom);
readStream.on("error", function (err) {
if (err.code == "ENOENT") {
callServerError(404);
serverconsole.errmessage("Resource not found.");
} else if (err.code == "ENOTDIR") {
callServerError(404); // Assume that file doesn't exist.
serverconsole.errmessage("Resource not found.");
} else if (err.code == "EACCES") {
callServerError(403);
serverconsole.errmessage("Access denied.");
} else if (err.code == "ENAMETOOLONG") {
callServerError(414);
} else if (err.code == "EMFILE") {
callServerError(503);
} else if (err.code == "ELOOP") {
callServerError(508); // The symbolic link loop is detected during file system operations.
serverconsole.errmessage("Symbolic link loop detected.");
} else {
callServerError(500, err);
}
}).on("open", function () {
try {
var resStream = {};
if (ext != "br" && filelen > 256 && isCompressable && !brNotImplementedBun && zlib.createBrotliCompress && acceptEncoding.match(/\bbr\b/)) {
resStream = zlib.createBrotliCompress();
resStream.pipe(res);
} else if (ext != "zip" && filelen > 256 && isCompressable && acceptEncoding.match(/\bdeflate\b/)) {
resStream = zlib.createDeflateRaw();
resStream.pipe(res);
} else if (ext != "gz" && filelen > 256 && isCompressable && acceptEncoding.match(/\bgzip\b/)) {
resStream = zlib.createGzip();
resStream.pipe(res);
} else {
resStream = res;
}
if (ext == "html") {
function afterWriteCallback() {
readStream.on("end", function () {
resStream.end(foot);
});
readStream.pipe(resStream, {
end: false
});
}
res.writeHead(200, http.STATUS_CODES[200], hdhds);
if (!resStream.write(head)) {
resStream.on("drain", afterWriteCallback);
} else {
process.nextTick(afterWriteCallback);
}
} else {
res.writeHead(200, http.STATUS_CODES[200], hdhds);
readStream.pipe(resStream);
}
serverconsole.resmessage("Client successfully received content.");
} catch (err) {
callServerError(500, err);
}
});
} else {
res.writeHead(200, http.STATUS_CODES[200], hdhds);
res.end();
serverconsole.resmessage("Client successfully received content.");
}
} catch (err) {
callServerError(500, err);
}
}
} else if (stats.isDirectory()) {
// Check if directory listing is enabled in the configuration
if (checkForEnabledDirectoryListing(req.headers.host, req.socket ? req.socket.localAddress : undefined)) {
var customHeaders = getCustomHeaders();
@ -3792,270 +4031,10 @@ if (!cluster.isPrimary) {
callServerError(403);
serverconsole.errmessage("Directory listing is disabled.");
}
} else {
var acceptEncoding = req.headers["accept-encoding"];
if (!acceptEncoding) acceptEncoding = "";
// Check if the requested file exists and handle errors
fs.stat(readFrom, function (err, stats) {
if (err) {
if (err.code == "ENOENT") {
callServerError(404);
serverconsole.errmessage("Resource not found.");
} else if (err.code == "ENOTDIR") {
callServerError(404); // Assume that file doesn't exist.
serverconsole.errmessage("Resource not found.");
} else if (err.code == "EACCES") {
callServerError(403);
serverconsole.errmessage("Access denied.");
} else if (err.code == "ENAMETOOLONG") {
callServerError(414);
} else if (err.code == "EMFILE") {
callServerError(503);
} else if (err.code == "ELOOP") {
callServerError(508); // The symbolic link loop is detected during file system operations.
serverconsole.errmessage("Symbolic link loop detected.");
} else {
callServerError(500, err);
}
return;
}
// Check if the requested resource is a file
if (stats.isDirectory()) {
callServerError(501);
serverconsole.errmessage("SVR.JS expected file but got directory instead.");
return;
} else if (!stats.isFile()) {
callServerError(501);
serverconsole.errmessage("SVR.JS doesn't support block devices, character devices, FIFOs nor sockets.");
return;
}
var filelen = stats.size;
// ETag code
var fileETag = undefined;
if (configJSON.enableETag == undefined || configJSON.enableETag) {
fileETag = generateETag(href, stats);
// Check if the client's request matches the ETag value (If-None-Match)
var clientETag = req.headers["if-none-match"];
if (clientETag === fileETag) {
var headers = getCustomHeaders();
headers.ETag = clientETag;
res.writeHead(304, http.STATUS_CODES[304], headers);
res.end();
return;
}
// Check if the client's request doesn't match the ETag value (If-Match)
var ifMatchETag = req.headers["if-match"];
if (ifMatchETag && ifMatchETag !== "*" && ifMatchETag !== fileETag) {
var headers = getCustomHeaders();
headers.ETag = clientETag;
callServerError(412, headers);
return;
}
}
// Helper function to check if compression is allowed for the file
function canCompress(path, list) {
var canCompress = true;
for (var i = 0; i < list.length; i++) {
if (createRegex(list[i], true).test(path)) {
canCompress = false;
break;
}
}
return canCompress;
}
var isCompressable = true;
try {
isCompressable = canCompress(href, dontCompress);
} catch (err) {
callServerError(500, err);
return;
}
// Check for browser quirks and adjust compression accordingly
if (ext != "html" && ext != "htm" && ext != "xhtml" && ext != "xht" && ext != "shtml" && /^Mozilla\/4\.[0-9]+(( *\[[^)]*\] *| *)\([^)\]]*\))? *$/.test(req.headers["user-agent"]) && !(/https?:\/\/|[bB][oO][tT]|[sS][pP][iI][dD][eE][rR]|[sS][uU][rR][vV][eE][yY]|MSIE/.test(req.headers["user-agent"]))) {
isCompressable = false; // Netscape 4.x doesn't handle compressed data properly outside of HTML documents.
} else if (/^Mozilla\/4\.0[6-8](( *\[[^)]*\] *| *)\([^)\]]*\))? *$/.test(req.headers["user-agent"]) && !(/https?:\/\/|[bB][oO][tT]|[sS][pP][iI][dD][eE][rR]|[sS][uU][rR][vV][eE][yY]|MSIE/.test(req.headers["user-agent"]))) {
isCompressable = false; // Netscape 4.06-4.08 doesn't handle compressed data properly.
} else if (ext != "html" && ext != "htm" && ext != "xhtml" && ext != "xht" && ext != "shtml" && /^w3m\/[^ ]*$/.test(req.headers["user-agent"])) {
isCompressable = false; // w3m doesn't handle compressed data properly outside of HTML documents.
}
// Handle partial content request
if (ext != "html" && req.headers["range"]) {
try {
var rhd = getCustomHeaders();
rhd["Accept-Ranges"] = "bytes";
rhd["Content-Range"] = "bytes */" + filelen;
var regexmatch = req.headers["range"].match(/bytes=([0-9]*)-([0-9]*)/);
if (!regexmatch) {
callServerError(416, rhd);
} else {
// Process the partial content request
var beginOrig = regexmatch[1];
var endOrig = regexmatch[2];
var begin = 0;
var end = filelen - 1;
if (beginOrig == "" && endOrig == "") {
callServerError(416, rhd);
return;
} else if (beginOrig == "") {
begin = end - parseInt(endOrig) + 1;
} else {
begin = parseInt(beginOrig);
if (endOrig != "") end = parseInt(endOrig);
}
if (begin > end || begin < 0 || begin > filelen - 1) {
callServerError(416, rhd);
return;
}
if (end > filelen - 1) end = filelen - 1;
rhd["Content-Range"] = "bytes " + begin + "-" + end + "/" + filelen;
rhd["Content-Length"] = end - begin + 1;
if (!(mime.contentType(ext) == false) && ext != "") rhd["Content-Type"] = mime.contentType(ext);
if (fileETag) rhd["ETag"] = fileETag;
if (req.method != "HEAD") {
var readStream = fs.createReadStream(readFrom, {
start: begin,
end: end
});
readStream.on("error", function (err) {
if (err.code == "ENOENT") {
callServerError(404);
serverconsole.errmessage("Resource not found.");
} else if (err.code == "ENOTDIR") {
callServerError(404); // Assume that file doesn't exist.
serverconsole.errmessage("Resource not found.");
} else if (err.code == "EACCES") {
callServerError(403);
serverconsole.errmessage("Access denied.");
} else if (err.code == "ENAMETOOLONG") {
callServerError(414);
} else if (err.code == "EMFILE") {
callServerError(503);
} else if (err.code == "ELOOP") {
callServerError(508); // The symbolic link loop is detected during file system operations.
serverconsole.errmessage("Symbolic link loop detected.");
} else {
callServerError(500, err);
}
}).on("open", function () {
try {
res.writeHead(206, http.STATUS_CODES[206], rhd);
readStream.pipe(res);
serverconsole.resmessage("Client successfully received content.");
} catch (err) {
callServerError(500, err);
}
});
} else {
res.writeHead(206, http.STATUS_CODES[206], rhd);
res.end();
}
}
} catch (err) {
callServerError(500, err);
}
} else {
try {
var hdhds = getCustomHeaders();
if (configJSON.enableCompression === true && ext != "br" && filelen > 256 && isCompressable && zlib.createBrotliCompress && acceptEncoding.match(/\bbr\b/)) {
hdhds["Content-Encoding"] = "br";
} else if (configJSON.enableCompression === true && ext != "zip" && filelen > 256 && isCompressable && acceptEncoding.match(/\bdeflate\b/)) {
hdhds["Content-Encoding"] = "deflate";
} else if (configJSON.enableCompression === true && ext != "gz" && filelen > 256 && isCompressable && acceptEncoding.match(/\bgzip\b/)) {
hdhds["Content-Encoding"] = "gzip";
} else {
if (ext == "html") {
hdhds["Content-Length"] = head.length + filelen + foot.length;
} else {
hdhds["Content-Length"] = filelen;
}
}
if (ext != "html") hdhds["Accept-Ranges"] = "bytes";
delete hdhds["Content-Type"];
if (!(mime.contentType(ext) == false) && ext != "") hdhds["Content-Type"] = mime.contentType(ext);
if (fileETag) hdhds["ETag"] = fileETag;
if (req.method != "HEAD") {
var readStream = fs.createReadStream(readFrom);
readStream.on("error", function (err) {
if (err.code == "ENOENT") {
callServerError(404);
serverconsole.errmessage("Resource not found.");
} else if (err.code == "ENOTDIR") {
callServerError(404); // Assume that file doesn't exist.
serverconsole.errmessage("Resource not found.");
} else if (err.code == "EACCES") {
callServerError(403);
serverconsole.errmessage("Access denied.");
} else if (err.code == "ENAMETOOLONG") {
callServerError(414);
} else if (err.code == "EMFILE") {
callServerError(503);
} else if (err.code == "ELOOP") {
callServerError(508); // The symbolic link loop is detected during file system operations.
serverconsole.errmessage("Symbolic link loop detected.");
} else {
callServerError(500, err);
}
}).on("open", function () {
try {
res.writeHead(200, http.STATUS_CODES[200], hdhds);
var resStream = {};
if (configJSON.enableCompression === true && ext != "br" && filelen > 256 && isCompressable && zlib.createBrotliCompress && acceptEncoding.match(/\bbr\b/)) {
resStream = zlib.createBrotliCompress();
resStream.pipe(res);
} else if (configJSON.enableCompression === true && ext != "zip" && filelen > 256 && isCompressable && acceptEncoding.match(/\bdeflate\b/)) {
resStream = zlib.createDeflateRaw();
resStream.pipe(res);
} else if (configJSON.enableCompression === true && ext != "gz" && filelen > 256 && isCompressable && acceptEncoding.match(/\bgzip\b/)) {
resStream = zlib.createGzip();
resStream.pipe(res);
} else {
resStream = res;
}
if (ext == "html") {
function afterWriteCallback() {
readStream.on("end", function () {
resStream.end(foot);
});
readStream.pipe(resStream, {
end: false
});
}
if (!resStream.write(head)) {
resStream.on("drain", afterWriteCallback);
} else {
process.nextTick(afterWriteCallback);
}
} else {
readStream.pipe(resStream);
}
serverconsole.resmessage("Client successfully received content.");
} catch (err) {
callServerError(500, err);
}
});
} else {
res.writeHead(200, http.STATUS_CODES[200], hdhds);
res.end();
serverconsole.resmessage("Client successfully received content.");
}
} catch (err) {
callServerError(500, err);
}
}
});
callServerError(501);
serverconsole.errmessage("SVR.JS doesn't support block devices, character devices, FIFOs nor sockets.");
return;
}
}
});
@ -4935,6 +4914,7 @@ function start(init) {
if (crypto.__disabled__ !== undefined) serverconsole.locwarnmessage("Your Node.JS version doesn't have crypto support! The 'crypto' module is essential for providing cryptographic functionality in Node.JS. Without crypto support, certain security features may be unavailable, and some functionality may not work as expected. It's recommended to use a Node.JS version that includes crypto support to ensure the security and proper functioning of your server.");
if (crypto.__disabled__ === undefined && !crypto.scrypt) serverconsole.locwarnmessage("Your JavaScript runtime doesn't have native scrypt support. HTTP authentication involving scrypt hashes will not work.");
if (!process.isBun && /^v(?:[0-9]\.|1[0-7]\.|18\.(?:[0-9]|1[0-8])\.|18\.19\.0|20\.(?:[0-9]|10)\.|20\.11\.0|21\.[0-5]\.|21\.6\.0|21\.6\.1(?![0-9]))/.test(process.version)) serverconsole.locwarnmessage("Your Node.JS version is vulnerable to HTTP server DoS (CVE-2024-22019).");
if (!process.isBun && /^v(?:[0-9]\.|1[0-7]\.|18\.(?:1?[0-9])\.|18\.20\.0|20\.(?:[0-9]|1[01])\.|20\.12\.0|21\.[0-6]\.|21\.7\.0|21\.7\.1(?![0-9]))/.test(process.version)) serverconsole.locwarnmessage("Your Node.JS version is vulnerable to HTTP server request smuggling (CVE-2024-27982).");
if (process.getuid && process.getuid() == 0) serverconsole.locwarnmessage("You're running SVR.JS as root. It's recommended to run SVR.JS as an non-root user. Running SVR.JS as root may increase the risks of OS command execution vulnerabilities.");
if (secure && process.versions && process.versions.openssl && process.versions.openssl.substr(0, 2) == "1.") {
if (new Date() > new Date("11 September 2023")) {

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>SVR.JS 3.14.10 Tests</title>
<title>SVR.JS 3.14.11 Tests</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta charset="UTF-8" />
<style>
@ -12,7 +12,7 @@
</style>
</head>
<body>
<h1>SVR.JS 3.14.10 Tests</h1>
<h1>SVR.JS 3.14.11 Tests</h1>
<h2>Directory (without trailing slash)</h2>
<iframe src="/testdir" width="50%" height="300px"></iframe>
<h2>Directory (with query)</h2>