Smail/test/review/pop3_parser_findings_test.go

150 lines
4.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package gopop
import (
"bufio"
"errors"
"net"
"strings"
"testing"
"time"
)
// 仅替换数据提供层,命令解析和响应编码执行真实实现;不启动公网监听。
type auditPOP3Action struct {
securityTestAction
committed []int64
topLines []int
}
func (a *auditPOP3Action) Delete(session *Session, id int64) error {
session.DeleteIds = append(session.DeleteIds, id)
return nil
}
func (a *auditPOP3Action) Rest(session *Session) error {
session.DeleteIds = nil
return nil
}
func (a *auditPOP3Action) Quit(session *Session) error {
a.committed = append([]int64(nil), session.DeleteIds...)
return nil
}
func (a *auditPOP3Action) Custom(session *Session, cmd string, args []string) ([]string, error) {
return nil, errors.New("unsupported command")
}
func (a *auditPOP3Action) List(session *Session, msg string) ([]MailInfo, error) {
return []MailInfo{{Id: 1, Size: 100}}, nil
}
func (a *auditPOP3Action) Uidl(session *Session, msg string) ([]UidlItem, error) {
return []UidlItem{{Id: 1, UnionId: "audit-message"}}, nil
}
func (a *auditPOP3Action) Top(session *Session, id int64, n int) (string, error) {
a.topLines = append(a.topLines, n)
return "fixture", nil
}
func auditPOP3Pipe(t *testing.T, action Action) (net.Conn, *bufio.Reader, <-chan struct{}) {
t.Helper()
serverConn, clientConn := net.Pipe()
server := &Server{Domain: "audit.invalid", Action: action}
done := make(chan struct{})
go func() {
server.handleClient(serverConn)
close(done)
}()
t.Cleanup(func() {
_ = clientConn.Close()
_ = serverConn.Close()
select {
case <-done:
case <-time.After(2 * time.Second):
t.Error("隔离 POP3 解析器未及时退出")
}
})
if err := clientConn.SetDeadline(time.Now().Add(2 * time.Second)); err != nil {
t.Fatal(err)
}
reader := bufio.NewReader(clientConn)
readResponseLine(t, reader, "+OK")
writeCommand(t, clientConn, "USER audit")
readResponseLine(t, reader, "+OK")
writeCommand(t, clientConn, "PASS fixture-only")
readResponseLine(t, reader, "+OK")
return clientConn, reader, done
}
func TestAuditPOP3RSETMustCancelPendingDeletion(t *testing.T) {
action := &auditPOP3Action{}
conn, reader, done := auditPOP3Pipe(t, action)
writeCommand(t, conn, "DELE 1")
readResponseLine(t, reader, "+OK")
writeCommand(t, conn, "RSET")
response, err := reader.ReadString('\n')
if err != nil {
t.Fatal(err)
}
writeCommand(t, conn, "QUIT")
readResponseLine(t, reader, "+OK")
<-done
t.Logf("rset_response=%q committed_ids=%v", strings.TrimSpace(response), action.committed)
if !strings.HasPrefix(response, "+OK") || len(action.committed) != 0 {
t.Fatal("标准 RSET 没有撤销删除标记,QUIT 仍提交原删除集合")
}
}
func TestAuditPOP3SingleMessageRepliesMustBeSingleLine(t *testing.T) {
for _, test := range []struct {
command string
want string
}{
{command: "LIST 1", want: "+OK 1 100"},
{command: "UIDL 1", want: "+OK 1 audit-message"},
} {
t.Run(test.command, func(t *testing.T) {
conn, reader, _ := auditPOP3Pipe(t, &auditPOP3Action{})
writeCommand(t, conn, test.command)
response, err := reader.ReadString('\n')
if err != nil {
t.Fatal(err)
}
t.Logf("command=%q first_response_line=%q buffered_extra_bytes=%d", test.command, strings.TrimSpace(response), reader.Buffered())
if strings.TrimSpace(response) != test.want || reader.Buffered() != 0 {
t.Fatal("指定消息编号的 LIST/UIDL 被错误编码成多行响应")
}
})
}
}
func TestAuditPOP3ParserMustRejectNegativeTOPLines(t *testing.T) {
action := &auditPOP3Action{}
conn, reader, done := auditPOP3Pipe(t, action)
writeCommand(t, conn, "TOP 1 -100000")
response, err := reader.ReadString('\n')
if err != nil {
t.Fatal(err)
}
if strings.HasPrefix(response, "+OK") {
for {
line, err := reader.ReadString('\n')
if err != nil {
t.Fatal(err)
}
if line == ".\r\n" {
break
}
}
}
writeCommand(t, conn, "QUIT")
readResponseLine(t, reader, "+OK")
<-done
t.Logf("negative_top_response=%q dispatched_line_counts=%v", strings.TrimSpace(response), action.topLines)
if !strings.HasPrefix(response, "-ERR") || len(action.topLines) != 0 {
t.Fatal("TOP 的负数行参数未在协议入口拒绝")
}
}