hfs 0.1.6 → 0.26.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 (91) hide show
  1. package/LICENSE.txt +674 -0
  2. package/README.md +102 -8
  3. package/admin/assets/index.dcc78777.css +1 -0
  4. package/admin/assets/index.f056db34.js +282 -0
  5. package/admin/assets/sha512.3c0e384c.js +8 -0
  6. package/admin/index.html +17 -0
  7. package/admin/logo.svg +36 -0
  8. package/frontend/assets/index.55c710c2.js +85 -0
  9. package/frontend/assets/index.ee805a6c.css +1 -0
  10. package/frontend/assets/sha512.634b743e.js +8 -0
  11. package/frontend/fontello.css +77 -0
  12. package/frontend/fontello.woff2 +0 -0
  13. package/frontend/index.html +18 -0
  14. package/package.json +93 -28
  15. package/plugins/antibrute/plugin.js +38 -0
  16. package/plugins/download-counter/plugin.js +47 -0
  17. package/plugins/download-counter/public/hits.js +5 -0
  18. package/plugins/updater-disabled/plugin.js +44 -0
  19. package/plugins/vhosting/plugin.js +42 -0
  20. package/src/QuickZipStream.js +285 -0
  21. package/src/ThrottledStream.js +93 -0
  22. package/src/adminApis.js +169 -0
  23. package/src/api.accounts.js +59 -0
  24. package/src/api.auth.js +130 -0
  25. package/src/api.file_list.js +103 -0
  26. package/src/api.helpers.js +32 -0
  27. package/src/api.monitor.js +102 -0
  28. package/src/api.plugins.js +125 -0
  29. package/src/api.vfs.js +164 -0
  30. package/src/apiMiddleware.js +136 -0
  31. package/src/block.js +33 -0
  32. package/src/commands.js +105 -0
  33. package/src/config.js +172 -0
  34. package/src/connections.js +57 -0
  35. package/src/const.js +83 -0
  36. package/src/crypt.js +21 -0
  37. package/src/debounceAsync.js +48 -0
  38. package/src/events.js +9 -0
  39. package/src/frontEndApis.js +38 -0
  40. package/src/github.js +102 -0
  41. package/src/index.js +53 -0
  42. package/src/listen.js +226 -0
  43. package/src/log.js +137 -0
  44. package/src/middlewares.js +154 -0
  45. package/src/misc.js +160 -0
  46. package/src/pbkdf2.js +74 -0
  47. package/src/perm.js +176 -0
  48. package/src/plugins.js +338 -0
  49. package/src/serveFile.js +104 -0
  50. package/src/serveGuiFiles.js +113 -0
  51. package/src/sse.js +29 -0
  52. package/src/throttler.js +91 -0
  53. package/src/update.js +69 -0
  54. package/src/util-files.js +141 -0
  55. package/src/util-generators.js +30 -0
  56. package/src/util-http.js +30 -0
  57. package/src/vfs.js +227 -0
  58. package/src/watchLoad.js +73 -0
  59. package/src/zip.js +69 -0
  60. package/.npmignore +0 -19
  61. package/admin-server.js +0 -212
  62. package/cli.js +0 -33
  63. package/file-server.js +0 -100
  64. package/lib/common.js +0 -10
  65. package/lib/extending.js +0 -158
  66. package/lib/mime.js +0 -19
  67. package/lib/misc.js +0 -75
  68. package/lib/serving.js +0 -81
  69. package/lib/vfs.js +0 -403
  70. package/main.js +0 -24
  71. package/note.txt +0 -104
  72. package/speedtest.js +0 -21
  73. package/static/backend.css +0 -14
  74. package/static/backend.html +0 -32
  75. package/static/backend.js +0 -694
  76. package/static/extending.js +0 -187
  77. package/static/frontend.css +0 -29
  78. package/static/frontend.html +0 -23
  79. package/static/frontend.js +0 -230
  80. package/static/icons/files/archive.png +0 -0
  81. package/static/icons/files/audio.png +0 -0
  82. package/static/icons/files/file.png +0 -0
  83. package/static/icons/files/folder.png +0 -0
  84. package/static/icons/files/image.png +0 -0
  85. package/static/icons/files/link.png +0 -0
  86. package/static/icons/files/video.png +0 -0
  87. package/static/jquery.js +0 -4
  88. package/static/jquery.rule-1.0.2.js +0 -273
  89. package/static/misc.js +0 -194
  90. package/static/tpl.js +0 -17
  91. package/todo.txt +0 -25
