Repo .git was damaged (refs/ missing, index truncated, object store near-empty). Repaired via git init (rebuilt refs), stale index preserved as .git/index.corrupt-20260910.bak, then full worktree committed as a rollback baseline.
55 lines
2.6 KiB
Go
55 lines
2.6 KiB
Go
// Copyright 2026 tamakyi. All rights reserved.
|
||
// Use of this source code is governed by a MIT-style
|
||
// license that can be found in the LICENSE file.
|
||
|
||
package route
|
||
|
||
import (
|
||
"net/http"
|
||
|
||
"github.com/flamego/flamego"
|
||
|
||
"github.com/tamakyi/TamaBox/internal/form"
|
||
)
|
||
|
||
// requestBodyLimitMiddleware 在任何业务代码读取请求体之前,全局封顶单次请求体总大小,
|
||
// 上限与 form.Bind 共用 form.MaxRequestBodySize(12 MiB),单一事实来源。
|
||
//
|
||
// 为什么需要全局层(而不是只依赖 form.Bind 内部限制):
|
||
// 1. 走 form.Bind 的路由中,Bind 内 MaxBytesReader 的执行顺序晚于 Contexter 组级
|
||
// CSRF 校验 —— flamego/csrf 的 Validate 在请求缺少 X-CSRF-Token 头时通过
|
||
// FormValue("_csrf") 惰性解析整包 body(multipart 超过 32 MiB 内存阈值后无上限
|
||
// 落盘临时文件),校验失败写出的 400 又发生在路由级限流之前。攻击者无需有效
|
||
// CSRF token、也不受限流约束,即可持续 POST 超大 multipart 打满磁盘。
|
||
// 2. chat /send、vote /submit 等不走 form.Bind 的入口此前完全没有请求体上限。
|
||
//
|
||
// 挂载于 internal/route/route.go 的 f.Use 链(autoban 之后、所有 Group 之前),
|
||
// 因此任何后续解析(CSRF FormValue、form.Bind、业务 FormValue)都发生在受限 reader 上。
|
||
// 合法最大单请求约为 8 MiB 单图(storage.MaxUploadBytes)+ 表单开销,12 MiB 不会误伤。
|
||
func requestBodyLimitMiddleware() flamego.Handler {
|
||
return func(ctx flamego.Context) {
|
||
req := ctx.Request()
|
||
|
||
// 无 body 的方法不处理(避免对静态资源等无谓开销)。
|
||
switch req.Method {
|
||
case http.MethodPost, http.MethodPut, http.MethodPatch:
|
||
default:
|
||
return
|
||
}
|
||
|
||
// Content-Length 明确超限时直接 413 快速失败,不进入解析流程。
|
||
// (chunked 或未知长度请求的 ContentLength 为 0/-1,走下方 MaxBytesReader 惰性封顶。)
|
||
if req.ContentLength > form.MaxRequestBodySize {
|
||
http.Error(ctx.ResponseWriter(), http.StatusText(http.StatusRequestEntityTooLarge), http.StatusRequestEntityTooLarge)
|
||
return
|
||
}
|
||
|
||
// 其余情况:用 MaxBytesReader 包一层。MaxBytesReader 在读取超过上限时令后续
|
||
// Read 报错,使 ParseForm / ParseMultipartForm 失败 —— 超大 body 不会落盘或读入内存。
|
||
// 注意:c.Request() 返回 flamego 的包装类型,其 Body 是方法而非字段,
|
||
// 必须取内嵌的 *http.Request 才能替换 Body(与 internal/form/form.go 同款写法)。
|
||
httpReq := req.Request
|
||
httpReq.Body = http.MaxBytesReader(ctx.ResponseWriter(), httpReq.Body, form.MaxRequestBodySize)
|
||
}
|
||
}
|