Files
SKYMirror 7a9015aad0
CI 测试 / go vet + go test (push) Failing after 5s
26.9:直链下载 + 过期回收 + SHA512 去重 + 防盗链 + 媒体预览 + 文件夹上传提示
- 对象存储直链: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
2026-09-08 04:54:05 +08:00

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
}