hfs 0.53.0 → 0.54.0-alpha3
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/admin/assets/index-CcUio6cm.css +1 -0
- package/admin/assets/index-DCsMhd8s.js +807 -0
- package/admin/assets/{sha512-rpja4qx0.js → sha512-VvQJXb6I.js} +1 -1
- package/admin/index.html +4 -2
- package/frontend/assets/index-legacy-vQymU_EM.js +60 -0
- package/frontend/assets/{sha512-legacy-pz5im6D1.js → sha512-legacy-BD2cMJIT.js} +1 -1
- package/frontend/index.html +2 -2
- package/package.json +8 -6
- package/plugins/antibrute/plugin.js +1 -1
- package/plugins/download-counter/plugin.js +0 -15
- package/plugins/list-uploader/public/main.js +3 -2
- package/src/acme.js +2 -2
- package/src/adminApis.js +23 -3
- package/src/api.accounts.js +1 -1
- package/src/api.auth.js +6 -4
- package/src/api.get_file_list.js +18 -9
- package/src/api.monitor.js +1 -1
- package/src/api.net.js +4 -1
- package/src/api.plugins.js +2 -0
- package/src/api.vfs.js +6 -4
- package/src/apiMiddleware.js +4 -1
- package/src/auth.js +7 -7
- package/src/basicWeb.js +1 -1
- package/src/block.js +13 -1
- package/src/commands.js +8 -2
- package/src/const.js +13 -10
- package/src/cross-const.js +2 -1
- package/src/cross.js +25 -8
- package/src/debounceAsync.js +3 -5
- package/src/dirStream.js +138 -0
- package/src/events.js +18 -8
- package/src/frontEndApis.js +32 -15
- package/src/github.js +21 -3
- package/src/langs/embedded.js +4 -1
- package/src/langs/hfs-lang-fi.json +5 -3
- package/src/langs/hfs-lang-hu.json +27 -19
- package/src/langs/hfs-lang-it.json +2 -1
- package/src/langs/hfs-lang-th.json +166 -0
- package/src/langs/hfs-lang-tr.json +166 -0
- package/src/langs/hfs-lang-uk.json +170 -0
- package/src/langs/hfs-lang-vi.json +5 -5
- package/src/listen.js +12 -4
- package/src/log.js +1 -1
- package/src/makeQ.js +31 -0
- package/src/middlewares.js +11 -8
- package/src/misc.js +2 -1
- package/src/nat.js +4 -4
- package/src/perm.js +1 -1
- package/src/persistence.js +2 -1
- package/src/plugins.js +22 -3
- package/src/serveFile.js +16 -14
- package/src/serveGuiAndSharedFiles.js +9 -4
- package/src/serveGuiFiles.js +8 -6
- package/src/srp.js +2 -2
- package/src/update.js +48 -7
- package/src/upload.js +33 -18
- package/src/util-files.js +7 -29
- package/src/vfs.js +23 -11
- package/src/watchLoad.js +1 -1
- package/src/zip.js +4 -4
- package/README.md +0 -232
- package/admin/assets/index-Ly9xr1F6.js +0 -811
- package/admin/assets/index-gNE0rRfC.css +0 -1
- package/frontend/assets/index-legacy-5MEl9EM8.js +0 -60
- /package/frontend/assets/{polyfills-legacy-31b7nyOi.js → polyfills-legacy-DMrMt_pQ.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
System.register(["./index-legacy-
|
|
1
|
+
System.register(["./index-legacy-vQymU_EM.js"],(function(h,t){"use strict";var i,s;return{setters:[function(h){i=h.g,s=h.c}],execute:function(){function t(h,t){for(var i=function(){var i=t[s];if("string"!=typeof i&&!Array.isArray(i)){var r=function(t){if("default"!==t&&!(t in h)){var s=Object.getOwnPropertyDescriptor(i,t);s&&Object.defineProperty(h,t,s.get?s:{enumerable:!0,get:function(){return i[t]}})}};for(var e in i)r(e)}},s=0;s<t.length;s++)i();return Object.freeze(Object.defineProperty(h,Symbol.toStringTag,{value:"Module"}))}var r={exports:{}};
|
|
2
2
|
/*
|
|
3
3
|
* [js-sha512]{@link https://github.com/emn178/js-sha512}
|
|
4
4
|
*
|
package/frontend/index.html
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
</head>
|
|
9
9
|
<body>
|
|
10
10
|
<div id="root">Wait for loading or <a href="?get=basic">use basic interface now</a></div>
|
|
11
|
-
<script crossorigin id="vite-legacy-polyfill" src="/assets/polyfills-legacy-
|
|
12
|
-
<script crossorigin id="vite-legacy-entry" data-src="/assets/index-legacy-
|
|
11
|
+
<script crossorigin id="vite-legacy-polyfill" src="/assets/polyfills-legacy-DMrMt_pQ.js"></script>
|
|
12
|
+
<script crossorigin id="vite-legacy-entry" data-src="/assets/index-legacy-vQymU_EM.js">System.import(document.getElementById('vite-legacy-entry').getAttribute('data-src'))</script>
|
|
13
13
|
</body>
|
|
14
14
|
</html>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hfs",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.54.0-alpha3",
|
|
4
4
|
"description": "HTTP File Server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file server",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"central.json",
|
|
60
60
|
"admin/**/*",
|
|
61
61
|
"frontend/**/*",
|
|
62
|
+
"**/node_modules/fswin/x64/*",
|
|
62
63
|
"**/node_modules/axios/dist/node/*"
|
|
63
64
|
],
|
|
64
65
|
"targets": [
|
|
@@ -69,15 +70,16 @@
|
|
|
69
70
|
]
|
|
70
71
|
},
|
|
71
72
|
"dependencies": {
|
|
72
|
-
"@koa/router": "^
|
|
73
|
-
"@node-rs/crc32": "^1.
|
|
73
|
+
"@koa/router": "^13.0.1",
|
|
74
|
+
"@node-rs/crc32": "^1.10.3",
|
|
74
75
|
"@rejetto/kvstorage": "^0.12.2",
|
|
75
|
-
"acme-client": "^5.
|
|
76
|
-
"buffer-crc32": "^0.
|
|
76
|
+
"acme-client": "^5.4.0",
|
|
77
|
+
"buffer-crc32": "^1.0.0",
|
|
77
78
|
"fast-glob": "^3.2.7",
|
|
78
79
|
"find-process": "^1.4.7",
|
|
79
80
|
"formidable": "^3.5.1",
|
|
80
81
|
"fs-x-attributes": "^1.0.2",
|
|
82
|
+
"fswin": "^3.24.829",
|
|
81
83
|
"iconv-lite": "^0.6.3",
|
|
82
84
|
"ip2location-nodejs": "^9.6.0",
|
|
83
85
|
"koa": "^2.13.4",
|
|
@@ -92,7 +94,7 @@
|
|
|
92
94
|
"qr-creator": "^1.0.0",
|
|
93
95
|
"picomatch": "^3.0.1",
|
|
94
96
|
"tssrp6a": "^3.0.0",
|
|
95
|
-
"unzip-stream": "^0.3.
|
|
97
|
+
"unzip-stream": "^0.3.4",
|
|
96
98
|
"valtio": "^1.10.3",
|
|
97
99
|
"yaml": "^2.0.0-10"
|
|
98
100
|
},
|
|
@@ -19,7 +19,7 @@ exports.init = api => {
|
|
|
19
19
|
const { block } = api.require('./block')
|
|
20
20
|
return {
|
|
21
21
|
unload: api.events.multi({
|
|
22
|
-
attemptingLogin: async
|
|
22
|
+
attemptingLogin: async ctx => {
|
|
23
23
|
const { ip } = ctx
|
|
24
24
|
const now = new Date
|
|
25
25
|
const rec = getOrSet(byIp, ip, () => ({ attempts: 0, next: now }))
|
|
@@ -16,21 +16,6 @@ exports.configDialog = {
|
|
|
16
16
|
|
|
17
17
|
exports.init = async api => {
|
|
18
18
|
const db = await api.openDb('counters.kv', { defaultPutDelay: 5_000, maxPutDelay: 30_000 })
|
|
19
|
-
|
|
20
|
-
try { // load legacy file
|
|
21
|
-
const countersFile = 'counters.yaml'
|
|
22
|
-
const yaml = api.require('yaml')
|
|
23
|
-
const { readFile, unlink } = api.require('fs/promises')
|
|
24
|
-
const data = await readFile(countersFile, 'utf8')
|
|
25
|
-
for (const [k,v] of Object.entries(yaml.parse(data)))
|
|
26
|
-
db.put(uri2key(k), v)
|
|
27
|
-
await unlink(countersFile)
|
|
28
|
-
api.log("data converted")
|
|
29
|
-
}
|
|
30
|
-
catch(err) {
|
|
31
|
-
if (err.code !== 'ENOENT')
|
|
32
|
-
api.log(err)
|
|
33
|
-
}
|
|
34
19
|
return {
|
|
35
20
|
frontend_js: 'main.js',
|
|
36
21
|
frontend_css: 'style.css',
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
const text = React.useMemo(() => {
|
|
11
11
|
if (!data || data === true) return ''
|
|
12
12
|
const { upload: x } = data
|
|
13
|
+
const shouldShowIpWithUser = display === 'ip+user' && x.ip || display === 'tooltip' && HFS.state.adminUrl
|
|
13
14
|
return !x ? ''
|
|
14
15
|
: display === 'user' ? x.username
|
|
15
|
-
: display === 'ip' || !x.username ? x.ip
|
|
16
|
-
: x.ip + ' (' + x.username + ')'
|
|
16
|
+
: display === 'ip' || !x.username && shouldShowIpWithUser ? x.ip
|
|
17
|
+
: shouldShowIpWithUser ? x.ip + ' (' + x.username + ')' : (x.username || ' ')
|
|
17
18
|
}, [data])
|
|
18
19
|
const iconOnly = display === 'tooltip'
|
|
19
20
|
return text && HFS.h('span', { className: 'uploader', title: HFS.t`Uploader` + (iconOnly ? ' ' + text : '') },
|
package/src/acme.js
CHANGED
|
@@ -113,7 +113,7 @@ exports.makeCert = (0, debounceAsync_1.debounceAsync)(async (domain, email, altN
|
|
|
113
113
|
await promises_1.default.writeFile(KEY_FILE, res.key);
|
|
114
114
|
listen_1.cert.set(CERT_FILE); // update config
|
|
115
115
|
listen_1.privateKey.set(KEY_FILE);
|
|
116
|
-
}
|
|
116
|
+
});
|
|
117
117
|
const acmeDomain = (0, config_1.defineConfig)('acme_domain', '');
|
|
118
118
|
const acmeEmail = (0, config_1.defineConfig)('acme_email', '');
|
|
119
119
|
const acmeRenew = (0, config_1.defineConfig)('acme_renew', false); // handle config changes
|
|
@@ -133,4 +133,4 @@ const renewCert = (0, debounceAsync_1.debounceAsync)(async () => {
|
|
|
133
133
|
return console.log("certificate still good");
|
|
134
134
|
await (0, exports.makeCert)(domain, acmeEmail.get(), altNames)
|
|
135
135
|
.catch(e => console.log("error renewing certificate: ", String(e.message || e)));
|
|
136
|
-
},
|
|
136
|
+
}, { retain: misc_1.DAY, retainFailure: misc_1.HOUR });
|
package/src/adminApis.js
CHANGED
|
@@ -32,6 +32,8 @@ const geo_1 = require("./geo");
|
|
|
32
32
|
const roots_1 = require("./roots");
|
|
33
33
|
const SendList_1 = require("./SendList");
|
|
34
34
|
const ddns_1 = require("./ddns");
|
|
35
|
+
const block_1 = require("./block");
|
|
36
|
+
const github_1 = require("./github");
|
|
35
37
|
exports.adminApis = {
|
|
36
38
|
...api_vfs_1.default,
|
|
37
39
|
...api_accounts_1.default,
|
|
@@ -71,6 +73,10 @@ exports.adminApis = {
|
|
|
71
73
|
async check_update() {
|
|
72
74
|
return { options: await (0, update_1.getUpdates)() };
|
|
73
75
|
},
|
|
76
|
+
async wait_project_info() {
|
|
77
|
+
await (0, github_1.getProjectInfo)();
|
|
78
|
+
return {};
|
|
79
|
+
},
|
|
74
80
|
async ip_country({ ips }) {
|
|
75
81
|
const res = await Promise.allSettled(ips.map(geo_1.ip2country));
|
|
76
82
|
return {
|
|
@@ -107,7 +113,11 @@ exports.adminApis = {
|
|
|
107
113
|
baseUrl: await (0, listen_1.getBaseUrlOrDefault)(),
|
|
108
114
|
roots: roots_1.roots.get(),
|
|
109
115
|
updatePossible: !await (0, update_1.updateSupported)() ? false : (await (0, update_1.localUpdateAvailable)()) ? 'local' : true,
|
|
116
|
+
autoCheckUpdateResult: update_1.autoCheckUpdateResult.get(),
|
|
117
|
+
alerts: github_1.alerts.get(),
|
|
110
118
|
proxyDetected: (0, middlewares_1.getProxyDetected)(),
|
|
119
|
+
cloudflareDetected: middlewares_1.cloudflareDetected,
|
|
120
|
+
ram: process.memoryUsage.rss(),
|
|
111
121
|
frpDetected: exports.localhostAdmin.get() && !(0, middlewares_1.getProxyDetected)()
|
|
112
122
|
&& (0, connections_1.getConnections)().every(misc_1.isLocalHost)
|
|
113
123
|
&& await frpDebounced(),
|
|
@@ -121,9 +131,19 @@ exports.adminApis = {
|
|
|
121
131
|
await (0, promises_1.writeFile)(files.cert, cert);
|
|
122
132
|
return files;
|
|
123
133
|
},
|
|
134
|
+
async add_block({ merge, ip, expire, comment }) {
|
|
135
|
+
(0, misc_1.apiAssertTypes)({
|
|
136
|
+
string: { ip },
|
|
137
|
+
string_undefined: { comment, expire },
|
|
138
|
+
object_undefined: { merge },
|
|
139
|
+
});
|
|
140
|
+
const optionals = lodash_1.default.pickBy({ expire, comment }, v => v !== undefined); // passing undefined-s would override values in merge
|
|
141
|
+
(0, block_1.addBlock)({ ip, ...optionals }, merge);
|
|
142
|
+
return {};
|
|
143
|
+
}
|
|
124
144
|
};
|
|
125
|
-
for (const [k, was] of
|
|
126
|
-
exports.adminApis[k] = (params, ctx) => {
|
|
145
|
+
for (const [k, was] of (0, misc_1.typedEntries)(exports.adminApis))
|
|
146
|
+
exports.adminApis[k] = ((params, ctx) => {
|
|
127
147
|
if (!allowAdmin(ctx))
|
|
128
148
|
return new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN);
|
|
129
149
|
if (ctxAdminAccess(ctx))
|
|
@@ -132,7 +152,7 @@ for (const [k, was] of Object.entries(exports.adminApis))
|
|
|
132
152
|
return ctx.headers.accept === 'text/event-stream'
|
|
133
153
|
? new SendList_1.SendListReadable({ doAtStart: x => x.error(const_1.HTTP_UNAUTHORIZED, true, props) })
|
|
134
154
|
: new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED, props);
|
|
135
|
-
};
|
|
155
|
+
});
|
|
136
156
|
exports.localhostAdmin = (0, config_1.defineConfig)('localhost_admin', true);
|
|
137
157
|
exports.adminNet = (0, config_1.defineConfig)('admin_net', '', v => (0, misc_1.makeNetMatcher)(v, true));
|
|
138
158
|
exports.favicon = (0, config_1.defineConfig)('favicon', '');
|
package/src/api.accounts.js
CHANGED
|
@@ -28,7 +28,7 @@ exports.default = {
|
|
|
28
28
|
|| new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
|
|
29
29
|
},
|
|
30
30
|
get_accounts() {
|
|
31
|
-
return { list: Object.values(perm_1.accountsConfig.get()).map(prepareAccount) };
|
|
31
|
+
return { list: (0, misc_1.onlyTruthy)(Object.values(perm_1.accountsConfig.get()).map(prepareAccount)) };
|
|
32
32
|
},
|
|
33
33
|
get_admins() {
|
|
34
34
|
return { list: lodash_1.default.filter(perm_1.accountsConfig.get(), perm_1.accountCanLoginAdmin).map(ac => ac.username) };
|
package/src/api.auth.js
CHANGED
|
@@ -21,16 +21,16 @@ const loginSrp1 = async ({ username }, ctx) => {
|
|
|
21
21
|
const account = (0, perm_1.getAccount)(username);
|
|
22
22
|
if (!ctx.session)
|
|
23
23
|
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR);
|
|
24
|
-
await events_1.default.emitAsync('attemptingLogin', ctx);
|
|
24
|
+
await events_1.default.emitAsync('attemptingLogin', { ctx, username });
|
|
25
25
|
if (!account || !(0, perm_1.accountCanLogin)(account)) { // TODO simulate fake account to prevent knowing valid usernames
|
|
26
26
|
ctx.logExtra({ u: username });
|
|
27
27
|
ctx.state.dontLog = false; // log even if log_api is false
|
|
28
28
|
return new apiMiddleware_1.ApiError(const_1.HTTP_UNAUTHORIZED);
|
|
29
29
|
}
|
|
30
30
|
try {
|
|
31
|
-
const {
|
|
31
|
+
const { srpServer, ...rest } = await (0, auth_1.srpServerStep1)(account);
|
|
32
32
|
const sid = Math.random();
|
|
33
|
-
ongoingLogins[sid] =
|
|
33
|
+
ongoingLogins[sid] = srpServer;
|
|
34
34
|
setTimeout(() => delete ongoingLogins[sid], 60000);
|
|
35
35
|
ctx.session.loggingIn = { username, sid }; // temporarily store until process is complete
|
|
36
36
|
return rest;
|
|
@@ -81,8 +81,10 @@ const logout = async ({}, ctx) => {
|
|
|
81
81
|
exports.logout = logout;
|
|
82
82
|
const refresh_session = async ({}, ctx) => {
|
|
83
83
|
var _a;
|
|
84
|
+
const username = (0, auth_1.getCurrentUsername)(ctx);
|
|
84
85
|
return !ctx.session ? new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR) : {
|
|
85
|
-
username
|
|
86
|
+
username,
|
|
87
|
+
expandedUsername: (0, perm_1.expandUsername)(username),
|
|
86
88
|
adminUrl: (0, adminApis_1.ctxAdminAccess)(ctx) ? ctx.state.revProxyPath + const_1.ADMIN_URI : undefined,
|
|
87
89
|
canChangePassword: canChangePassword(ctx.state.account),
|
|
88
90
|
exp: keepSessionAlive.get() ? new Date(Date.now() + middlewares_1.sessionDuration.compiled()) : undefined,
|
package/src/api.get_file_list.js
CHANGED
|
@@ -14,7 +14,8 @@ const connections_1 = require("./connections");
|
|
|
14
14
|
const adminApis_1 = require("./adminApis");
|
|
15
15
|
const upload_1 = require("./upload");
|
|
16
16
|
const SendList_1 = require("./SendList");
|
|
17
|
-
const get_file_list = async ({ uri = '/', offset, limit, search, c, onlyFolders, admin }, ctx) => {
|
|
17
|
+
const get_file_list = async ({ uri = '/', offset, limit, search, wild, c, onlyFolders, admin }, ctx) => {
|
|
18
|
+
var _a;
|
|
18
19
|
const node = await (0, vfs_1.urlToNode)(uri, ctx);
|
|
19
20
|
const list = ctx.get('accept') === 'text/event-stream' ? new SendList_1.SendListReadable() : undefined;
|
|
20
21
|
if (!node)
|
|
@@ -29,17 +30,19 @@ const get_file_list = async ({ uri = '/', offset, limit, search, c, onlyFolders,
|
|
|
29
30
|
return fail();
|
|
30
31
|
offset = Number(offset);
|
|
31
32
|
limit = Number(limit);
|
|
32
|
-
|
|
33
|
+
search = String(search || '').toLocaleLowerCase();
|
|
34
|
+
const filter = wild === 'no' ? (s) => s.includes(search)
|
|
35
|
+
: (0, misc_1.pattern2filter)(search);
|
|
33
36
|
const walker = (0, vfs_1.walkNode)(node, { ctx: admin ? undefined : ctx, onlyFolders, depth: search ? Infinity : 0 });
|
|
34
37
|
const onDirEntryHandlers = (0, plugins_1.mapPlugins)(plug => plug.onDirEntry);
|
|
35
38
|
const can_upload = admin || (0, vfs_1.hasPermission)(node, 'can_upload', ctx);
|
|
36
|
-
const fakeChild = await (0, vfs_1.applyParentToChild)(
|
|
39
|
+
const fakeChild = await (0, vfs_1.applyParentToChild)({ source: 'x' }, node); // can we delete children
|
|
37
40
|
const can_delete = admin || (0, vfs_1.hasPermission)(fakeChild, 'can_delete', ctx);
|
|
38
|
-
const can_archive = admin || (0, vfs_1.hasPermission)(
|
|
41
|
+
const can_archive = admin || (0, vfs_1.hasPermission)(fakeChild, 'can_archive', ctx);
|
|
39
42
|
const can_comment = can_upload && (0, comments_1.areCommentsEnabled)();
|
|
40
43
|
const can_overwrite = can_upload && (can_delete || !upload_1.dontOverwriteUploading.get());
|
|
41
|
-
const comment = await (0, comments_1.getCommentFor)(node.source);
|
|
42
|
-
const props = { can_archive, can_upload, can_delete, can_overwrite, can_comment, comment, accept: node.accept };
|
|
44
|
+
const comment = (_a = node.comment) !== null && _a !== void 0 ? _a : await (0, comments_1.getCommentFor)(node.source);
|
|
45
|
+
const props = { can_archive, can_upload, can_delete, can_overwrite, can_comment, comment, accept: node.accept, icon: getNodeIcon(node) };
|
|
43
46
|
ctx.state.browsing = uri.replace(/\/{2,}/g, '/');
|
|
44
47
|
(0, connections_1.updateConnectionForCtx)(ctx);
|
|
45
48
|
if (!list)
|
|
@@ -94,14 +97,19 @@ const get_file_list = async ({ uri = '/', offset, limit, search, c, onlyFolders,
|
|
|
94
97
|
break;
|
|
95
98
|
}
|
|
96
99
|
}
|
|
100
|
+
function getNodeIcon(node) {
|
|
101
|
+
var _a;
|
|
102
|
+
return ((_a = node.icon) === null || _a === void 0 ? void 0 : _a.includes('.')) || node.icon; // true = specific for this file, otherwise is a SYS_ICONS
|
|
103
|
+
}
|
|
97
104
|
async function nodeToDirEntry(ctx, node) {
|
|
105
|
+
var _a;
|
|
98
106
|
const { source, url } = node;
|
|
99
107
|
const name = (0, vfs_1.getNodeName)(node);
|
|
100
108
|
if (url)
|
|
101
109
|
return name ? { n: name, url, target: node.target } : null;
|
|
102
110
|
const isFolder = await (0, vfs_1.nodeIsDirectory)(node);
|
|
103
111
|
try {
|
|
104
|
-
const st = source ? await (0, promises_1.stat)(source) : undefined;
|
|
112
|
+
const st = source ? node.stats || await (0, promises_1.stat)(source) : undefined;
|
|
105
113
|
const pl = node.can_list === misc_1.WHO_NO_ONE ? 'l'
|
|
106
114
|
: !(0, vfs_1.hasPermission)(node, 'can_list', ctx) ? 'L'
|
|
107
115
|
: '';
|
|
@@ -117,11 +125,12 @@ const get_file_list = async ({ uri = '/', offset, limit, search, c, onlyFolders,
|
|
|
117
125
|
m: !st || Math.abs(+st.mtime - +st.ctime) < 1000 ? undefined : st.mtime,
|
|
118
126
|
s: isFolder ? undefined : st === null || st === void 0 ? void 0 : st.size,
|
|
119
127
|
p: (pr + pl + pd + pa) || undefined,
|
|
120
|
-
comment: await (0, comments_1.getCommentFor)(source),
|
|
128
|
+
comment: (_a = node.comment) !== null && _a !== void 0 ? _a : await (0, comments_1.getCommentFor)(source),
|
|
129
|
+
icon: getNodeIcon(node),
|
|
121
130
|
web: await (0, vfs_1.hasDefaultFile)(node, ctx) ? true : undefined,
|
|
122
131
|
};
|
|
123
132
|
}
|
|
124
|
-
catch (
|
|
133
|
+
catch (_b) {
|
|
125
134
|
return null;
|
|
126
135
|
}
|
|
127
136
|
function filesInsideCould(n = node) {
|
package/src/api.monitor.js
CHANGED
|
@@ -71,7 +71,7 @@ exports.default = {
|
|
|
71
71
|
...s.browsing ? { op: 'browsing', path: decodeURIComponent(s.browsing) }
|
|
72
72
|
: s.uploadPath ? { op: 'upload', path: decodeURIComponent(s.uploadPath) }
|
|
73
73
|
: {
|
|
74
|
-
op: !s.considerAsGui &&
|
|
74
|
+
op: !s.considerAsGui && (ctx.state.archive || ctx.state.vfsNode) ? 'download' : undefined,
|
|
75
75
|
path: (0, misc_1.try_)(() => decodeURIComponent(ctx.originalUrl), () => ctx.originalUrl),
|
|
76
76
|
},
|
|
77
77
|
opProgress: lodash_1.default.isNumber(s.opProgress) ? lodash_1.default.round(s.opProgress, 3) : undefined,
|
package/src/api.net.js
CHANGED
|
@@ -57,7 +57,10 @@ const apis = {
|
|
|
57
57
|
return new apiMiddleware_1.ApiError(const_1.HTTP_SERVER_ERROR, 'removeMapping failed: ' + String(e));
|
|
58
58
|
}
|
|
59
59
|
if (external) // must use the object form of 'public' to work around a bug of the library
|
|
60
|
-
await nat_1.upnpClient.createMapping({ private: internal || internalPort, public: { host: '', port: external }, description: 'hfs', ttl: 0 })
|
|
60
|
+
await nat_1.upnpClient.createMapping({ private: internal || internalPort, public: { host: '', port: external }, description: 'hfs', ttl: 0 })
|
|
61
|
+
.catch(res => {
|
|
62
|
+
throw new apiMiddleware_1.ApiError(res.errorCode, res.errorCode === 718 ? "Port not available" : res.errorDescription);
|
|
63
|
+
});
|
|
61
64
|
return {};
|
|
62
65
|
},
|
|
63
66
|
async self_check({ url }) {
|
package/src/api.plugins.js
CHANGED
|
@@ -43,6 +43,8 @@ const apis = {
|
|
|
43
43
|
if (online.version !== disk.version) { // not just newer one, in case a version was retired
|
|
44
44
|
online.id = disk.id; // id is installation-dependant, and online cannot know
|
|
45
45
|
online.repo = serialize(disk).repo; // show the user the current repo we are getting this update from, not a possibly-changed future one
|
|
46
|
+
if (online.version < disk.version)
|
|
47
|
+
online.downgrade = true;
|
|
46
48
|
list.add(online);
|
|
47
49
|
}
|
|
48
50
|
}
|
package/src/api.vfs.js
CHANGED
|
@@ -21,14 +21,14 @@ async function urlToNodeOriginal(uri) {
|
|
|
21
21
|
const n = await (0, vfs_1.urlToNode)(uri);
|
|
22
22
|
return (n === null || n === void 0 ? void 0 : n.isTemp) ? n.original : n;
|
|
23
23
|
}
|
|
24
|
-
const ALLOWED_KEYS = ['name', 'source', 'masks', 'default', 'accept', 'rename', 'mime', 'url', 'target', ...misc_1.PERM_KEYS];
|
|
24
|
+
const ALLOWED_KEYS = ['name', 'source', 'masks', 'default', 'accept', 'rename', 'mime', 'url', 'target', 'comment', 'icon', ...misc_1.PERM_KEYS];
|
|
25
25
|
const apis = {
|
|
26
26
|
async get_vfs() {
|
|
27
27
|
return { root: await recur() };
|
|
28
28
|
async function recur(node = vfs_1.vfs) {
|
|
29
29
|
var _a, _b, _c;
|
|
30
30
|
const { source } = node;
|
|
31
|
-
const stats = !source ? undefined : await (0, promises_1.stat)(source).catch(() => undefined);
|
|
31
|
+
const stats = !source ? undefined : (node.stats || await (0, promises_1.stat)(source).catch(() => undefined));
|
|
32
32
|
const isDir = !(0, vfs_1.nodeIsLink)(node) && (!source || ((_a = stats === null || stats === void 0 ? void 0 : stats.isDirectory()) !== null && _a !== void 0 ? _a : ((_b = node.children) === null || _b === void 0 ? void 0 : _b.length) > 0));
|
|
33
33
|
const copyStats = stats ? lodash_1.default.pick(stats, ['size', 'ctime', 'mtime'])
|
|
34
34
|
: { size: source ? -1 : undefined };
|
|
@@ -188,14 +188,16 @@ const apis = {
|
|
|
188
188
|
try {
|
|
189
189
|
const matching = (0, misc_1.makeMatcher)(fileMask);
|
|
190
190
|
path = (0, misc_1.isWindowsDrive)(path) ? path + '\\' : (0, path_1.resolve)(path || '/');
|
|
191
|
-
for await (const
|
|
191
|
+
for await (const entry of (0, misc_1.dirStream)(path)) {
|
|
192
192
|
if (ctx.req.aborted)
|
|
193
193
|
return;
|
|
194
|
+
const { path: name } = entry;
|
|
195
|
+
const isDir = entry.isDirectory();
|
|
194
196
|
if (!isDir)
|
|
195
197
|
if (!files || fileMask && !matching(name))
|
|
196
198
|
continue;
|
|
197
199
|
try {
|
|
198
|
-
const stats = await (0, promises_1.stat)((0, path_1.join)(path, name));
|
|
200
|
+
const stats = entry.stats || await (0, promises_1.stat)((0, path_1.join)(path, name));
|
|
199
201
|
list.add({
|
|
200
202
|
n: name,
|
|
201
203
|
s: stats.size,
|
package/src/apiMiddleware.js
CHANGED
|
@@ -10,6 +10,7 @@ const stream_1 = require("stream");
|
|
|
10
10
|
const misc_1 = require("./misc");
|
|
11
11
|
const const_1 = require("./const");
|
|
12
12
|
const config_1 = require("./config");
|
|
13
|
+
const plugins_1 = require("./plugins");
|
|
13
14
|
class ApiError extends Error {
|
|
14
15
|
constructor(status, message) {
|
|
15
16
|
super(typeof message === 'string' ? message : message && message instanceof Error ? message.message : JSON.stringify(message));
|
|
@@ -32,7 +33,9 @@ function apiMiddleware(apis) {
|
|
|
32
33
|
|| apiName.startsWith('get_'); // "get_" apis are safe because they make no change
|
|
33
34
|
if (!safe)
|
|
34
35
|
return send(const_1.HTTP_FOOL, "missing header x-hfs-anti-csrf=1");
|
|
35
|
-
const
|
|
36
|
+
const customApiRest = apiName.startsWith(const_1.PLUGIN_CUSTOM_REST_PREFIX) && apiName.slice(const_1.PLUGIN_CUSTOM_REST_PREFIX.length);
|
|
37
|
+
const apiFun = customApiRest && (0, plugins_1.firstPlugin)(pl => { var _a; return (_a = pl.getData().customRest) === null || _a === void 0 ? void 0 : _a[customApiRest]; })
|
|
38
|
+
|| apis.hasOwnProperty(apiName) && apis[apiName];
|
|
36
39
|
if (!apiFun)
|
|
37
40
|
return send(const_1.HTTP_NOT_FOUND, 'invalid api');
|
|
38
41
|
// we don't rely on SameSite cookie option because it's https-only
|
package/src/auth.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.invalidateSessionBefore = exports.setLoggedIn = exports.getCurrentUsername = exports.srpCheck = exports.
|
|
6
|
+
exports.invalidateSessionBefore = exports.setLoggedIn = exports.getCurrentUsername = exports.srpCheck = exports.srpServerStep1 = void 0;
|
|
7
7
|
const perm_1 = require("./perm");
|
|
8
8
|
const cross_const_1 = require("./cross-const");
|
|
9
9
|
const tssrp6a_1 = require("tssrp6a");
|
|
@@ -12,17 +12,17 @@ const cross_1 = require("./cross");
|
|
|
12
12
|
const node_crypto_1 = require("node:crypto");
|
|
13
13
|
const events_1 = __importDefault(require("./events"));
|
|
14
14
|
const srp6aNimbusRoutines = new tssrp6a_1.SRPRoutines(new tssrp6a_1.SRPParameters());
|
|
15
|
-
async function
|
|
15
|
+
async function srpServerStep1(account) {
|
|
16
16
|
if (!account.srp)
|
|
17
17
|
throw cross_const_1.HTTP_NOT_ACCEPTABLE;
|
|
18
18
|
const [salt, verifier] = account.srp.split('|');
|
|
19
19
|
if (!salt || !verifier)
|
|
20
20
|
throw Error("malformed account");
|
|
21
21
|
const srpSession = new tssrp6a_1.SRPServerSession(srp6aNimbusRoutines);
|
|
22
|
-
const
|
|
23
|
-
return {
|
|
22
|
+
const srpServer = await srpSession.step1(account.username, BigInt(salt), BigInt(verifier));
|
|
23
|
+
return { srpServer, salt, pubKey: String(srpServer.B) }; // cast to string cause bigint can't be jsonized
|
|
24
24
|
}
|
|
25
|
-
exports.
|
|
25
|
+
exports.srpServerStep1 = srpServerStep1;
|
|
26
26
|
const cache = {};
|
|
27
27
|
async function srpCheck(username, password) {
|
|
28
28
|
const account = (0, perm_1.getAccount)(username);
|
|
@@ -30,10 +30,10 @@ async function srpCheck(username, password) {
|
|
|
30
30
|
return;
|
|
31
31
|
const k = (0, node_crypto_1.createHash)('sha256').update(username + password + account.srp).digest("hex");
|
|
32
32
|
const good = await (0, cross_1.getOrSet)(cache, k, async () => {
|
|
33
|
-
const {
|
|
33
|
+
const { srpServer, salt, pubKey } = await srpServerStep1(account);
|
|
34
34
|
const client = await (0, srp_1.srpClientPart)(username, password, salt, pubKey);
|
|
35
35
|
setTimeout(() => delete cache[k], 60000);
|
|
36
|
-
return
|
|
36
|
+
return srpServer.step2(client.A, client.M1).then(() => 1, () => 0);
|
|
37
37
|
});
|
|
38
38
|
return good ? account : undefined;
|
|
39
39
|
}
|
package/src/basicWeb.js
CHANGED
|
@@ -46,7 +46,7 @@ function basicWeb(ctx, node) {
|
|
|
46
46
|
stream.push(`<title>${adminApis_1.title.get()}</title><body>`);
|
|
47
47
|
stream.push((0, customHtml_1.getSection)('basicHeader'));
|
|
48
48
|
const u = (0, auth_1.getCurrentUsername)(ctx);
|
|
49
|
-
const links = u ? { [`//LOGOUT%00:@${ctx.
|
|
49
|
+
const links = u ? { [`//LOGOUT%00:@${ctx.host}/?get=logout`]: `Logout (${u})` } : { '/?get=login': "Login" };
|
|
50
50
|
stream.push(lodash_1.default.map(links, (v, k) => a(k, v)).join(' ') + '\n<ul>\n');
|
|
51
51
|
if (ctx.state.originalPath.length > 1)
|
|
52
52
|
stream.push('<li>' + a('..' + force, '..') + '\n');
|
package/src/block.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
3
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
-
exports.applyBlock = exports.block = void 0;
|
|
7
|
+
exports.addBlock = exports.applyBlock = exports.block = void 0;
|
|
5
8
|
const config_1 = require("./config");
|
|
6
9
|
const connections_1 = require("./connections");
|
|
7
10
|
const misc_1 = require("./misc");
|
|
11
|
+
const lodash_1 = __importDefault(require("lodash"));
|
|
8
12
|
exports.block = (0, config_1.defineConfig)('block', [], rules => {
|
|
9
13
|
const now = new Date();
|
|
10
14
|
const ret = !Array.isArray(rules) ? []
|
|
@@ -31,3 +35,11 @@ setInterval(() => {
|
|
|
31
35
|
console.log("blocking rules:", n, "expired");
|
|
32
36
|
exports.block.set(next);
|
|
33
37
|
}, misc_1.MINUTE / 2);
|
|
38
|
+
function addBlock(rule, merge) {
|
|
39
|
+
exports.block.set(was => {
|
|
40
|
+
const foundIdx = merge ? lodash_1.default.findIndex(was, merge) : -1;
|
|
41
|
+
return foundIdx < 0 ? [...was, { ...merge, ...rule }]
|
|
42
|
+
: was.map((x, i) => i === foundIdx ? { ...x, ...rule, ip: `${x.ip}|${rule.ip}` } : x);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
exports.addBlock = addBlock;
|
package/src/commands.js
CHANGED
|
@@ -33,8 +33,11 @@ if (!const_1.argv.updating)
|
|
|
33
33
|
function parseCommandLine(line) {
|
|
34
34
|
if (!line)
|
|
35
35
|
return;
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
let [name, ...params] = line.trim().split(/ +/);
|
|
37
|
+
name = aliases[name] || name;
|
|
38
|
+
let cmd = commands[name];
|
|
39
|
+
if (cmd.alias)
|
|
40
|
+
cmd = commands[cmd.alias];
|
|
38
41
|
if (!cmd)
|
|
39
42
|
return console.error("cannot understand entered command, try 'help'");
|
|
40
43
|
if (cmd.cb.length > params.length)
|
|
@@ -45,6 +48,9 @@ function parseCommandLine(line) {
|
|
|
45
48
|
console.error("command failed:", err.message || err);
|
|
46
49
|
});
|
|
47
50
|
}
|
|
51
|
+
const aliases = {
|
|
52
|
+
exit: 'quit',
|
|
53
|
+
};
|
|
48
54
|
const commands = {
|
|
49
55
|
help: {
|
|
50
56
|
params: '',
|
package/src/const.js
CHANGED
|
@@ -37,7 +37,7 @@ const os_1 = require("os");
|
|
|
37
37
|
const fs_1 = require("fs");
|
|
38
38
|
const path_1 = require("path");
|
|
39
39
|
__exportStar(require("./cross-const"), exports);
|
|
40
|
-
exports.API_VERSION =
|
|
40
|
+
exports.API_VERSION = 9.2;
|
|
41
41
|
exports.COMPATIBLE_API_VERSION = 1; // while changes in the api are not breaking, this number stays the same, otherwise it is made equal to API_VERSION
|
|
42
42
|
exports.HFS_REPO = 'rejetto/hfs';
|
|
43
43
|
exports.argv = (0, minimist_1.default)(process.argv.slice(2));
|
|
@@ -45,15 +45,15 @@ exports.DEV = process.env.DEV || exports.argv.dev ? 'DEV' : '';
|
|
|
45
45
|
exports.ORIGINAL_CWD = process.cwd();
|
|
46
46
|
exports.HFS_STARTED = new Date();
|
|
47
47
|
const PKG_PATH = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
48
|
-
exports.BUILD_TIMESTAMP = "2024-
|
|
48
|
+
exports.BUILD_TIMESTAMP = "2024-09-28T14:50:14.403Z";
|
|
49
49
|
const pkg = JSON.parse(fs.readFileSync(PKG_PATH, 'utf8'));
|
|
50
50
|
exports.VERSION = pkg.version;
|
|
51
51
|
exports.RUNNING_BETA = exports.VERSION.includes('-');
|
|
52
52
|
exports.HFS_REPO_BRANCH = exports.RUNNING_BETA ? exports.VERSION.split('.')[1] : 'main';
|
|
53
53
|
exports.IS_WINDOWS = process.platform === 'win32';
|
|
54
54
|
exports.IS_MAC = process.platform === 'darwin';
|
|
55
|
-
exports.IS_BINARY =
|
|
56
|
-
exports.APP_PATH = (0, path_1.dirname)(exports.IS_BINARY ? process.execPath : __dirname);
|
|
55
|
+
exports.IS_BINARY = !/node|bun/.test((0, path_1.basename)(process.execPath)); // this won't be node if pkg was used
|
|
56
|
+
exports.APP_PATH = (0, path_1.dirname)(exports.IS_BINARY ? process.execPath : __dirname); // __dirname's parent can be compared with cwd
|
|
57
57
|
exports.MIME_AUTO = 'auto';
|
|
58
58
|
// we want this to be the first stuff to be printed, then we print it in this module, that is executed at the beginning
|
|
59
59
|
if (exports.DEV)
|
|
@@ -66,10 +66,10 @@ console.log('started', exports.HFS_STARTED.toLocaleString(), exports.DEV);
|
|
|
66
66
|
console.log('version', exports.VERSION || '-');
|
|
67
67
|
console.log('build', exports.BUILD_TIMESTAMP || '-');
|
|
68
68
|
const winExe = exports.IS_WINDOWS && process.execPath.match(/(?<!node)\.exe$/i);
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
// still considering whether to use ".hfs" with Windows users, who may be less accustomed to it
|
|
70
|
+
const useHomeDir = !winExe || process.execPath.includes(process.env.ProgramFiles || '|'); // you can't write in program-files
|
|
71
|
+
const dir = exports.argv.cwd || useHomeDir && (0, path_1.join)((0, os_1.homedir)(), '.hfs');
|
|
72
|
+
if (dir) {
|
|
73
73
|
try {
|
|
74
74
|
(0, fs_1.mkdirSync)(dir);
|
|
75
75
|
}
|
|
@@ -79,9 +79,12 @@ else if (!winExe) { // still considering whether to use this behavior with Windo
|
|
|
79
79
|
}
|
|
80
80
|
process.chdir(dir);
|
|
81
81
|
}
|
|
82
|
-
console.log('
|
|
82
|
+
console.log('working directory', process.cwd());
|
|
83
83
|
if (exports.APP_PATH !== process.cwd())
|
|
84
84
|
console.log('app', exports.APP_PATH);
|
|
85
85
|
console.log('node', process.version);
|
|
86
|
-
|
|
86
|
+
const bun = globalThis.Bun;
|
|
87
|
+
if (bun)
|
|
88
|
+
console.log('bun', bun.version);
|
|
89
|
+
console.log('platform', process.platform, exports.IS_BINARY ? 'binary' : (0, path_1.basename)(process.execPath));
|
|
87
90
|
console.log('pid', process.pid);
|
package/src/cross-const.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HTTP_MESSAGES = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_TOO_MANY_REQUESTS = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.NBSP = exports.PORT_DISABLED = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = void 0;
|
|
3
|
+
exports.HTTP_MESSAGES = exports.HTTP_SERVICE_UNAVAILABLE = exports.HTTP_SERVER_ERROR = exports.HTTP_TOO_MANY_REQUESTS = exports.HTTP_FAILED_DEPENDENCY = exports.HTTP_FOOL = exports.HTTP_RANGE_NOT_SATISFIABLE = exports.HTTP_PAYLOAD_TOO_LARGE = exports.HTTP_PRECONDITION_FAILED = exports.HTTP_CONFLICT = exports.HTTP_NOT_ACCEPTABLE = exports.HTTP_METHOD_NOT_ALLOWED = exports.HTTP_NOT_FOUND = exports.HTTP_FORBIDDEN = exports.HTTP_UNAUTHORIZED = exports.HTTP_BAD_REQUEST = exports.HTTP_NOT_MODIFIED = exports.HTTP_TEMPORARY_REDIRECT = exports.HTTP_MOVED_PERMANENTLY = exports.HTTP_PARTIAL_CONTENT = exports.HTTP_NO_CONTENT = exports.HTTP_OK = exports.PLUGIN_CUSTOM_REST_PREFIX = exports.NBSP = exports.PORT_DISABLED = exports.PLUGINS_PUB_URI = exports.API_URI = exports.ADMIN_URI = exports.FRONTEND_URI = exports.SPECIAL_URI = void 0;
|
|
4
4
|
exports.SPECIAL_URI = '/~/';
|
|
5
5
|
exports.FRONTEND_URI = exports.SPECIAL_URI + 'frontend/';
|
|
6
6
|
exports.ADMIN_URI = exports.SPECIAL_URI + 'admin/';
|
|
@@ -8,6 +8,7 @@ exports.API_URI = exports.SPECIAL_URI + 'api/';
|
|
|
8
8
|
exports.PLUGINS_PUB_URI = exports.SPECIAL_URI + 'plugins/';
|
|
9
9
|
exports.PORT_DISABLED = -1;
|
|
10
10
|
exports.NBSP = '\xA0';
|
|
11
|
+
exports.PLUGIN_CUSTOM_REST_PREFIX = '_';
|
|
11
12
|
exports.HTTP_OK = 200;
|
|
12
13
|
exports.HTTP_NO_CONTENT = 204;
|
|
13
14
|
exports.HTTP_PARTIAL_CONTENT = 206;
|