移动设备改进

This commit is contained in:
2026-04-05 05:11:10 +08:00
parent 3206519910
commit 91a71dd6a5
4 changed files with 125 additions and 67 deletions

View File

@@ -7,60 +7,72 @@ AFI 是 Go 编写的轻量级文件服务器, 是基于 Gossa 的现代化增强
特色: 特色:
- 后端代码量低, 易于审查; 我们(虽然目前只有一个维护者 但是还是称"我们" :))在 Gossa 的基础上进行了大量重构, 大幅增强了前后端的可维护性并提升了效率 - 后端代码量低, 易于审查; 我们(虽然目前只有一个维护者 但是还是称"我们" :))在 Gossa 的基础上进行了大量重构, 大幅增强了前后端的可维护性并提升了效率
- 清晰现代且轻量的网页界面, 比原版 Gossa 的信息量更高且更悦目, 相较于 FileBrowser, AFI 易于嵌入至 `<iframe>`不依赖任何 web 框架, 操作无动画不拖泥带水, 外部资源引用 (显然我们的前端不会引用 is-odd 和 is-even, 也不会因 left-pad 而崩溃 :D) - 清晰现代且轻量的网页界面, 比原版 Gossa 的信息量更高且更悦目, 不依赖任何 web 框架, 操作无动画不拖泥带水, 除字体外无任何外部资源引用 (显然我们的前端不会引用 is-odd 和 is-even, 也不会因 left-pad 而崩溃 :D)
- 相较于 FileBrowser, AFI 易于嵌入至 `<iframe>` 且速度极快, 您可以将它用作图床或美观地嵌入到自己的博客附件页
- 单个可执行文件, 无需数据库, 速度极快, 方便快速部署 - 单个可执行文件, 无需数据库, 速度极快, 方便快速部署
- 与 FUSE 文件系统兼容 (如 rclone 实例配置的网络挂载) - 与 FUSE 文件系统兼容 (如 rclone 实例配置的网络挂载)
- 支持拖拽上传文件和目录, 并以归档(.zip)形式方便地下载任意目录 - 支持拖拽上传文件和目录, 并以归档(.zip)形式方便地下载任意目录
- 支持 MD5, SHA-1, SHA-256, SHA-512 文件校验和的远程解算 - 支持 MD5, SHA-1, SHA-256, SHA-512 文件校验和的远程解算
- 支持通过 `DESCRIPT.ION` 文件存储的备注元信息 - 支持通过 `DESCRIPT.ION` 文件存储的备注元信息
- 完备的键盘操作支持 - 完备的键盘操作支持
- 基本的用户管理系统 (基于 HTTP Basic Authication), 我们移除了 Gossa 单一的只读/可写模式 (即 -ro 参数), 并保留了未登录只读模式 - 基本的用户管理系统 (基于 HTTP Basic Authication), 我们移除了 Gossa 单一的只读/可写模式 (即 -ro 参数), 并保留了未登录状态下的默认只读模式
- 支持跳过隐藏项目 (以 `.` 开头的文件/目录) - 支持跳过隐藏项目 (以 `.` 开头的文件/目录)
- 开发/生产模式分离, 便于免编译重载前端资源 - 开发/生产模式分离, 便于免编译重载前端资源
- 同时提供 RPC 调用接口便于编程与 webdav 接口便于挂载, 网页用户界面的 JavaScript 使用前者 - 同时提供简易的 RPC 调用接口便于编程与 webdav 接口(TODO)便于挂载, 网页用户界面的 JavaScript 使用前者
- 低占用高性能, 网页用户界面每秒可承受万级以上响应并持续保持毫秒级低延迟, 接口性能更高 - 低占用高性能, 网页用户界面每秒可承受万级以上响应并持续保持毫秒级低延迟, 接口性能更高
- 默认启用 gzip 压缩, 并为 iframe 嵌入优化 - 默认启用 gzip 压缩, 并为 iframe 嵌入优化
- 性能高效, 提供真正的毫秒级响应, 用户使用体验不让位于技术审美与性能 - 性能高效, 提供毫秒级响应, 用户使用体验不让位于技术审美与性能
- 改进的安全设计, 相较原项目增加了对 XSS 等攻击的防护
## 安全与不安全设计 ## 安全与不安全设计
考虑到此程序可能被使用的场景与便于方案选型, 笔者必须指出此程序存在的问题 考虑到此程序可能被使用的场景与便于方案选型, 笔者必须指出此程序存在的问题, 以免您产生虚假的安全感
您发现了其他可能的漏洞, 请在 issues 指出, 若属实, 笔者会将其修复或加入下方列表 果您在为安全性抉择是否使用此项目, 我们高度建议您不要使用它
如您发现了其他可能的漏洞, 请在 issues 指出, 若属实, 笔者会将其修复或加入下方列表, 并且将您的名字加入致谢名单
注意: 请不要把"文档写出来了"等同于"只有这些问题"(我们当然追求安全审计文档的完善度)
安全建议: 安全建议:
- 强烈建议配置一个 nginx 反向代理 - 强烈建议配置一个 nginx 反向代理并向外部阻断应用本身的端口
- 必要时设置超时, 限制请求大小与频率 - 必要时设置超时, 限制请求大小与频率
- 完全避免使用 HTTP - 如果可以的话, 最好套一层 WAF
- 配置好 SSL 证书, 完全避免使用 HTTP
- 必要时设置磁盘配额 - 必要时设置磁盘配额
- 一定不要把分享目录设置为用户目录或根目录
- 使用环境变量 `AFI_AUTH` 而不是参数设置用户验证键值对
- 使用普通用户, chroot 或容器运行
- 安全设计: - 安全设计:
- 可防止路径穿越攻击 - 可防止路径穿越攻击
- 有基本的权限管理系统 (基于 HTTP Basic Authication) - 有基本的权限管理系统 (基于 HTTP Basic Authentication)
- 不安全设计: - 不安全设计:
- 不能完全防范的攻击 - 不能防范且无法容忍的攻击
- XSS (不可防范) - 如上文所述, 这里没写不等于没有, 欢迎通过 issues 提出
- Timing Attack (不可防范, 但可通过设置较复杂的用户名规避) - 未防范但几乎不可能成功的攻击
- Timing Attack (未防范, 密码在后端明文比较, 但可通过设置较不寻常的用户名规避) -> 我们未来大概率不会改
- 能防范的攻击 - 能防范的攻击
- symlink 逃逸攻击 (可选且默认开启, 但开启后您就不能使用 linux 的符号链接便利了) - XSS 攻击
- Symlink 逃逸攻击 (可选且默认开启, 但开启后您就不能使用 linux 的符号链接便利了)
- 路径穿越攻击 - 路径穿越攻击
- CSRF (HTTP Basic Authication 天然防范) - CSRF 攻击 (HTTP Basic Authentication 天然防范)
- 其他不安全设计: - 其他不安全设计:
- 密码以明文形式传输 (因此千万不要在非 HTTPS 环境公网部署或预留 HTTP 访问, 除非您完全不配置用户(相当于原版 Gossa 的只读模式)) - 密码以明文形式传输 (因此千万不要在非 HTTPS 环境公网部署或预留 HTTP 访问, 除非您完全不配置用户(相当于原版 Gossa 的只读模式)) -> 我们未来会改, 但会在增加 Argon2 的基础上保留明文与 MD5 模式
- 后端密码参数化 (可登录服务器后用 ps 或类似工具查看) - 后端密码参数化 (可登录服务器后轻松用 ps 或类似工具查看), 已经可以环境变量安全设置, 但不会移除 `-auth` 选项以防您需要测试或使用 PaaS 部署
- 日志可能泄漏敏感信息 (可以通过修改源代码编译解决, 但我们提供的二进制释出没有移除敏感信息输出)
- DoS 攻击 - 我们认为这件事应该由 nginx 反向代理预防 - DoS 攻击 - 我们认为这件事应该由 nginx 反向代理预防
- 请求大小无限制导致的超大文件占用和可能导致的 OOM 崩溃 - 我们认为这件事应该由 nginx 反向代理或磁盘配额限制预防, 并且就事实而言, 这很大程度上是有权限的用户行为不当引起的问题 - 请求大小和配额无限制导致的超大文件占用和可能导致的 OOM 崩溃 - 我们认为这件事应该由 nginx 反向代理或磁盘配额限制预防, 并且就事实而言, 内部威胁不在模型内, 这很大程度上是有权限的用户行为不当引起的问题
- 外部字体 Referer 泄露 (仅会得知您访问的链接, 但如果您需要, 可以移除前端中引用的在线字体链接) 就事实而言, 外部字体提供商没有收集这种数据的动机, 但如果您在内网部署, 则您可能不希望任何外部服务知道内网的文件结构
- 无自带的响应超时: 可能招致 Slowloris 服务瘫痪 - 但这可由 nginx 反向代理预防 - 无自带的响应超时: 可能招致 Slowloris 服务瘫痪 - 但这可由 nginx 反向代理预防
- 权限提升风险: AFI 以什么用户运行, 就有什么权限 -- 如果以 root 运行且将分享目录设置为根目录, 则任何登录用户都能删除系统 - 权限提升风险: AFI 以什么用户运行, 就有什么权限 -- 如果以 root 运行且将分享目录设置为根目录, 则任何登录用户都能删除系统, 或者通过上传 `.ssh/authorized_keys``.bashrc` 远程控制您的服务器操作系统 -> 我们改不了
- 未默认杜绝的隐私问题:
- 外部字体 Referer 泄露 (仅会得知您访问的链接, 但如果您需要, 可以移除前端中引用的在线字体链接) 就事实而言, 我们认为外部字体提供商不太可能有收集这种数据的动机, 但如果您在高敏感内网部署, 则您可能不希望任何外部服务能够得知内网的文件结构
- 日志可能泄漏敏感信息 (可以通过修改源代码编译解决, 但我们提供的二进制释出没有移除敏感信息输出)
- 未对特定安全需求进行的设计: - 未对特定安全需求进行的设计:
- 不自带 https 服务器 - 如果您在公网直接跑这个程序并且使用它, 您等同于裸奔
- 不支持细粒度权限控制 (我们支持配置多个用户, 但登录后您就只能是对分享目录下的文件完全可写(取决于运行 afi 的用户)的状态了) - 不支持细粒度权限控制 (我们支持配置多个用户, 但登录后您就只能是对分享目录下的文件完全可写(取决于运行 afi 的用户)的状态了)
- 没有自带的防暴力破解机制 - 没有自带的防暴力破解机制
- 没有操作审计日志 - 没有操作审计日志
- 没有密码哈希存储 (后端明文比较) - 没有密码哈希存储 (后端明文比较)
- 可能被误解为是不恰当的设计 - 可能被误解为是不恰当的设计
- 代码中不恰当的注释, 但这些注释(html, js 和 css 中的)事实上会被 go 的模板系统清除(即使有 "dev" tag), 因此您不必担心这些注释会随页面传递给浏览器, 在"查看源代码页"呈现给用户 - 代码中不恰当的注释, 但这些注释(html, js 和 css 中的)事实上会被 go 的 embed 清除(即使有 "dev" tag), 因此您不必担心这些注释会随页面传递给浏览器, 在"查看源代码页"呈现给用户
- 注意: 这些不安全设计并非都是运维或服务器管理员能解决的, 因此请量需求而行 - 注意: 这些不安全设计并非都是运维或服务器管理员能解决的, 因此请量需求而行
## 使用方法 ## 使用方法
@@ -109,7 +121,7 @@ QPS Performace from test:
Server QPS Data QPS Bar (higher is better) Gzip Page Server QPS Data QPS Bar (higher is better) Gzip Page
Nginx 60,242 QPS ████████████████████████████████ Disabled Default Index Nginx 60,242 QPS ████████████████████████████████ Disabled Default Index
Apache 34,207 QPS █████████████████░░░░░░░░░░░░░░░ Disabled Default Index Apache 34,207 QPS █████████████████░░░░░░░░░░░░░░░ Disabled Default Index
AFI 15,262 QPS ████████░░░░░░░░░░░░░░░░░░░░░░░░ BestSpeed Full-functional web file manager (without CSS & JS embeded in bench) (net/http) AFI 15,262 QPS ████████░░░░░░░░░░░░░░░░░░░░░░░░ BestSpeed Full-functional web file manager (without CSS & JS embedded in bench) (net/http)
PyPy 4,169 QPS ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ Disabled Default Index (http.server) PyPy 4,169 QPS ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ Disabled Default Index (http.server)
CPython 2,128 QPS █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ Disabled Default Index (http.server) CPython 2,128 QPS █░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ Disabled Default Index (http.server)

