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.
- package/README.md +50 -9
- package/admin/assets/index-35f6e3dc.css +1 -0
- package/admin/assets/index-ef68a7ab.js +510 -0
- package/{frontend/assets/sha512-8ebf6e2a.js → admin/assets/sha512-d091720e.js} +1 -1
- package/admin/index.html +2 -2
- package/frontend/assets/index-a3b0d6ac.js +94 -0
- package/frontend/assets/index-fe0f3d77.css +1 -0
- package/{admin/assets/sha512-55ff2fa3.js → frontend/assets/sha512-96fd938f.js} +1 -1
- package/frontend/index.html +3 -2
- package/package.json +2 -2
- package/plugins/download-counter/plugin.js +5 -3
- package/plugins/download-counter/public/main.js +2 -2
- package/plugins/vhosting/plugin.js +17 -11
- package/src/adminApis.js +1 -1
- package/src/api.auth.js +4 -1
- package/src/api.file_list.js +7 -8
- package/src/api.lang.js +2 -1
- package/src/api.vfs.js +41 -28
- package/src/apiMiddleware.js +3 -2
- package/src/const.js +4 -3
- package/src/customHtml.js +1 -1
- package/src/debounceAsync.js +3 -3
- package/src/frontEndApis.js +6 -6
- package/src/github.js +26 -8
- package/src/index.js +2 -1
- package/src/lang.js +9 -19
- package/src/langs/embedded.js +13 -0
- package/src/langs/hfs-lang-ms.json +70 -0
- package/src/langs/hfs-lang-ru.json +22 -22
- package/src/langs/hfs-lang-zh-tw.json +106 -0
- package/src/listen.js +1 -1
- package/src/log.js +6 -2
- package/src/middlewares.js +14 -18
- package/src/misc.js +7 -3
- package/src/plugins.js +6 -6
- package/src/serveFile.js +1 -1
- package/src/serveGuiFiles.js +2 -2
- package/src/update.js +1 -2
- package/src/upload.js +6 -6
- package/src/util-http.js +5 -3
- package/src/vfs.js +100 -52
- package/src/zip.js +4 -4
- package/admin/assets/index-5cd667a5.js +0 -511
- package/admin/assets/index-8ff39373.css +0 -1
- package/frontend/assets/index-27488fde.js +0 -94
- 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.
|
|
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
|
|
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
|
|
21
|
-
"delete_completed": "{n,plural, one{Удалён} other{Удалено}} {n,plural, one{# элемент} few
|
|
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": "
|
|
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{
|
|
96
|
-
"upload_errors": "
|
|
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
|
-
|
|
139
|
-
debugLogFile.write(new Date().toLocaleString() + ': ' +
|
|
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"));
|
package/src/middlewares.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
//
|
|
159
|
+
// limited to http proxies
|
|
167
160
|
function getProxyDetected() {
|
|
168
|
-
|
|
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)('(' +
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
326
|
-
|
|
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
|
-
|
|
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.
|
|
36
|
+
if ('dl' in ctx.query) // please, download
|
|
37
37
|
ctx.attachment(name);
|
|
38
38
|
return serveFile(ctx, source || '', mimeString);
|
|
39
39
|
}
|
package/src/serveGuiFiles.js
CHANGED
|
@@ -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('
|
|
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
|
-
|
|
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',
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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',
|
|
29
|
+
}).on('error', e => {
|
|
30
|
+
reject(req.res || e);
|
|
31
|
+
}).end();
|
|
30
32
|
});
|
|
31
33
|
}
|
|
32
34
|
exports.httpsStream = httpsStream;
|