Camel.DataWrapper

CamelDataWrapper is the base-class used to implement structured data access. It is used as the programmatic interface to creating and reading MIME encoded data; primarily RFC822 messages, but it can be used for other formats too.

This is one of the oldest object heirarchies in Camel; they are based reasonly closely on the JavaMail API equivalents, with caveats because they were written before Camel supported interfaces. There are also some oddly-named and inconsistent interfaces, but most of the inconsistency is hidden for the normal usage of these structures.

Base Class

The data wrapper itself contains many virtual functions which are usually overriden by sub-classes, but it is usable in itself to hold the basic blocks of unstructured data at the leaves of the MIME tree.

The base class implements the structures and methods for i/o related functions, and describing the content's type and encoding.

 struct _CamelDataWrapper {
        CamelObject parent_object;
        struct _CamelDataWrapperPrivate *priv;
        
        CamelTransferEncoding encoding;
        
        CamelContentType *mime_type;
        CamelStream *stream;
        
        unsigned int offline:1;
 };
 
 CamelDataWrapper *camel_data_wrapper_new(void);

The i/o related functions are all based on CamelStream.

 ssize_t camel_data_wrapper_write_to_stream(CamelDataWrapper *data_wrapper, CamelStream *stream);

This will write the object to the <code>stream</code> in an encoded format. i.e. this is the entry point to create a MIME stream.

 ssize_t camel_data_wrapper_decode_to_stream(CamelDataWrapper *data_wrapper, CamelStream *stream);

This is a helper function which is normally only used on lowest-level leaf nodes in the MIME structure. It will take into account the content-transfer-encoding of the data block, and convert the content to un-encoded form. If the data is textual in content, it will also canonicalise the end of line markers to suit.

 int camel_data_wrapper_construct_from_stream(CamelDataWrapper *data_wrapper, CamelStream *stream);

This is the main entry point for parsing raw MIME content into an object hierarchy. This virtual method may be overridden by subclasses to implement structured parsing. If this is an instance of the base class, then the stream becomes the raw content of the object; this is used for leaf nodes.

Then there are an overlapping set of content-type handling functions. Some of these are redundant, it probably only needs to access the mime_type field in the object rather than having the string interfaces.

 void camel_data_wrapper_set_mime_type(CamelDataWrapper *data_wrapper, const char *mime_type);
 char *camel_data_wrapper_get_mime_type(CamelDataWrapper *data_wrapper);
 CamelContentType *camel_data_wrapper_get_mime_type_field(CamelDataWrapper *data_wrapper);
 void camel_data_wrapper_set_mime_type_field(CamelDataWrapper *data_wrapper, CamelContentType *mime_type);

And finally there is an old function which is no longer used. It ised to be available to let the client know that some content may need to be downloaded so is not fast to access. Don't use this anymore:

 gboolean camel_data_wrapper_is_offline(CamelDataWrapper *data_wrapper);

Cleanup ideas

camel_data_wrapper_is_offline and its bit could go. The mime-type functions should be cleaned up. Some of the other function names are inconveniently long. It could possibly take a void * object pointer to avoid numerous ugly casts whenever any of these functions are used.

Camel.Medium

CamelMedium is the next level of object. It is an abstract class which adds headers to a CamelDataWrapper, and a separate content object.

This object isn't really needed - it exists in the JavaMail API because (i think) it can process non-mime data; we don't. So this extra level of abstract complexity doesn't serve much purpose. Although its interfaces are needed, but they could use some work.

These header functions can add raw headers to the medium. For MIME messages these will be the raw headers directly; i.e. any rfc2047 or similar encoding must be applied at this point. These interfaces are used internally or by client code to add custom headers. remove_header will remove all occurrences of this header for multi-valued headers.

 void camel_medium_add_header(CamelMedium *medium, const char *name, const void *value);
 void camel_medium_set_header(CamelMedium *medium, const char *name, const void *value);
 void camel_medium_remove_header(CamelMedium *medium, const char *name);

