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  }