hfs 0.50.4 → 0.51.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.
Files changed (44) hide show
  1. package/README.md +2 -0
  2. package/admin/assets/index-101ecc9e.js +808 -0
  3. package/admin/assets/{index-bb75b3fc.css → index-65d16f45.css} +1 -1
  4. package/{frontend/assets/sha512-e82b909a.js → admin/assets/sha512-a1b6cce6.js} +1 -1
  5. package/admin/index.html +2 -2
  6. package/frontend/assets/index-a452a199.css +1 -0
  7. package/frontend/assets/index-dea364e7.js +95 -0
  8. package/{admin/assets/sha512-1694c0eb.js → frontend/assets/sha512-6365fe60.js} +1 -1
  9. package/frontend/fontello.css +4 -1
  10. package/frontend/fontello.woff2 +0 -0
  11. package/frontend/index.html +2 -2
  12. package/package.json +3 -2
  13. package/src/adminApis.js +3 -1
  14. package/src/api.auth.js +6 -1
  15. package/src/{api.file_list.js → api.get_file_list.js} +11 -7
  16. package/src/api.monitor.js +1 -34
  17. package/src/api.vfs.js +2 -5
  18. package/src/apiMiddleware.js +1 -1
  19. package/src/comments.js +1 -1
  20. package/src/const.js +5 -2
  21. package/src/cross-const.js +3 -4
  22. package/src/cross.js +56 -7
  23. package/src/debounceAsync.js +3 -3
  24. package/src/errorPages.js +1 -1
  25. package/src/frontEndApis.js +21 -2
  26. package/src/index.js +2 -0
  27. package/src/lang.js +10 -2
  28. package/src/langs/hfs-lang-it.json +10 -6
  29. package/src/listen.js +1 -2
  30. package/src/log.js +20 -5
  31. package/src/middlewares.js +12 -6
  32. package/src/misc.js +4 -14
  33. package/src/roots.js +47 -0
  34. package/src/serveFile.js +40 -4
  35. package/src/serveGuiFiles.js +2 -2
  36. package/src/update.js +7 -6
  37. package/src/upload.js +10 -5
  38. package/src/util-files.js +7 -6
  39. package/src/vfs.js +38 -28
  40. package/src/zip.js +2 -2
  41. package/admin/assets/index-a24642bd.js +0 -807
  42. package/frontend/assets/index-b90320b6.css +0 -1
  43. package/frontend/assets/index-e6ecc47c.js +0 -94
  44. package/plugins/vhosting/plugin.js +0 -62
@@ -1,4 +1,4 @@
1
- import{g as OF,c as UF}from"./index-a24642bd.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-dea364e7.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
@@ -1,6 +1,6 @@
1
1
  @font-face {
2
2
  font-family: 'fontello';
3
- src: url('fontello.woff2?11723613') format('woff2');
3
+ src: url('fontello.woff2?39711600') format('woff2');
4
4
  font-weight: normal;
5
5
  font-style: normal;
6
6
  }
@@ -64,6 +64,7 @@
64
64
  .fa-left:before { content: '\e816'; } /* '' */
65
65
  .fa-lock:before { content: '\e817'; } /* '' */
66
66
  .fa-reload:before { content: '\e818'; } /* '' */
67
+ .fa-cut:before { content: '\e819'; } /* '' */
67
68
  .fa-spin6:before { content: '\e839'; } /* '' */
68
69
  .fa-crown:before { content: '\e844'; } /* '' */
69
70
  .fa-download:before { content: '\f02e'; } /* '' */
@@ -71,8 +72,10 @@
71
72
  .fa-link:before { content: '\f08e'; } /* '' */
72
73
  .fa-filter:before { content: '\f0b0'; } /* '' */
73
74
  .fa-zoom:before { content: '\f0b2'; } /* '' */
75
+ .copy:before { content: '\f0c5'; } /* '' */
74
76
  .fa-menu:before { content: '\f0c9'; } /* '' */
75
77
  .fa-comment:before { content: '\f0e5'; } /* '' */
78
+ .fa-paste:before { content: '\f0ea'; } /* '' */
76
79
  .fa-fullscreen:before { content: '\f108'; } /* '' */
77
80
  .fa-quote:before { content: '\f10d'; } /* '' */
78
81
  .fa-unlink:before { content: '\f127'; } /* '' */
Binary file
@@ -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
 
8
- <script type="module" crossorigin src="/assets/index-e6ecc47c.js"></script>
9
- <link rel="stylesheet" href="/assets/index-b90320b6.css">
8
+ <script type="module" crossorigin src="/assets/index-dea364e7.js"></script>
9
+ <link rel="stylesheet" href="/assets/index-a452a199.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.50.4",
3
+ "version": "0.51.0-beta4",
4
4
  "description": "HTTP File Server",
