From 34fdd89d3706ddfccbdc09d4059e6481c667bba1 Mon Sep 17 00:00:00 2001 From: Dorian Niemiec Date: Wed, 1 May 2024 07:20:56 +0200 Subject: [PATCH] Removed blocking calls from directory listing function --- svr.js | 426 ++++++++++++++++++++++++++++++++------------------------- 1 file changed, 241 insertions(+), 185 deletions(-) diff --git a/svr.js b/svr.js index 3b80f5c..0dda952 100644 --- a/svr.js +++ b/svr.js @@ -3850,200 +3850,256 @@ if (!cluster.isPrimary) { customHeaders["Content-Type"] = "text/html; charset=utf-8"; res.writeHead(200, http.STATUS_CODES[200], customHeaders); - // Read custom header and footer content (if available) - var customDirListingHeader = fs.existsSync(("." + decodeURIComponent(href) + "/.dirhead").replace(/\/+/g, "/")) ? - fs.readFileSync(("." + decodeURIComponent(href) + "/.dirhead").replace(/\/+/g, "/")).toString() : - (fs.existsSync(("." + decodeURIComponent(href) + "/HEAD.html").replace(/\/+/g, "/")) && (os.platform != "win32" || href != "/")) ? - fs.readFileSync(("." + decodeURIComponent(href) + "/HEAD.html").replace(/\/+/g, "/")).toString() : - ""; - var customDirListingFooter = fs.existsSync(("." + decodeURIComponent(href) + "/.dirfoot").replace(/\/+/g, "/")) ? - fs.readFileSync(("." + decodeURIComponent(href) + "/.dirfoot").replace(/\/+/g, "/")).toString() : - (fs.existsSync(("." + decodeURIComponent(href) + "/FOOT.html").replace(/\/+/g, "/")) && (os.platform != "win32" || href != "/")) ? - fs.readFileSync(("." + decodeURIComponent(href) + "/FOOT.html").replace(/\/+/g, "/")).toString() : - ""; + var customDirListingHeader = ""; + var customDirListingFooter = ""; - // Check if custom header has HTML tag - var headerHasHTMLTag = customDirListingHeader.replace(/|$)/g, "").match(/])*(?:>|$)/i); - - // Generate HTML head and footer based on configuration and custom content - var htmlHead = (!configJSON.enableDirectoryListingWithDefaultHead || head == "" ? - (!headerHasHTMLTag ? - "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "" : - customDirListingHeader.replace(//i, "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "")) : - head.replace(//i, "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "")) + - (!headerHasHTMLTag ? customDirListingHeader : "") + - "

Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(//g, ">") + "

" + (checkPathLevel(decodeURIComponent(origHref)) < 1 ? "" : ""); - - var htmlFoot = "
Filename Size Date
\"[RET]\"Return

" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&").replace(//g, ">") + (req.headers.host == undefined ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "

" + customDirListingFooter + (!configJSON.enableDirectoryListingWithDefaultHead || foot == "" ? "" : foot); - - if (fs.existsSync("." + decodeURIComponent(href) + "/.maindesc".replace(/\/+/g, "/"))) { - htmlFoot = "
" + fs.readFileSync("." + decodeURIComponent(href) + "/.maindesc".replace(/\/+/g, "/")) + htmlFoot; - } - - fs.readdir("." + decodeURIComponent(href), function (err, list) { - try { - if (err) throw err; - list = list.sort(); - - // Function to get stats for all files in the directory - function getStatsForAllFilesI(fileList, callback, prefix, pushArray, index) { - if (fileList.length == 0) { - callback(pushArray); - return; - } - - fs.stat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) { - if (err) { - fs.lstat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) { - pushArray.push({ - name: fileList[index], - stats: err ? null : stats, - errored: true - }); - if (index < fileList.length - 1) { - getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1); + function getCustomDirListingHeader(callback) { + fs.readFile(("." + decodeURIComponent(href) + "/.dirhead").replace(/\/+/g, "/"), function (err, data) { + if (err) { + if (err.code == "ENOENT" || err.code == "EISDIR") { + if (os.platform != "win32" || href != "/") { + fs.readFile(("." + decodeURIComponent(href) + "/HEAD.html").replace(/\/+/g, "/"), function (err, data) { + if (err) { + if (err.code == "ENOENT" || err.code == "EISDIR") { + callback(); + } else { + callServerError(500, err); + } } else { - callback(pushArray); + customDirListingHeader = data.toString(); + callback(); } }); } else { - pushArray.push({ - name: fileList[index], - stats: stats, - errored: false - }); - if (index < fileList.length - 1) { - getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1); - } else { - callback(pushArray); - } - } - }); - } - - // Wrapper function to get stats for all files - function getStatsForAllFiles(fileList, prefix, callback) { - if (!prefix) prefix = ""; - getStatsForAllFilesI(fileList, callback, prefix, [], 0); - } - - // Get stats for all files in the directory and generate the listing - getStatsForAllFiles(list, "." + decodeURIComponent(href), function (filelist) { - var directoryListingRows = []; - for (var i = 0; i < filelist.length; i++) { - if (filelist[i].name[0] !== ".") { - var estats = filelist[i].stats; - var ename = filelist[i].name; - var eext = ename.match(/\.([^.]+)$/); - eext = eext ? eext[1] : ""; - var emime = eext ? mime.contentType(eext) : false; - if (filelist[i].errored) { - directoryListingRows.push( - "\"[BAD]\"" + - ename.replace(/&/g, "&").replace(//g, ">") + - "-" + - (estats ? estats.mtime.toDateString() : "-") + - "\r\n" - ); - } else { - var entry = "\"[alt]\"" + - ename.replace(/&/g, "&").replace(//g, ">") + - "" + - (estats.isDirectory() ? "-" : sizify(estats.size.toString())) + - "" + - estats.mtime.toDateString() + - "\r\n"; - - // Determine the file type and set the appropriate image and alt text - if (estats.isDirectory()) { - entry = entry.replace("[img]", "/.dirimages/directory.png").replace("[alt]", "[DIR]"); - } else if (!estats.isFile()) { - entry = "\"[alt]\"" + - ename.replace(/&/g, "&").replace(//g, ">") + - "-" + - estats.mtime.toDateString() + - "\r\n"; - - // Determine the special file types (block device, character device, etc.) - if (estats.isBlockDevice()) { - entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[BLK]"); - } else if (estats.isCharacterDevice()) { - entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[CHR]"); - } else if (estats.isFIFO()) { - entry = entry.replace("[img]", "/.dirimages/fifo.png").replace("[alt]", "[FIF]"); - } else if (estats.isSocket()) { - entry = entry.replace("[img]", "/.dirimages/socket.png").replace("[alt]", "[SCK]"); - } - } else if (ename.match(/README|LICEN[SC]E/i)) { - entry = entry.replace("[img]", "/.dirimages/important.png").replace("[alt]", "[IMP]"); - } else if (eext.match(/^(?:[xs]?html?|xml)$/i)) { - entry = entry.replace("[img]", "/.dirimages/html.png").replace("[alt]", (eext == "xml" ? "[XML]" : "[HTM]")); - } else if (eext == "js") { - entry = entry.replace("[img]", "/.dirimages/javascript.png").replace("[alt]", "[JS ]"); - } else if (eext == "php") { - entry = entry.replace("[img]", "/.dirimages/php.png").replace("[alt]", "[PHP]"); - } else if (eext == "css") { - entry = entry.replace("[img]", "/.dirimages/css.png").replace("[alt]", "[CSS]"); - } else if (emime && emime.split("/")[0] == "image") { - entry = entry.replace("[img]", "/.dirimages/image.png").replace("[alt]", (eext == "ico" ? "[ICO]" : "[IMG]")); - } else if (emime && emime.split("/")[0] == "font") { - entry = entry.replace("[img]", "/.dirimages/font.png").replace("[alt]", "[FON]"); - } else if (emime && emime.split("/")[0] == "audio") { - entry = entry.replace("[img]", "/.dirimages/audio.png").replace("[alt]", "[AUD]"); - } else if ((emime && emime.split("/")[0] == "text") || eext == "json") { - entry = entry.replace("[img]", "/.dirimages/text.png").replace("[alt]", (eext == "json" ? "[JSO]" : "[TXT]")); - } else if (emime && emime.split("/")[0] == "video") { - entry = entry.replace("[img]", "/.dirimages/video.png").replace("[alt]", "[VID]"); - } else if (eext.match(/^(?:zip|rar|bz2|[gb7x]z|lzma|tar)$/i)) { - entry = entry.replace("[img]", "/.dirimages/archive.png").replace("[alt]", "[ARC]"); - } else if (eext.match(/^(?:[id]mg|iso|flp)$/i)) { - entry = entry.replace("[img]", "/.dirimages/diskimage.png").replace("[alt]", "[DSK]"); - } else { - entry = entry.replace("[img]", "/.dirimages/other.png").replace("[alt]", "[OTH]"); - } - directoryListingRows.push(entry); - } + callback(); } + } else { + callServerError(500, err); } - - // Push the information about empty directory - if (directoryListingRows.length == 0) { - directoryListingRows.push("No files found"); - } - - // Send the directory listing response - res.end(htmlHead + directoryListingRows.join("") + htmlFoot); - serverconsole.resmessage("Client successfully received content."); - }); - - } catch (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); + customDirListingHeader = data.toString(); + callback(); } - } + }); + } + + function getCustomDirListingFooter(callback) { + fs.readFile(("." + decodeURIComponent(href) + "/.dirfoot").replace(/\/+/g, "/"), function (err, data) { + if (err) { + if (err.code == "ENOENT" || err.code == "EISDIR") { + if (os.platform != "win32" || href != "/") { + fs.readFile(("." + decodeURIComponent(href) + "/FOOT.html").replace(/\/+/g, "/"), function (err, data) { + if (err) { + if (err.code == "ENOENT" || err.code == "EISDIR") { + callback(); + } else { + callServerError(500, err); + } + } else { + customDirListingFooter = data.toString(); + callback(); + } + }); + } else { + callback(); + } + } else { + callServerError(500, err); + } + } else { + customDirListingFooter = data.toString(); + callback(); + } + }); + } + + // Read custom header and footer content (if available) + getCustomDirListingHeader(function () { + getCustomDirListingFooter(function () { + // Check if custom header has HTML tag + var headerHasHTMLTag = customDirListingHeader.replace(/|$)/g, "").match(/])*(?:>|$)/i); + + // Generate HTML head and footer based on configuration and custom content + var htmlHead = (!configJSON.enableDirectoryListingWithDefaultHead || head == "" ? + (!headerHasHTMLTag ? + "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "" : + customDirListingHeader.replace(//i, "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "")) : + head.replace(//i, "Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">") + "")) + + (!headerHasHTMLTag ? customDirListingHeader : "") + + "

Directory: " + decodeURIComponent(origHref).replace(/&/g, "&").replace(//g, ">") + "

" + (checkPathLevel(decodeURIComponent(origHref)) < 1 ? "" : ""); + + var htmlFoot = "
Filename Size Date
\"[RET]\"Return

" + (exposeServerVersion ? "SVR.JS/" + version + " (" + getOS() + "; " + (process.isBun ? ("Bun/v" + process.versions.bun + "; like Node.JS/" + process.version) : ("Node.JS/" + process.version)) + ")" : "SVR.JS").replace(/&/g, "&").replace(//g, ">") + (req.headers.host == undefined ? "" : " on " + String(req.headers.host).replace(/&/g, "&").replace(//g, ">")) + "

" + customDirListingFooter + (!configJSON.enableDirectoryListingWithDefaultHead || foot == "" ? "" : foot); + + if (fs.existsSync("." + decodeURIComponent(href) + "/.maindesc".replace(/\/+/g, "/"))) { + htmlFoot = "
" + fs.readFileSync("." + decodeURIComponent(href) + "/.maindesc".replace(/\/+/g, "/")) + htmlFoot; + } + + fs.readdir("." + decodeURIComponent(href), function (err, list) { + try { + if (err) throw err; + list = list.sort(); + + // Function to get stats for all files in the directory + function getStatsForAllFilesI(fileList, callback, prefix, pushArray, index) { + if (fileList.length == 0) { + callback(pushArray); + return; + } + + fs.stat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) { + if (err) { + fs.lstat((prefix + "/" + fileList[index]).replace(/\/+/g, "/"), function (err, stats) { + pushArray.push({ + name: fileList[index], + stats: err ? null : stats, + errored: true + }); + if (index < fileList.length - 1) { + getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1); + } else { + callback(pushArray); + } + }); + } else { + pushArray.push({ + name: fileList[index], + stats: stats, + errored: false + }); + if (index < fileList.length - 1) { + getStatsForAllFilesI(fileList, callback, prefix, pushArray, index + 1); + } else { + callback(pushArray); + } + } + }); + } + + // Wrapper function to get stats for all files + function getStatsForAllFiles(fileList, prefix, callback) { + if (!prefix) prefix = ""; + getStatsForAllFilesI(fileList, callback, prefix, [], 0); + } + + // Get stats for all files in the directory and generate the listing + getStatsForAllFiles(list, "." + decodeURIComponent(href), function (filelist) { + var directoryListingRows = []; + for (var i = 0; i < filelist.length; i++) { + if (filelist[i].name[0] !== ".") { + var estats = filelist[i].stats; + var ename = filelist[i].name; + var eext = ename.match(/\.([^.]+)$/); + eext = eext ? eext[1] : ""; + var emime = eext ? mime.contentType(eext) : false; + if (filelist[i].errored) { + directoryListingRows.push( + "\"[BAD]\"" + + ename.replace(/&/g, "&").replace(//g, ">") + + "-" + + (estats ? estats.mtime.toDateString() : "-") + + "\r\n" + ); + } else { + var entry = "\"[alt]\"" + + ename.replace(/&/g, "&").replace(//g, ">") + + "" + + (estats.isDirectory() ? "-" : sizify(estats.size.toString())) + + "" + + estats.mtime.toDateString() + + "\r\n"; + + // Determine the file type and set the appropriate image and alt text + if (estats.isDirectory()) { + entry = entry.replace("[img]", "/.dirimages/directory.png").replace("[alt]", "[DIR]"); + } else if (!estats.isFile()) { + entry = "\"[alt]\"" + + ename.replace(/&/g, "&").replace(//g, ">") + + "-" + + estats.mtime.toDateString() + + "\r\n"; + + // Determine the special file types (block device, character device, etc.) + if (estats.isBlockDevice()) { + entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[BLK]"); + } else if (estats.isCharacterDevice()) { + entry = entry.replace("[img]", "/.dirimages/hwdevice.png").replace("[alt]", "[CHR]"); + } else if (estats.isFIFO()) { + entry = entry.replace("[img]", "/.dirimages/fifo.png").replace("[alt]", "[FIF]"); + } else if (estats.isSocket()) { + entry = entry.replace("[img]", "/.dirimages/socket.png").replace("[alt]", "[SCK]"); + } + } else if (ename.match(/README|LICEN[SC]E/i)) { + entry = entry.replace("[img]", "/.dirimages/important.png").replace("[alt]", "[IMP]"); + } else if (eext.match(/^(?:[xs]?html?|xml)$/i)) { + entry = entry.replace("[img]", "/.dirimages/html.png").replace("[alt]", (eext == "xml" ? "[XML]" : "[HTM]")); + } else if (eext == "js") { + entry = entry.replace("[img]", "/.dirimages/javascript.png").replace("[alt]", "[JS ]"); + } else if (eext == "php") { + entry = entry.replace("[img]", "/.dirimages/php.png").replace("[alt]", "[PHP]"); + } else if (eext == "css") { + entry = entry.replace("[img]", "/.dirimages/css.png").replace("[alt]", "[CSS]"); + } else if (emime && emime.split("/")[0] == "image") { + entry = entry.replace("[img]", "/.dirimages/image.png").replace("[alt]", (eext == "ico" ? "[ICO]" : "[IMG]")); + } else if (emime && emime.split("/")[0] == "font") { + entry = entry.replace("[img]", "/.dirimages/font.png").replace("[alt]", "[FON]"); + } else if (emime && emime.split("/")[0] == "audio") { + entry = entry.replace("[img]", "/.dirimages/audio.png").replace("[alt]", "[AUD]"); + } else if ((emime && emime.split("/")[0] == "text") || eext == "json") { + entry = entry.replace("[img]", "/.dirimages/text.png").replace("[alt]", (eext == "json" ? "[JSO]" : "[TXT]")); + } else if (emime && emime.split("/")[0] == "video") { + entry = entry.replace("[img]", "/.dirimages/video.png").replace("[alt]", "[VID]"); + } else if (eext.match(/^(?:zip|rar|bz2|[gb7x]z|lzma|tar)$/i)) { + entry = entry.replace("[img]", "/.dirimages/archive.png").replace("[alt]", "[ARC]"); + } else if (eext.match(/^(?:[id]mg|iso|flp)$/i)) { + entry = entry.replace("[img]", "/.dirimages/diskimage.png").replace("[alt]", "[DSK]"); + } else { + entry = entry.replace("[img]", "/.dirimages/other.png").replace("[alt]", "[OTH]"); + } + directoryListingRows.push(entry); + } + } + } + + // Push the information about empty directory + if (directoryListingRows.length == 0) { + directoryListingRows.push("No files found"); + } + + // Send the directory listing response + res.end(htmlHead + directoryListingRows.join("") + htmlFoot); + serverconsole.resmessage("Client successfully received content."); + }); + + } catch (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); + } + } + }); + }); }); } else { // Directory listing is disabled, call 403 Forbidden error