22
afi.go
View File

@@ -31,6 +31,8 @@ import (
"time" "time"
) )
const ver = "26.04.03"
// rowTemplate 定义文件列表中的每一行数据结构 // rowTemplate 定义文件列表中的每一行数据结构
type rowTemplate struct { type rowTemplate struct {
Name string // 文件/文件夹名称 Name string // 文件/文件夹名称
@@ -56,12 +58,12 @@ type pageTemplate struct {
var host = flag.String("h", "127.0.0.1", "监听的主机地址") // 监听的主机地址 var host = flag.String("h", "127.0.0.1", "监听的主机地址") // 监听的主机地址
var port = flag.String("p", "8001", "监听的端口") // 监听的端口 var port = flag.String("p", "8001", "监听的端口") // 监听的端口
var extraPath = flag.String("prefix", "/", "afi 的 URL 访问路径前缀, 例如 /afi/ (斜杠很重要)") // URL前缀路径 var extraPath = flag.String("prefix", "/", "afi 的 URL 访问路径前缀, 例如 /afi/ (斜杠很重要)") // URL前缀路径
var symlinks = flag.Bool("symlinks", false, "跟随符号链接 \033[4m警告\033[0m: 符号链接可能会跳出已定义的\"根目录\" (默认值: false)") // 是否跟随符号链接 var symlinks = flag.Bool("symlinks", false, "跟随符号链接 警告: 启用后符号链接可能会跳出已定义的\"根目录\" (default false)") // 是否跟随符号链接
var verb = flag.Bool("verb", false, "详细输出") // 是否输出详细日志 var verb = flag.Bool("verb", false, "详细输出") // 是否输出详细日志
var skipHidden = flag.Bool("k", true, "\n跳过隐藏文件") // 是否跳过隐藏文件(以.开头) var skipHidden = flag.Bool("k", true, "\n跳过隐藏文件") // 是否跳过隐藏文件(以.开头)
// var ro = flag.Bool("ro", false, "只读模式(无法修改文件系统)") // 是否只读模式 // var ro = flag.Bool("ro", false, "只读模式(无法修改文件系统)") // 是否只读模式
var title = flag.String("title", "%PATH%", "页面标题, 用%PATH%指代完整路径, %ITEM%指代末端文件/目录名, 不会泄根目录目录名") var title = flag.String("title", "%PATH%", "页面标题, 用 %PATH% 指代完整路径, %ITEM% 指代末端文件/目录名, 不会泄根目录目录名")
var authstr = flag.String("auth", `{"admin": "password"}`, "可写用户的认证数据") // 如果非要安全 应该用stdin注入密码防盗窃, 这个flag只能用来调试 不然服务器一个ps就泄露了 var authstr = flag.String("auth", `{"admin": "password"}`, "可写用户的认证数据 (也可以用环境变量 AFI_AUTH 设置, AFI_AUTH 是最优先且最安全的)") // 这个flag只能用来调试 不然服务器一个ps就泄露了
// rpcCall 定义RPC调用的JSON数据结构 // rpcCall 定义RPC调用的JSON数据结构
// 用于处理前端发送的远程调用请求(创建目录、移动、删除、计算校验和) // 用于处理前端发送的远程调用请求(创建目录、移动、删除、计算校验和)
@@ -256,7 +258,7 @@ func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path str
// 根据类型添加到不同的列表 // 根据类型添加到不同的列表
if el.IsDir() { if el.IsDir() {
row := rowTemplate{name + "/", template.URL(href), "4.0kB", "folder", desc, "inode/directory"} row := rowTemplate{html.EscapeString(name + "/"), template.URL(href), "4.0kB", "folder", html.EscapeString(desc), "inode/directory"}
p.RowsFolders = append(p.RowsFolders, row) p.RowsFolders = append(p.RowsFolders, row)
} else { } else {
// 提取文件扩展名 // 提取文件扩展名
@@ -270,7 +272,7 @@ func replyList(w http.ResponseWriter, r *http.Request, fullPath string, path str
filemime = "unknown/unknown" filemime = "unknown/unknown"
} }
} }
row := rowTemplate{name, template.URL(href), humanize(el.Size()), ext, desc, filemime} row := rowTemplate{html.EscapeString(name), template.URL(href), humanize(el.Size()), html.EscapeString(ext), html.EscapeString(desc), filemime}
p.RowsFiles = append(p.RowsFiles, row) p.RowsFiles = append(p.RowsFiles, row)
} }
} }
@@ -337,7 +339,7 @@ func checkAuthRequest(w http.ResponseWriter, r *http.Request) bool {
} }
// 拒绝现代JWT, 古法auth, 我们有先进的https // 拒绝现代JWT, 古法auth, 我们有先进的https
// 这样不能防 XSS // 这样不利好防御 XSS 虽说已经把模板搞安全了
// 但是至于XSS, 用户都在浏览器随便运行tampermonkey了我还能说什么呢, 说不定是用户想写脚本调试呢, 我把xss预防了让他们费劲去找token反而不好 // 但是至于XSS, 用户都在浏览器随便运行tampermonkey了我还能说什么呢, 说不定是用户想写脚本调试呢, 我把xss预防了让他们费劲去找token反而不好
// 这才叫利好curl, 而且还防 CSRF // 这才叫利好curl, 而且还防 CSRF
func authLogin(w http.ResponseWriter, r *http.Request) { func authLogin(w http.ResponseWriter, r *http.Request) {
@@ -540,7 +542,11 @@ func enforcePath(p string) string {
func main() { func main() {
// 解析命令行参数 // 解析命令行参数
if flag.Parse(); len(flag.Args()) == 1 { flag.Parse()
if envAuth := os.Getenv("AFI_AUTH"); envAuth != "" {
*authstr = envAuth
}
if len(flag.Args()) == 1 {
rootPath = flag.Args()[0] rootPath = flag.Args()[0]
} else { } else {
fmt.Printf("Usage: ./afi [OPTION]... [ROOTDIR]\n\n") fmt.Printf("Usage: ./afi [OPTION]... [ROOTDIR]\n\n")
@@ -569,7 +575,7 @@ func main() {
handler = http.StripPrefix(*extraPath, http.FileServer(http.Dir(rootPath))) handler = http.StripPrefix(*extraPath, http.FileServer(http.Dir(rootPath)))
// 输出启动信息 // 输出启动信息
fmt.Printf("Agile File Indexer\n") fmt.Printf("Agile File Indexer 版本 %s\n", ver)
fmt.Printf("AFI 是 Gossa 的增强分支, 但是并不完全向下兼容原版 Gossa 的参数\n") fmt.Printf("AFI 是 Gossa 的增强分支, 但是并不完全向下兼容原版 Gossa 的参数\n")
fmt.Printf("AFI 已启动, 根目录为 %s\n", rootPath) fmt.Printf("AFI 已启动, 根目录为 %s\n", rootPath)
fmt.Printf("详细输出: %t, 符号链接跟随: %t, 跳过隐藏文件: %t\n", *verb, *symlinks, *skipHidden) fmt.Printf("详细输出: %t, 符号链接跟随: %t, 跳过隐藏文件: %t\n", *verb, *symlinks, *skipHidden)

View File

@@ -19,6 +19,45 @@ html {
scrollbar-gutter: stable; scrollbar-gutter: stable;
} }
@media (max-width: 640px) {
#index-table th:nth-child(5) {
display: none;
}
#index-table td:nth-child(6) {
display: none;
}
.ellipsis {
display: -webkit-box;
-webkit-box-orient: vertical;
line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-word;
}
.desktoponly {
display: none;
}
#index-table th:last-child,
#index-table td:last-child {
text-align: right;
padding: 0.25em 0 !important;
}
#index-table th:nth-child(2),
#index-table td:nth-child(3) {
width: 52%;
}
#index-table th:nth-child(3),
#index-table td:nth-child(4) {
display: none;
}
}
*, *,
*::before, *::before,
*::after { *::after {

View File

@@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<meta charset="utf-8"> <meta charset="utf-8">
<title>{{.Title}}</title> <title>{{.Title}}</title>
<link rel="preconnect" href="https://fonts.loli.net"> <link rel="preconnect" href="https://fonts.loli.net">
@@ -99,9 +100,9 @@
</table> </table>
<div style="display: none;" onclick="afi.utils.helpOff()" id="help-panel"> <div style="display: none;" onclick="afi.utils.helpOff()" id="help-panel">
<br> <br>
<h3>帮助信息与按键绑定</h3> <h3 class="desktoponly">帮助信息与按键绑定</h3>
<p>^[X] 表示组合键 Ctrl + [X] 或 Meta + [X]</p> <p class="desktoponly">^[X] 表示组合键 Ctrl + [X] 或 Meta + [X]</p>
<table> <table class="desktoponly">
<tbody> <tbody>
<tr> <tr>
<td>使用键盘浏览文件系统</td> <td>使用键盘浏览文件系统</td>
@@ -145,7 +146,7 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<br> <br class="desktoponly">
<h3>关于 AFI</h3> <h3>关于 AFI</h3>
<p>AFI (敏捷文件索引器) 是 Go 编写的轻量级文件服务器.<br> <p>AFI (敏捷文件索引器) 是 Go 编写的轻量级文件服务器.<br>
AFI 是基于 Gossa 的现代化改进与维护分支, 以 MIT 协议在<a href="">此处</a>开放源代码.<br> AFI 是基于 Gossa 的现代化改进与维护分支, 以 MIT 协议在<a href="">此处</a>开放源代码.<br>