From 67680fc97ffb2ef254e0a6dae5caacff8f8195ac Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Sun, 7 Apr 2024 15:53:24 +0200 Subject: [PATCH] Update to SVR.JS 3.14.11 --- index.html | 8 +- licenses/index.html | 8 +- svr.js | 510 +++++++++++++++++++++----------------------- tests.html | 4 +- 4 files changed, 256 insertions(+), 274 deletions(-) diff --git a/index.html b/index.html index a3f40ab..7f98409 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ - SVR.JS 3.14.10 + SVR.JS 3.14.11 -

Welcome to SVR.JS 3.14.10

+

Welcome to SVR.JS 3.14.11



@@ -84,7 +84,9 @@

Changes:

Tests
diff --git a/licenses/index.html b/licenses/index.html index 815dab5..17ce22a 100644 --- a/licenses/index.html +++ b/licenses/index.html @@ -1,7 +1,7 @@ - SVR.JS 3.14.10 Licenses + SVR.JS 3.14.11 Licenses -

SVR.JS 3.14.10 Licenses

-

SVR.JS 3.14.10

+

SVR.JS 3.14.11 Licenses

+

SVR.JS 3.14.11

MIT License

@@ -37,7 +37,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-

Packages used by SVR.JS 3.14.10

+

Packages used by SVR.JS 3.14.11

License: MIT
diff --git a/svr.js b/svr.js index 7ba07b9..e7dccc8 100644 --- a/svr.js +++ b/svr.js @@ -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")) { diff --git a/tests.html b/tests.html index a37ec52..16b7770 100644 --- a/tests.html +++ b/tests.html @@ -1,7 +1,7 @@ - SVR.JS 3.14.10 Tests + SVR.JS 3.14.11 Tests -

SVR.JS 3.14.10 Tests

+

SVR.JS 3.14.11 Tests

Directory (without trailing slash)

Directory (with query)