web-dev-server
Version:
Node.js simple http server for common development or training purposes.
527 lines • 27.4 kB
JavaScript
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