
Tutorial for MIME++

  1. Introduction
  
   Welcome to MIME++, a C++ class library for creating, parsing, and
   modifying messages in MIME format. MIME++ has been designed
   specifically with the following objectives in mind:

     * Create classes that directly correspond to the elements described
       in RFC-822, RFC-2045, and other MIME-related documents.
     * Create a library that is easy to use.
     * Create a library that is extensible.
       
   MIME++ classes directly model the elements of the BNF grammar
   specified in RFC-822, RFC-2045, and RFC-2046. For this reason, I
   recommend that you understand these RFCs and keep a copy of them handy
   as you learn MIME++. If you know C++ well, and if you are familiar
   with the RFCs, you should find MIME++ easy to learn and use. If you
   are new to C++ and object-oriented programming, you will find in
   MIME++ some very good object-oriented techinques, and hopefully you
   will learn a lot.
   
   Before looking at the MIME++ classes, it is important to understand
   how MIME++ represents a message. There are two representations of a
   message. The first is a string representation, in which a message is
   considered simply a sequence of characters. The second is a
   'broken-down' -- that is, parsed -- representation, in which the
   message is represented as a tree of components.
   
   The tree will be explained later, but for now, let's consider the
   relationship between the string representation and the broken-down
   representation. When you create a new message, the string
   representation is initially empty. After you set the contents of the
   broken-down representation, such as the header fields and the message
   body, you then assemble the message from its broken-down
   representation into its string representation. The assembling is done
   through a call to the Assemble() member function of the DwMessage
   class. Conversely, when you receive a message, it is received in its
   string representation, and you parse the message to create its
   broken-down representation. The parsing is done through a call to the
   Parse() member function of the DwMessage class. From the broken-down
   representation, you can access the header fields, the body, and so on.
   If you want to modify a received message, you can change the contents
   of the broken-down representation, then assemble the message to create
   the modified string representation. Because of the way MIME++
   implements the broken-down representation, only those specific
   components that were modified in the broken-down representation will
   be modified in the new string representation.
   
   The broken-down representation takes the form of a tree. The idea for
   the tree comes from the idea that a message can be broken down into
   various components, and that the components form a hierarchy. At the
   highest level, we have the complete message. We can break the message
   down into a header and a body to arrive at the second-highest level.
   We can break the header down into a collection of header fields. We
   can break each header field down into a field-name and a field-body.
   If the header field is a structured field, we can further break down
   its field-body into components specific to that field-body, such as a
   local-part and domain for a mailbox. Now, we can think of each
   component of the message as a node in the tree. The top, or root, node
   is the message itself. Below that, the message node contains child
   nodes for the header and body; the header node contains a child node
   for each header field; and so on. Each node contains a substring of
   the entire message, and a node's string is the concatenation of all of
   its child nodes' strings.
   
   In the MIME++ architecture, the abstract base class DwMessageComponent
   encapsulates all the common attributes and behavior of the tree's
   nodes. The most important member functions of DwMessageComponent are
   Parse() and Assemble(), which are declared as pure virtual functions.
   Normally, you would use these member functions only as operations on
   objects of the class DwMessage, a subclass of DwMessageComponent.
   Parse() builds the entire tree of components with the DwMessage object
   at the root. Assemble() builds the string representation of the
   DwMessage object by traversing the tree and concatenating the strings
   of the leaf nodes. While every node in the tree is a
   DwMessageComponent, and therefore has a Parse() and Assemble() member
   function, you do not have to call these member functions for every
   node in the tree. The reason is that both of these functions traverse
   the subtree rooted at the current node. Parse() acts first on the
   current node, then calls the Parse() member function of its child
   nodes. Assemble() first calls the Assemble() member functions of a
   node's child nodes, then concatenates the string representations of
   its child nodes. Therefore, when you call Parse() or Assemble() for an
   object of the class DwMessage, Parse() or Assemble() will be called
   automatically for every component (that is, child node) in the
   message.
   
   DwMessageComponent also has one important attribute that you should be
   aware of. That attribute is an is-modified flag (also known as a dirty
   flag), which is cleared whenever Parse() or Assemble() is called, and
   is set whenever the broken-down representation is modified. To
   understand how this works, suppose you have just called Parse() on a
   DwMessage object to create its broken-down representation. If you add
   a new DwField object (representing a new header field) to the
   DwHeaders object (representing the header), the is-modified flag will
   be set for the DwHeaders object, indicating that the string
   representation of the DwHeaders object will have to be re-assembled
   from the header fields that it contains. When a node's is-modified
   flag is set, it also notifies its parent node to set its is-modified
   flag. Thus, when the DwHeaders object's is-modified flag is set, the
   DwMessage object that is its parent will also have its is-modified
   flag set. That way, when Assemble() is called for the DwMessage
   object, it will call the Assemble() member function for the DwHeaders
   object, as required. Notice that the value of having an is-modified
   flag is that it can purge the tree traversal when the string
   representation of a message is being assembled.
   
   One of the first classes you should become familiar with is the
   DwString class, which handles character strings in MIME++. DwString
   has been designed to handle very large character strings, so it may be
   different from string classes in other libraries. Most of the standard
   C library string fuctions have DwString counterparts in MIME++. These
   functions all start with "Dw", and include DwStrcpy(), DwStrcmp(),
   DwStrcasecmp(), and so on. In addition, the equality operators and
   assignment operators work as expected. If you have used string classes
   from other libraries, you will find DwString fairly intuitive.
   
   The following sections describe how to create, parse, and modify a
   message. You should also look at the example programs included with
   the distribution. These example programs are well-commented and use
   wrapper classes. The wrapper classes BasicMessage, MultipartMessage,
   and MessageWithAttachments, are designed with three purposes in mind.
   First, if your requirements are very modest -- say you just want to
   send a few files as attachments -- then you may find these classes to
   be adequate for your needs, and you will not have to learn the MIME++
   library classes. Second, wrapper classes are the recommended way to
   use MIME++. You should consider starting with these classes and
   customizing them for your own application. Using wrapper classes will
   simplify the use of the MIME++ library, but will also help to shield
   your application from future changes in the MIME++ library. Third,
   these classes provide excellent examples for how to use the MIME++
   library classes.
   
   The rest of this tutorial focuses on the library classes themselves.
   
  2. Creating a Message
  
   Creating a message with MIME++ involves instantiating a DwMessage
   object, setting values for its parts, and assembling the message into
   its final string representation. The following simple example shows
   how to accomplish this.
   
    void SendMessage(
        const char* aTo,
        const char* aFrom,
        const char* aSubject,
        const char* aBody)
    {
        // Create an empty message

        DwMessage msg;

        // Set the header fields.
        // [ Note that a temporary DwString object is created for
        // the argument for FromString() using the
        // DwString::DwString(const char*) constructor. ]

        DwHeaders& headers = msg.Headers();
        headers.MessageId().CreateDefault();
        headers.Date().FromCalendarTime(time(NULL)); //current date, time
        headers.To().FromString(aTo);
        headers.From().FromString(aFrom);
        headers.Subject().FromString(aSubject);

        // Set the message body

        msg.Body().FromString(aBody);

        // Assemble the message from its parts

        msg.Assemble();

        // Finally, send it.  In this example, just print it to the
        // cout stream.

        cout << msg.AsString();
    }

   In this example, we set the fields 'Message-Id', 'Date', 'To', 'From',
   and 'Subject', which are all documented in RFC-822. The MIME++ class
   DwHeaders directly supports all header fields documented in RFC-822,
   RFC-2045, and RFC-1036. To access the field-body for any one these
   fields, use the member function from DwHeaders that has a name
   corresponding to the field-name for that field. The correspondence
   between a field-name and the name of the member function in DwHeaders
   is consistent: hyphens are dropped and the first character after the
   hyphen is capitalized. Thus, field-name Content-type in RFC-1521
   corresponds to the member function name ContentType(). These
   field-body access functions create an empty field in the headers if
   that field does not already exist. To check if a particular field
   exists already, DwHeaders provides member functions HasXxxxx(); for
   example, HasSender(), HasMimeVersion(), or HasXref() will indicate
   whether the DwHeaders object has a 'Sender' field, a 'MIME-Version'
   field, or an 'Xref' field, respectively.
   
   In the example, we used the FromString() member function of
   DwMessageComponent to set the string representation of the
   field-bodies. This is the simplest way to set the contents of a
   DwFieldBody object. Many of the field-bodies also have a broken-down
   represenation, and it is possible to set the parts of the broken-down
   representation. Consider, for example, the DwDateTime class, which
   represents the date-time element of the BNF grammar specified in
   RFC-822. In the example above, we did not set the string
   representation -- that would be more difficult and error prone.
   Instead we set the contents from the time_t value returned from a call
   to the ANSI C function time(). The DwDateTime class also contains
   member functions for setting individual attributes. For example, we
   could have used the following code:
   
    DwDateTime& date = msg.Headers().Date();
    time_t t = time(NULL);
    struct tm stm = *localtime(&t);
    date.SetYear(1900+stm.tm_year);
    date.SetMonth(stm.tm_mon);
    date.SetDay(stm.tm_mday);
    date.SetHour(stm.tm_hour);
    date.SetMinute(stm.tm_min);

  3. Parsing a Message
  
   Parsing a received message with MIME++ involves instantiating a
   DwMessage object, setting its string representation to contain the
   message, and then calling the Parse() member function of the DwMessage
   object. The following simple example shows how to accomplish this.
   
    void ParseMessage(DwString& aMessageStr)
    {
        // Create a message object
        // We can set the message's string representation directly from the
        // constructor, as in the uncommented version.  Or, we can use the
        // default constructor and set its string representation using
        // the member function DwMessage::FromString(), as in the
        // commented version.

        DwMessage msg(aMessageStr);

        // Alternate technique:
        // DwMessage msg;                 // Default constructor
        // msg.FromString(aMessageStr);   // Set its string representation

        // Execute the parse method, which will create the broken-down
        // representation (the tree representation, if you recall)

        msg.Parse();

        // Print some of the header fields, just to show how it's done

        // Date field.  First check if the field exists, since
        // DwHeaders::Date() will create it if is not found.

        if (msg.Headers().HasDate()) {
            cout << "Date of message is "
                << msg.Headers().Date().AsString()
                << '\n';
        }

        // From field.  Here we access the broken-down field body, too,
        // to get the full name (which may be empty), the local part,
        // and the domain of the first mailbox.  (The 'From' field can
        // have a list of mailboxes).

        if (msg.Headers().HasFrom()) {
            DwMailboxList& from = msg.Headers().From();
            cout << "Message is from ";

            // Get first mailbox, then iterate through the list

            int isFirst = 1;
            int numMailboxes = from.NumMailboxes();
            for (int i=0; i < numMailboxes; ++i) {
                DwMailbox& mb = from.MailboxAt(i);
                if (i > 0) {
                    cout << ", ";
                }
                DwString& fullName = mb.FullName();
                if (fullName != "") {
                    cout << fullName << '\n';
                }
                else {
                    // Apparently, there is no full name, so use the email
                    // address
                    cout << mb.LocalPart() << '@' << mb.Domain() << '\n';
                }
            }
        }

        // Finally, print the message body, just to show how the body is
        // retrieved.

        cout << msg.Body().AsString() << '\n';
    }

   Once you have parsed the message, you can access any of its parts. The
   field-bodies of well-known header fields can be accessed by calling
   member functions of DwHeaders. Some examples follow.
   
    DwMediaType& contType = msg.Headers().ContentType();
    DwMechanism& cte = msg.Headers().ContentTransferEncoding();
    DwDateTime& date = msg.Headers().Date();

   The various subclasses of DwFieldBody, including DwMediaType,
   DwMechanism, and DwDateTime above, have member functions that allow
   you to access the parts of the field-body. For example, DwMediaType
   has member functions to allow you to access its type, subtype, and
   parameters. If the message is a multipart message, you may access the
   body parts by calling member functions of the class DwBody. See the
   example code in multipar.cpp for an example of how to do this.
   
  4. Modifying a Message
  
   Modifying a message combines the procedures of parsing a message and
   creating a message. First, parse the message, as explained above. Then
   set the values of the components -- field-bodies, new fields, new body
   parts, or what have you -- that you wish to modify. Finally, call the
   Assemble() member function of the DwMessage object to reassemble the
   message. You can then access the modified message by calling
   DwMessage::AsString(). These final steps are the same as those
   involved in creating a new message.
   
  5. Customizing MIME++ Classes
  
   MIME++ has been designed to be easily customizable. Typically, you
   customize C++ library classes through inheritance. MIME++ allows you
   to create subclasses of most of its library classes in order to change
   their behavior. MIME++ also includes certain 'hooks', which make it
   far easier to customize certain parts of the library.
   
   The most common customization is that of changing the way header
   fields are dealt with. This could include adding the ability to handle
   certain non-standard header fields, or to change the way the
   field-bodies of certain standard header fields are interpreted or
   parsed. As an example of the former customization, you may want to add
   the 'X-status' field or 'X-sender' field to your messages. As an
   example of the latter, you may want to change DwMediaType so that it
   will handle other MIME subtypes.
   
   Let's begin with the latter situation -- that of subclassing
   DwMediaType. Obviously, you will have to become familiar with
   DwMediaType and its superclasses before you change its behavior. Then,
   at a minimum, you will want to provide your own implementation of the
   virtual member functions Parse() and Assemble(). Once you feel
   comfortable with the behavior of the behavior of your new class --
   call it MyMediaType -- you will have to take the right steps to ensure
   that the MIME++ library internal routines will create objects of type
   MyMediaType, and not DwMediaType. There are three such steps.
   
   First, define a function NewMyMediaType(), matching the prototype

    DwMediaType* NewMyMediaType(
        const DwString& aStr,
        DwMessage* aParent)

   that creates a new instance of MyMediaType and returns it. Set the
   static data member DwMediaType::sNewMediaType to point to this
   function. DwMediaType::sNewMediaType is normally NULL, meaning that no
   user-defined function is available. When you set this static data
   member, however, MIME++'s internal routines will call your own
   function, and will therefore be able to create instances of your
   subclass.
   
   Second, make sure you have reimplemented the virtual function
   DwMediaType::Clone() to return a clone of your own subclassed object.
   Clone() serves as a 'virtual constructor'. (See the discussion of
   virtual constructors in Stroustrup's _The C++ Programming Language_,
   2nd Ed).
   
   Third, you should define a function CreateFieldBody(), matching the
   prototype

    DwFieldBody* CreateFieldBody(
        const DwString& aFieldName,
        const DwString& aFieldBody,
        DwMessageComponent* aParent)

   that returns an object of a subclass of DwFieldBody. (DwFieldBody is a
   superclass of MyMediaType). CreateFieldBody() is similar to the
   NewMyMediaType() function already described, except that its first
   argument supplies the field-name for the particular field currently
   being handled by MIME++. CreateFieldBody() should examine the
   field-name, create an object of the appropriate subclass of
   DwFieldBody, and return a pointer to the object. In this particular
   case, you need to make sure that when the field-name is 'Content-Type'
   you return an object of the class MyMediaType. Set the hook for
   CreateFieldBody() setting the static data member
   DwField::sCreateFieldBody to point to your CreateFieldBody() function.
   DwField::sCreateFieldBody is normally NULL when no user function is
   provided.
   
   These three steps are sufficient to ensure that your subclass of
   DwMediaType is integrated with the other MIME++ classes.
   
   The other customization task mentioned above is that of adding support
   for a non-standard header field. There is a simple way to do this, and
   a way that involves creating a subclass of DwHeaders. You can access
   any header field by calling DwHeaders's member functions. In fact, you
   can iterate over all the header fields if you would like. Therefore,
   the really simple way is just to not change anything and just use
   existing member functions. The relevant functions include
   DwHeaders::HasField(), which will return a boolean value indicating if
   the header has the specified field, and DwHeaders::FieldBody(), which
   will return the DwFieldBody object associated with a specified field.
   [ Note that DwHeaders::FieldBody() will create a field if it is not
   found. ] The default DwFieldBody subclass, which applies to all header
   fields not recognized by MIME++, is DwText, which is suitable for the
   unstructured field-bodies described in RFC-822 such as 'Subject',
   'Comments', and so on. If a DwText object is suitable for your
   non-standard header field, then you don't have to do anything at all.
   Suppose, however, that you want an object of your own subclass of
   DwFieldBody, say StatusFieldBody, to be attached to the 'X-status'
   field. In this case, you will need to set the hook
   DwField::sCreateFieldBody as discussed above. Your CreateFieldBody()
   function should return an instance of StatusFieldBody whenever the
   field-name is 'X-status'.
   
   Finally, while you can access any header field using DwHeaders's
   member functions, you may want to create your own subclass of
   DwHeaders for some reason or other -- maybe to add a convenience
   function to access the 'X-status' header field. To ensure that your
   new class is integrated with the library routines, you basically
   follow steps 1 and 2 above for subclassing DwFieldBody. First, define
   a function NewMyHeaders() and set the static data member
   DwHeaders::sNewHeaders to point to your function. Second, make sure
   you have reimplemented the virtual function DwHeaders::Clone() to
   return an instance of your subclass. Step 3 for subclassing
   DwFieldBody does not apply when subclassing DwHeaders.
   
  6. Getting Help
  
   I will try to help anyone who needs help specific to MIME++. I won't
   try to answer general questions about C++ that could be answered by
   any C++ expert. Bug reports will receive the highest priority. Other
   questions about how to do something I will try to answer in time, but
   I ask for your patience. If you have any comments -- perhaps maybe you
   know of a better way to do something -- please send them. My email
   address is dwsauder@hunnysoft.com.
   
   Good luck!
