修复POP3协议会话和删除行为
Some checks are pending
Security Checks / security (push) Waiting to run

This commit is contained in:
smail 2026-09-06 05:38:09 +08:00
parent 64b1aebef3
commit bf2d7b6e2d
3 changed files with 157 additions and 76 deletions

View File

@ -234,6 +234,8 @@ func (s *Server) handleClient(conn net.Conn) {
infos, err := s.Action.List(session, getSafeArg(args, 0))
if err != nil {
fmt.Fprintf(conn, "-ERR %s %s", err.Error(), eol)
} else if len(args) > 0 && len(infos) == 1 {
fmt.Fprintf(conn, "+OK %d %d%s", infos[0].Id, infos[0].Size, eol)
} else {
ret := "+OK" + eol
for _, info := range infos {
@ -248,6 +250,8 @@ func (s *Server) handleClient(conn net.Conn) {
infos, err := s.Action.Uidl(session, getSafeArg(args, 0))
if err != nil {
fmt.Fprintf(conn, "-ERR %s %s", err.Error(), eol)
} else if len(args) > 0 && len(infos) == 1 {
fmt.Fprintf(conn, "+OK %d %s%s", infos[0].Id, infos[0].UnionId, eol)
} else {
ret := fmt.Sprintf("+OK%s", eol)
for _, info := range infos {
@ -261,7 +265,7 @@ func (s *Server) handleClient(conn net.Conn) {
if session.Status == TRANSACTION {
id, err1 := strconv.ParseInt(getSafeArg(args, 0), 10, 64)
line, err2 := strconv.Atoi(getSafeArg(args, 1))
if err1 != nil || err2 != nil {
if err1 != nil || err2 != nil || line < 0 {
fmt.Fprintf(conn, "-ERR %s %s", "params error", eol)
} else {
res, err := s.Action.Top(session, id, line)
@ -306,7 +310,7 @@ func (s *Server) handleClient(conn net.Conn) {
}
}
}
case "REST":
case "RSET", "REST":
if session.Status == TRANSACTION {
err := s.Action.Rest(session)
if err != nil {

View File

@ -15,11 +15,13 @@ const (
)
type Session struct {
Status Status
User string
DeleteIds []int64
Ctx context.Context
Conn net.Conn
InTls bool
AliveTime time.Time
Status Status
User string
DeleteIds []int64
MessageIDs []int64
MessageMap map[int64]int64
Ctx context.Context
Conn net.Conn
InTls bool
AliveTime time.Time
}

View File

@ -5,6 +5,7 @@ import (
"git.pcux.cn/xuans/Smail/consts"
"git.pcux.cn/xuans/Smail/dto"
"git.pcux.cn/xuans/Smail/dto/parsemail"
"git.pcux.cn/xuans/Smail/dto/response"
"git.pcux.cn/xuans/Smail/internal/gopop"
"git.pcux.cn/xuans/Smail/services/access"
"git.pcux.cn/xuans/Smail/services/auth"
@ -17,6 +18,7 @@ import (
"git.pcux.cn/xuans/Smail/utils/id"
log "github.com/sirupsen/logrus"
"github.com/spf13/cast"
"strconv"
"strings"
)
@ -129,52 +131,41 @@ func (a action) Apop(session *gopop.Session, username, digest string) error {
}
func (a action) Stat(session *gopop.Session) (msgNum, msgSize int64, err error) {
log.WithContext(session.Ctx).Debugf("POP3 CMD: STAT")
num, size, err := list.Stat(session.Ctx.(*context.Context))
emailList, err := a.visibleMessages(session)
if err != nil {
return 0, 0, err
}
log.WithContext(session.Ctx).Debugf("POP3 STAT RETURT : %d,%d", num, size)
return num, size, nil
for _, info := range emailList {
msgNum++
msgSize += int64(info.Size)
}
log.WithContext(session.Ctx).Debugf("POP3 STAT RETURT : %d,%d", msgNum, msgSize)
return msgNum, msgSize, nil
}
// Uidl 查询某封邮件的唯一标志符
func (a action) Uidl(session *gopop.Session, msg string) ([]gopop.UidlItem, error) {
log.WithContext(session.Ctx).Debugf("POP3 CMD: UIDL ,Args:%s", msg)
reqId := cast.ToInt64(msg)
if reqId > 0 {
info, err := detail.GetEmailDetail(session.Ctx.(*context.Context), cast.ToInt(reqId), false)
if err != nil || info == nil {
return nil, errors2.New("no such message")
if msg != "" {
number, err := parseMessageNumber(msg)
if err != nil {
return nil, err
}
log.WithContext(session.Ctx).Debugf("Uidl \n %+v", reqId)
return []gopop.UidlItem{
{
Id: reqId,
UnionId: msg,
},
}, nil
id, err := a.resolveMessageID(session, int64(number))
if err != nil {
return nil, err
}
return []gopop.UidlItem{{Id: int64(number), UnionId: cast.ToString(id)}}, nil
}
var res []listItem
emailList, _, err := list.GetEmailList(session.Ctx.(*context.Context), dto.SearchTag{Type: consts.EmailTypeReceive, Status: -1, GroupId: -1}, "", true, 0, 99999)
emailList, err := a.visibleMessages(session)
if err != nil {
return nil, err
}
for _, info := range emailList {
res = append(res, listItem{
Id: cast.ToInt64(info.Id),
Size: cast.ToInt64(info.Size),
})
}
ret := []gopop.UidlItem{}
for _, re := range res {
ret = append(ret, gopop.UidlItem{
Id: re.Id,
UnionId: cast.ToString(re.Id),
})
ret := make([]gopop.UidlItem, 0, len(emailList))
for number, info := range emailList {
ret = append(ret, gopop.UidlItem{Id: int64(number + 1), UnionId: cast.ToString(info.Id)})
}
log.WithContext(session.Ctx).Debugf("Uidl \n %+v", ret)
@ -189,49 +180,41 @@ type listItem struct {
// List 邮件列表
func (a action) List(session *gopop.Session, msg string) ([]gopop.MailInfo, error) {
log.WithContext(session.Ctx).Debugf("POP3 CMD: LIST ,Args:%s", msg)
var res []listItem
var listId int
requestedNumber := 0
if msg != "" {
listId = cast.ToInt(msg)
if listId == 0 {
return nil, errors.New("params error")
var err error
requestedNumber, err = parseMessageNumber(msg)
if err != nil {
return nil, err
}
}
if listId != 0 {
info, err := detail.GetEmailDetail(session.Ctx.(*context.Context), listId, false)
if err != nil {
return nil, err
}
item := listItem{
Id: cast.ToInt64(info.Id),
Size: cast.ToInt64(info.Size),
}
if item.Size == 0 {
item.Size = 9999
}
res = append(res, item)
} else {
emailList, _, err := list.GetEmailList(session.Ctx.(*context.Context), dto.SearchTag{Type: consts.EmailTypeReceive, Status: -1, GroupId: -1}, "", true, 0, 99999)
emailList, err := a.visibleMessages(session)
if err != nil {
return nil, err
}
if requestedNumber > 0 {
id, err := a.resolveMessageID(session, int64(requestedNumber))
if err != nil {
return nil, err
}
filtered := make([]*response.EmailResponseData, 0, 1)
for _, info := range emailList {
item := listItem{
Id: cast.ToInt64(info.Id),
Size: cast.ToInt64(info.Size),
if cast.ToInt64(info.Id) == id {
filtered = append(filtered, info)
break
}
if item.Size == 0 {
item.Size = 9999
}
res = append(res, item)
}
emailList = filtered
}
ret := []gopop.MailInfo{}
for _, re := range res {
ret := make([]gopop.MailInfo, 0, len(emailList))
for number, info := range emailList {
size := int64(info.Size)
if size == 0 {
size = 9999
}
ret = append(ret, gopop.MailInfo{
Id: re.Id,
Size: re.Size,
Id: int64(number + 1),
Size: size,
})
}
@ -242,7 +225,11 @@ func (a action) List(session *gopop.Session, msg string) ([]gopop.MailInfo, erro
// Retr 获取邮件详情
func (a action) Retr(session *gopop.Session, id int64) (string, int64, error) {
log.WithContext(session.Ctx).Debugf("POP3 CMD: RETR ,Args:%d", id)
email, err := detail.GetEmailDetail(session.Ctx.(*context.Context), cast.ToInt(id), false)
actualID, err := a.resolveMessageID(session, id)
if err != nil {
return "", 0, err
}
email, err := detail.GetEmailDetail(session.Ctx.(*context.Context), cast.ToInt(actualID), false)
if err != nil {
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
return "", 0, errors.New("server error")
@ -266,8 +253,16 @@ func (a action) Retr(session *gopop.Session, id int64) (string, int64, error) {
// Delete 删除邮件
func (a action) Delete(session *gopop.Session, id int64) error {
log.WithContext(session.Ctx).Debugf("POP3 CMD: DELE ,Args:%d", id)
session.DeleteIds = append(session.DeleteIds, id)
actualID, err := a.resolveMessageID(session, id)
if err != nil {
return err
}
for _, deletedID := range session.DeleteIds {
if deletedID == actualID {
return errors2.New("message already marked for deletion")
}
}
session.DeleteIds = append(session.DeleteIds, actualID)
session.DeleteIds = array.Unique(session.DeleteIds)
return nil
}
@ -280,7 +275,14 @@ func (a action) Rest(session *gopop.Session) error {
func (a action) Top(session *gopop.Session, id int64, n int) (string, error) {
log.WithContext(session.Ctx).Debugf("POP3 CMD: TOP %d %d", id, n)
email, err := detail.GetEmailDetail(session.Ctx.(*context.Context), cast.ToInt(id), false)
if n < 0 {
return "", errors2.New("invalid line count")
}
actualID, err := a.resolveMessageID(session, id)
if err != nil {
return "", err
}
email, err := detail.GetEmailDetail(session.Ctx.(*context.Context), cast.ToInt(actualID), false)
if err != nil {
log.WithContext(session.Ctx.(*context.Context)).Errorf("%+v", err)
return "", errors2.New("password error")
@ -314,6 +316,79 @@ func (a action) Top(session *gopop.Session, id int64, n int) (string, error) {
}
func parseMessageNumber(value string) (int, error) {
number, err := strconv.Atoi(strings.TrimSpace(value))
if err != nil || number <= 0 {
return 0, errors2.New("no such message")
}
return number, nil
}
func (a action) ensureMessageMap(session *gopop.Session) ([]*response.EmailResponseData, error) {
if session.MessageMap != nil {
return nil, nil
}
emailList, _, err := list.GetEmailList(session.Ctx.(*context.Context), dto.SearchTag{Type: consts.EmailTypeReceive, Status: -1, GroupId: -1}, "", true, 0, 99999)
if err != nil {
return nil, err
}
session.MessageMap = make(map[int64]int64, len(emailList))
session.MessageIDs = make([]int64, 0, len(emailList))
for number, info := range emailList {
id := cast.ToInt64(info.Id)
session.MessageMap[int64(number+1)] = id
session.MessageIDs = append(session.MessageIDs, id)
}
return emailList, nil
}
func (a action) resolveMessageID(session *gopop.Session, number int64) (int64, error) {
if number <= 0 {
return 0, errors2.New("no such message")
}
if _, err := a.ensureMessageMap(session); err != nil {
return 0, err
}
id, ok := session.MessageMap[number]
if !ok {
return 0, errors2.New("no such message")
}
for _, deletedID := range session.DeleteIds {
if deletedID == id {
return 0, errors2.New("message already marked for deletion")
}
}
return id, nil
}
func (a action) visibleMessages(session *gopop.Session) ([]*response.EmailResponseData, error) {
emailList, err := a.ensureMessageMap(session)
if err != nil {
return nil, err
}
if emailList == nil {
emailList, _, err = list.GetEmailList(session.Ctx.(*context.Context), dto.SearchTag{Type: consts.EmailTypeReceive, Status: -1, GroupId: -1}, "", true, 0, 99999)
if err != nil {
return nil, err
}
}
result := make([]*response.EmailResponseData, 0, len(emailList))
for _, info := range emailList {
id := cast.ToInt64(info.Id)
marked := false
for _, deletedID := range session.DeleteIds {
if deletedID == id {
marked = true
break
}
}
if !marked {
result = append(result, info)
}
}
return result, nil
}
func (a action) Noop(session *gopop.Session) error {
log.WithContext(session.Ctx).Debugf("POP3 CMD: NOOP ")
return nil