hfs 0.26.8 → 0.27.2

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 (163) hide show
  1. package/README.md +15 -2
  2. package/admin/assets/index-509bb1d6.js +415 -0
  3. package/admin/assets/index-60a380a7.css +1 -0
  4. package/admin/assets/sha512-738f0943.js +8 -0
  5. package/admin/index.html +3 -1
  6. package/admin/{public/logo.svg → logo.svg} +0 -0
  7. package/frontend/assets/index-6e178dfd.css +1 -0
  8. package/frontend/assets/index-aea7654e.js +85 -0
  9. package/frontend/assets/sha512-bf915587.js +8 -0
  10. package/frontend/{public/fontello.css → fontello.css} +0 -0
  11. package/frontend/{public/fontello.woff2 → fontello.woff2} +0 -0
  12. package/frontend/index.html +4 -2
  13. package/package.json +2 -6
  14. package/plugins/vhosting/plugin.js +23 -20
  15. package/src/QuickZipStream.js +285 -0
  16. package/src/ThrottledStream.js +93 -0
  17. package/src/adminApis.js +169 -0
  18. package/src/api.accounts.js +59 -0
  19. package/src/api.auth.js +128 -0
  20. package/src/api.file_list.js +110 -0
  21. package/src/api.helpers.js +32 -0
  22. package/src/api.monitor.js +104 -0
  23. package/src/api.plugins.js +128 -0
  24. package/src/api.vfs.js +167 -0
  25. package/src/apiMiddleware.js +123 -0
  26. package/src/block.js +34 -0
  27. package/src/commands.js +125 -0
  28. package/src/config.js +168 -0
  29. package/src/connections.js +57 -0
  30. package/src/const.js +94 -0
  31. package/src/crypt.js +21 -0
  32. package/src/debounceAsync.js +49 -0
  33. package/src/events.js +9 -0
  34. package/src/frontEndApis.js +38 -0
  35. package/src/github.js +104 -0
  36. package/src/index.js +57 -0
  37. package/src/listen.js +235 -0
  38. package/src/log.js +137 -0
  39. package/src/middlewares.js +195 -0
  40. package/src/misc.js +160 -0
  41. package/src/pbkdf2.js +74 -0
  42. package/src/perm.js +183 -0
  43. package/src/plugins.js +343 -0
  44. package/src/serveFile.js +105 -0
  45. package/src/serveGuiFiles.js +113 -0
  46. package/src/sse.js +30 -0
  47. package/src/throttler.js +91 -0
  48. package/src/update.js +70 -0
  49. package/src/util-files.js +163 -0
  50. package/src/util-generators.js +31 -0
  51. package/src/util-http.js +32 -0
  52. package/src/vfs.js +232 -0
  53. package/src/watchLoad.js +73 -0
  54. package/src/zip.js +73 -0
  55. package/admin/.DS_Store +0 -0
  56. package/admin/.eslintrc +0 -8
  57. package/admin/.gitignore +0 -23
  58. package/admin/package.json +0 -67
  59. package/admin/src/AccountForm.ts +0 -92
  60. package/admin/src/AccountsPage.ts +0 -143
  61. package/admin/src/App.ts +0 -83
  62. package/admin/src/ArrayField.ts +0 -84
  63. package/admin/src/ConfigPage.ts +0 -279
  64. package/admin/src/FileField.ts +0 -52
  65. package/admin/src/FileForm.ts +0 -148
  66. package/admin/src/FilePicker.ts +0 -166
  67. package/admin/src/HomePage.ts +0 -96
  68. package/admin/src/InstalledPlugins.ts +0 -158
  69. package/admin/src/LoginRequired.ts +0 -75
  70. package/admin/src/LogoutPage.ts +0 -27
  71. package/admin/src/LogsPage.ts +0 -75
  72. package/admin/src/MainMenu.ts +0 -74
  73. package/admin/src/MenuButton.ts +0 -38
  74. package/admin/src/MonitorPage.ts +0 -200
  75. package/admin/src/OnlinePlugins.ts +0 -101
  76. package/admin/src/PermField.ts +0 -80
  77. package/admin/src/PluginsPage.ts +0 -27
  78. package/admin/src/VfsMenuBar.ts +0 -58
  79. package/admin/src/VfsPage.ts +0 -124
  80. package/admin/src/VfsTree.ts +0 -95
  81. package/admin/src/addFiles.ts +0 -59
  82. package/admin/src/api.ts +0 -246
  83. package/admin/src/dialog.ts +0 -203
  84. package/admin/src/index.css +0 -21
  85. package/admin/src/index.ts +0 -10
  86. package/admin/src/md.ts +0 -31
  87. package/admin/src/misc.ts +0 -141
  88. package/admin/src/react-app-env.d.ts +0 -1
  89. package/admin/src/reportWebVitals.ts +0 -15
  90. package/admin/src/setupTests.ts +0 -5
  91. package/admin/src/state.ts +0 -40
  92. package/admin/src/theme.ts +0 -37
  93. package/admin/tsconfig.json +0 -26
  94. package/admin/vite.config.ts +0 -32
  95. package/frontend/.DS_Store +0 -0
  96. package/frontend/.eslintrc +0 -8
  97. package/frontend/.gitignore +0 -23
  98. package/frontend/package.json +0 -51
  99. package/frontend/src/App.ts +0 -25
  100. package/frontend/src/Breadcrumbs.ts +0 -43
  101. package/frontend/src/BrowseFiles.ts +0 -141
  102. package/frontend/src/Head.ts +0 -45
  103. package/frontend/src/UserPanel.ts +0 -52
  104. package/frontend/src/api.ts +0 -78
  105. package/frontend/src/components.ts +0 -54
  106. package/frontend/src/dialog.css +0 -76
  107. package/frontend/src/dialog.ts +0 -105
  108. package/frontend/src/icons.ts +0 -46
  109. package/frontend/src/index.scss +0 -307
  110. package/frontend/src/index.ts +0 -10
  111. package/frontend/src/login.ts +0 -50
  112. package/frontend/src/menu.ts +0 -188
  113. package/frontend/src/misc.ts +0 -54
  114. package/frontend/src/options.ts +0 -52
  115. package/frontend/src/react-app-env.d.ts +0 -1
  116. package/frontend/src/reportWebVitals.ts +0 -15
  117. package/frontend/src/setupTests.ts +0 -5
  118. package/frontend/src/state.ts +0 -82
  119. package/frontend/src/useAuthorized.ts +0 -17
  120. package/frontend/src/useFetchList.ts +0 -144
  121. package/frontend/src/useTheme.ts +0 -23
  122. package/frontend/tsconfig.json +0 -26
  123. package/frontend/vite.config.ts +0 -21
  124. package/src/QuickZipStream.ts +0 -279
  125. package/src/ThrottledStream.ts +0 -98
  126. package/src/adminApis.ts +0 -161
  127. package/src/api.accounts.ts +0 -78
  128. package/src/api.auth.ts +0 -131
  129. package/src/api.file_list.ts +0 -102
  130. package/src/api.helpers.ts +0 -30
  131. package/src/api.monitor.ts +0 -106
  132. package/src/api.plugins.ts +0 -139
  133. package/src/api.vfs.ts +0 -182
  134. package/src/apiMiddleware.ts +0 -124
  135. package/src/block.ts +0 -35
  136. package/src/commands.ts +0 -122
  137. package/src/config.ts +0 -166
  138. package/src/connections.ts +0 -60
  139. package/src/const.ts +0 -57
  140. package/src/crypt.ts +0 -16
  141. package/src/debounceAsync.ts +0 -51
  142. package/src/events.ts +0 -6
  143. package/src/frontEndApis.ts +0 -17
  144. package/src/github.ts +0 -102
  145. package/src/index.ts +0 -53
  146. package/src/listen.ts +0 -220
  147. package/src/log.ts +0 -128
  148. package/src/middlewares.ts +0 -176
  149. package/src/misc.ts +0 -149
  150. package/src/pbkdf2.ts +0 -83
  151. package/src/perm.ts +0 -194
  152. package/src/plugins.ts +0 -342
  153. package/src/serveFile.ts +0 -104
  154. package/src/serveGuiFiles.ts +0 -95
  155. package/src/sse.ts +0 -29
  156. package/src/throttler.ts +0 -106
  157. package/src/update.ts +0 -67
  158. package/src/util-files.ts +0 -137
  159. package/src/util-generators.ts +0 -29
  160. package/src/util-http.ts +0 -29
  161. package/src/vfs.ts +0 -258
  162. package/src/watchLoad.ts +0 -75
  163. package/src/zip.ts +0 -69
