Files
tamabox/internal/route/banned_test.go
T
tamakyi 0318ba81b4 feat(autoban): 封禁响应体改成一张 403 图片
被封禁来源访问任何路径时,原来只回 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,两条用例均按预期变红。
2026-10-23 15:04:13 +08:00

56 lines
1.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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))
}
}