Implement the POP3 UIDL command.
authorRobert Sesek <rsesek@bluestatic.org>
Mon, 2 Jan 2017 06:32:26 +0000 (01:32 -0500)
committerRobert Sesek <rsesek@bluestatic.org>
Mon, 2 Jan 2017 06:32:26 +0000 (01:32 -0500)
pop3.go
pop3/conn.go
pop3/conn_test.go
pop3/server.go

diff --git a/pop3.go b/pop3.go
index c803143b4a8edcac1f802106fa20730008730ec3..dc476d43d6922c3414e6a646829850d8181327a5 100644 (file)
--- 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
 }
index b16dcc40e2ca1941c46007d24572ad412295cf56..1eecd0a4f2bc09eec79207ae6c509760fecd590b 100644 (file)
@@ -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
index 4671bcabbb38b72dc9db084a5975ced971034883..4d25a3978312d8260fe5640eac2f160669b80868 100644 (file)
@@ -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},
+       })
+}
index 6e5dc366dab453a641b2581f379f41beb494a11c..5ff787ed26f1ca9d1c29a759d8ec0fb12f8bea23 100644 (file)
@@ -5,6 +5,7 @@ import (
 )
 
 type Message interface {
+       UniqueID() string
        ID() int
        Size() int
        Deleted() bool