From 5da71dab4aef6ee95e3994a5be9644a91423f9fe Mon Sep 17 00:00:00 2001 From: Robert Sesek Date: Mon, 2 Jan 2017 01:32:26 -0500 Subject: [PATCH] Implement the POP3 UIDL command. --- pop3.go | 5 +++++ pop3/conn.go | 25 +++++++++++++++++++++++++ pop3/conn_test.go | 47 +++++++++++++++++++++++++++++++++++++++++++++++ pop3/server.go | 1 + 4 files changed, 78 insertions(+) diff --git a/pop3.go b/pop3.go index c803143..dc476d4 100644 --- a/pop3.go +++ b/pop3.go @@ -126,6 +126,11 @@ type message struct { deleted bool } +func (m message) UniqueID() string { + l := len(m.filename) + return path.Base(m.filename[:l-len(".msg")]) +} + func (m message) ID() int { return m.index + 1 } diff --git a/pop3/conn.go b/pop3/conn.go index b16dcc4..1eecd0a 100644 --- a/pop3/conn.go +++ b/pop3/conn.go @@ -90,6 +90,8 @@ func AcceptConnection(netConn net.Conn, po PostOffice, log zap.Logger) { conn.ok("") case "RSET": conn.doRSET() + case "UIDL": + conn.doUIDL() default: conn.log.Error("unknown command") conn.err("unknown command") @@ -267,6 +269,29 @@ func (conn *connection) doRSET() { conn.ok("") } +func (conn *connection) doUIDL() { + if conn.state != stateTxn { + conn.err(errStateTxn) + return + } + + msgs, err := conn.mb.ListMessages() + if err != nil { + conn.log.Error("failed to list messages", zap.Error(err)) + conn.err(err.Error()) + return + } + + conn.ok("unique-id listing") + for _, msg := range msgs { + if msg.Deleted() { + continue + } + conn.tp.PrintfLine("%d %s", msg.ID(), msg.UniqueID()) + } + conn.tp.PrintfLine(".") +} + func (conn *connection) getRequestedMessage() Message { var cmd string var idx int diff --git a/pop3/conn_test.go b/pop3/conn_test.go index 4671bca..4d25a39 100644 --- a/pop3/conn_test.go +++ b/pop3/conn_test.go @@ -129,6 +129,10 @@ type testMessage struct { body string } +func (m *testMessage) UniqueID() string { + return fmt.Sprintf("%p", m) +} + func (m *testMessage) ID() int { return m.id } @@ -297,6 +301,10 @@ func TestRetr(t *testing.T) { {"STAT", responseOK}, {"RETR 1", func(t testing.TB, tp *textproto.Conn) string { responseOK(t, tp) + if t.Failed() { + return "" + } + resp, err := tp.ReadDotLines() if err != nil { t.Error(err) @@ -312,6 +320,10 @@ func TestRetr(t *testing.T) { }}, {"RETR 2", func(t testing.TB, tp *textproto.Conn) string { responseOK(t, tp) + if t.Failed() { + return "" + } + resp, err := tp.ReadDotLines() if err != nil { t.Error(err) @@ -328,3 +340,38 @@ func TestRetr(t *testing.T) { {"QUIT", responseOK}, }) } + +func TestUidl(t *testing.T) { + s := newTestServer() + s.mb.msgs[1] = &testMessage{1, 3, false, "abc"} + s.mb.msgs[2] = &testMessage{2, 1, true, "Z"} + s.mb.msgs[3] = &testMessage{3, 4, false, "test"} + + clientServerTest(t, s, []requestResponse{ + {"USER u", responseOK}, + {"PASS p", responseOK}, + {"UIDL", func(t testing.TB, tp *textproto.Conn) string { + responseOK(t, tp) + if t.Failed() { + return "" + } + + resp, err := tp.ReadDotLines() + if err != nil { + t.Error(err) + return "" + } + + expected := []string{ + fmt.Sprintf("1 %p", s.mb.msgs[1]), + fmt.Sprintf("3 %p", s.mb.msgs[3]), + } + if !reflect.DeepEqual(resp, expected) { + t.Errorf("Expected %v, got %v", expected, resp) + } + + return "" + }}, + {"QUIT", responseOK}, + }) +} diff --git a/pop3/server.go b/pop3/server.go index 6e5dc36..5ff787e 100644 --- a/pop3/server.go +++ b/pop3/server.go @@ -5,6 +5,7 @@ import ( ) type Message interface { + UniqueID() string ID() int Size() int Deleted() bool -- 2.22.5