Evolution"> ]>
The &Camel; Messaging Library Dan Winship
danw@helixcode.com
Bertrand Guiheneuf
bertrand@helixcode.com
2000 Helix Code, Inc.
Introduction &Camel; is a generic messaging library. It is being used as the back end for the mail component of &Evolution;. The name "&Camel;" is not an acronym; it refers to the fact that the library is capable of going several days without food or water. &Camel;'s initial design is heavily based on Sun's JavaMail API. It uses the Gtk+ object system, and many of its classes are direct analags of JavaMail classes. Its design has also been influenced by the features of IMAP, and the limitations of the standard UNIX mbox mail store, which set some of the boundaries on its requirements and extensibility. &Camel; sees all message repositories as stores containing folders. These folders in turn contain the messages the client actually accesses. The use of such a unified interface allows the client applications to be very extensible. &Camel; includes an external provider mechanism which allows applications to dynamically load and use protocols which were not available when the application was initially written. The abstract store/folder mechanism is a powerful and versatile way of accessing messages. No particular asumptions are made on the client side, thus allowing new ways of managing the messages. For example, the messages stored in the folders don't necessarily have to share some common physical location. The folder can be a purely virtual folder, containing only references to the actual messages. This is used by the "vFolder" provider, which allows you select messages meeting particular criteria and deal with them as a group. In addition to these possibilities, &Camel; has full MIME support. &Camel; MIME messages are lightweight objects representing the MIME skeleton of the actual message. The data contained in the subparts are never stored in memory except when they are actually needed. The application, when accessing the various MIME objects contained in the message (text parts, attachments, embedded binary objects ...) asks &Camel; for a stream that it can read data from. This scheme is particularly useful with the IMAP provider. IMAP has strong MIME support built-in, which allows &Camel; to download only the parts of messages that it actually needs: attachments need not be downloaded until they are viewed, and unnecessary "multipart/alternative" parts will never be read off the server. Overview To begin using &Camel;, an application first creates a CamelSession object. This object is used to store application defaults, and to coordinate communication between providers and the application. A CamelProvider is a dynamically-loadable module that provides functionality associated with a specific service. Examples of providers are IMAP and SMTP. Providers include subclasses of the various other &Camel; classes for accessing and manipulating messages. CamelService is an abstract class for describing a connection to a local or remote service. It currently has two subclasses: CamelStore, for services that store messages (such as IMAP servers and mbox files), and CamelTransport, for services that deliver messages (such as SMTP, or a local MTA). A provider could also be both a store and a transport, as in the case of NNTP. A CamelStore contains some number of CamelFolder objects, which in turn contain messages. A CamelFolder provides a CamelFolderSummary object, which includes details about the subject, date, and sender of each message in the folder. The folder also includes the messages themselves, as subclasses of CamelMedium. Email messages are represented by the CamelMimeMessage class, a subclass of CamelMedium. This class includes operations for accessing RFC822 and MIME headers, accessing subparts of MIME messages, encoding and decoding Base64 and Quoted-Printable, etc. CamelTransport includes methods for delivering messages. While the abstract CamelTransport::send method takes a CamelMedium, its subclasses may only be able to deliver messages of specific CamelMedium subclasses. For instance, CamelSendmailTransport requires a CamelMimeMessage, because it needs a message that includes a "To:" header. A hypothetical CamelNNTPTransport would need a CamelNewsMessage, which would have a "Newsgroups:" header. The content of messages are referred to using CamelStream and its subclasses. In the case of an mbox-based store, the CamelStream would abstract the operation of reading the correct section of the mbox file. For IMAP, reading off the CamelStream might result in commands being issued to the remote IMAP server and data being read off a socket. The final major class in &Camel; is CamelException, which is used to propagate information about errors. Many methods take a CamelException as an argument, which the caller can then check if an error occurs. It includes both a numeric error code which can be interpreted by the program, and a text error message that can be displayed to the user. Major Subcomponents The Message Store A CamelStore inherits the ability to connect and authenticate to a service from its parent class, CamelService. It then adds the ability to retrieve folders. A store must contain at least one folder, which can be retrieved with CamelStore::get_default_folder. There are also methods to retrieve the "top-level" folder (for hieararchical stores), and to retrieve an arbitrary folder by name. All CamelFolders must implement certain core operations, most notably generating a summary and retrieving and deleting messages. A CamelFolder must assign a permanently unique identifier to each message it contains. Messages can then be retrieved via CamelFolder::get_message_by_uid. Alternately, within a single mail-reading session, messages can be referred to by their linear position within the store using CamelFolder::get_message_by_number. Folders must also implement the get_parent_folder and list_subfolders methods. For stores that don't allow multiple folders, they would return NULL and an empty list, respectively. Stores that do allow multiple folders will also define methods for creating and deleting folders, and for moving messages between them (assuming the folders are writable). Folders that support searching can define the search_by_expression method. For mbox folders, this is implemented by indexing the messages with the ibex library and using that to search them later. For IMAP folders, this uses the IMAP SEARCH command. Other folder types might not be able to implement this functionality, in which case users would not be able to do full-content searches on them. Messages As mentioned before, messages are represented by subclasses of CamelMedium. CamelMedium itself is a subclass of CamelDataWrapper, a generic class for connecting a typed data source to a data sink. CamelMedium adds the concept of message headers versus message body. (CamelDataWrapper has one other important subclass, CamelMultipart, which is used to provide separate access to the multiple independent parts of a multipart MIME type.) CamelMedium's subclasses provide more specialized handling of various headers: CamelMimePart adds special handling for the &ldquot;Content-*&rdquot; headers in MIME messages, and its subclass CamelMimeMessage adds handling for the RFC822 headers. Consider a message with two parts: a text part (in both plain text and HTML), and an attached image: From: Dan Winship <danw@helixcode.com> To: Matt Loper <matt@helixcode.com> Subject: the Camel white paper MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="jhTYrnsRrdhDFGa" This is a multi-part message in MIME format. --jhTYrnsRrdhDFGa Content-Type: multipart/alternative; boundary="sFSenbAFDSgDfg" --sFSenbAFDSgDfg Content-Type: text/plain Hey, Matt Check out this graphic... -- Dan --sFSenbAFDSgDfg Content-Type: text/html Hey, Matt<br> <br> Check out this graphic...<br> <br> -- Dan<br> <br> --sFSenbAFDSgDfg-- --jhTYrnsRrdhDFGa Content-Type: image/png Content-Transfer-Encoding: base64 F4JLw0ORrkRa8AwAMQJLAaI3UDIGsco9RAaB92... --jhTYrnsRrdhDFGa-- In &Camel;, this would be represented as follows: Streams Streams are a generic data transport layer. Two basic stream classes are CamelStreamFs, for reading and writing files, and CamelStreamMem, for reading from and writing to objects that are already in memory. Streams can also be chained together. So a CamelMimePart containing base64-encoded data can filter its output through a CamelStreamB64. Other parts of the application that want to read its data will never need to even realize that the original data was encoded.