This commit is contained in:
parent
a4e2857cee
commit
7fc0e1ca8f
@ -670,19 +670,15 @@ func parseLegacyMySQLDSN(value string) (DatabaseConfig, string, bool) {
|
||||
if matches == nil {
|
||||
return DatabaseConfig{}, "", false
|
||||
}
|
||||
port := int(DefaultMySQLPort)
|
||||
if matches[4] != "" {
|
||||
parsed, err := strconv.Atoi(matches[4])
|
||||
if err != nil || !validPort(parsed) {
|
||||
port, valid := parseLegacyDatabasePort(matches[4], DefaultMySQLPort)
|
||||
if !valid {
|
||||
return DatabaseConfig{}, "", false
|
||||
}
|
||||
port = parsed
|
||||
}
|
||||
host, ok := normalizeLegacyDatabaseHost(matches[3])
|
||||
if !ok || !validLegacyDatabaseUser(matches[1]) || !validLegacyDatabasePassword(matches[2]) {
|
||||
return DatabaseConfig{}, "", false
|
||||
}
|
||||
return DatabaseConfig{Type: DatabaseTypeMySQL, Host: host, Port: uint16(port), Username: matches[1], Name: matches[5]}, matches[2], true
|
||||
return DatabaseConfig{Type: DatabaseTypeMySQL, Host: host, Port: port, Username: matches[1], Name: matches[5]}, matches[2], true
|
||||
}
|
||||
|
||||
func parseLegacyPostgreSQLDSN(value string) (DatabaseConfig, string, bool) {
|
||||
@ -699,19 +695,27 @@ func parseLegacyPostgreSQLDSN(value string) (DatabaseConfig, string, bool) {
|
||||
if !ok {
|
||||
return DatabaseConfig{}, "", false
|
||||
}
|
||||
port := int(DefaultPostgreSQLPort)
|
||||
if rawPort := parsed.Port(); rawPort != "" {
|
||||
parsedPort, parseErr := strconv.Atoi(rawPort)
|
||||
if parseErr != nil || !validPort(parsedPort) {
|
||||
port, valid := parseLegacyDatabasePort(parsed.Port(), DefaultPostgreSQLPort)
|
||||
if !valid {
|
||||
return DatabaseConfig{}, "", false
|
||||
}
|
||||
port = parsedPort
|
||||
}
|
||||
name := strings.TrimPrefix(parsed.EscapedPath(), "/")
|
||||
if name == "" || strings.Contains(name, "/") || !databaseLocalRefPattern.MatchString(name) {
|
||||
return DatabaseConfig{}, "", false
|
||||
}
|
||||
return DatabaseConfig{Type: DatabaseTypePostgreSQL, Host: host, Port: uint16(port), Username: username, Name: name}, password, true
|
||||
return DatabaseConfig{Type: DatabaseTypePostgreSQL, Host: host, Port: port, Username: username, Name: name}, password, true
|
||||
}
|
||||
|
||||
// 直接按端口位宽解析,避免先解析为平台整数再缩窄。
|
||||
func parseLegacyDatabasePort(value string, fallback uint16) (uint16, bool) {
|
||||
if value == "" {
|
||||
return fallback, fallback != 0
|
||||
}
|
||||
port, err := strconv.ParseUint(value, 10, 16)
|
||||
if err != nil || port == 0 {
|
||||
return 0, false
|
||||
}
|
||||
return uint16(port), true
|
||||
}
|
||||
|
||||
func normalizeLegacyDatabaseHost(value string) (string, bool) {
|
||||
|
||||
36
backend/config/migrate_port_test.go
Normal file
36
backend/config/migrate_port_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
package config
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestLegacyDatabasePortBoundaries(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
value string
|
||||
want uint16
|
||||
valid bool
|
||||
}{
|
||||
{value: "", want: 3306, valid: true},
|
||||
{value: "1", want: 1, valid: true},
|
||||
{value: "65535", want: 65535, valid: true},
|
||||
{value: "0"},
|
||||
{value: "-1"},
|
||||
{value: "65536"},
|
||||
{value: "18446744073709551616"},
|
||||
{value: " 3306"},
|
||||
} {
|
||||
port, valid := parseLegacyDatabasePort(test.value, DefaultMySQLPort)
|
||||
if port != test.want || valid != test.valid {
|
||||
t.Fatalf("端口 %q 的解析结果错误:%d %t", test.value, port, valid)
|
||||
}
|
||||
}
|
||||
for _, value := range []string{"0", "65536", "18446744073709551616"} {
|
||||
if _, _, valid := parseLegacyMySQLDSN("smail:sample@tcp(db.example.test:" + value + ")/smail"); valid {
|
||||
t.Fatal("MySQL 连接串接受了越界端口")
|
||||
}
|
||||
if _, _, valid := parseLegacyPostgreSQLDSN("postgres://smail:sample@db.example.test:" + value + "/smail"); valid {
|
||||
t.Fatal("PostgreSQL 连接串接受了越界端口")
|
||||
}
|
||||
}
|
||||
if _, valid := parseLegacyDatabasePort("", 0); valid {
|
||||
t.Fatal("默认端口不能为零")
|
||||
}
|
||||
}
|
||||
@ -22,3 +22,20 @@ func imapUID(value int) (imap.UID, error) {
|
||||
}
|
||||
return imap.UID(number), nil
|
||||
}
|
||||
|
||||
// 在发送更新、选择邮件或删除数据之前校验整个会话快照,禁止截断后指向另一封邮件。
|
||||
func imapSnapshotBounds(uids []int) (uint32, uint32, error) {
|
||||
count, err := imapUint32(len(uids))
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
var maximum uint32
|
||||
for _, value := range uids {
|
||||
uid, err := imapUID(value)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
maximum = max(maximum, uint32(uid))
|
||||
}
|
||||
return maximum, count, nil
|
||||
}
|
||||
|
||||
114
backend/listen/imap_server/numbers_test.go
Normal file
114
backend/listen/imap_server/numbers_test.go
Normal file
@ -0,0 +1,114 @@
|
||||
package imap_server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"git.pcux.cn/xuans/Smail/db"
|
||||
"git.pcux.cn/xuans/Smail/models"
|
||||
"git.pcux.cn/xuans/Smail/utils/context"
|
||||
"github.com/emersion/go-imap/v2"
|
||||
)
|
||||
|
||||
func TestIMAPSnapshotBounds(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
uids []int
|
||||
maximum uint32
|
||||
invalid bool
|
||||
}{
|
||||
{name: "empty"},
|
||||
{name: "valid", uids: []int{7, 2, 9}, maximum: 9},
|
||||
{name: "zero", uids: []int{7, 0}, invalid: true},
|
||||
{name: "negative", uids: []int{7, -1}, invalid: true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
maximum, count, err := imapSnapshotBounds(test.uids)
|
||||
if test.invalid {
|
||||
if err == nil || maximum != 0 || count != 0 {
|
||||
t.Fatal("非法快照必须完整拒绝")
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil || maximum != test.maximum || int(count) != len(test.uids) {
|
||||
t.Fatalf("快照边界错误:%d %d %v", maximum, count, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIMAPRejectsOverflowBeforeProtocolSideEffects(t *testing.T) {
|
||||
if strconv.IntSize < 64 {
|
||||
t.Skip("此用例验证大于协议上限的六十四位数据库标识")
|
||||
}
|
||||
limit := uint64(math.MaxUint32)
|
||||
if maximum, count, err := imapSnapshotBounds([]int{int(limit)}); err != nil || maximum != math.MaxUint32 || count != 1 {
|
||||
t.Fatalf("最大合法 UID 被拒绝:%d %d %v", maximum, count, err)
|
||||
}
|
||||
for _, uid := range []int{0, -1, int(limit + 1), int(limit + 2)} {
|
||||
snapshot := []int{1, uid}
|
||||
session := &serverSession{ctx: &context.Context{UserID: 1}, currentMailbox: "INBOX", mailboxUIDs: append([]int{}, snapshot...)}
|
||||
if _, err := session.selectedMessages(imap.UIDSetNum(1)); err == nil {
|
||||
t.Fatal("邮件选择未拒绝非法快照")
|
||||
}
|
||||
if err := session.poll(nil, true, true); err == nil {
|
||||
t.Fatal("轮询未在发送更新前拒绝非法快照")
|
||||
}
|
||||
if err := session.Expunge(nil, nil); err == nil {
|
||||
t.Fatal("删除未在写数据库前拒绝非法快照")
|
||||
}
|
||||
if !reflect.DeepEqual(session.mailboxUIDs, snapshot) {
|
||||
t.Fatal("拒绝非法快照时修改了会话状态")
|
||||
}
|
||||
}
|
||||
set := imap.UIDSet{{Start: 0, Stop: 0}}
|
||||
if _, err := expandIMAPUIDSet(&set, int(limit+1)); err == nil {
|
||||
t.Fatal("动态范围上界发生截断")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAppendUIDOverflowRollsBackMessageAndRelation(t *testing.T) {
|
||||
if strconv.IntSize < 64 {
|
||||
t.Skip("此用例验证六十四位数据库自增值超过协议上限")
|
||||
}
|
||||
rows, err := db.Instance.QueryString("SELECT seq FROM sqlite_sequence WHERE name = 'user_email'")
|
||||
if err != nil || len(rows) != 1 {
|
||||
t.Fatalf("读取测试自增序列失败:%v", err)
|
||||
}
|
||||
previous := rows[0]["seq"]
|
||||
defer func() {
|
||||
if _, err := db.Instance.Exec("UPDATE sqlite_sequence SET seq = ? WHERE name = 'user_email'", previous); err != nil {
|
||||
t.Errorf("恢复测试序列失败:%v", err)
|
||||
}
|
||||
}()
|
||||
if _, err := db.Instance.Exec("UPDATE sqlite_sequence SET seq = ? WHERE name = 'user_email'", int64(math.MaxUint32)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
beforeMessages, err := db.Instance.Count(new(models.Email))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
beforeRelations, err := db.Instance.Count(new(models.UserEmail))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
message := []byte("From: sender@test.domain\r\nTo: testCase@test.domain\r\nSubject: boundary\r\nMessage-ID: <uid-overflow@test.domain>\r\n\r\nbody\r\n")
|
||||
session := &serverSession{ctx: &context.Context{UserID: clientLoginUserID(t)}, status: AUTHORIZED}
|
||||
if data, err := session.Append("INBOX", bytes.NewReader(message), nil); err == nil || data != nil {
|
||||
t.Fatal("越界 APPEND 不应提交或返回 UID")
|
||||
}
|
||||
afterMessages, err := db.Instance.Count(new(models.Email))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
afterRelations, err := db.Instance.Count(new(models.UserEmail))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if beforeMessages != afterMessages || beforeRelations != afterRelations {
|
||||
t.Fatal("失败 APPEND 留下了邮件或关联记录")
|
||||
}
|
||||
}
|
||||
@ -110,6 +110,10 @@ func (s *serverSession) Append(mailbox string, r imap.LiteralReader, options *im
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
validity, err := imapUID(uidValidity)
|
||||
if err != nil {
|
||||
return nil, appendNoError("邮箱标识超出协议允许范围")
|
||||
}
|
||||
|
||||
if r.Size() < 0 || r.Size() > maxIMAPAppendBytes {
|
||||
return nil, appendTooBigError()
|
||||
@ -180,7 +184,7 @@ func (s *serverSession) Append(mailbox string, r imap.LiteralReader, options *im
|
||||
MsgID: parsed.MsgID,
|
||||
}
|
||||
|
||||
var savedUID int
|
||||
var savedUID imap.UID
|
||||
if err := db.Transaction(db.Instance, func(session *xorm.Session) error {
|
||||
candidate := modelEmail
|
||||
candidate.Id = 0
|
||||
@ -200,15 +204,19 @@ func (s *serverSession) Append(mailbox string, r imap.LiteralReader, options *im
|
||||
if _, err := session.Insert(&relation); err != nil {
|
||||
return fmt.Errorf("insert appended mailbox relation: %w", err)
|
||||
}
|
||||
savedUID = relation.ID
|
||||
// 必须在事务提交前拒绝越界 UID,不能留下客户端无法寻址的邮件。
|
||||
savedUID, err = imapUID(relation.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, appendNoError("Save message failed")
|
||||
}
|
||||
|
||||
return &imap.AppendData{
|
||||
UID: imap.UID(savedUID),
|
||||
UIDValidity: uint32(uidValidity),
|
||||
UID: savedUID,
|
||||
UIDValidity: uint32(validity),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@ -34,16 +34,20 @@ func (s *serverSession) Expunge(w *imapserver.ExpungeWriter, uids *imap.UIDSet)
|
||||
return &imap.Error{Type: imap.StatusResponseTypeNo, Text: "Expunge failed"}
|
||||
}
|
||||
// 只匹配会话内真实存在的 UID,不展开巨大或稀疏区间。
|
||||
var maximum uint32
|
||||
for _, uid := range snapshot {
|
||||
maximum = max(maximum, uint32(uid))
|
||||
maximum, _, err := imapSnapshotBounds(snapshot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestedUIDs := make([]int, 0, len(snapshot))
|
||||
for _, uid := range snapshot {
|
||||
number, err := imapUID(uid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
matched := uids == nil
|
||||
if uids != nil {
|
||||
for _, span := range *uids {
|
||||
matched = matched || numberInRange(uint32(uid), uint32(span.Start), uint32(span.Stop), maximum)
|
||||
matched = matched || numberInRange(uint32(number), uint32(span.Start), uint32(span.Stop), maximum)
|
||||
}
|
||||
}
|
||||
if matched {
|
||||
@ -96,6 +100,10 @@ func expandIMAPUIDSet(uids *imap.UIDSet, maxUID int) ([]int, error) {
|
||||
if uids == nil {
|
||||
return nil, nil
|
||||
}
|
||||
maximum, err := imapUint32(maxUID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]int, 0)
|
||||
seen := make(map[int]struct{})
|
||||
appendUID := func(uid imap.UID) error {
|
||||
@ -118,7 +126,7 @@ func expandIMAPUIDSet(uids *imap.UIDSet, maxUID int) ([]int, error) {
|
||||
stop := uidRange.Stop
|
||||
if start == 0 {
|
||||
if maxUID > 0 {
|
||||
if err := appendUID(imap.UID(maxUID)); err != nil {
|
||||
if err := appendUID(imap.UID(maximum)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@ -128,13 +136,13 @@ func expandIMAPUIDSet(uids *imap.UIDSet, maxUID int) ([]int, error) {
|
||||
if maxUID <= 0 {
|
||||
continue
|
||||
}
|
||||
if imap.UID(maxUID) < start {
|
||||
if err := appendUID(imap.UID(maxUID)); err != nil {
|
||||
if imap.UID(maximum) < start {
|
||||
if err := appendUID(imap.UID(maximum)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
stop = imap.UID(maxUID)
|
||||
stop = imap.UID(maximum)
|
||||
}
|
||||
if stop < start {
|
||||
return nil, fmt.Errorf("invalid UID range")
|
||||
|
||||
@ -18,10 +18,16 @@ func (s *serverSession) selectedUIDs() ([]int, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.mailboxUIDs = make([]int, 0, len(relations))
|
||||
uids := make([]int, 0, len(relations))
|
||||
for _, relation := range relations {
|
||||
s.mailboxUIDs = append(s.mailboxUIDs, relation.ID)
|
||||
uids = append(uids, relation.ID)
|
||||
}
|
||||
if _, _, err := imapSnapshotBounds(uids); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.mailboxUIDs = uids
|
||||
} else if _, _, err := imapSnapshotBounds(s.mailboxUIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return append([]int{}, s.mailboxUIDs...), nil
|
||||
}
|
||||
@ -44,23 +50,31 @@ func (s *serverSession) selectedMessages(numbers imap.NumSet) ([]*response.Email
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var maximum uint32
|
||||
for _, uid := range uids {
|
||||
maximum = max(maximum, uint32(uid))
|
||||
maximum, count, err := imapSnapshotBounds(uids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
selected := make([]int, 0)
|
||||
sequences := make(map[int]int, len(uids))
|
||||
for index, uid := range uids {
|
||||
number, err := imapUID(uid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequence, err := imapUint32(index + 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sequences[uid] = index + 1
|
||||
matched := false
|
||||
switch set := numbers.(type) {
|
||||
case imap.SeqSet:
|
||||
for _, span := range set {
|
||||
matched = matched || numberInRange(uint32(index+1), span.Start, span.Stop, uint32(len(uids)))
|
||||
matched = matched || numberInRange(sequence, span.Start, span.Stop, count)
|
||||
}
|
||||
case imap.UIDSet:
|
||||
for _, span := range set {
|
||||
matched = matched || numberInRange(uint32(uid), uint32(span.Start), uint32(span.Stop), maximum)
|
||||
matched = matched || numberInRange(uint32(number), uint32(span.Start), uint32(span.Stop), maximum)
|
||||
}
|
||||
}
|
||||
if matched {
|
||||
|
||||
@ -17,6 +17,9 @@ func (s *serverSession) poll(w *imapserver.UpdateWriter, allowExpunge, forceExis
|
||||
if s.currentMailbox == "" {
|
||||
return nil
|
||||
}
|
||||
if _, _, err := imapSnapshotBounds(s.mailboxUIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
relations, err := list.GetUEListByUID(s.ctx, s.currentMailbox, 0, 0, nil)
|
||||
if err != nil {
|
||||
log.WithContext(s.ctx).WithError(err).Error("query IMAP polling updates failed")
|
||||
@ -24,8 +27,14 @@ func (s *serverSession) poll(w *imapserver.UpdateWriter, allowExpunge, forceExis
|
||||
}
|
||||
|
||||
present := make(map[int]bool, len(relations))
|
||||
nextUIDs := make([]int, 0, len(relations))
|
||||
for _, relation := range relations {
|
||||
present[relation.ID] = true
|
||||
nextUIDs = append(nextUIDs, relation.ID)
|
||||
}
|
||||
_, count, err := imapSnapshotBounds(nextUIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 不允许 EXPUNGE 的命令后保留旧序号,等到可通知时再同步整个变化。
|
||||
for _, uid := range s.mailboxUIDs {
|
||||
@ -35,24 +44,21 @@ func (s *serverSession) poll(w *imapserver.UpdateWriter, allowExpunge, forceExis
|
||||
}
|
||||
for index := len(s.mailboxUIDs) - 1; index >= 0; index-- {
|
||||
if !present[s.mailboxUIDs[index]] {
|
||||
if err := w.WriteExpunge(uint32(index + 1)); err != nil {
|
||||
sequence, err := imapUint32(index + 1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteExpunge(sequence); err != nil {
|
||||
return err
|
||||
}
|
||||
s.mailboxUIDs = append(s.mailboxUIDs[:index], s.mailboxUIDs[index+1:]...)
|
||||
}
|
||||
}
|
||||
if forceExists || len(relations) != len(s.mailboxUIDs) {
|
||||
count, err := imapUint32(len(relations))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := w.WriteNumMessages(count); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
s.mailboxUIDs = s.mailboxUIDs[:0]
|
||||
for _, relation := range relations {
|
||||
s.mailboxUIDs = append(s.mailboxUIDs, relation.ID)
|
||||
}
|
||||
s.mailboxUIDs = nextUIDs
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -64,6 +64,14 @@ type IMAPSearchResult struct {
|
||||
|
||||
// SearchEmails performs IMAP search based on criteria
|
||||
func SearchEmails(ctx *context.Context, groupName string, criteria *imap.SearchCriteria, snapshot ...[]int) ([]*response.UserEmailUIDData, error) {
|
||||
var snapshotCount uint32
|
||||
if len(snapshot) > 0 {
|
||||
var err error
|
||||
snapshotCount, err = imapSnapshotCount(snapshot[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// First get the base list for the mailbox
|
||||
baseList, err := GetUEListByUID(ctx, groupName, 0, 0, nil)
|
||||
if err != nil {
|
||||
@ -104,7 +112,7 @@ func SearchEmails(ctx *context.Context, groupName string, criteria *imap.SearchC
|
||||
}
|
||||
if len(snapshot) > 0 {
|
||||
maxUID = 0
|
||||
maxSequence = uint32(len(snapshot[0]))
|
||||
maxSequence = snapshotCount
|
||||
for _, uid := range snapshot[0] {
|
||||
if number, ok := validIMAPNumber(uid); ok {
|
||||
maxUID = max(maxUID, number)
|
||||
@ -195,6 +203,18 @@ func validIMAPNumber(value int) (uint32, bool) {
|
||||
return cast.ToUint32(value), true
|
||||
}
|
||||
|
||||
func imapSnapshotCount(snapshot []int) (uint32, error) {
|
||||
if uint64(len(snapshot)) > math.MaxUint32 {
|
||||
return 0, fmt.Errorf("邮箱邮件数量超出协议允许范围")
|
||||
}
|
||||
for _, value := range snapshot {
|
||||
if _, ok := validIMAPNumber(value); !ok {
|
||||
return 0, fmt.Errorf("邮箱邮件标识超出协议允许范围")
|
||||
}
|
||||
}
|
||||
return cast.ToUint32(len(snapshot)), nil
|
||||
}
|
||||
|
||||
// filterByUIDSets filters the list by UID sets
|
||||
func filterByUIDSets(list []*response.UserEmailUIDData, uidSets []imap.UIDSet, upper ...uint32) []*response.UserEmailUIDData {
|
||||
var maximum uint32
|
||||
|
||||
27
backend/services/list/imap_snapshot_test.go
Normal file
27
backend/services/list/imap_snapshot_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
package list
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIMAPSnapshotCountRejectsInvalidUIDBeforeDatabaseRead(t *testing.T) {
|
||||
for _, snapshot := range [][]int{{0}, {-1}, {1, 0}} {
|
||||
if _, err := SearchEmails(nil, "INBOX", nil, snapshot); err == nil {
|
||||
t.Fatal("非法快照必须在查询数据库之前拒绝")
|
||||
}
|
||||
}
|
||||
if count, err := imapSnapshotCount(nil); err != nil || count != 0 {
|
||||
t.Fatalf("空快照验证失败:%d %v", count, err)
|
||||
}
|
||||
if strconv.IntSize >= 64 {
|
||||
limit := uint64(math.MaxUint32)
|
||||
if count, err := imapSnapshotCount([]int{int(limit)}); err != nil || count != 1 {
|
||||
t.Fatalf("最大合法 UID 被拒绝:%d %v", count, err)
|
||||
}
|
||||
if _, err := SearchEmails(nil, "INBOX", nil, []int{int(limit + 1)}); err == nil {
|
||||
t.Fatal("越界 UID 不应被截断为搜索结果")
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user