UNPKG

web-dev-server

Version:

Node.js simple http server for common development or training purposes.

527 lines 27.4 kB
Object.defineProperty(exports, "__esModule", { value: true }); exports.DirectoriesHandler = void 0; var tslib_1 = require("tslib"); var fs_1 = require("fs"); var path_1 = require("path"); var Server_1 = require("../Server"); var DirItem_1 = require("./Directories/DirItem"); var StringHelper_1 = require("../Tools/Helpers/StringHelper"); var NumberHelper_1 = require("../Tools/Helpers/NumberHelper"); var DateHelper_1 = require("../Tools/Helpers/DateHelper"); var Register_1 = require("../Applications/Register"); var DirectoriesHandler = /** @class */ (function () { function DirectoriesHandler(server, cache, filesHandler, errorsHandler) { this.indexFiles = new Map(); this.indexScripts = new Map(); this.server = server; this.cache = cache; this.filesHandler = filesHandler; this.errorsHandler = errorsHandler; var scripts = this.server.GetIndexScripts(), files = this.server.GetIndexFiles(), i, l; for (i = 0, l = scripts.length; i < l; i++) this.indexScripts.set(scripts[i], i); for (i = 0, l = files.length; i < l; i++) this.indexFiles.set(files[i], i); } /** * @summary Display directory content or send index.html file: */ DirectoriesHandler.prototype.HandleDirectory = function (fullPath, requestPath, dirStats, dirItems, statusCode, req, res) { var _this = this; var indexScriptsAndFiles = DirItem_1.DirItem.FindIndex(dirItems, this.indexScripts, this.indexFiles); if (indexScriptsAndFiles.scripts.length > 0) { // try to get stat about any index script handler this.indexScriptOrFileStats(fullPath, indexScriptsAndFiles.scripts, 0, function (indexFullPath, indexScript, indexScriptStat) { // index script handler _this.HandleIndexScript(fullPath, indexScript, indexScriptStat.mtime.getTime(), req, res); }, function () { if (indexScriptsAndFiles.files.length > 0) { // try to get stat about any index file handler _this.indexScriptOrFileStats(fullPath, indexScriptsAndFiles.files, 0, function (indexFullPath, indexFile, indexFileStat) { // index file handler _this.filesHandler.HandleFile(indexFullPath, indexFile, indexFileStat, res); }, function () { if (!_this.server.IsDevelopment()) { _this.HandleForbidden(res); } else { // directory handler _this.renderDirContent(statusCode, dirStats, dirItems, requestPath, fullPath, res); } }); } else { if (!_this.server.IsDevelopment()) { _this.HandleForbidden(res); } else { // directory handler _this.renderDirContent(statusCode, dirStats, dirItems, requestPath, fullPath, res); } } }); } else if (indexScriptsAndFiles.files.length > 0) { this.indexScriptOrFileStats(fullPath, indexScriptsAndFiles.files, 0, function (indexFullPath, indexFile, indexFileStat) { // index file handler _this.filesHandler.HandleFile(indexFullPath, indexFile, indexFileStat, res); }, function () { if (!_this.server.IsDevelopment()) { _this.HandleForbidden(res); } else { // directory handler _this.renderDirContent(statusCode, dirStats, dirItems, requestPath, fullPath, res); } }); } else { if (!this.server.IsDevelopment()) { this.HandleForbidden(res); } else { // directory handler this.renderDirContent(200, dirStats, dirItems, requestPath, fullPath, res); } } }; /** * @summary Process any application in index.js in directory request or on non-existing path request: */ DirectoriesHandler.prototype.HandleIndexScript = function (dirFullPath, indexScript, indexScriptModTime, req, res) { var _this = this; (function () { return tslib_1.__awaiter(_this, void 0, void 0, function () { var cachedModule, moduleInstance, requireCacheKey, e1_1, e2_1, e3_1, e4_1, e5_1, e6_1, e7_1; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: cachedModule = this.cache.GetIndexScriptModuleRecord(dirFullPath); // set up request before index script execution: // @ts-ignore req.setUpIndexScriptExec(this.server.GetDocumentRoot(), dirFullPath, indexScript, this.server.GetBasePath(), res); if (!(cachedModule != null)) return [3 /*break*/, 24]; if (!this.server.IsDevelopment()) return [3 /*break*/, 16]; _a.label = 1; case 1: _a.trys.push([1, 10, , 15]); requireCacheKey = path_1.resolve(dirFullPath + '/' + indexScript); if (!(indexScriptModTime > cachedModule.IndexScriptModTime || !require.cache[requireCacheKey])) return [3 /*break*/, 7]; if (!cachedModule.Instance.Stop) return [3 /*break*/, 5]; _a.label = 2; case 2: _a.trys.push([2, 4, , 5]); return [4 /*yield*/, cachedModule.Instance.Stop(this.server)]; case 3: _a.sent(); return [3 /*break*/, 5]; case 4: e1_1 = _a.sent(); this.errorsHandler.LogError(e1_1, 500, req, res); return [3 /*break*/, 5]; case 5: cachedModule.Instance = null; this.cache.ClearModuleInstanceAndModuleRequireCache(dirFullPath); cachedModule = null; return [4 /*yield*/, this.indexScriptModuleCreate(dirFullPath, indexScript, indexScriptModTime, req, res)]; case 6: moduleInstance = _a.sent(); return [3 /*break*/, 8]; case 7: moduleInstance = cachedModule.Instance; _a.label = 8; case 8: return [4 /*yield*/, this.indexScriptModuleExecute(dirFullPath, indexScript, moduleInstance, req, res)]; case 9: _a.sent(); return [3 /*break*/, 15]; case 10: e2_1 = _a.sent(); this.errorsHandler .LogError(e2_1, 500, req, res) .PrintError(e2_1, 500, req, res); if (!(moduleInstance != null && moduleInstance.Stop)) return [3 /*break*/, 14]; _a.label = 11; case 11: _a.trys.push([11, 13, , 14]); return [4 /*yield*/, moduleInstance.Stop(this.server)]; case 12: _a.sent(); return [3 /*break*/, 14]; case 13: e3_1 = _a.sent(); this.errorsHandler.LogError(e3_1, 500, req, res); return [3 /*break*/, 14]; case 14: this.cache.ClearModuleInstanceAndModuleRequireCache(dirFullPath); return [3 /*break*/, 15]; case 15: return [3 /*break*/, 23]; case 16: _a.trys.push([16, 18, , 23]); moduleInstance = cachedModule.Instance; return [4 /*yield*/, this.indexScriptModuleExecute(dirFullPath, indexScript, moduleInstance, req, res)]; case 17: _a.sent(); return [3 /*break*/, 23]; case 18: e4_1 = _a.sent(); this.errorsHandler .LogError(e4_1, 500, req, res) .PrintError(e4_1, 500, req, res); if (!(moduleInstance != null && moduleInstance.Stop)) return [3 /*break*/, 22]; _a.label = 19; case 19: _a.trys.push([19, 21, , 22]); return [4 /*yield*/, moduleInstance.Stop(this.server)]; case 20: _a.sent(); return [3 /*break*/, 22]; case 21: e5_1 = _a.sent(); this.errorsHandler.LogError(e5_1, 500, req, res); return [3 /*break*/, 22]; case 22: this.cache.ClearModuleInstanceAndModuleRequireCache(dirFullPath); return [3 /*break*/, 23]; case 23: return [3 /*break*/, 32]; case 24: _a.trys.push([24, 27, , 32]); return [4 /*yield*/, this.indexScriptModuleCreate(dirFullPath, indexScript, indexScriptModTime, req, res)]; case 25: moduleInstance = _a.sent(); return [4 /*yield*/, this.indexScriptModuleExecute(dirFullPath, indexScript, moduleInstance, req, res)]; case 26: _a.sent(); return [3 /*break*/, 32]; case 27: e6_1 = _a.sent(); this.errorsHandler .LogError(e6_1, 500, req, res) .PrintError(e6_1, 500, req, res); if (!(moduleInstance != null && moduleInstance.Stop)) return [3 /*break*/, 31]; _a.label = 28; case 28: _a.trys.push([28, 30, , 31]); return [4 /*yield*/, moduleInstance.Stop(this.server)]; case 29: _a.sent(); return [3 /*break*/, 31]; case 30: e7_1 = _a.sent(); this.errorsHandler.LogError(e7_1, 500, req, res); return [3 /*break*/, 31]; case 31: this.cache.ClearModuleInstanceAndModuleRequireCache(dirFullPath); return [3 /*break*/, 32]; case 32: return [2 /*return*/]; } }); }); })(); }; /** * @summary Render and send 403 forbidden page - do not list directory content: */ DirectoriesHandler.prototype.HandleForbidden = function (res) { var outputStr = Server_1.Server.DEFAULTS.RESPONSES.CODES.HTML .replace('%head%', Server_1.Server.DEFAULTS.RESPONSES.CODES.HEAD_NOT_ALLOWED) .replace('%icon%', '') .replace('%body%', Server_1.Server.DEFAULTS.RESPONSES.CODES.HEADER_NOT_ALLOWED); res.SetHeader('Content-Type', 'text/html') .SetCode(403) .SetEncoding('utf-8') .SetBody(outputStr) .Send(); }; /** * @summary Get first index script (or index static file) file system stats: */ DirectoriesHandler.prototype.indexScriptOrFileStats = function (fullPath, files, index, successCallback, errorCallback) { var _this = this; var indexFullPath = fullPath + '/' + files[index]; fs_1.stat(indexFullPath, function (err, itemStat) { if (err == null && itemStat.isFile()) { successCallback(indexFullPath, files[index], itemStat); } else { index++; if (index + 1 > files.length) { errorCallback(); } else { _this.indexScriptOrFileStats(fullPath, files, index, successCallback, errorCallback); } } }); }; /** * @summary Create directory index.js script module instance with optional development require cache resolving: */ DirectoriesHandler.prototype.indexScriptModuleCreate = function (dirFullPath, indexScript, indexScriptModTime, req, res) { return tslib_1.__awaiter(this, void 0, void 0, function () { var appDeclaration, cacheKeysBeforeRequire, cacheKeysAfterRequire, cacheKeysToWatch, appInstance; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: if (this.server.IsDevelopment()) { cacheKeysBeforeRequire = Object.keys(require.cache); appDeclaration = this.indexScriptModuleGetDeclaration(dirFullPath + '/' + indexScript); cacheKeysAfterRequire = Object.keys(require.cache); if (cacheKeysBeforeRequire.length != cacheKeysAfterRequire.length) { cacheKeysToWatch = Register_1.Register.GetRequireCacheDifferenceKeys(cacheKeysBeforeRequire, cacheKeysAfterRequire, dirFullPath + '/' + indexScript, ['/node_modules/']); if (cacheKeysToWatch.length > 0) this.cache.AddWatchHandlers(dirFullPath + '/' + indexScript, cacheKeysToWatch); } } else { appDeclaration = this.indexScriptModuleGetDeclaration(dirFullPath + '/' + indexScript); } appInstance = new appDeclaration(); if (!appInstance.Start) return [3 /*break*/, 2]; return [4 /*yield*/, appInstance.Start(this.server, req, res)]; case 1: _a.sent(); _a.label = 2; case 2: this.cache.SetNewApplicationCacheRecord(appInstance, indexScriptModTime, indexScript, dirFullPath); return [2 /*return*/, appInstance]; } }); }); }; /** * @summary Create directory index.js script module instance with optional development require cache resolving: */ DirectoriesHandler.prototype.indexScriptModuleGetDeclaration = function (modulefullPath) { var appDeclaration = null, startMethodName = 'Start', handleMethodName = 'HttpHandle', stopMethodName = 'Stop', module = require(modulefullPath); if (module && module.prototype && handleMethodName in module.prototype) { appDeclaration = module; } else if (module && module.__esModule) { var moduleKeys = Object.keys(module); var moduleDefaultPrototype = module.default && module.default.prototype ? module.default.prototype : {}; if (moduleKeys.indexOf('default') != -1 && moduleDefaultPrototype && (startMethodName in moduleDefaultPrototype || handleMethodName in moduleDefaultPrototype || stopMethodName in moduleDefaultPrototype)) { appDeclaration = module.default; } else { var moduleKey, moduleItem; for (var i = 0, l = moduleKeys.length; i < l; i++) { moduleKey = moduleKeys[i]; moduleItem = module[moduleKey]; if (moduleItem && moduleItem.prototype && (startMethodName in moduleItem || handleMethodName in moduleItem || stopMethodName in moduleItem)) { appDeclaration = moduleItem; break; } } } } if (appDeclaration === null) throw new Error("Cannot find `IAplication` declaration in directory index script: `" + modulefullPath + "`."); return appDeclaration; }; /** * @summary Process directory index.js script http request handler with optional development require cache resolving: */ DirectoriesHandler.prototype.indexScriptModuleExecute = function (fullPath, indexScript, appInstance, req, res) { return tslib_1.__awaiter(this, void 0, void 0, function () { var isDevelopment, cacheKeysBeforeRequire, cacheKeysAfterRequire, cacheKeysToWatch; return tslib_1.__generator(this, function (_a) { switch (_a.label) { case 0: isDevelopment = this.server.IsDevelopment(); if (!appInstance.HttpHandle) return [3 /*break*/, 4]; if (!isDevelopment) return [3 /*break*/, 2]; cacheKeysBeforeRequire = Object.keys(require.cache); return [4 /*yield*/, appInstance.HttpHandle(req, res)]; case 1: _a.sent(); cacheKeysAfterRequire = Object.keys(require.cache); if (cacheKeysBeforeRequire.length != cacheKeysAfterRequire.length) { cacheKeysToWatch = Register_1.Register.GetRequireCacheDifferenceKeys(cacheKeysBeforeRequire, cacheKeysAfterRequire, fullPath + '/' + indexScript, ['/node_modules/']); if (cacheKeysToWatch.length > 0) this.cache.AddWatchHandlers(fullPath + '/' + indexScript, cacheKeysToWatch); } return [3 /*break*/, 4]; case 2: return [4 /*yield*/, appInstance.HttpHandle(req, res)]; case 3: _a.sent(); _a.label = 4; case 4: if (isDevelopment) this.errorsHandler.SetHandledRequestProperties(null, null); return [2 /*return*/]; } }); }); }; /** * @summary Go through all files and folders in current directory: */ DirectoriesHandler.prototype.renderDirContent = function (statusCode, dirStats, dirItemsNames, reqRelPath, fullPath, res) { var _this = this; var promises = [], dirRows = [], fileRows = []; reqRelPath = StringHelper_1.StringHelper.Trim(reqRelPath, '/'); dirItemsNames.forEach(function (dirItemName, index) { promises.push(new Promise(function (resolve, reject) { fs_1.stat(fullPath + '/' + dirItemName, function (err, itemStats) { if (err != null) return reject(err); _this.renderDirContentRowStats(reqRelPath, dirItemName, itemStats, dirRows, fileRows, resolve); }); })); }); Promise.all(promises).then(function () { _this.handleDirContentRows(statusCode, reqRelPath, fullPath, dirStats, dirRows, fileRows, res); }); }; /** * @summary File system directory item stats handler to complete given `dirRows` and `fileRows` arrays. * @param reqRelPath * @param dirItemName * @param itemStats * @param dirRows * @param fileRows * @param resolve */ DirectoriesHandler.prototype.renderDirContentRowStats = function (reqRelPath, dirItemName, itemStats, dirRows, fileRows, resolve) { if (itemStats.isDirectory()) { dirRows.push(new DirItem_1.DirItem(itemStats.isSymbolicLink() ? DirItem_1.DirItem.TYPE_DIR | DirItem_1.DirItem.TYPE_SYMLINK : DirItem_1.DirItem.TYPE_DIR, dirItemName, this.renderDirContentDirRow(reqRelPath, dirItemName, itemStats))); } else { var dirItemType; if (itemStats.isFile()) { dirItemType = itemStats.isSymbolicLink() ? DirItem_1.DirItem.TYPE_FILE | DirItem_1.DirItem.TYPE_SYMLINK : DirItem_1.DirItem.TYPE_FILE; } else if (itemStats.isBlockDevice()) { dirItemType = DirItem_1.DirItem.TYPE_BLOCK_DEVICE; } else if (itemStats.isCharacterDevice()) { dirItemType = DirItem_1.DirItem.TYPE_CHARACTER_DEVICE; } else if (itemStats.isSocket()) { dirItemType = DirItem_1.DirItem.TYPE_SOCKET; } else if (itemStats.isFIFO()) { dirItemType = DirItem_1.DirItem.TYPE_FIFO; } fileRows.push(new DirItem_1.DirItem(dirItemType, dirItemName, this.renderDirContentFileRow(reqRelPath, dirItemName, itemStats))); } resolve(); }; /** * @summary Display directory content - complete directory row code for directory content: */ DirectoriesHandler.prototype.renderDirContentDirRow = function (reqRelPath, dirItemName, itemStats) { var baseUrl = this.server.GetBasePath(); var hrefParts = []; if (baseUrl) hrefParts.push(baseUrl); if (reqRelPath) hrefParts.push(reqRelPath); hrefParts.push(StringHelper_1.StringHelper.Trim(dirItemName, '/')); return Server_1.Server.DEFAULTS.RESPONSES.CODES.DIR_ROW .replace('%href%', '/' + hrefParts.join('/') + '/') .replace('%path%', StringHelper_1.StringHelper.HtmlEntitiesEncode(dirItemName)) .replace('%date%', DateHelper_1.DateHelper.FormatForDirOutput(itemStats.mtime)); }; /** * @summary Display directory content - complete file row code for directory content: */ DirectoriesHandler.prototype.renderDirContentFileRow = function (reqRelPath, fileItemName, itemStats) { if (itemStats === void 0) { itemStats = null; } var date, size = 0, baseUrl = this.server.GetBasePath(); if (itemStats) { date = itemStats.mtime; size = itemStats.size; } else { date = new Date(); date.setTime(0); } var hrefParts = []; if (baseUrl) hrefParts.push(baseUrl); if (reqRelPath) hrefParts.push(reqRelPath); hrefParts.push(StringHelper_1.StringHelper.Trim(fileItemName, '/')); return Server_1.Server.DEFAULTS.RESPONSES.CODES.FILE_ROW .replace('%href%', '/' + hrefParts.join('/')) .replace('%path%', StringHelper_1.StringHelper.HtmlEntitiesEncode(fileItemName)) .replace('%filesize%', NumberHelper_1.NumberHelper.FormatFileSize(size)) .replace('%date%', DateHelper_1.DateHelper.FormatForDirOutput(date)); }; /** * @summary Display directory content - send directory content html code: */ DirectoriesHandler.prototype.handleDirContentRows = function (statusCode, path, fullPath, dirStats, dirRows, fileRows, res) { var headerCode = '', listCode = '', outputStr = ''; dirRows.sort(DirItem_1.DirItem.SortByPath); fileRows.sort(DirItem_1.DirItem.SortByPath); if (statusCode == 200) { headerCode = this.handleDirReqCompleteHeader(path, fullPath, dirStats); if (path) { dirRows.unshift(new DirItem_1.DirItem(DirItem_1.DirItem.TYPE_DIR, '..', this.renderDirContentDirRow(path, '..', dirStats))); } dirRows.forEach(function (item) { listCode += item.code; }); fileRows.forEach(function (item) { listCode += item.code; }); outputStr = Server_1.Server.DEFAULTS.RESPONSES.CODES.HTML .replace('%head%', Server_1.Server.DEFAULTS.RESPONSES.CODES.HEAD_FOUND .replace('%fullPath%', fullPath)) .replace('%icon%', Server_1.Server.DEFAULTS.RESPONSES.ICONS.FAVICON) .replace('%body%', headerCode + Server_1.Server.DEFAULTS.RESPONSES.CODES.LIST .replace('%tbody%', listCode)); } else /*if (statusCode == 403)*/ { outputStr = Server_1.Server.DEFAULTS.RESPONSES.CODES.HTML .replace('%head%', Server_1.Server.DEFAULTS.RESPONSES.CODES.HEAD_NOT_ALLOWED) .replace('%icon%', Server_1.Server.DEFAULTS.RESPONSES.ICONS.FAVICON) .replace('%body%', Server_1.Server.DEFAULTS.RESPONSES.CODES.HEADER_NOT_ALLOWED); } res.SetHeader('Content-Type', 'text/html') .SetEncoding('utf-8') .SetCode(statusCode) .SetBody(outputStr) .Send(); }; /** * @summary Display directory content - complete heading code for directory content: */ DirectoriesHandler.prototype.handleDirReqCompleteHeader = function (path, fullPath, dirStats) { var headerCode = '', pathStep = '', pathCodes = [], pathExploded = path.split('/'), portStr = this.server.GetPort().toString(), domain = this.server.GetHostname(); if (pathExploded[0] != '') { for (var i = 0, l = pathExploded.length; i < l; i++) { pathStep += ((i > 0) ? '/' : '') + pathExploded[i]; pathCodes.push('<a href="/' + StringHelper_1.StringHelper.HtmlEntitiesEncode(pathStep) + '/">' + pathExploded[i] + '/</a> '); } } else { pathCodes = [path]; } headerCode = Server_1.Server.DEFAULTS.RESPONSES.CODES.HEADER_FOUND .replace('%domain%', StringHelper_1.StringHelper.HtmlEntitiesEncode(domain)) .replace('%port%', portStr) .replace('%path%', pathCodes.join('')) .replace('%fullPath%', fullPath) .replace('%lastMod%', DateHelper_1.DateHelper.FormatForDirOutput(dirStats.mtime)); return headerCode; }; return DirectoriesHandler; }()); exports.DirectoriesHandler = DirectoriesHandler; //# sourceMappingURL=Directory.js.map