hfs 3.3.4 → 3.4.0-alpha2
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/AccountsPage-CoiPofEv.js +4 -0
- package/admin/assets/ConfigFilePage-jUH2feqi.js +4 -0
- package/admin/assets/CustomHtmlPage-Pl_AJZrl.js +2 -0
- package/admin/assets/InternetPage-DIcAwhFk.js +6 -0
- package/admin/assets/LangPage-BwVY8QRD.js +2 -0
- package/admin/assets/LogsPage-CcjS12cP.js +3 -0
- package/admin/assets/MonitorPage-DMVFXyMe.js +2 -0
- package/admin/assets/OptionsPage-BB40ax9G.js +3 -0
- package/admin/assets/PluginsPage-CC2dINcb.js +1 -0
- package/admin/assets/icons-C_ux1zLA.js +1 -0
- package/admin/assets/index-BSSQOMG_.js +755 -0
- package/admin/assets/index-mI9kZlpb.css +1 -0
- package/admin/assets/routing-Be72yFmd.js +1 -0
- package/admin/index.html +2 -2
- package/central.json +100 -1
- package/frontend/assets/{dist-legacy-CnYHQXZ6.js → dist-legacy-D7Dg5w3L.js} +1 -1
- package/frontend/assets/index-legacy-CnazC3Dj.js +1 -0
- package/frontend/assets/{lib-legacy-3xYueNHt.js → lib-legacy-BxFHpdER.js} +1 -1
- package/frontend/assets/{sha512-legacy-Cl3f_PhN.js → sha512-legacy-C8xoQBnP.js} +1 -1
- package/frontend/index.html +1 -1
- package/npm-shrinkwrap.json +9 -9
- package/package.json +2 -2
- package/plugins/antibrute/plugin.js +62 -15
- package/src/acme.js +143 -50
- package/src/acmeConfig.js +43 -0
- package/src/acmeDns.js +285 -0
- package/src/admin-langs/embedded.js +33 -0
- package/src/admin-langs/hfs-admin-lang-ar.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-bg.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-de.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-el.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-en.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-es.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-fi.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-fr.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-hu.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-it.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-ja.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-ko.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-lt.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-ms.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-nl.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-pl.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-pt-br.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-ro.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-ru.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-sr-latn.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-sr.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-th.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-tr.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-uk.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-vi.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-zh-hant.json +1149 -0
- package/src/admin-langs/hfs-admin-lang-zh.json +1149 -0
- package/src/adminApis.js +2 -2
- package/src/adminLang.js +51 -0
- package/src/api.auth.js +1 -1
- package/src/api.lang.js +22 -13
- package/src/api.log.js +5 -3
- package/src/api.net.js +20 -3
- package/src/api.plugins.js +53 -19
- package/src/api.vfs.js +3 -2
- package/src/argv.js +4 -0
- package/src/auth.js +13 -9
- package/src/basicWeb.js +6 -2
- package/src/block.js +1 -1
- package/src/config.js +1 -0
- package/src/connections.js +4 -7
- package/src/consoleLog.js +4 -0
- package/src/const.js +1 -1
- package/src/cross-const.js +8 -6
- package/src/cross.js +16 -2
- package/src/customHtml.js +3 -2
- package/src/frontEndApis.js +44 -7
- package/src/github.js +6 -37
- package/src/i18n.js +16 -4
- package/src/index.js +4 -3
- package/src/lang.js +21 -9
- package/src/langs/embedded.js +3 -2
- package/src/langs/hfs-lang-ar.json +7 -2
- package/src/langs/hfs-lang-bg.json +7 -2
- package/src/langs/hfs-lang-de.json +7 -2
- package/src/langs/hfs-lang-el.json +7 -2
- package/src/langs/hfs-lang-en.json +189 -0
- package/src/langs/hfs-lang-es.json +9 -4
- package/src/langs/hfs-lang-fi.json +8 -3
- package/src/langs/hfs-lang-fr.json +12 -7
- package/src/langs/hfs-lang-hu.json +7 -2
- package/src/langs/hfs-lang-it.json +11 -5
- package/src/langs/hfs-lang-ja.json +7 -2
- package/src/langs/hfs-lang-ko.json +7 -2
- package/src/langs/hfs-lang-lt.json +7 -2
- package/src/langs/hfs-lang-ms.json +9 -2
- package/src/langs/hfs-lang-nl.json +7 -2
- package/src/langs/hfs-lang-pl.json +7 -2
- package/src/langs/hfs-lang-pt-br.json +9 -4
- package/src/langs/hfs-lang-ro.json +7 -2
- package/src/langs/hfs-lang-ru.json +9 -4
- package/src/langs/hfs-lang-sr-latn.json +7 -2
- package/src/langs/hfs-lang-sr.json +7 -2
- package/src/langs/hfs-lang-th.json +8 -3
- package/src/langs/hfs-lang-tr.json +8 -3
- package/src/langs/hfs-lang-uk.json +16 -11
- package/src/langs/hfs-lang-vi.json +7 -2
- package/src/langs/hfs-lang-zh-hant.json +189 -0
- package/src/langs/hfs-lang-zh.json +97 -95
- package/src/listen.js +6 -3
- package/src/log.js +7 -1
- package/src/middlewares.js +87 -31
- package/src/multipartUpload.js +21 -0
- package/src/perm.js +38 -20
- package/src/pluginCatalog.js +66 -0
- package/src/pluginUsage.js +64 -0
- package/src/plugins.js +14 -2
- package/src/roots.js +22 -13
- package/src/serveFile.js +28 -11
- package/src/serveGuiAndSharedFiles.js +6 -6
- package/src/serveGuiFiles.js +7 -4
- package/src/update.js +51 -18
- package/src/upload.js +49 -8
- package/src/util-http.js +31 -5
- package/src/vfs.js +93 -4
- package/src/webdav.js +14 -8
- package/admin/assets/AccountsPage-CnZGcUox.js +0 -6
- package/admin/assets/ArrayField-F7fpCJIv.js +0 -2
- package/admin/assets/ConfigFilePage-CEgZ8Wq4.js +0 -4
- package/admin/assets/CustomHtmlPage-DDUNxF1H.js +0 -2
- package/admin/assets/DataTable-D_TeYxvk.js +0 -119
- package/admin/assets/DateTimeField-CsIdkTqz.js +0 -1
- package/admin/assets/EventManager--HiXIAY0.js +0 -1
- package/admin/assets/FileField-HsDCdr5S.js +0 -117
- package/admin/assets/InternetPage-DDwKrXJH.js +0 -8
- package/admin/assets/LangPage-Dd8hi_9K.js +0 -2
- package/admin/assets/LogsPage-BcPfugNY.js +0 -4
- package/admin/assets/MenuButton-cuehhVig.js +0 -1
- package/admin/assets/MonitorPage-Bre9d6MA.js +0 -2
- package/admin/assets/OptionsPage-Dd_cRDf4.js +0 -3
- package/admin/assets/PluginsPage-DJiseD4G.js +0 -1
- package/admin/assets/Tabs-C87E85Fh.js +0 -1
- package/admin/assets/TextEditor-BeS-mR2I.css +0 -1
- package/admin/assets/TextEditor-Cr0VI9Xi.js +0 -34
- package/admin/assets/VfsPathField-D1BkfhAM.js +0 -1
- package/admin/assets/agentIcons-CDoUTPUC.js +0 -1
- package/admin/assets/baseUrl-CnrV7Huu.js +0 -2
- package/admin/assets/icons-DJJYoK9E.js +0 -1
- package/admin/assets/index-11n5z0Ez.css +0 -1
- package/admin/assets/index-DwXlUHAs.js +0 -477
- package/admin/assets/routing-BQpoUdjq.js +0 -1
- package/frontend/assets/index-legacy-JYULclbY.js +0 -1
- package/src/langs/hfs-lang-zh-tw.json +0 -183
|
@@ -1,187 +1,189 @@
|
|
|
1
1
|
{
|
|
2
|
-
"author": "
|
|
3
|
-
"version": 1.
|
|
4
|
-
"hfs_version": "0.57.5",
|
|
2
|
+
"author": "Hug3O",
|
|
3
|
+
"version": 1.2,
|
|
5
4
|
"translate": {
|
|
6
5
|
"Select": "选择",
|
|
7
|
-
"n_files": "{n}
|
|
8
|
-
"n_folders": "{n}
|
|
9
|
-
"filter_count": "{n}
|
|
10
|
-
"select_count": "{n}
|
|
11
|
-
"filter_placeholder": "
|
|
6
|
+
"n_files": "{n,plural,one{# 文件} other{# 文件}}",
|
|
7
|
+
"n_folders": "{n,plural,one{# 文件夹} other{# 文件夹}}",
|
|
8
|
+
"filter_count": "{n,plural, one{# 个已筛选} other{# 个已筛选}}",
|
|
9
|
+
"select_count": "{n,plural, one{# 个已选择} other{# 个已选择}}",
|
|
10
|
+
"filter_placeholder": "在此输入以筛选下方列表",
|
|
12
11
|
"Select some files": "选择一些文件",
|
|
13
|
-
"zip_checkboxes": "
|
|
14
|
-
"zip_tooltip_selected": "
|
|
15
|
-
"zip_tooltip_whole": "
|
|
16
|
-
"zip_confirm_search": "
|
|
17
|
-
"zip_confirm_folder": "
|
|
18
|
-
"select_tooltip": "
|
|
19
|
-
"delete_hint": "
|
|
20
|
-
"delete_confirm": "
|
|
21
|
-
"delete_completed": "
|
|
22
|
-
"delete_failed": "
|
|
12
|
+
"zip_checkboxes": "使用复选框选择文件,然后可以再次使用 Zip",
|
|
13
|
+
"zip_tooltip_selected": "将选中的元素作为单个 zip 文件下载",
|
|
14
|
+
"zip_tooltip_whole": "将整个列表(未筛选)作为单个 zip 文件下载。如果你选择了一些元素,则只下载那些元素。",
|
|
15
|
+
"zip_confirm_search": "将此搜索的所有结果下载为 ZIP 压缩包?",
|
|
16
|
+
"zip_confirm_folder": "将整个文件夹打包为ZIP再进行下载?",
|
|
17
|
+
"select_tooltip": "选择适用于“Zip”和“删除”(如果可用),但你也可以筛选列表",
|
|
18
|
+
"delete_hint": "要删除,请先点击“选择”",
|
|
19
|
+
"delete_confirm": "删除 {n,plural, one{# 个项目} other{# 个项目}}?",
|
|
20
|
+
"delete_completed": "删除:已完成 {n} 个",
|
|
21
|
+
"delete_failed": ",{n,plural, one{# 个失败} other{# 个失败}}",
|
|
23
22
|
"delete_select": "选择要删除的内容",
|
|
24
23
|
"Delete": "删除",
|
|
25
24
|
"Options": "选项",
|
|
26
25
|
"Search": "搜索",
|
|
27
|
-
"Zip": "
|
|
28
|
-
"search_msg": "
|
|
29
|
-
"Searching": "
|
|
30
|
-
"Searched": "
|
|
31
|
-
"Clear search": "
|
|
32
|
-
"Interrupted": "
|
|
33
|
-
"stopped_before": "
|
|
34
|
-
"empty_list": "
|
|
35
|
-
"filter_none": "
|
|
36
|
-
"
|
|
26
|
+
"Zip": "Zip",
|
|
27
|
+
"search_msg": "搜索此文件夹及子文件夹",
|
|
28
|
+
"Searching": "正在搜索",
|
|
29
|
+
"Searched": "已搜索",
|
|
30
|
+
"Clear search": "清除搜索",
|
|
31
|
+
"Interrupted": "已中断",
|
|
32
|
+
"stopped_before": "在找到任何内容之前已停止",
|
|
33
|
+
"empty_list": "这里什么都没有",
|
|
34
|
+
"filter_none": "没有与此筛选条件匹配的内容",
|
|
35
|
+
"alpha_idx": "字母索引",
|
|
36
|
+
"alpha_idx_button": "AZ",
|
|
37
|
+
"Admin-panel": "后台管理",
|
|
37
38
|
"Login": "登录",
|
|
38
39
|
"Username": "用户名",
|
|
39
40
|
"Password": "密码",
|
|
40
|
-
"login_untrusted": "
|
|
41
|
-
"login_bad_credentials": "
|
|
42
|
-
"
|
|
41
|
+
"login_untrusted": "登录已中止:无法信任服务器身份",
|
|
42
|
+
"login_bad_credentials": "凭据无效",
|
|
43
|
+
"confirm_url_login": "此链接正尝试以“{username}”身份登录你",
|
|
44
|
+
"login_bad_cookies": "Cookie 无法正常工作 - 登录失败",
|
|
43
45
|
"User panel": "用户面板",
|
|
44
46
|
"Change password": "更改密码",
|
|
45
47
|
"enter_pass": "输入新密码",
|
|
46
|
-
"enter_pass2": "
|
|
47
|
-
"pass2_mismatch": "
|
|
48
|
-
"password_changed": "
|
|
49
|
-
"Logout": "
|
|
48
|
+
"enter_pass2": "再次输入相同的新密码",
|
|
49
|
+
"pass2_mismatch": "你输入的第二个密码与第一个不匹配。过程已中止。",
|
|
50
|
+
"password_changed": "密码已更改",
|
|
51
|
+
"Logout": "退出登录",
|
|
50
52
|
"connection error": "连接错误",
|
|
51
53
|
"Full timestamp:": "完整时间戳:",
|
|
52
54
|
"Search was interrupted": "搜索已中断",
|
|
53
55
|
"Stop list": "停止列表",
|
|
54
|
-
"download_starting": "
|
|
55
|
-
"wrong_account": "
|
|
56
|
-
"no_upload_here": "
|
|
56
|
+
"download_starting": "你的下载现在应开始",
|
|
57
|
+
"wrong_account": "账户 {u} 没有访问权限,请尝试其他账户",
|
|
58
|
+
"no_upload_here": "当前文件夹没有上传权限",
|
|
57
59
|
"Create folder": "创建文件夹",
|
|
58
60
|
"Pick files": "选择文件",
|
|
59
61
|
"Pick folder": "选择文件夹",
|
|
60
|
-
"ready_to_upload": "{n,plural,other{# 个文件}},{size},准备上传",
|
|
62
|
+
"ready_to_upload": "{n,plural,one{# 个文件} other{# 个文件}},{size},准备上传",
|
|
61
63
|
"Send": "发送",
|
|
62
64
|
"Clear": "清除",
|
|
63
65
|
"failed_upload": "无法上传 {name}",
|
|
64
|
-
"confirm_resume": "是否重命名?",
|
|
65
66
|
"file too large": "文件过大",
|
|
66
67
|
"Enter folder name": "输入文件夹名称",
|
|
67
68
|
"Successfully created": "创建成功",
|
|
68
|
-
"enter_folder": "
|
|
69
|
-
"folder_exists": "
|
|
70
|
-
"Sort by": "
|
|
71
|
-
"name": "
|
|
72
|
-
"extension": "
|
|
69
|
+
"enter_folder": "输入文件夹",
|
|
70
|
+
"folder_exists": "同名文件夹已存在",
|
|
71
|
+
"Sort by:": "排序:{by}",
|
|
72
|
+
"name": "文件名称",
|
|
73
|
+
"extension": "文件后缀",
|
|
73
74
|
"size": "文件大小",
|
|
74
|
-
"time": "
|
|
75
|
-
"Invert order": "
|
|
76
|
-
"Folders first": "
|
|
75
|
+
"time": "修改时间",
|
|
76
|
+
"Invert order": "倒序排列",
|
|
77
|
+
"Folders first": "文件夹先",
|
|
77
78
|
"Numeric names": "数字名称",
|
|
78
79
|
"theme:": "主题:",
|
|
79
80
|
"auto": "自动",
|
|
80
|
-
"light": "
|
|
81
|
-
"dark": "
|
|
82
|
-
"parent folder": "
|
|
81
|
+
"light": "浅色",
|
|
82
|
+
"dark": "深色",
|
|
83
|
+
"parent folder": "父文件夹",
|
|
83
84
|
"home": "主页",
|
|
84
85
|
"Continue": "继续",
|
|
85
86
|
"Confirm": "确认",
|
|
86
|
-
"Don't": "
|
|
87
|
+
"Don't": "不要",
|
|
87
88
|
"Warning": "警告",
|
|
88
89
|
"Error": "错误",
|
|
89
90
|
"Info": "信息",
|
|
90
91
|
"Unauthorized": "未授权",
|
|
91
|
-
"Forbidden": "
|
|
92
|
+
"Forbidden": "禁止",
|
|
92
93
|
"Not found": "未找到",
|
|
93
94
|
"Server error": "服务器错误",
|
|
94
95
|
"Upload": "上传",
|
|
95
96
|
"upload_concluded": "上传已结束:",
|
|
96
|
-
"upload_finished": "
|
|
97
|
-
"upload_errors": "
|
|
98
|
-
"upload_file_rejected": "
|
|
99
|
-
"delete_unfinished_upload": "
|
|
97
|
+
"upload_finished": "{n} 个已完成({size})",
|
|
98
|
+
"upload_errors": "{n} 个失败",
|
|
99
|
+
"upload_file_rejected": "某些文件未被接受",
|
|
100
|
+
"delete_unfinished_upload": "删除未完成的上传?",
|
|
100
101
|
"File menu": "文件菜单",
|
|
101
102
|
"Folder menu": "文件夹菜单",
|
|
102
103
|
"Name": "名称",
|
|
103
104
|
"file_open": "打开",
|
|
104
105
|
"Download": "下载",
|
|
105
|
-
"Missing permission": "
|
|
106
|
+
"Missing permission": "缺少权限",
|
|
106
107
|
"Reload": "重新加载",
|
|
107
108
|
"Get list": "获取列表",
|
|
108
|
-
"Skip existing files": "
|
|
109
|
+
"Skip existing files": "跳过现有文件",
|
|
109
110
|
"Size": "大小",
|
|
110
|
-
"Timestamp": "
|
|
111
|
-
"Show": "
|
|
111
|
+
"Timestamp": "修改时间",
|
|
112
|
+
"Show": "显示",
|
|
112
113
|
"Loading failed": "加载失败",
|
|
114
|
+
"Download limit reached": "已达到下载数量上限",
|
|
115
|
+
"Retry": "重试",
|
|
113
116
|
"Rename": "重命名",
|
|
114
117
|
"Tiles mode:": "平铺模式:",
|
|
115
118
|
"off": "关闭",
|
|
116
119
|
"Operation successful": "操作成功",
|
|
117
120
|
"Uploader": "上传者",
|
|
118
|
-
"Download counter": "
|
|
121
|
+
"Download counter": "下载计数器",
|
|
119
122
|
"Switch zoom mode": "切换缩放模式",
|
|
120
123
|
"Full screen": "全屏",
|
|
121
124
|
"File Show help": "文件显示帮助",
|
|
122
|
-
"showHelpMain": "
|
|
125
|
+
"showHelpMain": "你可以使用键盘执行一些操作:",
|
|
123
126
|
"showHelp_←/→": "←/→",
|
|
124
127
|
"showHelp_↑/↓": "↑/↓",
|
|
125
128
|
"showHelp_space": "空格",
|
|
126
129
|
"showHelp_←/→_body": "转到上一个/下一个文件",
|
|
127
|
-
"showHelp_↑/↓_body": "
|
|
128
|
-
"
|
|
129
|
-
"
|
|
130
|
-
"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"in_queue": "{n} 个在队列中",
|
|
134
|
-
"enter_comment": "对此文件的评论",
|
|
135
|
-
"Comment": "评论",
|
|
136
|
-
"upload_dd_hint": "您可以将文件拖放到文件列表中进行上传",
|
|
130
|
+
"showHelp_↑/↓_body": "滚动长图片",
|
|
131
|
+
"Destination": "目标",
|
|
132
|
+
"in_queue": "队列中有 {n} 个",
|
|
133
|
+
"enter_comment": "为 {name} 添加注释",
|
|
134
|
+
"Comment": "注释",
|
|
135
|
+
"upload_dd_hint": "你可以通过将文件拖放到文件列表上来上传文件",
|
|
137
136
|
"Upload not available": "上传不可用",
|
|
138
137
|
"Cut": "剪切",
|
|
139
|
-
"n_items": "{n,plural, other{# 个项目}}",
|
|
140
|
-
"move_results": "{good}
|
|
141
|
-
"after_cut": "
|
|
138
|
+
"n_items": "{n,plural, one{# 个项目} other{# 个项目}}",
|
|
139
|
+
"move_results": "{good} 个已移动{bad,plural, =0{} other{,# 个失败}}",
|
|
140
|
+
"after_cut": "你的选择现在已在剪贴板中。\n转到目标文件夹进行粘贴。",
|
|
142
141
|
"Cancel clipboard": "取消剪贴板",
|
|
143
142
|
"to_clipboard_source": "返回源文件夹",
|
|
144
143
|
"Paste": "粘贴",
|
|
145
|
-
"clipboard_list": "
|
|
144
|
+
"clipboard_list": "剪贴板中的项目:",
|
|
146
145
|
"Close": "关闭",
|
|
147
146
|
"Folder": "文件夹",
|
|
148
147
|
"Web page": "网页",
|
|
149
148
|
"Link": "链接",
|
|
150
149
|
"Auto-play": "自动播放",
|
|
151
|
-
"autoplay_seconds": "
|
|
150
|
+
"autoplay_seconds": "图片等待秒数",
|
|
152
151
|
"Select all": "全选",
|
|
153
|
-
"go_first": "
|
|
154
|
-
"go_last": "
|
|
152
|
+
"go_first": "转到第一项",
|
|
153
|
+
"go_last": "转到最后一项",
|
|
155
154
|
"Shuffle": "随机播放",
|
|
156
155
|
"Repeat": "重复",
|
|
157
|
-
"showHelpListShortcut": "在文件列表中,按住 {key}
|
|
156
|
+
"showHelpListShortcut": "在文件列表中,按住 {key} 点击可显示",
|
|
158
157
|
"Invalid value": "无效值",
|
|
159
|
-
"upload_interrupted": "{n}
|
|
158
|
+
"upload_interrupted": "{n} 个已中断",
|
|
160
159
|
"Overwrite policy": "覆盖策略",
|
|
161
160
|
"Rename to avoid overwriting": "重命名以避免覆盖",
|
|
162
|
-
"Overwrite existing files": "
|
|
161
|
+
"Overwrite existing files": "覆盖现有文件",
|
|
163
162
|
"Menu": "菜单",
|
|
164
|
-
"clipboard": "
|
|
165
|
-
"to_clipboard_source_tooltip": "
|
|
163
|
+
"clipboard": "剪贴板({content})",
|
|
164
|
+
"to_clipboard_source_tooltip": "转到剪贴板内容所在的文件夹",
|
|
166
165
|
"more_items": "还有 {n} 个项目",
|
|
167
|
-
"Show details": "
|
|
166
|
+
"Show details": "显示详情",
|
|
168
167
|
"upload_conflict": "已存在",
|
|
169
168
|
"Logged in": "已登录",
|
|
170
|
-
"Logged out": "
|
|
169
|
+
"Logged out": "已退出登录",
|
|
171
170
|
"Cancel": "取消",
|
|
172
|
-
"allow_session_ip_change": "
|
|
173
|
-
"focus_hint": "
|
|
171
|
+
"allow_session_ip_change": "允许此会话期间 IP 变更",
|
|
172
|
+
"focus_hint": "通过在键盘上输入,你可以搜索并聚焦列表中的项目。",
|
|
174
173
|
"copy_links": "复制链接",
|
|
175
|
-
"Calculate": "
|
|
174
|
+
"Calculate": "统计",
|
|
176
175
|
"Creation": "创建时间",
|
|
177
|
-
"creation": "
|
|
178
|
-
"required_change_password": "
|
|
176
|
+
"creation": "创建时间",
|
|
177
|
+
"required_change_password": "你需要更改密码",
|
|
179
178
|
"Wildcards": "通配符",
|
|
180
|
-
"Considering resume": "
|
|
179
|
+
"Considering resume": "考虑恢复",
|
|
181
180
|
"Yes": "是",
|
|
182
181
|
"No": "否",
|
|
183
|
-
"esc_again": "
|
|
182
|
+
"esc_again": "按两次 ESC 关闭",
|
|
184
183
|
"insufficient storage": "存储空间不足",
|
|
185
|
-
"Account disabled": "
|
|
184
|
+
"Account disabled": "账户已停用",
|
|
185
|
+
"upload_name": "使用新名称上传",
|
|
186
|
+
"Uploading": "正在上传",
|
|
187
|
+
"proxy_413": "请检查代理服务器上的此限制"
|
|
186
188
|
}
|
|
187
189
|
}
|
package/src/listen.js
CHANGED
|
@@ -74,7 +74,12 @@ const sessionStamp_1 = require("./sessionStamp");
|
|
|
74
74
|
let httpSrv;
|
|
75
75
|
let httpsSrv;
|
|
76
76
|
// the update relaunch can keep a bridge process alive, so we proactively close listeners here to release ports before the next binary binds; do it before (5) the storage file is closed, because sockets write there
|
|
77
|
-
(0, first_1.onProcessExit)(() =>
|
|
77
|
+
(0, first_1.onProcessExit)(() => {
|
|
78
|
+
const stopped = Promise.all([stopServer(httpSrv), stopServer(httpsSrv)]);
|
|
79
|
+
for (const { socket } of (0, connections_1.getConnections)()) // closeAllConnections misses TCP sockets awaiting TLS and connections upgraded by plugins
|
|
80
|
+
socket.destroy();
|
|
81
|
+
return stopped;
|
|
82
|
+
}, 5);
|
|
78
83
|
const openBrowserAtStart = (0, config_1.defineConfig)(misc_1.CFG.open_browser_at_start, true);
|
|
79
84
|
exports.baseUrl = (0, config_1.defineConfig)(misc_1.CFG.base_url, '', x => /(?<=\/\/)[^\/]+/.exec(x)?.[0]); // compiled is host only
|
|
80
85
|
async function getBaseUrlOrDefault() {
|
|
@@ -303,8 +308,6 @@ function stopServer(srv) {
|
|
|
303
308
|
console.debug("Failed to stop server", String(err));
|
|
304
309
|
resolve(err);
|
|
305
310
|
});
|
|
306
|
-
if (first_1.quitting)
|
|
307
|
-
srv.closeAllConnections();
|
|
308
311
|
});
|
|
309
312
|
}
|
|
310
313
|
function getCurrentPort(srv) {
|
package/src/log.js
CHANGED
|
@@ -48,6 +48,7 @@ const lodash_1 = __importDefault(require("lodash"));
|
|
|
48
48
|
const util_files_1 = require("./util-files");
|
|
49
49
|
const auth_1 = require("./auth");
|
|
50
50
|
const misc_1 = require("./misc");
|
|
51
|
+
const vfs_1 = require("./vfs");
|
|
51
52
|
const path_1 = require("path");
|
|
52
53
|
const yazl_1 = __importDefault(require("yazl"));
|
|
53
54
|
const promises_1 = require("stream/promises");
|
|
@@ -109,6 +110,7 @@ const logRotation = (0, config_1.defineConfig)(misc_1.CFG.log_rotation, 'weekly'
|
|
|
109
110
|
const dontLogNet = (0, config_1.defineConfig)(misc_1.CFG.dont_log_net, '127.0.0.1|::1', v => (0, misc_1.makeNetMatcher)(v));
|
|
110
111
|
const logUA = (0, config_1.defineConfig)(misc_1.CFG.log_ua, false);
|
|
111
112
|
const logSpam = (0, config_1.defineConfig)(misc_1.CFG.log_spam, false);
|
|
113
|
+
const logSpamRegex = (0, config_1.defineConfig)(misc_1.CFG.log_spam_regex, /wlwmanifest.xml$|\.(php|asp)$|cgi|robots.txt$/.source, v => v ? new RegExp(v) : null);
|
|
112
114
|
const logHost = (0, config_1.defineConfig)(misc_1.CFG.log_host, false);
|
|
113
115
|
const debounce = lodash_1.default.debounce(cb => cb(), 1000); // with this technique, i'll be able to debounce some code respecting the references in its closure
|
|
114
116
|
const logMw = async (ctx, next) => {
|
|
@@ -122,7 +124,9 @@ const logMw = async (ctx, next) => {
|
|
|
122
124
|
if (!logSpam.get()
|
|
123
125
|
&& (ctx.querystring.includes('{.exec|') // v2's bug
|
|
124
126
|
// requests generated by security scanners, other bots, and macos' finder
|
|
125
|
-
||
|
|
127
|
+
|| logSpamRegex.compiled()?.test(ctx.path)
|
|
128
|
+
&& (ctx.status === misc_1.HTTP_NOT_FOUND
|
|
129
|
+
|| ctx.status === misc_1.HTTP_TEMPORARY_REDIRECT_KEEP_METHOD && !await (0, vfs_1.urlToNode)(ctx.path, ctx)))) {
|
|
126
130
|
events_2.default.emit('spam', ctx);
|
|
127
131
|
return;
|
|
128
132
|
}
|
|
@@ -140,6 +144,8 @@ const logMw = async (ctx, next) => {
|
|
|
140
144
|
let { stream, last, path } = logger;
|
|
141
145
|
if (!stream)
|
|
142
146
|
return;
|
|
147
|
+
if (events_2.default.emit('beforeLog', { ctx })?.isDefaultPrevented())
|
|
148
|
+
return;
|
|
143
149
|
const rotate = logRotation.get()?.[0];
|
|
144
150
|
const reqEnd = logger.last = new Date();
|
|
145
151
|
if (rotate && last) { // rotation enabled and a file exists?
|
package/src/middlewares.js
CHANGED
|
@@ -4,13 +4,12 @@ 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.sessionMiddleware = exports.paramsDecoder = exports.prepareState = exports.blockFilter = exports.prepareConnection = exports.someSecurity = exports.cloudflareDetected = exports.
|
|
7
|
+
exports.sessionMiddleware = exports.paramsDecoder = exports.prepareState = exports.blockFilter = exports.prepareConnection = exports.someSecurity = exports.cloudflareDetected = exports.gzipper = exports.sessionDuration = void 0;
|
|
8
8
|
exports.getProxyDetected = getProxyDetected;
|
|
9
9
|
exports.failAllowNet = failAllowNet;
|
|
10
10
|
const koa_compress_1 = __importDefault(require("koa-compress"));
|
|
11
11
|
const const_1 = require("./const");
|
|
12
12
|
const misc_1 = require("./misc");
|
|
13
|
-
const stream_1 = require("stream");
|
|
14
13
|
const block_1 = require("./block");
|
|
15
14
|
const perm_1 = require("./perm");
|
|
16
15
|
const connections_1 = require("./connections");
|
|
@@ -24,6 +23,9 @@ const config_1 = require("./config");
|
|
|
24
23
|
const koa_session_1 = __importDefault(require("koa-session"));
|
|
25
24
|
const index_1 = require("./index");
|
|
26
25
|
const events_1 = __importDefault(require("./events"));
|
|
26
|
+
const node_crypto_1 = require("node:crypto");
|
|
27
|
+
const lang_1 = require("./lang");
|
|
28
|
+
const i18n_1 = require("./i18n");
|
|
27
29
|
const forceHttps = (0, config_1.defineConfig)(misc_1.CFG.force_https, true);
|
|
28
30
|
(0, config_1.defineConfig)(misc_1.CFG.ignore_proxies, false);
|
|
29
31
|
const allowAuthorizationHeader = (0, config_1.defineConfig)(misc_1.CFG.authorization_header, true);
|
|
@@ -38,21 +40,6 @@ exports.gzipper = (0, koa_compress_1.default)({
|
|
|
38
40
|
return /text|javascript|style/i.test(type);
|
|
39
41
|
},
|
|
40
42
|
});
|
|
41
|
-
const headRequests = async (ctx, next) => {
|
|
42
|
-
const head = ctx.method === 'HEAD';
|
|
43
|
-
if (head)
|
|
44
|
-
ctx.method = 'GET'; // let other middlewares work, so we can collect the size at the end
|
|
45
|
-
await next();
|
|
46
|
-
if (!head || ctx.body === undefined)
|
|
47
|
-
return;
|
|
48
|
-
const { length, status } = ctx.response;
|
|
49
|
-
if (ctx.body)
|
|
50
|
-
ctx.body = stream_1.Readable.from(''); // empty the body for this is a HEAD request. Using Readable avoids koa from trying to set length to 0
|
|
51
|
-
ctx.status = status;
|
|
52
|
-
if (length)
|
|
53
|
-
ctx.response.length = length;
|
|
54
|
-
};
|
|
55
|
-
exports.headRequests = headRequests;
|
|
56
43
|
let proxyDetected;
|
|
57
44
|
const someSecurity = (ctx, next) => {
|
|
58
45
|
enforceSessionIp(ctx);
|
|
@@ -65,15 +52,53 @@ const someSecurity = (ctx, next) => {
|
|
|
65
52
|
const { URL } = ctx;
|
|
66
53
|
URL.protocol = 'https';
|
|
67
54
|
URL.port = (0, listen_1.getHttpsWorkingPort)();
|
|
68
|
-
ctx.status =
|
|
55
|
+
ctx.status = const_1.HTTP_TEMPORARY_REDIRECT_KEEP_METHOD; // this ensures the client doesn't switch to a simpler GET request
|
|
69
56
|
return ctx.redirect(URL.href);
|
|
70
57
|
}
|
|
58
|
+
const page = ctx.state.urlLoginPage;
|
|
59
|
+
if (page)
|
|
60
|
+
return sendUrlLoginPage(page.cleanUrl, page.username);
|
|
71
61
|
return next();
|
|
62
|
+
function sendUrlLoginPage(cleanUrl, username) {
|
|
63
|
+
return sendPage(ctx, username === undefined ? 'login_bad_credentials' : 'Confirm', t => {
|
|
64
|
+
if (username === undefined)
|
|
65
|
+
return `<script>history.replaceState(null,'',location.pathname)</script>
|
|
66
|
+
<p><a href="${(0, misc_1.escapeHTML)(cleanUrl)}">${(0, misc_1.escapeHTML)(t('Continue'))}</a></p>`;
|
|
67
|
+
const token = (0, node_crypto_1.randomUUID)();
|
|
68
|
+
(ctx.session ??= {}).urlLoginConfirmation = {
|
|
69
|
+
token,
|
|
70
|
+
username,
|
|
71
|
+
expires: Date.now() + 5 * 60_000,
|
|
72
|
+
};
|
|
73
|
+
return `<p>${(0, misc_1.escapeHTML)(t('confirm_url_login', { username }))}</p>
|
|
74
|
+
<p><strong><a class="confirm" href="${(0, misc_1.escapeHTML)(cleanUrl)}">${(0, misc_1.escapeHTML)(t('Continue'))}</a></strong>
|
|
75
|
+
<a href="${(0, misc_1.escapeHTML)(cleanUrl)}">${(0, misc_1.escapeHTML)(t('Cancel'))}</a></p>
|
|
76
|
+
<script>
|
|
77
|
+
const loginUrl=new URL(location.href);
|
|
78
|
+
history.replaceState(null,'',loginUrl.pathname);
|
|
79
|
+
document.querySelector('.confirm').onclick=ev=>{ev.preventDefault();loginUrl.searchParams.set('login_confirm','${token}');location.replace(loginUrl)}
|
|
80
|
+
</script>`;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
72
83
|
};
|
|
73
84
|
exports.someSecurity = someSecurity;
|
|
85
|
+
async function sendPage(ctx, titleKey, content) {
|
|
86
|
+
const { t } = (0, i18n_1.i18nFromTranslations)(await (0, lang_1.getLangData)(ctx));
|
|
87
|
+
const title = t(titleKey);
|
|
88
|
+
ctx.status = 200;
|
|
89
|
+
ctx.type = 'html';
|
|
90
|
+
ctx.set('cache-control', 'no-store');
|
|
91
|
+
ctx.set('referrer-policy', 'no-referrer');
|
|
92
|
+
ctx.set('content-security-policy', "frame-ancestors 'none'");
|
|
93
|
+
ctx.set('x-frame-options', 'DENY');
|
|
94
|
+
ctx.body = `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
95
|
+
<title>${(0, misc_1.escapeHTML)(title)}</title><style>
|
|
96
|
+
body{margin-top:30vh;text-align:center;font-family:sans-serif}a{margin:1em}
|
|
97
|
+
</style></head><body><h1>${(0, misc_1.escapeHTML)(title)}</h1>${content(t)}</body></html>`;
|
|
98
|
+
}
|
|
74
99
|
const prepareConnection = (ctx, next) => {
|
|
75
100
|
// normalize once so auth, filters and logging agree on the same client address
|
|
76
|
-
ctx.request.ip = (0,
|
|
101
|
+
ctx.request.ip = (0, misc_1.normalizeIp)(ctx.ip);
|
|
77
102
|
ctx.state.connection = (0, connections_1.socket2connection)(ctx.socket);
|
|
78
103
|
(0, connections_1.updateConnectionForCtx)(ctx);
|
|
79
104
|
return next();
|
|
@@ -99,6 +124,7 @@ function getProxyDetected() {
|
|
|
99
124
|
return proxyDetected && { from: proxyDetected.socket.remoteAddress, for: proxyDetected.get('X-Forwarded-For') };
|
|
100
125
|
}
|
|
101
126
|
const prepareState = async (ctx, next) => {
|
|
127
|
+
ctx.state.getOrHead = ctx.method === 'GET' || ctx.method === 'HEAD';
|
|
102
128
|
// invalidate before account resolution; someSecurity calls again to bind logins made below
|
|
103
129
|
enforceSessionIp(ctx);
|
|
104
130
|
// rootsMiddleware consults proxy-aware admin access before someSecurity runs
|
|
@@ -125,7 +151,9 @@ const prepareState = async (ctx, next) => {
|
|
|
125
151
|
}
|
|
126
152
|
// explicit credentials and existing sessions must take precedence, so a matching IP cannot override a chosen account
|
|
127
153
|
let via;
|
|
128
|
-
|
|
154
|
+
const urlAccount = await urlLogin();
|
|
155
|
+
let a = ctx.state.urlLoginPage ? undefined
|
|
156
|
+
: urlAccount || await getHttpAccount() || !s?.username && autoLogin();
|
|
129
157
|
const loggedInNotBySession = a;
|
|
130
158
|
ctx.state.account = a ||= (0, perm_1.getAccount)(s?.username, false); // with least precedence, we consider session
|
|
131
159
|
if (a)
|
|
@@ -147,14 +175,43 @@ const prepareState = async (ctx, next) => {
|
|
|
147
175
|
ctx.state.revProxyPath = ctx.get('x-forwarded-prefix');
|
|
148
176
|
(0, connections_1.updateConnectionForCtx)(ctx); // publish the resolved account after the initial pre-auth connection update
|
|
149
177
|
await next();
|
|
150
|
-
function urlLogin() {
|
|
178
|
+
async function urlLogin() {
|
|
151
179
|
via = 'url';
|
|
152
180
|
const { login } = ctx.query;
|
|
153
181
|
if (!login)
|
|
154
182
|
return;
|
|
155
183
|
const [u, p] = (0, misc_1.splitAt)(':', String(login));
|
|
156
|
-
ctx.
|
|
157
|
-
|
|
184
|
+
const cleanUrl = ctx.originalUrl.slice(0, -ctx.querystring.length - 1);
|
|
185
|
+
const token = String(ctx.query.login_confirm || '');
|
|
186
|
+
const pending = s?.urlLoginConfirmation;
|
|
187
|
+
const confirmed = Boolean(token && pending?.token === token && pending.username === u
|
|
188
|
+
&& pending.expires > Date.now());
|
|
189
|
+
if (token && s)
|
|
190
|
+
delete s.urlLoginConfirmation; // confirmation tokens are single-use, including failed attempts
|
|
191
|
+
if (!confirmed && needsConfirmation(u)) {
|
|
192
|
+
ctx.state.urlLoginPage = { username: u, cleanUrl };
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (s)
|
|
196
|
+
delete s.urlLoginConfirmation;
|
|
197
|
+
const account = u && await (0, auth_1.clearTextLogin)(ctx, u, p, 'url');
|
|
198
|
+
if (!account && confirmed)
|
|
199
|
+
ctx.state.urlLoginPage = { cleanUrl };
|
|
200
|
+
else
|
|
201
|
+
ctx.redirect(cleanUrl); // redirect to hide credentials
|
|
202
|
+
return account;
|
|
203
|
+
function needsConfirmation(username) {
|
|
204
|
+
if (ctx.get('sec-fetch-mode') !== 'navigate' && !/^Mozilla\//.test(ctx.get('user-agent')))
|
|
205
|
+
return false;
|
|
206
|
+
const current = s?.username;
|
|
207
|
+
if (current)
|
|
208
|
+
return (0, perm_1.getAccount)(username)?.username !== (0, perm_1.getAccount)(current)?.username;
|
|
209
|
+
const fetchSite = ctx.get('sec-fetch-site');
|
|
210
|
+
if (fetchSite)
|
|
211
|
+
return fetchSite === 'same-site' || fetchSite === 'cross-site';
|
|
212
|
+
const referer = ctx.get('referer');
|
|
213
|
+
return !referer || (0, misc_1.try_)(() => new URL(referer).host.toLowerCase() !== ctx.host.toLowerCase(), () => true);
|
|
214
|
+
}
|
|
158
215
|
}
|
|
159
216
|
function getHttpAccount() {
|
|
160
217
|
via = 'header';
|
|
@@ -185,20 +242,19 @@ const prepareState = async (ctx, next) => {
|
|
|
185
242
|
};
|
|
186
243
|
exports.prepareState = prepareState;
|
|
187
244
|
function failAllowNet(ctx, a) {
|
|
188
|
-
|
|
189
|
-
const sameAccount = ctx.session?.username === (0, perm_1.normalizeUsername)(a?.username || '');
|
|
190
|
-
const cached = sameAccount ? ctx.session?.allowNet : undefined;
|
|
191
|
-
const mask = cached ?? (0, perm_1.getFromAccount)(a || '', a => a.allow_net);
|
|
192
|
-
if (sameAccount && !cached && mask)
|
|
193
|
-
ctx.session.allowNet = mask; // must be deleted on logout by setLoggedIn
|
|
245
|
+
const mask = (0, perm_1.getFromAccount)(a || '', a => a.allow_net);
|
|
194
246
|
const ret = mask && !(0, misc_1.netMatches)(ctx.ip, mask, true);
|
|
195
247
|
if (ret)
|
|
196
248
|
console.debug("Login failed: allow_net");
|
|
197
249
|
return ret;
|
|
198
250
|
}
|
|
199
251
|
const paramsDecoder = async (ctx, next) => {
|
|
200
|
-
|
|
201
|
-
|
|
252
|
+
if (ctx.method !== 'POST' || !ctx.originalUrl.startsWith(const_1.API_URI))
|
|
253
|
+
return next();
|
|
254
|
+
const body = await (0, misc_1.readRequestBodyLimited)(ctx, 10 * 1024 * 1024);
|
|
255
|
+
if (!body)
|
|
256
|
+
return;
|
|
257
|
+
ctx.state.params = (0, misc_1.tryJson)(body.text) || {};
|
|
202
258
|
await next();
|
|
203
259
|
};
|
|
204
260
|
exports.paramsDecoder = paramsDecoder;
|
package/src/multipartUpload.js
CHANGED
|
@@ -11,9 +11,15 @@ const upload_1 = require("./upload");
|
|
|
11
11
|
const cross_const_1 = require("./cross-const");
|
|
12
12
|
const first_1 = require("./first");
|
|
13
13
|
const cross_1 = require("./cross");
|
|
14
|
+
const config_1 = require("./config");
|
|
14
15
|
async function handleMultipartUpload(ctx, node) {
|
|
15
16
|
if (ctx.request.type !== 'multipart/form-data')
|
|
16
17
|
return ctx.status = cross_const_1.HTTP_BAD_REQUEST;
|
|
18
|
+
if (isCrossOriginBrowserRequest()) {
|
|
19
|
+
ctx.set('Connection', 'close'); // the rejected multipart body is intentionally left unread
|
|
20
|
+
ctx.body = "Cross-origin multipart upload blocked";
|
|
21
|
+
return ctx.status = cross_const_1.HTTP_FOOL;
|
|
22
|
+
}
|
|
17
23
|
ctx.state.uploads = [];
|
|
18
24
|
const locks = [];
|
|
19
25
|
const fileJobs = [];
|
|
@@ -73,6 +79,20 @@ async function handleMultipartUpload(ctx, node) {
|
|
|
73
79
|
stream.resume();
|
|
74
80
|
return new Promise(res => (0, first_1.onFirstEvent)(stream, ['end', 'close', 'error'], res));
|
|
75
81
|
}
|
|
82
|
+
function isCrossOriginBrowserRequest() {
|
|
83
|
+
if (ctx.get('x-hfs-anti-csrf'))
|
|
84
|
+
return false;
|
|
85
|
+
const origin = ctx.get('origin');
|
|
86
|
+
if (origin && origin !== 'null' && allowedUploadOrigin.compiled()(origin))
|
|
87
|
+
return false;
|
|
88
|
+
const fetchSite = ctx.get('sec-fetch-site');
|
|
89
|
+
if (fetchSite)
|
|
90
|
+
return fetchSite !== 'same-origin' && fetchSite !== 'none';
|
|
91
|
+
if (!origin)
|
|
92
|
+
return false; // missing browser metadata preserves non-browser and legacy upload clients
|
|
93
|
+
// compare hosts because TLS termination can make the public and internal protocols differ
|
|
94
|
+
return (0, cross_1.try_)(() => new URL(origin).host.toLowerCase() !== ctx.host.toLowerCase(), () => true);
|
|
95
|
+
}
|
|
76
96
|
async function subfolderBlocksUpload(fn) {
|
|
77
97
|
const prefix = (0, path_1.dirname)(fn.replaceAll('\\', '/'));
|
|
78
98
|
if (prefix === '.') // no subdir
|
|
@@ -81,3 +101,4 @@ async function handleMultipartUpload(ctx, node) {
|
|
|
81
101
|
return subfolderNode && !(0, vfs_1.hasPermission)(subfolderNode, 'can_upload', ctx);
|
|
82
102
|
}
|
|
83
103
|
}
|
|
104
|
+
const allowedUploadOrigin = (0, config_1.defineConfig)('allowed_upload_origin', '', mask => (0, cross_1.makeMatcher)(mask, false, false));
|