UNPKG

hfs

Version:
153 lines (152 loc) 8.68 kB
"use strict"; // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt Object.defineProperty(exports, "__esModule", { value: true }); exports.get_file_list = void 0; exports.paramsToFilter = paramsToFilter; const vfs_1 = require("./vfs"); const apiMiddleware_1 = require("./apiMiddleware"); const promises_1 = require("fs/promises"); const plugins_1 = require("./plugins"); const misc_1 = require("./misc"); const const_1 = require("./const"); const comments_1 = require("./comments"); const path_1 = require("path"); const connections_1 = require("./connections"); const adminApis_1 = require("./adminApis"); const upload_1 = require("./upload"); const SendList_1 = require("./SendList"); function paramsToFilter({ search, wild, searchComment, fileMask }) { search = String(search || '').toLocaleLowerCase(); searchComment = String(searchComment || '').toLocaleLowerCase(); return { depth: search || searchComment ? Infinity : 0, filterName: search > '' && (wild === 'no' ? (s) => s.includes(search) : (0, misc_1.pattern2filter)(search)), fileMask: fileMask > '' && (0, misc_1.pattern2filter)(fileMask), filterComment: searchComment > '' && (wild === 'no' ? (s) => s.includes(searchComment) : (0, misc_1.pattern2filter)(searchComment)) }; } const get_file_list = async ({ uri = '/', offset, limit, c, onlyFolders, onlyFiles, admin, ...rest }, ctx) => { var _a; const node = await (0, vfs_1.urlToNode)(uri, ctx); const list = ctx.get('accept') === 'text/event-stream' ? new SendList_1.SendListReadable() : undefined; if (!node) return fail(const_1.HTTP_NOT_FOUND); admin && (admin = (0, adminApis_1.ctxAdminAccess)(ctx)); // validate 'admin' flag if (await (0, vfs_1.hasDefaultFile)(node, ctx) || !await (0, vfs_1.nodeIsDirectory)(node)) // in case of files without permission, we are provided with the frontend, and the location is the file itself // so, we first check if you have a permission problem, to tell frontend to show login, otherwise we fall back to method_not_allowed, as it's proper for files. return fail(!admin && (0, vfs_1.statusCodeForMissingPerm)(node, 'can_read', ctx) ? undefined : const_1.HTTP_METHOD_NOT_ALLOWED); if (!admin && (0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx)) return fail(); offset = Number(offset); limit = Number(limit); const { filterName, filterComment, fileMask, depth } = paramsToFilter(rest); const walker = (0, vfs_1.walkNode)(node, { ctx: admin ? undefined : ctx, onlyFolders, onlyFiles, depth }); const onDirEntryHandlers = (0, plugins_1.mapPlugins)(plug => plug.onDirEntry); const can_upload = admin || (0, vfs_1.hasPermission)(node, 'can_upload', ctx); const can_delete = admin || (0, vfs_1.hasPermission)(node, 'can_delete', ctx); const fakeChild = await (0, vfs_1.applyParentToChild)({ source: 'dummy-file', original: undefined }, node); // used to check permission; simple but can produce false results; 'original' to simulate a non-vfs node const can_delete_children = admin || (0, vfs_1.hasPermission)(fakeChild, 'can_delete', ctx); const can_archive = admin || (0, vfs_1.hasPermission)(node, 'can_archive', ctx); const can_comment = can_upload && (0, comments_1.areCommentsEnabled)(); const can_overwrite = can_upload && (can_delete || !upload_1.dontOverwriteUploading.get()); const comment = (_a = node.comment) !== null && _a !== void 0 ? _a : await (0, comments_1.getCommentFor)(node.source); const props = { can_archive, can_upload, can_delete, can_delete_children, can_overwrite, can_comment, comment, accept: node.accept, icon: getNodeIcon(node) }; ctx.state.browsing = uri.replace(/\/{2,}/g, '/'); (0, connections_1.updateConnectionForCtx)(ctx); if (!list) return { ...props, list: await (0, misc_1.asyncGeneratorToArray)(produceEntries()) }; setTimeout(async () => { list.props(props); for await (const entry of produceEntries()) list.add(entry); list.close(); }); return list; function fail(code = ctx.status) { if (!list) return new apiMiddleware_1.ApiError(code); list.error(code, true); return list; } async function* produceEntries() { for await (const sub of walker) { let name = (0, vfs_1.getNodeName)(sub); name = (0, path_1.basename)(name) || name; // on windows, basename('C:') === '' if (filterName && !filterName(name) || fileMask && !await (0, vfs_1.nodeIsDirectory)(sub) && !fileMask(name) || filterComment && !filterComment(await (0, comments_1.getCommentFor)(sub.source) || '')) continue; const entry = await nodeToDirEntry(ctx, sub); if (!entry) continue; const cbParams = { entry, ctx, listUri: uri, node: sub }; try { const res = await Promise.all(onDirEntryHandlers.map(cb => cb(cbParams))); if (res.some(x => x === false)) continue; } catch (e) { console.log("a plugin with onDirEntry is causing problems:", e); } if (offset) { --offset; continue; } if (c === 'no' && entry.c) // allow excluding c for smaller payload entry.c = undefined; yield entry; if (limit && !--limit) break; } } function getNodeIcon(node) { var _a; return ((_a = node.icon) === null || _a === void 0 ? void 0 : _a.includes('.')) || node.icon; // true = specific for this file, otherwise is a SYS_ICONS } async function nodeToDirEntry(ctx, node) { var _a; const { source, url } = node; const name = (0, vfs_1.getNodeName)(node); if (url) return name ? { n: name, url, target: node.target } : null; const isFolder = await (0, vfs_1.nodeIsDirectory)(node); try { const st = source ? node.stats || await (0, promises_1.stat)(source).catch(e => { var _a; if (!isFolder || !((_a = node.children) === null || _a === void 0 ? void 0 : _a.length)) // folders with virtual children, keep them throw e; }) : undefined; // permissions of entries are sent as a difference with permissions of parent const pl = node.can_list === misc_1.WHO_NO_ONE ? 'l' : !(0, vfs_1.hasPermission)(node, 'can_list', ctx) ? 'L' : ''; // no download here, but maybe inside? const pr = node.can_read === misc_1.WHO_NO_ONE && !(isFolder && filesInsideCould()) ? 'r' : !(0, vfs_1.hasPermission)(node, 'can_read', ctx) ? 'R' : ''; // for delete, the diff is based on can_delete_children instead of can_delete, because it will produce fewer data const pd = Boolean(can_delete_children) === (0, vfs_1.hasPermission)(node, 'can_delete', ctx) ? '' : can_delete_children ? 'd' : 'D'; const pa = Boolean(can_archive) === (0, vfs_1.hasPermission)(node, 'can_archive', ctx) ? '' : can_archive ? 'a' : 'A'; const pu = !isFolder || Boolean(can_upload) === (0, vfs_1.hasPermission)(node, 'can_upload', ctx) ? '' : can_upload ? 'u' : 'U'; return { n: name + (isFolder ? '/' : ''), c: st === null || st === void 0 ? void 0 : st.birthtime, m: !st || Math.abs(st.mtimeMs - st.birthtimeMs) < 1000 ? undefined : st.mtime, s: isFolder ? undefined : st === null || st === void 0 ? void 0 : st.size, p: (pr + pl + pd + pa + pu) || undefined, order: node.order, comment: (_a = node.comment) !== null && _a !== void 0 ? _a : await (0, comments_1.getCommentFor)(source), icon: getNodeIcon(node), web: await (0, vfs_1.hasDefaultFile)(node, ctx) ? true : undefined, }; } catch (_b) { return null; } function filesInsideCould(n = node) { var _a; return (0, vfs_1.masksCouldGivePermission)(n.masks, 'can_read') || ((_a = n.children) === null || _a === void 0 ? void 0 : _a.some(c => c.can_read || filesInsideCould(c))); // we count on the boolean-compliant nature of the permission type here } } }; exports.get_file_list = get_file_list;