1package memory
2
3import (
4	"io/ioutil"
5	"time"
6
7	"github.com/emersion/go-imap"
8	"github.com/emersion/go-imap/backend"
9	"github.com/emersion/go-imap/backend/backendutil"
10)
11
12var Delimiter = "/"
13
14type Mailbox struct {
15	Subscribed bool
16	Messages   []*Message
17
18	name string
19	user *User
20}
21
22func (mbox *Mailbox) Name() string {
23	return mbox.name
24}
25
26func (mbox *Mailbox) Info() (*imap.MailboxInfo, error) {
27	info := &imap.MailboxInfo{
28		Delimiter: Delimiter,
29		Name:      mbox.name,
30	}
31	return info, nil
32}
33
34func (mbox *Mailbox) uidNext() uint32 {
35	var uid uint32
36	for _, msg := range mbox.Messages {
37		if msg.Uid > uid {
38			uid = msg.Uid
39		}
40	}
41	uid++
42	return uid
43}
44
45func (mbox *Mailbox) flags() []string {
46	flagsMap := make(map[string]bool)
47	for _, msg := range mbox.Messages {
48		for _, f := range msg.Flags {
49			if !flagsMap[f] {
50				flagsMap[f] = true
51			}
52		}
53	}
54
55	var flags []string
56	for f := range flagsMap {
57		flags = append(flags, f)
58	}
59	return flags
60}
61
62func (mbox *Mailbox) unseenSeqNum() uint32 {
63	for i, msg := range mbox.Messages {
64		seqNum := uint32(i + 1)
65
66		seen := false
67		for _, flag := range msg.Flags {
68			if flag == imap.SeenFlag {
69				seen = true
70				break
71			}
72		}
73
74		if !seen {
75			return seqNum
76		}
77	}
78	return 0
79}
80
81func (mbox *Mailbox) Status(items []imap.StatusItem) (*imap.MailboxStatus, error) {
82	status := imap.NewMailboxStatus(mbox.name, items)
83	status.Flags = mbox.flags()
84	status.PermanentFlags = []string{"\\*"}
85	status.UnseenSeqNum = mbox.unseenSeqNum()
86
87	for _, name := range items {
88		switch name {
89		case imap.StatusMessages:
90			status.Messages = uint32(len(mbox.Messages))
91		case imap.StatusUidNext:
92			status.UidNext = mbox.uidNext()
93		case imap.StatusUidValidity:
94			status.UidValidity = 1
95		case imap.StatusRecent:
96			status.Recent = 0 // TODO
97		case imap.StatusUnseen:
98			status.Unseen = 0 // TODO
99		}
100	}
101
102	return status, nil
103}
104
105func (mbox *Mailbox) SetSubscribed(subscribed bool) error {
106	mbox.Subscribed = subscribed
107	return nil
108}
109
110func (mbox *Mailbox) Check() error {
111	return nil
112}
113
114func (mbox *Mailbox) ListMessages(uid bool, seqSet *imap.SeqSet, items []imap.FetchItem, ch chan<- *imap.Message) error {
115	defer close(ch)
116
117	for i, msg := range mbox.Messages {
118		seqNum := uint32(i + 1)
119
120		var id uint32
121		if uid {
122			id = msg.Uid
123		} else {
124			id = seqNum
125		}
126		if !seqSet.Contains(id) {
127			continue
128		}
129
130		m, err := msg.Fetch(seqNum, items)
131		if err != nil {
132			continue
133		}
134
135		ch <- m
136	}
137
138	return nil
139}
140
141func (mbox *Mailbox) SearchMessages(uid bool, criteria *imap.SearchCriteria) ([]uint32, error) {
142	var ids []uint32
143	for i, msg := range mbox.Messages {
144		seqNum := uint32(i + 1)
145
146		ok, err := msg.Match(seqNum, criteria)
147		if err != nil || !ok {
148			continue
149		}
150
151		var id uint32
152		if uid {
153			id = msg.Uid
154		} else {
155			id = seqNum
156		}
157		ids = append(ids, id)
158	}
159	return ids, nil
160}
161
162func (mbox *Mailbox) CreateMessage(flags []string, date time.Time, body imap.Literal) error {
163	if date.IsZero() {
164		date = time.Now()
165	}
166
167	b, err := ioutil.ReadAll(body)
168	if err != nil {
169		return err
170	}
171
172	mbox.Messages = append(mbox.Messages, &Message{
173		Uid:   mbox.uidNext(),
174		Date:  date,
175		Size:  uint32(len(b)),
176		Flags: flags,
177		Body:  b,
178	})
179	return nil
180}
181
182func (mbox *Mailbox) UpdateMessagesFlags(uid bool, seqset *imap.SeqSet, op imap.FlagsOp, flags []string) error {
183	for i, msg := range mbox.Messages {
184		var id uint32
185		if uid {
186			id = msg.Uid
187		} else {
188			id = uint32(i + 1)
189		}
190		if !seqset.Contains(id) {
191			continue
192		}
193
194		msg.Flags = backendutil.UpdateFlags(msg.Flags, op, flags)
195	}
196
197	return nil
198}
199
200func (mbox *Mailbox) CopyMessages(uid bool, seqset *imap.SeqSet, destName string) error {
201	dest, ok := mbox.user.mailboxes[destName]
202	if !ok {
203		return backend.ErrNoSuchMailbox
204	}
205
206	for i, msg := range mbox.Messages {
207		var id uint32
208		if uid {
209			id = msg.Uid
210		} else {
211			id = uint32(i + 1)
212		}
213		if !seqset.Contains(id) {
214			continue
215		}
216
217		msgCopy := *msg
218		msgCopy.Uid = dest.uidNext()
219		dest.Messages = append(dest.Messages, &msgCopy)
220	}
221
222	return nil
223}
224
225func (mbox *Mailbox) Expunge() error {
226	for i := len(mbox.Messages) - 1; i >= 0; i-- {
227		msg := mbox.Messages[i]
228
229		deleted := false
230		for _, flag := range msg.Flags {
231			if flag == imap.DeletedFlag {
232				deleted = true
233				break
234			}
235		}
236
237		if deleted {
238			mbox.Messages = append(mbox.Messages[:i], mbox.Messages[i+1:]...)
239		}
240	}
241
242	return nil
243}
244