5
5
  "keywords": [
6
6
  "file server",
@@ -82,6 +82,7 @@
82
82
  "minimist": "^1.2.6",
83
83
  "nat-upnp-ts": "^2.0.1",
84
84
  "open": "^8.4.0",
85
+ "picomatch": "^3.0.1",
85
86
  "tssrp6a": "^3.0.0",
86
87
  "unzip-stream": "^0.3.1",
87
88
  "valtio": "^1.10.3",
@@ -97,11 +98,11 @@
97
98
  "@types/koa-mount": "^4.0.1",
98
99
  "@types/koa-session": "^5.10.4",
99
100
  "@types/lodash": "^4.14.178",
100
- "@types/micromatch": "^4.0.2",
101
101
  "@types/mime-types": "^2.1.1",
102
102
  "@types/minimist": "^1.2.2",
103
103
  "@types/mocha": "^9.0.0",
104
104
  "@types/node": "^18.17.14",
105
+ "@types/picomatch": "^2.3.3",
105
106
  "@types/tough-cookie": "^4.0.2",
106
107
  "@types/unzipper": "^0.10.5",
107
108
  "cross-env": "^7.0.3",
package/src/adminApis.js CHANGED
@@ -56,6 +56,7 @@ const consoleLog_1 = require("./consoleLog");
56
56
  const path_1 = require("path");
57
57
  const errorPages_1 = require("./errorPages");
58
58
  const geo_1 = require("./geo");
59
+ const roots_1 = require("./roots");
59
60
  exports.adminApis = {
60
61
  ...api_vfs_1.default,
61
62
  ...api_accounts_1.default,
@@ -124,6 +125,7 @@ exports.adminApis = {
124
125
  urls: await (0, listen_1.getUrls)(),
125
126
  ips: await (0, listen_1.getIps)(false),
126
127
  baseUrl: (0, listen_1.getBaseUrlOrDefault)(),
128
+ roots: roots_1.roots.get(),
127
129
  updatePossible: !(0, update_1.updateSupported)() ? false : await (0, update_1.localUpdateAvailable)() ? 'local' : true,
128
130
  proxyDetected: (0, middlewares_1.getProxyDetected)(),
129
131
  frpDetected: exports.localhostAdmin.get() && !(0, middlewares_1.getProxyDetected)()
@@ -216,7 +218,7 @@ exports.title = (0, config_1.defineConfig)('title', "File server");
216
218
  function ctxAdminAccess(ctx) {
217
219
  return !ctx.ips.length // we consider localhost_admin only if no proxy is being used
218
220
  && exports.localhostAdmin.get() && (0, misc_1.isLocalHost)(ctx)
219
- || (0, perm_1.getFromAccount)(ctx.state.account, a => a.admin);
221
+ || ctx.state.account && (0, perm_1.accountCanLoginAdmin)(ctx.state.account);
220
222
  }
221
223
  exports.ctxAdminAccess = ctxAdminAccess;
222
224
  const frpDebounced = (0, misc_1.debounceAsync)(async () => {
package/src/api.auth.js CHANGED
@@ -39,8 +39,11 @@ const loginSrp1 = async ({ username }, ctx) => {
39
39
  const account = (0, perm_1.getAccount)(username);
40
40
  if (!ctx.session)
41
41
  return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
42
- if (!account || !(0, perm_1.accountCanLogin)(account)) // TODO simulate fake account to prevent knowing valid usernames
42
+ if (!account || !(0, perm_1.accountCanLogin)(account)) { // TODO simulate fake account to prevent knowing valid usernames
43
+ ctx.logExtra({ u: username });
44
+ ctx.state.dontLog = false; // log even if log_api is false
43
45
  return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
46
+ }
44
47
  try {
45
48
  const { step1, ...rest } = await (0, auth_1.srpStep1)(account);
46
49
  const sid = Math.random();
@@ -75,6 +78,8 @@ const loginSrp2 = async ({ pubKey, proof }, ctx) => {
75
78
  };
76
79
  }
77
80
  catch (e) {
81
+ ctx.logExtra({ u: username });
82
+ ctx.state.dontLog = false; // log even if log_api is false
78
83
  return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED, String(e));
79
84
  }
80
85
  finally {
@@ -11,12 +11,15 @@ const const_1 = require("./const");
11
11
  const comments_1 = require("./comments");
12
12
  const path_1 = require("path");
13
13
  const connections_1 = require("./connections");
14
- const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
14
+ const adminApis_1 = require("./adminApis");
15
+ const upload_1 = require("./upload");
16
+ const get_file_list = async ({ uri, offset, limit, search, c, onlyFolders, admin }, ctx) => {
15
17
  const node = await (0, vfs_1.urlToNode)(uri || '/', ctx);
16
18
  const list = ctx.get('accept') === 'text/event-stream' ? new apiMiddleware_1.SendListReadable() : undefined;
17
19
  if (!node)
18
20
  return fail(const_1.HTTP_NOT_FOUND);
19
- if ((0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx))
21
+ admin && (admin = (0, adminApis_1.ctxAdminAccess)(ctx)); // validate 'admin' flag
22
+ if (!admin && (0, vfs_1.statusCodeForMissingPerm)(node, 'can_list', ctx))
20
23
  return fail();
21
24
  if ((0, misc_1.dirTraversal)(search))
22
25
  return fail(const_1.HTTP_FOOL);
@@ -25,14 +28,15 @@ const get_file_list = async ({ uri, offset, limit, search, c }, ctx) => {
25
28
  offset = Number(offset);
26
29
  limit = Number(limit);
27
30
  const filter = (0, misc_1.pattern2filter)(search);
28
- const walker = (0, vfs_1.walkNode)(node, ctx, search ? Infinity : 0);
31
+ const walker = (0, vfs_1.walkNode)(node, { ctx: admin ? undefined : ctx, onlyFolders, depth: search ? Infinity : 0 });
29
32
  const onDirEntryHandlers = (0, plugins_1.mapPlugins)(plug => plug.onDirEntry);
30
- const can_upload = (0, vfs_1.hasPermission)(node, 'can_upload', ctx);
33
+ const can_upload = admin || (0, vfs_1.hasPermission)(node, 'can_upload', ctx);
31
34
  const fakeChild = (0, vfs_1.applyParentToChild)({}, node); // we want to know if we want to delete children
32
- const can_delete = (0, vfs_1.hasPermission)(fakeChild, 'can_delete', ctx);
33
- const can_archive = (0, vfs_1.hasPermission)(fakeChild, 'can_archive', ctx);
35
+ const can_delete = admin || (0, vfs_1.hasPermission)(fakeChild, 'can_delete', ctx);
36
+ const can_archive = admin || (0, vfs_1.hasPermission)(fakeChild, 'can_archive', ctx);
34
37
  const can_comment = can_upload && (0, comments_1.areCommentsEnabled)();
35
- const props = { can_archive, can_upload, can_delete, accept: node.accept, can_comment };
38
+ const can_overwrite = can_upload && (can_delete || !upload_1.dontOverwriteUploading.get());
39
+ const props = { can_archive, can_upload, can_delete, can_overwrite, accept: node.accept, can_comment };
36
40
  if (!list)
37
41
  return { ...props, list: await (0, misc_1.asyncGeneratorToArray)(produceEntries()) };
38
42
  setTimeout(async () => {
@@ -89,7 +89,7 @@ const apis = {
89
89
  || (ctx.fileSource || ctx.state.archive) && decodeURIComponent(ctx.path); // downloads
90
90
  return {
91
91
  user: (0, auth_1.getCurrentUsername)(ctx),
92
- agent: getBrowser(ctx.get('user-agent')),
92
+ agent: (0, misc_1.shortenAgent)(ctx.get('user-agent')),
93
93
  archive: ctx.state.archive,
94
94
  upload: ctx.state.uploadProgress,
95
95
  ...path && { path },
@@ -119,36 +119,3 @@ function getConnAddress(conn) {
119
119
  port: conn.socket.remotePort,
120
120
  };
121
121
  }
122
- function getBrowser(agent) {
123
- for (const [name, re] of Object.entries(BROWSERS))
124
- if (re.test(agent))
125
- return name;
126
- return '';
127
- }
128
- const BROWSERS = {
129
- YaBrowser: /yabrowser/i,
130
- AlamoFire: /alamofire/i,
131
- Edge: /edge|edga|edgios|edg/i,
132
- PhantomJS: /phantomjs/i,
133
- Konqueror: /konqueror/i,
134
- Amaya: /amaya/i,
135
- Epiphany: /epiphany/i,
136
- SeaMonkey: /seamonkey/i,
137
- Flock: /flock/i,
138
- OmniWeb: /omniweb/i,
139
- Opera: /opera|OPR\//i,
140
- Chromium: /chromium/i,
141
- Facebook: /FBA[NV]/,
142
- Chrome: /chrome|crios/i,
143
- WinJs: /msapphost/i,
144
- IE: /msie|trident/i,
145
- Firefox: /firefox|fxios/i,
146
- Safari: /safari/i,
147
- PS5: /playstation 5/i,
148
- PS4: /playstation 4/i,
149
- PS3: /playstation 3/i,
150
- PSP: /playstation portable/i,
151
- PS: /playstation/i,
152
- Xbox: /xbox/i,
153
- UC: /UCBrowser/i,
154
- };
package/src/api.vfs.js CHANGED
@@ -207,14 +207,14 @@ const apis = {
207
207
  }
208
208
  });
209
209
  },
210
- async windows_integration() {
210
+ async windows_integration({ parent }) {
211
211
  const status = await (0, listen_1.getServerStatus)(true);
212
212
  const h = status.http.listening ? status.http : status.https;
213
213
  const url = h.srv.name + '://localhost:' + h.port;
214
214
  for (const k of ['*', 'Directory']) {
215
215
  await reg('add', WINDOWS_REG_KEY.replace('*', k), '/ve', '/f', '/d', 'Add to HFS (new)');
216
216
  await reg('add', WINDOWS_REG_KEY.replace('*', k) + '\\command', '/ve', '/f', '/d', `powershell -Command "
217
- $j = '{ \\"source\\": "' + ('%1'|convertTo-json) + '" }'; $j = [System.Text.Encoding]::UTF8.GetBytes($j); $wsh = New-Object -ComObject Wscript.Shell;
217
+ $j = '{ \\"parent\\": \\"${parent}\\", \\"source\\": "' + ('%1'|convertTo-json) + '" }'; $j = [System.Text.Encoding]::UTF8.GetBytes($j); $wsh = New-Object -ComObject Wscript.Shell;
218
218
  try { $res = Invoke-WebRequest -Uri '${url}/~/api/add_vfs' -Method POST -Headers @{ 'x-hfs-anti-csrf' = '1' } -ContentType 'application/json' -TimeoutSec 1 -Body $j;
219
219
  $json = $res.Content | ConvertFrom-Json; $link = $json.link; $link | Set-Clipboard; } catch { $wsh.Popup('Server is down', 0, 'Error', 16); }"`);
220
220
  }
@@ -248,9 +248,6 @@ function simplifyName(node) {
248
248
  delete node.name;
249
249
  }
250
250
  const WINDOWS_REG_KEY = 'HKCU\\Software\\Classes\\*\\shell\\AddToHFS3';
251
- if (const_1.IS_WINDOWS) // legacy 0.49.0-beta7 2023-10-27. Remove in 0.50
252
- for (const k of ['*', 'Directory'])
253
- reg('delete', `HKCR\\${k}\\shell\\AddToHFS3`, '/f').catch(() => { });
254
251
  function reg(...pars) {
255
252
  return (0, util_1.promisify)(child_process_1.execFile)('reg', pars);
256
253
  }
@@ -23,7 +23,7 @@ const logApi = (0, config_1.defineConfig)('log_api', true);
23
23
  function apiMiddleware(apis) {
24
24
  return async (ctx) => {
25
25
  if (!logApi.get())
26
- ctx.state.dont_log = true;
26
+ ctx.state.dontLog = true;
27
27
  const isPost = ctx.params;
28
28
  const params = isPost ? ctx.params || {} : ctx.query;
29
29
  const apiName = ctx.path;
package/src/comments.js CHANGED
@@ -20,7 +20,7 @@ async function getCommentFor(path) {
20
20
  : readDescription((0, path_1.dirname)(path)).then(x => x.get((0, cross_1.basename)(path)), () => undefined);
21
21
  }
22
22
  exports.getCommentFor = getCommentFor;
23
- exports.setCommentFor = (0, misc_1.singleFromBatch)(async (jobs) => {
23
+ exports.setCommentFor = (0, misc_1.singleWorkerFromBatchWorker)(async (jobs) => {
24
24
  const byFolder = lodash_1.default.groupBy(jobs, job => (0, path_1.dirname)(job[0]));
25
25
  return Promise.allSettled(lodash_1.default.map(byFolder, async (jobs, folder) => {
26
26
  const comments = await readDescription(folder).catch(() => new Map());
package/src/const.js CHANGED
@@ -30,19 +30,22 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
30
30
  return (mod && mod.__esModule) ? mod : { "default": mod };
31
31
  };
32
32
  Object.defineProperty(exports, "__esModule", { value: true });
33
- exports.MIME_AUTO = exports.APP_PATH = exports.IS_BINARY = exports.IS_MAC = exports.IS_WINDOWS = exports.HFS_REPO_BRANCH = exports.RUNNING_BETA = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.argv = void 0;
33
+ exports.MIME_AUTO = exports.APP_PATH = exports.IS_BINARY = exports.IS_MAC = exports.IS_WINDOWS = exports.HFS_REPO_BRANCH = exports.RUNNING_BETA = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.argv = exports.HFS_REPO = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = void 0;
34
34
  const minimist_1 = __importDefault(require("minimist"));
35
35
  const fs = __importStar(require("fs"));
36
36
  const os_1 = require("os");
37
37
  const fs_1 = require("fs");
38
38
  const path_1 = require("path");
39
39
  __exportStar(require("./cross-const"), exports);
40
+ exports.API_VERSION = 8.62;
41
+ exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
42
+ exports.HFS_REPO = 'rejetto/hfs';
40
43
  exports.argv = (0, minimist_1.default)(process.argv.slice(2));
41
44
  exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
42
45
  exports.ORIGINAL_CWD = process.cwd();
43
46
  exports.HFS_STARTED = new Date();
44
47
  const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
45
- exports.BUILD_TIMESTAMP = "2023-12-18T13:45:53.278Z";
48
+ exports.BUILD_TIMESTAMP = "2023-12-21T13:46:51.866Z";
46
49
  const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
47
50
  exports.VERSION = pkg.version;
48
51
  exports.RUNNING_BETA = exports.VERSION.includes('-');
@@ -1,9 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HTTP_MESSAGES = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.PORT_DISABLED = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = exports.HFS_REPO = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = void 0;
4
- exports.API_VERSION = 8.4;
5
- exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
6
- exports.HFS_REPO = 'rejetto/hfs';
3
+ exports.HTTP_MESSAGES = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_TOO_MANY_REQUESTS = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.PORT_DISABLED = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = void 0;
7
4
  exports.SPECIAL_URI = '/~/';
8
5
  exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
9
6
  exports.ADMIN_URI = exports.SPECIAL_URI + 'admin/';
@@ -28,6 +25,7 @@ exports.HTTP_PAYLOAD_TOO_LARGE = 413;
28
25
  exports.HTTP_RANGE_NOT_SATISFIABLE = 416;
29
26
  exports.HTTP_FOOL = 418;
30
27
  exports.HTTP_FAILED_DEPENDENCY = 424;
28
+ exports.HTTP_TOO_MANY_REQUESTS = 429;
31
29
  exports.HTTP_SERVER_ERROR = 500;
32
30
  exports.HTTP_SERVICE_UNAVAILABLE = 503;
33
31
  exports.HTTP_MESSAGES = {
@@ -35,4 +33,5 @@ exports.HTTP_MESSAGES = {
35
33
  [exports.HTTP_FORBIDDEN]: "Forbidden",
36
34
  [exports.HTTP_NOT_FOUND]: "Not found",
37
35
  [exports.HTTP_SERVER_ERROR]: "Server error",
36
+ [exports.HTTP_TOO_MANY_REQUESTS]: "Too many requests",
38
37
  };
package/src/cross.js CHANGED
@@ -17,11 +17,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17
17
  return (mod && mod.__esModule) ? mod : { "default": mod };
18
18
  };
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
- exports.throw_ = exports.hasProp = exports.typedEntries = exports.typedKeys = exports.objRenameKey = exports.randomId = exports.getOrSet = exports.waitFor = exports.newObj = exports.removeStarting = exports.findDefined = exports.isOrderedEqual = exports.swap = exports.tryJson = 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.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.runAt = exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.ipLocalHost = exports.xlate = exports.isEqualLax = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = void 0;
20
+ exports.hasProp = 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.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.matches = exports.makeMatcher = exports.runAt = exports.promiseBestEffort = exports.escapeHTML = exports.ipForUrl = exports.ipLocalHost = exports.xlate = exports.isEqualLax = exports.isWindowsDrive = exports.isIP = exports.isPrimitive = exports.formatTimestamp = exports.repeat = exports.asyncGeneratorToArray = exports.filterMapGenerator = exports.throw_ = 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"));
25
+ const picomatch_1 = __importDefault(require("picomatch/lib/picomatch")); // point directly to the browser-compatible source
25
26
  __exportStar(require("./cross-const"), exports);
26
27
  exports.REPO_URL = 'https://github.com/rejetto/hfs/';
27
28
  exports.WIKI_URL = exports.REPO_URL + 'wiki/';
@@ -40,7 +41,8 @@ exports.FRONTEND_OPTIONS = {
40
41
  };
41
42
  exports.SORT_BY_OPTIONS = ['name', 'extension', 'size', 'time'];
42
43
  exports.THEME_OPTIONS = { auto: '', light: 'light', dark: 'dark' };
43
- exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknown']);
44
+ exports.CFG = constMap(['geo_enable', 'geo_allow', 'geo_list', 'geo_allow_unknown',
45
+ 'max_downloads', 'max_downloads_per_ip', 'max_downloads_per_account', 'roots', 'roots_mandatory']);
44
46
  exports.LIST = { add: '+', remove: '-', update: '=', props: 'props', ready: 'ready', error: 'e' };
45
47
  exports.WHO_ANYONE = true;
46
48
  exports.WHO_NO_ONE = false;
@@ -97,6 +99,10 @@ function enforceFinal(sub, s, evenEmpty = false) {
97
99
  return !evenEmpty && !s || s.endsWith(sub) ? s : s + sub;
98
100
  }
99
101
  exports.enforceFinal = enforceFinal;
102
+ function removeStarting(sub, s) {
103
+ return s.startsWith(sub) ? s.slice(sub.length) : s;
104
+ }
105
+ exports.removeStarting = removeStarting;
100
106
  function splitAt(sub, all) {
101
107
  if (typeof sub === 'number')
102
108
  return [all.slice(0, sub), all.slice(sub + 1)];
@@ -161,6 +167,10 @@ function basename(path) {
161
167
  return path.slice(path.lastIndexOf('/') + 1 || path.lastIndexOf('\\') + 1);
162
168
  }
163
169
  exports.basename = basename;
170
+ function dirname(path) {
171
+ return path.slice(0, path.lastIndexOf('/', path.length - 1));
172
+ }
173
+ exports.dirname = dirname;
164
174
  function tryJson(s, except) {
165
175
  try {
166
176
  return s && JSON.parse(s);
@@ -199,10 +209,6 @@ function findDefined(a, cb) {
199
209
  }
200
210
  }
201
211
  exports.findDefined = findDefined;
202
- function removeStarting(sub, s) {
203
- return s.startsWith(sub) ? s.slice(sub.length) : s;
204
- }
205
- exports.removeStarting = removeStarting;
206
212
  function newObj(src, returnNewValue, recur = false) {
207
213
  if (!src)
208
214
  return {};
@@ -363,3 +369,46 @@ function runAt(ts, cb) {
363
369
  };
364
370
  }
365
371
  exports.runAt = runAt;
372
+ function makeMatcher(mask, emptyMaskReturns = false) {
373
+ return mask ? (0, picomatch_1.default)(mask.replace(/^(!)?/, '$1(') + ')', { nocase: true }) // adding () will allow us to use the pipe at root level
374
+ : () => emptyMaskReturns;
375
+ }
376
+ exports.makeMatcher = makeMatcher;
377
+ function matches(s, mask, emptyMaskReturns = false) {
378
+ return makeMatcher(mask, emptyMaskReturns)(s); // adding () will allow us to use the pipe at root level
379
+ }
380
+ exports.matches = matches;
381
+ function shortenAgent(agent) {
382
+ var _a;
383
+ return lodash_1.default.findKey(BROWSERS, re => re.test(agent))
384
+ || ((_a = /^[^/(]+ ?/.exec(agent)) === null || _a === void 0 ? void 0 : _a[0])
385
+ || agent;
386
+ }
387
+ exports.shortenAgent = shortenAgent;
388
+ const BROWSERS = {
389
+ YaBrowser: /yabrowser/i,
390
+ AlamoFire: /alamofire/i,
391
+ Edge: /edge|edga|edgios|edg/i,
392
+ PhantomJS: /phantomjs/i,
393
+ Konqueror: /konqueror/i,
394
+ Amaya: /amaya/i,
395
+ Epiphany: /epiphany/i,
396
+ SeaMonkey: /seamonkey/i,
397
+ Flock: /flock/i,
398
+ OmniWeb: /omniweb/i,
399
+ Opera: /opera|OPR\//i,
400
+ Chromium: /chromium/i,
401
+ Facebook: /FBA[NV]/,
402
+ Chrome: /chrome|crios/i,
403
+ WinJs: /msapphost/i,
404
+ IE: /msie|trident/i,
405
+ Firefox: /firefox|fxios/i,
406
+ Safari: /safari/i,
407
+ PS5: /playstation 5/i,
408
+ PS4: /playstation 4/i,
409
+ PS3: /playstation 3/i,
410
+ PSP: /playstation portable/i,
411
+ PS: /playstation/i,
412
+ Xbox: /xbox/i,
413
+ UC: /UCBrowser/i,
414
+ };
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  // This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.singleFromBatch = exports.debounceAsync = void 0;
4
+ exports.singleWorkerFromBatchWorker = exports.debounceAsync = void 0;
5
5
  // like lodash.debounce, but also avoids async invocations to overlap
6
6
  function debounceAsync(callback, wait = 100, options = {}) {
7
7
  const { leading = false, maxWait = Infinity, cancelable = false, retain = 0, retainFailure } = options;
@@ -64,7 +64,7 @@ function debounceAsync(callback, wait = 100, options = {}) {
64
64
  }
65
65
  exports.debounceAsync = debounceAsync;
66
66
  // given a function that works on a batch of requests, returns the function that works on a single request
67
- function singleFromBatch(batchWorker) {
67
+ function singleWorkerFromBatchWorker(batchWorker) {
68
68
  let batch = [];
69
69
  const debounced = debounceAsync(async () => {
70
70
  const ret = batchWorker(batch);
@@ -76,4 +76,4 @@ function singleFromBatch(batchWorker) {
76
76
  return debounced();
77
77
  };
78
78
  }
79
- exports.singleFromBatch = singleFromBatch;
79
+ exports.singleWorkerFromBatchWorker = singleWorkerFromBatchWorker;
package/src/errorPages.js CHANGED
@@ -4,7 +4,7 @@ exports.sendErrorPage = exports.getErrorSections = void 0;
4
4
  const lang_1 = require("./lang");
5
5
  const customHtml_1 = require("./customHtml");
6
6
  const cross_1 = require("./cross");
7
- const declaredErrorPages = [cross_1.HTTP_NOT_FOUND, cross_1.HTTP_FORBIDDEN].map(String);
7
+ const declaredErrorPages = [cross_1.HTTP_NOT_FOUND, cross_1.HTTP_FORBIDDEN, cross_1.HTTP_TOO_MANY_REQUESTS].map(String);
8
8
  function getErrorSections() {
9
9
  return declaredErrorPages;
10
10
  }
@@ -29,7 +29,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
29
29
  Object.defineProperty(exports, "__esModule", { value: true });
30
30
  exports.notifyClient = exports.frontEndApis = void 0;
31
31
  const apiMiddleware_1 = require("./apiMiddleware");
32
- const api_file_list_1 = require("./api.file_list");
32
+ const api_get_file_list_1 = require("./api.get_file_list");
33
33
  const api_auth = __importStar(require("./api.auth"));
34
34
  const events_1 = __importDefault(require("./events"));
35
35
  const util_files_1 = require("./util-files");
@@ -41,7 +41,7 @@ const upload_1 = require("./upload");
41
41
  const misc_1 = require("./misc");
42
42
  const comments_1 = require("./comments");
43
43
  exports.frontEndApis = {
44
- get_file_list: api_file_list_1.get_file_list,
44
+ get_file_list: api_get_file_list_1.get_file_list,
45
45
  ...api_auth,
46
46
  get_notifications({ channel }, ctx) {
47
47
  (0, misc_1.apiAssertTypes)({ string: { channel } });
@@ -134,6 +134,25 @@ exports.frontEndApis = {
134
134
  throw new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, e);
135
135
  }
136
136
  },
137
+ async move_files({ uri_from, uri_to }, ctx) {
138
+ (0, misc_1.apiAssertTypes)({ array: { uri_from }, string: { uri_to } });
139
+ const destNode = await (0, vfs_1.urlToNode)(uri_to, ctx);
140
+ const code = !destNode ? const_1.HTTP_NOT_FOUND : (0, vfs_1.statusCodeForMissingPerm)(destNode, 'can_upload', ctx);
141
+ if (code)
142
+ return new apiMiddleware_1.ApiError(code);
143
+ return {
144
+ errors: await Promise.all(uri_from.map(async (src) => {
145
+ if (typeof src !== 'string')
146
+ return const_1.HTTP_BAD_REQUEST;
147
+ const srcNode = await (0, vfs_1.urlToNode)(src, ctx);
148
+ if (!srcNode)
149
+ return const_1.HTTP_NOT_FOUND;
150
+ return (0, vfs_1.statusCodeForMissingPerm)(srcNode, 'can_delete', ctx)
151
+ || (0, promises_1.rename)(srcNode.source, (0, path_1.join)(destNode.source, (0, path_1.basename)(srcNode.source)))
152
+ .catch(e => e.code || String(e));
153
+ }))
154
+ };
155
+ },
137
156
  async comment({ uri, comment }, ctx) {
138
157
  (0, misc_1.apiAssertTypes)({ string: { uri, comment } });
139
158
  const node = await (0, vfs_1.urlToNode)(uri, ctx);
package/src/index.js CHANGED
@@ -28,6 +28,7 @@ const selfCheck_1 = require("./selfCheck");
28
28
  const acme_1 = require("./acme");
29
29
  require("./geo");
30
30
  const geo_1 = require("./geo");
31
+ const roots_1 = require("./roots");
31
32
  const events_1 = __importDefault(require("./events"));
32
33
  (0, assert_1.ok)(lodash_1.default.intersection(Object.keys(frontEndApis_1.frontEndApis), Object.keys(adminApis_1.adminApis)).length === 0); // they share same endpoints, don't clash
33
34
  process.title = 'HFS ' + const_1.VERSION;
@@ -46,6 +47,7 @@ exports.app.use(middlewares_1.sessionMiddleware)
46
47
  .use(middlewares_1.headRequests)
47
48
  .use(log_1.logMw)
48
49
  .use(throttler_1.throttler)
50
+ .use(roots_1.rootsMiddleware)
49
51
  .use((0, koa_mount_1.default)(const_1.API_URI, (0, apiMiddleware_1.apiMiddleware)({ ...frontEndApis_1.frontEndApis, ...adminApis_1.adminApis })))
50
52
  .use(middlewares_1.serveGuiAndSharedFiles)
51
53
  .on('error', errorHandler);
package/src/lang.js CHANGED
@@ -9,6 +9,9 @@ const promises_1 = require("fs/promises");
9
9
  const config_1 = require("./config");
10
10
  const watchLoad_1 = require("./watchLoad");
11
11
  const embedded_1 = __importDefault(require("./langs/embedded"));
12
+ const plugins_1 = require("./plugins");
13
+ const path_1 = require("path");
14
+ const lodash_1 = __importDefault(require("lodash"));
12
15
  const PREFIX = 'hfs-lang-';
13
16
  const SUFFIX = '.json';
14
17
  const EMBEDDED_LANGUAGE = 'en';
@@ -21,6 +24,7 @@ function file2code(fn) {
21
24
  }
22
25
  exports.file2code = file2code;
23
26
  async function getLangData(ctx) {
27
+ var _a;
24
28
  const param = String(ctx.query.lang || '');
25
29
  if (!param && forceLangData)
26
30
  return forceLangData;
@@ -32,10 +36,11 @@ async function getLangData(ctx) {
32
36
  let k = langs[i] || ''; // shut up ts
33
37
  if (!k || k === EMBEDDED_LANGUAGE)
34
38
  break;
39
+ const fn = code2file(k);
35
40
  try {
36
- ret[k] = JSON.parse(await (0, promises_1.readFile)(`hfs-lang-${k}.json`, 'utf8'));
41
+ ret[k] = JSON.parse(await (0, promises_1.readFile)(fn, 'utf8'));
37
42
  }
38
- catch (_a) {
43
+ catch (_b) {
39
44
  if ((0, misc_1.hasProp)(embedded_1.default, k))
40
45
  ret[k] = embedded_1.default[k];
41
46
  else {
@@ -48,6 +53,9 @@ async function getLangData(ctx) {
48
53
  }
49
54
  }
50
55
  }
56
+ const fromPlugins = (0, misc_1.onlyTruthy)(await Promise.all((0, plugins_1.mapPlugins)(async (pl) => { var _a; return (_a = (0, misc_1.tryJson)(await (0, promises_1.readFile)((0, path_1.join)(pl.folder, fn), 'utf8').catch(() => ''))) === null || _a === void 0 ? void 0 : _a.translate; })));
57
+ if (fromPlugins.length)
58
+ lodash_1.default.defaults((_a = (ret[k] || (ret[k] = {}))).translate || (_a.translate = {}), ...fromPlugins); // be sure we have an entry for k
51
59
  i++;
52
60
  }
53
61
  return ret;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "Massimo Melina",
3
- "version": 1.8,
4
- "hfs_version": "0.50.0",
3
+ "version": 1.9,
4
+ "hfs_version": "0.51.0",
5
5
  "translate": {
6
6
  "Select": "Seleziona",
7
7
  "n_files": "{n} file",
@@ -125,9 +125,13 @@
125
125
  "Comment": "Comment",
126
126
  "upload_dd_hint": "Puoi uploadare file trascinandoli sulla lista principale",
127
127
  "Upload not available": "Upload non disponibile",
128
- "_PLUGINS SECTION": "",
129
- "_PLUGIN thumbnails": "",
130
- "Enable tiles mode": "Modalità griglia",
131
- "thumbnails_switchBack": "Puoi tornare alla lista com'era prima entrando in Opzioni"
128
+ "Cut": "Taglia",
129
+ "n_items": "{n,plural, one{# elemento} other{# elementi}}",
130
+ "good_bad": "{good} spostati, {bad} falliti",
131
+ "after_cut": "Hai messo negli appunti, ora spostati fino alla destinazione e premi \"Incolla\".",
132
+ "Cancel clipboard": "Annulla appunti",
133
+ "to_clipboard_source": "Torna all'origine'",
134
+ "Paste": "Incolla",
135
+ "clipboard_list": "Elementi negli appunti"
132
136
  }
133
137
  }
package/src/listen.js CHANGED
@@ -109,8 +109,7 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
109
109
  nat_1.defaultBaseUrl.port = (_a = getCurrentPort(httpSrv)) !== null && _a !== void 0 ? _a : 0;
110
110
  let port = exports.httpsPortCfg.get();
111
111
  try {
112
- while (!index_1.app)
113
- await (0, misc_1.wait)(100);
112
+ await (0, misc_1.waitFor)(() => index_1.app);
114
113
  httpsSrv = Object.assign(https.createServer(port === const_1.PORT_DISABLED ? {} : { ...commonOptions, key: httpsOptions.private_key, cert: httpsOptions.cert }, index_1.app.callback()), { name: 'https' });
115
114
  if (port >= 0) {
116
115
  const cert = getCertObject();