This is the v1 version, had the v2 before.
This commit is contained in:
78
backend/memory/backend.go
Normal file
78
backend/memory/backend.go
Normal file
@@ -0,0 +1,78 @@
|
||||
// A memory backend.
|
||||
package memory
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
)
|
||||
|
||||
type Backend struct {
|
||||
users map[string]*User
|
||||
}
|
||||
|
||||
func (be *Backend) Login(_ *imap.ConnInfo, username, password string) (backend.User, error) {
|
||||
user, ok := be.users[username]
|
||||
if ok && user.password == password {
|
||||
return user, nil
|
||||
}
|
||||
|
||||
return nil, errors.New("Bad username or password")
|
||||
}
|
||||
|
||||
func New() *Backend {
|
||||
user := &User{username: "username", password: "password"}
|
||||
|
||||
body := "From: contact@example.org\r\n" +
|
||||
"To: contact@example.org\r\n" +
|
||||
"Subject: A little message, just for you\r\n" +
|
||||
"Date: Wed, 11 May 2016 14:31:59 +0000\r\n" +
|
||||
"Message-ID: <0000000@localhost/>\r\n" +
|
||||
"Content-Type: text/plain\r\n" +
|
||||
"\r\n" +
|
||||
"Hi there :)"
|
||||
|
||||
user.mailboxes = map[string]*Mailbox{
|
||||
"INBOX": {
|
||||
name: "INBOX",
|
||||
user: user,
|
||||
Messages: []*Message{
|
||||
{
|
||||
Uid: 6,
|
||||
Date: time.Now(),
|
||||
Flags: []string{"\\Seen"},
|
||||
Size: uint32(len(body)),
|
||||
Body: []byte(body),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &Backend{
|
||||
users: map[string]*User{user.username: user},
|
||||
}
|
||||
}
|
||||
|
||||
// NewUser adds a user to the backend.
|
||||
func (be *Backend) NewUser(username, password string) (*User, error) {
|
||||
_, ok := be.users[username]
|
||||
if ok {
|
||||
return nil, fmt.Errorf("user %s is already defined.", username)
|
||||
}
|
||||
u := &User{username: username, password: password, mailboxes: make(map[string]*Mailbox)}
|
||||
be.users[username] = u
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// DeleteUser removes a user from the backend.
|
||||
func (be *Backend) DeleteUser(username string) error {
|
||||
_, ok := be.users[username]
|
||||
if !ok {
|
||||
return fmt.Errorf("user %s is not defined.", username)
|
||||
}
|
||||
delete(be.users, username)
|
||||
return nil
|
||||
}
|
||||
243
backend/memory/mailbox.go
Normal file
243
backend/memory/mailbox.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend"
|
||||
"github.com/emersion/go-imap/backend/backendutil"
|
||||
)
|
||||
|
||||
var Delimiter = "/"
|
||||
|
||||
type Mailbox struct {
|
||||
Subscribed bool
|
||||
Messages []*Message
|
||||
|
||||
name string
|
||||
user *User
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) Name() string {
|
||||
return mbox.name
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) Info() (*imap.MailboxInfo, error) {
|
||||
info := &imap.MailboxInfo{
|
||||
Delimiter: Delimiter,
|
||||
Name: mbox.name,
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) uidNext() uint32 {
|
||||
var uid uint32
|
||||
for _, msg := range mbox.Messages {
|
||||
if msg.Uid > uid {
|
||||
uid = msg.Uid
|
||||
}
|
||||
}
|
||||
uid++
|
||||
return uid
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) flags() []string {
|
||||
flagsMap := make(map[string]bool)
|
||||
for _, msg := range mbox.Messages {
|
||||
for _, f := range msg.Flags {
|
||||
if !flagsMap[f] {
|
||||
flagsMap[f] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var flags []string
|
||||
for f := range flagsMap {
|
||||
flags = append(flags, f)
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) unseenSeqNum() uint32 {
|
||||
for i, msg := range mbox.Messages {
|
||||
seqNum := uint32(i + 1)
|
||||
|
||||
seen := false
|
||||
for _, flag := range msg.Flags {
|
||||
if flag == imap.SeenFlag {
|
||||
seen = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !seen {
|
||||
return seqNum
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, error) {
|
||||
status := imap.NewMailboxStatus(mbox.name, items)
|
||||
status.Flags = mbox.flags()
|
||||
status.PermanentFlags = []string{"\\*"}
|
||||
status.UnseenSeqNum = mbox.unseenSeqNum()
|
||||
|
||||
for _, name := range items {
|
||||
switch name {
|
||||
case imap.StatusMessages:
|
||||
status.Messages = uint32(len(mbox.Messages))
|
||||
case imap.StatusUidNext:
|
||||
status.UidNext = mbox.uidNext()
|
||||
case imap.StatusUidValidity:
|
||||
status.UidValidity = 1
|
||||
case imap.StatusRecent:
|
||||
status.Recent = 0 // TODO
|
||||
case imap.StatusUnseen:
|
||||
status.Unseen = 0 // TODO
|
||||
}
|
||||
}
|
||||
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) SetSubscribed(subscribed bool) error {
|
||||
mbox.Subscribed = subscribed
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) Check() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) ListMessages(uid bool, seqSet *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error {
|
||||
defer close(ch)
|
||||
|
||||
for i, msg := range mbox.Messages {
|
||||
seqNum := uint32(i + 1)
|
||||
|
||||
var id uint32
|
||||
if uid {
|
||||
id = msg.Uid
|
||||
} else {
|
||||
id = seqNum
|
||||
}
|
||||
if !seqSet.Contains(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
m, err := msg.Fetch(seqNum, items)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ch <- m
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error) {
|
||||
var ids []uint32
|
||||
for i, msg := range mbox.Messages {
|
||||
seqNum := uint32(i + 1)
|
||||
|
||||
ok, err := msg.Match(seqNum, criteria)
|
||||
if err != nil || !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var id uint32
|
||||
if uid {
|
||||
id = msg.Uid
|
||||
} else {
|
||||
id = seqNum
|
||||
}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) CreateMessage(flags []string, date time.Time, body imap.Literal) error {
|
||||
if date.IsZero() {
|
||||
date = time.Now()
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mbox.Messages = append(mbox.Messages, &Message{
|
||||
Uid: mbox.uidNext(),
|
||||
Date: date,
|
||||
Size: uint32(len(b)),
|
||||
Flags: flags,
|
||||
Body: b,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, op imap.FlagsOp, flags []string) error {
|
||||
for i, msg := range mbox.Messages {
|
||||
var id uint32
|
||||
if uid {
|
||||
id = msg.Uid
|
||||
} else {
|
||||
id = uint32(i + 1)
|
||||
}
|
||||
if !seqset.Contains(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
msg.Flags = backendutil.UpdateFlags(msg.Flags, op, flags)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) CopyMessages(uid bool, seqset *imap.SeqSet, destName string) error {
|
||||
dest, ok := mbox.user.mailboxes[destName]
|
||||
if !ok {
|
||||
return backend.ErrNoSuchMailbox
|
||||
}
|
||||
|
||||
for i, msg := range mbox.Messages {
|
||||
var id uint32
|
||||
if uid {
|
||||
id = msg.Uid
|
||||
} else {
|
||||
id = uint32(i + 1)
|
||||
}
|
||||
if !seqset.Contains(id) {
|
||||
continue
|
||||
}
|
||||
|
||||
msgCopy := *msg
|
||||
msgCopy.Uid = dest.uidNext()
|
||||
dest.Messages = append(dest.Messages, &msgCopy)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mbox *Mailbox) Expunge() error {
|
||||
for i := len(mbox.Messages) - 1; i >= 0; i-- {
|
||||
msg := mbox.Messages[i]
|
||||
|
||||
deleted := false
|
||||
for _, flag := range msg.Flags {
|
||||
if flag == imap.DeletedFlag {
|
||||
deleted = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if deleted {
|
||||
mbox.Messages = append(mbox.Messages[:i], mbox.Messages[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
74
backend/memory/message.go
Normal file
74
backend/memory/message.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/emersion/go-imap"
|
||||
"github.com/emersion/go-imap/backend/backendutil"
|
||||
"github.com/emersion/go-message"
|
||||
"github.com/emersion/go-message/textproto"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
Uid uint32
|
||||
Date time.Time
|
||||
Size uint32
|
||||
Flags []string
|
||||
Body []byte
|
||||
}
|
||||
|
||||
func (m *Message) entity() (*message.Entity, error) {
|
||||
return message.Read(bytes.NewReader(m.Body))
|
||||
}
|
||||
|
||||
func (m *Message) headerAndBody() (textproto.Header, io.Reader, error) {
|
||||
body := bufio.NewReader(bytes.NewReader(m.Body))
|
||||
hdr, err := textproto.ReadHeader(body)
|
||||
return hdr, body, err
|
||||
}
|
||||
|
||||
func (m *Message) Fetch(seqNum uint32, items []imap.FetchItem) (*imap.Message, error) {
|
||||
fetched := imap.NewMessage(seqNum, items)
|
||||
for _, item := range items {
|
||||
switch item {
|
||||
case imap.FetchEnvelope:
|
||||
hdr, _, _ := m.headerAndBody()
|
||||
fetched.Envelope, _ = backendutil.FetchEnvelope(hdr)
|
||||
case imap.FetchBody, imap.FetchBodyStructure:
|
||||
hdr, body, _ := m.headerAndBody()
|
||||
fetched.BodyStructure, _ = backendutil.FetchBodyStructure(hdr, body, item == imap.FetchBodyStructure)
|
||||
case imap.FetchFlags:
|
||||
fetched.Flags = m.Flags
|
||||
case imap.FetchInternalDate:
|
||||
fetched.InternalDate = m.Date
|
||||
case imap.FetchRFC822Size:
|
||||
fetched.Size = m.Size
|
||||
case imap.FetchUid:
|
||||
fetched.Uid = m.Uid
|
||||
default:
|
||||
section, err := imap.ParseBodySectionName(item)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
body := bufio.NewReader(bytes.NewReader(m.Body))
|
||||
hdr, err := textproto.ReadHeader(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l, _ := backendutil.FetchBodySection(hdr, body, section)
|
||||
fetched.Body[section] = l
|
||||
}
|
||||
}
|
||||
|
||||
return fetched, nil
|
||||
}
|
||||
|
||||
func (m *Message) Match(seqNum uint32, c *imap.SearchCriteria) (bool, error) {
|
||||
e, _ := m.entity()
|
||||
return backendutil.Match(e, seqNum, m.Uid, m.Date, m.Flags, c)
|
||||
}
|
||||
82
backend/memory/user.go
Normal file
82
backend/memory/user.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package memory
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-imap/backend"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
username string
|
||||
password string
|
||||
mailboxes map[string]*Mailbox
|
||||
}
|
||||
|
||||
func (u *User) Username() string {
|
||||
return u.username
|
||||
}
|
||||
|
||||
func (u *User) ListMailboxes(subscribed bool) (mailboxes []backend.Mailbox, err error) {
|
||||
for _, mailbox := range u.mailboxes {
|
||||
if subscribed && !mailbox.Subscribed {
|
||||
continue
|
||||
}
|
||||
|
||||
mailboxes = append(mailboxes, mailbox)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u *User) GetMailbox(name string) (mailbox backend.Mailbox, err error) {
|
||||
mailbox, ok := u.mailboxes[name]
|
||||
if !ok {
|
||||
err = errors.New("No such mailbox")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (u *User) CreateMailbox(name string) error {
|
||||
if _, ok := u.mailboxes[name]; ok {
|
||||
return errors.New("Mailbox already exists")
|
||||
}
|
||||
|
||||
u.mailboxes[name] = &Mailbox{name: name, user: u}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) DeleteMailbox(name string) error {
|
||||
if name == "INBOX" {
|
||||
return errors.New("Cannot delete INBOX")
|
||||
}
|
||||
if _, ok := u.mailboxes[name]; !ok {
|
||||
return errors.New("No such mailbox")
|
||||
}
|
||||
|
||||
delete(u.mailboxes, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) RenameMailbox(existingName, newName string) error {
|
||||
mbox, ok := u.mailboxes[existingName]
|
||||
if !ok {
|
||||
return errors.New("No such mailbox")
|
||||
}
|
||||
|
||||
u.mailboxes[newName] = &Mailbox{
|
||||
name: newName,
|
||||
Messages: mbox.Messages,
|
||||
user: u,
|
||||
}
|
||||
|
||||
mbox.Messages = nil
|
||||
|
||||
if existingName != "INBOX" {
|
||||
delete(u.mailboxes, existingName)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) Logout() error {
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user