hfs
Version:
HTTP File Server
216 lines (215 loc) • 10.5 kB
JavaScript
// 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:';
;