github.com/jordwest/imap-server@v0.0.0-20200627020849-1cf758ba359f/mailstore/dummy_mailstore.go (about) 1 package mailstore 2 3 import ( 4 "errors" 5 "fmt" 6 "net/textproto" 7 "time" 8 9 "github.com/jordwest/imap-server/types" 10 "github.com/jordwest/imap-server/util" 11 ) 12 13 // DummyMailstore is an in-memory mail storage for testing purposes and to 14 // provide an example implementation of a mailstore 15 type DummyMailstore struct { 16 User *DummyUser 17 } 18 19 func newDummyMailbox(name string) *DummyMailbox { 20 return &DummyMailbox{ 21 name: name, 22 messages: make([]Message, 0), 23 nextuid: 10, 24 } 25 } 26 27 // NewDummyMailstore performs some initialisation and should always be 28 // used to create a new DummyMailstore 29 func NewDummyMailstore() *DummyMailstore { 30 ms := &DummyMailstore{ 31 User: &DummyUser{ 32 authenticated: false, 33 mailboxes: make([]*DummyMailbox, 2), 34 }, 35 } 36 ms.User.mailstore = ms 37 ms.User.mailboxes[0] = newDummyMailbox("INBOX") 38 ms.User.mailboxes[0].ID = 0 39 ms.User.mailboxes[0].mailstore = ms 40 // Mon Jan 2 15:04:05 -0700 MST 2006 41 mailTime, _ := time.Parse("02-Jan-2006 15:04:05 -0700", "28-Oct-2014 00:09:00 +0700") 42 ms.User.mailboxes[0].addEmail("me@test.com", "you@test.com", "Test email", mailTime, 43 "Test email\r\n"+ 44 "Regards,\r\n"+ 45 "Me") 46 ms.User.mailboxes[0].addEmail("me@test.com", "you@test.com", "Another test email", mailTime, 47 "Another test email") 48 ms.User.mailboxes[0].addEmail("me@test.com", "you@test.com", "Last email", mailTime, 49 "Hello") 50 51 ms.User.mailboxes[1] = newDummyMailbox("Trash") 52 ms.User.mailboxes[1].ID = 1 53 ms.User.mailboxes[1].mailstore = ms 54 return ms 55 } 56 57 // Authenticate implements the Authenticate method on the Mailstore interface 58 func (d *DummyMailstore) Authenticate(username string, password string) (User, error) { 59 if username != "username" { 60 return &DummyUser{}, errors.New("Invalid username. Use 'username'") 61 } 62 63 if password != "password" { 64 return &DummyUser{}, errors.New("Invalid password. Use 'password'") 65 } 66 67 d.User.authenticated = true 68 return d.User, nil 69 } 70 71 // DummyUser is an in-memory representation of a mailstore's user 72 type DummyUser struct { 73 authenticated bool 74 mailboxes []*DummyMailbox 75 mailstore *DummyMailstore 76 } 77 78 // Mailboxes implements the Mailboxes method on the User interface 79 func (u *DummyUser) Mailboxes() []Mailbox { 80 mailboxes := make([]Mailbox, len(u.mailboxes)) 81 index := 0 82 for _, element := range u.mailboxes { 83 mailboxes[index] = element 84 index++ 85 } 86 return mailboxes 87 } 88 89 // MailboxByName returns a DummyMailbox object, given the mailbox's name 90 func (u *DummyUser) MailboxByName(name string) (Mailbox, error) { 91 for _, mailbox := range u.mailboxes { 92 if mailbox.Name() == name { 93 return mailbox, nil 94 } 95 } 96 return nil, errors.New("Invalid mailbox") 97 } 98 99 // DummyMailbox is an in-memory implementation of a Mailstore Mailbox 100 type DummyMailbox struct { 101 ID uint32 102 name string 103 nextuid uint32 104 messages []Message 105 mailstore *DummyMailstore 106 } 107 108 // DebugPrintMailbox prints out all messages in the mailbox to the command line 109 // for debugging purposes 110 func (m *DummyMailbox) DebugPrintMailbox() { 111 debugPrintMessages(m.messages) 112 } 113 114 // Name returns the Mailbox's name 115 func (m *DummyMailbox) Name() string { return m.name } 116 117 // NextUID returns the UID that is likely to be assigned to the next 118 // new message in the Mailbox 119 func (m *DummyMailbox) NextUID() uint32 { return m.nextuid } 120 121 // LastUID returns the UID of the last message in the mailbox or if the 122 // mailbox is empty, the next expected UID 123 func (m *DummyMailbox) LastUID() uint32 { 124 lastMsgIndex := len(m.messages) - 1 125 126 // If no messages in the mailbox, return the next UID 127 if lastMsgIndex == -1 { 128 return m.NextUID() 129 } 130 131 return m.messages[lastMsgIndex].UID() 132 } 133 134 // Recent returns the number of messages in the mailbox which are currently 135 // marked with the 'Recent' flag 136 func (m *DummyMailbox) Recent() uint32 { 137 var count uint32 138 for _, message := range m.messages { 139 if message.Flags().HasFlags(types.FlagRecent) { 140 count++ 141 } 142 } 143 return count 144 } 145 146 // Messages returns the total number of messages in the Mailbox 147 func (m *DummyMailbox) Messages() uint32 { return uint32(len(m.messages)) } 148 149 // Unseen returns the number of messages in the mailbox which are currently 150 // marked with the 'Unseen' flag 151 func (m *DummyMailbox) Unseen() uint32 { 152 count := uint32(0) 153 for _, message := range m.messages { 154 if !message.Flags().HasFlags(types.FlagSeen) { 155 count++ 156 } 157 } 158 return count 159 } 160 161 // MessageBySequenceNumber returns a single message given the message's sequence number 162 func (m *DummyMailbox) MessageBySequenceNumber(seqno uint32) Message { 163 if seqno > uint32(len(m.messages)) { 164 return nil 165 } 166 return m.messages[seqno-1] 167 } 168 169 // MessageByUID returns a single message given the message's sequence number 170 func (m *DummyMailbox) MessageByUID(uidno uint32) Message { 171 for _, message := range m.messages { 172 if message.UID() == uidno { 173 return message 174 } 175 } 176 177 // No message found 178 return nil 179 } 180 181 // MessageSetByUID returns a slice of messages given a set of UID ranges. 182 // eg 1,5,9,28:140,190:* 183 func (m *DummyMailbox) MessageSetByUID(set types.SequenceSet) []Message { 184 var msgs []Message 185 186 // If the mailbox is empty, return empty array 187 if m.Messages() == 0 { 188 return msgs 189 } 190 191 for _, msgRange := range set { 192 // If Min is "*", meaning the last UID in the mailbox, Max should 193 // always be Nil 194 if msgRange.Min.Last() { 195 // Return the last message in the mailbox 196 msgs = append(msgs, m.MessageByUID(m.LastUID())) 197 continue 198 } 199 200 start, err := msgRange.Min.Value() 201 if err != nil { 202 fmt.Printf("Error: %s\n", err.Error()) 203 return msgs 204 } 205 206 // If no Max is specified, the sequence number must be either a fixed 207 // sequence number or 208 if msgRange.Max.Nil() { 209 var uid uint32 210 // Fetch specific message by sequence number 211 uid, err = msgRange.Min.Value() 212 msg := m.MessageByUID(uid) 213 if err != nil { 214 fmt.Printf("Error: %s\n", err.Error()) 215 return msgs 216 } 217 if msg != nil { 218 msgs = append(msgs, msg) 219 } 220 continue 221 } 222 223 var end uint32 224 if msgRange.Max.Last() { 225 end = m.LastUID() 226 } else { 227 end, err = msgRange.Max.Value() 228 } 229 230 // Note this is very inefficient when 231 // the message array is large. A proper 232 // storage system using eg SQL might 233 // instead perform a query here using 234 // the range values instead. 235 for _, msg := range m.messages { 236 uid := msg.UID() 237 if uid >= start && uid <= end { 238 msgs = append(msgs, msg) 239 } 240 } 241 for index := uint32(start); index <= end; index++ { 242 } 243 } 244 245 return msgs 246 } 247 248 // MessageSetBySequenceNumber returns a slice of messages given a set of 249 // sequence number ranges 250 func (m *DummyMailbox) MessageSetBySequenceNumber(set types.SequenceSet) []Message { 251 var msgs []Message 252 253 // If the mailbox is empty, return empty array 254 if m.Messages() == 0 { 255 return msgs 256 } 257 258 // For each sequence range in the sequence set 259 for _, msgRange := range set { 260 // If Min is "*", meaning the last message in the mailbox, Max should 261 // always be Nil 262 if msgRange.Min.Last() { 263 // Return the last message in the mailbox 264 msgs = append(msgs, m.MessageBySequenceNumber(m.Messages())) 265 continue 266 } 267 268 start, err := msgRange.Min.Value() 269 if err != nil { 270 fmt.Printf("Error: %s\n", err.Error()) 271 return msgs 272 } 273 274 // If no Max is specified, the sequence number must be either a fixed 275 // sequence number or 276 if msgRange.Max.Nil() { 277 var sequenceNo uint32 278 // Fetch specific message by sequence number 279 sequenceNo, err = msgRange.Min.Value() 280 if err != nil { 281 fmt.Printf("Error: %s\n", err.Error()) 282 return msgs 283 } 284 msg := m.MessageBySequenceNumber(sequenceNo) 285 if msg != nil { 286 msgs = append(msgs, msg) 287 } 288 continue 289 } 290 291 var end uint32 292 if msgRange.Max.Last() { 293 end = uint32(len(m.messages)) 294 } else { 295 end, err = msgRange.Max.Value() 296 } 297 298 // Note this is very inefficient when 299 // the message array is large. A proper 300 // storage system using eg SQL might 301 // instead perform a query here using 302 // the range values instead. 303 for seqNo := start; seqNo <= end; seqNo++ { 304 msgs = append(msgs, m.MessageBySequenceNumber(seqNo)) 305 } 306 } 307 return msgs 308 309 } 310 311 // NewMessage creates a new message in the dummy mailbox. 312 func (m *DummyMailbox) NewMessage() Message { 313 return &DummyMessage{ 314 sequenceNumber: 0, 315 uid: 0, 316 header: make(textproto.MIMEHeader), 317 internalDate: time.Now(), 318 flags: types.Flags(0), 319 mailstore: m.mailstore, 320 mailboxID: m.ID, 321 body: "", 322 } 323 } 324 325 func (m *DummyMailbox) addEmail(from string, to string, subject string, date time.Time, body string) { 326 uid := m.nextuid 327 m.nextuid++ 328 329 hdr := make(textproto.MIMEHeader) 330 hdr.Set("Date", date.Format(util.RFC822Date)) 331 hdr.Set("To", to) 332 hdr.Set("From", from) 333 hdr.Set("Subject", subject) 334 hdr.Set("Message-ID", fmt.Sprintf("<%d@test.com>", uid)) 335 336 newMessage := &DummyMessage{ 337 sequenceNumber: uint32(len(m.messages) + 1), 338 uid: uid, 339 header: hdr, 340 body: body, 341 internalDate: date, 342 } 343 newMessage = newMessage.AddFlags(types.FlagRecent).(*DummyMessage) 344 newMessage.mailboxID = m.ID 345 newMessage.mailstore = m.mailstore 346 m.messages = append(m.messages, newMessage) 347 } 348 349 // DummyMessage is a representation of a single in-memory message in a 350 // DummyMailbox. 351 type DummyMessage struct { 352 sequenceNumber uint32 353 uid uint32 354 header textproto.MIMEHeader 355 internalDate time.Time 356 flags types.Flags 357 mailboxID uint32 358 mailstore *DummyMailstore 359 body string 360 } 361 362 // Header returns the message's MIME Header. 363 func (m *DummyMessage) Header() (hdr textproto.MIMEHeader) { 364 return m.header 365 } 366 367 // UID returns the message's unique identifier (UID). 368 func (m *DummyMessage) UID() uint32 { return m.uid } 369 370 // SequenceNumber returns the message's sequence number. 371 func (m *DummyMessage) SequenceNumber() uint32 { return m.sequenceNumber } 372 373 // Size returns the message's full RFC822 size, including full message header 374 // and body. 375 func (m *DummyMessage) Size() uint32 { 376 hdrStr := fmt.Sprintf("%s\r\n", m.Header()) 377 return uint32(len(hdrStr)) + uint32(len(m.Body())) 378 } 379 380 // InternalDate returns the internally stored date of the message 381 func (m *DummyMessage) InternalDate() time.Time { 382 return m.internalDate 383 } 384 385 // Body returns the full body of the message 386 func (m *DummyMessage) Body() string { 387 return m.body 388 } 389 390 // Keywords returns any keywords associated with the message 391 func (m *DummyMessage) Keywords() []string { 392 var f []string 393 //f[0] = "Test" 394 return f 395 } 396 397 // Flags returns any flags on the message. 398 func (m *DummyMessage) Flags() types.Flags { 399 return m.flags 400 } 401 402 // OverwriteFlags replaces any flags on the message with those specified. 403 func (m *DummyMessage) OverwriteFlags(newFlags types.Flags) Message { 404 m.flags = newFlags 405 return m 406 } 407 408 // AddFlags adds the given flag to the message. 409 func (m *DummyMessage) AddFlags(newFlags types.Flags) Message { 410 m.flags = m.flags.SetFlags(newFlags) 411 return m 412 } 413 414 // RemoveFlags removes the given flag from the message. 415 func (m *DummyMessage) RemoveFlags(newFlags types.Flags) Message { 416 m.flags = m.flags.ResetFlags(newFlags) 417 return m 418 } 419 420 // SetHeaders sets the e-mail headers of the message. 421 func (m *DummyMessage) SetHeaders(newHeader textproto.MIMEHeader) Message { 422 m.header = newHeader 423 return m 424 } 425 426 // SetBody sets the body of the message. 427 func (m *DummyMessage) SetBody(newBody string) Message { 428 m.body = newBody 429 return m 430 } 431 432 // Save saves the message to the mailbox it belongs to. 433 func (m *DummyMessage) Save() (Message, error) { 434 mailbox := m.mailstore.User.mailboxes[m.mailboxID] 435 if m.sequenceNumber == 0 { 436 // Message is new 437 m.uid = mailbox.nextuid 438 mailbox.nextuid++ 439 m.sequenceNumber = uint32(len(mailbox.messages)) 440 mailbox.messages = append(mailbox.messages, m) 441 } else { 442 // Message exists 443 mailbox.messages[m.sequenceNumber-1] = m 444 } 445 return m, nil 446 } 447 448 // DeleteFlaggedMessages deletes messages marked with the Delete flag and 449 // returns them. 450 func (m *DummyMailbox) DeleteFlaggedMessages() ([]Message, error) { 451 var delIDs []int 452 var delMsgs []Message 453 454 // Find messages to be deleted. 455 for i, msg := range m.messages { 456 if msg.Flags().HasFlags(types.FlagDeleted) { 457 delIDs = append(delIDs, i) 458 delMsgs = append(delMsgs, msg) 459 } 460 } 461 462 // Delete message from slice. Run this backward because otherwise it would 463 // fail if we have multiple items to remove. 464 for x := len(delIDs) - 1; x >= 0; x-- { 465 i := delIDs[x] 466 // From: https://github.com/golang/go/wiki/SliceTricks 467 m.messages, m.messages[len(m.messages)-1] = append(m.messages[:i], 468 m.messages[i+1:]...), nil 469 } 470 471 // Update sequence numbers. 472 for i, msg := range m.messages { 473 dmsg := msg.(*DummyMessage) 474 dmsg.sequenceNumber = uint32(i) + 1 475 } 476 477 return delMsgs, nil 478 } 479 480 func debugPrintMessages(messages []Message) { 481 fmt.Printf("SeqNo |UID |From |To |Subject\n") 482 fmt.Printf("-------+-------+----------+----------+-------\n") 483 for _, msg := range messages { 484 from := msg.Header().Get("from") 485 to := msg.Header().Get("to") 486 subject := msg.Header().Get("subject") 487 fmt.Printf("%-7d|%-7d|%-10.10s|%-10.10s|%s\n", msg.SequenceNumber(), msg.UID(), from, to, subject) 488 } 489 }