UNPKG

hfs

Version:
216 lines (215 loc) 10.5 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 var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.frontEndApis = void 0; exports.notifyClient = notifyClient; const apiMiddleware_1 = require("./apiMiddleware"); const api_get_file_list_1 = require("./api.get_file_list"); const api_auth = __importStar(require("./api.auth")); const events_1 = __importDefault(require("./events")); const util_files_1 = require("./util-files"); const const_1 = require("./const"); const vfs_1 = require("./vfs"); const fs_1 = __importDefault(require("fs")); const promises_1 = require("fs/promises"); const path_1 = require("path"); const upload_1 = require("./upload"); const misc_1 = require("./misc"); const comments_1 = require("./comments"); const SendList_1 = require("./SendList"); const adminApis_1 = require("./adminApis"); const lodash_1 = __importDefault(require("lodash")); const partialFolderSize = {}; exports.frontEndApis = { get_file_list: api_get_file_list_1.get_file_list, ...api_auth, get_notifications({ channel }, ctx) { (0, misc_1.apiAssertTypes)({ string: { channel } }); const list = new SendList_1.SendListReadable(); list.ready(); // on chrome109 EventSource doesn't emit 'open' until something is sent return list.events(ctx, { [NOTIFICATION_PREFIX + channel](name, data) { list.custom(name, data); } }); }, async get_file_details({ uris }, ctx) { if (typeof (uris === null || uris === void 0 ? void 0 : uris[0]) !== 'string') return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad uris'); const isAdmin = (0, adminApis_1.ctxAdminAccess)(ctx); return { details: await Promise.all(uris.map(async (uri) => { if (typeof uri !== 'string') return false; // false means error const node = await (0, vfs_1.urlToNode)(uri, ctx); if (!node) return false; let upload = node.source && await (0, upload_1.getUploadMeta)(node.source).catch(() => undefined); if (!upload) return; if (!isAdmin) upload = lodash_1.default.omit(upload, 'ip'); return { upload }; })) }; }, async create_folder({ uri, name }, ctx) { (0, misc_1.apiAssertTypes)({ string: { uri, name } }); ctx.logExtra(null, { name, target: decodeURI(uri) }); if (!(0, util_files_1.isValidFileName)(name)) return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad name'); const parentNode = await (0, vfs_1.urlToNode)(uri, ctx); if (!parentNode) return new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND, 'parent not found'); const err = (0, vfs_1.statusCodeForMissingPerm)(parentNode, 'can_upload', ctx); if (err) return new apiMiddleware_1.ApiError(err); try { await (0, promises_1.mkdir)((0, path_1.join)(parentNode.source, name)); return {}; } catch (e) { return new apiMiddleware_1.ApiError(e.code === 'EEXIST' ? const_1.HTTP_CONFLICT : const_1.HTTP_BAD_REQUEST, e); } }, async rename({ uri, dest }, ctx) { (0, misc_1.apiAssertTypes)({ string: { uri, dest } }); ctx.logExtra(null, { target: decodeURI(uri), destination: decodeURI(dest) }); const node = await (0, vfs_1.urlToNode)(uri, ctx); if (!node) throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND); if ((0, vfs_1.isRoot)(node) || dest.includes('/') || (0, util_files_1.dirTraversal)(dest)) throw new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN); if (!(0, vfs_1.hasPermission)(node, 'can_delete', ctx)) throw new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED); try { if (!node.source) throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY); const destSource = (0, path_1.join)((0, path_1.dirname)(node.source), dest); await (0, promises_1.rename)(node.source, destSource); (0, comments_1.getCommentFor)(node.source).then(c => { if (!c) return; void (0, comments_1.setCommentFor)(node.source, ''); void (0, comments_1.setCommentFor)(destSource, c); }); return {}; } catch (e) { throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e); } }, async move_files({ uri_from, uri_to }, ctx, override) { (0, misc_1.apiAssertTypes)({ array: { uri_from }, string: { uri_to } }); ctx.logExtra(null, { target: uri_from.map(decodeURI), destination: decodeURI(uri_to) }); const destNode = await (0, vfs_1.urlToNode)(uri_to, ctx); const err = !destNode ? const_1.HTTP_NOT_FOUND : (0, vfs_1.statusCodeForMissingPerm)(destNode, 'can_upload', ctx); if (err) return new apiMiddleware_1.ApiError(err); return { errors: await Promise.all(uri_from.map(async (from1) => { if (typeof from1 !== 'string') return const_1.HTTP_BAD_REQUEST; const srcNode = await (0, vfs_1.urlToNode)(from1, ctx); const src = srcNode === null || srcNode === void 0 ? void 0 : srcNode.source; if (!src) return const_1.HTTP_NOT_FOUND; const dest = (0, path_1.join)(destNode.source, (0, path_1.basename)(src)); if (lodash_1.default.isFunction(override)) return override === null || override === void 0 ? void 0 : override(srcNode, dest); return (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_delete', ctx) || (0, promises_1.rename)(src, dest).catch(async (e) => { if (e.code !== 'EXDEV') throw e; // exdev = different drive await (0, promises_1.copyFile)(src, dest); await (0, promises_1.unlink)(src); }).catch(e => e.code || String(e)); })) }; }, async copy_files(params, ctx) { return exports.frontEndApis.move_files(params, ctx, // same parameters (srcNode, dest) => // but override behavior (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_read', ctx) // .source is checked by move_files || (0, promises_1.copyFile)(srcNode.source, dest, fs_1.default.constants.COPYFILE_EXCL | fs_1.default.constants.COPYFILE_FICLONE) .catch(e => e.code || String(e))); }, async comment({ uri, comment }, ctx) { (0, misc_1.apiAssertTypes)({ string: { uri, comment } }); ctx.logExtra(null, { target: decodeURI(uri) }); const node = await (0, vfs_1.urlToNode)(uri, ctx); if (!node) throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND); if (!(0, vfs_1.hasPermission)(node, 'can_upload', ctx)) throw new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED); if (!node.source) throw new apiMiddleware_1.ApiError(const_1.HTTP_FAILED_DEPENDENCY); await (0, comments_1.setCommentFor)(node.source, comment); return {}; }, async get_folder_size_partial({ id }, ctx) { (0, misc_1.apiAssertTypes)({ string: { id } }); return partialFolderSize[id] || new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND); }, async get_folder_size({ uri, id }, ctx) { (0, misc_1.apiAssertTypes)({ string: { uri } }); const folder = await (0, vfs_1.urlToNode)(uri, ctx); if (!folder) throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND); if (!await (0, vfs_1.nodeIsDirectory)(folder)) throw new apiMiddleware_1.ApiError(const_1.HTTP_METHOD_NOT_ALLOWED); if ((0, vfs_1.statusCodeForMissingPerm)(folder, 'can_list', ctx)) return new apiMiddleware_1.ApiError(ctx.status); let bytes = 0; let files = 0; for await (const n of (0, vfs_1.walkNode)(folder, { ctx, onlyFiles: true, depth: Infinity })) { bytes += await (0, vfs_1.nodeStats)(n).then(x => (x === null || x === void 0 ? void 0 : x.size) || 0, () => 0); files++; partialFolderSize[id] = { bytes, files }; } return (0, misc_1.popKey)(partialFolderSize, id) || { bytes, files }; }, }; function notifyClient(channel, name, data) { if (typeof channel !== 'string') channel = String(channel.query.notifications); events_1.default.emit(NOTIFICATION_PREFIX + channel, name, data); } const NOTIFICATION_PREFIX = 'notificationChannel:';