fix(httpapi): no-store 接线、图片缓存头时序、解压炸弹上限与 RSS 空日期
/api/admin/* 挂 NoStore、/api/me 内联 no-store(P1-3); 图片缓存头移至数据读取成功后,404 不携带 public immutable(P2-9); 上传先 DecodeConfig 限制像素 ≤2^25 再解码(P2-12); 无公开笔记时省略 lastBuildDate(P2-10)。
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"hash/crc32"
|
||||
"image"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// pngWithDims 构造仅含文件签名 + IHDR 的 PNG 头(DecodeConfig 只解析头部即可
|
||||
// 得到宽高,无需真实像素数据;CRC 按 PNG 规范计算)。
|
||||
func pngWithDims(w, h uint32) []byte {
|
||||
ihdr := make([]byte, 13)
|
||||
binary.BigEndian.PutUint32(ihdr[0:4], w)
|
||||
binary.BigEndian.PutUint32(ihdr[4:8], h)
|
||||
// 8bit / truecolor / deflate / adaptive / no interlace
|
||||
ihdr[8], ihdr[9], ihdr[10], ihdr[11], ihdr[12] = 8, 2, 0, 0, 0
|
||||
|
||||
chunk := bytes.NewBuffer(nil)
|
||||
_ = binary.Write(chunk, binary.BigEndian, uint32(len(ihdr)))
|
||||
chunk.WriteString("IHDR")
|
||||
chunk.Write(ihdr)
|
||||
_ = binary.Write(chunk, binary.BigEndian, crc32.ChecksumIEEE(chunk.Bytes()[4:]))
|
||||
|
||||
out := bytes.NewBuffer(nil)
|
||||
out.Write([]byte{0x89, 'P', 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A})
|
||||
out.Write(chunk.Bytes())
|
||||
return out.Bytes()
|
||||
}
|
||||
|
||||
// TestUploadPixelBomb 解压炸弹防御(评审 round2 P2-12):小体积超大尺寸图片
|
||||
// 在 DecodeConfig 阶段被 413 拒绝,不进入全量 Decode。
|
||||
func TestUploadPixelBomb(t *testing.T) {
|
||||
// 头部自证合法:DecodeConfig 可解析出宽高
|
||||
bomb := pngWithDims(20000, 20000) // 4 亿像素 > 1<<25
|
||||
if _, _, err := image.DecodeConfig(bytes.NewReader(bomb)); err != nil {
|
||||
t.Fatalf("夹具应可解析出尺寸: %v", err)
|
||||
}
|
||||
// 正常小图不受影响
|
||||
if _, _, err := image.DecodeConfig(bytes.NewReader(png1x1)); err != nil {
|
||||
t.Fatalf("1x1 夹具应合法: %v", err)
|
||||
}
|
||||
|
||||
e := newEnv(t)
|
||||
c := e.loginAdmin()
|
||||
// 手工 multipart(uploadPNG 辅助对非 201 会 Fatal)
|
||||
var body bytes.Buffer
|
||||
boundary := "bombboundary456"
|
||||
body.WriteString("--" + boundary + "\r\n")
|
||||
body.WriteString(`Content-Disposition: form-data; name="file"; filename="bomb.png"` + "\r\n")
|
||||
body.WriteString("Content-Type: image/png\r\n\r\n")
|
||||
body.Write(bomb)
|
||||
body.WriteString("\r\n--" + boundary + "--\r\n")
|
||||
req, err := http.NewRequest(http.MethodPost, e.ts.URL+"/api/admin/images", &body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "multipart/form-data; boundary="+boundary)
|
||||
req.Header.Set("Origin", e.ts.URL)
|
||||
req.Header.Set("X-CSRF-Token", e.csrf)
|
||||
resp, err := c.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusRequestEntityTooLarge {
|
||||
t.Fatalf("超大像素图片应 413,实际 %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user