Implement POP3 LIST, RETR, and DELE.
authorRobert Sesek <rsesek@bluestatic.org>
Tue, 13 Dec 2016 05:59:58 +0000 (00:59 -0500)
committerRobert Sesek <rsesek@bluestatic.org>
Tue, 13 Dec 2016 05:59:58 +0000 (00:59 -0500)
pop3.go
pop3/conn.go
pop3/server.go

diff --git a/pop3.go b/pop3.go
index 84e075b7c26a9d954c3ed0f6a3fd6bb2049db2d2..ad4d1a4739fcd563e02702acf4648cf508e80bef 100644 (file)
--- a/pop3.go
+++ b/pop3.go
@@ -4,8 +4,10 @@ import (
        "errors"
        "fmt"
        "io"
+       "io/ioutil"
        "net"
        "os"
+       "path"
 
        "src.bluestatic.org/mailpopbox/pop3"
 )
@@ -62,9 +64,31 @@ func (server *pop3Server) OpenMailbox(user, pass string) (pop3.Mailbox, error) {
 }
 
 func (server *pop3Server) openMailbox(maildrop string) (*mailbox, error) {
+       files, err := ioutil.ReadDir(maildrop)
+       if err != nil {
+               // TODO: hide error, log instead
+               return nil, err
+       }
+
        mb := &mailbox{
-               messages: make([]message, 0),
+               messages: make([]message, 0, len(files)),
+       }
+
+       i := 0
+       for _, file := range files {
+               if file.IsDir() {
+                       continue
+               }
+
+               msg := message{
+                       filename: path.Join(maildrop, file.Name()),
+                       index:    i,
+                       size:     file.Size(),
+               }
+               mb.messages = append(mb.messages, msg)
+               i++
        }
+
        return mb, nil
 }
 
@@ -75,7 +99,7 @@ type mailbox struct {
 type message struct {
        filename string
        index    int
-       size     int
+       size     int64
        deleted  bool
 }
 
@@ -84,7 +108,11 @@ func (m message) ID() int {
 }
 
 func (m message) Size() int {
-       return m.size
+       return int(m.size)
+}
+
+func (m message) Deleted() bool {
+       return m.deleted
 }
 
 func (mb *mailbox) ListMessages() ([]pop3.Message, error) {
@@ -95,13 +123,16 @@ func (mb *mailbox) ListMessages() ([]pop3.Message, error) {
        return msgs, nil
 }
 
-func (mb *mailbox) Retrieve(msg pop3.Message) (io.ReadCloser, error) {
-       filename := msg.(*message).filename
+func (mb *mailbox) Retrieve(idx int) (io.ReadCloser, error) {
+       if idx > len(mb.messages) {
+               return nil, errors.New("no such message")
+       }
+       filename := mb.messages[idx-1].filename
        return os.Open(filename)
 }
 
-func (mb *mailbox) Delete(msg pop3.Message) error {
-       message := msg.(*message)
+func (mb *mailbox) Delete(idx int) error {
+       message := &mb.messages[idx-1]
        if message.deleted {
                return errors.New("already deleted")
        }
index c909bae41ee72c6ce65c8381f227e81128b3e0d3..5341bcbd68988c6f6d73ff3c141373ac7a3dc07b 100644 (file)
@@ -2,6 +2,7 @@ package pop3
 
 import (
        "fmt"
+       "io"
        "net"
        "net/textproto"
        "strings"
@@ -150,6 +151,24 @@ func (conn *connection) doSTAT() {
                conn.err(errStateTxn)
                return
        }
+
+       msgs, err := conn.mb.ListMessages()
+       if err != nil {
+               conn.err(err.Error())
+               return
+       }
+
+       size := 0
+       num := 0
+       for _, msg := range msgs {
+               if msg.Deleted() {
+                       continue
+               }
+               size += msg.Size()
+               num++
+       }
+
+       conn.ok(fmt.Sprintf("%d %d", num, size))
 }
 
 func (conn *connection) doLIST() {
@@ -157,6 +176,18 @@ func (conn *connection) doLIST() {
                conn.err(errStateTxn)
                return
        }
+
+       msgs, err := conn.mb.ListMessages()
+       if err != nil {
+               conn.err(err.Error())
+               return
+       }
+
+       conn.ok("scan listing")
+       for _, msg := range msgs {
+               conn.tp.PrintfLine("%d %d", msg.ID(), msg.Size())
+       }
+       conn.tp.PrintfLine(".")
 }
 
 func (conn *connection) doRETR() {
@@ -164,6 +195,21 @@ func (conn *connection) doRETR() {
                conn.err(errStateTxn)
                return
        }
+
+       idx, ok := conn.intParam()
+       if !ok {
+               return
+       }
+
+       rc, err := conn.mb.Retrieve(idx)
+       if err != nil {
+               conn.err(err.Error())
+               return
+       }
+
+       w := conn.tp.DotWriter()
+       io.Copy(w, rc)
+       w.Close()
 }
 
 func (conn *connection) doDELE() {
@@ -171,6 +217,17 @@ func (conn *connection) doDELE() {
                conn.err(errStateTxn)
                return
        }
+
+       idx, ok := conn.intParam()
+       if !ok {
+               return
+       }
+
+       if err := conn.mb.Delete(idx); err != nil {
+               conn.err(err.Error())
+       } else {
+               conn.ok("")
+       }
 }
 
 func (conn *connection) doRSET() {
@@ -181,3 +238,13 @@ func (conn *connection) doRSET() {
        conn.mb.Reset()
        conn.ok("")
 }
+
+func (conn *connection) intParam() (int, bool) {
+       var cmd string
+       var param int
+       if _, err := fmt.Sscanf(conn.line, "%s %d", &cmd, &param); err != nil {
+               conn.err(errSyntax)
+               return 0, false
+       }
+       return param, true
+}
index 1bce28f115de9b026b194ae0061f3e8f47ba1d39..f0377b54bcab62c4dca5c7ecd09b6b872714195f 100644 (file)
@@ -7,12 +7,13 @@ import (
 type Message interface {
        ID() int
        Size() int
+       Deleted() bool
 }
 
 type Mailbox interface {
        ListMessages() ([]Message, error)
-       Retrieve(Message) (io.ReadCloser, error)
-       Delete(Message) error
+       Retrieve(int) (io.ReadCloser, error)
+       Delete(int) error
        Close() error
        Reset()
 }