From 4649fd0b055435851458af163dfe27fd50fb616e Mon Sep 17 00:00:00 2001 From: James Hillyerd Date: Wed, 11 Sep 2013 23:12:22 -0700 Subject: [PATCH] Started impl DELE --- pop3d/handler.go | 164 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 141 insertions(+), 23 deletions(-) diff --git a/pop3d/handler.go b/pop3d/handler.go index b5ae365..9ffd6b1 100644 --- a/pop3d/handler.go +++ b/pop3d/handler.go @@ -50,16 +50,18 @@ var commands = map[string]bool{ } type Session struct { - server *Server - id int - conn net.Conn - remoteHost string - sendError error - state State - reader *bufio.Reader - user string - mailbox smtpd.Mailbox - messages []smtpd.Message + server *Server // Reference to the server we belong to + id int // Session ID number + conn net.Conn // Our network connection + remoteHost string // IP address of client + sendError error // Used to bail out of read loop on send error + state State // Current session state + reader *bufio.Reader // Buffered reader for our net conn + user string // Mailbox name + mailbox smtpd.Mailbox // Mailbox instance + messages []smtpd.Message // Slice of messages in mailbox + retain []bool // Messages to retain upon UPDATE (true=retain) + msgCount int // Number of undeleted messages } func NewSession(server *Server, id int, conn net.Conn) *Session { @@ -187,7 +189,7 @@ func (ses *Session) authorizationHandler(cmd string, args []string) { return } ses.loadMailbox() - ses.send(fmt.Sprintf("+OK Found %v messages for %v", len(ses.messages), ses.user)) + ses.send(fmt.Sprintf("+OK Found %v messages for %v", ses.msgCount, ses.user)) ses.enterState(TRANSACTION) } default: @@ -199,12 +201,111 @@ func (ses *Session) authorizationHandler(cmd string, args []string) { func (ses *Session) transactionHandler(cmd string, args []string) { switch cmd { case "LIST": - // TODO implement list argument - ses.send(fmt.Sprintf("+OK Listing %v messages", len(ses.messages))) - for i, msg := range ses.messages { - ses.send(fmt.Sprintf("%v %v", i+1, msg.Size())) + if len(args) > 1 { + ses.warn("LIST command had more than 1 argument") + ses.send("-ERR LIST command must have zero or one argument") + return + } + if len(args) == 1 { + msgNum, err := strconv.ParseInt(args[0], 10, 32) + if err != nil { + ses.warn("LIST command argument was not an integer") + ses.send("-ERR LIST command requires an integer argument") + return + } + if msgNum < 1 { + ses.warn("LIST command argument was less than 1") + ses.send("-ERR LIST argument must be greater than 0") + return + } + if int(msgNum) > len(ses.messages) { + ses.warn("LIST command argument was greater than number of messages") + ses.send("-ERR LIST argument must not exceed the number of messages") + return + } + if !ses.retain[msgNum-1] { + ses.warn("Client tried to LIST a message it had deleted") + ses.send(fmt.Sprintf("-ERR You deleted message %v", msgNum)) + return + } + ses.send(fmt.Sprintf("+OK %v %v", msgNum, ses.messages[msgNum-1].Size())) + } else { + ses.send(fmt.Sprintf("+OK Listing %v messages", ses.msgCount)) + for i, msg := range ses.messages { + if ses.retain[i] { + ses.send(fmt.Sprintf("%v %v", i+1, msg.Size())) + } + } + ses.send(".") + } + case "UIDL": + if len(args) > 1 { + ses.warn("UIDL command had more than 1 argument") + ses.send("-ERR UIDL command must have zero or one argument") + return + } + if len(args) == 1 { + msgNum, err := strconv.ParseInt(args[0], 10, 32) + if err != nil { + ses.warn("UIDL command argument was not an integer") + ses.send("-ERR UIDL command requires an integer argument") + return + } + if msgNum < 1 { + ses.warn("UIDL command argument was less than 1") + ses.send("-ERR UIDL argument must be greater than 0") + return + } + if int(msgNum) > len(ses.messages) { + ses.warn("UIDL command argument was greater than number of messages") + ses.send("-ERR UIDL argument must not exceed the number of messages") + return + } + if !ses.retain[msgNum-1] { + ses.warn("Client tried to UIDL a message it had deleted") + ses.send(fmt.Sprintf("-ERR You deleted message %v", msgNum)) + return + } + ses.send(fmt.Sprintf("+OK %v %v", msgNum, ses.messages[msgNum-1].Id())) + } else { + ses.send(fmt.Sprintf("+OK Listing %v messages", ses.msgCount)) + for i, msg := range ses.messages { + if ses.retain[i] { + ses.send(fmt.Sprintf("%v %v", i+1, msg.Id())) + } + } + ses.send(".") + } + case "DELE": + if len(args) != 1 { + ses.warn("DELE command had invalid number of arguments") + ses.send("-ERR DELE command requires a single argument") + return + } + msgNum, err := strconv.ParseInt(args[0], 10, 32) + if err != nil { + ses.warn("DELE command argument was not an integer") + ses.send("-ERR DELE command requires an integer argument") + return + } + if msgNum < 1 { + ses.warn("DELE command argument was less than 1") + ses.send("-ERR DELE argument must be greater than 0") + return + } + if int(msgNum) > len(ses.messages) { + ses.warn("DELE command argument was greater than number of messages") + ses.send("-ERR DELE argument must not exceed the number of messages") + return + } + if ses.retain[msgNum-1] { + ses.retain[msgNum-1] = false + ses.msgCount -= 1 + ses.send(fmt.Sprintf("+OK Deleted message %v", msgNum)) + } else { + ses.warn("Client tried to DELE an already deleted message") + ses.send(fmt.Sprintf("-ERR Message %v has already been deleted", msgNum)) } - ses.send(".") case "RETR": if len(args) != 1 { ses.warn("RETR command had invalid number of arguments") @@ -228,8 +329,14 @@ func (ses *Session) transactionHandler(cmd string, args []string) { return } - // TODO actually retrieve the message... - ses.send("+OK") + raw, err := ses.messages[msgNum-1].ReadRaw() + if err != nil { + ses.error("Failed to read message for RETR command") + ses.send("-ERR Failed to RETR that message, internal error") + return + } + ses.send(*raw) + ses.send(".") case "QUIT": ses.send("+OK We will process your deletes") ses.processDeletes() @@ -253,6 +360,17 @@ func (ses *Session) loadMailbox() { if err != nil { ses.error("Failed to load messages for %v", ses.user) } + + ses.retainAll() +} + +// Reset retain flag to true for all messages +func (ses *Session) retainAll() { + ses.retain = make([]bool, len(ses.messages)) + for i, _ := range ses.retain { + ses.retain[i] = true + } + ses.msgCount = len(ses.messages) } // This would be considered the "UPDATE" state in the RFC, but it does not fit @@ -339,7 +457,7 @@ func (ses *Session) parseCmd(line string) (cmd string, args []string, ok bool) { } func (ses *Session) reset() { - //ses.enterState(READY) + ses.retainAll() } func (ses *Session) ooSeq(cmd string) { @@ -349,21 +467,21 @@ func (ses *Session) ooSeq(cmd string) { // Session specific logging methods func (ses *Session) trace(msg string, args ...interface{}) { - log.Trace("POP3 %v<%v> %v", ses.remoteHost, ses.id, fmt.Sprintf(msg, args...)) + log.Trace("POP3<%v> %v", ses.id, fmt.Sprintf(msg, args...)) } func (ses *Session) info(msg string, args ...interface{}) { - log.Info("POP3 %v<%v> %v", ses.remoteHost, ses.id, fmt.Sprintf(msg, args...)) + log.Info("POP3<%v> %v", ses.id, fmt.Sprintf(msg, args...)) } func (ses *Session) warn(msg string, args ...interface{}) { // Update metrics //expWarnsTotal.Add(1) - log.Warn("POP3 %v<%v> %v", ses.remoteHost, ses.id, fmt.Sprintf(msg, args...)) + log.Warn("POP3<%v> %v", ses.id, fmt.Sprintf(msg, args...)) } func (ses *Session) error(msg string, args ...interface{}) { // Update metrics //expErrorsTotal.Add(1) - log.Error("POP3 %v<%v> %v", ses.remoteHost, ses.id, fmt.Sprintf(msg, args...)) + log.Error("POP3<%v> %v", ses.id, fmt.Sprintf(msg, args...)) }