"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.VFS_STORED_KEYS = exports.PERM_KEYS = exports.defaultPerms = exports.WHO_ADMIN = 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.objFromKeys = objFromKeys;
exports.enforceFinal = enforceFinal;
exports.removeFinal = removeFinal;
exports.enforceStarting = enforceStarting;
exports.removeStarting = removeStarting;
exports.strinsert = strinsert;
exports.splitAt = splitAt;
exports.stringAfter = stringAfter;
exports.stringBefore = stringBefore;
exports.truthy = truthy;
exports.onlyTruthy = onlyTruthy;
exports.countUniqueBy = countUniqueBy;
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.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.isAsyncIterable = isAsyncIterable;
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.pathDecode = pathDecode;
exports.pathDecodeSegments = pathDecodeSegments;
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.retry = retry;
exports.toMutable = toMutable;
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 = 60_000;
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,
    page_size: 100,
    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',
    'force_webdav_login', 'webdav_initial_auth', 'outbound_proxy', 'mapped_port', 'upnp_enabled', 'show_uploader']);
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.WHO_ADMIN = 'admin';
exports.defaultPerms = {
    can_read: exports.WHO_ANYONE,
    can_see: 'can_read',
    can_list: 'can_read',
    can_archive: 'can_read',
    can_upload: exports.WHO_ADMIN,
    can_delete: exports.WHO_ADMIN,
};
exports.PERM_KEYS = typedKeys(exports.defaultPerms);
exports.VFS_STORED_KEYS = ['name', 'source', 'masks', 'default', 'accept', 'rename',
    'mime', 'url', 'target', 'comment', 'icon', 'order', 'children', ...exports.PERM_KEYS];
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 = ' ' } = {}) {
    if (isNaN(Number(n)) || n < 0)
        return '';
    k ||= formatBytes.k ?? 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) {
    let h;
    return Promise.race([
        job.finally(() => clearTimeout(h)), // don't leave pending timeout if the job is done first
        new Promise((_resolve, reject) => h = setTimeout(() => reject(error || Error('timeout')), ms))
    ]);
}
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 stringBefore(sub, all, returnEmptyWhenSubMissing = true) {
    const i = all.indexOf(sub);
    return i >= 0 ? all.slice(0, i + sub.length - 1) : returnEmptyWhenSubMissing ? '' : all;
}
function truthy(value) {
    return Boolean(value);
}
function onlyTruthy(arr) {
    return arr.filter(truthy);
}
function countUniqueBy(items, keyFn, predicate) {
    // use a Set so unique counting stays linear even on very large live lists
    const seen = new Set();
    let count = 0;
    for (const item of items) {
        if (predicate && !predicate(item))
            continue;
        const key = keyFn(item);
        if (seen.has(key))
            continue;
        seen.add(key);
        count++;
    }
    return count;
}
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?.(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 tryJson(s, except) {
    try {
        return s && JSON.parse(s);
    }
    catch {
        return 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) {
    if (o instanceof Map)
        return o.get(k)
            || with_(creator(), x => o.set(k, x) && x);
    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;
}
function isAsyncIterable(iterable) {
    return Symbol.asyncIterator in iterable;
}
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 { }
            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) {
    return overrideRule?.(a, b) ?? (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) {
    return table[input] ?? input;
}
function normalizeHost(host) {
    return host[0] === '[' ? host.slice(1, host.indexOf(']')) : 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, all = false) {
    return all ? encodeURI(s).replace(/#/g, escape) : s.replace(/[:&#'"% ?\\]/g, escape); // escape() is not utf8, but we are encoding only ascii chars
}
function pathDecode(s) {
    return decodeURI(s).replace(/%23/g, '#');
}
function pathDecodeSegments(s, map = String) {
    // decode segment by segment so reserved escapes are decoded without turning encoded slashes into separators
    return s.split('/').map(x => map(safeDecodeURIComponent(x)).replaceAll('/', '%2F')).join('/');
}
// 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, extglobs = true) {
    if (!mask)
        return () => emptyMaskReturns;
    const wrapped = mask.replace(/^(!)?/, '$1(') + ')'; // adding () will allow us to use the pipe at root level
    const opts = { nocase: true, noextglob: !extglobs };
    // reject patterns that compile to nested quantified groups, causing catastrophic backtracking (CVE-2026-33671)
    return /\)\)[+*]/.test(picomatch_1.default.makeRe(wrapped, opts).source) ? () => false
        : (0, picomatch_1.default)(wrapped, opts);
}
// 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) {
    const cache = matches.cache ||= {};
    return (cache[mask + (emptyMaskReturns ? '1' : '0')] ||= 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, invert = false) {
    const keep = filter ?? ((x) => x !== undefined);
    return arr[invert ? 'reduceRight' : 'reduce']((ret, x, idx) => {
        const y = map(x, idx);
        if (keep(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, fallback = s) {
    try {
        return decodeURIComponent(s);
    }
    catch {
        return fallback;
    }
}
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;
}
// consider the callback successful if it returns a truthy value
async function retry(cb, delay = 1000) {
    let retry = 3;
    while (true) {
        if (await cb())
            break;
        if (!retry--)
            break;
        await wait(delay);
    }
}
function toMutable(value) {
    return Array.isArray(value) ? value.slice() : { ...value };
}
function shortenAgent(agent) {
    return lodash_1.default.findKey(BROWSERS, re => re.test(agent))
        || /^[^/(]+ ?/.exec(agent)?.[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,
    Finder: /WebDAVFS.+Darwin|WebDAVLib/,
    Cyberduck: /^Cyberduck/,
    ForkLift: /^ForkLift/,
    Explorer: /^Microsoft-WebDAV-MiniRedir/,
};
