修复: 评论组件泄露私密 token、cwebp 无超时、选项长度与去重

Waline(安全)
- item.html 的 path 从 window.location.href 改为剔除 ?t= 后的 URL。
  私密题 /_/<domain>/<id>?t=<token> 的钥匙原本会整条发往 Waline 服务端
  并落进其访问日志。只删 t、保留其他参数,公开题的评论线程键不变。

cwebp(可靠性)
- ConvertGifToAnimatedWebP 改用 exec.CommandContext + 30s 上限。
  原来裸 exec.Command 无超时,畸形/超大 GIF 会把子进程和上传 goroutine 一起挂住。
  超时杀进程并记 Warn,调用方按既有逻辑回退为原 GIF,不影响上传成功。

投票选项长度(正确性)
- vote_options.content 由 varchar(200) 放宽到 300,与配置默认值一致。
  此前 201–300 字的选项通过校验后在写库阶段失败。
- 新增 db.MaxVoteOptionLen;voteOptionMaxLen() 取配置与存储上限的较小值,
  校验与前端 maxlength 同源,避免配置调大后再次越界。

多选 checkbox 去重(正确性)
- 抽出 appendUniqueValue,预置值与「其他」文本共用同一 seen 集合。
  此前重复提交同名字段会让同一选项在统计里被计多次。

测试
- vote_dedupe_test.go:去重与顺序。
- vote_option_len_test.go:配置小于/等于/大于存储上限三种情形。
- TestVoteOptionMaxLengthRoundTrip:300 个多字节字符存回不截断。
- TestVoteOptionColumnWidens:把列改回 200 后 AutoMigrate 能扩回 300(老库升级路径)。

验证:WSL2 Debian + PostgreSQL 全量 go test ./... 27 个包全绿;
隔离实例渲染单题页确认新的 path 逻辑。
This commit is contained in:
tamakyi
2026-11-05 17:04:47 +08:00
parent 7ffab24025
commit 5fdc3df22d
8 changed files with 249 additions and 12 deletions
+15 -1
View File
@@ -16,6 +16,7 @@ import (
"mime/multipart"
"os"
"os/exec"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
@@ -248,6 +249,12 @@ func fitsWithin(w, h, maxSide int) bool {
// cwebp 长时间占用 CPU 与内存,故在此显式设限。
const maxGIFFrames = 200
// gifConvertTimeout 是单次 cwebp 转换的硬上限。
// 帧数限制挡不住「帧少但每帧极大」或畸形数据把 cwebp 拖住的情况;子进程一旦挂住,
// 调用它的上传 goroutine 会一直等,最终拖死整个上传路径。超时后 exec 会杀掉子进程,
// 调用方按错误回退到原 GIF,不影响上传本身成功。
const gifConvertTimeout = 30 * time.Second
// ConvertGifToAnimatedWebP 将 GIF 转换为动画 WebP(需要系统安装 cwebp)。
// cwebp 不可用、GIF 损坏或帧数超限时返回错误,调用方应回退到原始 GIF。
func ConvertGifToAnimatedWebP(gifData []byte) ([]byte, error) {
@@ -281,9 +288,16 @@ func ConvertGifToAnimatedWebP(gifData []byte) ([]byte, error) {
// -mixed:对透明帧用无损、其余有损,是动画的推荐组合。
// -metadata none:剥离调色板外的元数据,进一步减小体积。
cmd := exec.Command("cwebp", "-q", "80", "-mixed", "-metadata", "none",
// 用 CommandContext 而非裸 Command:超时会杀进程,不会让上传 goroutine 无限等待。
ctx, cancel := context.WithTimeout(context.Background(), gifConvertTimeout)
defer cancel()
cmd := exec.CommandContext(ctx, "cwebp", "-q", "80", "-mixed", "-metadata", "none",
"-mt", "-quiet", tmpGif.Name(), "-o", tmpWebp.Name())
if err := cmd.Run(); err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
logrus.WithField("timeout", gifConvertTimeout).Warn("cwebp 转换超时,回退为原 GIF")
return nil, errors.Errorf("cwebp timed out after %s", gifConvertTimeout)
}
return nil, errors.Wrap(err, "run cwebp")
}