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
package/src/listen.js ADDED
@@ -0,0 +1,235 @@
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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || function (mod) {
20
+ if (mod && mod.__esModule) return mod;
21
+ var result = {};
22
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
23
+ __setModuleDefault(result, mod);
24
+ return result;
25
+ };
26
+ var __importDefault = (this && this.__importDefault) || function (mod) {
27
+ return (mod && mod.__esModule) ? mod : { "default": mod };
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.getUrls = exports.getStatus = exports.httpsPortCfg = exports.openAdmin = exports.portCfg = void 0;
31
+ const http = __importStar(require("http"));
32
+ const config_1 = require("./config");
33
+ const index_1 = require("./index");
34
+ const https = __importStar(require("https"));
35
+ const watchLoad_1 = require("./watchLoad");
36
+ const os_1 = require("os");
37
+ const connections_1 = require("./connections");
38
+ const open_1 = __importDefault(require("open"));
39
+ const misc_1 = require("./misc");
40
+ const const_1 = require("./const");
41
+ const find_process_1 = __importDefault(require("find-process"));
42
+ const perm_1 = require("./perm");
43
+ const lodash_1 = __importDefault(require("lodash"));
44
+ let httpSrv;
45
+ let httpsSrv;
46
+ const openBrowserAtStart = (0, config_1.defineConfig)('open_browser_at_start', !const_1.DEV);
47
+ exports.portCfg = (0, config_1.defineConfig)('port', 80);
48
+ exports.portCfg.sub(async (port) => {
49
+ while (!index_1.app)
50
+ await (0, misc_1.wait)(100);
51
+ stopServer(httpSrv).then();
52
+ httpSrv = Object.assign(http.createServer(index_1.app.callback()), { name: 'http' });
53
+ port = await startServer(httpSrv, { port });
54
+ if (!port)
55
+ return;
56
+ httpSrv.on('connection', connections_1.newConnection);
57
+ printUrls(port, 'http');
58
+ if (openBrowserAtStart.get())
59
+ openAdmin();
60
+ });
61
+ function openAdmin() {
62
+ for (const srv of [httpSrv, httpsSrv]) {
63
+ const a = srv.address();
64
+ if (!a || typeof a === 'string')
65
+ continue;
66
+ const baseUrl = srv.name + '://localhost:' + a.port;
67
+ (0, open_1.default)(baseUrl + const_1.ADMIN_URI, { wait: true }).catch(e => {
68
+ console.debug(String(e));
69
+ console.warn("cannot launch browser on this machine >PLEASE< open your browser and reach one of these (you may need a different address)", ...Object.values(getUrls()).flat().map(x => '\n - ' + x + const_1.ADMIN_URI));
70
+ if (!(0, perm_1.anyAccountCanLoginAdmin)())
71
+ console.log(`HINT: you can enter command: create-admin YOUR_PASSWORD`);
72
+ });
73
+ return true;
74
+ }
75
+ console.log("openAdmin failed");
76
+ }
77
+ exports.openAdmin = openAdmin;
78
+ const considerHttps = (0, misc_1.debounceAsync)(async () => {
79
+ var _a, _b;
80
+ stopServer(httpsSrv).then();
81
+ let port = exports.httpsPortCfg.get();
82
+ try {
83
+ while (!index_1.app)
84
+ await (0, misc_1.wait)(100);
85
+ httpsSrv = Object.assign(https.createServer(port < 0 ? {} : { key: httpsOptions.private_key, cert: httpsOptions.cert }, index_1.app.callback()), { name: 'https', error: undefined });
86
+ if (port >= 0) {
87
+ const namesForOutput = { cert: 'certificate', private_key: 'private key' };
88
+ const missing = (_a = httpsNeeds.find(x => !x.get())) === null || _a === void 0 ? void 0 : _a.key();
89
+ if (missing)
90
+ return httpsSrv.error = "missing " + namesForOutput[missing];
91
+ const cantRead = (_b = httpsNeeds.find(x => !httpsOptions[x.key()])) === null || _b === void 0 ? void 0 : _b.key();
92
+ if (cantRead)
93
+ return httpsSrv.error = "cannot read " + namesForOutput[cantRead];
94
+ }
95
+ }
96
+ catch (e) {
97
+ httpsSrv.error = "bad private key or certificate";
98
+ console.log("failed to create https server: check your private key and certificate", String(e));
99
+ return;
100
+ }
101
+ port = await startServer(httpsSrv, { port });
102
+ if (!port)
103
+ return;
104
+ httpsSrv.on('connection', connections_1.newConnection);
105
+ printUrls(port, 'https');
106
+ });
107
+ const cert = (0, config_1.defineConfig)('cert');
108
+ const privateKey = (0, config_1.defineConfig)('private_key');
109
+ const httpsNeeds = [cert, privateKey];
110
+ const httpsOptions = { cert: '', private_key: '' };
111
+ for (const cfg of httpsNeeds) {
112
+ let unwatch;
113
+ cfg.sub(async (v) => {
114
+ unwatch === null || unwatch === void 0 ? void 0 : unwatch();
115
+ const k = cfg.key();
116
+ httpsOptions[k] = v;
117
+ if (!v || v.includes('\n'))
118
+ return considerHttps();
119
+ // v is a path
120
+ httpsOptions[k] = '';
121
+ unwatch = (0, watchLoad_1.watchLoad)(v, data => {
122
+ httpsOptions[k] = data;
123
+ considerHttps();
124
+ }).unwatch;
125
+ await considerHttps();
126
+ });
127
+ }
128
+ exports.httpsPortCfg = (0, config_1.defineConfig)('https_port', -1);
129
+ exports.httpsPortCfg.sub(considerHttps);
130
+ function startServer(srv, { port, host }) {
131
+ return new Promise(async (resolve) => {
132
+ try {
133
+ if (port < 0 || !host && !await testIpV4()) // !host means ipV4+6, and if v4 port alone is busy we won't be notified of the failure, so we'll first test it on its own
134
+ return resolve(0);
135
+ port = await listen(host);
136
+ if (port)
137
+ console.log(srv.name, "serving on", host || "any network", ':', port);
138
+ resolve(port);
139
+ }
140
+ catch (e) {
141
+ srv.error = String(e);
142
+ console.error(srv.name, "couldn't listen on port", port, srv.error);
143
+ resolve(0);
144
+ }
145
+ });
146
+ async function testIpV4() {
147
+ const res = await listen('0.0.0.0');
148
+ await new Promise(res => srv.close(res));
149
+ return res > 0;
150
+ }
151
+ function listen(host) {
152
+ return new Promise(async (resolve, reject) => {
153
+ srv.listen({ port, host }, () => {
154
+ const ad = srv.address();
155
+ if (!ad)
156
+ return reject('no address');
157
+ if (typeof ad === 'string') {
158
+ srv.close();
159
+ return reject('type of socket not supported');
160
+ }
161
+ resolve(ad.port);
162
+ }).on('error', async (e) => {
163
+ srv.error = String(e);
164
+ srv.busy = undefined;
165
+ const { code } = e;
166
+ if (code === 'EADDRINUSE') {
167
+ srv.busy = (0, find_process_1.default)('port', port).then(res => { var _a; return ((_a = res === null || res === void 0 ? void 0 : res[0]) === null || _a === void 0 ? void 0 : _a.name) || ''; }, () => '');
168
+ srv.error = `port ${port} busy: ${await srv.busy || "unknown process"}`;
169
+ }
170
+ console.error(srv.name, srv.error);
171
+ const k = (srv === httpSrv ? exports.portCfg : exports.httpsPortCfg).key();
172
+ console.log(` >> try specifying a different port, enter this command: config ${k} 8011`);
173
+ resolve(0);
174
+ });
175
+ });
176
+ }
177
+ }
178
+ function stopServer(srv) {
179
+ return new Promise(resolve => {
180
+ if (!(srv === null || srv === void 0 ? void 0 : srv.listening))
181
+ return resolve(null);
182
+ const ad = srv.address();
183
+ if (ad && typeof ad !== 'string')
184
+ console.log("stopped port", ad.port);
185
+ srv.close(err => {
186
+ if (err && err.code !== 'ERR_SERVER_NOT_RUNNING')
187
+ console.debug("failed to stop server", String(err));
188
+ resolve(err);
189
+ });
190
+ });
191
+ }
192
+ function getStatus() {
193
+ return {
194
+ httpSrv,
195
+ httpsSrv,
196
+ };
197
+ }
198
+ exports.getStatus = getStatus;
199
+ const ignore = /^(lo|.*loopback.*|virtualbox.*|.*\(wsl\).*|llw\d|awdl\d|utun\d|anpi\d)$/i; // avoid giving too much information
200
+ function getUrls() {
201
+ return Object.fromEntries((0, misc_1.onlyTruthy)([httpSrv, httpsSrv].map(srv => {
202
+ var _a;
203
+ if (!(srv === null || srv === void 0 ? void 0 : srv.listening))
204
+ return false;
205
+ const port = (_a = srv === null || srv === void 0 ? void 0 : srv.address()) === null || _a === void 0 ? void 0 : _a.port;
206
+ const appendPort = port === (srv.name === 'https' ? 443 : 80) ? '' : ':' + port;
207
+ const urls = (0, misc_1.onlyTruthy)(Object.entries((0, os_1.networkInterfaces)()).map(([name, nets]) => nets && !ignore.test(name) && nets.map(net => {
208
+ if (net.internal)
209
+ return;
210
+ let { address } = net;
211
+ if (address.includes(':'))
212
+ address = '[' + address + ']';
213
+ return srv.name + '://' + address + appendPort;
214
+ })).flat());
215
+ return urls.length && [srv.name, urls];
216
+ })));
217
+ }
218
+ exports.getUrls = getUrls;
219
+ function printUrls(port, proto) {
220
+ if (!port)
221
+ return;
222
+ for (const [name, nets] of Object.entries((0, os_1.networkInterfaces)())) {
223
+ if (!nets || ignore.test(name))
224
+ continue;
225
+ lodash_1.default.remove(nets, 'internal');
226
+ if (!nets.length)
227
+ continue;
228
+ const best = lodash_1.default.find(nets, { family: 'IPv4' }) || nets[0];
229
+ const appendPort = port === (proto === 'https' ? 443 : 80) ? '' : ':' + port;
230
+ let { address } = best;
231
+ if (address.includes(':'))
232
+ address = '[' + address + ']';
233
+ console.log('network', name, proto + '://' + address + appendPort);
234
+ }
235
+ }
package/src/log.js ADDED
@@ -0,0 +1,137 @@
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 __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || function (mod) {
20
+ if (mod && mod.__esModule) return mod;
21
+ var result = {};
22
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
23
+ __setModuleDefault(result, mod);
24
+ return result;
25
+ };
26
+ var __importDefault = (this && this.__importDefault) || function (mod) {
27
+ return (mod && mod.__esModule) ? mod : { "default": mod };
28
+ };
29
+ Object.defineProperty(exports, "__esModule", { value: true });
30
+ exports.log = exports.loggers = void 0;
31
+ const config_1 = require("./config");
32
+ const fs_1 = require("fs");
33
+ const util = __importStar(require("util"));
34
+ const promises_1 = require("fs/promises");
35
+ const const_1 = require("./const");
36
+ const events_1 = __importDefault(require("./events"));
37
+ const lodash_1 = __importDefault(require("lodash"));
38
+ const util_files_1 = require("./util-files");
39
+ const perm_1 = require("./perm");
40
+ class Logger {
41
+ constructor(name) {
42
+ this.name = name;
43
+ this.path = '';
44
+ }
45
+ async setPath(path) {
46
+ var _a;
47
+ this.path = path;
48
+ (_a = this.stream) === null || _a === void 0 ? void 0 : _a.end();
49
+ this.last = undefined;
50
+ if (!path)
51
+ return this.stream = undefined;
52
+ try {
53
+ const stats = await (0, promises_1.stat)(path);
54
+ this.last = stats.mtime || stats.ctime;
55
+ }
56
+ catch (_b) {
57
+ if (await (0, util_files_1.prepareFolder)(path) === false)
58
+ console.log("cannot create folder for", path);
59
+ }
60
+ this.reopen();
61
+ }
62
+ reopen() {
63
+ return this.stream = (0, fs_1.createWriteStream)(this.path, { flags: 'a' })
64
+ .on('error', () => this.stream = undefined);
65
+ }
66
+ }
67
+ // we'll have names same as config keys. These are used also by the get_log api.
68
+ const accessLogger = new Logger('log');
69
+ const accessErrorLog = new Logger('error_log');
70
+ exports.loggers = [accessLogger, accessErrorLog];
71
+ (0, config_1.defineConfig)('log', 'logs/access.log').sub(path => {
72
+ console.debug('access log file: ' + (path || 'disabled'));
73
+ accessLogger.setPath(path);
74
+ });
75
+ const errorLogFile = (0, config_1.defineConfig)(accessErrorLog.name, 'logs/access-error.log');
76
+ errorLogFile.sub(path => {
77
+ console.debug('access error log: ' + (path || 'disabled'));
78
+ accessErrorLog.setPath(path);
79
+ });
80
+ const logRotation = (0, config_1.defineConfig)('log_rotation', 'weekly');
81
+ function log() {
82
+ const debounce = lodash_1.default.debounce(cb => cb(), 1000);
83
+ return async (ctx, next) => {
84
+ var _a;
85
+ await next();
86
+ const isError = ctx.status >= 400;
87
+ const logger = isError && accessErrorLog || accessLogger;
88
+ const rotate = (_a = logRotation.get()) === null || _a === void 0 ? void 0 : _a[0];
89
+ let { stream, last, path } = logger;
90
+ if (!stream)
91
+ return;
92
+ const now = new Date();
93
+ const a = now.toString().split(' ');
94
+ logger.last = now;
95
+ if (rotate && last) { // rotation enabled and a file exists?
96
+ const passed = Number(now) - Number(last)
97
+ - 3600000; // be pessimistic and count a possible DST change
98
+ if (rotate === 'm' && (passed >= 31 * const_1.DAY || now.getMonth() !== last.getMonth())
99
+ || rotate === 'd' && (passed >= const_1.DAY || now.getDate() !== last.getDate()) // checking passed will solve the case when the day of the month is the same but a month has passed
100
+ || rotate === 'w' && (passed >= 7 * const_1.DAY || now.getDay() < last.getDay())) {
101
+ stream.end();
102
+ const postfix = last.getFullYear() + '-' + doubleDigit(last.getMonth() + 1) + '-' + doubleDigit(last.getDate());
103
+ try { // other logging requests shouldn't happen while we are renaming. Since this is very infrequent we can tolerate solving this by making it sync.
104
+ (0, fs_1.renameSync)(path, path + '-' + postfix);
105
+ }
106
+ catch (e) { // ok, rename failed, but this doesn't mean we ain't gonna log
107
+ console.error(e);
108
+ }
109
+ stream = logger.reopen(); // keep variable updated
110
+ if (!stream)
111
+ return;
112
+ }
113
+ }
114
+ const format = '%s - %s [%s] "%s %s HTTP/%s" %d %s\n'; // Apache's Common Log Format
115
+ const date = a[2] + '/' + a[1] + '/' + a[3] + ':' + a[4] + ' ' + a[5].slice(3);
116
+ const user = (0, perm_1.getCurrentUsername)(ctx);
117
+ events_1.default.emit(logger.name, Object.assign(lodash_1.default.pick(ctx, ['ip', 'method', 'status', 'length']), { user, ts: now, uri: ctx.path }));
118
+ console.debug(ctx.status, ctx.method, ctx.path);
119
+ debounce(() => // once in a while we check if the file is still good (not deleted, etc), or we'll reopen it
120
+ (0, promises_1.stat)(logger.path).catch(() => logger.reopen())); // async = smoother but we may lose some entries
121
+ stream.write(util.format(format, ctx.ip, user || '-', date, ctx.method, ctx.path, ctx.req.httpVersion, ctx.status, ctx.length ? ctx.length.toString() : '-'));
122
+ };
123
+ }
124
+ exports.log = log;
125
+ function doubleDigit(n) {
126
+ return n > 9 ? n : '0' + n;
127
+ }
128
+ // dump console.error to file
129
+ const debugLogFile = (0, fs_1.createWriteStream)('debug.log', { flags: 'a' });
130
+ debugLogFile.on('open', () => {
131
+ const was = console.error;
132
+ console.error = function (...args) {
133
+ was.apply(this, args);
134
+ const params = args.map(x => typeof x === 'string' ? x : JSON.stringify(x)).join(' ');
135
+ debugLogFile.write(new Date().toLocaleString() + ': ' + params + '\n');
136
+ };
137
+ }).on('error', () => console.log("cannot create debug.log"));
@@ -0,0 +1,195 @@
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.paramsDecoder = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.serveGuiAndSharedFiles = exports.sessions = exports.headRequests = exports.gzipper = void 0;
8
+ const koa_compress_1 = __importDefault(require("koa-compress"));
9
+ const koa_session_1 = __importDefault(require("koa-session"));
10
+ const const_1 = require("./const");
11
+ const const_2 = require("./const");
12
+ const vfs_1 = require("./vfs");
13
+ const misc_1 = require("./misc");
14
+ const zip_1 = require("./zip");
15
+ const serveFile_1 = require("./serveFile");
16
+ const serveGuiFiles_1 = require("./serveGuiFiles");
17
+ const koa_mount_1 = __importDefault(require("koa-mount"));
18
+ const stream_1 = require("stream");
19
+ const block_1 = require("./block");
20
+ const perm_1 = require("./perm");
21
+ const connections_1 = require("./connections");
22
+ const basic_auth_1 = __importDefault(require("basic-auth"));
23
+ const tssrp6a_1 = require("tssrp6a");
24
+ const api_auth_1 = require("./api.auth");
25
+ const path_1 = require("path");
26
+ const fs_1 = require("fs");
27
+ const promises_1 = require("stream/promises");
28
+ exports.gzipper = (0, koa_compress_1.default)({
29
+ threshold: 2048,
30
+ gzip: { flush: require('zlib').constants.Z_SYNC_FLUSH },
31
+ deflate: { flush: require('zlib').constants.Z_SYNC_FLUSH },
32
+ br: false,
33
+ filter(type) {
34
+ return /text|javascript|style/i.test(type);
35
+ },
36
+ });
37
+ const headRequests = async (ctx, next) => {
38
+ const head = ctx.method === 'HEAD';
39
+ if (head)
40
+ ctx.method = 'GET'; // let other middlewares work, so we can collect the size at the end
41
+ await next();
42
+ if (!head || ctx.body === undefined)
43
+ return;
44
+ const { length, status } = ctx.response;
45
+ if (ctx.body)
46
+ ctx.body = stream_1.Readable.from(''); // empty the body for this is a HEAD request. Using Readable avoids koa from trying to set length to 0
47
+ ctx.status = status;
48
+ if (length)
49
+ ctx.response.length = length;
50
+ };
51
+ exports.headRequests = headRequests;
52
+ const sessions = (app) => (0, koa_session_1.default)({
53
+ key: 'hfs_$id',
54
+ signed: true,
55
+ rolling: true,
56
+ maxAge: const_1.SESSION_DURATION,
57
+ }, app);
58
+ exports.sessions = sessions;
59
+ const serveFrontendFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.FRONTEND_PROXY, const_2.FRONTEND_URI);
60
+ const serveFrontendPrefixed = (0, koa_mount_1.default)(const_2.FRONTEND_URI.slice(0, -1), serveFrontendFiles);
61
+ const serveAdminPrefixed = (0, koa_mount_1.default)(const_1.ADMIN_URI.slice(0, -1), (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, const_1.ADMIN_URI));
62
+ const serveGuiAndSharedFiles = async (ctx, next) => {
63
+ const { path } = ctx;
64
+ if (ctx.body)
65
+ return next();
66
+ if (path.startsWith(const_2.FRONTEND_URI))
67
+ return serveFrontendPrefixed(ctx, next);
68
+ if (path + '/' === const_1.ADMIN_URI)
69
+ return ctx.redirect(const_1.ADMIN_URI);
70
+ if (path.startsWith(const_1.ADMIN_URI))
71
+ return serveAdminPrefixed(ctx, next);
72
+ if (ctx.method === 'PUT') { // curl -T file url/
73
+ const decPath = decodeURI(path);
74
+ let rest = (0, path_1.basename)(decPath);
75
+ const folder = await (0, vfs_1.urlToNode)((0, path_1.dirname)(decPath), ctx, vfs_1.vfs, v => rest = v + '/' + rest);
76
+ if (!folder)
77
+ return ctx.status = const_1.HTTP_NOT_FOUND;
78
+ return await receiveUpload(folder, rest, ctx.req, ctx);
79
+ }
80
+ const node = await (0, vfs_1.urlToNode)(path, ctx);
81
+ if (!node)
82
+ return ctx.status = const_1.HTTP_NOT_FOUND;
83
+ const canRead = (0, vfs_1.hasPermission)(node, 'can_read', ctx);
84
+ const isFolder = await (0, vfs_1.nodeIsDirectory)(node);
85
+ if (isFolder && !path.endsWith('/'))
86
+ return ctx.redirect(path + '/');
87
+ if (canRead && !isFolder)
88
+ return node.source ? (0, serveFile_1.serveFileNode)(node)(ctx, next)
89
+ : next();
90
+ if (!canRead) {
91
+ ctx.status = (0, vfs_1.cantReadStatusCode)(node);
92
+ if (ctx.status === const_1.HTTP_FORBIDDEN)
93
+ return;
94
+ const browserDetected = ctx.get('Upgrade-Insecure-Requests') || ctx.get('Sec-Fetch-Mode'); // ugh, heuristics
95
+ if (!browserDetected) // we don't want to trigger basic authentication on browsers, it's meant for download managers only
96
+ return ctx.set('WWW-Authenticate', 'Basic'); // we support basic authentication
97
+ ctx.state.serveApp = true;
98
+ return serveFrontendFiles(ctx, next);
99
+ }
100
+ ctx.set({ server: 'HFS ' + const_1.BUILD_TIMESTAMP });
101
+ const { get } = ctx.query;
102
+ if (get === 'zip')
103
+ return await (0, zip_1.zipStreamFromFolder)(node, ctx);
104
+ if (node.default) {
105
+ const def = await (0, vfs_1.urlToNode)(path + node.default, ctx);
106
+ return !def ? next()
107
+ : (0, vfs_1.hasPermission)(def, 'can_read', ctx) ? (0, serveFile_1.serveFileNode)(def)(ctx, next)
108
+ : ctx.status = (0, vfs_1.cantReadStatusCode)(def);
109
+ }
110
+ return serveFrontendFiles(ctx, next);
111
+ };
112
+ exports.serveGuiAndSharedFiles = serveGuiAndSharedFiles;
113
+ async function receiveUpload(base, path, stream, ctx) {
114
+ if (!base.source || !(0, vfs_1.hasPermission)(base, 'can_upload', ctx))
115
+ return ctx.status = base.can_upload === false ? const_1.HTTP_FORBIDDEN : const_1.HTTP_UNAUTHORIZED;
116
+ path = (0, path_1.join)(base.source, path);
117
+ await (0, misc_1.prepareFolder)(path);
118
+ const dest = (0, fs_1.createWriteStream)(path);
119
+ await (0, promises_1.pipeline)(stream, dest);
120
+ ctx.body = '{}';
121
+ }
122
+ let proxyDetected = false;
123
+ const someSecurity = async (ctx, next) => {
124
+ ctx.request.ip = (0, connections_1.normalizeIp)(ctx.ip);
125
+ try {
126
+ let proxy = ctx.get('X-Forwarded-For');
127
+ // we have some dev-proxies to ignore
128
+ if (const_1.DEV && proxy && [process.env.FRONTEND_PROXY, process.env.ADMIN_PROXY].includes(ctx.get('X-Forwarded-port')))
129
+ proxy = '';
130
+ if ((0, misc_1.dirTraversal)(decodeURI(ctx.path)))
131
+ return ctx.status = const_1.HTTP_FOOL;
132
+ if ((0, block_1.applyBlock)(ctx.socket, ctx.ip))
133
+ return;
134
+ proxyDetected || (proxyDetected = proxy > '');
135
+ ctx.state.proxiedFor = proxy;
136
+ }
137
+ catch (_a) {
138
+ return ctx.status = const_1.HTTP_FOOL;
139
+ }
140
+ return next();
141
+ };
142
+ exports.someSecurity = someSecurity;
143
+ // this is only about http proxies
144
+ function getProxyDetected() {
145
+ return proxyDetected;
146
+ }
147
+ exports.getProxyDetected = getProxyDetected;
148
+ const prepareState = async (ctx, next) => {
149
+ var _a, _b;
150
+ // calculate these once and for all
151
+ ctx.state.account = (_a = await getHttpAccount(ctx)) !== null && _a !== void 0 ? _a : (0, perm_1.getAccount)((_b = ctx.session) === null || _b === void 0 ? void 0 : _b.username, false);
152
+ const conn = ctx.state.connection = (0, connections_1.socket2connection)(ctx.socket);
153
+ await next();
154
+ if (conn)
155
+ (0, connections_1.updateConnection)(conn, { ctx });
156
+ };
157
+ exports.prepareState = prepareState;
158
+ async function getHttpAccount(ctx) {
159
+ const credentials = (0, basic_auth_1.default)(ctx.req);
160
+ const account = (0, perm_1.getAccount)((credentials === null || credentials === void 0 ? void 0 : credentials.name) || '');
161
+ if (account && await srpCheck(account.username, credentials.pass))
162
+ return account;
163
+ }
164
+ async function srpCheck(username, password) {
165
+ const account = (0, perm_1.getAccount)(username);
166
+ if (!(account === null || account === void 0 ? void 0 : account.srp) || !password)
167
+ return false;
168
+ const { step1, salt, pubKey } = await (0, api_auth_1.srpStep1)(account);
169
+ const client = new tssrp6a_1.SRPClientSession(new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters()));
170
+ const clientRes1 = await client.step1(username, password);
171
+ const clientRes2 = await clientRes1.step2(BigInt(salt), BigInt(pubKey));
172
+ return await step1.step2(clientRes2.A, clientRes2.M1).then(() => true, () => false);
173
+ }
174
+ // unify get/post parameters, with JSON decoding to not be limited to strings
175
+ const paramsDecoder = async (ctx, next) => {
176
+ ctx.params = ctx.method === 'POST' ? (0, misc_1.tryJson)(await stream2string(ctx.req))
177
+ : (0, misc_1.objSameKeys)(ctx.query, x => Array.isArray(x) ? x : (0, misc_1.tryJson)(x));
178
+ await next();
179
+ };
180
+ exports.paramsDecoder = paramsDecoder;
181
+ async function stream2string(stream) {
182
+ return new Promise((resolve, reject) => {
183
+ let data = '';
184
+ stream.on('data', chunk => data += chunk);
185
+ stream.on('error', reject);
186
+ stream.on('end', () => {
187
+ try {
188
+ resolve(data);
189
+ }
190
+ catch (e) {
191
+ reject(e);
192
+ }
193
+ });
194
+ });
195
+ }