UNPKG

hfs

Version:
561 lines (560 loc) 19.9 kB
"use strict"; 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 __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ANY_ACCOUNT = exports.WHO_NO_ONE = exports.WHO_ANYONE = exports.LIST = exports.CFG = exports.THEME_OPTIONS = exports.SORT_BY_OPTIONS = exports.FRONTEND_OPTIONS = exports.MAX_TILE_SIZE = exports.DAY = exports.HOUR = exports.MINUTE = exports.WIKI_URL = exports.REPO_URL = exports.WEBSITE = void 0; exports.isWhoObject = isWhoObject; exports.formatBytes = formatBytes; exports.formatSpeed = formatSpeed; exports.prefix = prefix; exports.join = join; exports.wait = wait; exports.haveTimeout = haveTimeout; exports.objSameKeys = objSameKeys; exports.objFromKeys = objFromKeys; exports.enforceFinal = enforceFinal; exports.removeFinal = removeFinal; exports.enforceStarting = enforceStarting; exports.removeStarting = removeStarting; exports.strinsert = strinsert; exports.splitAt = splitAt; exports.stringAfter = stringAfter; exports.truthy = truthy; exports.onlyTruthy = onlyTruthy; exports.setHidden = setHidden; exports.try_ = try_; exports.with_ = with_; exports.formatPerc = formatPerc; exports.wantArray = wantArray; exports._log = _log; exports._dbg = _dbg; exports.pendingPromise = pendingPromise; exports.basename = basename; exports.dirname = dirname; exports.tryJson = tryJson; exports.swap = swap; exports.isOrderedEqual = isOrderedEqual; exports.findDefined = findDefined; exports.newObj = newObj; exports.waitFor = waitFor; exports.getOrSet = getOrSet; exports.randomId = randomId; exports.objRenameKey = objRenameKey; exports.typedKeys = typedKeys; exports.typedEntries = typedEntries; exports.hasProp = hasProp; exports.throw_ = throw_; exports.filterMapGenerator = filterMapGenerator; exports.asyncGeneratorToArray = asyncGeneratorToArray; exports.repeat = repeat; exports.formatTimestamp = formatTimestamp; exports.formatTime = formatTime; exports.formatDate = formatDate; exports.isNumeric = isNumeric; exports.isPrimitive = isPrimitive; exports.isIP = isIP; exports.isWindowsDrive = isWindowsDrive; exports.isTimestampString = isTimestampString; exports.isEqualLax = isEqualLax; exports.xlate = xlate; exports.normalizeHost = normalizeHost; exports.isIpLocalHost = isIpLocalHost; exports.isIpLan = isIpLan; exports.ipForUrl = ipForUrl; exports.escapeHTML = escapeHTML; exports.promiseBestEffort = promiseBestEffort; exports.pathEncode = pathEncode; exports.runAt = runAt; exports.makeMatcher = makeMatcher; exports.matches = matches; exports.replace = replace; exports.inCommon = inCommon; exports.mapFilter = mapFilter; exports.callable = callable; exports.safeDecodeURIComponent = safeDecodeURIComponent; exports.popKey = popKey; exports.patchKey = patchKey; exports.shortenAgent = shortenAgent; // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt // all content here is shared between client and server const lodash_1 = __importDefault(require("lodash")); const picomatch_1 = __importDefault(require("picomatch/lib/picomatch")); const cross_const_1 = require("./cross-const"); // point directly to the browser-compatible source __exportStar(require("./cross-const"), exports); exports.WEBSITE = 'https://rejetto.com/hfs/'; exports.REPO_URL = `https://github.com/${cross_const_1.HFS_REPO}/`; exports.WIKI_URL = exports.REPO_URL + 'wiki/'; exports.MINUTE = 60000; exports.HOUR = 60 * exports.MINUTE; exports.DAY = 24 * exports.HOUR; exports.MAX_TILE_SIZE = 10; exports.FRONTEND_OPTIONS = { file_menu_on_link: true, tile_size: 0, sort_by: 'name', invert_order: false, folders_first: true, sort_numerics: false, title_with_path: true, theme: '', auto_play_seconds: 5, disableTranslation: false, }; exports.SORT_BY_OPTIONS = ['name', 'extension', 'size', 'time', 'creation']; exports.THEME_OPTIONS = { auto: '', light: 'light', dark: 'dark' }; // had found an interesting way to infer a type from all the calls to defineConfig (by the literals passed), but would not be usable also by admin-panel exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknown', 'dynamic_dns_url', 'log', 'error_log', 'log_rotation', 'dont_log_net', 'log_gui', 'log_api', 'log_ua', 'log_spam', 'track_ips', 'max_downloads', 'max_downloads_per_ip', 'max_downloads_per_account', 'roots', 'force_address', 'split_uploads', 'force_lang', 'suspend_plugins', 'base_url', 'size_1024', 'disable_custom_html', 'comments_storage']); exports.LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' }; exports.WHO_ANYONE = true; exports.WHO_NO_ONE = false; exports.WHO_ANY_ACCOUNT = '*'; exports.defaultPerms = { can_see: 'can_read', can_read: exports.WHO_ANYONE, can_list: 'can_read', can_upload: exports.WHO_NO_ONE, can_delete: exports.WHO_NO_ONE, can_archive: 'can_read' }; exports.PERM_KEYS = typedKeys(exports.defaultPerms); function constMap(a) { return Object.fromEntries(a.map(x => [x, x])); } function isWhoObject(v) { return v !== null && typeof v === 'object' && !Array.isArray(v); } const MULTIPLIERS = ['', 'K', 'M', 'G', 'T']; function formatBytes(n, { post = 'B', k = 0, digits = NaN, sep = ' ' } = {}) { var _a; if (isNaN(Number(n)) || n < 0) return ''; k || (k = (_a = formatBytes.k) !== null && _a !== void 0 ? _a : 1024); // default value const i = n && Math.min(MULTIPLIERS.length - 1, Math.floor(Math.log2(n) / Math.log2(k))); n /= k ** i; const nAsString = i && !isNaN(digits) ? n.toFixed(digits) : lodash_1.default.round(n, isNaN(digits) ? (n >= 100 ? 0 : 1) : digits); return nAsString + sep + (MULTIPLIERS[i] || '') + post; } // formatBytes function formatSpeed(n, options = {}) { return formatBytes(n, { post: 'B/s', ...options }); } function prefix(pre, v, post = '') { return v ? (pre || '') + v + (post || '') : ''; } function join(a, b, joiner = '/') { if (!b) return a; if (!a) return b; const ends = a.at(-1) === joiner; const starts = b[0] === joiner; return a + (!ends && !starts ? joiner + b : ends && starts ? b.slice(1) : b); } function wait(ms, val) { return new Promise(res => setTimeout(res, ms, val)); } // throws after ms function haveTimeout(ms, job, error) { return Promise.race([job, wait(ms).then(() => { throw error || Error('timeout'); })]); } function objSameKeys(src, newValue) { return Object.fromEntries(Object.entries(src).map(([k, v]) => [k, newValue(v, k)])); } function objFromKeys(src, getValue) { return Object.fromEntries(src.map(k => [k, getValue(k)])); } function enforceFinal(sub, s, evenEmpty = false) { return (s ? !s.endsWith(sub) : evenEmpty) ? s + sub : s; } function removeFinal(sub, s) { return s.endsWith(sub) ? s.slice(0, -sub.length) : s; } function enforceStarting(sub, s, evenEmpty = false) { return (s ? !s.startsWith(sub) : evenEmpty) ? sub + s : s; } function removeStarting(sub, s) { return s.startsWith(sub) ? s.slice(sub.length) : s; } function strinsert(s, at, insert, remove = 0) { return s.slice(0, at) + insert + s.slice(at + remove); } function splitAt(sub, all) { if (typeof sub === 'number') return [all.slice(0, sub), all.slice(sub + 1)]; const i = all.indexOf(sub); return i < 0 ? [all, ''] : [all.slice(0, i), all.slice(i + sub.length)]; } function stringAfter(sub, all) { const i = all.indexOf(sub); return i < 0 ? '' : all.slice(i + sub.length); } function truthy(value) { return Boolean(value); } function onlyTruthy(arr) { return arr.filter(truthy); } function setHidden(dest, src) { return Object.defineProperties(dest, newObj(src, value => ({ enumerable: false, writable: true, value, }))); } function try_(cb, onException) { try { return cb(); } catch (e) { return onException === null || onException === void 0 ? void 0 : onException(e); } } function with_(par, cb) { return cb(par); } function formatPerc(p) { return (p * 100).toFixed(1) + '%'; } function wantArray(x) { return x == null ? [] : Array.isArray(x) ? x : [x]; } function _log(...args) { console.log('**', ...args); return args[args.length - 1]; } function _dbg(x) { debugger; return x; } function pendingPromise() { let takeOut; const ret = new Promise((resolve, reject) => takeOut = { resolve, reject }); return Object.assign(ret, takeOut); } function basename(path) { var _a; return ((_a = path.match(/([^\\/]+)[\\/]*$/)) === null || _a === void 0 ? void 0 : _a[1]) || ''; } function dirname(path) { return path.slice(0, Math.max(0, path.lastIndexOf('/', path.length - 1))); } function tryJson(s, except) { try { return s && JSON.parse(s); } catch (_a) { return except === null || except === void 0 ? void 0 : except(s); } } function swap(obj, k1, k2) { const temp = obj[k1]; obj[k1] = obj[k2]; obj[k2] = temp; return obj; } function isOrderedEqual(a, b) { return lodash_1.default.isEqualWith(a, b, (a1, b1) => { if (!lodash_1.default.isPlainObject(a1) || !lodash_1.default.isPlainObject(b1)) return; const ka = Object.keys(a1); const kb = Object.keys(b1); return ka.length === kb.length && ka.every((ka1, i) => { const kb1 = kb[i]; return ka1 === kb1 && isOrderedEqual(a1[ka1], b1[kb1]); }); }); } function findDefined(a, cb) { if (a) for (const k in a) { const ret = cb(a[k], k); if (ret !== undefined) return ret; } } // create new object with values returned by callback. Keys are kept the same unless you call `setK('myKey')`. Calling `setK` without parameters, which implies `undefined`, will remove the key. function newObj(src, returnNewValue, recur = false // recur on the returned value, if it's an object ) { let _k; const entries = Object.entries(src || {}).map(([k, v]) => { const curDepth = typeof recur === 'number' ? recur : 0; _k = k; let newV = returnNewValue(v, k, setK, curDepth); if ((recur !== false || returnNewValue.length === 4) // if callback is using depth parameter, then it wants recursion && lodash_1.default.isPlainObject(newV)) // is it recurrable? newV = newObj(newV, returnNewValue, curDepth + 1); return _k !== undefined && [_k, newV]; }); return Object.fromEntries(onlyTruthy(entries)); function setK(newK) { _k = newK; return true; // for convenient expression concatenation: setK('newK') && 'newValue' } } // returns undefined if timeout is reached, otherwise the value returned by the callback async function waitFor(cb, { interval = 200, timeout = Infinity } = {}) { const started = Date.now(); while (1) { const res = await cb(); if (res) return res; if (Date.now() - started >= timeout) return; await wait(interval); } } function getOrSet(o, k, creator) { return k in o ? o[k] : (o[k] = creator()); } // 10 chars is 51+bits, 8 is 41+bits function randomId(len = 10) { if (len > 10) return randomId(10) + randomId(len - 10); return Math.random() .toString(36) .substring(2, 2 + len) .replace(/l/g, 'L'); // avoid confusion reading l1 } function objRenameKey(o, from, to) { if (!o || !o.hasOwnProperty(from) || from === to) return; o[to] = o[from]; delete o[from]; return true; } function typedKeys(o) { return Object.keys(o); } function typedEntries(o) { return Object.entries(o); } function hasProp(obj, key) { return key in obj; } function throw_(err) { throw err; } async function* filterMapGenerator(generator, filterMap) { for await (const x of generator) { const res = await filterMap(x); if (res !== undefined) yield res; } } async function asyncGeneratorToArray(generator) { const ret = []; for await (const x of generator) ret.push(x); return ret; } // like setInterval but: async executions don't overlap AND the first execution is immediate function repeat(everyMs, cb) { let stop = false; (async () => { while (!stop) { try { await cb(stopIt); } // you can use stopIt passed as a parameter or the returned value, whatever makes you happy catch (_a) { } await wait(everyMs); } })(); return stopIt; function stopIt() { stop = true; } } function formatTimestamp(x, includeSeconds = true) { if (!x) return ''; if (!(x instanceof Date)) x = new Date(x); return formatDate(x) + ' ' + formatTime(x, includeSeconds); } function formatTime(d, includeSeconds = true) { // bundled nodejs doesn't have locales return String(d.getHours()).padStart(2, '0') + ':' + String(d.getMinutes()).padStart(2, '0') + (includeSeconds ? ':' + String(d.getSeconds()).padStart(2, '0') : ''); } function formatDate(d) { return [d.getFullYear(), d.getMonth() + 1, d.getDate()].map(x => x.toString().padStart(2, '0')).join('-'); } function isNumeric(x) { return lodash_1.default.isNumber(x) || lodash_1.default.isString(x) && !isNaN(Number(x)); } function isPrimitive(x) { return x === null || typeof x !== 'object' && typeof x !== 'function'; // from node's documentation } function isIP(address) { return /^([.:\da-f]+)$/i.test(address); } function isWindowsDrive(s) { return s && /^[a-zA-Z]:$/.test(s); } function isTimestampString(v) { return typeof v === 'string' && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(\.\d+)?Z*$/.test(v); } function isEqualLax(a, b, overrideRule) { var _a; return (_a = overrideRule === null || overrideRule === void 0 ? void 0 : overrideRule(a, b)) !== null && _a !== void 0 ? _a : (a == b || a && b && typeof a === 'object' && typeof b === 'object' && Object.entries(a).every(([k, v]) => isEqualLax(v, b[k], overrideRule)) && Object.entries(b).every(([k, v]) => k in a /*already checked*/ || isEqualLax(v, a[k], overrideRule))); } function xlate(input, table) { var _a; return (_a = table[input]) !== null && _a !== void 0 ? _a : input; } function normalizeHost(host) { return host[0] === '[' ? host.slice(1, host.indexOf(']')) : host === null || host === void 0 ? void 0 : host.split(':')[0]; } function isIpLocalHost(ip) { return ip === '::1' || ip.endsWith('127.0.0.1'); } function isIpLan(ip) { return /^(?:10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|fe80::)/.test(ip); } function ipForUrl(ip) { return ip.includes(':') ? '[' + ip + ']' : ip; } function escapeHTML(text) { return text.replace(/[\u0000-\u002F\u003A-\u0040\u005B-\u0060\u007B-\u00FF]/g, c => '&#' + ('000' + c.charCodeAt(0)).slice(-4) + ';'); } // wait for all, but returns only those that resolved async function promiseBestEffort(promises) { const res = await Promise.allSettled(promises); return res.filter(x => x.status === 'fulfilled').map((x) => x.value); } // encode paths leaving / separator unencoded (not like encodeURIComponent), but still encode # function pathEncode(s) { return s.replace(/[:&#'"% ?\\]/g, escape); // escape() is not utf8, but we are encoding only ascii chars } //unused function pathDecode(s: string) { return decodeURI(s).replace(/%23/g, '#') } // run at a specific point in time, also solving the limit of setTimeout, which doesn't work with +32bit delays function runAt(ts, cb) { let cancel = false; let t; setTimeout(async () => { if (missing() < 0) return; const max = 0x7FFFFFFF; while (!cancel && missing() > max) await wait(max); if (cancel) return; t = setTimeout(cb, missing()); function missing() { return ts - Date.now(); } }); return () => { cancel = true; clearTimeout(t); }; } function makeMatcher(mask, emptyMaskReturns = false) { return mask ? (0, picomatch_1.default)(mask.replace(/^(!)?/, '$1(') + ')', { nocase: true }) // adding () will allow us to use the pipe at root level : () => emptyMaskReturns; } // this is caching all matchers, so don't use it with frequently changing masks. Benchmarks revealed that _.memoize make it slower than not using it, while this simple cache can speed up to 30x function matches(s, mask, emptyMaskReturns = false) { var _a, _b; const cache = (_a = matches).cache || (_a.cache = {}); return (cache[_b = mask + (emptyMaskReturns ? '1' : '0')] || (cache[_b] = makeMatcher(mask, emptyMaskReturns)))(s); } // if delimiter is specified, it is prefixed to symbols. If it contains a space, the part after the space is considered as suffix. function replace(s, symbols, delimiter = '') { const [open, close] = splitAt(' ', delimiter); for (const [k, v] of Object.entries(symbols)) s = s.replaceAll(open + k + close, v); // typescript doesn't handle overloaded functions (like replaceAll) with union types https://stackoverflow.com/a/66510061/646132 return s; } function inCommon(a, b) { let i = 0; const n = a.length; while (i < n && a[i] === b[i]) i++; return i; } function mapFilter(arr, map, filter = (x) => x === undefined, invert = false) { return arr[invert ? 'reduceRight' : 'reduce']((ret, x, idx) => { const y = map(x, idx); if (filter(y)) ret.push(y); // push is much faster than unshift, therefore invert using reduceRight https://measurethat.net/Benchmarks/Show/29/0/array-push-vs-unshift return ret; }, []); } function callable(x, ...args) { return lodash_1.default.isFunction(x) ? x(...args) : x; } function safeDecodeURIComponent(s) { try { return decodeURIComponent(s); } catch (_a) { return s; } } function popKey(o, k) { if (!o) return; const x = o[k]; delete o[k]; return x; } function patchKey(o, k, replacer) { o[k] = replacer(o[k]); return o; } function shortenAgent(agent) { var _a; return lodash_1.default.findKey(BROWSERS, re => re.test(agent)) || ((_a = /^[^/(]+ ?/.exec(agent)) === null || _a === void 0 ? void 0 : _a[0]) || agent; } const BROWSERS = { YaBrowser: /yabrowser/i, AlamoFire: /alamofire/i, Edge: /edge|edga|edgios|edg/i, PhantomJS: /phantomjs/i, Konqueror: /konqueror/i, Amaya: /amaya/i, Epiphany: /epiphany/i, SeaMonkey: /seamonkey/i, Flock: /flock/i, OmniWeb: /omniweb/i, Opera: /opera|OPR\//i, Chromium: /chromium/i, Facebook: /FBA[NV]/, Chrome: /chrome|crios/i, WinJs: /msapphost/i, IE: /msie|trident/i, Firefox: /firefox|fxios/i, Safari: /safari/i, PS5: /playstation 5/i, PS4: /playstation 4/i, PS3: /playstation 3/i, PSP: /playstation portable/i, PS: /playstation/i, Xbox: /xbox/i, UC: /UCBrowser/i, };