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.
- package/LICENSE.txt +674 -0
- package/README.md +102 -8
- package/admin/assets/index.dcc78777.css +1 -0
- package/admin/assets/index.f056db34.js +282 -0
- package/admin/assets/sha512.3c0e384c.js +8 -0
- package/admin/index.html +17 -0
- package/admin/logo.svg +36 -0
- package/frontend/assets/index.55c710c2.js +85 -0
- package/frontend/assets/index.ee805a6c.css +1 -0
- package/frontend/assets/sha512.634b743e.js +8 -0
- package/frontend/fontello.css +77 -0
- package/frontend/fontello.woff2 +0 -0
- package/frontend/index.html +18 -0
- package/package.json +93 -28
- package/plugins/antibrute/plugin.js +38 -0
- package/plugins/download-counter/plugin.js +47 -0
- package/plugins/download-counter/public/hits.js +5 -0
- package/plugins/updater-disabled/plugin.js +44 -0
- package/plugins/vhosting/plugin.js +42 -0
- package/src/QuickZipStream.js +285 -0
- package/src/ThrottledStream.js +93 -0
- package/src/adminApis.js +169 -0
- package/src/api.accounts.js +59 -0
- package/src/api.auth.js +130 -0
- package/src/api.file_list.js +103 -0
- package/src/api.helpers.js +32 -0
- package/src/api.monitor.js +102 -0
- package/src/api.plugins.js +125 -0
- package/src/api.vfs.js +164 -0
- package/src/apiMiddleware.js +136 -0
- package/src/block.js +33 -0
- package/src/commands.js +105 -0
- package/src/config.js +172 -0
- package/src/connections.js +57 -0
- package/src/const.js +83 -0
- package/src/crypt.js +21 -0
- package/src/debounceAsync.js +48 -0
- package/src/events.js +9 -0
- package/src/frontEndApis.js +38 -0
- package/src/github.js +102 -0
- package/src/index.js +53 -0
- package/src/listen.js +226 -0
- package/src/log.js +137 -0
- package/src/middlewares.js +154 -0
- package/src/misc.js +160 -0
- package/src/pbkdf2.js +74 -0
- package/src/perm.js +176 -0
- package/src/plugins.js +338 -0
- package/src/serveFile.js +104 -0
- package/src/serveGuiFiles.js +113 -0
- package/src/sse.js +29 -0
- package/src/throttler.js +91 -0
- package/src/update.js +69 -0
- package/src/util-files.js +141 -0
- package/src/util-generators.js +30 -0
- package/src/util-http.js +30 -0
- package/src/vfs.js +227 -0
- package/src/watchLoad.js +73 -0
- package/src/zip.js +69 -0
- package/.npmignore +0 -19
- package/admin-server.js +0 -212
- package/cli.js +0 -33
- package/file-server.js +0 -100
- package/lib/common.js +0 -10
- package/lib/extending.js +0 -158
- package/lib/mime.js +0 -19
- package/lib/misc.js +0 -75
- package/lib/serving.js +0 -81
- package/lib/vfs.js +0 -403
- package/main.js +0 -24
- package/note.txt +0 -104
- package/speedtest.js +0 -21
- package/static/backend.css +0 -14
- package/static/backend.html +0 -32
- package/static/backend.js +0 -694
- package/static/extending.js +0 -187
- package/static/frontend.css +0 -29
- package/static/frontend.html +0 -23
- package/static/frontend.js +0 -230
- package/static/icons/files/archive.png +0 -0
- package/static/icons/files/audio.png +0 -0
- package/static/icons/files/file.png +0 -0
- package/static/icons/files/folder.png +0 -0
- package/static/icons/files/image.png +0 -0
- package/static/icons/files/link.png +0 -0
- package/static/icons/files/video.png +0 -0
- package/static/jquery.js +0 -4
- package/static/jquery.rule-1.0.2.js +0 -273
- package/static/misc.js +0 -194
- package/static/tpl.js +0 -17
- package/todo.txt +0 -25
|
@@ -0,0 +1,285 @@
|
|
|
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.QuickZipStream = void 0;
|
|
31
|
+
const stream_1 = require("stream");
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
const buffer_crc32_1 = require("buffer-crc32");
|
|
34
|
+
const assert_1 = __importDefault(require("assert"));
|
|
35
|
+
const ZIP64_SIZE_LIMIT = 0xffffffff;
|
|
36
|
+
const ZIP64_NUMBER_LIMIT = 0xffff;
|
|
37
|
+
let crc32function;
|
|
38
|
+
Promise.resolve().then(() => __importStar(require('@node-rs/crc32'))).then(lib => crc32function = lib.crc32, () => {
|
|
39
|
+
console.log('using generic lib for crc32');
|
|
40
|
+
crc32function = buffer_crc32_1.unsigned;
|
|
41
|
+
});
|
|
42
|
+
const FLAGS = 0x0808; // bit3 = no crc in local header + bit11 = utf8
|
|
43
|
+
class QuickZipStream extends stream_1.Readable {
|
|
44
|
+
constructor(walker) {
|
|
45
|
+
super({});
|
|
46
|
+
this.walker = walker;
|
|
47
|
+
this.finished = false;
|
|
48
|
+
this.entries = [];
|
|
49
|
+
this.dataWritten = 0;
|
|
50
|
+
this.consumedCalculating = [];
|
|
51
|
+
this.skip = 0;
|
|
52
|
+
}
|
|
53
|
+
earlyClose() {
|
|
54
|
+
this.finished = true;
|
|
55
|
+
this.push(null);
|
|
56
|
+
}
|
|
57
|
+
applyRange(start, end) {
|
|
58
|
+
if (end < start)
|
|
59
|
+
return this.earlyClose();
|
|
60
|
+
this.skip = start;
|
|
61
|
+
this.limit = end - start + 1;
|
|
62
|
+
}
|
|
63
|
+
controlledPush(chunk) {
|
|
64
|
+
if (Array.isArray(chunk))
|
|
65
|
+
chunk = buffer(chunk);
|
|
66
|
+
this.dataWritten += chunk.length;
|
|
67
|
+
if (this.skip) {
|
|
68
|
+
if (this.skip >= chunk.length) {
|
|
69
|
+
this.skip -= chunk.length;
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
chunk = chunk.subarray(this.skip);
|
|
73
|
+
this.skip = 0;
|
|
74
|
+
}
|
|
75
|
+
const lastBit = this.limit < chunk.length;
|
|
76
|
+
if (lastBit)
|
|
77
|
+
chunk = chunk.subarray(0, this.limit);
|
|
78
|
+
const ret = this.push(chunk);
|
|
79
|
+
if (lastBit)
|
|
80
|
+
this.earlyClose();
|
|
81
|
+
return ret;
|
|
82
|
+
}
|
|
83
|
+
async calculateSize(howLong = 1000) {
|
|
84
|
+
const endBy = Date.now() + howLong;
|
|
85
|
+
while (1) {
|
|
86
|
+
if (Date.now() >= endBy)
|
|
87
|
+
return NaN;
|
|
88
|
+
const { value } = await this.walker.next();
|
|
89
|
+
if (!value)
|
|
90
|
+
break;
|
|
91
|
+
this.consumedCalculating.push(value); // we keep same shape of the generator, so
|
|
92
|
+
}
|
|
93
|
+
// if we reach here, then we were able to consume all entries of the walker (in time)
|
|
94
|
+
let offset = 0;
|
|
95
|
+
let centralDirSize = 0;
|
|
96
|
+
for (const file of this.consumedCalculating) {
|
|
97
|
+
const pathSize = Buffer.from(file.path, 'utf8').length;
|
|
98
|
+
const extraLength = (file.size > ZIP64_SIZE_LIMIT ? 2 : 0) + (offset > ZIP64_SIZE_LIMIT ? 1 : 0);
|
|
99
|
+
const extraDataSize = extraLength && (2 + 2 + extraLength * 8);
|
|
100
|
+
offset += 4 + 2 + 2 + 2 + 4 + 4 + 4 + 4 + 2 + 2 + pathSize + file.size;
|
|
101
|
+
centralDirSize += 4 + 2 + 2 + 2 + 2 + 4 + 4 + 4 + 4 + 2 + 2 + 2 + 2 + 2 + 4 + 4 + pathSize + extraDataSize;
|
|
102
|
+
}
|
|
103
|
+
const n = this.consumedCalculating.length;
|
|
104
|
+
const centralDirOffset = offset;
|
|
105
|
+
if (n >= ZIP64_NUMBER_LIMIT
|
|
106
|
+
|| centralDirOffset >= ZIP64_SIZE_LIMIT
|
|
107
|
+
|| centralDirSize >= ZIP64_SIZE_LIMIT)
|
|
108
|
+
centralDirSize += 4 + 8 + 2 + 2 + 4 + 4 + 8 + 8 + 8 + 8 + 4 + 4 + 8 + 4;
|
|
109
|
+
centralDirSize += 4 + 4 + 2 + 2 + 4 + 4 + 2;
|
|
110
|
+
return offset + centralDirSize;
|
|
111
|
+
}
|
|
112
|
+
async _read() {
|
|
113
|
+
if (this.finished || this.destroyed)
|
|
114
|
+
return;
|
|
115
|
+
if (this.workingFile)
|
|
116
|
+
return this.workingFile.resume();
|
|
117
|
+
const file = this.consumedCalculating.shift() || (await this.walker.next()).value;
|
|
118
|
+
if (!file)
|
|
119
|
+
return this.closeArchive();
|
|
120
|
+
let { path, sourcePath, getData, size, ts, mode } = file;
|
|
121
|
+
const pathAsBuffer = Buffer.from(path, 'utf8');
|
|
122
|
+
const offset = this.dataWritten;
|
|
123
|
+
const version = 20;
|
|
124
|
+
this.controlledPush([
|
|
125
|
+
4, 0x04034b50,
|
|
126
|
+
2, version,
|
|
127
|
+
2, FLAGS,
|
|
128
|
+
2, 0,
|
|
129
|
+
...ts2buf(ts),
|
|
130
|
+
4, 0,
|
|
131
|
+
4, 0,
|
|
132
|
+
4, 0,
|
|
133
|
+
2, pathAsBuffer.length,
|
|
134
|
+
2, 0, // extra length
|
|
135
|
+
]);
|
|
136
|
+
this.controlledPush(pathAsBuffer);
|
|
137
|
+
if (this.finished)
|
|
138
|
+
return;
|
|
139
|
+
const cache = sourcePath ? crcCache[sourcePath] : undefined;
|
|
140
|
+
const cacheHit = Number(cache === null || cache === void 0 ? void 0 : cache.ts) === Number(ts);
|
|
141
|
+
let crc = cacheHit ? cache.crc : crc32function('');
|
|
142
|
+
const extAttr = !mode ? 0 : (mode | 0x8000) * 0x10000; // it's like <<16 but doesn't overflow so easily
|
|
143
|
+
const entry = { size, crc, pathAsBuffer, ts, offset, version, extAttr };
|
|
144
|
+
if (this.skip >= size && cacheHit) {
|
|
145
|
+
this.skip -= size;
|
|
146
|
+
this.dataWritten += size;
|
|
147
|
+
this.entries.push(entry);
|
|
148
|
+
setTimeout(() => this.push('')); // this "signal" works only after _read() is done
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const data = getData();
|
|
152
|
+
data.on('error', (err) => console.error(err));
|
|
153
|
+
data.on('end', () => {
|
|
154
|
+
this.workingFile = undefined;
|
|
155
|
+
entry.crc = crc;
|
|
156
|
+
if (sourcePath)
|
|
157
|
+
crcCache[sourcePath] = { ts, crc };
|
|
158
|
+
this.entries.push(entry);
|
|
159
|
+
this.push(''); // continue piping
|
|
160
|
+
});
|
|
161
|
+
this.workingFile = data;
|
|
162
|
+
data.on('data', chunk => {
|
|
163
|
+
if (this.destroyed)
|
|
164
|
+
return data.destroy();
|
|
165
|
+
if (!this.controlledPush(chunk)) // destination buffer full
|
|
166
|
+
data.pause(); // slow down
|
|
167
|
+
if (!cacheHit)
|
|
168
|
+
crc = crc32function(chunk, crc);
|
|
169
|
+
if (this.finished)
|
|
170
|
+
return data.destroy();
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
closeArchive() {
|
|
174
|
+
this.finished = true;
|
|
175
|
+
let centralDirOffset = this.dataWritten;
|
|
176
|
+
for (let { size, ts, crc, offset, pathAsBuffer, version, extAttr } of this.entries) {
|
|
177
|
+
const extra = [];
|
|
178
|
+
if (size > ZIP64_SIZE_LIMIT) {
|
|
179
|
+
extra.push(size, size);
|
|
180
|
+
size = ZIP64_SIZE_LIMIT;
|
|
181
|
+
}
|
|
182
|
+
if (offset > ZIP64_SIZE_LIMIT) {
|
|
183
|
+
extra.push(offset);
|
|
184
|
+
offset = ZIP64_SIZE_LIMIT;
|
|
185
|
+
}
|
|
186
|
+
const extraData = buffer(!extra.length ? []
|
|
187
|
+
: [2, 1, 2, 8 * extra.length, ...extra.map(x => [8, x]).flat()]);
|
|
188
|
+
if (extraData.length && version < 45)
|
|
189
|
+
version = 45;
|
|
190
|
+
this.controlledPush([
|
|
191
|
+
4, 0x02014b50,
|
|
192
|
+
2, version,
|
|
193
|
+
2, version,
|
|
194
|
+
2, FLAGS,
|
|
195
|
+
2, 0,
|
|
196
|
+
...ts2buf(ts),
|
|
197
|
+
4, crc,
|
|
198
|
+
4, size,
|
|
199
|
+
4, size,
|
|
200
|
+
2, pathAsBuffer.length,
|
|
201
|
+
2, extraData.length,
|
|
202
|
+
2, 0,
|
|
203
|
+
2, 0,
|
|
204
|
+
2, 0,
|
|
205
|
+
4, extAttr,
|
|
206
|
+
4, offset,
|
|
207
|
+
]);
|
|
208
|
+
this.controlledPush(pathAsBuffer);
|
|
209
|
+
this.controlledPush(extraData);
|
|
210
|
+
}
|
|
211
|
+
const after = this.dataWritten;
|
|
212
|
+
let centralDirSize = after - centralDirOffset;
|
|
213
|
+
let n = this.entries.length;
|
|
214
|
+
if (n >= ZIP64_NUMBER_LIMIT
|
|
215
|
+
|| centralDirOffset >= ZIP64_SIZE_LIMIT
|
|
216
|
+
|| centralDirSize >= ZIP64_SIZE_LIMIT) {
|
|
217
|
+
this.controlledPush([
|
|
218
|
+
4, 0x06064b50,
|
|
219
|
+
8, 44,
|
|
220
|
+
2, 45,
|
|
221
|
+
2, 45,
|
|
222
|
+
4, 0,
|
|
223
|
+
4, 0,
|
|
224
|
+
8, n,
|
|
225
|
+
8, n,
|
|
226
|
+
8, centralDirSize,
|
|
227
|
+
8, centralDirOffset,
|
|
228
|
+
]);
|
|
229
|
+
this.controlledPush([
|
|
230
|
+
4, 0x07064b50,
|
|
231
|
+
4, 0,
|
|
232
|
+
8, after,
|
|
233
|
+
4, 1,
|
|
234
|
+
]);
|
|
235
|
+
centralDirOffset = ZIP64_SIZE_LIMIT;
|
|
236
|
+
centralDirSize = ZIP64_SIZE_LIMIT;
|
|
237
|
+
n = ZIP64_NUMBER_LIMIT;
|
|
238
|
+
}
|
|
239
|
+
this.controlledPush([
|
|
240
|
+
4, 0x06054b50,
|
|
241
|
+
4, 0,
|
|
242
|
+
2, n,
|
|
243
|
+
2, n,
|
|
244
|
+
4, centralDirSize,
|
|
245
|
+
4, centralDirOffset,
|
|
246
|
+
2, 0, // comment length
|
|
247
|
+
]);
|
|
248
|
+
this.push(null); // EOF
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
exports.QuickZipStream = QuickZipStream;
|
|
252
|
+
function buffer(pairs) {
|
|
253
|
+
(0, assert_1.default)(pairs.length % 2 === 0);
|
|
254
|
+
let total = 0;
|
|
255
|
+
for (let i = 0; i < pairs.length; i += 2)
|
|
256
|
+
total += pairs[i];
|
|
257
|
+
const ret = Buffer.alloc(total, 0);
|
|
258
|
+
let offset = 0;
|
|
259
|
+
let i = 0;
|
|
260
|
+
while (i < pairs.length) {
|
|
261
|
+
const size = pairs[i++];
|
|
262
|
+
const data = pairs[i++];
|
|
263
|
+
if (size === 1)
|
|
264
|
+
ret.writeUInt8(data, offset);
|
|
265
|
+
else if (size === 2)
|
|
266
|
+
ret.writeUInt16LE(data, offset);
|
|
267
|
+
else if (size === 4)
|
|
268
|
+
ret.writeUInt32LE(data, offset);
|
|
269
|
+
else if (size === 8)
|
|
270
|
+
ret.writeBigUInt64LE(BigInt(data), offset);
|
|
271
|
+
else
|
|
272
|
+
throw 'unsupported';
|
|
273
|
+
offset += size;
|
|
274
|
+
}
|
|
275
|
+
return ret;
|
|
276
|
+
}
|
|
277
|
+
function ts2buf(ts) {
|
|
278
|
+
const date = ((ts.getFullYear() - 1980) & 0x7F) << 9 | (ts.getMonth() + 1) << 5 | ts.getDate();
|
|
279
|
+
const time = ts.getHours() << 11 | ts.getMinutes() << 5 | (ts.getSeconds() / 2) & 0x0F;
|
|
280
|
+
return [
|
|
281
|
+
2, time,
|
|
282
|
+
2, date,
|
|
283
|
+
];
|
|
284
|
+
}
|
|
285
|
+
const crcCache = {};
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.ThrottleGroup = exports.ThrottledStream = void 0;
|
|
5
|
+
const stream_1 = require("stream");
|
|
6
|
+
const limiter_1 = require("limiter");
|
|
7
|
+
// throttled stream
|
|
8
|
+
class ThrottledStream extends stream_1.Transform {
|
|
9
|
+
constructor(group, copyStats) {
|
|
10
|
+
super();
|
|
11
|
+
this.group = group;
|
|
12
|
+
this.sent = 0;
|
|
13
|
+
this.lastSpeed = 0;
|
|
14
|
+
this.lastSpeedTime = Date.now();
|
|
15
|
+
this.totalSent = 0; // total sent over connection, since connection can be re-used for multiple requests
|
|
16
|
+
if (!copyStats)
|
|
17
|
+
return;
|
|
18
|
+
this.sent = copyStats.sent;
|
|
19
|
+
this.totalSent = copyStats.totalSent;
|
|
20
|
+
this.lastSpeedTime = copyStats.lastSpeedTime;
|
|
21
|
+
this.lastSpeed = copyStats.lastSpeed;
|
|
22
|
+
}
|
|
23
|
+
async _transform(chunk, encoding, done) {
|
|
24
|
+
let pos = 0;
|
|
25
|
+
while (1) {
|
|
26
|
+
let n = this.group.suggestChunkSize();
|
|
27
|
+
const slice = chunk.slice(pos, pos + n);
|
|
28
|
+
n = slice.length;
|
|
29
|
+
if (!n) // we're done here
|
|
30
|
+
return done();
|
|
31
|
+
try {
|
|
32
|
+
await this.group.consume(n);
|
|
33
|
+
this.push(slice);
|
|
34
|
+
this.sent += n;
|
|
35
|
+
this.totalSent += n;
|
|
36
|
+
pos += n;
|
|
37
|
+
this.emit('sent', n);
|
|
38
|
+
}
|
|
39
|
+
catch (e) {
|
|
40
|
+
done(e);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
// @return kBs
|
|
46
|
+
getSpeed() {
|
|
47
|
+
const now = Date.now();
|
|
48
|
+
const past = now - this.lastSpeedTime;
|
|
49
|
+
if (past >= 1000) { // recalculate?
|
|
50
|
+
this.lastSpeedTime = now;
|
|
51
|
+
this.lastSpeed = this.sent / past;
|
|
52
|
+
this.sent = 0;
|
|
53
|
+
}
|
|
54
|
+
return this.lastSpeed;
|
|
55
|
+
}
|
|
56
|
+
getBytesSent() {
|
|
57
|
+
return this.totalSent;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
exports.ThrottledStream = ThrottledStream;
|
|
61
|
+
class ThrottleGroup {
|
|
62
|
+
constructor(kBs, parent) {
|
|
63
|
+
this.parent = parent;
|
|
64
|
+
this.bucket = this.updateLimit(kBs); // assignment is redundant and yet the best way I've found to shut up typescript
|
|
65
|
+
}
|
|
66
|
+
// @return kBs
|
|
67
|
+
getLimit() {
|
|
68
|
+
return this.bucket.bucketSize / 1000;
|
|
69
|
+
}
|
|
70
|
+
updateLimit(kBs) {
|
|
71
|
+
if (kBs < 0)
|
|
72
|
+
throw new Error('invalid bytesPerSecond');
|
|
73
|
+
kBs *= 1000;
|
|
74
|
+
return this.bucket = new limiter_1.TokenBucket({
|
|
75
|
+
bucketSize: kBs,
|
|
76
|
+
tokensPerInterval: kBs,
|
|
77
|
+
interval: 'second',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
suggestChunkSize() {
|
|
81
|
+
var _a;
|
|
82
|
+
let b = this.bucket;
|
|
83
|
+
b.parentBucket = (_a = this.parent) === null || _a === void 0 ? void 0 : _a.bucket;
|
|
84
|
+
let min = b.bucketSize;
|
|
85
|
+
while (b = b.parentBucket)
|
|
86
|
+
min = Math.min(min, b.bucketSize);
|
|
87
|
+
return min / 10;
|
|
88
|
+
}
|
|
89
|
+
consume(n) {
|
|
90
|
+
return this.bucket.removeTokens(n);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
exports.ThrottleGroup = ThrottleGroup;
|
package/src/adminApis.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
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.ctxAdminAccess = exports.localhostAdmin = exports.adminApis = void 0;
|
|
31
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
32
|
+
const config_1 = require("./config");
|
|
33
|
+
const listen_1 = require("./listen");
|
|
34
|
+
const const_1 = require("./const");
|
|
35
|
+
const api_vfs_1 = __importDefault(require("./api.vfs"));
|
|
36
|
+
const api_accounts_1 = __importDefault(require("./api.accounts"));
|
|
37
|
+
const api_plugins_1 = __importDefault(require("./api.plugins"));
|
|
38
|
+
const api_monitor_1 = __importDefault(require("./api.monitor"));
|
|
39
|
+
const connections_1 = require("./connections");
|
|
40
|
+
const misc_1 = require("./misc");
|
|
41
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
42
|
+
const events_1 = __importDefault(require("./events"));
|
|
43
|
+
const perm_1 = require("./perm");
|
|
44
|
+
const middlewares_1 = require("./middlewares");
|
|
45
|
+
const promises_1 = require("fs/promises");
|
|
46
|
+
const fs_1 = require("fs");
|
|
47
|
+
const readline = __importStar(require("readline"));
|
|
48
|
+
const log_1 = require("./log");
|
|
49
|
+
const child_process_1 = require("child_process");
|
|
50
|
+
const util_1 = require("util");
|
|
51
|
+
exports.adminApis = {
|
|
52
|
+
...api_vfs_1.default,
|
|
53
|
+
...api_accounts_1.default,
|
|
54
|
+
...api_plugins_1.default,
|
|
55
|
+
...api_monitor_1.default,
|
|
56
|
+
async set_config({ values: v }) {
|
|
57
|
+
var _a, _b;
|
|
58
|
+
if (v) {
|
|
59
|
+
const st = (0, listen_1.getStatus)();
|
|
60
|
+
const noHttp = ((_a = v.port) !== null && _a !== void 0 ? _a : listen_1.portCfg.get()) < 0 || !st.httpSrv.listening;
|
|
61
|
+
const noHttps = ((_b = v.https_port) !== null && _b !== void 0 ? _b : listen_1.httpsPortCfg.get()) < 0 || !st.httpsSrv.listening;
|
|
62
|
+
if (noHttp && noHttps)
|
|
63
|
+
return new apiMiddleware_1.ApiError(const_1.FORBIDDEN, "You cannot switch off both http and https ports");
|
|
64
|
+
await (0, config_1.setConfig)(v);
|
|
65
|
+
}
|
|
66
|
+
return {};
|
|
67
|
+
},
|
|
68
|
+
get_config: config_1.getWholeConfig,
|
|
69
|
+
async get_status() {
|
|
70
|
+
const st = (0, listen_1.getStatus)();
|
|
71
|
+
return {
|
|
72
|
+
started: const_1.HFS_STARTED,
|
|
73
|
+
build: const_1.BUILD_TIMESTAMP,
|
|
74
|
+
version: const_1.VERSION,
|
|
75
|
+
apiVersion: const_1.API_VERSION,
|
|
76
|
+
compatibleApiVersion: const_1.COMPATIBLE_API_VERSION,
|
|
77
|
+
http: await serverStatus(st.httpSrv, listen_1.portCfg.get()),
|
|
78
|
+
https: await serverStatus(st.httpsSrv, listen_1.httpsPortCfg.get()),
|
|
79
|
+
urls: (0, listen_1.getUrls)(),
|
|
80
|
+
proxyDetected: (0, middlewares_1.getProxyDetected)(),
|
|
81
|
+
frpDetected: exports.localhostAdmin.get() && !(0, middlewares_1.getProxyDetected)()
|
|
82
|
+
&& (0, connections_1.getConnections)().every(misc_1.isLocalHost)
|
|
83
|
+
&& await frpDebounced(),
|
|
84
|
+
};
|
|
85
|
+
async function serverStatus(h, configuredPort) {
|
|
86
|
+
var _a;
|
|
87
|
+
const busy = await h.busy;
|
|
88
|
+
await (0, misc_1.wait)(0); // simple trick to wait for also .error to be updated. If this trickery becomes necessary elsewhere, then we should make also error a Promise.
|
|
89
|
+
return {
|
|
90
|
+
...lodash_1.default.pick(h, ['listening', 'error']),
|
|
91
|
+
busy,
|
|
92
|
+
port: ((_a = h === null || h === void 0 ? void 0 : h.address()) === null || _a === void 0 ? void 0 : _a.port) || configuredPort,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
async save_pem({ cert, private_key, name = 'self' }) {
|
|
97
|
+
if (!cert || !private_key)
|
|
98
|
+
return new apiMiddleware_1.ApiError(400);
|
|
99
|
+
const files = { cert: name + '.cert', private_key: name + '.key' };
|
|
100
|
+
await (0, promises_1.writeFile)(files.private_key, private_key);
|
|
101
|
+
await (0, promises_1.writeFile)(files.cert, cert);
|
|
102
|
+
return files;
|
|
103
|
+
},
|
|
104
|
+
async get_log({ file = 'log' }, ctx) {
|
|
105
|
+
return new apiMiddleware_1.SendListReadable({
|
|
106
|
+
bufferTime: 10,
|
|
107
|
+
doAtStart(list) {
|
|
108
|
+
const logger = log_1.loggers.find(l => l.name === file);
|
|
109
|
+
if (!logger)
|
|
110
|
+
return list.error(404, true);
|
|
111
|
+
const input = (0, fs_1.createReadStream)(logger.path);
|
|
112
|
+
input.on('error', async (e) => {
|
|
113
|
+
if (e.code === 'ENOENT') // ignore ENOENT, consider it an empty log
|
|
114
|
+
return list.ready();
|
|
115
|
+
list.error(e.code || e.message);
|
|
116
|
+
});
|
|
117
|
+
input.on('end', () => list.ready());
|
|
118
|
+
input.on('ready', () => {
|
|
119
|
+
readline.createInterface({ input }).on('line', line => {
|
|
120
|
+
if (ctx.aborted)
|
|
121
|
+
return input.close();
|
|
122
|
+
const obj = parse(line);
|
|
123
|
+
if (obj)
|
|
124
|
+
list.add(obj);
|
|
125
|
+
}).on('close', () => {
|
|
126
|
+
ctx.res.once('close', (0, misc_1.onOff)(events_1.default, {
|
|
127
|
+
[logger.name](entry) {
|
|
128
|
+
list.add(entry);
|
|
129
|
+
}
|
|
130
|
+
}));
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
function parse(line) {
|
|
136
|
+
const m = /^(.+?) (.+?) (.+?) \[(.{11}):(.{14})] "(\w+) ([^"]+) HTTP\/\d.\d" (\d+) (-|\d+)/.exec(line);
|
|
137
|
+
if (!m)
|
|
138
|
+
return;
|
|
139
|
+
const [, ip, , user, date, time, method, uri, status, length] = m;
|
|
140
|
+
return {
|
|
141
|
+
ip,
|
|
142
|
+
user: user === '-' ? undefined : user,
|
|
143
|
+
ts: new Date(date + ' ' + time),
|
|
144
|
+
method,
|
|
145
|
+
uri,
|
|
146
|
+
status: Number(status),
|
|
147
|
+
length: length === '-' ? undefined : Number(length),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
for (const k in exports.adminApis) {
|
|
153
|
+
const was = exports.adminApis[k];
|
|
154
|
+
exports.adminApis[k] = (params, ctx) => ctxAdminAccess(ctx) ? was(params, ctx)
|
|
155
|
+
: new apiMiddleware_1.ApiError(const_1.UNAUTHORIZED);
|
|
156
|
+
}
|
|
157
|
+
exports.localhostAdmin = (0, config_1.defineConfig)('localhost_admin', true);
|
|
158
|
+
function ctxAdminAccess(ctx) {
|
|
159
|
+
return !ctx.state.proxiedFor // we consider localhost_admin only if no proxy is detected
|
|
160
|
+
&& exports.localhostAdmin.get() && (0, misc_1.isLocalHost)(ctx)
|
|
161
|
+
|| (0, perm_1.getFromAccount)(ctx.state.account, a => a.admin);
|
|
162
|
+
}
|
|
163
|
+
exports.ctxAdminAccess = ctxAdminAccess;
|
|
164
|
+
const frpDebounced = (0, misc_1.debounceAsync)(async () => {
|
|
165
|
+
if (!const_1.IS_WINDOWS)
|
|
166
|
+
return false;
|
|
167
|
+
const { stdout } = await (0, util_1.promisify)(child_process_1.execFile)('tasklist', ['/fi', 'imagename eq frpc.exe', '/nh']);
|
|
168
|
+
return stdout.includes('frpc');
|
|
169
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
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 api_helpers_1 = require("./api.helpers");
|
|
8
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
9
|
+
const perm_1 = require("./perm");
|
|
10
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
11
|
+
const const_1 = require("./const");
|
|
12
|
+
function prepareAccount(ac) {
|
|
13
|
+
return ac && {
|
|
14
|
+
...lodash_1.default.omit(ac, ['password', 'hashed_password', 'srp']),
|
|
15
|
+
username: ac.username,
|
|
16
|
+
hasPassword: (0, perm_1.accountHasPassword)(ac),
|
|
17
|
+
adminActualAccess: (0, perm_1.accountCanLoginAdmin)(ac),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
const apis = {
|
|
21
|
+
get_usernames() {
|
|
22
|
+
return { list: Object.keys((0, perm_1.getAccounts)()) };
|
|
23
|
+
},
|
|
24
|
+
get_account({ username }, ctx) {
|
|
25
|
+
return prepareAccount((0, perm_1.getAccount)(username || (0, perm_1.getCurrentUsername)(ctx)))
|
|
26
|
+
|| new apiMiddleware_1.ApiError(404);
|
|
27
|
+
},
|
|
28
|
+
get_accounts() {
|
|
29
|
+
return { list: Object.values((0, perm_1.getAccounts)()).map(prepareAccount) };
|
|
30
|
+
},
|
|
31
|
+
get_admins() {
|
|
32
|
+
return { list: Object.values((0, perm_1.getAccounts)()).map(prepareAccount).filter(ac => ac === null || ac === void 0 ? void 0 : ac.adminActualAccess).map(ac => ac.username) };
|
|
33
|
+
},
|
|
34
|
+
set_account({ username, changes }) {
|
|
35
|
+
const { admin } = changes;
|
|
36
|
+
if (admin === null)
|
|
37
|
+
changes.admin = undefined;
|
|
38
|
+
else if (admin !== undefined && typeof admin !== 'boolean')
|
|
39
|
+
return new apiMiddleware_1.ApiError(400, "invalid admin");
|
|
40
|
+
return (0, perm_1.setAccount)(username, changes) ? {} : new apiMiddleware_1.ApiError(400);
|
|
41
|
+
},
|
|
42
|
+
add_account({ username, ...rest }) {
|
|
43
|
+
if ((0, perm_1.getAccount)(username))
|
|
44
|
+
return new apiMiddleware_1.ApiError(const_1.FORBIDDEN);
|
|
45
|
+
if (!(0, perm_1.addAccount)(username, rest))
|
|
46
|
+
return new apiMiddleware_1.ApiError(400);
|
|
47
|
+
return {};
|
|
48
|
+
},
|
|
49
|
+
del_account({ username }) {
|
|
50
|
+
return (0, perm_1.delAccount)(username) ? {} : new apiMiddleware_1.ApiError(400);
|
|
51
|
+
},
|
|
52
|
+
async change_password_others({ username, newPassword }) {
|
|
53
|
+
return (0, api_helpers_1.changePasswordHelper)((0, perm_1.getAccount)(username), newPassword);
|
|
54
|
+
},
|
|
55
|
+
async change_srp_others({ username, salt, verifier }) {
|
|
56
|
+
return (0, api_helpers_1.changeSrpHelper)((0, perm_1.getAccount)(username), salt, verifier);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
exports.default = apis;
|