Then we have some query interfaces. Note that get_header will only retrieve the first header it finds, which will be the first header added (header order is preserved). get_headers can be used to retrieve all headers in a particularly inefficient way - this isn't used as the only subclass of CamelMedium provides a better interface.

 const void *camel_medium_get_header(CamelMedium *medium, const char *name);
 
 typedef struct {
        const char *name;
        const char *value;
 } CamelMediumHeader;
 
 GArray *camel_medium_get_headers(CamelMedium *medium);
 void camel_medium_free_headers(CamelMedium *medium, GArray *headers);

And finally there are some accessors for the content data for this medium. These should always be used as the content may be dynamically created.

 CamelDataWrapper *camel_medium_get_content_object(CamelMedium *medium);
 void camel_medium_set_content_object(CamelMedium *medium, CamelDataWrapper *content);

get_content_object does not, despite appearances, add a separate reference to the object, it only returns a pointer to the internal reference.

Cleanup ideas

get_headers and free_headers and the associated one-use structure could go. set_header could take a NULL value to replace remove_header. get_content_object could be renamed to get_content.

It could possibly just be made an interface, or just merged into CamelMimePart - but only if Camel was never going to be used for some other form of data, like VCard storage.

Camel.MimePart

Now we come to one of the first specifically MIME related objects. Above the header interface, this adds structured accessors to the various Content- headers. This enforces headers that are required by the spec, and knows how to encode and decode each type of header specifically. It also adds a new i/o related virtual method: construct_from_parser, which uses a Camel.MimeParser to build the message structure.

A CamelMimePart is used to wrap basic parts in a MIME message. Whether they are the content of a message type, or the individual parts in a multipart type.

 struct _CamelMimePart {
        CamelMedium parent_object;
        
        struct _camel_header_raw *headers; /* mime headers */
        
        /* All fields here are -** PRIVATE **- */
        char *description;
        CamelContentDisposition *disposition;
        char *content_id;
        char *content_MD5;
        char *content_location;
        GList *content_languages;
        CamelTransferEncoding encoding;
 };
 
 typedef struct _CamelMimePartClass {
        CamelMediumClass parent_class;
        
        int (*construct_from_parser) (CamelMimePart *, CamelMimeParser *);
 } CamelMimePartClass;
 
 CamelMimePart *camel_mime_part_new(void);

Then come the accessors. All accessors take and return UTF-8 strings. If required they perform appropriate rfc-2047 or otherwise de/encoding on the raw headers or parameters.

The first lot deal with the Content-Disposition header, and it's various structured fields.

 void camel_mime_part_set_description(CamelMimePart *mime_part, const char *description);
 const char *camel_mime_part_get_description(CamelMimePart *mime_part);
 
 void camel_mime_part_set_disposition(CamelMimePart *mime_part, const char *disposition);
 const char *camel_mime_part_get_disposition(CamelMimePart *mime_part);
 
 void camel_mime_part_set_filename(CamelMimePart *mime_part, const char *filename);
 const char  *camel_mime_part_get_filename(CamelMimePart *mime_part);

The filename is also set on the Content-Type field for interoperability, and may also be queried from there if it isn't present in the Content-Disposition.

Then we have the Content-ID header. These interfaces take and return the content-id as a raw addrspec - without the enclosing <> characters.

 void camel_mime_part_set_content_id(CamelMimePart *mime_part, const char *contentid);
 const char  *camel_mime_part_get_content_id(CamelMimePart *mime_part);

The Content-MD5 header. We don't use it. Does anybody? Use S/MIME.

 void camel_mime_part_set_content_MD5(CamelMimePart *mime_part, const char *md5sum);
 const char *camel_mime_part_get_content_MD5(CamelMimePart *mime_part);

The Content-Location header. Takes raw encoded url's.

 void camel_mime_part_set_content_location(CamelMimePart *mime_part, const char *location);
 const char *camel_mime_part_get_content_location(CamelMimePart *mime_part);

The Content-Transfer-Encoding header. Only the standard transfer-encodings are supported.

 void camel_mime_part_set_encoding(CamelMimePart *mime_part, CamelTransferEncoding encoding);
 CamelTransferEncoding camel_mime_part_get_encoding(CamelMimePart *mime_part);

