hfs
Version:
HTTP File Server
561 lines (560 loc) • 19.9 kB
JavaScript
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,
};
;