hfs 0.52.0-alpha3 → 0.52.0-beta4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import{g as OF,c as UF}from"./index-25y2vRgI.js";function gF(sF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in sF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(sF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(sF,Symbol.toStringTag,{value:"Module"}))}var dF={exports:{}};/*
1
+ import{g as OF,c as UF}from"./index-SY22o0dA.js";function gF(sF,hF){for(var eF=0;eF<hF.length;eF++){const tF=hF[eF];if(typeof tF!="string"&&!Array.isArray(tF)){for(const w in tF)if(w!=="default"&&!(w in sF)){const lF=Object.getOwnPropertyDescriptor(tF,w);lF&&Object.defineProperty(sF,w,lF.get?lF:{enumerable:!0,get:()=>tF[w]})}}}return Object.freeze(Object.defineProperty(sF,Symbol.toStringTag,{value:"Module"}))}var dF={exports:{}};/*
2
2
  * [js-sha512]{@link https://github.com/emn178/js-sha512}
3
3
  *
4
4
  * @version 0.8.0
@@ -5,8 +5,8 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=0" />
6
6
  <link href="/fontello.css" rel="stylesheet" />
7
7
  <script src="//polyfill.io/v3/polyfill.min.js?features=es2022%2Ces2021%2Ces2020%2Ces2019%2Ces2018%2Ces2017"></script>
8
- <script type="module" crossorigin src="/assets/index-25y2vRgI.js"></script>
9
- <link rel="stylesheet" crossorigin href="/assets/index-9d6fXdcb.css">
8
+ <script type="module" crossorigin src="/assets/index-SY22o0dA.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-Kys6yJtN.css">
10
10
  </head>
11
11
  <body>
12
12
  <noscript>You need to enable JavaScript to run this app.</noscript>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hfs",
3
- "version": "0.52.0-alpha3",
3
+ "version": "0.52.0-beta4",
4
4
  "description": "HTTP File Server",
5
5
  "keywords": [
6
6
  "file server",
@@ -28,7 +28,7 @@ const get_file_list = async ({ uri = '/', offset, limit, search, c, onlyFolders,
28
28
  return fail(const_1.HTTP_METHOD_NOT_ALLOWED);
29
29
  offset = Number(offset);
30
30
  limit = Number(limit);
31
- const filter = (0, misc_1.pattern2filter)(search);
31
+ const filter = (0, misc_1.pattern2filter)(String(search || ''));
32
32
  const walker = (0, vfs_1.walkNode)(node, { ctx: admin ? undefined : ctx, onlyFolders, depth: search ? Infinity : 0 });
33
33
  const onDirEntryHandlers = (0, plugins_1.mapPlugins)(plug => plug.onDirEntry);
34
34
  const can_upload = admin || (0, vfs_1.hasPermission)(node, 'can_upload', ctx);
package/src/api.log.js CHANGED
@@ -8,11 +8,24 @@ const consoleLog_1 = require("./consoleLog");
8
8
  const cross_1 = require("./cross");
9
9
  const events_1 = __importDefault(require("./events"));
10
10
  const log_1 = require("./log");
11
- const fs_1 = require("fs");
12
- const readline_1 = __importDefault(require("readline"));
13
11
  const misc_1 = require("./misc");
14
12
  const SendList_1 = require("./SendList");
13
+ const serveFile_1 = require("./serveFile");
15
14
  exports.default = {
15
+ async get_log_file({ file = 'log', range = '' }, ctx) {
16
+ const log = lodash_1.default.find(log_1.loggers, { name: file });
17
+ if (!log)
18
+ throw cross_1.HTTP_NOT_FOUND;
19
+ if (!log.path)
20
+ throw cross_1.HTTP_NOT_ACCEPTABLE;
21
+ ctx.attachment(log.path);
22
+ if (range)
23
+ ctx.request.header.range = `bytes=${range}`;
24
+ if (ctx.method === 'POST') // this would cause method_not_allowed
25
+ ctx.method = 'GET';
26
+ await (0, serveFile_1.serveFile)(ctx, log.path);
27
+ return null;
28
+ },
16
29
  get_log({ file = 'log' }, ctx) {
17
30
  return new SendList_1.SendListReadable({
18
31
  bufferTime: 10,
@@ -27,48 +40,15 @@ exports.default = {
27
40
  events_1.default.on('console', x => list.add(x));
28
41
  return;
29
42
  }
30
- const logger = log_1.loggers.find(l => l.name === file);
31
- if (!logger)
43
+ if (!lodash_1.default.find(log_1.loggers, { name: file }))
32
44
  return list.error(cross_1.HTTP_NOT_FOUND, true);
33
- const input = (0, fs_1.createReadStream)(logger.path);
34
- input.on('error', async (e) => {
35
- if (e.code === 'ENOENT') // ignore ENOENT, consider it an empty log
36
- return list.ready();
37
- list.error(e.code || e.message);
38
- });
39
- input.on('end', () => list.ready());
40
- input.on('ready', () => {
41
- readline_1.default.createInterface({ input }).on('line', line => {
42
- if (ctx.aborted)
43
- return input.close();
44
- const obj = parse(line);
45
- if (obj)
46
- list.add(obj);
47
- }).on('close', () => {
48
- ctx.res.once('close', (0, misc_1.onOff)(events_1.default, {
49
- [logger.name](entry) {
50
- list.add(entry);
51
- }
52
- }));
53
- });
54
- });
45
+ list.ready();
46
+ ctx.res.once('close', (0, misc_1.onOff)(events_1.default, {
47
+ [file](entry) {
48
+ list.add(entry);
49
+ }
50
+ }));
55
51
  }
56
52
  });
57
- function parse(line) {
58
- const m = /^(.+?) (.+?) (.+?) \[(.{11}):(.{14})] "(\w+) ([^"]+) HTTP\/\d.\d" (\d+) (-|\d+) ?(.*)/.exec(line);
59
- if (!m)
60
- return;
61
- const [, ip, , user, date, time, method, uri, status, length, extra] = m;
62
- return {
63
- ip,
64
- user: user === '-' ? undefined : user,
65
- ts: new Date(date + ' ' + time),
66
- method,
67
- uri,
68
- status: Number(status),
69
- length: length === '-' ? undefined : Number(length),
70
- extra: (0, cross_1.tryJson)((0, cross_1.tryJson)(extra)) || undefined,
71
- };
72
- }
73
53
  },
74
54
  };
@@ -44,11 +44,15 @@ function apiMiddleware(apis) {
44
44
  else if (typeof (v === null || v === void 0 ? void 0 : v[0]) === 'string')
45
45
  v.forEach((x, i) => fixUri(v, i));
46
46
  res = await apiFun(params, ctx);
47
+ if (res === null)
48
+ return;
47
49
  function fixUri(obj, k) {
48
50
  obj[k] = (0, misc_1.removeStarting)(ctx.state.revProxyPath, obj[k]);
49
51
  }
50
52
  }
51
53
  catch (e) {
54
+ if (typeof e === 'number')
55
+ e = new ApiError(const_1.HTTP_NOT_FOUND);
52
56
  res = e;
53
57
  }
54
58
  if (isAsyncGenerator(res))
package/src/const.js CHANGED
@@ -45,7 +45,7 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
45
45
  exports.ORIGINAL_CWD = process.cwd();
46
46
  exports.HFS_STARTED = new Date();
47
47
  const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
48
- exports.BUILD_TIMESTAMP = "2024-02-04T18:56:19.667Z";
48
+ exports.BUILD_TIMESTAMP = "2024-02-11T10:59:36.071Z";
49
49
  const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
50
50
  exports.VERSION = pkg.version;
51
51
  exports.RUNNING_BETA = exports.VERSION.includes('-');
package/src/cross.js CHANGED
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = exports.waitFor = exports.newObj = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = exports.dirname = exports.basename = exports.pendingPromise = exports._dbg = exports._log = exports.wantArray = exports.formatPerc = exports.with_ = exports.try_ = exports.setHidden = exports.onlyTruthy = exports.truthy = exports.splitAt = exports.strinsert = exports.removeStarting = exports.enforceFinal = exports.objSameKeys = exports.haveTimeout = exports.wait = exports.prefix = exports.formatSpeed = exports.formatBytes = exports.isWhoObject = 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 = void 0;
21
- exports.shortenAgent = exports.replace = exports.matches = exports.makeMatcher = exports.runAt = exports.pathEncode = exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.isIpLan = exports.isIpLocalHost = exports.xlate = exports.isEqualLax = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = void 0;
21
+ exports.shortenAgent = exports.inCommon = exports.replace = exports.matches = exports.makeMatcher = exports.runAt = exports.pathEncode = exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.isIpLan = exports.isIpLocalHost = exports.xlate = exports.isEqualLax = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = exports.hasProp = void 0;
22
22
  // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
23
23
  // all content here is shared between client and server
24
24
  const lodash_1 = __importDefault(require("lodash"));
@@ -405,6 +405,14 @@ function replace(s, symbols, delimiter = '') {
405
405
  return s;
406
406
  }
407
407
  exports.replace = replace;
408
+ function inCommon(a, b) {
409
+ let i = 0;
410
+ const n = a.length;
411
+ while (i < n && a[i] === b[i])
412
+ i++;
413
+ return i;
414
+ }
415
+ exports.inCommon = inCommon;
408
416
  function shortenAgent(agent) {
409
417
  var _a;
410
418
  return lodash_1.default.findKey(BROWSERS, re => re.test(agent))
package/src/errorPages.js CHANGED
@@ -10,7 +10,7 @@ function getErrorSections() {
10
10
  }
11
11
  exports.getErrorSections = getErrorSections;
12
12
  // to be used with errors whose recipient is possibly human
13
- async function sendErrorPage(ctx, code) {
13
+ async function sendErrorPage(ctx, code = ctx.status) {
14
14
  var _a, _b;
15
15
  ctx.type = 'text';
16
16
  ctx.set('content-disposition', ''); // reset ctx.attachment
@@ -23,7 +23,7 @@ async function sendErrorPage(ctx, code) {
23
23
  return;
24
24
  const trans = (_a = Object.values(lang)[0]) === null || _a === void 0 ? void 0 : _a.translate;
25
25
  ctx.body = (_b = trans === null || trans === void 0 ? void 0 : trans[msg]) !== null && _b !== void 0 ? _b : msg;
26
- const errorPage = (0, customHtml_1.getSection)(String(ctx.status));
26
+ const errorPage = (0, customHtml_1.getSection)(ctx.status === cross_1.HTTP_UNAUTHORIZED ? 'unauthorized' : String(ctx.status));
27
27
  if (!errorPage)
28
28
  return;
29
29
  if (errorPage.includes('<'))
@@ -148,9 +148,15 @@ exports.frontEndApis = {
148
148
  const srcNode = await (0, vfs_1.urlToNode)(src, ctx);
149
149
  if (!srcNode)
150
150
  return const_1.HTTP_NOT_FOUND;
151
+ const s = srcNode.source;
152
+ const d = (0, path_1.join)(destNode.source, (0, path_1.basename)(srcNode.source));
151
153
  return (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_delete', ctx)
152
- || (0, promises_1.rename)(srcNode.source, (0, path_1.join)(destNode.source, (0, path_1.basename)(srcNode.source)))
153
- .catch(e => e.code || String(e));
154
+ || (0, promises_1.rename)(s, d).catch(async (e) => {
155
+ if (e.code !== 'EXDEV')
156
+ throw e; // exdev = different drive
157
+ await (0, promises_1.copyFile)(s, d);
158
+ await (0, promises_1.unlink)(s);
159
+ }).catch(e => e.code || String(e));
154
160
  }))
155
161
  };
156
162
  },
@@ -140,6 +140,7 @@
140
140
  "go_last": "Vai alla fine",
141
141
  "Shuffle": "Riproduzione casuale",
142
142
  "Repeat": "Ripeti a fine lista",
143
- "showHelpListShortcut": "Dalla lista dei file, clicca tenendo {key} per accedere velocemente al File Show"
143
+ "showHelpListShortcut": "Dalla lista dei file, clicca tenendo {key} per accedere velocemente al File Show",
144
+ "Invalid value": "Valore non valido"
144
145
  }
145
146
  }
package/src/misc.js CHANGED
@@ -20,7 +20,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
20
20
  Object.defineProperty(exports, "__esModule", { value: true });
21
21
  exports.apiAssertTypes = exports.AsapStream = exports.asyncGeneratorToReadable = exports.same = exports.makeNetMatcher = exports.isLocalHost = exports.onOff = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit = void 0;
22
22
  const path_1 = require("path");
23
- const lodash_1 = __importDefault(require("lodash"));
24
23
  const assert_1 = __importDefault(require("assert"));
25
24
  __exportStar(require("./util-http"), exports);
26
25
  __exportStar(require("./util-files"), exports);
@@ -51,8 +50,8 @@ function onFirstEvent(emitter, events, cb) {
51
50
  }
52
51
  exports.onFirstEvent = onFirstEvent;
53
52
  function pattern2filter(pattern) {
54
- const re = new RegExp(lodash_1.default.escapeRegExp(pattern), 'i');
55
- return (s) => !s || !pattern || re.test((0, path_1.basename)(s));
53
+ const matcher = (0, cross_1.makeMatcher)(pattern.includes('*') ? pattern : `*${pattern}*`); // if you specify *, we'll respect its position
54
+ return (s) => !s || !pattern || matcher((0, path_1.basename)(s));
56
55
  }
57
56
  exports.pattern2filter = pattern2filter;
58
57
  // install multiple handlers and returns a handy 'uninstall' function which requires no parameter. Pass a map {event:handler}
package/src/nat.js CHANGED
@@ -66,12 +66,13 @@ exports.getPublicIps = (0, debounceAsync_1.debounceAsync)(async () => {
66
66
  exports.getNatInfo = (0, debounceAsync_1.debounceAsync)(async () => {
67
67
  var _a, _b, _c, _d;
68
68
  const gettingIps = (0, exports.getPublicIps)(); // don't wait, do it in parallel
69
- const res = await exports.upnpClient.getGateway().catch(() => null);
69
+ const res = await (0, cross_1.haveTimeout)(10000, exports.upnpClient.getGateway()).catch(() => null);
70
70
  const status = await (0, listen_1.getServerStatus)();
71
71
  const mappings = res && await (0, cross_1.haveTimeout)(5000, exports.upnpClient.getMappings()).catch(() => null);
72
72
  console.debug('mappings found', mappings === null || mappings === void 0 ? void 0 : mappings.map(x => x.description));
73
73
  const gatewayIp = res ? new URL(res.gateway.description).hostname : await findGateway().catch(() => undefined);
74
- const localIp = (res === null || res === void 0 ? void 0 : res.address) || (await (0, listen_1.getIps)())[0];
74
+ const localIps = await (0, listen_1.getIps)(false);
75
+ const localIp = (res === null || res === void 0 ? void 0 : res.address) || gatewayIp ? lodash_1.default.maxBy(localIps, x => (0, cross_1.inCommon)(x, gatewayIp)) : localIps[0];
75
76
  const internalPort = ((_a = status === null || status === void 0 ? void 0 : status.https) === null || _a === void 0 ? void 0 : _a.listening) && status.https.port || ((_b = status === null || status === void 0 ? void 0 : status.http) === null || _b === void 0 ? void 0 : _b.listening) && status.http.port || undefined;
76
77
  const mapped = lodash_1.default.find(mappings, x => x.private.host === localIp && x.private.port === internalPort);
77
78
  const externalPort = mapped === null || mapped === void 0 ? void 0 : mapped.public.port;
package/src/serveFile.js CHANGED
@@ -122,7 +122,7 @@ function applyRange(ctx, totalSize = ctx.response.length) {
122
122
  return ctx.throw(const_1.HTTP_BAD_REQUEST, 'bad range');
123
123
  const max = totalSize - 1;
124
124
  const start = bytes[0] ? Number(bytes[0]) : Math.max(0, totalSize - Number(bytes[1])); // a negative start is relative to the end
125
- const end = bytes[0] ? Number(bytes[1] || max) : max;
125
+ const end = (bytes[0] && bytes[1]) ? Math.min(max, Number(bytes[1])) : max;
126
126
  // we don't support last-bytes without knowing max
127
127
  if (isNaN(end) && isNaN(max) || end > max || start > max) {
128
128
  ctx.status = const_1.HTTP_RANGE_NOT_SATISFIABLE;
@@ -88,7 +88,7 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
88
88
  return !node.source ? (0, errorPages_1.sendErrorPage)(ctx, cross_const_1.HTTP_METHOD_NOT_ALLOWED)
89
89
  : !(0, vfs_1.statusCodeForMissingPerm)(node, 'can_read', ctx) ? (0, serveFile_1.serveFileNode)(ctx, node)
90
90
  : ctx.status !== cross_const_1.HTTP_UNAUTHORIZED ? null
91
- : !path.endsWith('/') ? ctx.set('WWW-Authenticate', 'Basic') // this is necessary to support standard urls with credentials. Final / means we are dealing with default file...
91
+ : !path.endsWith('/') ? (ctx.set('WWW-Authenticate', 'Basic'), (0, errorPages_1.sendErrorPage)(ctx)) // this is necessary to support standard urls with credentials. Final / means we are dealing with default file...
92
92
  : (ctx.state.serveApp = true) && serveFrontendFiles(ctx, next); // ...for which we still provide fancy login
93
93
  if (!path.endsWith('/'))
94
94
  return ctx.redirect(ctx.state.revProxyPath + ctx.originalUrl.replace(/(\?|$)/, '/$1')); // keep query-string, if any