Content-Languages header. The arguments are the language codes as separate GList items. This is actually not used, is not decoded and never written.

 void camel_mime_part_set_content_languages(CamelMimePart *mime_part, GList *content_languages);
 const GList *camel_mime_part_get_content_languages(CamelMimePart *mime_part);

The Content-Type header. This is actually stored in the DataWrapper.mime_type - so lo, there is even more duplication for these apis. The set interface takes the raw string of a content-type; it should probably take a CamelContentType.

 void camel_mime_part_set_content_type(CamelMimePart *mime_part, const char *content_type);
 CamelContentType  *camel_mime_part_get_content_type(CamelMimePart *mime_part);

The main i/o utility function used to construct structured MIME objects. CamelMimePart overrides construct_from_stream to create a parser and then invoke construct_from_parser so subclasses don't need to override it.

 int camel_mime_part_construct_from_parser(CamelMimePart *mime_part, CamelMimeParser *parser);

And one simple utility function for creating simple parts, like text/plain parts.

 void camel_mime_part_set_content(CamelMimePart *mime_part, const char *content, int length, const char *type);

Cleanup ideas

This is a mostly ok object from an api standpoint. The Content-Type functions could probably be cleaned up though.

Internally things are a bit strange. It needs to make its mind up whether it should use decoded header structures and cache them as it does now, or always just parse the raw headers whenever they are requested. As it is now, it snoops headers being set and parses them into its cache pointers always. When you write out the object is just uses the raw headers. So if you're just reading in a message and writing it out without looking at all the various headers there is a lot of wasted overhead.

It is possible that all of the accessors could be replaced with Camel.Object#Object Properties instead.

Camel.MimeMessage

CamelMimeMessage adds extra structured accessors for RFC822+MIME messages. Envelope accessors primarily.

 #define CAMEL_MESSAGE_DATE_CURRENT (~0)
 
 struct _CamelMimeMessage
 {
        CamelMimePart parent_object;
 
        time_t date;
        int date_offset;
 
        time_t date_received;
        int date_received_offset;
 
        char *subject;
        char *message_id;
 
        CamelInternetAddress *reply_to;
        CamelInternetAddress *from;
 
        GHashTable *recipients; /* hash table of CamelInternetAddress's */
 };
 
 CamelMimeMessage *camel_mime_message_new(void);

Not much to be said about the above structures, basically just used by the accessors and shouldn't be accessed directly. A hashtable is probably overkill for the receipients lists.

The subject accessor functions. As with other structured fields, the accessors operate on UTF-8 encoded data. They will store their results in RFC2047 format if the text is not US-ASCII.

 void camel_mime_message_set_subject(CamelMimeMessage *message, const char *subject);
 const char *camel_mime_message_get_subject(CamelMimeMessage *message);

The date accessor functions. The normal date functions relate to the RFC822 Date header. Because a date can be from any timezone, the date is stored in a GMT standardised time using a time_t value, and the offset is stored along with it. The offset is stored in a decimal format encoded in binary; "930" means 9 hours and 30 minutes, not nine hunred and thirty. Usually you don't need to worry about this, and just pass it to formatting functions to get a properly formatted date.

The special date CAMEL_MESSAGE_DATE_CURRENT can be used with set_date to specify the current time and current system timezone, without having to look it up. Note that the operating system timezone is used, not the Evolution Calendar timezone.

The received date is different, cannot be set, and depends a little on the backend. By default, CamelMimeMessage will calculate it from the newest Received header entry, but IMAP for example has other mechanisms for determining this date - the INTERNAL-DATE field.

 void camel_mime_message_set_date(CamelMimeMessage *message, time_t date, int offset);
 time_t camel_mime_message_get_date(CamelMimeMessage *message, int *offset);
 time_t camel_mime_message_get_date_received(CamelMimeMessage *message, int *offset);

Then we have the Message-ID fields. Setting a NULL message_id will make Camel calculate one that conforms to the various rfcs. A message-id is an address-spec, without leading or trainling < or >.

 void camel_mime_message_set_message_id(CamelMimeMessage *message, const char *message_id);
 const char *camel_mime_message_get_message_id(CamelMimeMessage *message);

