CI 测试 / go vet + go test (push) Failing after 5s
- 对象存储直链:S3 引擎 302 到限时预签名 URL(有效期钳位分享剩余时效),失败自动回落代理 - 过期回收:janitor 定时扫描 + 取件惰性回收 + 管理端手动触发(POST /admin/recycle/run), retention_days 最长存储时长;删除走引用计数(去重对象安全) - SHA512 内容去重:三条上传链路落库后计算哈希,命中即复用旧对象并删除本次副本 - 下载防盗链:Referer 白名单(同源/空 Referer/通配域名放行),挂 /share/download - 取件页图片/音频内联预览(下载地址直连,加载失败回退下载按钮) - 文件夹上传:拖拽目录明确提示"建议压缩后上传"(webkitGetAsEntry 探测) - 管理端设置卡「回收与下载安全」8 个新配置键(KVSchema + configKeys + UI + i18n) - 前端产物重建并同步 server/web/dist 与 web-embed - 文档:10-config 配置表、03-file-share 直链/防盗链/文件夹章节、07-admin 回收端点、openapi
69 lines
1.9 KiB
Go
69 lines
1.9 KiB
Go
// Package middleware — hotlink.go 下载防盗链(26.9):
|
|
// 校验 Referer 白名单。规则:
|
|
// - Referer 为空(直接访问/curl/浏览器地址栏):放行(不误伤正常取件);
|
|
// - Referer 与当前请求 Host 同源:放行;
|
|
// - Referer 主机命中管理端白名单(hotlink_whitelist,逗号分隔域名,支持 *.example.com 通配):放行;
|
|
// - 其余一律 403。
|
|
package middleware
|
|
|
|
import (
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"fileshare/internal/config"
|
|
)
|
|
|
|
// HotlinkMiddleware 返回防盗链中间件;cfg 动态读取开关与白名单(管理端改后立即生效)。
|
|
func HotlinkMiddleware(cfg *config.Config) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
if !cfg.HotlinkEnabled() {
|
|
c.Next()
|
|
return
|
|
}
|
|
ref := c.GetHeader("Referer")
|
|
if ref == "" {
|
|
c.Next() // 空 Referer 放行
|
|
return
|
|
}
|
|
u, err := url.Parse(ref)
|
|
if err != nil || u.Host == "" {
|
|
c.Next() // 非法 Referer 视同空,放行(避免误伤)
|
|
return
|
|
}
|
|
if strings.EqualFold(u.Host, c.Request.Host) {
|
|
c.Next() // 同源放行
|
|
return
|
|
}
|
|
if hostAllowed(u.Host, cfg.HotlinkWhitelist()) {
|
|
c.Next()
|
|
return
|
|
}
|
|
c.AbortWithStatusJSON(403, gin.H{"message": "防盗链:外部站点引用不允许访问该资源"})
|
|
}
|
|
}
|
|
|
|
// hostAllowed 判断主机是否命中白名单(精确匹配或 *. 通配后缀匹配)。
|
|
// 白名单条目可带端口;通配写作 .example.com 或 *.example.com。
|
|
func hostAllowed(host string, whitelist []string) bool {
|
|
if len(whitelist) == 0 {
|
|
return false
|
|
}
|
|
host = strings.ToLower(host)
|
|
for _, w := range whitelist {
|
|
w = strings.ToLower(strings.TrimSpace(w))
|
|
w = strings.TrimPrefix(w, "*") // *.example.com → .example.com
|
|
if w == "" {
|
|
continue
|
|
}
|
|
if host == strings.TrimPrefix(w, ".") {
|
|
return true
|
|
}
|
|
if strings.HasSuffix(host, w) && strings.HasPrefix(w, ".") {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|