Class MboxMailbox

java.lang.Object
org.bluezoo.gumdrop.mailbox.mbox.MboxMailbox
All Implemented Interfaces:
Mailbox

public class MboxMailbox extends Object implements Mailbox
Mbox-format mailbox implementation.

This implementation handles the standard Unix mbox format as defined in RFC 4155. An mbox file contains multiple messages separated by "From " envelope lines.

Mbox Format:

 From sender@example.com Mon Jan  1 00:00:00 2025
 [RFC 822 message headers]
 
 [message body]
 
 From another@example.com Tue Jan  2 00:00:00 2025
 [next message...]
 

From_ Escaping: Lines in the message body that begin with "From " are escaped by prepending ">" to become ">From ". When reading, this escaping is reversed.

This implementation uses file locking to prevent concurrent access.

Author:
Chris Burdess
See Also:
  • Constructor Details

    • MboxMailbox

      public MboxMailbox(Path mboxFile, String name, boolean readOnly) throws IOException
      Creates and opens an mbox mailbox.
      Parameters:
      mboxFile - the mbox file path
      name - the mailbox name
      readOnly - true to open in read-only mode
      Throws:
      IOException - if the mailbox cannot be opened
  • Method Details

    • getName

      public String getName()
      Description copied from interface: Mailbox
      Returns the name of this mailbox. For POP3, this is typically "INBOX".
      Specified by:
      getName in interface Mailbox
      Returns:
      the mailbox name
    • isReadOnly

      public boolean isReadOnly()
      Description copied from interface: Mailbox
      Returns whether this mailbox is open in read-only mode.
      Specified by:
      isReadOnly in interface Mailbox
      Returns:
      true if read-only
    • close

      public void close(boolean expunge) throws IOException
      Description copied from interface: Mailbox
      Closes the mailbox and releases any locks. This should commit any pending deletions.
      Specified by:
      close in interface Mailbox
      Parameters:
      expunge - if true, permanently remove messages marked for deletion; if false, unmark all deletion marks
      Throws:
      IOException - if the mailbox cannot be closed properly
    • getMessageCount

      public int getMessageCount() throws IOException
      Description copied from interface: Mailbox
      Returns the number of messages in the mailbox. This does not include messages marked for deletion.
      Specified by:
      getMessageCount in interface Mailbox
      Returns:
      the number of accessible messages
      Throws:
      IOException - if the count cannot be determined
    • getMailboxSize

      public long getMailboxSize() throws IOException
      Description copied from interface: Mailbox
      Returns the total size of all messages in octets. This does not include messages marked for deletion.
      Specified by:
      getMailboxSize in interface Mailbox
      Returns:
      the total size in octets
      Throws:
      IOException - if the size cannot be determined
    • getMessageList

      public Iterator<MessageDescriptor> getMessageList() throws IOException
      Description copied from interface: Mailbox
      Returns an iterator over all message descriptors in the mailbox. Messages are returned in order by message number (1-based). Messages marked for deletion are not included.

      Using an iterator rather than a list allows implementations to lazily load descriptors, reducing memory usage for large mailboxes. Use Mailbox.getMessageCount() to get the total count.

      Specified by:
      getMessageList in interface Mailbox
      Returns:
      iterator over message descriptors
      Throws:
      IOException - if the messages cannot be enumerated
    • getMessage

      public MessageDescriptor getMessage(int messageNumber) throws IOException
      Description copied from interface: Mailbox
      Returns the message descriptor for the specified message number.
      Specified by:
      getMessage in interface Mailbox
      Parameters:
      messageNumber - the message sequence number (1-based)
      Returns:
      the message descriptor, or null if the message doesn't exist
      Throws:
      IOException - if the descriptor cannot be retrieved
    • getMessageContent

      public ReadableByteChannel getMessageContent(int messageNumber) throws IOException
      Description copied from interface: Mailbox
      Opens a channel to read the entire message content. The content includes headers and body, with proper RFC 822 formatting.

      The caller is responsible for closing the returned channel.

      Specified by:
      getMessageContent in interface Mailbox
      Parameters:
      messageNumber - the message sequence number (1-based)
      Returns:
      a readable channel for the message content
      Throws:
      IOException - if the message cannot be opened
    • getMessageTop

      public ReadableByteChannel getMessageTop(int messageNumber, int bodyLines) throws IOException
      Description copied from interface: Mailbox
      Opens a channel to read the message headers and optionally a portion of the body, as required by the POP3 TOP command (RFC 1939 Section 7).

      The content returned includes:

      1. All message headers (RFC 822 format)
      2. A blank line (CRLF) separating headers from body
      3. The first bodyLines lines of the message body

      The caller is responsible for closing the returned channel.

      Specified by:
      getMessageTop in interface Mailbox
      Parameters:
      messageNumber - the message sequence number (1-based)
      bodyLines - the number of body lines to retrieve after headers (0 means headers only, with trailing blank line)
      Returns:
      a readable channel for the headers and body lines
      Throws:
      IOException - if the content cannot be retrieved
    • deleteMessage

      public void deleteMessage(int messageNumber) throws IOException
      Description copied from interface: Mailbox
      Marks a message for deletion. The message is not actually deleted until close() is called with expunge=true, or expunge() is called explicitly.
      Specified by:
      deleteMessage in interface Mailbox
      Parameters:
      messageNumber - the message sequence number (1-based)
      Throws:
      IOException - if the message cannot be marked for deletion
    • isDeleted

      public boolean isDeleted(int messageNumber) throws IOException
      Description copied from interface: Mailbox
      Checks if a message is marked for deletion.
      Specified by:
      isDeleted in interface Mailbox
      Parameters:
      messageNumber - the message sequence number (1-based)
      Returns:
      true if the message is marked for deletion
      Throws:
      IOException - if the status cannot be determined
    • undeleteAll

      public void undeleteAll() throws IOException
      Description copied from interface: Mailbox
      Unmarks all messages marked for deletion. This is used by the POP3 RSET command.
      Specified by:
      undeleteAll in interface Mailbox
      Throws:
      IOException - if the messages cannot be unmarked
    • expunge

      public List<Integer> expunge() throws IOException
      Description copied from interface: Mailbox
      Permanently removes all messages marked for deletion. This is the IMAP EXPUNGE command.
      Specified by:
      expunge in interface Mailbox
      Returns:
      list of expunged message numbers (in ascending order)
      Throws:
      IOException - if messages cannot be expunged
    • getUniqueId

      public String getUniqueId(int messageNumber) throws IOException
      Description copied from interface: Mailbox
      Returns the unique identifier for a message. This must be unique and persistent across sessions. Typically derived from Message-ID or a content hash.
      Specified by:
      getUniqueId in interface Mailbox
      Parameters:
      messageNumber - the message sequence number (1-based)
      Returns:
      the unique identifier string
      Throws:
      IOException - if the UID cannot be retrieved
    • startAppendMessage

      public void startAppendMessage(Set<Flag> flags, OffsetDateTime internalDate) throws IOException
      Description copied from interface: Mailbox
      Begins appending a new message to this mailbox.

      This is the first of three methods used for streaming message append in an event-driven architecture. After calling this method, call Mailbox.appendMessageContent(ByteBuffer) zero or more times as data arrives, then call Mailbox.endAppendMessage() to complete the operation.

      Only one append operation may be in progress at a time per mailbox instance. Implementations should throw an exception if this method is called while an append is already in progress.

      Specified by:
      startAppendMessage in interface Mailbox
      Parameters:
      flags - initial flags for the message, or null for no flags
      internalDate - the internal date, or null for current time
      Throws:
      IOException - if the append cannot be started
    • appendMessageContent

      public void appendMessageContent(ByteBuffer data) throws IOException
      Description copied from interface: Mailbox
      Appends content data to the message currently being appended.

      This method may be called multiple times as data arrives from the client. The data should be in RFC 822 format. The buffer should be ready for reading (flipped).

      This method must only be called after Mailbox.startAppendMessage(java.util.Set<org.bluezoo.gumdrop.mailbox.Flag>, java.time.OffsetDateTime) and before Mailbox.endAppendMessage().

      Specified by:
      appendMessageContent in interface Mailbox
      Parameters:
      data - buffer containing message content data, ready for reading
      Throws:
      IOException - if the content cannot be written
    • endAppendMessage

      public long endAppendMessage() throws IOException
      Description copied from interface: Mailbox
      Completes the message append operation.

      This method finalizes the append, assigns a UID to the new message, and makes it visible in the mailbox. After this method returns, the server can send the response to the client.

      If an error occurs, implementations should clean up any partial data and throw an exception. The mailbox should remain in a consistent state.

      Specified by:
      endAppendMessage in interface Mailbox
      Returns:
      the UID of the newly appended message
      Throws:
      IOException - if the append cannot be completed
    • search

      public List<Integer> search(SearchCriteria criteria) throws IOException
      Searches for messages matching the given criteria using the search index. Falls back to parsing messages for TEXT/BODY searches.
      Specified by:
      search in interface Mailbox
      Parameters:
      criteria - the search criteria
      Returns:
      list of matching message sequence numbers
      Throws:
      IOException - if the search fails
      See Also: