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.
Files changed (150) hide show
  1. package/admin/assets/AccountsPage-CoiPofEv.js +4 -0
  2. package/admin/assets/ConfigFilePage-jUH2feqi.js +4 -0
  3. package/admin/assets/CustomHtmlPage-Pl_AJZrl.js +2 -0
  4. package/admin/assets/InternetPage-DIcAwhFk.js +6 -0
  5. package/admin/assets/LangPage-BwVY8QRD.js +2 -0
  6. package/admin/assets/LogsPage-CcjS12cP.js +3 -0
  7. package/admin/assets/MonitorPage-DMVFXyMe.js +2 -0
  8. package/admin/assets/OptionsPage-BB40ax9G.js +3 -0
  9. package/admin/assets/PluginsPage-CC2dINcb.js +1 -0
  10. package/admin/assets/icons-C_ux1zLA.js +1 -0
  11. package/admin/assets/index-BSSQOMG_.js +755 -0
  12. package/admin/assets/index-mI9kZlpb.css +1 -0
  13. package/admin/assets/routing-Be72yFmd.js +1 -0
  14. package/admin/index.html +2 -2
  15. package/central.json +100 -1
  16. package/frontend/assets/{dist-legacy-CnYHQXZ6.js → dist-legacy-D7Dg5w3L.js} +1 -1
  17. package/frontend/assets/index-legacy-CnazC3Dj.js +1 -0
  18. package/frontend/assets/{lib-legacy-3xYueNHt.js → lib-legacy-BxFHpdER.js} +1 -1
  19. package/frontend/assets/{sha512-legacy-Cl3f_PhN.js → sha512-legacy-C8xoQBnP.js} +1 -1
  20. package/frontend/index.html +1 -1
  21. package/npm-shrinkwrap.json +9 -9
  22. package/package.json +2 -2
  23. package/plugins/antibrute/plugin.js +62 -15
  24. package/src/acme.js +143 -50
  25. package/src/acmeConfig.js +43 -0
  26. package/src/acmeDns.js +285 -0
  27. package/src/admin-langs/embedded.js +33 -0
  28. package/src/admin-langs/hfs-admin-lang-ar.json +1149 -0
  29. package/src/admin-langs/hfs-admin-lang-bg.json +1149 -0
  30. package/src/admin-langs/hfs-admin-lang-de.json +1149 -0
  31. package/src/admin-langs/hfs-admin-lang-el.json +1149 -0
  32. package/src/admin-langs/hfs-admin-lang-en.json +1149 -0
  33. package/src/admin-langs/hfs-admin-lang-es.json +1149 -0
  34. package/src/admin-langs/hfs-admin-lang-fi.json +1149 -0
  35. package/src/admin-langs/hfs-admin-lang-fr.json +1149 -0
  36. package/src/admin-langs/hfs-admin-lang-hu.json +1149 -0
  37. package/src/admin-langs/hfs-admin-lang-it.json +1149 -0
  38. package/src/admin-langs/hfs-admin-lang-ja.json +1149 -0
  39. package/src/admin-langs/hfs-admin-lang-ko.json +1149 -0
  40. package/src/admin-langs/hfs-admin-lang-lt.json +1149 -0
  41. package/src/admin-langs/hfs-admin-lang-ms.json +1149 -0
  42. package/src/admin-langs/hfs-admin-lang-nl.json +1149 -0
  43. package/src/admin-langs/hfs-admin-lang-pl.json +1149 -0
  44. package/src/admin-langs/hfs-admin-lang-pt-br.json +1149 -0
  45. package/src/admin-langs/hfs-admin-lang-ro.json +1149 -0
  46. package/src/admin-langs/hfs-admin-lang-ru.json +1149 -0
  47. package/src/admin-langs/hfs-admin-lang-sr-latn.json +1149 -0
  48. package/src/admin-langs/hfs-admin-lang-sr.json +1149 -0
  49. package/src/admin-langs/hfs-admin-lang-th.json +1149 -0
  50. package/src/admin-langs/hfs-admin-lang-tr.json +1149 -0
  51. package/src/admin-langs/hfs-admin-lang-uk.json +1149 -0
  52. package/src/admin-langs/hfs-admin-lang-vi.json +1149 -0
  53. package/src/admin-langs/hfs-admin-lang-zh-hant.json +1149 -0
  54. package/src/admin-langs/hfs-admin-lang-zh.json +1149 -0
  55. package/src/adminApis.js +2 -2
  56. package/src/adminLang.js +51 -0
  57. package/src/api.auth.js +1 -1
  58. package/src/api.lang.js +22 -13
  59. package/src/api.log.js +5 -3
  60. package/src/api.net.js +20 -3
  61. package/src/api.plugins.js +53 -19
  62. package/src/api.vfs.js +3 -2
  63. package/src/argv.js +4 -0
  64. package/src/auth.js +13 -9
  65. package/src/basicWeb.js +6 -2
  66. package/src/block.js +1 -1
  67. package/src/config.js +1 -0
  68. package/src/connections.js +4 -7
  69. package/src/consoleLog.js +4 -0
  70. package/src/const.js +1 -1
  71. package/src/cross-const.js +8 -6
  72. package/src/cross.js +16 -2
  73. package/src/customHtml.js +3 -2
  74. package/src/frontEndApis.js +44 -7
  75. package/src/github.js +6 -37
  76. package/src/i18n.js +16 -4
  77. package/src/index.js +4 -3
  78. package/src/lang.js +21 -9
  79. package/src/langs/embedded.js +3 -2
  80. package/src/langs/hfs-lang-ar.json +7 -2
  81. package/src/langs/hfs-lang-bg.json +7 -2
  82. package/src/langs/hfs-lang-de.json +7 -2
  83. package/src/langs/hfs-lang-el.json +7 -2
  84. package/src/langs/hfs-lang-en.json +189 -0
  85. package/src/langs/hfs-lang-es.json +9 -4
  86. package/src/langs/hfs-lang-fi.json +8 -3
  87. package/src/langs/hfs-lang-fr.json +12 -7
  88. package/src/langs/hfs-lang-hu.json +7 -2
  89. package/src/langs/hfs-lang-it.json +11 -5
  90. package/src/langs/hfs-lang-ja.json +7 -2
  91. package/src/langs/hfs-lang-ko.json +7 -2
  92. package/src/langs/hfs-lang-lt.json +7 -2
  93. package/src/langs/hfs-lang-ms.json +9 -2
  94. package/src/langs/hfs-lang-nl.json +7 -2
  95. package/src/langs/hfs-lang-pl.json +7 -2
  96. package/src/langs/hfs-lang-pt-br.json +9 -4
  97. package/src/langs/hfs-lang-ro.json +7 -2
  98. package/src/langs/hfs-lang-ru.json +9 -4
  99. package/src/langs/hfs-lang-sr-latn.json +7 -2
  100. package/src/langs/hfs-lang-sr.json +7 -2
  101. package/src/langs/hfs-lang-th.json +8 -3
  102. package/src/langs/hfs-lang-tr.json +8 -3
  103. package/src/langs/hfs-lang-uk.json +16 -11
  104. package/src/langs/hfs-lang-vi.json +7 -2
  105. package/src/langs/hfs-lang-zh-hant.json +189 -0
  106. package/src/langs/hfs-lang-zh.json +97 -95
  107. package/src/listen.js +6 -3
  108. package/src/log.js +7 -1
  109. package/src/middlewares.js +87 -31
  110. package/src/multipartUpload.js +21 -0
  111. package/src/perm.js +38 -20
  112. package/src/pluginCatalog.js +66 -0
  113. package/src/pluginUsage.js +64 -0
  114. package/src/plugins.js +14 -2
  115. package/src/roots.js +22 -13
  116. package/src/serveFile.js +28 -11
  117. package/src/serveGuiAndSharedFiles.js +6 -6
  118. package/src/serveGuiFiles.js +7 -4
  119. package/src/update.js +51 -18
  120. package/src/upload.js +49 -8
  121. package/src/util-http.js +31 -5
  122. package/src/vfs.js +93 -4
  123. package/src/webdav.js +14 -8
  124. package/admin/assets/AccountsPage-CnZGcUox.js +0 -6
  125. package/admin/assets/ArrayField-F7fpCJIv.js +0 -2
  126. package/admin/assets/ConfigFilePage-CEgZ8Wq4.js +0 -4
  127. package/admin/assets/CustomHtmlPage-DDUNxF1H.js +0 -2
  128. package/admin/assets/DataTable-D_TeYxvk.js +0 -119
  129. package/admin/assets/DateTimeField-CsIdkTqz.js +0 -1
  130. package/admin/assets/EventManager--HiXIAY0.js +0 -1
  131. package/admin/assets/FileField-HsDCdr5S.js +0 -117
  132. package/admin/assets/InternetPage-DDwKrXJH.js +0 -8
  133. package/admin/assets/LangPage-Dd8hi_9K.js +0 -2
  134. package/admin/assets/LogsPage-BcPfugNY.js +0 -4
  135. package/admin/assets/MenuButton-cuehhVig.js +0 -1
  136. package/admin/assets/MonitorPage-Bre9d6MA.js +0 -2
  137. package/admin/assets/OptionsPage-Dd_cRDf4.js +0 -3
  138. package/admin/assets/PluginsPage-DJiseD4G.js +0 -1
  139. package/admin/assets/Tabs-C87E85Fh.js +0 -1
  140. package/admin/assets/TextEditor-BeS-mR2I.css +0 -1
  141. package/admin/assets/TextEditor-Cr0VI9Xi.js +0 -34
  142. package/admin/assets/VfsPathField-D1BkfhAM.js +0 -1
  143. package/admin/assets/agentIcons-CDoUTPUC.js +0 -1
  144. package/admin/assets/baseUrl-CnrV7Huu.js +0 -2
  145. package/admin/assets/icons-DJJYoK9E.js +0 -1
  146. package/admin/assets/index-11n5z0Ez.css +0 -1
  147. package/admin/assets/index-DwXlUHAs.js +0 -477
  148. package/admin/assets/routing-BQpoUdjq.js +0 -1
  149. package/frontend/assets/index-legacy-JYULclbY.js +0 -1
  150. package/src/langs/hfs-lang-zh-tw.json +0 -183
@@ -1,187 +1,189 @@
1
1
  {
2
- "author": "yx3016 & MircoH0 & CMJNB & Cheuksing",
3
- "version": 1.5,
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": "点击复选框选择文件,然后再次使用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} 项?",
21
- "delete_completed": "删除 {n} 项成功!",
22
- "delete_failed": "删除失败,{n,plural, other{# 项失败}}",
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
- "Admin-panel": "管理面板",
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
- "login_bad_cookies": "Cookie错误 - 登录失败",
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": "用户 {u} 无访问权限,请更换用户",
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": "上传{n} 完成 ({size})",
97
- "upload_errors": "上传失败{n} 次",
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
- "showHelp_space_body": "选择",
129
- "showHelp_D_body": "下载",
130
- "showHelp_Z_body": "缩放模式",
131
- "showHelp_F_body": "全屏",
132
- "Destination": "目标位置",
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} 个已移动,{bad} 个失败",
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": "剪贴板 ({content})",
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": "允许会话期间IP变更",
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": "按 ESC 两次关闭",
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)(() => Promise.all([stopServer(httpSrv), stopServer(httpsSrv)]), 5);
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
- || ctx.status === misc_1.HTTP_NOT_FOUND && /wlwmanifest.xml$|\.(php)$|cgi|robots.txt$/.test(ctx.path))) {
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?
@@ -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.headRequests = exports.gzipper = exports.sessionDuration = void 0;
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 = 307; // this ensures the client doesn't switch to a simpler GET request
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, connections_1.normalizeIp)(ctx.ip);
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
- let a = await urlLogin() || await getHttpAccount() || !s?.username && autoLogin();
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.redirect(ctx.originalUrl.slice(0, -ctx.querystring.length - 1)); // redirect to hide credentials
157
- return u && (0, auth_1.clearTextLogin)(ctx, u, p, 'url');
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
- // a cached mask is valid only for the identity that stored it
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
- ctx.state.params = ctx.method === 'POST' && ctx.originalUrl.startsWith(const_1.API_URI)
201
- && ((0, misc_1.tryJson)(await (0, misc_1.stream2string)(ctx.req)) || {});
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;
@@ -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));