This commit is contained in:
parent
b396afa90c
commit
fc027636c2
@ -82,11 +82,6 @@ export function usePage() {
|
||||
|
||||
const protocolLabel = protocol => ({ imap: 'IMAP', pop3: 'POP3', smtp: 'SMTP' }[protocol] || protocol.toUpperCase())
|
||||
|
||||
const loadProtocolCredentials = async () => {
|
||||
const res = await http.get('/api/v1/security/credentials/protocols')
|
||||
if (res.errorNo !== 0) throw new Error(res.errorMsg || '读取第三方协议设置失败')
|
||||
protocolCredentials.value = Array.isArray(res.data) ? res.data : []
|
||||
}
|
||||
|
||||
const setProtocolEnabled = async (item, enabled) => {
|
||||
const previous = item.enabled
|
||||
|
||||
@ -18,6 +18,7 @@ import (
|
||||
|
||||
"git.pcux.cn/xuans/Smail/config"
|
||||
"git.pcux.cn/xuans/Smail/db"
|
||||
"git.pcux.cn/xuans/Smail/dto/response"
|
||||
"git.pcux.cn/xuans/Smail/models"
|
||||
"git.pcux.cn/xuans/Smail/services/credentials"
|
||||
"git.pcux.cn/xuans/Smail/utils/array"
|
||||
@ -398,7 +399,79 @@ func TestDelete(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAppend(t *testing.T) {
|
||||
rawMessage := "From: sender@test.domain\r\n" +
|
||||
"To: testCase@test.domain\r\n" +
|
||||
"Subject: IMAP APPEND test\r\n" +
|
||||
"Message-ID: <imap-append-test@test.domain>\r\n" +
|
||||
"Content-Type: text/plain; charset=utf-8\r\n" +
|
||||
"\r\n" +
|
||||
"Appended message body.\r\n"
|
||||
|
||||
appendCommand := clientLogin.Append("INBOX", int64(len(rawMessage)), &imap.AppendOptions{
|
||||
Flags: []imap.Flag{imap.FlagSeen},
|
||||
})
|
||||
if _, err := appendCommand.Write([]byte(rawMessage)); err != nil {
|
||||
t.Fatalf("write APPEND message: %v", err)
|
||||
}
|
||||
if err := appendCommand.Close(); err != nil {
|
||||
t.Fatalf("close APPEND message: %v", err)
|
||||
}
|
||||
appendData, err := appendCommand.Wait()
|
||||
if err != nil {
|
||||
t.Fatalf("APPEND message: %v", err)
|
||||
}
|
||||
if appendData == nil || appendData.UID == 0 || appendData.UIDValidity != uint32(models.GroupNameToCode["INBOX"]) {
|
||||
t.Fatalf("unexpected APPEND data: %#v", appendData)
|
||||
}
|
||||
|
||||
var saved models.Email
|
||||
found, err := db.Instance.Where("msg_id = ?", "imap-append-test@test.domain").Get(&saved)
|
||||
if err != nil {
|
||||
t.Fatalf("load appended email: %v", err)
|
||||
}
|
||||
if !found {
|
||||
t.Fatal("appended email was not persisted")
|
||||
}
|
||||
if saved.Subject != "IMAP APPEND test" || saved.Text.String != "Appended message body.\n" {
|
||||
t.Fatalf("unexpected appended email: %#v", saved)
|
||||
}
|
||||
if saved.Type != 0 || saved.Status != 0 {
|
||||
t.Fatalf("unexpected appended email classification: type=%d status=%d", saved.Type, saved.Status)
|
||||
}
|
||||
|
||||
var relation models.UserEmail
|
||||
found, err = db.Instance.Where("email_id = ? and user_id = ?", saved.Id, clientLoginUserID(t)).Get(&relation)
|
||||
if err != nil {
|
||||
t.Fatalf("load appended mailbox relation: %v", err)
|
||||
}
|
||||
if !found || relation.ID != int(appendData.UID) || relation.IsRead != 1 || relation.Status != 0 {
|
||||
t.Fatalf("unexpected appended mailbox relation: %#v", relation)
|
||||
}
|
||||
|
||||
invalidMessage := "not an RFC 822 message"
|
||||
invalidCommand := clientLogin.Append("INBOX", int64(len(invalidMessage)), nil)
|
||||
if _, err := invalidCommand.Write([]byte(invalidMessage)); err != nil {
|
||||
t.Fatalf("write invalid APPEND message: %v", err)
|
||||
}
|
||||
if err := invalidCommand.Close(); err != nil {
|
||||
t.Fatalf("close invalid APPEND message: %v", err)
|
||||
}
|
||||
if _, err := invalidCommand.Wait(); err == nil {
|
||||
t.Fatal("invalid APPEND message unexpectedly succeeded")
|
||||
}
|
||||
}
|
||||
|
||||
func clientLoginUserID(t *testing.T) int {
|
||||
t.Helper()
|
||||
var user models.User
|
||||
found, err := db.Instance.Where("account = ?", "testCase").Get(&user)
|
||||
if err != nil {
|
||||
t.Fatalf("load test user: %v", err)
|
||||
}
|
||||
if !found || user.ID <= 0 {
|
||||
t.Fatal("test user was not found")
|
||||
}
|
||||
return user.ID
|
||||
}
|
||||
func TestSelect(t *testing.T) {
|
||||
res, err := clientUnLogin.Select("INBOX", &imap.SelectOptions{}).Wait()
|
||||
@ -512,18 +585,74 @@ func TestFetch(t *testing.T) {
|
||||
}
|
||||
}
|
||||
func TestStore(t *testing.T) {
|
||||
res, err := clientLogin.Store(
|
||||
imap.UIDSetNum(1),
|
||||
&imap.StoreFlags{
|
||||
Op: imap.StoreFlagsAdd,
|
||||
Flags: []imap.Flag{"\\Seen"},
|
||||
},
|
||||
&imap.StoreOptions{}).Collect()
|
||||
if err != nil {
|
||||
t.Errorf("%+v", err)
|
||||
if _, err := clientLogin.Select("INBOX", &imap.SelectOptions{}).Wait(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Logf("%+v", res)
|
||||
|
||||
userID := clientLoginUserID(t)
|
||||
var relation models.UserEmail
|
||||
found, err := db.Instance.Where("user_id = ? AND status = ? AND group_id = 0", userID, 0).
|
||||
OrderBy("id ASC").Get(&relation)
|
||||
if err != nil {
|
||||
t.Fatalf("load INBOX relation: %v", err)
|
||||
}
|
||||
if !found || relation.ID == 0 {
|
||||
t.Fatal("no INBOX relation available for STORE")
|
||||
}
|
||||
|
||||
uid := imap.UID(relation.ID)
|
||||
if _, err := clientLogin.Store(uidSet(uid), &imap.StoreFlags{
|
||||
Op: imap.StoreFlagsAdd,
|
||||
Flags: []imap.Flag{imap.FlagSeen},
|
||||
}, &imap.StoreOptions{}).Collect(); err != nil {
|
||||
t.Fatalf("add \\Seen: %v", err)
|
||||
}
|
||||
assertReadState(t, relation.ID, userID, 1)
|
||||
|
||||
if _, err := clientLogin.Store(uidSet(uid), &imap.StoreFlags{
|
||||
Op: imap.StoreFlagsDel,
|
||||
Silent: true,
|
||||
Flags: []imap.Flag{imap.FlagSeen},
|
||||
}, &imap.StoreOptions{}).Collect(); err != nil {
|
||||
t.Fatalf("delete \\Seen: %v", err)
|
||||
}
|
||||
assertReadState(t, relation.ID, userID, 0)
|
||||
|
||||
if _, err := clientLogin.Store(uidSet(uid), &imap.StoreFlags{
|
||||
Op: imap.StoreFlagsSet,
|
||||
Flags: nil,
|
||||
}, &imap.StoreOptions{}).Collect(); err != nil {
|
||||
t.Fatalf("set empty flags: %v", err)
|
||||
}
|
||||
assertReadState(t, relation.ID, userID, 0)
|
||||
}
|
||||
|
||||
func uidSet(uid imap.UID) imap.UIDSet {
|
||||
return imap.UIDSetNum(uid)
|
||||
}
|
||||
|
||||
func assertReadState(t *testing.T, relationID, userID int, expected int8) {
|
||||
t.Helper()
|
||||
var relation models.UserEmail
|
||||
found, err := db.Instance.Where("id = ? AND user_id = ?", relationID, userID).Get(&relation)
|
||||
if err != nil {
|
||||
t.Fatalf("load relation read state: %v", err)
|
||||
}
|
||||
if !found || relation.IsRead != expected {
|
||||
t.Fatalf("relation %d read state = %d, want %d", relationID, relation.IsRead, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageIDForEmail(t *testing.T) {
|
||||
stored := &response.EmailResponseData{Email: models.Email{Id: 42, MsgID: "persisted@example.com"}}
|
||||
if got := messageIDForEmail(stored); got != "<persisted@example.com>" {
|
||||
t.Fatalf("stored Message-ID = %q", got)
|
||||
}
|
||||
|
||||
fallback := &response.EmailResponseData{Email: models.Email{Id: 42}}
|
||||
if got := messageIDForEmail(fallback); got != "<42@test.domain>" {
|
||||
t.Fatalf("fallback Message-ID = %q", got)
|
||||
}
|
||||
}
|
||||
func TestClose(t *testing.T) {
|
||||
|
||||
|
||||
@ -1,17 +1,31 @@
|
||||
package imap_server
|
||||
|
||||
import (
|
||||
"git.pcux.cn/xuans/Smail/utils/context"
|
||||
"git.pcux.cn/xuans/Smail/utils/id"
|
||||
"github.com/emersion/go-imap/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.pcux.cn/xuans/Smail/config"
|
||||
"git.pcux.cn/xuans/Smail/consts"
|
||||
"git.pcux.cn/xuans/Smail/db"
|
||||
"git.pcux.cn/xuans/Smail/dto/parsemail"
|
||||
"git.pcux.cn/xuans/Smail/models"
|
||||
"git.pcux.cn/xuans/Smail/services/group"
|
||||
"git.pcux.cn/xuans/Smail/utils/context"
|
||||
"git.pcux.cn/xuans/Smail/utils/id"
|
||||
"github.com/emersion/go-imap/v2"
|
||||
|
||||
"github.com/emersion/go-imap/v2/imapserver"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
const maxIMAPAppendBytes = 30 << 20
|
||||
|
||||
// Server is a server instance.
|
||||
//
|
||||
// A server contains a list of users.
|
||||
@ -73,8 +87,192 @@ func (s *serverSession) Unsubscribe(mailbox string) error {
|
||||
}
|
||||
|
||||
func (s *serverSession) Append(mailbox string, r imap.LiteralReader, options *imap.AppendOptions) (*imap.AppendData, error) {
|
||||
log.WithContext(s.ctx).Errorf("Append Not Implemented")
|
||||
return nil, nil
|
||||
if s == nil || s.ctx == nil || s.ctx.UserID <= 0 || s.status < AUTHORIZED {
|
||||
return nil, &imap.Error{
|
||||
Type: imap.StatusResponseTypeNo,
|
||||
Code: imap.ResponseCodeAuthenticationFailed,
|
||||
Text: "Authentication required",
|
||||
}
|
||||
}
|
||||
if r == nil {
|
||||
return nil, appendBadError("Message content is missing")
|
||||
}
|
||||
|
||||
mailbox = strings.Trim(strings.TrimSpace(mailbox), `"`)
|
||||
if mailbox == "" {
|
||||
return nil, appendBadError("Mailbox is missing")
|
||||
}
|
||||
|
||||
groupID, uidValidity, err := resolveAppendMailbox(s.ctx, mailbox)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.Size() < 0 || r.Size() > maxIMAPAppendBytes {
|
||||
return nil, appendTooBigError()
|
||||
}
|
||||
rawMessage, err := io.ReadAll(io.LimitReader(r, maxIMAPAppendBytes+1))
|
||||
if err != nil {
|
||||
return nil, appendNoError("Read message failed")
|
||||
}
|
||||
if len(rawMessage) == 0 {
|
||||
return nil, appendBadError("Message content is empty")
|
||||
}
|
||||
if len(rawMessage) > maxIMAPAppendBytes {
|
||||
return nil, appendTooBigError()
|
||||
}
|
||||
|
||||
parsed, err := parsemail.NewEmailFromReader(nil, bytes.NewReader(rawMessage), len(rawMessage))
|
||||
if err != nil {
|
||||
return nil, appendNoError("Invalid message content")
|
||||
}
|
||||
if parsed == nil || parsed.From == nil {
|
||||
return nil, appendNoError("Message sender is missing")
|
||||
}
|
||||
|
||||
status, emailType := appendMailboxStatus(mailbox)
|
||||
if emailType == int8(consts.EmailTypeSend) && strings.TrimSpace(parsed.From.EmailAddress) == "" {
|
||||
return nil, appendNoError("Message sender is missing")
|
||||
}
|
||||
|
||||
domains := config.CurrentMailDomains()
|
||||
if parsed.MsgID == "" {
|
||||
if domains.Primary == "" {
|
||||
return nil, appendNoError("Message ID domain is not configured")
|
||||
}
|
||||
parsed.MsgID, err = parsemail.GenerateMsgID(domains.Primary)
|
||||
if err != nil {
|
||||
return nil, appendNoError("Generate message ID failed")
|
||||
}
|
||||
}
|
||||
|
||||
storedFields, err := parsed.EncodeStoredFields()
|
||||
if err != nil {
|
||||
return nil, appendNoError("Encode message failed")
|
||||
}
|
||||
|
||||
appendTime := time.Now()
|
||||
if options != nil && !options.Time.IsZero() {
|
||||
appendTime = options.Time
|
||||
}
|
||||
modelEmail := models.Email{
|
||||
Type: emailType,
|
||||
Subject: parsed.Subject,
|
||||
ReplyTo: storedFields.ReplyTo,
|
||||
FromName: parsed.From.Name,
|
||||
FromAddress: parsed.From.EmailAddress,
|
||||
To: storedFields.To,
|
||||
Bcc: storedFields.Bcc,
|
||||
Cc: storedFields.Cc,
|
||||
Text: sql.NullString{String: string(parsed.Text), Valid: true},
|
||||
Html: sql.NullString{String: string(parsed.HTML), Valid: true},
|
||||
Sender: storedFields.Sender,
|
||||
Attachments: storedFields.Attachments,
|
||||
Size: len(rawMessage),
|
||||
Status: status,
|
||||
SendUserID: s.ctx.UserID,
|
||||
SendDate: appendTime,
|
||||
CreateTime: appendTime,
|
||||
CronSendTime: appendTime,
|
||||
MsgID: parsed.MsgID,
|
||||
}
|
||||
|
||||
var savedUID int
|
||||
if err := db.Transaction(db.Instance, func(session *xorm.Session) error {
|
||||
candidate := modelEmail
|
||||
candidate.Id = 0
|
||||
if _, err := session.Insert(&candidate); err != nil {
|
||||
return fmt.Errorf("insert appended email: %w", err)
|
||||
}
|
||||
if candidate.Id <= 0 {
|
||||
return fmt.Errorf("insert appended email returned no id")
|
||||
}
|
||||
relation := models.UserEmail{
|
||||
UserID: s.ctx.UserID,
|
||||
EmailID: candidate.Id,
|
||||
IsRead: appendMessageRead(options),
|
||||
GroupId: groupID,
|
||||
Status: status,
|
||||
}
|
||||
if _, err := session.Insert(&relation); err != nil {
|
||||
return fmt.Errorf("insert appended mailbox relation: %w", err)
|
||||
}
|
||||
savedUID = relation.ID
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, appendNoError("Save message failed")
|
||||
}
|
||||
|
||||
return &imap.AppendData{
|
||||
UID: imap.UID(savedUID),
|
||||
UIDValidity: uint32(uidValidity),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func resolveAppendMailbox(ctx *context.Context, mailbox string) (int, int, error) {
|
||||
if group.IsDefaultBox(mailbox) {
|
||||
uidValidity, ok := models.GroupNameToCode[mailbox]
|
||||
if !ok {
|
||||
return 0, 0, appendNoError("Mailbox not found")
|
||||
}
|
||||
return 0, uidValidity, nil
|
||||
}
|
||||
mailboxGroup, err := group.GetGroupByFullPath(ctx, mailbox)
|
||||
if err != nil {
|
||||
return 0, 0, appendNoError("Mailbox lookup failed")
|
||||
}
|
||||
if mailboxGroup == nil || mailboxGroup.ID <= 0 || mailboxGroup.UserId != ctx.UserID {
|
||||
return 0, 0, &imap.Error{
|
||||
Type: imap.StatusResponseTypeNo,
|
||||
Code: imap.ResponseCodeTryCreate,
|
||||
Text: "No such mailbox",
|
||||
}
|
||||
}
|
||||
return mailboxGroup.ID, mailboxGroup.ID, nil
|
||||
}
|
||||
|
||||
func appendMailboxStatus(mailbox string) (int8, int8) {
|
||||
switch mailbox {
|
||||
case "Sent Messages", "Drafts":
|
||||
if mailbox == "Drafts" {
|
||||
return consts.EmailStatusDrafts, consts.EmailTypeSend
|
||||
}
|
||||
return consts.EmailStatusSent, consts.EmailTypeSend
|
||||
case "Deleted Messages":
|
||||
return consts.EmailStatusDel, consts.EmailTypeReceive
|
||||
case "Junk":
|
||||
return consts.EmailStatusJunk, consts.EmailTypeReceive
|
||||
default:
|
||||
return consts.EmailStatusWait, consts.EmailTypeReceive
|
||||
}
|
||||
}
|
||||
|
||||
func appendMessageRead(options *imap.AppendOptions) int8 {
|
||||
if options == nil {
|
||||
return 0
|
||||
}
|
||||
for _, flag := range options.Flags {
|
||||
if flag == imap.FlagSeen {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func appendBadError(text string) error {
|
||||
return &imap.Error{Type: imap.StatusResponseTypeBad, Text: text}
|
||||
}
|
||||
|
||||
func appendNoError(text string) error {
|
||||
return &imap.Error{Type: imap.StatusResponseTypeNo, Text: text}
|
||||
}
|
||||
|
||||
func appendTooBigError() error {
|
||||
return &imap.Error{
|
||||
Type: imap.StatusResponseTypeNo,
|
||||
Code: imap.ResponseCodeTooBig,
|
||||
Text: "Message exceeds the maximum size",
|
||||
}
|
||||
}
|
||||
|
||||
func (s *serverSession) Unselect() error {
|
||||
|
||||
@ -91,16 +91,9 @@ func buildEnvelope(email *response.EmailResponseData, traEmail *parsemail.Email)
|
||||
replyTo = from
|
||||
}
|
||||
|
||||
// Message-ID: use the persisted value so IMAP clients see a stable ID on every sync.
|
||||
var messageID string
|
||||
if email.MsgID != "" {
|
||||
messageID = fmt.Sprintf("<%s>", email.MsgID)
|
||||
} else {
|
||||
cfg := config.CurrentConfig()
|
||||
if cfg == nil || cfg.Domain == "" {
|
||||
return nil
|
||||
}
|
||||
messageID = fmt.Sprintf("<%d@%s>", email.Id, cfg.Domain)
|
||||
messageID := messageIDForEmail(email)
|
||||
if messageID == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &imap.Envelope{
|
||||
@ -117,6 +110,20 @@ func buildEnvelope(email *response.EmailResponseData, traEmail *parsemail.Email)
|
||||
}
|
||||
}
|
||||
|
||||
func messageIDForEmail(email *response.EmailResponseData) string {
|
||||
if email == nil {
|
||||
return ""
|
||||
}
|
||||
if strings.TrimSpace(email.MsgID) != "" {
|
||||
return fmt.Sprintf("<%s>", strings.TrimSpace(email.MsgID))
|
||||
}
|
||||
cfg := config.CurrentConfig()
|
||||
if cfg == nil || strings.TrimSpace(cfg.Domain) == "" || email.Id <= 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("<%d@%s>", email.Id, strings.TrimSpace(cfg.Domain))
|
||||
}
|
||||
|
||||
func (s *serverSession) Fetch(w *imapserver.FetchWriter, numSet imap.NumSet, options *imap.FetchOptions) error {
|
||||
switch numSet.(type) {
|
||||
case imap.SeqSet:
|
||||
@ -399,11 +406,11 @@ func write(ctx *context.Context, w *imapserver.FetchWriter, emailList []*respons
|
||||
fmt.Fprintf(&b, "Bcc: %s\r\n", traEmail.BuildBcc2String())
|
||||
}
|
||||
case "message-id":
|
||||
cfg := config.CurrentConfig()
|
||||
if cfg == nil || cfg.Domain == "" {
|
||||
messageID := messageIDForEmail(email)
|
||||
if messageID == "" {
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintf(&b, "Message-ID: <%d@%s>\r\n", email.Id, cfg.Domain)
|
||||
fmt.Fprintf(&b, "Message-ID: %s\r\n", messageID)
|
||||
case "content-type":
|
||||
args := strings.SplitN(string(emailContent), "\r\n", 3)
|
||||
if len(args) >= 2 {
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
package imap_server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.pcux.cn/xuans/Smail/db"
|
||||
"git.pcux.cn/xuans/Smail/dto/response"
|
||||
"git.pcux.cn/xuans/Smail/models"
|
||||
"git.pcux.cn/xuans/Smail/services/del_email"
|
||||
"git.pcux.cn/xuans/Smail/services/detail"
|
||||
"git.pcux.cn/xuans/Smail/services/list"
|
||||
"git.pcux.cn/xuans/Smail/utils/array"
|
||||
"github.com/emersion/go-imap/v2"
|
||||
@ -13,9 +16,8 @@ import (
|
||||
)
|
||||
|
||||
func (s *serverSession) Store(w *imapserver.FetchWriter, numSet imap.NumSet, flags *imap.StoreFlags, options *imap.StoreOptions) error {
|
||||
|
||||
if flags.Op == imap.StoreFlagsSet {
|
||||
return nil
|
||||
if s == nil || s.ctx == nil || s.ctx.UserID <= 0 || flags == nil {
|
||||
return &imap.Error{Type: imap.StatusResponseTypeNo, Text: "Store failed"}
|
||||
}
|
||||
|
||||
var emailList []*response.EmailResponseData
|
||||
@ -50,28 +52,88 @@ func (s *serverSession) Store(w *imapserver.FetchWriter, numSet imap.NumSet, fla
|
||||
}
|
||||
}
|
||||
|
||||
if array.InArray(imap.FlagSeen, flags.Flags) && flags.Op == imap.StoreFlagsAdd {
|
||||
seenRequested := array.InArray(imap.FlagSeen, flags.Flags)
|
||||
deletedRequested := array.InArray(imap.FlagDeleted, flags.Flags)
|
||||
updateReadState := false
|
||||
readState := false
|
||||
switch flags.Op {
|
||||
case imap.StoreFlagsSet:
|
||||
updateReadState = true
|
||||
readState = seenRequested
|
||||
case imap.StoreFlagsAdd:
|
||||
updateReadState = seenRequested
|
||||
readState = true
|
||||
case imap.StoreFlagsDel:
|
||||
updateReadState = seenRequested
|
||||
readState = false
|
||||
default:
|
||||
return &imap.Error{Type: imap.StatusResponseTypeBad, Text: "Unsupported STORE operation"}
|
||||
}
|
||||
|
||||
if updateReadState {
|
||||
for _, data := range emailList {
|
||||
if err := detail.MakeRead(s.ctx, data.Id, flags.Op == imap.StoreFlagsAdd); err != nil {
|
||||
if err := updateReadByUID(s.ctx.UserID, data.UeId, readState); err != nil {
|
||||
log.WithContext(s.ctx).WithError(err).Error("update IMAP message read state failed")
|
||||
return &imap.Error{Type: imap.StatusResponseTypeNo, Text: "Store failed"}
|
||||
}
|
||||
data.IsRead = 0
|
||||
if readState {
|
||||
data.IsRead = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if array.InArray(imap.FlagDeleted, flags.Flags) && flags.Op == imap.StoreFlagsAdd {
|
||||
if deletedRequested && flags.Op == imap.StoreFlagsAdd {
|
||||
for _, data := range emailList {
|
||||
s.deleteUidList = append(s.deleteUidList, data.UeId)
|
||||
}
|
||||
}
|
||||
|
||||
err := del_email.DelByUID(s.ctx, s.deleteUidList)
|
||||
s.deleteUidList = []int{}
|
||||
if len(s.deleteUidList) > 0 {
|
||||
err := del_email.DelByUID(s.ctx, s.deleteUidList)
|
||||
s.deleteUidList = []int{}
|
||||
if err != nil {
|
||||
log.WithContext(s.ctx).WithError(err).Error("delete IMAP message failed")
|
||||
return &imap.Error{Type: imap.StatusResponseTypeNo, Text: "Store failed"}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.WithContext(s.ctx).WithError(err).Error("delete IMAP message failed")
|
||||
return &imap.Error{Type: imap.StatusResponseTypeNo, Text: "Store failed"}
|
||||
if !flags.Silent && w != nil {
|
||||
for _, data := range emailList {
|
||||
sequence, err := imapUint32(data.SerialNumber)
|
||||
if err != nil || sequence == 0 {
|
||||
return &imap.Error{Type: imap.StatusResponseTypeNo, Text: "Store failed"}
|
||||
}
|
||||
message := w.CreateMessage(sequence)
|
||||
if data.IsRead == 1 {
|
||||
message.WriteFlags([]imap.Flag{imap.FlagSeen})
|
||||
} else {
|
||||
message.WriteFlags(nil)
|
||||
}
|
||||
if err := message.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateReadByUID(userID, relationID int, read bool) error {
|
||||
if userID <= 0 || relationID <= 0 {
|
||||
return fmt.Errorf("invalid mailbox relation")
|
||||
}
|
||||
value := int8(0)
|
||||
if read {
|
||||
value = 1
|
||||
}
|
||||
affected, err := db.Instance.Where("id = ? and user_id = ?", relationID, userID).
|
||||
Cols("is_read").Update(&models.UserEmail{IsRead: value})
|
||||
if err != nil {
|
||||
return fmt.Errorf("update mailbox relation read state: %w", err)
|
||||
}
|
||||
if affected != 1 {
|
||||
return fmt.Errorf("mailbox relation not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user