hfs 0.43.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +50 -9
  2. package/admin/assets/index-35f6e3dc.css +1 -0
  3. package/admin/assets/index-ef68a7ab.js +510 -0
  4. package/{frontend/assets/sha512-8ebf6e2a.js → admin/assets/sha512-d091720e.js} +1 -1
  5. package/admin/index.html +2 -2
  6. package/frontend/assets/index-a3b0d6ac.js +94 -0
  7. package/frontend/assets/index-fe0f3d77.css +1 -0
  8. package/{admin/assets/sha512-55ff2fa3.js → frontend/assets/sha512-96fd938f.js} +1 -1
  9. package/frontend/index.html +3 -2
  10. package/package.json +2 -2
  11. package/plugins/download-counter/plugin.js +5 -3
  12. package/plugins/download-counter/public/main.js +2 -2
  13. package/plugins/vhosting/plugin.js +17 -11
  14. package/src/adminApis.js +1 -1
  15. package/src/api.auth.js +4 -1
  16. package/src/api.file_list.js +7 -8
  17. package/src/api.lang.js +2 -1
  18. package/src/api.vfs.js +41 -28
  19. package/src/apiMiddleware.js +3 -2
  20. package/src/const.js +4 -3
  21. package/src/customHtml.js +1 -1
  22. package/src/debounceAsync.js +3 -3
  23. package/src/frontEndApis.js +6 -6
  24. package/src/github.js +26 -8
  25. package/src/index.js +2 -1
  26. package/src/lang.js +9 -19
  27. package/src/langs/embedded.js +13 -0
  28. package/src/langs/hfs-lang-ms.json +70 -0
  29. package/src/langs/hfs-lang-ru.json +22 -22
  30. package/src/langs/hfs-lang-zh-tw.json +106 -0
  31. package/src/listen.js +1 -1
  32. package/src/log.js +6 -2
  33. package/src/middlewares.js +14 -18
  34. package/src/misc.js +7 -3
  35. package/src/plugins.js +6 -6
  36. package/src/serveFile.js +1 -1
  37. package/src/serveGuiFiles.js +2 -2
  38. package/src/update.js +1 -2
  39. package/src/upload.js +6 -6
  40. package/src/util-http.js +5 -3
  41. package/src/vfs.js +100 -52
  42. package/src/zip.js +4 -4
  43. package/admin/assets/index-5cd667a5.js +0 -511
  44. package/admin/assets/index-8ff39373.css +0 -1
  45. package/frontend/assets/index-27488fde.js +0 -94
  46. package/frontend/assets/index-54a5c76f.css +0 -1
