安全审计 #19/#20(security_todo.md P1): - 新增 store.PurgeMessages 共享清理入口:删附件文件、回退配额、 删附件记录、删邮件记录,Web/IMAP/POP3 三条删除路径同源 - IMAP EXPUNGE(含 UID 分支)与 POP3 expungeDeleted 接入,修复 协议删除只删记录导致的"幽灵配额"与附件文件泄漏 - Web purgeMessages 改为委托同一入口,消除三处重复实现 - SMTP 入站投递前按收件人原子预扣附件配额(TryReserveQuota), 任一收件人超限整封拒收并返回 452 临时失败(避免 go-smtp 将 普通 error 映射为 554 永久失败);Sent 副本尽力而为不回滚整封 - saveMessage 返回实际落库字节数,reconcileQuota 对账退还差额 (覆盖收件人被删/落库失败/部分附件失败) - NewIMAPServer/NewPOP3Server 注入 AttachmentStorage - 新增 4 项单测(配额拒收/正常记账/对账回退/POP3 删除清理) - security_todo.md 勾选 #19/#20,登记存量发现 #27(IncrementFail 并发首建窗口丢失更新,待决策)
37 lines
1.2 KiB
Go
37 lines
1.2 KiB
Go
package store
|
|
|
|
import (
|
|
"log"
|
|
|
|
"mail_go/internal/db"
|
|
"mail_go/internal/storage"
|
|
)
|
|
|
|
// PurgeMessages 永久删除一批邮件:逐封删除附件文件并回退其占用的配额、
|
|
// 删除附件记录,最后批量删除邮件记录。Web 删除/清空、IMAP EXPUNGE、
|
|
// POP3 删除共用本入口,保证磁盘文件、配额与数据库三者一致。
|
|
// att 为 nil 时跳过文件清理(仍回退配额并删除记录,测试与降级场景使用)。
|
|
func (s *Stores) PurgeMessages(att *storage.AttachmentStorage, userID uint, msgs []db.Message) error {
|
|
ids := make([]uint, 0, len(msgs))
|
|
for i := range msgs {
|
|
attachments, err := s.Attachments.ListByMessage(msgs[i].ID)
|
|
if err != nil {
|
|
log.Printf("store: 查询附件失败 msg=%d: %v", msgs[i].ID, err)
|
|
}
|
|
for _, a := range attachments {
|
|
if att != nil {
|
|
_ = att.Delete(a.FilePath)
|
|
}
|
|
_ = s.Users.UpdateUsedBytes(userID, -a.FileSize)
|
|
}
|
|
if err := s.Attachments.DeleteByMessage(msgs[i].ID); err != nil {
|
|
log.Printf("store: 删除附件记录失败 msg=%d: %v", msgs[i].ID, err)
|
|
}
|
|
ids = append(ids, msgs[i].ID)
|
|
}
|
|
if len(ids) == 0 {
|
|
return nil
|
|
}
|
|
return s.Mails.DeleteMany(ids)
|
|
}
|