hfs 0.48.0-alpha2 → 0.48.0-alpha4
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-67290e78.js +536 -0
- package/admin/assets/{sha512-136b5a7c.js → sha512-92cfe0b0.js} +1 -1
- package/admin/index.html +1 -1
- package/frontend/assets/index-110410d0.js +94 -0
- package/frontend/assets/{sha512-549e6c3a.js → sha512-8d938ca9.js} +1 -1
- package/frontend/index.html +1 -1
- package/package.json +4 -3
- package/src/QuickZipStream.js +1 -0
- package/src/api.file_list.js +38 -37
- package/src/api.net.js +141 -46
- package/src/api.plugins.js +25 -20
- package/src/config.js +2 -3
- package/src/const.js +3 -3
- package/src/cross.js +30 -1
- package/src/debounceAsync.js +13 -4
- package/src/frontEndApis.js +5 -11
- package/src/github.js +30 -29
- package/src/index.js +2 -0
- package/src/langs/embedded.js +2 -1
- package/src/langs/hfs-lang-fi.json +131 -0
- package/src/langs/hfs-lang-it.json +2 -0
- package/src/langs/hfs-lang-ru.json +11 -5
- package/src/listen.js +29 -15
- package/src/log.js +4 -5
- package/src/middlewares.js +2 -2
- package/src/misc.js +29 -2
- package/src/util-files.js +3 -7
- package/src/watchLoad.js +5 -5
- package/admin/assets/index-344723f8.js +0 -520
- package/frontend/assets/index-12411ff6.js +0 -94
package/src/frontEndApis.js
CHANGED
|
@@ -38,11 +38,12 @@ const vfs_1 = require("./vfs");
|
|
|
38
38
|
const promises_1 = require("fs/promises");
|
|
39
39
|
const path_1 = require("path");
|
|
40
40
|
const upload_1 = require("./upload");
|
|
41
|
+
const misc_1 = require("./misc");
|
|
41
42
|
exports.frontEndApis = {
|
|
42
43
|
get_file_list: api_file_list_1.get_file_list,
|
|
43
44
|
...api_auth,
|
|
44
45
|
get_notifications({ channel }, ctx) {
|
|
45
|
-
apiAssertTypes({ string: { channel } });
|
|
46
|
+
(0, misc_1.apiAssertTypes)({ string: { channel } });
|
|
46
47
|
const list = new apiMiddleware_1.SendListReadable();
|
|
47
48
|
list.ready(); // on chrome109 EventSource doesn't emit 'open' until something is sent
|
|
48
49
|
return list.events(ctx, {
|
|
@@ -67,7 +68,7 @@ exports.frontEndApis = {
|
|
|
67
68
|
};
|
|
68
69
|
},
|
|
69
70
|
async create_folder({ uri, name }, ctx) {
|
|
70
|
-
apiAssertTypes({ string: { uri, name } });
|
|
71
|
+
(0, misc_1.apiAssertTypes)({ string: { uri, name } });
|
|
71
72
|
if (!(0, util_files_1.isValidFileName)(name) || (0, util_files_1.dirTraversal)(name))
|
|
72
73
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad name');
|
|
73
74
|
const parentNode = await (0, vfs_1.urlToNode)(uri, ctx);
|
|
@@ -84,7 +85,7 @@ exports.frontEndApis = {
|
|
|
84
85
|
}
|
|
85
86
|
},
|
|
86
87
|
async delete({ uri }, ctx) {
|
|
87
|
-
apiAssertTypes({ string: { uri } });
|
|
88
|
+
(0, misc_1.apiAssertTypes)({ string: { uri } });
|
|
88
89
|
const node = await (0, vfs_1.urlToNode)(uri, ctx);
|
|
89
90
|
if (!node)
|
|
90
91
|
throw new apiMiddleware_1.ApiError(const_1.HTTP_NOT_FOUND);
|
|
@@ -101,7 +102,7 @@ exports.frontEndApis = {
|
|
|
101
102
|
}
|
|
102
103
|
},
|
|
103
104
|
async rename({ uri, dest }, ctx) {
|
|
104
|
-
apiAssertTypes({ string: { uri, dest } });
|
|
105
|
+
(0, misc_1.apiAssertTypes)({ string: { uri, dest } });
|
|
105
106
|
if (dest.includes('/') || (0, util_files_1.dirTraversal)(dest))
|
|
106
107
|
throw new apiMiddleware_1.ApiError(const_1.HTTP_FORBIDDEN);
|
|
107
108
|
const node = await (0, vfs_1.urlToNode)(uri, ctx);
|
|
@@ -130,10 +131,3 @@ function notifyClient(ctx, name, data) {
|
|
|
130
131
|
}
|
|
131
132
|
exports.notifyClient = notifyClient;
|
|
132
133
|
const NOTIFICATION_PREFIX = 'notificationChannel:';
|
|
133
|
-
function apiAssertTypes(paramsByType) {
|
|
134
|
-
for (const [types, params] of Object.entries(paramsByType))
|
|
135
|
-
for (const type of types.split('_'))
|
|
136
|
-
for (const [name, val] of Object.entries(params))
|
|
137
|
-
if (typeof val !== type)
|
|
138
|
-
throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad ' + name);
|
|
139
|
-
}
|
package/src/github.js
CHANGED
|
@@ -23,8 +23,17 @@ function downloadProgress(id, status) {
|
|
|
23
23
|
downloading[id] = status;
|
|
24
24
|
events_1.default.emit('pluginDownload_' + id, status);
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
// determine default branch, possibly without consuming api quota
|
|
27
|
+
async function getGithubDefaultBranch(repo) {
|
|
28
|
+
var _a;
|
|
29
|
+
const res = await (0, misc_1.httpString)(`https://github.com/${repo}/archive/refs/heads/main.zip`, { method: 'HEAD' });
|
|
30
|
+
return res.ok ? 'main'
|
|
31
|
+
: (_a = (await getRepoInfo(repo))) === null || _a === void 0 ? void 0 : _a.default_branch;
|
|
32
|
+
}
|
|
33
|
+
async function downloadPlugin(repo, { branch = '', overwrite = false } = {}) {
|
|
27
34
|
var _a, _b, _c;
|
|
35
|
+
if (typeof repo !== 'string')
|
|
36
|
+
repo = repo.main;
|
|
28
37
|
if (downloading[repo])
|
|
29
38
|
return new apiMiddleware_1.ApiError(const_1.HTTP_CONFLICT, "already downloading");
|
|
30
39
|
console.log('downloading plugin', repo);
|
|
@@ -42,9 +51,7 @@ async function downloadPlugin(repo, branch = '', overwrite) {
|
|
|
42
51
|
url = customRepo.web + url;
|
|
43
52
|
return await go(url, pl === null || pl === void 0 ? void 0 : pl.id, (_c = customRepo.zipRoot) !== null && _c !== void 0 ? _c : DIST_ROOT);
|
|
44
53
|
}
|
|
45
|
-
|
|
46
|
-
if (!branch)
|
|
47
|
-
branch = rec.default_branch;
|
|
54
|
+
branch || (branch = await getGithubDefaultBranch(repo));
|
|
48
55
|
const short = repo.split('/')[1]; // second part, repo without the owner
|
|
49
56
|
if (!short)
|
|
50
57
|
return new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, "bad repo");
|
|
@@ -75,7 +82,7 @@ async function downloadPlugin(repo, branch = '', overwrite) {
|
|
|
75
82
|
});
|
|
76
83
|
const main = (0, path_1.join)(installPath, MAIN);
|
|
77
84
|
await (0, promises_1.rename)(main + plugins_1.DISABLING_POSTFIX, main) // we are good now, restore name
|
|
78
|
-
.catch(e => { throw e.code !== 'ENOENT' ? e : new apiMiddleware_1.ApiError(const_1.
|
|
85
|
+
.catch(e => { throw e.code !== 'ENOENT' ? e : new apiMiddleware_1.ApiError(const_1.HTTP_NOT_ACCEPTABLE, "missing main file"); });
|
|
79
86
|
return folder;
|
|
80
87
|
}
|
|
81
88
|
}
|
|
@@ -109,19 +116,13 @@ async function readOnlinePlugin(repo, branch = '') {
|
|
|
109
116
|
throw Error("bad repo.main");
|
|
110
117
|
return (0, plugins_1.parsePluginSource)(main, res.body); // use 'repo' as 'id' client-side
|
|
111
118
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (!res)
|
|
120
|
-
continue;
|
|
121
|
-
const pl = (0, plugins_1.parsePluginSource)(repo, res); // use 'repo' as 'id' client-side
|
|
122
|
-
pl.branch = b || undefined;
|
|
123
|
-
return pl;
|
|
124
|
-
}
|
|
119
|
+
branch || (branch = await getGithubDefaultBranch(repo));
|
|
120
|
+
const res = await readGithubFile(`${repo}/${branch}/${DIST_ROOT}/plugin.js`);
|
|
121
|
+
if (!res)
|
|
122
|
+
throw Error("missing plugin.js");
|
|
123
|
+
const pl = (0, plugins_1.parsePluginSource)(repo, res); // use 'repo' as 'id' client-side
|
|
124
|
+
pl.branch = branch;
|
|
125
|
+
return pl;
|
|
125
126
|
}
|
|
126
127
|
exports.readOnlinePlugin = readOnlinePlugin;
|
|
127
128
|
function getFolder2repo() {
|
|
@@ -148,19 +149,19 @@ async function apiGithub(uri) {
|
|
|
148
149
|
: e;
|
|
149
150
|
}
|
|
150
151
|
}
|
|
151
|
-
async function
|
|
152
|
-
var _a, _b;
|
|
152
|
+
async function searchPlugins(text = '') {
|
|
153
153
|
const projectInfo = await getProjectInfo();
|
|
154
|
-
const
|
|
155
|
-
|
|
154
|
+
const list = await apiGithub('search/repositories?q=topic:hfs-plugin+' + encodeURI(text));
|
|
155
|
+
return new misc_1.AsapStream(list.items.map(async (it) => {
|
|
156
|
+
var _a, _b;
|
|
156
157
|
const repo = it.full_name;
|
|
157
158
|
if ((_a = projectInfo === null || projectInfo === void 0 ? void 0 : projectInfo.plugins_blacklist) === null || _a === void 0 ? void 0 : _a.includes(repo))
|
|
158
|
-
|
|
159
|
+
return;
|
|
159
160
|
let pl = await readOnlinePlugin(repo, it.default_branch);
|
|
160
161
|
if (!(pl === null || pl === void 0 ? void 0 : pl.apiRequired))
|
|
161
|
-
|
|
162
|
+
return; // mandatory field
|
|
162
163
|
if (pl.badApi) { // we try other branches (starting with 'api')
|
|
163
|
-
const res = await apiGithub('repos/' +
|
|
164
|
+
const res = await apiGithub('repos/' + repo + '/branches');
|
|
164
165
|
const branches = res.map((x) => x === null || x === void 0 ? void 0 : x.name)
|
|
165
166
|
.filter((x) => typeof x === 'string' && x.startsWith('api'))
|
|
166
167
|
.sort().reverse();
|
|
@@ -175,13 +176,13 @@ async function* searchPlugins(text = '') {
|
|
|
175
176
|
}
|
|
176
177
|
}
|
|
177
178
|
if (!pl || pl.badApi)
|
|
178
|
-
|
|
179
|
+
return;
|
|
179
180
|
Object.assign(pl, {
|
|
180
181
|
downloading: downloading[repo],
|
|
181
182
|
license: (_b = it.license) === null || _b === void 0 ? void 0 : _b.spdx_id,
|
|
182
183
|
}, lodash_1.default.pick(it, ['pushed_at', 'stargazers_count', 'default_branch']));
|
|
183
|
-
|
|
184
|
-
}
|
|
184
|
+
return pl;
|
|
185
|
+
}));
|
|
185
186
|
}
|
|
186
187
|
exports.searchPlugins = searchPlugins;
|
|
187
188
|
// centralized hosted information, to be used as little as possible
|
|
@@ -194,7 +195,7 @@ function getProjectInfo() {
|
|
|
194
195
|
.then(x => {
|
|
195
196
|
if (!x)
|
|
196
197
|
throw x; // go catch
|
|
197
|
-
setTimeout(() => cache = undefined,
|
|
198
|
+
setTimeout(() => cache = undefined, misc_1.DAY); // invalidate cache
|
|
198
199
|
return latest = JSON.parse(x);
|
|
199
200
|
}).catch(() => {
|
|
200
201
|
setTimeout(() => cache = undefined, 10000); // invalidate cache sooner on errors
|
package/src/index.js
CHANGED
|
@@ -25,11 +25,13 @@ const assert_1 = require("assert");
|
|
|
25
25
|
const lodash_1 = __importDefault(require("lodash"));
|
|
26
26
|
const misc_1 = require("./misc");
|
|
27
27
|
const koa_session_1 = __importDefault(require("koa-session"));
|
|
28
|
+
const api_net_1 = require("./api.net");
|
|
28
29
|
(0, assert_1.ok)(lodash_1.default.intersection(Object.keys(frontEndApis_1.frontEndApis), Object.keys(adminApis_1.adminApis)).length === 0); // they share same endpoints, don't clash
|
|
29
30
|
process.title = 'HFS ' + const_1.VERSION;
|
|
30
31
|
const keys = ((_a = process.env.COOKIE_SIGN_KEYS) === null || _a === void 0 ? void 0 : _a.split(',')) || [(0, misc_1.randomId)(30)];
|
|
31
32
|
exports.app = new koa_1.default({ keys });
|
|
32
33
|
exports.app.use(middlewares_1.someSecurity)
|
|
34
|
+
.use(api_net_1.acmeMiddleware)
|
|
33
35
|
.use((0, koa_session_1.default)({ key: 'hfs_$id', signed: true, rolling: true, sameSite: 'lax' }, exports.app))
|
|
34
36
|
.use(middlewares_1.prepareState)
|
|
35
37
|
.use(middlewares_1.gzipper)
|
package/src/langs/embedded.js
CHANGED
|
@@ -17,4 +17,5 @@ const hfs_lang_es_json_1 = __importDefault(require("./hfs-lang-es.json"));
|
|
|
17
17
|
const hfs_lang_nl_json_1 = __importDefault(require("./hfs-lang-nl.json"));
|
|
18
18
|
const hfs_lang_el_json_1 = __importDefault(require("./hfs-lang-el.json"));
|
|
19
19
|
const hfs_lang_de_json_1 = __importDefault(require("./hfs-lang-de.json"));
|
|
20
|
-
|
|
20
|
+
const hfs_lang_fi_json_1 = __importDefault(require("./hfs-lang-fi.json"));
|
|
21
|
+
exports.default = { it: hfs_lang_it_json_1.default, zh: hfs_lang_zh_json_1.default, ru: hfs_lang_ru_json_1.default, sr: hfs_lang_sr_json_1.default, ko: hfs_lang_ko_json_1.default, ms: hfs_lang_ms_json_1.default, 'zh-tw': hfs_lang_zh_tw_json_1.default, fr: hfs_lang_fr_json_1.default, pt: hfs_lang_pt_json_1.default, vi: hfs_lang_vi_json_1.default, es: hfs_lang_es_json_1.default, nl: hfs_lang_nl_json_1.default, el: hfs_lang_el_json_1.default, de: hfs_lang_de_json_1.default, fi: hfs_lang_fi_json_1.default };
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
{
|
|
2
|
+
"author": "Pultsari",
|
|
3
|
+
"version": 1.0,
|
|
4
|
+
"hfs_version": "0.47.0",
|
|
5
|
+
"translate": {
|
|
6
|
+
"Select": "Valitse",
|
|
7
|
+
"n_files": "{n,plural,one{# tiedosto} other{# tiedostoa}}",
|
|
8
|
+
"n_folders": "{n,plural,one{# kansio} other{# kansiota}}",
|
|
9
|
+
"filter_count": "{n,plural, one{# suodatettu} other{# suodatettu}}",
|
|
10
|
+
"select_count": "{n,plural, one{# valittu} other{# valittu}}",
|
|
11
|
+
"filter_placeholder": "Kirjoita tähän suodattaaksesi listasta",
|
|
12
|
+
"Select some files": "Valitse joitakin tiedostoja tai kansioita",
|
|
13
|
+
"zip_checkboxes": "Käytä valintaruutuja valitaksesi ladattavat tiedostot ja kansiot, sitten voit ladata pakatun zip tiedoston",
|
|
14
|
+
"zip_tooltip_selected": "Lataa valitut tiedostot ja kansiot pakatussa zip tiedostossa",
|
|
15
|
+
"zip_tooltip_whole": "Lataa ja pakkaa kaikki (suodattamattomana) yhtenä zip tiedostona. Jos valitset vain joitakin tiedostoja tai kansioita, pakataan ja ladataan vain ne.",
|
|
16
|
+
"zip_confirm_search": "Haluatko ladata kaikki haun tulokset pakattuna zip tiedostona?",
|
|
17
|
+
"zip_confirm_folder": "Haluatko ladata aivan kaiken pakattuna zip tiedostona?",
|
|
18
|
+
"select_tooltip": "Valinnat koskevat pakattua zip tiedostoa ja tiedostojen poistamista (kun sallittu), sen lisäksi voit suodattaa listaa.",
|
|
19
|
+
"delete_hint": "Poistaaksesi tiedostoja tai kansioita, klikkaa ensin Valitse yläpalkista",
|
|
20
|
+
"delete_confirm": "Poista {n,plural, one{# valittu tiedosto tai kansio} other{# valittua tiedostoa tai / ja kansiota}}?",
|
|
21
|
+
"delete_completed": "{n} tiedostoa tai / ja kansiota poistettu",
|
|
22
|
+
"delete_failed": ", {n,plural, one{# epäonnistui} other{# epäonnistui}}",
|
|
23
|
+
"delete_select": "Valitse tiedostoja tai / ja kansioita poistettavaksi",
|
|
24
|
+
"Delete": "Poista",
|
|
25
|
+
"Options": "Asetukset",
|
|
26
|
+
"Search": "Haku",
|
|
27
|
+
"Zip": "Zip",
|
|
28
|
+
"search_msg": "Hae tästä kansiosta ja alikansioista",
|
|
29
|
+
"Searching": "Haetaan",
|
|
30
|
+
"Searched": "Haettu",
|
|
31
|
+
"Clear search": "Tyhjennä haku",
|
|
32
|
+
"Interrupted": "Keskeytetty",
|
|
33
|
+
"stopped_before": "pysäytetty ennen",
|
|
34
|
+
"empty_list": "Tyhjä",
|
|
35
|
+
"filter_none": "Ei tuloksia",
|
|
36
|
+
"Admin-panel": "Hallintapaneeli",
|
|
37
|
+
"Login": "Kirjaudu",
|
|
38
|
+
"Username": "Käyttäjätunnus",
|
|
39
|
+
"Password": "Salasana",
|
|
40
|
+
"login_untrusted": "Kirjautuminen keskeytetty:: palvelimen identiteettiin ei voida luottaa",
|
|
41
|
+
"login_bad_credentials": "Käyttäjätunnus tai salasana virheellinen.",
|
|
42
|
+
"login_bad_cookies": "Evästeet eivät toimi - kirjautuminen epäonnistui",
|
|
43
|
+
"User panel": "Hallintapaneeli",
|
|
44
|
+
"Change password": "Vaihda salasana",
|
|
45
|
+
"enter_pass": "Kirjoita uusi salasana",
|
|
46
|
+
"enter_pass2": "Kirjoita uusi salasana uudelleen",
|
|
47
|
+
"pass2_mismatch": "Toinen kirjoittamasi salasana ei vastannut ensimmäistä. Salasanan vaihtaminen keskeytetty.",
|
|
48
|
+
"password_changed": "Salasana vaihdettu",
|
|
49
|
+
"Logout": "Kirjaudu ulos",
|
|
50
|
+
"connection error": "Yhteysvirhe",
|
|
51
|
+
"Full timestamp:": "Täysi aikaleima:",
|
|
52
|
+
"Search was interrupted": "Haku keskeytetty",
|
|
53
|
+
"Stop list": "Pysäytä listaus",
|
|
54
|
+
"upload_starting": "Aloitetaan lataaminen",
|
|
55
|
+
"wrong_account": "Tilillä {u} ei ole pääsyä, kokeile toista",
|
|
56
|
+
"no_upload_here": "Ei lähetysoikeutta tähän kansioon",
|
|
57
|
+
"Create folder": "Tee uusi kansio",
|
|
58
|
+
"Pick files": "Valitse lähetettävät tiedostot",
|
|
59
|
+
"Pick folder": "Valitse lähetettävä kansio",
|
|
60
|
+
"send_files": "Lähetä {n,plural,one{# tiedosto} other{# tiedostot}}, {size}",
|
|
61
|
+
"Clear": "Tyhjennä",
|
|
62
|
+
"failed_upload": "Lähetys epäonnistui {name}",
|
|
63
|
+
"confirm_resume": "Jatketaanko lähetystä?",
|
|
64
|
+
"file too large": "Tiedoston koko on liian suuri",
|
|
65
|
+
"Enter folder name": "Kirjoita kansion nimi",
|
|
66
|
+
"Successfully created": "Kansio luotu onnistuneesti",
|
|
67
|
+
"enter_folder": "Siirry kansioon",
|
|
68
|
+
"folder_exists": "Kansio samalla nimellä on jo olemassa",
|
|
69
|
+
"Sort by:": "Lajittele: {by}",
|
|
70
|
+
"name": "nimen mukaan",
|
|
71
|
+
"extension": "tiedostopäätteen mukaan",
|
|
72
|
+
"size": "koon mukaan",
|
|
73
|
+
"time": "ajan mukaan",
|
|
74
|
+
"Invert order": "Käänteinen järjestys",
|
|
75
|
+
"Folders first": "Kansiot ensin",
|
|
76
|
+
"Numeric names": "Numeroidut nimet",
|
|
77
|
+
"theme:": "Teema:",
|
|
78
|
+
"auto": "auto",
|
|
79
|
+
"light": "vaalea",
|
|
80
|
+
"dark": "tumma",
|
|
81
|
+
"parent folder": "Ylempi kansio",
|
|
82
|
+
"home": "Koti",
|
|
83
|
+
"Continue": "Jatka",
|
|
84
|
+
"Confirm": "Vahvista",
|
|
85
|
+
"Don't": "Älä",
|
|
86
|
+
"Warning": "Varoitus",
|
|
87
|
+
"Error": "Virhe",
|
|
88
|
+
"Info": "Tiedoksi",
|
|
89
|
+
"Unauthorized": "Ei lupaa",
|
|
90
|
+
"Forbidden": "Kielletty",
|
|
91
|
+
"Not found": "Ei löydy",
|
|
92
|
+
"Server error": "Palvelinvirhe",
|
|
93
|
+
"Upload": "Lähetä",
|
|
94
|
+
"upload_concluded": "Lähetys päättyi:",
|
|
95
|
+
"upload_finished": "{n} tiedostoa tai kansiota lähetetty onnistuneesti ({size})",
|
|
96
|
+
"upload_errors": "{n} epäonnistui",
|
|
97
|
+
"upload_file_rejected": "Joitakin tiedostoja ei hyväksytty",
|
|
98
|
+
"File menu": "Tiedostovalikko",
|
|
99
|
+
"Folder menu": "Kansiovalikko",
|
|
100
|
+
"Name": "Nimi",
|
|
101
|
+
"file_open": "Avaa",
|
|
102
|
+
"Download": "Lataa",
|
|
103
|
+
"Missing permission": "Oikeudet puuuttuvat",
|
|
104
|
+
"Reload": "Päivitä",
|
|
105
|
+
"Get list": "Näytä listana",
|
|
106
|
+
"Skip existing files": "Ohita jo olemassa olevat tiedostot",
|
|
107
|
+
"Size": "Koko",
|
|
108
|
+
"Timestamp": "Aikaleima",
|
|
109
|
+
"Show": "Näytä",
|
|
110
|
+
"Loading failed": "Lataaminen epäonnistui",
|
|
111
|
+
"Rename": "Nimeä uudelleen",
|
|
112
|
+
"Tiles mode:": "Kuvakkeiden koko:",
|
|
113
|
+
"off": "pienin",
|
|
114
|
+
"Operation successful": "Suoritettu onnistuneesti",
|
|
115
|
+
"Uploader": "Lähettäjä",
|
|
116
|
+
"Download counter": "Latauslaskuri",
|
|
117
|
+
"Switch zoom mode": "Vaihda zoomaus tilaa",
|
|
118
|
+
"Full screen": "Koko näyttö",
|
|
119
|
+
"File Show help": "Ohjeita",
|
|
120
|
+
"showHelpMain": "Voit käyttää näppäimistöä joihinkin toimintoihin:",
|
|
121
|
+
"showHelp_←/→": "←/→",
|
|
122
|
+
"showHelp_↑/↓": "↑/↓",
|
|
123
|
+
"showHelp_space": "välilyönti",
|
|
124
|
+
"showHelp_←/→_body": "siirry edelliseen/seuraavaan tiedostoon",
|
|
125
|
+
"showHelp_↑/↓_body": "vieritä korkeita kuvia",
|
|
126
|
+
"showHelp_space_body": "valitse",
|
|
127
|
+
"showHelp_D_body": "lataa",
|
|
128
|
+
"showHelp_Z_body": "vaihda zoomauksen tilaa",
|
|
129
|
+
"showHelp_F_body": "koko näyttö"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -119,6 +119,8 @@
|
|
|
119
119
|
"showHelp_D_body": "download",
|
|
120
120
|
"showHelp_Z_body": "cambia modalità zoom",
|
|
121
121
|
"showHelp_F_body": "pieno schermo",
|
|
122
|
+
"Destination": "Destinazione",
|
|
123
|
+
"in_queue": "{n} in coda",
|
|
122
124
|
"": "PLUGINS SECTION",
|
|
123
125
|
"": "PLUGIN thumbnails",
|
|
124
126
|
"Enable tiles mode": "Modalità griglia",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"author": "Mefistofell, SanokKule",
|
|
3
|
-
"version": 2.
|
|
4
|
-
"hfs_version": "0.
|
|
3
|
+
"version": 2.1,
|
|
4
|
+
"hfs_version": "0.48.0",
|
|
5
5
|
"translate": {
|
|
6
6
|
"Select": "Выбор",
|
|
7
7
|
"n_files": "{n,plural, one{1 файл} few{# файла} other{# файлов}}",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"zip_tooltip_whole": "Выбрать файлы для скачивания в zip-архиве. Если файлы не выбраны, то будет скачана вся папка",
|
|
16
16
|
"zip_confirm_search": "Скачать все результаты поиска в одном zip-архиве",
|
|
17
17
|
"zip_confirm_folder": "Скачать всю папку как zip-архив?",
|
|
18
|
-
"select_tooltip": "
|
|
18
|
+
"select_tooltip": "Позволяет выбрать файлы для скачивания или удаления, а так же использовать фильтры для быстрого поиска",
|
|
19
19
|
"delete_hint": "Для удаления файлов сначала нажмите Выбор",
|
|
20
20
|
"delete_confirm": "Удалить {n,plural, =1{элемент} one{# элемент} few{# элемента} many{# элементов} other{# элементов}}?",
|
|
21
21
|
"delete_completed": "{n,plural, one{Удалён} other{Удалено}} {n,plural, one{# элемент} few{# элемента} many{# элементов} other{# элементов}}",
|
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
"Download counter": "Счётчик загрузок",
|
|
117
117
|
"Switch zoom mode": "Переключить увеличение",
|
|
118
118
|
"Full screen": "Полный экран",
|
|
119
|
-
"File Show help": "Помощь
|
|
119
|
+
"File Show help": "Помощь",
|
|
120
120
|
"showHelpMain": "Для некоторых действий можно использовать клавиатуру:",
|
|
121
121
|
"showHelp_←/→": "←/→",
|
|
122
122
|
"showHelp_↑/↓": "↑/↓",
|
|
@@ -126,6 +126,12 @@
|
|
|
126
126
|
"showHelp_space_body": "выбрать файл",
|
|
127
127
|
"showHelp_D_body": "скачать",
|
|
128
128
|
"showHelp_Z_body": "переключение увеличения",
|
|
129
|
-
"showHelp_F_body": "полноэкранный режим"
|
|
129
|
+
"showHelp_F_body": "полноэкранный режим",
|
|
130
|
+
"Destination": "Назначение",
|
|
131
|
+
"in_queue": "{n} в очереди",
|
|
132
|
+
"": "PLUGINS SECTION",
|
|
133
|
+
"": "PLUGIN thumbnails",
|
|
134
|
+
"Enable tiles mode": "Просмотр в режиме плиток",
|
|
135
|
+
"thumbnails_switchBack": "Изменить вид можно в настройках"
|
|
130
136
|
}
|
|
131
137
|
}
|
package/src/listen.js
CHANGED
|
@@ -27,7 +27,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
27
27
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
28
28
|
};
|
|
29
29
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
30
|
-
exports.getUrls = exports.getIps = exports.getServerStatus = exports.httpsPortCfg = exports.openAdmin = exports.portCfg = exports.getHttpsWorkingPort = void 0;
|
|
30
|
+
exports.getUrls = exports.getIps = exports.getServerStatus = exports.stopServer = exports.startServer = exports.httpsPortCfg = exports.privateKey = exports.cert = exports.getCertObject = exports.openAdmin = exports.portCfg = exports.getHttpsWorkingPort = void 0;
|
|
31
31
|
const http = __importStar(require("http"));
|
|
32
32
|
const config_1 = require("./config");
|
|
33
33
|
const index_1 = require("./index");
|
|
@@ -56,7 +56,7 @@ exports.portCfg.sub(async (port) => {
|
|
|
56
56
|
while (!index_1.app)
|
|
57
57
|
await (0, misc_1.wait)(100);
|
|
58
58
|
stopServer(httpSrv).then();
|
|
59
|
-
httpSrv = Object.assign(http.createServer(index_1.app.callback()), { name: 'http' });
|
|
59
|
+
httpSrv = Object.assign(http.createServer({ requestTimeout: 0 }, index_1.app.callback()), { name: 'http' });
|
|
60
60
|
port = await startServer(httpSrv, { port });
|
|
61
61
|
if (!port)
|
|
62
62
|
return;
|
|
@@ -82,6 +82,12 @@ function openAdmin() {
|
|
|
82
82
|
console.log("openAdmin failed");
|
|
83
83
|
}
|
|
84
84
|
exports.openAdmin = openAdmin;
|
|
85
|
+
function getCertObject() {
|
|
86
|
+
const o = new crypto_1.X509Certificate(httpsOptions.cert);
|
|
87
|
+
const some = lodash_1.default.pick(o, ['subject', 'issuer', 'validFrom', 'validTo']);
|
|
88
|
+
return (0, misc_1.objSameKeys)(some, v => (v === null || v === void 0 ? void 0 : v.includes('=')) ? Object.fromEntries(v.split('\n').map(x => x.split('='))) : v);
|
|
89
|
+
}
|
|
90
|
+
exports.getCertObject = getCertObject;
|
|
85
91
|
const considerHttps = (0, misc_1.debounceAsync)(async () => {
|
|
86
92
|
var _a, _b, _c;
|
|
87
93
|
stopServer(httpsSrv).then();
|
|
@@ -91,8 +97,8 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
|
|
|
91
97
|
await (0, misc_1.wait)(100);
|
|
92
98
|
httpsSrv = Object.assign(https.createServer(port === PORT_DISABLED ? {} : { key: httpsOptions.private_key, cert: httpsOptions.cert }, index_1.app.callback()), { name: 'https' });
|
|
93
99
|
if (port >= 0) {
|
|
94
|
-
const cert =
|
|
95
|
-
const cn = (_a = cert.subject
|
|
100
|
+
const cert = getCertObject();
|
|
101
|
+
const cn = (_a = cert.subject) === null || _a === void 0 ? void 0 : _a.CN;
|
|
96
102
|
if (cn)
|
|
97
103
|
console.log("certificate loaded for", cn);
|
|
98
104
|
const now = new Date();
|
|
@@ -119,9 +125,9 @@ const considerHttps = (0, misc_1.debounceAsync)(async () => {
|
|
|
119
125
|
httpsSrv.on('connection', connections_1.newConnection);
|
|
120
126
|
printUrls(httpsSrv.name);
|
|
121
127
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
const httpsNeeds = [cert, privateKey];
|
|
128
|
+
exports.cert = (0, config_1.defineConfig)('cert', '');
|
|
129
|
+
exports.privateKey = (0, config_1.defineConfig)('private_key', '');
|
|
130
|
+
const httpsNeeds = [exports.cert, exports.privateKey];
|
|
125
131
|
const httpsOptions = { cert: '', private_key: '' };
|
|
126
132
|
for (const cfg of httpsNeeds) {
|
|
127
133
|
let unwatch;
|
|
@@ -170,7 +176,7 @@ function startServer(srv, { port, host }) {
|
|
|
170
176
|
}
|
|
171
177
|
function listen(host) {
|
|
172
178
|
return new Promise(async (resolve, reject) => {
|
|
173
|
-
srv === null || srv === void 0 ? void 0 : srv.listen({ port, host }, () => {
|
|
179
|
+
srv === null || srv === void 0 ? void 0 : srv.on('error', onError).listen({ port, host }, () => {
|
|
174
180
|
const ad = srv.address();
|
|
175
181
|
if (!ad)
|
|
176
182
|
return reject('no address');
|
|
@@ -178,8 +184,12 @@ function startServer(srv, { port, host }) {
|
|
|
178
184
|
srv.close();
|
|
179
185
|
return reject('type of socket not supported');
|
|
180
186
|
}
|
|
187
|
+
srv.removeListener('error', onError); // necessary in case someone calls stop/start many times
|
|
181
188
|
resolve(ad.port);
|
|
182
|
-
})
|
|
189
|
+
});
|
|
190
|
+
async function onError(e) {
|
|
191
|
+
if (!srv)
|
|
192
|
+
return;
|
|
183
193
|
srv.error = String(e);
|
|
184
194
|
srv.busy = undefined;
|
|
185
195
|
const { code } = e;
|
|
@@ -191,10 +201,11 @@ function startServer(srv, { port, host }) {
|
|
|
191
201
|
const k = (srv === httpSrv ? exports.portCfg : exports.httpsPortCfg).key();
|
|
192
202
|
console.log(` >> try specifying a different port, enter this command: config ${k} 8011`);
|
|
193
203
|
resolve(0);
|
|
194
|
-
}
|
|
204
|
+
}
|
|
195
205
|
});
|
|
196
206
|
}
|
|
197
207
|
}
|
|
208
|
+
exports.startServer = startServer;
|
|
198
209
|
function stopServer(srv) {
|
|
199
210
|
return new Promise(resolve => {
|
|
200
211
|
if (!(srv === null || srv === void 0 ? void 0 : srv.listening))
|
|
@@ -209,19 +220,22 @@ function stopServer(srv) {
|
|
|
209
220
|
});
|
|
210
221
|
});
|
|
211
222
|
}
|
|
223
|
+
exports.stopServer = stopServer;
|
|
212
224
|
async function getServerStatus() {
|
|
213
225
|
return {
|
|
214
226
|
http: await serverStatus(httpSrv, exports.portCfg.get()),
|
|
215
227
|
https: await serverStatus(httpsSrv, exports.httpsPortCfg.get()),
|
|
216
228
|
};
|
|
217
|
-
async function serverStatus(
|
|
229
|
+
async function serverStatus(srv, configuredPort) {
|
|
218
230
|
var _a;
|
|
219
|
-
const busy = await (
|
|
231
|
+
const busy = await (srv === null || srv === void 0 ? void 0 : srv.busy);
|
|
220
232
|
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.
|
|
221
233
|
return {
|
|
222
|
-
...lodash_1.default.pick(
|
|
234
|
+
...lodash_1.default.pick(srv, ['listening', 'error']),
|
|
223
235
|
busy,
|
|
224
|
-
port: ((_a =
|
|
236
|
+
port: ((_a = srv === null || srv === void 0 ? void 0 : srv.address()) === null || _a === void 0 ? void 0 : _a.port) || configuredPort,
|
|
237
|
+
configuredPort,
|
|
238
|
+
srv,
|
|
225
239
|
};
|
|
226
240
|
}
|
|
227
241
|
}
|
|
@@ -232,7 +246,7 @@ async function getIps() {
|
|
|
232
246
|
&& v4first((0, misc_1.onlyTruthy)(nets.map(net => !net.internal && net.address)))[0] // for each interface we consider only 1 address
|
|
233
247
|
)).flat();
|
|
234
248
|
const e = await api_net_1.externalIp;
|
|
235
|
-
if (e)
|
|
249
|
+
if (e && !ips.includes(e))
|
|
236
250
|
ips.unshift(e);
|
|
237
251
|
return v4first(ips)
|
|
238
252
|
.filter((x, i, a) => a.length > 1 || !x.startsWith('169.254')); // 169.254 = dhcp failure on the interface, but keep it if it's our only one
|
package/src/log.js
CHANGED
|
@@ -33,7 +33,6 @@ const config_1 = require("./config");
|
|
|
33
33
|
const fs_1 = require("fs");
|
|
34
34
|
const util = __importStar(require("util"));
|
|
35
35
|
const promises_1 = require("fs/promises");
|
|
36
|
-
const const_1 = require("./const");
|
|
37
36
|
const lodash_1 = __importDefault(require("lodash"));
|
|
38
37
|
const util_files_1 = require("./util-files");
|
|
39
38
|
const perm_1 = require("./perm");
|
|
@@ -104,9 +103,9 @@ const logMw = async (ctx, next) => {
|
|
|
104
103
|
if (rotate && last) { // rotation enabled and a file exists?
|
|
105
104
|
const passed = Number(now) - Number(last)
|
|
106
105
|
- 3600000; // be pessimistic and count a possible DST change
|
|
107
|
-
if (rotate === 'm' && (passed >= 31 *
|
|
108
|
-
|| rotate === 'd' && (passed >=
|
|
109
|
-
|| rotate === 'w' && (passed >= 7 *
|
|
106
|
+
if (rotate === 'm' && (passed >= 31 * misc_1.DAY || now.getMonth() !== last.getMonth())
|
|
107
|
+
|| rotate === 'd' && (passed >= misc_1.DAY || now.getDate() !== last.getDate()) // checking passed will solve the case when the day of the month is the same but a month has passed
|
|
108
|
+
|| rotate === 'w' && (passed >= 7 * misc_1.DAY || now.getDay() < last.getDay())) {
|
|
110
109
|
stream.end();
|
|
111
110
|
const postfix = last.getFullYear() + '-' + doubleDigit(last.getMonth() + 1) + '-' + doubleDigit(last.getDate());
|
|
112
111
|
try { // other logging requests shouldn't happen while we are renaming. Since this is very infrequent we can tolerate solving this by making it sync.
|
|
@@ -126,7 +125,7 @@ const logMw = async (ctx, next) => {
|
|
|
126
125
|
const user = (0, perm_1.getCurrentUsername)(ctx);
|
|
127
126
|
const length = (_c = ctx.state.length) !== null && _c !== void 0 ? _c : ctx.length;
|
|
128
127
|
const uri = ctx.originalUrl;
|
|
129
|
-
const extra = ctx.state.includesLastByte && ctx.vfsNode && { dl: 1 }
|
|
128
|
+
const extra = ctx.state.includesLastByte && ctx.vfsNode && ctx.res.finished && { dl: 1 }
|
|
130
129
|
|| ctx.state.uploadPath && { ul: ctx.state.uploadPath, size: ctx.state.uploadSize }
|
|
131
130
|
|| undefined;
|
|
132
131
|
events_1.default.emit(logger.name, Object.assign(lodash_1.default.pick(ctx, ['ip', 'method', 'status']), { length, user, ts: now, uri, extra }));
|
package/src/middlewares.js
CHANGED
|
@@ -32,7 +32,7 @@ const config_1 = require("./config");
|
|
|
32
32
|
const lang_1 = require("./lang");
|
|
33
33
|
const forceHttps = (0, config_1.defineConfig)('force_https', true);
|
|
34
34
|
const ignoreProxies = (0, config_1.defineConfig)('ignore_proxies', false);
|
|
35
|
-
exports.sessionDuration = (0, config_1.defineConfig)('session_duration', Number(process.env.SESSION_DURATION) ||
|
|
35
|
+
exports.sessionDuration = (0, config_1.defineConfig)('session_duration', Number(process.env.SESSION_DURATION) || misc_1.DAY / 1000, v => v * 1000);
|
|
36
36
|
exports.gzipper = (0, koa_compress_1.default)({
|
|
37
37
|
threshold: 2048,
|
|
38
38
|
gzip: { flush: zlib_1.constants.Z_SYNC_FLUSH },
|
|
@@ -213,7 +213,7 @@ const someSecurity = async (ctx, next) => {
|
|
|
213
213
|
exports.someSecurity = someSecurity;
|
|
214
214
|
// limited to http proxies
|
|
215
215
|
function getProxyDetected() {
|
|
216
|
-
if ((proxyDetected === null || proxyDetected === void 0 ? void 0 : proxyDetected.state.when) < Date.now() -
|
|
216
|
+
if ((proxyDetected === null || proxyDetected === void 0 ? void 0 : proxyDetected.state.when) < Date.now() - misc_1.DAY)
|
|
217
217
|
proxyDetected = undefined;
|
|
218
218
|
return !ignoreProxies.get() && proxyDetected
|
|
219
219
|
&& { from: proxyDetected.ip, for: proxyDetected.get('X-Forwarded-For') };
|
package/src/misc.js
CHANGED
|
@@ -18,7 +18,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
18
18
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
19
|
};
|
|
20
20
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
-
exports.asyncGeneratorToReadable = exports.stream2string = exports.same = exports.matches = exports.makeMatcher = exports.makeNetMatcher = exports.isLocalHost = exports.onOff = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit = exports.debounceAsync = void 0;
|
|
21
|
+
exports.apiAssertTypes = exports.AsapStream = exports.asyncGeneratorToReadable = exports.stream2string = exports.same = exports.matches = exports.makeMatcher = exports.makeNetMatcher = exports.isLocalHost = exports.onOff = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit = exports.debounceAsync = void 0;
|
|
22
22
|
const path_1 = require("path");
|
|
23
23
|
const lodash_1 = __importDefault(require("lodash"));
|
|
24
24
|
const assert_1 = __importDefault(require("assert"));
|
|
@@ -30,6 +30,8 @@ const micromatch_1 = require("micromatch");
|
|
|
30
30
|
const node_net_1 = require("node:net");
|
|
31
31
|
const debounceAsync_1 = __importDefault(require("./debounceAsync"));
|
|
32
32
|
exports.debounceAsync = debounceAsync_1.default;
|
|
33
|
+
const apiMiddleware_1 = require("./apiMiddleware");
|
|
34
|
+
const const_1 = require("./const");
|
|
33
35
|
const cbs = new Set();
|
|
34
36
|
function onProcessExit(cb) {
|
|
35
37
|
cbs.add(cb);
|
|
@@ -83,7 +85,7 @@ function makeNetMatcher(mask, emptyMaskReturns = false) {
|
|
|
83
85
|
all[0] = all[0].slice(1);
|
|
84
86
|
const bl = new node_net_1.BlockList();
|
|
85
87
|
for (const x of all) {
|
|
86
|
-
const m = /^([.:\
|
|
88
|
+
const m = /^([.:\da-f]+)(?:\/(\d+)|-(.+)|)$/i.exec(x);
|
|
87
89
|
if (!m) {
|
|
88
90
|
console.warn("error in network mask", x);
|
|
89
91
|
continue;
|
|
@@ -147,3 +149,28 @@ function asyncGeneratorToReadable(generator) {
|
|
|
147
149
|
});
|
|
148
150
|
}
|
|
149
151
|
exports.asyncGeneratorToReadable = asyncGeneratorToReadable;
|
|
152
|
+
// produces as soon as a promise resolves, not sequentially
|
|
153
|
+
class AsapStream extends stream_1.Readable {
|
|
154
|
+
constructor(promises) {
|
|
155
|
+
super({ objectMode: true });
|
|
156
|
+
this.promises = promises;
|
|
157
|
+
this.finished = false;
|
|
158
|
+
}
|
|
159
|
+
_read() {
|
|
160
|
+
if (this.finished)
|
|
161
|
+
return;
|
|
162
|
+
this.finished = true;
|
|
163
|
+
for (const p of this.promises)
|
|
164
|
+
p.then(data => this.push(data), e => this.emit('error', e));
|
|
165
|
+
Promise.allSettled(this.promises).then(() => this.push(null));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
exports.AsapStream = AsapStream;
|
|
169
|
+
function apiAssertTypes(paramsByType) {
|
|
170
|
+
for (const [types, params] of Object.entries(paramsByType))
|
|
171
|
+
for (const type of types.split('_'))
|
|
172
|
+
for (const [name, val] of Object.entries(params))
|
|
173
|
+
if (typeof val !== type)
|
|
174
|
+
throw new apiMiddleware_1.ApiError(const_1.HTTP_BAD_REQUEST, 'bad ' + name);
|
|
175
|
+
}
|
|
176
|
+
exports.apiAssertTypes = apiAssertTypes;
|