Files
tamabox/internal/route/bodylimit.go
T
2026-09-08 10:10:44 +08:00

55 lines
2.6 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.
// 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)
}
}