From 13332136c2a8539ce3cb13e4ba132e9a514e442a Mon Sep 17 00:00:00 2001 From: LanQin Date: Wed, 24 Jun 2026 18:22:24 +0800 Subject: [PATCH] =?UTF-8?q?chore(maildir):=20=E8=A1=A5=E5=85=85=E9=82=AE?= =?UTF-8?q?=E4=BB=B6=E7=9B=AE=E5=BD=95=E5=BD=92=E5=B1=9E=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 Unix 与非 Unix 平台的目录归属处理实现。 - 在写入、移动和标记邮件时同步调整 Maildir 目录与文件归属。 - 扩展目录初始化逻辑,确保父目录与子目录权限一致。 --- .../internal/app/maildir_ownership_other.go | 7 ++++ .../internal/app/maildir_ownership_unix.go | 17 ++++++++++ apps/api/internal/app/maildir_write.go | 34 +++++++++++++++++-- 3 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 apps/api/internal/app/maildir_ownership_other.go create mode 100644 apps/api/internal/app/maildir_ownership_unix.go diff --git a/apps/api/internal/app/maildir_ownership_other.go b/apps/api/internal/app/maildir_ownership_other.go new file mode 100644 index 0000000..261ecc4 --- /dev/null +++ b/apps/api/internal/app/maildir_ownership_other.go @@ -0,0 +1,7 @@ +//go:build !unix + +package app + +func applyMaildirOwnership(path string) error { + return nil +} diff --git a/apps/api/internal/app/maildir_ownership_unix.go b/apps/api/internal/app/maildir_ownership_unix.go new file mode 100644 index 0000000..3010df2 --- /dev/null +++ b/apps/api/internal/app/maildir_ownership_unix.go @@ -0,0 +1,17 @@ +//go:build unix + +package app + +import "os" + +const ( + maildirOwnerUID = 5000 + maildirOwnerGID = 5000 +) + +func applyMaildirOwnership(path string) error { + if os.Geteuid() != 0 { + return nil + } + return os.Chown(path, maildirOwnerUID, maildirOwnerGID) +} diff --git a/apps/api/internal/app/maildir_write.go b/apps/api/internal/app/maildir_write.go index c26d08e..4b7fba3 100644 --- a/apps/api/internal/app/maildir_write.go +++ b/apps/api/internal/app/maildir_write.go @@ -120,7 +120,7 @@ func (a *App) writeRawMessageToMaildirFolder(ctx context.Context, messageID, fol if strings.EqualFold(folderName, "Inbox") && !state.IsRead { subdir = "new" } - if err := ensureMaildirFolderDirs(folderBase); err != nil { + if err := ensureMaildirFolderDirs(base, folderBase); err != nil { return err } filename := maildirFilename(messageID, state.MessageID) @@ -130,6 +130,10 @@ func (a *App) writeRawMessageToMaildirFolder(ctx context.Context, messageID, fol if err := os.WriteFile(tmpPath, raw, 0o600); err != nil { return err } + if err := applyMaildirOwnership(tmpPath); err != nil { + _ = os.Remove(tmpPath) + return err + } if err := os.Rename(tmpPath, finalPath); err != nil { _ = os.Remove(tmpPath) return err @@ -213,7 +217,7 @@ func (a *App) moveMessageMaildir(ctx context.Context, messageID, targetFolderID } base := filepath.Join(strings.TrimSpace(a.cfg.MaildirRoot), mb.Domain, mb.LocalPart, "Maildir") folderBase := maildirFolderPath(base, folderName) - if err := ensureMaildirFolderDirs(folderBase); err != nil { + if err := ensureMaildirFolderDirs(base, folderBase); err != nil { return err } subdir := "cur" @@ -225,6 +229,9 @@ func (a *App) moveMessageMaildir(ctx context.Context, messageID, targetFolderID if err := os.Rename(state.RawPath, targetPath); err != nil { return err } + if err := applyMaildirOwnership(targetPath); err != nil { + return err + } } if state.FolderID != "" && state.FolderID != targetFolderID { if _, err := a.bumpFolderModSeq(ctx, state.FolderID); err != nil { @@ -318,6 +325,9 @@ func (a *App) updateMessageMaildirFlags(ctx context.Context, messageID string, r if err := os.Rename(state.RawPath, targetPath); err != nil { return err } + if err := applyMaildirOwnership(targetPath); err != nil { + return err + } modSeq, err := a.bumpFolderModSeq(ctx, state.FolderID) if err != nil { return err @@ -481,15 +491,33 @@ func (a *App) pathIsUnderMaildirRoot(path string) (bool, error) { return rel != "." && !strings.HasPrefix(rel, ".."+string(filepath.Separator)) && rel != "..", nil } -func ensureMaildirFolderDirs(folderBase string) error { +func ensureMaildirFolderDirs(base, folderBase string) error { for _, sub := range []string{"tmp", "new", "cur"} { if err := os.MkdirAll(filepath.Join(folderBase, sub), 0o755); err != nil { return err } } + for _, dir := range maildirOwnershipDirs(base, folderBase) { + if err := applyMaildirOwnership(dir); err != nil { + return err + } + } return nil } +func maildirOwnershipDirs(base, folderBase string) []string { + dirs := []string{ + filepath.Dir(filepath.Dir(base)), + filepath.Dir(base), + base, + } + if filepath.Clean(folderBase) != filepath.Clean(base) { + dirs = append(dirs, folderBase) + } + dirs = append(dirs, filepath.Join(folderBase, "tmp"), filepath.Join(folderBase, "new"), filepath.Join(folderBase, "cur")) + return dirs +} + func maildirFilename(messageID, headerMessageID string) string { base := strings.TrimSpace(headerMessageID) if base == "" {