@@ -0,0 +1,91 @@
1
+ "use strict";
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.totalInSpeed = exports.totalOutSpeed = exports.totalGot = exports.totalSent = exports.throttler = void 0;
8
+ const stream_1 = require("stream");
9
+ const ThrottledStream_1 = require("./ThrottledStream");
10
+ const config_1 = require("./config");
11
+ const misc_1 = require("./misc");
12
+ const connections_1 = require("./connections");
13
+ const lodash_1 = __importDefault(require("lodash"));
14
+ const events_1 = __importDefault(require("./events"));
15
+ const mainThrottleGroup = new ThrottledStream_1.ThrottleGroup(Infinity);
16
+ (0, config_1.defineConfig)('max_kbps', Infinity).sub(v => mainThrottleGroup.updateLimit(v));
17
+ const ip2group = {};
18
+ const SymThrStr = Symbol('stream');
19
+ const SymTimeout = Symbol('timeout');
20
+ const maxKbpsPerIp = (0, config_1.defineConfig)('max_kbps_per_ip', Infinity);
21
+ const throttler = async (ctx, next) => {
22
+ await next();
23
+ const { body } = ctx;
24
+ if (!body || !(body instanceof stream_1.Readable))
25
+ return;
26
+ // we wrap the stream also for unlimited connections to get speed and other features
27
+ const ipGroup = (0, misc_1.getOrSet)(ip2group, ctx.ip, () => {
28
+ var _a;
29
+ const doLimit = ((_a = ctx.state.account) === null || _a === void 0 ? void 0 : _a.ignore_limits) || (0, misc_1.isLocalHost)(ctx) ? undefined : true;
30
+ const group = new ThrottledStream_1.ThrottleGroup(Infinity, doLimit && mainThrottleGroup);
31
+ const unsub = doLimit && maxKbpsPerIp.sub(v => group.updateLimit(v));
32
+ return { group, count: 0, destroy: unsub };
33
+ });
34
+ const conn = ctx.state.connection;
35
+ if (!conn)
36
+ throw 'assert throttler connection';
37
+ const ts = conn[SymThrStr] = new ThrottledStream_1.ThrottledStream(ipGroup.group, conn[SymThrStr]);
38
+ let closed = false;
39
+ const DELAY = 1000;
40
+ const update = lodash_1.default.debounce(() => {
41
+ const ts = conn[SymThrStr];
42
+ const outSpeed = roundKb(ts.getSpeed());
43
+ (0, connections_1.updateConnection)(conn, { outSpeed, sent: ts.getBytesSent() });
44
+ /* in case this stream stands still for a while (before the end), we'll have neither 'sent' or 'close' events,
45
+ * so who will take care to updateConnection? This artificial next-call will ensure just that */
46
+ clearTimeout(conn[SymTimeout]);
47
+ if (outSpeed || !closed)
48
+ conn[SymTimeout] = setTimeout(update, DELAY);
49
+ }, DELAY, { maxWait: DELAY });
50
+ ts.on('sent', (n) => {
51
+ exports.totalSent += n;
52
+ update();
53
+ });
54
+ ++ipGroup.count;
55
+ ts.on('close', () => {
56
+ var _a;
57
+ update.flush();
58
+ closed = true;
59
+ if (--ipGroup.count)
60
+ return; // any left?
61
+ (_a = ipGroup.destroy) === null || _a === void 0 ? void 0 : _a.call(ipGroup);
62
+ delete ip2group[ctx.ip];
63
+ });
64
+ const bak = ctx.response.length; // preserve
65
+ ctx.body = ctx.body.pipe(ts);
66
+ if (bak)
67
+ ctx.response.length = bak;
68
+ };
69
+ exports.throttler = throttler;
70
+ function roundKb(n) {
71
+ return lodash_1.default.round(n, 1) || lodash_1.default.round(n, 3); // further precision if necessary
72
+ }
73
+ exports.totalSent = 0;
74
+ exports.totalGot = 0;
75
+ exports.totalOutSpeed = 0;
76
+ exports.totalInSpeed = 0;
77
+ let lastSent = exports.totalSent;
78
+ let lastGot = exports.totalGot;
79
+ let last = Date.now();
80
+ setInterval(() => {
81
+ const now = Date.now();
82
+ const past = (now - last) / 1000; // seconds
83
+ last = now;
84
+ const deltaSentKb = (exports.totalSent - lastSent) / 1000;
85
+ lastSent = exports.totalSent;
86
+ const deltaGotKb = (exports.totalGot - lastGot) / 1000;
87
+ lastGot = exports.totalGot;
88
+ exports.totalOutSpeed = roundKb(deltaSentKb / past);
89
+ exports.totalInSpeed = roundKb(deltaGotKb / past);
90
+ }, 1000);
91
+ events_1.default.on('connection', (c) => c.socket.on('data', data => exports.totalGot += data.length));
package/src/update.js ADDED
@@ -0,0 +1,70 @@
1
+ "use strict";
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
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.update = exports.getUpdate = void 0;
5
+ const github_1 = require("./github");
6
+ const const_1 = require("./const");
7
+ const path_1 = require("path");
8
+ const child_process_1 = require("child_process");
9
+ const misc_1 = require("./misc");
10
+ const fs_1 = require("fs");
11
+ const plugins_1 = require("./plugins");
12
+ const promises_1 = require("fs/promises");
13
+ const HFS_REPO = 'rejetto/hfs';
14
+ async function getUpdate() {
15
+ const [latest] = await (0, github_1.getRepoInfo)(HFS_REPO + '/releases?per_page=1');
16
+ if (latest.name === const_1.VERSION)
17
+ throw "you already have the latest version: " + const_1.VERSION;
18
+ return latest;
19
+ }
20
+ exports.getUpdate = getUpdate;
21
+ async function update() {
22
+ if (process.argv0.endsWith('node'))
23
+ throw "only binary versions are supported for now";
24
+ const update = await getUpdate();
25
+ const assetSearch = { win32: 'windows', darwin: 'mac', linux: 'linux' }[process.platform];
26
+ if (!assetSearch)
27
+ throw "this feature doesn't support your platform: " + process.platform;
28
+ const asset = update.assets.find((x) => x.name.endsWith('.zip') && x.name.includes(assetSearch));
29
+ if (!asset)
30
+ throw "asset not found";
31
+ const url = asset.browser_download_url;
32
+ console.log("downloading", url);
33
+ const bin = process.argv[0];
34
+ const binPath = (0, path_1.dirname)(bin);
35
+ const binFile = (0, path_1.basename)(bin);
36
+ const newBinFile = 'new-' + binFile;
37
+ plugins_1.pluginsWatcher.pause();
38
+ try {
39
+ await (0, misc_1.unzip)(await (0, misc_1.httpsStream)(url), path => (0, path_1.join)(binPath, path === binFile ? newBinFile : path));
40
+ const newBin = (0, path_1.join)(binPath, newBinFile);
41
+ if (!const_1.IS_WINDOWS) {
42
+ const { mode } = await (0, promises_1.stat)(bin);
43
+ await (0, promises_1.chmod)(newBin, mode).catch(console.error);
44
+ }
45
+ (0, misc_1.onProcessExit)(() => {
46
+ const oldBinFile = 'old-' + binFile;
47
+ console.log("old version is kept as", oldBinFile);
48
+ const oldBin = (0, path_1.join)(binPath, oldBinFile);
49
+ try {
50
+ (0, fs_1.unlinkSync)(oldBin);
51
+ }
52
+ catch (_a) { }
53
+ (0, fs_1.renameSync)(bin, oldBin);
54
+ console.log("launching new version in background", newBinFile);
55
+ (0, child_process_1.spawn)(newBin, ['--updating', binFile], { detached: true, shell: true })
56
+ .on('error', console.error);
57
+ });
58
+ console.log('quitting');
59
+ process.exit();
60
+ }
61
+ catch (_a) {
62
+ plugins_1.pluginsWatcher.unpause();
63
+ }
64
+ }
65
+ exports.update = update;
66
+ if (const_1.argv.updating) { // we were launched with a temporary name, restore original name to avoid breaking references
67
+ const bin = process.argv[0];
68
+ (0, fs_1.renameSync)(bin, (0, path_1.join)((0, path_1.dirname)(bin), const_1.argv.updating));
69
+ console.log("renamed binary file to", const_1.argv.updating);
70
+ }
@@ -0,0 +1,163 @@
1
+ "use strict";
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.prepareFolder = exports.unzip = exports.run = exports.dirStream = exports.adjustStaticPathForGlob = exports.isWindowsDrive = exports.dirTraversal = exports.watchDir = exports.readFileBusy = exports.isFile = exports.isDirectory = void 0;
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const misc_1 = require("./misc");
10
+ const fs_1 = require("fs");
11
+ const path_1 = require("path");
12
+ const fast_glob_1 = __importDefault(require("fast-glob"));
13
+ const const_1 = require("./const");
14
+ const child_process_1 = require("child_process");
15
+ const stream_1 = require("stream");
16
+ // @ts-ignore
17
+ const unzip_stream_1 = __importDefault(require("unzip-stream"));
18
+ async function isDirectory(path) {
19
+ try {
20
+ return (await promises_1.default.stat(path)).isDirectory();
21
+ }
22
+ catch (_a) {
23
+ return false;
24
+ }
25
+ }
26
+ exports.isDirectory = isDirectory;
27
+ async function isFile(path) {
28
+ try {
29
+ return (await promises_1.default.stat(path)).isFile();
30
+ }
31
+ catch (_a) {
32
+ return false;
33
+ }
34
+ }
35
+ exports.isFile = isFile;
36
+ async function readFileBusy(path) {
37
+ return promises_1.default.readFile(path, 'utf8').catch(e => {
38
+ if ((e === null || e === void 0 ? void 0 : e.code) !== 'EBUSY')
39
+ throw e;
40
+ console.debug('busy');
41
+ return (0, misc_1.wait)(100).then(() => readFileBusy(path));
42
+ });
43
+ }
44
+ exports.readFileBusy = readFileBusy;
45
+ function watchDir(dir, cb) {
46
+ let watcher;
47
+ let paused = false;
48
+ try {
49
+ watcher = (0, fs_1.watch)(dir, controlledCb);
50
+ }
51
+ catch (_a) {
52
+ // failing watching the content of the dir, we try to monitor its parent, but filtering events only for our target dir
53
+ const base = (0, path_1.basename)(dir);
54
+ try {
55
+ watcher = (0, fs_1.watch)((0, path_1.dirname)(dir), (event, name) => {
56
+ if (name !== base)
57
+ return;
58
+ try {
59
+ watcher.close(); // if we succeed, we give up the parent watching
60
+ watcher = (0, fs_1.watch)(dir, controlledCb); // attempt at passing to a more specific watching
61
+ }
62
+ catch (_a) { }
63
+ controlledCb();
64
+ });
65
+ }
66
+ catch (e) {
67
+ console.debug(String(e));
68
+ }
69
+ }
70
+ return {
71
+ working() { return Boolean(watcher); },
72
+ stop() { watcher === null || watcher === void 0 ? void 0 : watcher.close(); },
73
+ pause() { paused = true; },
74
+ unpause() { paused = false; },
75
+ };
76
+ function controlledCb() {
77
+ if (!paused)
78
+ cb();
79
+ }
80
+ }
81
+ exports.watchDir = watchDir;
82
+ function dirTraversal(s) {
83
+ return s && /(^|[/\\])\.\.($|[/\\])/.test(s);
84
+ }
85
+ exports.dirTraversal = dirTraversal;
86
+ function isWindowsDrive(s) {
87
+ return s && /^[a-zA-Z]:$/.test(s);
88
+ }
89
+ exports.isWindowsDrive = isWindowsDrive;
90
+ // apply this to paths that may contain \ as separator (not supported by fast-glob) and other special chars to be escaped (parenthesis)
91
+ function adjustStaticPathForGlob(path) {
92
+ return fast_glob_1.default.escapePath(path.replace(/\\/g, '/'));
93
+ }
94
+ exports.adjustStaticPathForGlob = adjustStaticPathForGlob;
95
+ async function* dirStream(path, deep) {
96
+ if (!await isDirectory(path))
97
+ throw Error('ENOTDIR');
98
+ const dirStream = fast_glob_1.default.stream(deep ? '**/*' : '*', {
99
+ cwd: path,
100
+ dot: true,
101
+ deep,
102
+ onlyFiles: false,
103
+ suppressErrors: true,
104
+ objectMode: true,
105
+ });
106
+ const skip = await getItemsToSkip(path);
107
+ for await (const entry of dirStream) {
108
+ let { path, dirent } = entry;
109
+ if (!dirent.isDirectory() && !dirent.isFile())
110
+ continue;
111
+ path = String(path);
112
+ if (!(skip === null || skip === void 0 ? void 0 : skip.includes(path)))
113
+ yield path;
114
+ }
115
+ async function getItemsToSkip(path) {
116
+ if (!const_1.IS_WINDOWS)
117
+ return;
118
+ const out = await run('dir', ['/ah', '/b', path.replace(/\//g, '\\')])
119
+ .catch(() => ''); // error in case of no matching file
120
+ return out.split('\r\n').slice(0, -1);
121
+ }
122
+ }
123
+ exports.dirStream = dirStream;
124
+ function run(cmd, args = []) {
125
+ return new Promise((resolve, reject) => (0, child_process_1.execFile)('cmd', ['/c', cmd, ...args], (err, stdout) => {
126
+ if (err)
127
+ reject(err);
128
+ else
129
+ resolve(stdout);
130
+ }));
131
+ }
132
+ exports.run = run;
133
+ async function unzip(stream, cb) {
134
+ let pending = Promise.resolve();
135
+ return new Promise(resolve => stream.pipe(unzip_stream_1.default.Parse())
136
+ .on('end', () => pending.then(resolve))
137
+ .on('entry', async (entry) => {
138
+ const { path, type } = entry;
139
+ const dest = cb(path);
140
+ if (!dest || type !== 'File')
141
+ return entry.autodrain();
142
+ await pending; // don't overlap writings
143
+ console.debug('unzip', dest);
144
+ await prepareFolder(dest);
145
+ const thisFile = entry.pipe((0, fs_1.createWriteStream)(dest));
146
+ pending = (0, stream_1.once)(thisFile, 'finish');
147
+ }));
148
+ }
149
+ exports.unzip = unzip;
150
+ async function prepareFolder(path, dirnameIt = true) {
151
+ if (dirnameIt)
152
+ path = (0, path_1.dirname)(path);
153
+ if (isWindowsDrive(path))
154
+ return;
155
+ try {
156
+ await promises_1.default.mkdir(path, { recursive: true });
157
+ return true;
158
+ }
159
+ catch (_a) {
160
+ return false;
161
+ }
162
+ }
163
+ exports.prepareFolder = prepareFolder;
@@ -0,0 +1,31 @@
1
+ "use strict";
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
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.asyncGeneratorToReadable = exports.asyncGeneratorToArray = exports.filterMapGenerator = void 0;
5
+ // callback can return undefined to skip element
6
+ const stream_1 = require("stream");
7
+ async function* filterMapGenerator(generator, filterMap) {
8
+ for await (const x of generator) {
9
+ const res = await filterMap(x);
10
+ if (res !== undefined)
11
+ yield res;
12
+ }
13
+ }
14
+ exports.filterMapGenerator = filterMapGenerator;
15
+ async function asyncGeneratorToArray(generator) {
16
+ const ret = [];
17
+ for await (const x of generator)
18
+ ret.push(x);
19
+ return ret;
20
+ }
21
+ exports.asyncGeneratorToArray = asyncGeneratorToArray;
22
+ function asyncGeneratorToReadable(generator) {
23
+ const iterator = generator[Symbol.asyncIterator]();
24
+ return new stream_1.Readable({
25
+ objectMode: true,
26
+ read() {
27
+ iterator.next().then(it => this.push(it.done ? null : it.value));
28
+ }
29
+ });
30
+ }
31
+ exports.asyncGeneratorToReadable = asyncGeneratorToReadable;
@@ -0,0 +1,32 @@
1
+ "use strict";
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.httpsStream = exports.httpsString = void 0;
8
+ const node_https_1 = __importDefault(require("node:https"));
9
+ const const_1 = require("./const");
10
+ function httpsString(url, options = {}) {
11
+ return httpsStream(url, options).then(res => new Promise(resolve => {
12
+ let buf = '';
13
+ res.on('data', chunk => buf += chunk.toString());
14
+ res.on('end', () => resolve(Object.assign(res, {
15
+ ok: (res.statusCode || 400) < 400,
16
+ body: buf
17
+ })));
18
+ }));
19
+ }
20
+ exports.httpsString = httpsString;
21
+ function httpsStream(url, options = {}) {
22
+ return new Promise((resolve, reject) => {
23
+ node_https_1.default.request(url, options, res => {
24
+ if (!res.statusCode || res.statusCode >= 400)
25
+ throw res;
26
+ if (res.statusCode === const_1.HTTP_TEMPORARY_REDIRECT && res.headers.location)
27
+ return resolve(httpsStream(res.headers.location, options));
28
+ resolve(res);
29
+ }).on('error', reject).end();
30
+ });
31
+ }
32
+ exports.httpsStream = httpsStream;
package/src/vfs.js ADDED
@@ -0,0 +1,232 @@
1
+ "use strict";
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.cantReadStatusCode = exports.walkNode = exports.hasPermission = exports.nodeIsDirectory = exports.getNodeName = exports.saveVfs = exports.vfs = exports.urlToNode = exports.MIME_AUTO = exports.defaultPerms = void 0;
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const path_1 = require("path");
10
+ const micromatch_1 = require("micromatch");
11
+ const misc_1 = require("./misc");
12
+ const lodash_1 = __importDefault(require("lodash"));
13
+ const config_1 = require("./config");
14
+ const const_1 = require("./const");
15
+ const events_1 = __importDefault(require("./events"));
16
+ const perm_1 = require("./perm");
17
+ const misc_2 = require("./misc");
18
+ const WHO_ANYONE = true;
19
+ const WHO_NO_ONE = false;
20
+ const WHO_ANY_ACCOUNT = '*';
21
+ exports.defaultPerms = {
22
+ can_see: WHO_ANYONE,
23
+ can_read: WHO_ANYONE,
24
+ can_upload: WHO_NO_ONE,
25
+ };
26
+ exports.MIME_AUTO = 'auto';
27
+ function inheritFromParent(parent, child) {
28
+ var _a;
29
+ for (const k of (0, misc_1.typedKeys)(exports.defaultPerms)) {
30
+ const v = parent[k];
31
+ if (v !== undefined)
32
+ (_a = child[k]) !== null && _a !== void 0 ? _a : (child[k] = v);
33
+ }
34
+ if (typeof parent.mime === 'object' && typeof child.mime === 'object')
35
+ lodash_1.default.defaults(child.mime, parent.mime);
36
+ else
37
+ child.mime || (child.mime = parent.mime);
38
+ return child;
39
+ }
40
+ async function urlToNode(url, ctx, parent = exports.vfs, getRest) {
41
+ var _a;
42
+ let initialSlashes = 0;
43
+ while (url[initialSlashes] === '/')
44
+ initialSlashes++;
45
+ let nextSlash = url.indexOf('/', initialSlashes);
46
+ const name = decodeURIComponent(url.slice(initialSlashes, nextSlash < 0 ? undefined : nextSlash));
47
+ if (!name)
48
+ return parent;
49
+ const rest = nextSlash < 0 ? '' : url.slice(nextSlash + 1, url.endsWith('/') ? -1 : undefined);
50
+ if ((0, misc_1.dirTraversal)(name) || /[\/]/.test(name)) {
51
+ if (ctx)
52
+ ctx.status = const_1.HTTP_FOOL;
53
+ return;
54
+ }
55
+ // does the tree node have a child that goes by this name?
56
+ const sameName = !const_1.IS_WINDOWS ? (x) => x === name // easy
57
+ : (0, misc_2.with_)(name.toLowerCase(), lc => (x) => x.toLowerCase() === lc);
58
+ const child = (_a = parent.children) === null || _a === void 0 ? void 0 : _a.find(x => sameName(getNodeName(x)));
59
+ const ret = {
60
+ ...child,
61
+ original: child,
62
+ isTemp: true,
63
+ };
64
+ inheritFromParent(parent, ret);
65
+ inheritMasks(ret, parent, name);
66
+ applyMasks(ret, parent, name);
67
+ if (child) // yes
68
+ return urlToNode(rest, ctx, ret, getRest);
69
+ // not in the tree, we can see consider continuing on the disk
70
+ if (!parent.source)
71
+ return; // but then we need the current node to be linked to the disk, otherwise, we give up
72
+ let onDisk = name;
73
+ if (parent.rename) { // reverse the mapping
74
+ for (const [from, to] of Object.entries(parent.rename))
75
+ if (name === to) {
76
+ onDisk = from;
77
+ break; // found, search no more
78
+ }
79
+ ret.rename = renameUnderPath(parent.rename, name);
80
+ }
81
+ ret.source = (0, misc_1.enforceFinal)('/', parent.source) + onDisk;
82
+ if (parent.default)
83
+ inheritFromParent({ mime: { '*': exports.MIME_AUTO } }, ret);
84
+ if (rest)
85
+ return urlToNode(rest, ctx, ret, getRest);
86
+ if (ret.source)
87
+ try {
88
+ await promises_1.default.stat(ret.source);
89
+ } // check existence
90
+ catch (_b) {
91
+ if (!getRest)
92
+ return;
93
+ getRest(onDisk);
94
+ return parent;
95
+ }
96
+ return ret;
97
+ }
98
+ exports.urlToNode = urlToNode;
99
+ exports.vfs = {};
100
+ (0, config_1.defineConfig)('vfs', {}).sub(data => exports.vfs = data);
101
+ function saveVfs() {
102
+ return (0, config_1.setConfig)({ vfs: lodash_1.default.cloneDeep(exports.vfs) }, true);
103
+ }
104
+ exports.saveVfs = saveVfs;
105
+ function getNodeName(node) {
106
+ return node.name
107
+ || node.source && (/^[a-zA-Z]:\\?$/.test(node.source) && node.source.slice(0, 2)
108
+ || (0, path_1.basename)(node.source)
109
+ || node.source)
110
+ || ''; // should happen only for root
111
+ }
112
+ exports.getNodeName = getNodeName;
113
+ async function nodeIsDirectory(node) {
114
+ return Boolean(!node.source || await (0, misc_1.isDirectory)(node.source));
115
+ }
116
+ exports.nodeIsDirectory = nodeIsDirectory;
117
+ function hasPermission(node, perm, ctx) {
118
+ var _a;
119
+ return matchWho((_a = node[perm]) !== null && _a !== void 0 ? _a : exports.defaultPerms[perm], ctx)
120
+ && (perm !== 'can_see' || hasPermission(node, 'can_read', ctx)); // can_see is used to hide something you nonetheless can_read, so you MUST also can_read
121
+ }
122
+ exports.hasPermission = hasPermission;
123
+ async function* walkNode(parent, ctx, depth = 0, prefixPath = '') {
124
+ var _a;
125
+ const { children, source } = parent;
126
+ const took = prefixPath ? undefined : new Set();
127
+ if (children)
128
+ for (let idx = 0; idx < children.length; idx++) {
129
+ const child = children[idx];
130
+ const name = prefixPath + getNodeName(child);
131
+ took === null || took === void 0 ? void 0 : took.add(name);
132
+ yield* workItem({
133
+ ...child,
134
+ name,
135
+ }, depth > 0 && await nodeIsDirectory(child).catch(() => false));
136
+ }
137
+ if (!source)
138
+ return;
139
+ try {
140
+ for await (const path of (0, misc_1.dirStream)(source, depth)) {
141
+ if (ctx === null || ctx === void 0 ? void 0 : ctx.req.aborted)
142
+ return;
143
+ const name = prefixPath + (((_a = parent.rename) === null || _a === void 0 ? void 0 : _a[path]) || path);
144
+ if (took === null || took === void 0 ? void 0 : took.has(name))
145
+ continue;
146
+ yield* workItem({
147
+ name,
148
+ source: (0, path_1.join)(source, path),
149
+ rename: renameUnderPath(parent.rename, path),
150
+ });
151
+ }
152
+ }
153
+ catch (e) {
154
+ console.debug('glob', source, e); // ENOTDIR, or lacking permissions
155
+ }
156
+ // item will be changed, so be sure to pass a temp node
157
+ async function* workItem(item, recur = false) {
158
+ const name = getNodeName(item);
159
+ // we basename for depth>0 where we already have the rest of the path in the parent's url, and would be duplicated
160
+ const virtualBasename = (0, path_1.basename)(name);
161
+ item.isTemp = true;
162
+ inheritFromParent(parent, item);
163
+ applyMasks(item, parent, virtualBasename);
164
+ if (ctx && !hasPermission(item, 'can_see', ctx))
165
+ return;
166
+ yield item;
167
+ if (!recur)
168
+ return;
169
+ inheritMasks(item, parent, virtualBasename);
170
+ yield* walkNode(item, ctx, depth - 1, name + '/');
171
+ }
172
+ }
173
+ exports.walkNode = walkNode;
174
+ function applyMasks(item, parent, virtualBasename) {
175
+ const { masks } = parent;
176
+ if (!masks)
177
+ return;
178
+ for (const k in masks)
179
+ if (k.startsWith('**/') && (0, micromatch_1.isMatch)(virtualBasename, k.slice(3))
180
+ || !k.includes('/') && (0, micromatch_1.isMatch)(virtualBasename, k))
181
+ Object.assign(item, masks[k]);
182
+ }
183
+ function inheritMasks(item, parent, virtualBasename) {
184
+ const { masks } = parent;
185
+ if (!masks)
186
+ return;
187
+ const o = {};
188
+ for (const k in masks)
189
+ if (k.startsWith('**/'))
190
+ o[k.slice(3)] = masks[k];
191
+ else if (k.startsWith(virtualBasename + '/'))
192
+ o[k.slice(virtualBasename.length + 1)] = masks[k];
193
+ if (Object.keys(o).length)
194
+ item.masks = o;
195
+ }
196
+ function renameUnderPath(rename, path) {
197
+ if (!rename)
198
+ return rename;
199
+ const match = path + '/';
200
+ rename = Object.fromEntries(Object.entries(rename).map(([k, v]) => [k.startsWith(match) ? k.slice(match.length) : '', v]));
201
+ delete rename[''];
202
+ return lodash_1.default.isEmpty(rename) ? undefined : rename;
203
+ }
204
+ function matchWho(who, ctx) {
205
+ return who === WHO_ANYONE
206
+ || who === WHO_ANY_ACCOUNT && Boolean(ctx.state.account)
207
+ || Array.isArray(who) && (() => // check if I or any ancestor match `who`, but cache ancestors' usernames inside context state
208
+ (0, misc_1.getOrSet)(ctx.state, 'usernames', () => (0, perm_1.getCurrentUsernameExpanded)(ctx)).some((u) => who.includes(u)))();
209
+ }
210
+ function cantReadStatusCode(node) {
211
+ return node.can_read === false ? const_1.HTTP_FORBIDDEN : const_1.HTTP_UNAUTHORIZED;
212
+ }
213
+ exports.cantReadStatusCode = cantReadStatusCode;
214
+ events_1.default.on('accountRenamed', (from, to) => {
215
+ recur(exports.vfs);
216
+ saveVfs();
217
+ function recur(n) {
218
+ var _a;
219
+ for (const k of (0, misc_1.typedKeys)(exports.defaultPerms))
220
+ replace(n[k]);
221
+ if (n.masks)
222
+ Object.values(n.masks).forEach(recur);
223
+ (_a = n.children) === null || _a === void 0 ? void 0 : _a.forEach(recur);
224
+ }
225
+ function replace(a) {
226
+ if (!Array.isArray(a))
227
+ return;
228
+ for (let i = 0; i < a.length; i++)
229
+ if (a[i] === from)
230
+ a[i] = to;
231
+ }
232
+ });