@@ -0,0 +1,136 @@
1
+ "use strict";
2
+ // This file is part of HFS - Copyright 2021-2022, 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.SendListReadable = exports.apiMiddleware = exports.ApiError = void 0;
8
+ const sse_1 = __importDefault(require("./sse"));
9
+ const stream_1 = require("stream");
10
+ const misc_1 = require("./misc");
11
+ const events_1 = __importDefault(require("./events"));
12
+ const const_1 = require("./const");
13
+ const lodash_1 = __importDefault(require("lodash"));
14
+ class ApiError extends Error {
15
+ constructor(status, message) {
16
+ super(typeof message === 'string' ? message : message === null || message === void 0 ? void 0 : message.message);
17
+ this.status = status;
18
+ }
19
+ }
20
+ exports.ApiError = ApiError;
21
+ function apiMiddleware(apis) {
22
+ return async (ctx) => {
23
+ const params = ctx.method === 'POST' ? await getJsonFromReq(ctx.req)
24
+ : (0, misc_1.objSameKeys)(ctx.request.query, x => Array.isArray(x) ? x : (0, misc_1.tryJson)(x));
25
+ console.debug('API', ctx.method, ctx.path, { ...params });
26
+ if (!apis.hasOwnProperty(ctx.path)) {
27
+ ctx.body = 'invalid api';
28
+ return ctx.status = 404;
29
+ }
30
+ const csrf = ctx.cookies.get('csrf');
31
+ // we don't rely on SameSite cookie option because it's https-only
32
+ let res = csrf && csrf !== params.csrf ? new ApiError(const_1.UNAUTHORIZED, 'csrf')
33
+ : await apis[ctx.path](params || {}, ctx);
34
+ if (isAsyncGenerator(res))
35
+ res = (0, misc_1.asyncGeneratorToReadable)(res);
36
+ if (res instanceof stream_1.Readable) { // Readable, we'll go SSE-mode
37
+ res.pipe((0, sse_1.default)(ctx));
38
+ const stillRes = res; // satisfy ts
39
+ ctx.req.on('close', () => // by closing the generated stream, creator of the stream will know the request is over without having to access anything else
40
+ stillRes.destroy());
41
+ return;
42
+ }
43
+ if (res instanceof ApiError) {
44
+ ctx.body = res.message;
45
+ return ctx.status = res.status;
46
+ }
47
+ if (res instanceof Error) { // generic exception
48
+ ctx.body = String(res);
49
+ return ctx.status = 400;
50
+ }
51
+ ctx.body = res;
52
+ };
53
+ }
54
+ exports.apiMiddleware = apiMiddleware;
55
+ function isAsyncGenerator(x) {
56
+ return typeof (x === null || x === void 0 ? void 0 : x.next) === 'function';
57
+ }
58
+ async function getJsonFromReq(req) {
59
+ return new Promise((resolve, reject) => {
60
+ let data = '';
61
+ req.on('data', chunk => data += chunk);
62
+ req.on('error', reject);
63
+ req.on('end', () => {
64
+ try {
65
+ resolve(data && JSON.parse(data));
66
+ }
67
+ catch (e) {
68
+ reject(e);
69
+ }
70
+ });
71
+ });
72
+ }
73
+ class SendListReadable extends stream_1.Readable {
74
+ constructor({ addAtStart, doAtStart, bufferTime } = {}) {
75
+ super({ objectMode: true, read() { } });
76
+ this.buffer = [];
77
+ if (!bufferTime)
78
+ bufferTime = 100;
79
+ this.processBuffer = lodash_1.default.debounce(() => {
80
+ this.push(this.buffer);
81
+ this.buffer = [];
82
+ }, bufferTime, { maxWait: bufferTime });
83
+ this.on('end', () => this.destroy());
84
+ if (doAtStart)
85
+ setTimeout(() => doAtStart(this)); // work later, when list object has been received by Koa
86
+ if (addAtStart) {
87
+ for (const x of addAtStart)
88
+ this.add(x);
89
+ this.ready();
90
+ }
91
+ }
92
+ _push(rec) {
93
+ this.buffer.push(rec);
94
+ if (this.buffer.length > 10000) // hard limit
95
+ this.processBuffer.flush();
96
+ else
97
+ this.processBuffer();
98
+ }
99
+ add(rec) {
100
+ this._push({ add: rec });
101
+ }
102
+ remove(key) {
103
+ this._push({ remove: [key] });
104
+ }
105
+ update(search, change) {
106
+ this._push({ update: [{ search, change }] });
107
+ }
108
+ ready() {
109
+ this._push('ready');
110
+ }
111
+ custom(data) {
112
+ this._push(data);
113
+ }
114
+ error(msg, close = false) {
115
+ this._push({ error: msg });
116
+ this.lastError = msg;
117
+ if (close)
118
+ this.close();
119
+ }
120
+ getLastError() {
121
+ return this.lastError;
122
+ }
123
+ close() {
124
+ this.processBuffer.flush();
125
+ this.push(null);
126
+ }
127
+ events(ctx, eventMap) {
128
+ const off = (0, misc_1.onOff)(events_1.default, eventMap);
129
+ ctx.res.once('close', off);
130
+ return this;
131
+ }
132
+ isClosed() {
133
+ return this.destroyed;
134
+ }
135
+ }
136
+ exports.SendListReadable = SendListReadable;
package/src/block.js ADDED
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.applyBlock = void 0;
7
+ const config_1 = require("./config");
8
+ const connections_1 = require("./connections");
9
+ const misc_1 = require("./misc");
10
+ const cidr_tools_1 = __importDefault(require("cidr-tools"));
11
+ const lodash_1 = __importDefault(require("lodash"));
12
+ (0, config_1.defineConfig)('block', []).sub(rules => {
13
+ compileBlock(rules);
14
+ for (const { socket, ip } of (0, connections_1.getConnections)())
15
+ applyBlock(socket, ip);
16
+ });
17
+ let blockFunctions = []; // "compiled" versions of the rules in config.block
18
+ function compileBlock(rules) {
19
+ blockFunctions = !Array.isArray(rules) ? []
20
+ : (0, misc_1.onlyTruthy)(rules.map(rule => !rule ? null
21
+ : (0, misc_1.with_)(rule.ip, ip => typeof ip !== 'string' ? null
22
+ : ip.includes('/') ? x => cidr_tools_1.default.contains(ip, x)
23
+ : ip.includes('*') ? (0, misc_1.with_)(ipMask2regExp(ip), re => x => re.test(x))
24
+ : x => x === ip)));
25
+ function ipMask2regExp(ipMask) {
26
+ return new RegExp(lodash_1.default.escapeRegExp(ipMask).replace(/\\\*/g, '.*'));
27
+ }
28
+ }
29
+ function applyBlock(socket, ip = (0, connections_1.normalizeIp)(socket.remoteAddress || '')) {
30
+ if (ip && blockFunctions.find(rule => rule(ip)))
31
+ return socket.destroy();
32
+ }
33
+ exports.applyBlock = applyBlock;
@@ -0,0 +1,105 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const perm_1 = require("./perm");
7
+ const config_1 = require("./config");
8
+ const lodash_1 = __importDefault(require("lodash"));
9
+ const update_1 = require("./update");
10
+ const yaml_1 = __importDefault(require("yaml"));
11
+ const const_1 = require("./const");
12
+ console.log(`HINT: type "help" for help`);
13
+ require('readline').createInterface({ input: process.stdin }).on('line', (line) => {
14
+ if (!line)
15
+ return;
16
+ const [name, ...params] = line.trim().split(/ +/);
17
+ const cmd = commands[name];
18
+ if (!cmd)
19
+ return console.error("cannot understand entered command, try 'help'");
20
+ if (cmd.cb.length > params.length)
21
+ return console.error("insufficient parameters, expected: " + cmd.params);
22
+ cmd.cb(...params).then(() => console.log("+++ command executed"), (err) => {
23
+ if (typeof err === 'string')
24
+ console.error("command failed:", err);
25
+ else
26
+ throw err;
27
+ });
28
+ });
29
+ const commands = {
30
+ help: {
31
+ params: '',
32
+ async cb() {
33
+ console.log("supported commands:", ...lodash_1.default.map(commands, ({ params }, name) => '\n - ' + name + ' ' + params));
34
+ }
35
+ },
36
+ 'create-admin': {
37
+ params: '<password> [<username>=admin]',
38
+ async cb(password, username = 'admin') {
39
+ if ((0, perm_1.getAccount)(username))
40
+ throw `user ${username} already exists`;
41
+ const acc = (0, perm_1.addAccount)(username, { admin: true });
42
+ await (0, perm_1.updateAccount)(acc, acc => {
43
+ acc.password = password;
44
+ });
45
+ }
46
+ },
47
+ 'change-password': {
48
+ params: '<user> <password>',
49
+ async cb(user, password) {
50
+ const acc = (0, perm_1.getAccount)(user);
51
+ if (!acc)
52
+ throw "user doesn't exist";
53
+ await (0, perm_1.updateAccount)(acc, acc => {
54
+ acc.password = password;
55
+ });
56
+ }
57
+ },
58
+ config: {
59
+ params: '<key> <value>',
60
+ async cb(key, value) {
61
+ const conf = (0, config_1.getConfigDefinition)(key);
62
+ if (!conf)
63
+ throw "specified key doesn't exist";
64
+ let v = value;
65
+ try {
66
+ v = JSON.parse(v);
67
+ }
68
+ catch (_a) { }
69
+ (0, config_1.setConfig)({ [key]: v });
70
+ }
71
+ },
72
+ 'show-config': {
73
+ params: '<key>',
74
+ async cb(key) {
75
+ const conf = (0, config_1.getConfigDefinition)(key);
76
+ if (!conf)
77
+ throw "specified key doesn't exist";
78
+ console.log(yaml_1.default.stringify((0, config_1.getConfig)(key), { lineWidth: 1000 }).trim());
79
+ }
80
+ },
81
+ quit: {
82
+ params: '',
83
+ async cb() {
84
+ process.exit(0);
85
+ }
86
+ },
87
+ update: {
88
+ params: '',
89
+ cb: update_1.update
90
+ },
91
+ 'check-update': {
92
+ params: '',
93
+ async cb() {
94
+ const update = await (0, update_1.getUpdate)();
95
+ console.log("new version available", update.name);
96
+ }
97
+ },
98
+ version: {
99
+ params: '',
100
+ async cb() {
101
+ console.log(const_1.VERSION);
102
+ console.log(const_1.BUILD_TIMESTAMP);
103
+ }
104
+ },
105
+ };
package/src/config.js ADDED
@@ -0,0 +1,172 @@
1
+ "use strict";
2
+ // This file is part of HFS - Copyright 2021-2022, 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.saveConfigAsap = exports.setConfig = exports.getWholeConfig = exports.getConfig = exports.getConfigDefinition = exports.defineConfig = void 0;
8
+ const events_1 = __importDefault(require("events"));
9
+ const const_1 = require("./const");
10
+ const watchLoad_1 = require("./watchLoad");
11
+ const yaml_1 = __importDefault(require("yaml"));
12
+ const lodash_1 = __importDefault(require("lodash"));
13
+ const misc_1 = require("./misc");
14
+ const fs_1 = require("fs");
15
+ const util_1 = require("util");
16
+ const path_1 = require("path");
17
+ const events_2 = __importDefault(require("./events"));
18
+ const FILE = 'config.yaml';
19
+ const configProps = {};
20
+ let started = false; // this will tell the difference for subscribeConfig()s that are called before or after config is loaded
21
+ let state = {};
22
+ const cfgEvents = new events_1.default();
23
+ cfgEvents.setMaxListeners(10000);
24
+ const path = (0, misc_1.with_)(const_1.argv.config || process.env.HFS_CONFIG, p => {
25
+ if (!p)
26
+ return FILE;
27
+ p = (0, path_1.resolve)(const_1.ORIGINAL_CWD, p);
28
+ try {
29
+ if ((0, fs_1.statSync)(p).isDirectory()) // try to detect if path points to a folder, in which case we add the standard filename
30
+ return (0, path_1.join)(p, FILE);
31
+ }
32
+ catch (_a) { }
33
+ return p;
34
+ });
35
+ console.log("config", path);
36
+ const legacyPosition = (0, path_1.join)(const_1.APP_PATH, FILE);
37
+ if (!(0, fs_1.existsSync)(path) && (0, fs_1.existsSync)(legacyPosition))
38
+ try {
39
+ (0, fs_1.renameSync)(legacyPosition, path);
40
+ console.log("moved from legacy position", legacyPosition);
41
+ }
42
+ catch (_a) {
43
+ try { // attempt copying, in case moving the source file proves to be impractical
44
+ (0, fs_1.copyFileSync)(legacyPosition, path);
45
+ console.log("copied from legacy position", legacyPosition);
46
+ }
47
+ catch (_b) { }
48
+ }
49
+ const { save } = (0, watchLoad_1.watchLoad)(path, values => setConfig(values || {}, false), {
50
+ failedOnFirstAttempt() {
51
+ console.log("No config file, using defaults");
52
+ setConfig({}, false);
53
+ }
54
+ });
55
+ function defineConfig(k, defaultValue) {
56
+ configProps[k] = { defaultValue };
57
+ return {
58
+ key() {
59
+ return k;
60
+ },
61
+ get() {
62
+ return getConfig(k);
63
+ },
64
+ sub(cb) {
65
+ return subscribeConfig(k, cb);
66
+ },
67
+ set(v) {
68
+ if (typeof v === 'function')
69
+ this.set(v(this.get()));
70
+ else
71
+ setConfig1(k, v);
72
+ }
73
+ };
74
+ }
75
+ exports.defineConfig = defineConfig;
76
+ function getConfigDefinition(k) {
77
+ return configProps[k];
78
+ }
79
+ exports.getConfigDefinition = getConfigDefinition;
80
+ const stack = [];
81
+ function subscribeConfig(k, cb) {
82
+ if (started) // initial event already passed, we'll make the first call
83
+ cb(getConfig(k));
84
+ const eventName = 'new.' + k;
85
+ return (0, misc_1.onOff)(cfgEvents, {
86
+ [eventName]() {
87
+ if (stack.includes(cb))
88
+ return; // avoid infinite loop in case a subscriber changes the value
89
+ stack.push(cb); // @ts-ignore arguments
90
+ try {
91
+ return cb.apply(this, arguments);
92
+ }
93
+ finally {
94
+ stack.pop();
95
+ }
96
+ }
97
+ });
98
+ }
99
+ function getConfig(k) {
100
+ var _a, _b;
101
+ return (_a = state[k]) !== null && _a !== void 0 ? _a : lodash_1.default.cloneDeep((_b = configProps[k]) === null || _b === void 0 ? void 0 : _b.defaultValue); // clone to avoid changing
102
+ }
103
+ exports.getConfig = getConfig;
104
+ function getWholeConfig({ omit, only }) {
105
+ const defs = (0, misc_1.objSameKeys)(configProps, x => x.defaultValue);
106
+ let copy = lodash_1.default.defaults({}, state, defs);
107
+ if (omit === null || omit === void 0 ? void 0 : omit.length)
108
+ copy = lodash_1.default.omit(copy, omit);
109
+ if (only)
110
+ copy = lodash_1.default.pick(copy, only);
111
+ return lodash_1.default.cloneDeep(copy);
112
+ }
113
+ exports.getWholeConfig = getWholeConfig;
114
+ // pass a value to `save` to force saving decision, or leave undefined for auto. Passing false will also reset previously loaded configs.
115
+ function setConfig(newCfg, save) {
116
+ if (!started) { // first time we consider also CLI args
117
+ const argCfg = lodash_1.default.pickBy((0, misc_1.objSameKeys)(configProps, (x, k) => const_1.argv[k]), x => x !== undefined);
118
+ if (!lodash_1.default.isEmpty(argCfg)) {
119
+ (0, exports.saveConfigAsap)().then(); // don't set `save` argument, as it would interfere below at check `save===false`
120
+ Object.assign(newCfg, argCfg);
121
+ }
122
+ }
123
+ for (const k in newCfg)
124
+ apply(k, newCfg[k]);
125
+ if (save) {
126
+ (0, exports.saveConfigAsap)().then();
127
+ return;
128
+ }
129
+ if (started) {
130
+ if (save === false) // false is used when loading whole config, and in such case we should not leave previous values untreated. Also, we need this only after we already `started`.
131
+ for (const k of Object.keys(state))
132
+ if (!newCfg.hasOwnProperty(k))
133
+ apply(k, newCfg[k]);
134
+ return;
135
+ }
136
+ // first time we emit also for the default values
137
+ for (const k of Object.keys(configProps))
138
+ if (!newCfg.hasOwnProperty(k))
139
+ apply(k, newCfg[k]);
140
+ started = true;
141
+ events_2.default.emit('config ready');
142
+ function apply(k, newV) {
143
+ return setConfig1(k, newV, save === undefined);
144
+ }
145
+ }
146
+ exports.setConfig = setConfig;
147
+ function setConfig1(k, newV, saveChanges = true) {
148
+ var _a;
149
+ if (lodash_1.default.isPlainObject(newV))
150
+ newV = lodash_1.default.pickBy(newV, x => x !== undefined);
151
+ if ((0, misc_1.same)(newV, (_a = configProps[k]) === null || _a === void 0 ? void 0 : _a.defaultValue))
152
+ newV = undefined;
153
+ if (started && (0, misc_1.same)(newV, state[k]))
154
+ return; // no change
155
+ const was = getConfig(k); // include cloned default, if necessary
156
+ state[k] = newV;
157
+ cfgEvents.emit('new.' + k, getConfig(k), was);
158
+ if (saveChanges)
159
+ (0, exports.saveConfigAsap)().then();
160
+ }
161
+ exports.saveConfigAsap = (0, misc_1.debounceAsync)(async () => {
162
+ while (!started)
163
+ await (0, misc_1.wait)(100);
164
+ let txt = yaml_1.default.stringify(state, { lineWidth: 1000 });
165
+ if (txt.trim() === '{}') // most users wouldn't understand
166
+ if (await (0, util_1.promisify)(fs_1.exists)(path)) // if a file exists then empty it, else don't bother creating it
167
+ txt = '';
168
+ else
169
+ return;
170
+ save(path, txt)
171
+ .catch(err => console.error('Failed at saving config file, please ensure it is writable.', String(err)));
172
+ });
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ // This file is part of HFS - Copyright 2021-2022, 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.updateConnection = exports.socket2connection = exports.getConnections = exports.newConnection = exports.normalizeIp = exports.Connection = void 0;
8
+ const events_1 = __importDefault(require("./events"));
9
+ const lodash_1 = __importDefault(require("lodash"));
10
+ class Connection {
11
+ constructor(socket) {
12
+ this.socket = socket;
13
+ this.started = new Date();
14
+ this.sent = 0;
15
+ all.push(this);
16
+ socket.on('close', () => {
17
+ all.splice(all.indexOf(this), 1);
18
+ events_1.default.emit('connectionClosed', this);
19
+ });
20
+ events_1.default.emit('connection', this);
21
+ }
22
+ get ip() {
23
+ var _a, _b;
24
+ return ((_a = this.ctx) === null || _a === void 0 ? void 0 : _a.ip) || ((_b = this._cachedIp) !== null && _b !== void 0 ? _b : (this._cachedIp = normalizeIp(this.socket.remoteAddress || '')));
25
+ }
26
+ get secure() {
27
+ return this.socket.server.cert > '';
28
+ }
29
+ }
30
+ exports.Connection = Connection;
31
+ function normalizeIp(ip) {
32
+ return ip.replace(/^::ffff:/, ''); // simplify ipv6-mapped addresses
33
+ }
34
+ exports.normalizeIp = normalizeIp;
35
+ const all = [];
36
+ function newConnection(socket) {
37
+ new Connection(socket);
38
+ }
39
+ exports.newConnection = newConnection;
40
+ function getConnections() {
41
+ return all;
42
+ }
43
+ exports.getConnections = getConnections;
44
+ function socket2connection(socket) {
45
+ return all.find(x => // socket exposed by Koa is TLSSocket which encapsulates simple Socket, and I've found no way to access it for simple comparison
46
+ x.socket.remotePort === socket.remotePort // but we can still match them because IP:PORT is key
47
+ && x.socket.remoteAddress === socket.remoteAddress);
48
+ }
49
+ exports.socket2connection = socket2connection;
50
+ function updateConnection(conn, change) {
51
+ // if no change is detected, skip update. ctx is a special case
52
+ if (!change.ctx && Object.entries(change).every(([k, v]) => lodash_1.default.isEqual(v, conn[k])))
53
+ return;
54
+ Object.assign(conn, change);
55
+ events_1.default.emit('connectionUpdated', conn, change);
56
+ }
57
+ exports.updateConnection = updateConnection;
package/src/const.js ADDED
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ // This file is part of HFS - Copyright 2021-2022, 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.APP_PATH = exports.IS_WINDOWS = exports.UNAUTHORIZED = exports.FORBIDDEN = exports.NO_CONTENT = exports.METHOD_NOT_ALLOWED = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = exports.COMPATIBLE_API_VERSION = exports.API_VERSION = exports.SESSION_DURATION = exports.DAY = exports.VERSION = exports.BUILD_TIMESTAMP = exports.HFS_STARTED = exports.ORIGINAL_CWD = exports.DEV = exports.argv = void 0;
31
+ const minimist_1 = __importDefault(require("minimist"));
32
+ const fs = __importStar(require("fs"));
33
+ const os_1 = require("os");
34
+ const fs_1 = require("fs");
35
+ const path_1 = require("path");
36
+ exports.argv = (0, minimist_1.default)(process.argv.slice(2));
37
+ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
38
+ exports.ORIGINAL_CWD = process.cwd();
39
+ exports.HFS_STARTED = new Date();
40
+ const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
41
+ exports.BUILD_TIMESTAMP = fs.statSync(PKG_PATH).mtime.toISOString();
42
+ const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
43
+ exports.VERSION = pkg.version;
44
+ exports.DAY = 86400000;
45
+ exports.SESSION_DURATION = exports.DAY;
46
+ exports.API_VERSION = 4; // introduced type:real_path and api.subscribeConfig/setConfig/getHfsConfig
47
+ exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise is made equal to API_VERSION
48
+ exports.SPECIAL_URI = '/~/';
49
+ exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
50
+ exports.ADMIN_URI = exports.SPECIAL_URI + 'admin/';
51
+ exports.API_URI = exports.SPECIAL_URI + 'api/';
52
+ exports.PLUGINS_PUB_URI = exports.SPECIAL_URI + 'plugins/';
53
+ exports.METHOD_NOT_ALLOWED = 405;
54
+ exports.NO_CONTENT = 204;
55
+ exports.FORBIDDEN = 403;
56
+ exports.UNAUTHORIZED = 401;
57
+ exports.IS_WINDOWS = process.platform === 'win32';
58
+ const IS_BINARY = !(0, path_1.basename)(process.argv0).includes('node'); // this won't be node if pkg was used
59
+ exports.APP_PATH = (0, path_1.dirname)(IS_BINARY ? process.argv0 : __dirname);
60
+ // we want this to be the first stuff to be printed, then we print it in this module, that is executed at the beginning
61
+ if (exports.DEV)
62
+ console.clear();
63
+ else
64
+ console.debug = () => { };
65
+ console.log(`HFS ~ HTTP File Server - Copyright 2021-2022, Massimo Melina <a@rejetto.com>`);
66
+ console.log(`License https://www.gnu.org/licenses/gpl-3.0.txt`);
67
+ console.log('started', exports.HFS_STARTED.toLocaleString(), exports.DEV);
68
+ console.log('version', exports.VERSION || '-');
69
+ console.log('build', exports.BUILD_TIMESTAMP || '-');
70
+ if (exports.argv.cwd)
71
+ process.chdir(exports.argv.cwd);
72
+ else if (!process.argv0.endsWith('.exe')) { // still considering whether to use this behavior with Windows users, who may be less accustomed to it
73
+ const dir = (0, path_1.join)((0, os_1.homedir)(), '.hfs');
74
+ try {
75
+ (0, fs_1.mkdirSync)(dir);
76
+ }
77
+ catch (e) {
78
+ if (e.code !== 'EEXIST')
79
+ console.error(e);
80
+ }
81
+ process.chdir(dir);
82
+ }
83
+ console.log('cwd', process.cwd());
package/src/crypt.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ // This file is part of HFS - Copyright 2021-2022, 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.verifyPassword = exports.hashPassword = void 0;
8
+ // simple wrapper
9
+ // @ts-ignore
10
+ const pbkdf2_1 = require("./pbkdf2");
11
+ const assert_1 = __importDefault(require("assert"));
12
+ async function hashPassword(s) {
13
+ return 'p2:' + await (0, pbkdf2_1.pbkdf2)(s);
14
+ }
15
+ exports.hashPassword = hashPassword;
16
+ async function verifyPassword(hashed, given) {
17
+ const i = hashed.indexOf(':');
18
+ (0, assert_1.default)(i > 0, 'bad hashed');
19
+ return await (0, pbkdf2_1.pbkdf2Verify)(hashed.slice(i + 1), given); // for the time being we totally ignore the "method" part
20
+ }
21
+ exports.verifyPassword = verifyPassword;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ // like lodash.debounce, but also avoids async invocations to overlap
4
+ function debounceAsync(callback, wait = 100, { leading = false, maxWait = Infinity } = {}) {
5
+ let started = 0; // latest callback invocation
6
+ let runningCallback; // latest callback invocation result
7
+ let runningDebouncer; // latest wrapper invocation
8
+ let waitingSince = 0; // we are delaying invocation since
9
+ let whoIsWaiting; // args' array object identifies the pending instance, and incidentally stores args
10
+ const interceptingWrapper = (...args) => runningDebouncer = debouncer.apply(null, args);
11
+ return Object.assign(interceptingWrapper, {
12
+ cancel: () => {
13
+ waitingSince = 0;
14
+ whoIsWaiting = undefined;
15
+ },
16
+ flush: () => runningCallback !== null && runningCallback !== void 0 ? runningCallback : exec(),
17
+ });
18
+ async function debouncer(...args) {
19
+ if (runningCallback)
20
+ return await runningCallback;
21
+ whoIsWaiting = args;
22
+ waitingSince || (waitingSince = Date.now());
23
+ const waitingCap = maxWait - (Date.now() - (waitingSince || started));
24
+ const waitFor = Math.min(waitingCap, leading ? wait - (Date.now() - started) : wait);
25
+ if (waitFor > 0)
26
+ await new Promise(resolve => setTimeout(resolve, waitFor));
27
+ if (!whoIsWaiting) // canceled
28
+ return void (waitingSince = 0);
29
+ if (whoIsWaiting !== args) // another fresher call is waiting
30
+ return runningDebouncer;
31
+ return await exec();
32
+ }
33
+ async function exec() {
34
+ if (!whoIsWaiting)
35
+ return;
36
+ waitingSince = 0;
37
+ started = Date.now();
38
+ try {
39
+ runningCallback = callback.apply(null, whoIsWaiting);
40
+ return await runningCallback;
41
+ }
42
+ finally {
43
+ whoIsWaiting = undefined;
44
+ runningCallback = undefined;
45
+ }
46
+ }
47
+ }
48
+ exports.default = debounceAsync;
package/src/events.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ // This file is part of HFS - Copyright 2021-2022, 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
+ const events_1 = __importDefault(require("events"));
8
+ // app-wide events
9
+ exports.default = new events_1.default();