Information about the sender. These take a Camel.Address#Camel.InternetAddress, who's content is copied.

 void camel_mime_message_set_reply_to(CamelMimeMessage *message, const CamelInternetAddress *reply_to);
 const CamelInternetAddress *camel_mime_message_get_reply_to(CamelMimeMessage *message);

 void camel_mime_message_set_from(CamelMimeMessage *message, const CamelInternetAddress *from);
 const CamelInternetAddress *camel_mime_message_get_from(CamelMimeMessage *message);

Information about the recipients. Instead of an accessor for each type, there is one which specifies the type as an argument. There are some macro's to 'simplify' the recipient type, although it is far easier to just use the RFC822 header names directly. They work like the sender functions otherwise.

 #define CAMEL_RECIPIENT_TYPE_TO "To"
 #define CAMEL_RECIPIENT_TYPE_CC "Cc"
 #define CAMEL_RECIPIENT_TYPE_BCC "Bcc"
 
 #define CAMEL_RECIPIENT_TYPE_RESENT_TO "Resent-To"
 #define CAMEL_RECIPIENT_TYPE_RESENT_CC "Resent-Cc"
 #define CAMEL_RECIPIENT_TYPE_RESENT_BCC "Resent-Bcc"
 
 const CamelInternetAddress *camel_mime_message_get_recipients(CamelMimeMessage *message, const char *type);
 void camel_mime_message_set_recipients(CamelMimeMessage *message, const char *type, const CamelInternetAddress *recipients);

The 'source' is an evolution-specific header used to communicate information from backends to the filters. This should probably be done some other way; there is absolutely no reason to store this as a header.

 void camel_mime_message_set_source(CamelMimeMessage *message, const char *identity);
 const char *camel_mime_message_get_source(CamelMimeMessage *message);

And now we have some utility functions which may process a message or just interrogate it. They are helpers for various backends and client code to avoid having to rewrite it many times. See the API documentation for specific information on these.

 gboolean camel_mime_message_has_8bit_parts(CamelMimeMessage *message);
 void camel_mime_message_set_best_encoding(CamelMimeMessage *message, CamelBestencRequired required, CamelBestencEncoding enctype);
 void camel_mime_message_encode_8bit_parts(CamelMimeMessage *message);
 CamelMimePart *camel_mime_message_get_part_by_content_id(CamelMimeMessage *message, const char *content_id);
 char *camel_mime_message_build_mbox_from(CamelMimeMessage *message);

And finally a debugging function. This will dump the structure of the message, and optionally dump the content as well. This is very useful for debugging any functions which create their own message or part structures.

 void camel_mime_message_dump(CamelMimeMessage *msg, int body);

Cleanup ideas

The source api/mechanism needs altering.

It is possible that all of the accessors could be replaced with Camel.Object#Object Properties instead.

CamelMultipart

