被封禁来源访问任何路径时,原来只回 13 字节的 "403 Forbidden":既没有信息量, 人肉访问(尤其是共享出口被连累的误封)时看着像站点挂了;扫描/爆破脚本也常靠 读这行明文确认「自己已被拦」。 改为返回一张 256×256 的 webp(约 8.6 KB,画面写着 403 Forbidden),状态码不变: - 图片用 //go:embed 编进二进制,不依赖外部 URL、不占公开路径; - 显式给 Content-Length(8 KB 超过 Go 的 2 KB 写缓冲,不设会退回 chunked); - Cache-Control: no-store —— 中间层缓存 403 没有意义,还会让解封后的响应继续命中; - HEAD 只回头不写 body; - 放在 internal/route/assets/ 而不是 static/:static 下的东西有可公开访问的 URL, 等于多一个指纹入口。 注意链接:给的 R2 预签名地址 X-Amz-Expires=604800(7 天),热链到期就会挂, 所以是把字节取回来打包,而不是重定向或代理。 测试: - TestWriteBanned:状态码 403、image/webp、Content-Length 与内嵌字节数一致、 body 前 12 字节是 RIFF/WEBP(防止被换成 HTML 或明文这类回归); - TestWriteBannedHead:HEAD 无 body 但有 Content-Length; - 原 TestHoneypotBansEvenWhenAutoBanDisabled 里断言 "403 Forbidden" 明文的用例 改为断言 image/webp 与体积。 变异验证:把 writeBanned 改回写明文 + text/plain,两条用例均按预期变红。
56 lines
1.9 KiB
Go
56 lines
1.9 KiB
Go
package route
|
||
|
||
import (
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"strconv"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/tamakyi/TamaBox/internal/route/assets"
|
||
)
|
||
|
||
// TestWriteBanned 守住「被封禁后的响应是一张图」。
|
||
func TestWriteBanned(t *testing.T) {
|
||
rec := httptest.NewRecorder()
|
||
writeBanned(rec, http.MethodGet)
|
||
|
||
if rec.Code != http.StatusForbidden {
|
||
t.Fatalf("status = %d, want 403(换成图片不改变状态码语义)", rec.Code)
|
||
}
|
||
if ct := rec.Header().Get("Content-Type"); ct != "image/webp" {
|
||
t.Errorf("Content-Type = %q, want image/webp", ct)
|
||
}
|
||
if cl := rec.Header().Get("Content-Length"); cl != strconv.Itoa(len(assets.BannedImage)) {
|
||
t.Errorf("Content-Length = %q, want %d", cl, len(assets.BannedImage))
|
||
}
|
||
if cc := rec.Header().Get("Cache-Control"); !strings.Contains(cc, "no-store") {
|
||
t.Errorf("Cache-Control = %q, want 含 no-store(中间层缓存 403 无意义)", cc)
|
||
}
|
||
|
||
body := rec.Body.Bytes()
|
||
// webp 的容器头:RIFF....WEBP。写成字节判断,避免「图片被换成 HTML」这类回归。
|
||
if len(body) < 12 || string(body[0:4]) != "RIFF" || string(body[8:12]) != "WEBP" {
|
||
t.Errorf("body 不是 webp(前 12 字节 %q)", body[:min(12, len(body))])
|
||
}
|
||
if strings.Contains(string(body), "403 Forbidden") && len(body) < 512 {
|
||
t.Error("body 仍是明文文案,没换成图片")
|
||
}
|
||
}
|
||
|
||
// TestWriteBannedHead 覆盖 HEAD:头照给(含 Content-Length),body 不发。
|
||
func TestWriteBannedHead(t *testing.T) {
|
||
rec := httptest.NewRecorder()
|
||
writeBanned(rec, http.MethodHead)
|
||
|
||
if rec.Code != http.StatusForbidden {
|
||
t.Fatalf("status = %d, want 403", rec.Code)
|
||
}
|
||
if rec.Body.Len() != 0 {
|
||
t.Errorf("HEAD 不应有 body,实际 %d 字节", rec.Body.Len())
|
||
}
|
||
if cl := rec.Header().Get("Content-Length"); cl != strconv.Itoa(len(assets.BannedImage)) {
|
||
t.Errorf("Content-Length = %q, want %d", cl, len(assets.BannedImage))
|
||
}
|
||
}
|