@@ -0,0 +1,70 @@
1
+ {
2
+ "author": "ChatGPT",
3
+ "version": 1.0,
4
+ "hfs_version": "0.43.0",
5
+ "translate": {
6
+ "Select": "Pilih",
7
+ "n_files": "{n,plural, one{# fail} other{# fail}}",
8
+ "n_folders": "{n,plural, one{# folder} other{# folder}}",
9
+ "filter_count": "{n,plural, one{# disenaraikan} other{# disenaraikan}}",
10
+ "select_count": "{n,plural, one{# dipilih} other{# dipilih}}",
11
+ "filter_placeholder": "Taip di sini untuk menyaring senarai di bawah",
12
+ "Select some files": "Pilih beberapa fail",
13
+ "zip_checkboxes": "Guna kotak semak untuk memilih fail, kemudian anda boleh gunakan Zip lagi",
14
+ "zip_tooltip_selected": "Muat turun elemen yang dipilih sebagai fail zip tunggal",
15
+ "zip_tooltip_whole": "Muat turun senarai keseluruhan (tidak disaring) sebagai fail zip tunggal. Jika anda memilih beberapa elemen, hanya yang dipilih akan dimuat turun.",
16
+ "zip_confirm_search": "Muat turun SEMUA hasil carian ini sebagai arkib ZIP?",
17
+ "zip_confirm_folder": "Muat turun FOLDER KESULURUHAN sebagai arkib ZIP?",
18
+ "select_tooltip": "Pilihan digunakan untuk \"Zip\" dan \"Padam\" (apabila tersedia), tetapi anda juga boleh menyaring senarai",
19
+ "delete_hint": "Untuk memadam, klik Pilih dahulu",
20
+ "delete_confirm": "Padam {n,plural, one{# item} other{# items}}?",
21
+ "delete_completed": "Penghapusan: {n} selesai",
22
+ "delete_failed": ", {n,plural, one{# gagal} other{# gagal}}",
23
+ "delete_select": "Pilih sesuatu untuk dipadam",
24
+ "Delete": "Padam",
25
+ "Options": "Pilihan",
26
+ "Search": "Carian",
27
+ "Zip": "Zip",
28
+ "search_msg": "Cari folder ini dan sub-folder",
29
+ "Searching": "Mencari",
30
+ "Searched": "Dicari",
31
+ "Clear search": "Kosongkan carian",
32
+ "Interrupted": "Dihentikan",
33
+ "stopped_before": "berhenti_sebelum",
34
+ "empty_list": "senarai_kosong",
35
+ "filter_none": "filter_tidak_ada",
36
+ "Admin-panel": "Panel pentadbir",
37
+ "Login": "Log masuk",
38
+ "Username": "Nama pengguna",
39
+ "Password": "Kata laluan",
40
+ "login_untrusted": "Log masuk dibatalkan: identiti pelayan tidak boleh dipercayai",
41
+ "login_bad_credentials": "Kelayakan tidak sah",
42
+ "login_bad_cookies": "Kuki tidak berfungsi - log masuk gagal",
43
+ "User panel": "Panel pengguna",
44
+ "Change password": "Tukar kata laluan",
45
+ "enter_pass": "Masukkan kata laluan baru",
46
+ "enter_pass2": "Masukkan semula kata laluan baru yang sama",
47
+ "pass2_mismatch": "Kata laluan kedua yang anda masukkan tidak sepadan dengan yang pertama.",
48
+ "Confirm": "Sahkan",
49
+ "Don't": "Jangan",
50
+ "Warning": "Amaran",
51
+ "Error": "Ralat",
52
+ "Info": "Maklumat",
53
+ "Unauthorized": "Tidak dibenarkan",
54
+ "Forbidden": "Dilarang",
55
+ "Not found": "Tidak ditemui",
56
+ "Server error": "Ralat pelayan",
57
+ "Upload": "Muat naik",
58
+ "upload_concluded": "Muat naik selesai:",
59
+ "upload_finished": "{n} selesai ({size})",
60
+ "upload_errors": "{n} gagal",
61
+ "upload_file_rejected": "Beberapa fail tidak diterima",
62
+ "download counter": "pengira muat turun",
63
+ "File menu": "Menu fail",
64
+ "Folder menu": "Menu folder",
65
+ "Name": "Nama",
66
+ "file_open": "Buka",
67
+ "Download": "Muat turun",
68
+ "Missing permission": "Keizinan hilang"
69
+ }
70
+ }
@@ -1,32 +1,32 @@
1
1
  {
2
2
  "author": "Mefistofell, SanokKule",
3
- "version": 1.9,
3
+ "version": 1.92,
4
4
  "hfs_version": "0.43.0",
5
5
  "translate": {
6
6
  "Select": "Выбор",
7
7
  "n_files": "{n,plural, one{1 файл} few{# файла} other{# файлов}}",
8
- "n_folders": "{n,plural, one{1 папка} few {# папки} other{# папок}}",
8
+ "n_folders": "{n,plural, one{1 папка} few{# папки} other{# папок}}",
9
9
  "filter_count": "{n,plural, one{1 элемент} few{# элемента} other{# элементов}}",
10
10
  "select_count": "{n,plural, one{Выбран #} other{Выбрано #}}",
11
- "filter_placeholder": "Фильтр",
12
- "Select some files": "Выберите файлы",
11
+ "filter_placeholder": "Введите текст для фильтрации списка",
12
+ "Select some files": "Выделите файлы",
13
13
  "zip_checkboxes": "Выделите нужные файлы и снова нажмите zip",
14
14
  "zip_tooltip_selected": "Скачать выбранные элементы в zip-архиве",
15
15
  "zip_tooltip_whole": "Выбрать файлы для скачивания в zip-архиве. Если файлы не выбраны, то будет скачана вся папка",
16
16
  "zip_confirm_search": "Скачать все результаты поиска в одном zip-архиве",
17
17
  "zip_confirm_folder": "Скачать всю папку как zip-архив?",
18
- "select_tooltip": "Выбор необходимых файлов",
18
+ "select_tooltip": "Выбор применяется к \"Zip\" и \"Удалить\" (если доступно), но также список можно отфильтровать",
19
19
  "delete_hint": "Для удаления файлов сначала нажмите Выбор",
20
- "delete_confirm": "Удалить {n,plural, =1{элемент} one{# элемент} few {# элемента} many{# элементов} other{# элементов}}?",
21
- "delete_completed": "{n,plural, one{Удалён} other{Удалено}} {n,plural, one{# элемент} few {# элемента} many{# элементов} other{# элементов}}",
20
+ "delete_confirm": "Удалить {n,plural, =1{элемент} one{# элемент} few{# элемента} many{# элементов} other{# элементов}}?",
21
+ "delete_completed": "{n,plural, one{Удалён} other{Удалено}} {n,plural, one{# элемент} few{# элемента} many{# элементов} other{# элементов}}",
22
22
  "delete_failed": ", {n} не удалось",
23
- "delete_select": "Выберите элементы для удаления",
23
+ "delete_select": "Выделите элементы для удаления",
24
24
  "Delete": "Удалить",
25
25
  "Options": "Настройки",
26
26
  "Search": "Поиск",
27
27
  "Zip": "Zip",
28
28
  "search_msg": "Поиск в текущей и вложенных папках",
29
- "Searching": "Поиск...",
29
+ "Searching": "Идёт поиск",
30
30
  "Searched": "Результаты поиска",
31
31
  "Clear search": "Отменить поиск",
32
32
  "Interrupted": "Прерван",
@@ -37,9 +37,9 @@
37
37
  "Login": "Вход",
38
38
  "Username": "Имя пользователя",
39
39
  "Password": "Пароль",
40
- "login_untrusted": "Вход в систему прерван",
40
+ "login_untrusted": "Вход отменён: личности сервера нельзя доверять",
41
41
  "login_bad_credentials": "Неверное имя пользователя или пароль",
42
- "login_bad_cookies": "Вход прерван. Отключены cookies",
42
+ "login_bad_cookies": "Не удалось войти - отключены cookies",
43
43
  "User panel": "Панель пользователя",
44
44
  "Change password": "Изменить пароль",
45
45
  "enter_pass": "Введите новый пароль",
@@ -61,23 +61,23 @@
61
61
  "Clear": "Очистить",
62
62
  "failed_upload": "Не удалась загрузить {name}",
63
63
  "confirm_resume": "Возобновить загрузку?",
64
- "file too large": "Слишком большой файл",
64
+ "file too large": "слишком большой файл",
65
65
  "Enter folder name": "Введите имя папки",
66
66
  "Successfully created": "Успешно создано",
67
67
  "enter_folder": "Перейти в папку",
68
68
  "folder_exists": "Папка уже существует",
69
- "Sort by": "Сортировка",
70
- "name": "Имя",
71
- "extension": "Расширение",
72
- "size": "Размер",
73
- "time": "Время",
69
+ "Sort by": "Сортировка по",
70
+ "name": "имени",
71
+ "extension": "расширению",
72
+ "size": "размеру",
73
+ "time": "времени",
74
74
  "Invert order": "Инвертировать",
75
75
  "Folders first": "Сначала папки",
76
76
  "Numeric names": "Сначала цифры",
77
77
  "theme:": "Тема:",
78
- "auto": "Авто",
79
- "light": "Светлая",
80
- "dark": "Тёмная",
78
+ "auto": "авто",
79
+ "light": "светлая",
80
+ "dark": "тёмная",
81
81
  "parent folder": "Родительская папка",
82
82
  "home": "Домашняя папка",
83
83
  "Continue": "Продолжить",
@@ -92,8 +92,8 @@
92
92
  "Server error": "Ошибка сервера",
93
93
  "Upload": "Загрузить",
94
94
  "upload_concluded": "Загрузка окончена:",
95
- "upload_finished": "{n,plural, one{Загружен # файл} few{Загружено # файла} other{Загружено # файлов}} ({size})",
96
- "upload_errors": "Не удалось загрузить {n,plural, one{# файл} few{# файла} other{# файлов}}",
95
+ "upload_finished": "{n,plural, one{# файл загружен} few{# файла загружено} other{# файлов загружено}} ({size})",
96
+ "upload_errors": "{n,plural, one{# файл} few{# файла} other{# файлов}} не удалось загрузить",
97
97
  "upload_file_rejected": "Некоторые файлы не были приняты",
98
98
  "download counter": "Количество скачиваний",
99
99
  "File menu": "Меню файла",
@@ -0,0 +1,106 @@
1
+ {
2
+ "author": "Phoenix",
3
+ "version": 1.0,
4
+ "hfs_version": "0.43.0",
5
+ "translate": {
6
+ "Select": "選擇",
7
+ "n_files": "{n,plural,one{# 個檔案} other{# 個檔案}}",
8
+ "n_folders": "{n,plural,one{# 個資料夾} other{# 個資料夾}}",
9
+ "filter_count": "{n,plural, one{# 已過濾} other{# 已過濾}}",
10
+ "select_count": "{n,plural, one{# 已選擇} other{# 已選擇}}",
11
+ "filter_placeholder": "輸入內容,過濾下方列表",
12
+ "Select some files": "選擇一些檔案",
13
+ "zip_checkboxes": "使用選擇來複選檔案,然後再次使用Zip壓縮",
14
+ "zip_tooltip_selected": "將所選檔案下載為單一Zip壓縮檔",
15
+ "zip_tooltip_whole": "將整個列表(未過濾)作為單一Zip壓縮檔下載.如果選擇了一些檔案,則只會下載那些檔案",
16
+ "zip_confirm_search": "將此搜尋的所有結果下載為Zip壓縮檔?",
17
+ "zip_confirm_folder": "確認將整個資料夾下載為Zip壓縮檔?",
18
+ "select_tooltip": "選擇適用於\"Zip\"和\"刪除\"(如果有的話),也可以過濾列表",
19
+ "delete_hint": "請先點擊“選擇”某些項目,才能執行刪除.",
20
+ "delete_confirm": "確認刪除 {n,plural, one{# 項} other{# 項}}?",
21
+ "delete_completed": "刪除: {n} 完成",
22
+ "delete_failed": ", {n,plural, one{# 失敗} other{# 失敗}}",
23
+ "delete_select": "選擇要刪除的項目",
24
+ "Delete": "刪除",
25
+ "Options": "選項",
26
+ "Search": "搜尋",
27
+ "Zip": "Zip",
28
+ "search_msg": "搜尋目前資料夾和子資料夾",
29
+ "Searching": "搜尋中",
30
+ "Searched": "搜尋完成",
31
+ "Clear search": "清除搜尋條件",
32
+ "Interrupted": "中止",
33
+ "stopped_before": "已停止",
34
+ "empty_list": "空列表",
35
+ "filter_none": "過濾後無找到",
36
+ "Admin-panel": "管理面板",
37
+ "Login": "登入",
38
+ "Username": "使用者名稱",
39
+ "Password": "密碼",
40
+ "login_untrusted": "登入已中止:無法信任伺服器身份",
41
+ "login_bad_credentials": "帳號密碼錯誤",
42
+ "login_bad_cookies": "Cookies無法運作 - 登入失敗",
43
+ "User panel": "使用者面板",
44
+ "Change password": "修改密碼",
45
+ "enter_pass": "輸入新密碼",
46
+ "enter_pass2": "再次輸入相同的新密碼",
47
+ "pass2_mismatch": "密碼輸入不一致。修改失敗!",
48
+ "password_changed": "密碼修改成功",
49
+ "Logout": "登出",
50
+ "connection error": "連線錯誤",
51
+ "Full timestamp:": "完整的時間戳:",
52
+ "Search was interrupted": "搜尋已中止",
53
+ "Stop list": "停止",
54
+ "upload_starting": "開始下載",
55
+ "wrong_account": "使用者 {u} 沒有存取權限,請更換使用者",
56
+ "no_upload_here": "沒有上傳權限",
57
+ "Create folder": "建立資料夾",
58
+ "Pick files": "選擇檔案",
59
+ "Pick folder": "選擇資料夾",
60
+ "send_files": "開始上傳 {n,plural,one{# 檔案} other{# 檔案}}, {size}",
61
+ "Clear": "清空",
62
+ "failed_upload": "無法上傳 {name}",
63
+ "confirm_resume": "繼續上傳?",
64
+ "file too large": "檔案太大",
65
+ "Enter folder name": "輸入資料夾名稱",
66
+ "Successfully created": "建立成功",
67
+ "enter_folder": "進入資料夾",
68
+ "folder_exists": "已有相同名稱資料夾存在",
69
+ "Sort by": "排序依據",
70
+ "name": "檔案名稱",
71
+ "extension": "副檔名",
72
+ "size": "檔案大小",
73
+ "time": "檔案時間",
74
+ "Invert order": "倒序排列",
75
+ "Folders first": "資料夾先排序",
76
+ "Numeric names": "數字編號排序",
77
+ "theme:": "主題:",
78
+ "auto": "自動",
79
+ "light": "淺色",
80
+ "dark": "深色",
81
+ "parent folder": "上層資料夾",
82
+ "home": "主目錄",
83
+ "Continue": "繼續",
84
+ "Confirm": "確認",
85
+ "Don't": "取消",
86
+ "Warning": "警告",
87
+ "Error": "錯誤",
88
+ "Info": "資訊",
89
+ "Unauthorized": "未授權",
90
+ "Forbidden": "禁止",
91
+ "Not found": "找不到",
92
+ "Server error": "伺服器錯誤",
93
+ "Upload": "上傳",
94
+ "upload_concluded": "上傳完成:",
95
+ "upload_finished": "{n} 個檔案完成 ({size})",
96
+ "upload_errors": "{n} 失敗",
97
+ "upload_file_rejected": "部分檔案不被允許",
98
+ "download counter": "下載次數",
99
+ "File menu": "檔案選單",
100
+ "Folder menu": "資料夾選單",
101
+ "Name": "名稱",
102
+ "file_open": "開啟",
103
+ "Download": "下載",
104
+ "Missing permission": "權限不足"
105
+ }
106
+ }
package/src/listen.js CHANGED
@@ -46,7 +46,7 @@ let httpsSrv;
46
46
  const openBrowserAtStart = (0, config_1.defineConfig)('open_browser_at_start', !const_1.DEV);
47
47
  function getHttpsWorkingPort() {
48
48
  var _a;
49
- return httpsSrv.listening && ((_a = httpsSrv.address()) === null || _a === void 0 ? void 0 : _a.port);
49
+ return (httpsSrv === null || httpsSrv === void 0 ? void 0 : httpsSrv.listening) && ((_a = httpsSrv.address()) === null || _a === void 0 ? void 0 : _a.port);
50
50
  }
51
51
  exports.getHttpsWorkingPort = getHttpsWorkingPort;
52
52
  exports.portCfg = (0, config_1.defineConfig)('port', 80);
package/src/log.js CHANGED
@@ -38,6 +38,7 @@ const events_1 = __importDefault(require("./events"));
38
38
  const lodash_1 = __importDefault(require("lodash"));
39
39
  const util_files_1 = require("./util-files");
40
40
  const perm_1 = require("./perm");
41
+ const misc_1 = require("./misc");
41
42
  class Logger {
42
43
  constructor(name) {
43
44
  this.name = name;
@@ -79,6 +80,7 @@ errorLogFile.sub(path => {
79
80
  accessErrorLog.setPath(path);
80
81
  });
81
82
  const logRotation = (0, config_1.defineConfig)('log_rotation', 'weekly');
83
+ const dontLogNet = (0, config_1.defineConfig)('dont_log_net', '127.0.0.1|::1', misc_1.makeNetMatcher);
82
84
  function log() {
83
85
  const debounce = lodash_1.default.debounce(cb => cb(), 1000);
84
86
  return async (ctx, next) => {
@@ -87,6 +89,8 @@ function log() {
87
89
  console.debug(ctx.status, ctx.method, ctx.path);
88
90
  Promise.race([(0, stream_1.once)(ctx.res, 'finish'), (0, stream_1.once)(ctx.res, 'close')]).then(() => {
89
91
  var _a, _b, _c, _d;
92
+ if (dontLogNet.compiled()(ctx.ip))
93
+ return;
90
94
  const isError = ctx.status >= 400;
91
95
  const logger = isError && accessErrorLog || accessLogger;
92
96
  const rotate = (_a = logRotation.get()) === null || _a === void 0 ? void 0 : _a[0];
@@ -135,7 +139,7 @@ debugLogFile.on('open', () => {
135
139
  const was = console.error;
136
140
  console.error = function (...args) {
137
141
  was.apply(this, args);
138
- const params = args.map(x => typeof x === 'string' ? x : JSON.stringify(x)).join(' ');
139
- debugLogFile.write(new Date().toLocaleString() + ': ' + params + '\n');
142
+ args = args.map(x => { var _a; return typeof x === 'string' ? x : ((_a = (0, misc_1.tryJson)(x)) !== null && _a !== void 0 ? _a : String(x)); });
143
+ debugLogFile.write(new Date().toLocaleString() + ': ' + args.join(' ') + '\n');
140
144
  };
141
145
  }).on('error', () => console.log("cannot create debug.log"));
@@ -4,9 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.paramsDecoder = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.serveGuiAndSharedFiles = exports.sessions = exports.headRequests = exports.gzipper = void 0;
7
+ exports.paramsDecoder = exports.prepareState = exports.getProxyDetected = exports.someSecurity = exports.serveGuiAndSharedFiles = exports.headRequests = exports.gzipper = void 0;
8
8
  const koa_compress_1 = __importDefault(require("koa-compress"));
9
- const koa_session_1 = __importDefault(require("koa-session"));
10
9
  const const_1 = require("./const");
11
10
  const const_2 = require("./const");
12
11
  const vfs_1 = require("./vfs");
@@ -31,6 +30,7 @@ const zlib_1 = require("zlib");
31
30
  const listen_1 = require("./listen");
32
31
  const config_1 = require("./config");
33
32
  const forceHttps = (0, config_1.defineConfig)('force_https', true);
33
+ const ignoreProxies = (0, config_1.defineConfig)('ignore_proxies', false);
34
34
  exports.gzipper = (0, koa_compress_1.default)({
35
35
  threshold: 2048,
36
36
  gzip: { flush: zlib_1.constants.Z_SYNC_FLUSH },
@@ -55,13 +55,6 @@ const headRequests = async (ctx, next) => {
55
55
  ctx.response.length = length;
56
56
  };
57
57
  exports.headRequests = headRequests;
58
- const sessions = (app) => (0, koa_session_1.default)({
59
- key: 'hfs_$id',
60
- signed: true,
61
- rolling: true,
62
- maxAge: const_1.SESSION_DURATION,
63
- }, app);
64
- exports.sessions = sessions;
65
58
  const serveFrontendFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.FRONTEND_PROXY, const_2.FRONTEND_URI);
66
59
  const serveFrontendPrefixed = (0, koa_mount_1.default)(const_2.FRONTEND_URI.slice(0, -1), serveFrontendFiles);
67
60
  const serveAdminFiles = (0, serveGuiFiles_1.serveGuiFiles)(process.env.ADMIN_PROXY, const_1.ADMIN_URI);
@@ -142,20 +135,20 @@ const serveGuiAndSharedFiles = async (ctx, next) => {
142
135
  : serveFrontendFiles(ctx, next);
143
136
  };
144
137
  exports.serveGuiAndSharedFiles = serveGuiAndSharedFiles;
145
- let proxyDetected = false;
138
+ let proxyDetected;
146
139
  const someSecurity = async (ctx, next) => {
147
140
  ctx.request.ip = (0, connections_1.normalizeIp)(ctx.ip);
148
141
  try {
149
- let proxy = ctx.get('X-Forwarded-For');
150
- // we have some dev-proxies to ignore
151
- if (const_1.DEV && proxy && [process.env.FRONTEND_PROXY, process.env.ADMIN_PROXY].includes(ctx.get('X-Forwarded-port')))
152
- proxy = '';
153
142
  if ((0, misc_1.dirTraversal)(decodeURI(ctx.path)))
154
143
  return ctx.status = const_1.HTTP_FOOL;
155
144
  if ((0, block_1.applyBlock)(ctx.socket, ctx.ip))
156
145
  return;
157
- proxyDetected || (proxyDetected = proxy > '');
158
- ctx.state.proxiedFor = proxy;
146
+ if (!ctx.ips.length && ctx.get('X-Forwarded-For') // empty ctx.ips implies we didn't configure for proxies
147
+ // we have some dev-proxies to ignore
148
+ && !(const_1.DEV && [process.env.FRONTEND_PROXY, process.env.ADMIN_PROXY].includes(ctx.get('X-Forwarded-port')))) {
149
+ proxyDetected = ctx;
150
+ ctx.state.when = new Date();
151
+ }
159
152
  }
160
153
  catch (_a) {
161
154
  return ctx.status = const_1.HTTP_FOOL;
@@ -163,9 +156,12 @@ const someSecurity = async (ctx, next) => {
163
156
  return next();
164
157
  };
165
158
  exports.someSecurity = someSecurity;
166
- // this is only about http proxies
159
+ // limited to http proxies
167
160
  function getProxyDetected() {
168
- return proxyDetected;
161
+ if ((proxyDetected === null || proxyDetected === void 0 ? void 0 : proxyDetected.state.when) < Date.now() - const_1.DAY)
162
+ proxyDetected = undefined;
163
+ return !ignoreProxies.get() && proxyDetected
164
+ && { from: proxyDetected.ip, for: proxyDetected.get('X-Forwarded-For') };
169
165
  }
170
166
  exports.getProxyDetected = getProxyDetected;
171
167
  const prepareState = async (ctx, next) => {
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.try_ = exports.stream2string = exports.tryJson = exports.same = exports.matches = exports.makeMatcher = exports.makeNetMatcher = exports.isLocalHost = exports.with_ = exports.typedKeys = exports.objRenameKey = exports.onOff = exports.pendingPromise = exports.onlyTruthy = exports.truthy = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit = exports.randomId = exports.getOrSet = exports.wantArray = exports.waitFor = exports.wait = exports.newObj = exports.setHidden = exports.prefix = exports.removeStarting = exports.enforceFinal = exports.debounceAsync = void 0;
21
+ exports.try_ = exports.stream2string = exports.tryJson = exports.same = exports.matches = exports.makeMatcher = exports.makeNetMatcher = exports.isLocalHost = exports.with_ = exports.hasProp = exports.typedKeys = exports.objRenameKey = exports.onOff = exports.pendingPromise = exports.onlyTruthy = exports.truthy = exports.pattern2filter = exports.onFirstEvent = exports.onProcessExit = exports.randomId = exports.getOrSet = exports.wantArray = exports.waitFor = exports.wait = exports.newObj = exports.setHidden = exports.prefix = exports.removeStarting = exports.enforceFinal = 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"));
@@ -166,6 +166,10 @@ function typedKeys(o) {
166
166
  return Object.keys(o);
167
167
  }
168
168
  exports.typedKeys = typedKeys;
169
+ function hasProp(obj, key) {
170
+ return key in obj;
171
+ }
172
+ exports.hasProp = hasProp;
169
173
  function with_(par, cb) {
170
174
  return cb(par);
171
175
  }
@@ -182,12 +186,12 @@ function makeNetMatcher(mask, emptyMaskReturns = false) {
182
186
  }
183
187
  exports.makeNetMatcher = makeNetMatcher;
184
188
  function makeMatcher(mask, emptyMaskReturns = false) {
185
- return mask ? (0, micromatch_1.matcher)('(' + mask + ')') // adding () will allow us to use the pipe at root level
189
+ return mask ? (0, micromatch_1.matcher)(mask.replace(/^(!)?/, '$1(') + ')') // adding () will allow us to use the pipe at root level
186
190
  : () => emptyMaskReturns;
187
191
  }
188
192
  exports.makeMatcher = makeMatcher;
189
193
  function matches(s, mask, emptyMaskReturns = false) {
190
- return makeMatcher('(' + mask + ')', emptyMaskReturns)(s); // adding () will allow us to use the pipe at root level
194
+ return makeMatcher(mask, emptyMaskReturns)(s); // adding () will allow us to use the pipe at root level
191
195
  }
192
196
  exports.matches = matches;
193
197
  function same(a, b) {
package/src/plugins.js CHANGED
@@ -231,7 +231,8 @@ async function rescan() {
231
231
  if (found.includes(id)) // not twice
232
232
  continue;
233
233
  found.push(id);
234
- loadPlugin(id, f);
234
+ if (!plugins[id]) // already loaded
235
+ loadPlugin(id, f);
235
236
  }
236
237
  for (const [id, p] of Object.entries(foundDisabled)) {
237
238
  const a = availablePlugins[id];
@@ -322,17 +323,16 @@ function deleteModule(id) {
322
323
  for (const child of (0, misc_1.wantArray)((_a = cache[k]) === null || _a === void 0 ? void 0 : _a.children))
323
324
  (0, misc_1.getOrSet)(requiredBy, child.id, () => []).push(k);
324
325
  const deleted = [];
325
- recur(id);
326
- function recur(id) {
327
- let mod = cache[id];
326
+ (function deleteCache(id) {
327
+ const mod = cache[id];
328
328
  if (!mod)
329
329
  return;
330
330
  delete cache[id];
331
331
  deleted.push(id);
332
332
  for (const child of mod.children)
333
333
  if (!lodash_1.default.difference(requiredBy[child.id], deleted).length)
334
- recur(child.id);
335
- }
334
+ deleteCache(child.id);
335
+ })(id);
336
336
  }
337
337
  (0, misc_1.onProcessExit)(() => Promise.allSettled(mapPlugins(pl => pl.unload())));
338
338
  function parsePluginSource(id, source) {
package/src/serveFile.js CHANGED
@@ -33,7 +33,7 @@ function serveFileNode(ctx, node) {
33
33
  }
34
34
  }
35
35
  ctx.vfsNode = node; // useful to tell service files from files shared by the user
36
- if ('dl' in ctx.params) // please, download
36
+ if ('dl' in ctx.query) // please, download
37
37
  ctx.attachment(name);
38
38
  return serveFile(ctx, source || '', mimeString);
39
39
  }
@@ -101,7 +101,7 @@ async function treatIndex(ctx, filesUri, body) {
101
101
  const lang = await (0, lang_1.getLangData)(ctx);
102
102
  let ret = body
103
103
  .replace(/((?:src|href) *= *['"])\/?(?![a-z]+:\/\/)/g, '$1' + ctx.state.revProxyPath + filesUri)
104
- .replace('</head>', () => `
104
+ .replace('<body>', () => `<body>
105
105
  ${!isFrontend ? '' : `
106
106
  <title>${adminApis_1.title.get()}</title>
107
107
  <link rel="icon" href="${adminApis_1.favicon.get() ? '/favicon.ico' : 'data:;'}" />
@@ -139,7 +139,7 @@ async function treatIndex(ctx, filesUri, body) {
139
139
  return (_a = plug.frontend_js) === null || _a === void 0 ? void 0 : _a.map(f => `<script defer plugin=${JSON.stringify(k)} src='${pub + k + '/' + f}'></script>`);
140
140
  })
141
141
  .flat().filter(Boolean).join('\n')}
142
- </head>`);
142
+ `);
143
143
  if (isFrontend)
144
144
  ret = ret
145
145
  .replace('<body>', '<body>' + (0, customHtml_1.getSection)('top'))
package/src/update.js CHANGED
@@ -10,9 +10,8 @@ const misc_1 = require("./misc");
10
10
  const fs_1 = require("fs");
11
11
  const plugins_1 = require("./plugins");
12
12
  const promises_1 = require("fs/promises");
13
- const HFS_REPO = 'rejetto/hfs';
14
13
  async function getUpdate() {
15
- const [latest] = await (0, github_1.getRepoInfo)(HFS_REPO + '/releases?per_page=1');
14
+ const [latest] = await (0, github_1.getRepoInfo)(const_1.HFS_REPO + '/releases?per_page=1');
16
15
  if (latest.name === const_1.VERSION)
17
16
  throw "you already have the latest version: " + const_1.VERSION;
18
17
  return latest;
package/src/upload.js CHANGED
@@ -15,14 +15,13 @@ const util_os_1 = require("./util-os");
15
15
  const connections_1 = require("./connections");
16
16
  const throttler_1 = require("./throttler");
17
17
  const lodash_1 = __importDefault(require("lodash"));
18
- exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after', undefined);
18
+ exports.deleteUnfinishedUploadsAfter = (0, config_1.defineConfig)('delete_unfinished_uploads_after', 86400);
19
19
  exports.minAvailableMb = (0, config_1.defineConfig)('min_available_mb', 100);
20
20
  const dontOverwriteUploading = (0, config_1.defineConfig)('dont_overwrite_uploading', false);
21
21
  const waitingToBeDeleted = {};
22
22
  function uploadWriter(base, path, ctx) {
23
- const res = (0, vfs_1.statusCodeForMissingPerm)(base, 'can_upload', ctx);
24
- if (res)
25
- return fail(res);
23
+ if ((0, vfs_1.statusCodeForMissingPerm)(base, 'can_upload', ctx))
24
+ return fail();
26
25
  const fullPath = (0, path_1.join)(base.source, path);
27
26
  const dir = (0, path_1.dirname)(fullPath);
28
27
  const min = exports.minAvailableMb.get() * (1 << 20);
@@ -36,7 +35,7 @@ function uploadWriter(base, path, ctx) {
36
35
  return fail(const_1.HTTP_PAYLOAD_TOO_LARGE);
37
36
  }
38
37
  catch (e) {
39
- console.warn("can't check disk size", e.message || String(e));
38
+ console.warn("can't check disk size:", e.message || String(e));
40
39
  }
41
40
  fs_1.default.mkdirSync(dir, { recursive: true });
42
41
  const keepName = (0, path_1.basename)(fullPath).slice(-200);
@@ -122,7 +121,8 @@ function uploadWriter(base, path, ctx) {
122
121
  delete waitingToBeDeleted[path];
123
122
  }
124
123
  function fail(status) {
125
- ctx.status = status;
124
+ if (status)
125
+ ctx.status = status;
126
126
  (0, frontEndApis_1.notifyClient)(ctx, 'upload.status', { [path]: ctx.status }); // allow browsers to detect failure while still sending body
127
127
  }
128
128
  }
package/src/util-http.js CHANGED
@@ -20,13 +20,15 @@ function httpsString(url, options = {}) {
20
20
  exports.httpsString = httpsString;
21
21
  function httpsStream(url, options = {}) {
22
22
  return new Promise((resolve, reject) => {
23
- node_https_1.default.request(url, options, res => {
23
+ const req = node_https_1.default.request(url, options, res => {
24
24
  if (!res.statusCode || res.statusCode >= 400)
25
- throw res;
25
+ return reject(new Error(String(res.statusCode), { cause: res }));
26
26
  if (res.statusCode === const_1.HTTP_TEMPORARY_REDIRECT && res.headers.location)
27
27
  return resolve(httpsStream(res.headers.location, options));
28
28
  resolve(res);
29
- }).on('error', reject).end();
29
+ }).on('error', e => {
30
+ reject(req.res || e);
31
+ }).end();
30
32
  });
31
33
  }
32
34
  exports.httpsStream = httpsStream;