fix anwser token and database issus

This commit is contained in:
tamakyi
2026-09-15 11:06:09 +08:00
parent e683e798aa
commit adcc42cc8c
33 changed files with 1011 additions and 802 deletions
+15 -29
View File
@@ -31,25 +31,18 @@ type Error struct {
}
const (
// multipartMemoryLimit 是 multipart 解析时驻留内存的上限,超出部分由
// mime/multipart 落盘到临时文件。
multipartMemoryLimit = 10 << 20 // 10 MiB
multipartMemoryLimit = 10 << 20 // 10 MiB:multipart 内存驻留上限,超出由 mime/multipart 落盘
// MaxRequestBodySize 是单次请求体的**总**大小上限(含落盘部分)。
// MaxRequestBodySize 请求体总大小上限(含落盘部分),略高于内存上限以留出开销。
//
// ⚠️ 必须显式限制:ParseMultipartForm 的参数只约束内存缓冲,超出部分会无上限地
// 写入临时文件。匿名可达的提问接口即接受 multipart,若不加总量限制,攻击者可持续
// POST 超大 body 撑满磁盘(http.Server 的 ReadTimeout 在高带宽下不足以形成约束)。
// 略高于内存上限,为 multipart 边界与其他表单字段留出开销。
// 导出供 internal/route 的全局 requestBodyLimitMiddleware 复用同一上限:
// 仅靠本 Bind 内的 MaxBytesReader 不够——Contexter 组级 CSRF 校验先于 Bind 执行,
// 无 X-CSRF-Token 头时它会整包惰性解析 body,超大 multipart 会在 Bind 限制生效前
// 落盘;全局中间件把上限提前到一切解析之前(见 internal/route/bodylimit.go)。
// ⚠️ 必须显式限制:ParseMultipartForm 的参数只约束内存缓冲,超出部分会无上限写入
// 临时文件;匿名可达的提问接口即接受 multipart,无总量限制则可持续撑满磁盘。
// 导出供 internal/route/bodylimit.go 的全局中间件复用——仅靠本 Bind 内限制不够,
// 组级 CSRF 校验先于 Bind 执行且会整包惰性解析 body,须把上限提前到一切解析之前。
MaxRequestBodySize = 12 << 20 // 12 MiB
)
func Bind(model interface{}) flamego.Handler {
// Ensure not pointer.
if reflect.TypeOf(model).Kind() == reflect.Ptr {
panic("form: pointer can not be accepted as binding model")
}
@@ -60,11 +53,9 @@ func Bind(model interface{}) flamego.Handler {
r := c.Request()
// 在任何解析之前封顶请求体总大小。
// 注意:c.Request() 返回 flamego 的包装类型,其 Body 是方法而非字段,
// 必须取内嵌的 *http.Request 才能替换 Body。
// MaxBytesReader 在超限时让后续 Read 返回错误,从而使 ParseForm /
// ParseMultipartForm 失败并走下方的反序列化错误分支,避免超大 body 落盘或读入内存。
// 在任何解析之前封顶请求体总大小。c.Request() 是 flamego 包装类型(Body 是方法
// 而非字段),须取内嵌 *http.Request 才能替换。超限后 Read 报错,使 ParseForm /
// ParseMultipartForm 失败并走反序列化错误分支,避免超大 body 落盘或读入内存。
httpReq := r.Request
httpReq.Body = http.MaxBytesReader(c.ResponseWriter(), httpReq.Body, MaxRequestBodySize)
@@ -72,11 +63,9 @@ func Bind(model interface{}) flamego.Handler {
switch contentType {
case "application/x-www-form-urlencoded", "":
if err := r.ParseForm(); err != nil {
// ⚠️ 必须同时 SetError(写响应则在此提前中止):flamego 的 handler 链
// 只要响应未写入就会自动执行下一 handler,仅 Map(Error) 不中断链,
// 零值结构体会照常进入业务动作(如把用户资料/屏蔽词静默清空、解除页面密码)。
// SetError 置位 HasError,业务 action 开头若检查 ctx.HasError() 即会早退;
// 不检查的 action 也已在 route/user 各 handler 补了 abortOnFormError 守卫。
// ⚠️ 必须 SetError:flamego 的 handler 链只要响应未写入就会自动执行下一 handler,
// 仅 Map(Error) 不中断链,零值结构体会照常进入业务动作(静默清空屏蔽词、解除
// 页面密码等)。SetError 置位 HasError,action 开头的 abortOnFormError 守卫会早退。
c.SetError(err)
c.Map(Error{Category: ErrorCategoryDeserialization, Error: err})
return
@@ -89,7 +78,6 @@ func Bind(model interface{}) flamego.Handler {
}
}
// Bind the form data to the given struct.
typ := reflect.TypeOf(obj.Interface())
val := reflect.ValueOf(obj.Interface())
if typ.Kind() == reflect.Ptr {
@@ -131,11 +119,9 @@ func Bind(model interface{}) flamego.Handler {
}
}
} else {
// 非 multipart 请求(application/x-www-form-urlencoded):仅绑定 string 字段。
// 其余类型(如 []*multipart.FileHeader 图片字段)保持零值,
// 否则 reflect.Set 会因类型不匹配 panic(string → []*multipart.FileHeader)。
// 此前提问表单因带 enctype="multipart/form-data" 走上面分支而从未触发;
// 独立私聊表单等纯文本提交会命中此路径,必须先做类型判断。
// 非 multipart 请求仅绑定 string 字段,其余类型(如 []*multipart.FileHeader
// 图片字段)保持零值,否则 reflect.Set 会因类型不匹配 panic。
// 带 enctype 的提问表单走上面分支从未触发,纯文本提交(如私聊)会命中此路径。
if val.Field(i).Kind() == reflect.String {
val.Field(i).Set(reflect.ValueOf(r.Form.Get(fieldName)))
}