1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2025-12-18 10:07:02 +00:00

Started impl DELE

This commit is contained in:
James Hillyerd
2013-09-11 23:12:22 -07:00
parent 983b4f745a
commit 01f6ad514b
3 changed files with 256 additions and 30 deletions

View File

@@ -3,7 +3,6 @@ package pop3d
import ( import (
"bufio" "bufio"
"bytes" "bytes"
//"container/list"
"fmt" "fmt"
"github.com/jhillyerd/inbucket/log" "github.com/jhillyerd/inbucket/log"
"github.com/jhillyerd/inbucket/smtpd" "github.com/jhillyerd/inbucket/smtpd"
@@ -50,16 +49,18 @@ var commands = map[string]bool{
} }
type Session struct { type Session struct {
server *Server server *Server // Reference to the server we belong to
id int id int // Session ID number
conn net.Conn conn net.Conn // Our network connection
remoteHost string remoteHost string // IP address of client
sendError error sendError error // Used to bail out of read loop on send error
state State state State // Current session state
reader *bufio.Reader reader *bufio.Reader // Buffered reader for our net conn
user string user string // Mailbox name
mailbox smtpd.Mailbox mailbox smtpd.Mailbox // Mailbox instance
messages []smtpd.Message 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 { func NewSession(server *Server, id int, conn net.Conn) *Session {
@@ -110,7 +111,7 @@ func (s *Server) startSession(id int, conn net.Conn) {
// Commands we handle in any state // Commands we handle in any state
switch cmd { switch cmd {
case "APOP", "TOP": case "APOP":
// These commands are not implemented in any state // These commands are not implemented in any state
ses.send(fmt.Sprintf("-ERR %v command not implemented", cmd)) ses.send(fmt.Sprintf("-ERR %v command not implemented", cmd))
ses.warn("Command %v not implemented by Inbucket", cmd) ses.warn("Command %v not implemented by Inbucket", cmd)
@@ -187,7 +188,7 @@ func (ses *Session) authorizationHandler(cmd string, args []string) {
return return
} }
ses.loadMailbox() 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) ses.enterState(TRANSACTION)
} }
default: default:
@@ -199,12 +200,111 @@ func (ses *Session) authorizationHandler(cmd string, args []string) {
func (ses *Session) transactionHandler(cmd string, args []string) { func (ses *Session) transactionHandler(cmd string, args []string) {
switch cmd { switch cmd {
case "LIST": case "LIST":
// TODO implement list argument if len(args) > 1 {
ses.send(fmt.Sprintf("+OK Listing %v messages", len(ses.messages))) 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 { for i, msg := range ses.messages {
if ses.retain[i] {
ses.send(fmt.Sprintf("%v %v", i+1, msg.Size())) ses.send(fmt.Sprintf("%v %v", i+1, msg.Size()))
} }
}
ses.send(".") 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))
}
case "RETR": case "RETR":
if len(args) != 1 { if len(args) != 1 {
ses.warn("RETR command had invalid number of arguments") ses.warn("RETR command had invalid number of arguments")
@@ -227,9 +327,43 @@ func (ses *Session) transactionHandler(cmd string, args []string) {
ses.send("-ERR RETR argument must not exceed the number of messages") ses.send("-ERR RETR argument must not exceed the number of messages")
return return
} }
ses.sendMessage(ses.messages[msgNum-1])
case "TOP":
if len(args) != 2 {
ses.warn("TOP command had invalid number of arguments")
ses.send("-ERR TOP command requires two arguments")
return
}
msgNum, err := strconv.ParseInt(args[0], 10, 32)
if err != nil {
ses.warn("TOP command first argument was not an integer")
ses.send("-ERR TOP command requires an integer argument")
return
}
if msgNum < 1 {
ses.warn("TOP command first argument was less than 1")
ses.send("-ERR TOP first argument must be greater than 0")
return
}
if int(msgNum) > len(ses.messages) {
ses.warn("TOP command first argument was greater than number of messages")
ses.send("-ERR TOP first argument must not exceed the number of messages")
return
}
// TODO actually retrieve the message... var lines int64
ses.send("+OK") lines, err = strconv.ParseInt(args[1], 10, 32)
if err != nil {
ses.warn("TOP command second argument was not an integer")
ses.send("-ERR TOP command requires an integer argument")
return
}
if lines < 0 {
ses.warn("TOP command second argument was negative")
ses.send("-ERR TOP second argument must be non-negative")
return
}
ses.sendMessageTop(ses.messages[msgNum-1], int(lines))
case "QUIT": case "QUIT":
ses.send("+OK We will process your deletes") ses.send("+OK We will process your deletes")
ses.processDeletes() ses.processDeletes()
@@ -246,6 +380,76 @@ func (ses *Session) transactionHandler(cmd string, args []string) {
} }
} }
// Send the contents of the message to the client
func (ses *Session) sendMessage(msg smtpd.Message) {
reader, err := msg.RawReader()
defer reader.Close()
if err != nil {
ses.error("Failed to read message for RETR command")
ses.send("-ERR Failed to RETR that message, internal error")
return
}
scanner := bufio.NewScanner(reader)
for scanner.Scan() {
line := scanner.Text()
// Lines starting with . must be prefixed with another .
if strings.HasPrefix(line, ".") {
line = "." + line
}
ses.send(line)
}
if err = scanner.Err(); err != nil {
ses.error("Failed to read message for RETR command")
ses.send(".")
ses.send("-ERR Failed to RETR that message, internal error")
return
}
ses.send(".")
}
// Send the headers plus the top N lines to the client
func (ses *Session) sendMessageTop(msg smtpd.Message, lineCount int) {
reader, err := msg.RawReader()
defer reader.Close()
if err != nil {
ses.error("Failed to read message for RETR command")
ses.send("-ERR Failed to RETR that message, internal error")
return
}
scanner := bufio.NewScanner(reader)
inBody := false
for scanner.Scan() {
line := scanner.Text()
// Lines starting with . must be prefixed with another .
if strings.HasPrefix(line, ".") {
line = "." + line
}
if inBody {
// Check if we need to send anymore lines
if lineCount < 1 {
break
} else {
lineCount -= 1
}
} else {
if line == "" {
// We've hit the end of the header
inBody = true
}
}
ses.send(line)
}
if err = scanner.Err(); err != nil {
ses.error("Failed to read message for RETR command")
ses.send(".")
ses.send("-ERR Failed to RETR that message, internal error")
return
}
ses.send(".")
}
// Load the users mailbox // Load the users mailbox
func (ses *Session) loadMailbox() { func (ses *Session) loadMailbox() {
var err error var err error
@@ -253,6 +457,17 @@ func (ses *Session) loadMailbox() {
if err != nil { if err != nil {
ses.error("Failed to load messages for %v", ses.user) 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 // This would be considered the "UPDATE" state in the RFC, but it does not fit
@@ -339,7 +554,7 @@ func (ses *Session) parseCmd(line string) (cmd string, args []string, ok bool) {
} }
func (ses *Session) reset() { func (ses *Session) reset() {
//ses.enterState(READY) ses.retainAll()
} }
func (ses *Session) ooSeq(cmd string) { func (ses *Session) ooSeq(cmd string) {
@@ -349,21 +564,21 @@ func (ses *Session) ooSeq(cmd string) {
// Session specific logging methods // Session specific logging methods
func (ses *Session) trace(msg string, args ...interface{}) { 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{}) { 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{}) { func (ses *Session) warn(msg string, args ...interface{}) {
// Update metrics // Update metrics
//expWarnsTotal.Add(1) //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{}) { func (ses *Session) error(msg string, args ...interface{}) {
// Update metrics // Update metrics
//expErrorsTotal.Add(1) //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...))
} }

View File

@@ -2,6 +2,7 @@ package smtpd
import ( import (
"github.com/jhillyerd/go.enmime" "github.com/jhillyerd/go.enmime"
"io"
"net/mail" "net/mail"
"time" "time"
) )
@@ -23,6 +24,7 @@ type Message interface {
From() string From() string
Date() time.Time Date() time.Time
Subject() string Subject() string
RawReader() (reader io.ReadCloser, err error)
ReadHeader() (msg *mail.Message, err error) ReadHeader() (msg *mail.Message, err error)
ReadBody() (msg *mail.Message, body *enmime.MIMEBody, err error) ReadBody() (msg *mail.Message, body *enmime.MIMEBody, err error)
ReadRaw() (raw *string, err error) ReadRaw() (raw *string, err error)

View File

@@ -8,6 +8,7 @@ import (
"github.com/jhillyerd/go.enmime" "github.com/jhillyerd/go.enmime"
"github.com/jhillyerd/inbucket/config" "github.com/jhillyerd/inbucket/config"
"github.com/jhillyerd/inbucket/log" "github.com/jhillyerd/inbucket/log"
"io"
"io/ioutil" "io/ioutil"
"net/mail" "net/mail"
"os" "os"
@@ -266,15 +267,23 @@ func (m *FileMessage) ReadBody() (msg *mail.Message, body *enmime.MIMEBody, err
return msg, mime, err return msg, mime, err
} }
// ReadRaw opens the .raw portion of a Message and returns it as a string // RawReader opens the .raw portion of a Message as an io.ReadCloser
func (m *FileMessage) ReadRaw() (raw *string, err error) { func (m *FileMessage) RawReader() (reader io.ReadCloser, err error) {
file, err := os.Open(m.rawPath()) file, err := os.Open(m.rawPath())
defer file.Close()
if err != nil { if err != nil {
return nil, err return nil, err
} }
reader := bufio.NewReader(file) return file, nil
bodyBytes, err := ioutil.ReadAll(reader) }
// ReadRaw opens the .raw portion of a Message and returns it as a string
func (m *FileMessage) ReadRaw() (raw *string, err error) {
reader, err := m.RawReader()
defer reader.Close()
if err != nil {
return nil, err
}
bodyBytes, err := ioutil.ReadAll(bufio.NewReader(reader))
if err != nil { if err != nil {
return nil, err return nil, err
} }