diff options
Diffstat (limited to 'mimelib/Tutorial')
-rw-r--r-- | mimelib/Tutorial | 437 |
1 files changed, 437 insertions, 0 deletions
diff --git a/mimelib/Tutorial b/mimelib/Tutorial new file mode 100644 index 000000000..358fab7e5 --- /dev/null +++ b/mimelib/Tutorial @@ -0,0 +1,437 @@ + + + T U T O R I A L F O R M I M E + + + +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++ implementation, 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 (aka 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 functions 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(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; + DwMailbox* mb = from.FirstMailbox(); + while (mb) { + if (isFirst) { + isFirst = 0; + } + else { + 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'; + } + mb = mb->Next(); + } + + } + + // 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 preferred email is +dwsauder@fwb.gulf.net, but dwsauder@tasc.com is also acceptable. + +Good luck! + |