The CamelMultpart object is used to store most multipart/* types. Each part it stores will be a CamelMimePart, and they can be retrived by index, etc.

 struct _CamelMultipart
 {
        CamelDataWrapper parent_object;
 
        GList *parts;
        gchar *preface;
        gchar *postface;
 };

 CamelMultipart *camel_multipart_new(void);

Parts can be either appended to the end, or inserted at a specific location. This is overengineering, parts are never inserted at a specific point.

 void camel_multipart_add_part(CamelMultipart *multipart, CamelMimePart *part);
 void camel_multipart_add_part_at(CamelMultipart *multipart, CamelMimePart *part, guint index);

Parts can be removed by part, or by index. Again, an index remove is a convenience function that is unecessary (particularly since it is also a virtual method).

 void camel_multipart_remove_part(CamelMultipart *multipart, CamelMimePart *part);
 CamelMimePart * camel_multipart_remove_part_at(CamelMultipart *multipart, guint index);

Parts can be retrieved by index, and you can find out how many parts are present.

 guint camel_multipart_get_number(CamelMultipart *multipart);
 CamelMimePart * camel_multipart_get_part(CamelMultipart *multipart, guint index);

Multiparts have boundaries, they can be looked up, or when creating messages, set to something specific. Setting the boundary to NULL will create a new boundary using reliable rules to ensure unique boundaries. You should never set the boundary to anything other than NULL unless you are creating a message wrapper that builds messages directly from non-streamed data.

 void camel_multipart_set_boundary(CamelMultipart *multipart, const char *boundary);
 const char * camel_multipart_get_boundary(CamelMultipart *multipart);

Multipart messages may have additional non-multipart data before the multipart and after the multipart. By spec this data is unimportant to the parsing process, but can be added for humans. These interfaces let you access that data.

 void camel_multipart_set_preface(CamelMultipart *multipart, const char *preface);
 void camel_multipart_set_postface(CamelMultipart *multipart, const char *postface);

And finally we have a construct_from_parser method. CamelMultipart does not derive from CamelMimePart, so it cannot inherit the method from there.

 int camel_multipart_construct_from_parser(CamelMultipart *multipart, struct _CamelMimeParser *parser);

Cleanup ideas

This one interface prevents the CamelMimeMessage from being able to parse content on the fly. That is, because you need to ask for the number of parts before you query them, it needs to parse the entire message. If this was changed to be an iterator, it may be possible for the message to be parsed more incrementally. Whether this is useful or not depends on how the message is used. For message display, parts may need to be looked up by content-id, which means the whole message needs to be pre-parsed anyway. For many other operations, a complete parse is unecessary.

Some of the function names are inconsistent with other interfaces. i.e. get_number vs get_count.

Parts should probably be an array or a typed list rather than a GList.

Some of the part_at methods are overengineering, particularly being virtual methods. This could be cleaned up somewhat.

If Camel wants to support non-MIME messages, then parts of the functionality of CamelMultipart would belong in a CamelMIMEMultipart instead.

Camel.MultipartSigned

There is an unfortunate requirement of the extremely poorly designed RFC1847 that multipart/signed (and multipart/encrypted) content be treated as completely opaque, binary data, by any mail handling software.

It is clear the RFC was created in the first place after arguments with MTA designers about their right to transcode and reconfigure messages any way they saw fit in order to transport the message. So the PGP designers just said 'you can't do that to our parts'. Idiots.

Anyway, we're stuck with supporting this rubbish, so these objects are required.

They throw away all the normal multipart code, and replace it with something that stores the entire raw blob in memory separately, and allow the GPG code to properly separate out the parts for use and hopefully still be able to verify the content.

 enum {
        CAMEL_MULTIPART_SIGNED_CONTENT,
        CAMEL_MULTIPART_SIGNED_SIGNATURE,
 };
 
 CamelMultipartSigned *camel_multipart_signed_new(void);

Apart from this, they behave like a normal CamelMultipart object. However each multipart can only have 2 parts, the content part and the signature part.

Then there is a utility funciton used by the GPG code to get the content part in a format suitable for GPG. For messages built internally this will just be the raw data - which must already be canonicalised. For messages built from an external stream this will be canonicalised for line endings.

 CamelStream *camel_multipart_signed_get_content_stream(CamelMultipartSigned *mps, CamelException *ex);

Camel.MultipartEncrypted

A few of the arguments above also apply here - but fortunately an encrypted block is already converted into a textual format, so there can't be arguments about ill-defined content canonicalisation rules and signatures mis-matching because of different encoding types.

 enum {
        CAMEL_MULTIPART_ENCRYPTED_VERSION,
        CAMEL_MULTIPART_ENCRYPTED_CONTENT,
 };
 
 struct _CamelMultipartEncrypted {
        CamelMultipart parent_object;
        
        CamelMimePart *version;
        CamelMimePart *content;
        CamelMimePart *decrypted;
        
        char *protocol;
 };
 
 CamelMultipartEncrypted *camel_multipart_encrypted_new(void);

Since these are used internally, there are no accessors for things like protocol. There are enumerations supplied for accessing each of the two possible parts of a multipart/encrypted type however.

Creating messages

Creating messages can be a fairly long-winded process; it depends on how complex you want to get. Basic messages aren't all that complex though.

The basic steps are:

# Create a CamelMimeMessage object to hold everything. # Setup the CamelMimeMessage, with subject, date, from and to addresses at least. # Create a CamelDataWrapper, or CamelMultipart to add to the CamelMimeMessage. # Add the part to the message.

The message can then be used with message-accepting functions, written to a stream, etc.

Example: Creating a simple one part message

This example shows the absolute minimum required for creating a simple message.

        CamelMimeMessage *msg;
        CamelInternetAddress *addr;
        const char *text;
  
        msg = camel_mime_message_new();
        camel_mime_message_set_subject(msg, "Talking to myself again");
 
        addr = camel_internet_address_new();
        camel_internet_address_add(addr, "NotZed", "notzed@somewhere.com");
        camel_mime_message_set_from(msg, addr);
        camel_address_remove(addr, -1);
  
        camel_internet_address_add(addr, "NotZed", "notzed@somewhere.else.com");
        camel_mime_message_set_to(msg, addr);
        camel_object_unref(addr);
 
        text = "Hello NotZed,\nTalking to yourself again?\n\n  NotZed\n";
        camel_mime_part_set_content(msg, text, strlen(text), "text/plain");

If this is then written to a stream using camel_data_wrapper_write_to_stream, the following will be created:

 Subject: Talking to myself again
 From: NotZed &lt;notzed@somewhere.com&gt;
 To: NotZed &lt;notzed@somewhere.else.com&gt;
 Content-Type: text/plain
 Date: Fri, 16 Sep 2005 13:20:54 +0800
 Message-Id: &lt;1126848054.28946.0.camel@lostzed.mmc.com.au&gt;
 Mime-Version: 1.0
 
 Hello NotZed,
 Talking to yourself again?
 
   NotZed

Note that using <code>camel_mime_part_set_content</code> is only useful for making simple in-memory parts. If you want to create large parts from say disk files, it is easier to create the part content and set it yourself directly, rather than reading it into memory first which will just be copied anyway. The same goes if you wish to create a complex textual part in memory; it is easier to write to a Camel.Stream#Camel.StreamMem object and create a data wrapper from that.

To replace the <code>camel_mime_part_set_content</code> function above, with one created from a stream, you might do something like this:

        CamelDataWrapper *dw;
        CamelStream *stream;
 
        dw = camel_data_wrapper_new();
        camel_data_wrapper_set_mime_type(dw, "image/png");
 
        stream = camel_stream_fs_new_with_name("test.png", O_RDONLY, 0);
        camel_data_wrapper_construct_from_stream(dw, stream);
        camel_object_unref(stream);
 
        camel_medium_set_content_object(msg, dw);
        camel_object_unref(dw);
 
        camel_mime_part_set_filename(msg, "test.png");
        camel_mime_part_set_disposition(msg, "inline");

Even if you do not specify a content-encoding, the message should end up being properly formatted for whatever transport it is sent to; each backend will ensure this. But you would normally set known binary data to base64 just for consistency.

Example: Creating a simple multi-part message

Multipart messages aren't really any more complex, but they are more work.

        CamelMimeMessage *msg;
        CamelInternetAddress *addr;
        const char *text;
        CamelMultipart *mp;
        CamelMimePart *part;
 
        msg = camel_mime_message_new();
        camel_mime_message_set_subject(msg, "Talking to myself again");
 
        addr = camel_internet_address_new();
        camel_internet_address_add(addr, "NotZed", "notzed@somewhere.com");
        camel_mime_message_set_from(msg, addr);
        camel_address_remove(addr, -1);
  
        camel_internet_address_add(addr, "NotZed", "notzed@somewhere.else.com");
        camel_mime_message_set_recipients(msg, "To", addr);
        camel_object_unref(addr);
 
        mp = camel_multipart_new();
        camel_data_wrapper_set_mime_type(mp, "multipart/alternative");
        camel_multipart_set_boundary(mp, NULL);
        camel_multipart_set_preface(mp, "This is a message in MIME format.  Get with the decade\n");
        camel_multipart_set_postface(mp, "\nSome useless after-message text you should never see\n\n");
 
        part = camel_mime_part_new();
        text = "Hello NotZed,\nTalking to yourself again?\n\n  NotZed\n";
        camel_mime_part_set_content(part, text, strlen(text), "text/plain");
        camel_multipart_add_part(mp, part);
        camel_object_unref(part);
 
        part = camel_mime_part_new();
        text = "&lt;html&gt;\n&lt;body&gt;\nHello NotZed,&lt;br&gt;\nTalking to yourself again?&lt;br&gt;\n&amp;nbsp;&amp;nbsp;NotZed&lt;br&gt;\n&lt;/body&gt;\n&lt;/html&gt;\n";
        camel_mime_part_set_content(part, text, strlen(text), "text/html");
        camel_multipart_add_part(mp, part);
        camel_object_unref(part);
 
        camel_medium_set_content_object(msg, mp);

It is important that if you use camel_data_wrapper_set_mime_type on the multipart, then you need to set the boundary to NULL afterwards, so it generates one, otherwise it treats it as an incorrectly formed message.

This will produce the following message:

 Subject: Talking to myself again
 From: NotZed &lt;notzed@somewhere.com&gt;
 To: NotZed &lt;notzed@somewhere.else.com&gt;
 Content-Type: multipart/alternative; boundary="=-7zXIzBHdFsrgMwKt89/I"
 Date: Fri, 16 Sep 2005 13:38:01 +0800
 Message-Id: &lt;1126849081.29274.0.camel@lostzed.mmc.com.au&gt;
 Mime-Version: 1.0
 
 This is a message in MIME format.  Get with the decade
 
 --=-7zXIzBHdFsrgMwKt89/I
 Content-Type: text/plain
 
 Hello NotZed,
 Talking to yourself again?
 
   NotZed
 
 --=-7zXIzBHdFsrgMwKt89/I
 Content-Type: text/html
 
 &lt;html&gt;
 &lt;body&gt;
 Hello NotZed,&lt;br&gt;
 Talking to yourself again?&lt;br&gt;
 &amp;nbsp;&amp;nbsp;NotZed&lt;br&gt;
 &lt;/body&gt;
 &lt;/html&gt;
 
 --=-7zXIzBHdFsrgMwKt89/I--
 
 Some useless after-message text you should never see

So that's about it I guess, obviously parts can be mixed and matched in a multipart, you can have multiple levels of embedding, etc. There are RFC's which explain how the various subtypes of multipart should be created (which ones?), you can specify character sets for textual data, although for the most part things are pretty automatic.

One last not-obvious thing is that if you're attaching a CamelMimeMessage to a CamelMimeMessage, then you need to embed it inside of a CamelMimePart first (I think?).

Reading messages

Creating a structured object from a raw message stream is extremely easy.

 stream = camel_stream_fs_new_with_name("/path/to/message/file", O_RDONLY, 0);
 msg = camel_mime_message_new();
 if (camel_data_wrapper_construct_from_stream(msg, stream) == 0) {
        // message creation ok ...

There is also support for reading Berkeley Mailbox files directly - see CamelMimeParser.

Using messages

Another reason to go to all of this trouble is to be able to access message content programatically, and this isn't particularly difficult. The main issue is trying to deal with al the various cases of embedded types - but there are only 3 cases to consider:

# The part contains a message # The part contains a multipart # The part contains simple data (a leaf node)

The following code snippet can be used to recursively scan an entire message structure:

 scan_message(CamelMimeMessage *msg, CamelMimePart *part)
 {
        CamelDataWrapper *dw;
 
        dw = camel_medium_get_content_object(part);
        if (dw == NULL)
                return;
 
        if (CAMEL_IS_MIME_MESSAGE(dw)) {
                scan_message(msg, dw);
        } else if (CAMEL_IS_MULTIPART(dw)) {
                int parts, i;
 
                parts = camel_multipart_get_number(dw);
                for (i=0;i<parts;i++)
                        scan_message(msg, camel_multipart_get_part(dw, i));
        } else {
                look at leaf part, and do something with it;
        }
 }

To kick it off, pass the message to the the function as both arguments.

At each leaf object, you can query it's data-type, perform various processing on it, etc. The rules for displaying messages can get quite hairy - that is what a sizeable part of Evolution Mail exists to do afterall, so see that code for examples.

Apps/Evolution/Camel.DataWrapper (last edited 2013-08-08 22:50:09 by WilliamJonMcCann)